[
  {
    "path": ".dockerignore",
    "content": "*\n\n"
  },
  {
    "path": ".editorconfig",
    "content": "[*]\nindent_style = space\nindent_size = 2\ntrim_trailing_whitespace = true\ninsert_final_newline = true\nend_of_line = lf\n"
  },
  {
    "path": ".gitattributes",
    "content": "* text=auto\n**/test/fixtures/** text eol=lf\n**/test/gateway/** text eol=lf\n**/src/init-files/** text eol=lf\n\n*.data binary\n*.png binary\n*.jpg binary\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n - name: Getting Help on IPFS\n   url: https://ipfs.io/help\n   about: All information about how and where to get help on IPFS\n - name: IPFS Official Forum\n   url: https://discuss.ipfs.io\n   about: For general questions, support requests and discussions\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/open_an_issue.md",
    "content": "---\nname: Open an issue\nabout: For reporting bugs or errors in the JavaScript IPFS implementation\ntitle: ''\nlabels: need/triage\nassignees: ''\n---\n\n<!--\nThank you for reporting an issue.\n\nThis issue tracker is for bugs found within the JavaScript implementation of IPFS.\n\nIf you are asking a question about how to use IPFS, please ask on https://discuss.ipfs.io\n\nOtherwise please fill in as much of the template below as possible.\n-->\n\n- **Version**:\n<!--\nOutput of `jsipfs version --all` if using the CLI or `await ipfs.version()` if using the instance\n-->\n\n- **Platform**:\n<!--\nOutput of `uname -a` (UNIX), or version and 32 or 64-bit (Windows). If using in a Browser, please share the browser version as well\n-->\n\n- **Subsystem**:\n<!--\nIf known, please specify affected core module name (e.g Bitswap, libp2p, etc)\n-->\n\n#### Severity:\n<!--\nOne of following:\n  Critical - System crash, application panic.\n  High - The main functionality of the application does not work, API breakage, repo format breakage, etc.\n  Medium - A non-essential functionality does not work, performance issues, etc.\n  Low - An optional functionality does not work.\n  Very Low - Translation or documentation mistake. Something that won't give anyone a bad day.\n-->\n\n#### Description:\n<!--\n- What you did\n- What happened\n- What you expected to happen\n-->\n\n#### Steps to reproduce the error:\n<!--\nIf possible, please provide code that demonstrates the problem, keeping it as simple and free of external dependencies as you are able\n-->\n\n"
  },
  {
    "path": ".github/config.yml",
    "content": "# Configuration for welcome - https://github.com/behaviorbot/welcome\n\n# Configuration for new-issue-welcome - https://github.com/behaviorbot/new-issue-welcome\n# Comment to be posted to on first time issues\nnewIssueWelcomeComment: >\n  Thank you for submitting your first issue to this repository! A maintainer\n  will be here shortly to triage and review.\n\n  In the meantime, please double-check that you have provided all the\n  necessary information to make this process easy! Any information that can\n  help save additional round trips is useful! We currently aim to give\n  initial feedback within **two business days**. If this does not happen, feel\n  free to leave a comment.\n\n  Please keep an eye on how this issue will be labeled, as labels give an\n  overview of priorities, assignments and additional actions requested by the\n  maintainers:\n\n    - \"Priority\" labels will show how urgent this is for the team.\n    - \"Status\" labels will show if this is ready to be worked on, blocked, or in progress.\n    - \"Need\" labels will indicate if additional input or analysis is required.\n\n  Finally, remember to use https://discuss.ipfs.io if you just need general\n  support.\n\n# Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome\n# Comment to be posted to on PRs from first time contributors in your repository\nnewPRWelcomeComment: >\n  Thank you for submitting this PR!\n\n  A maintainer will be here shortly to review it.\n\n  We are super grateful, but we are also overloaded! Help us by making sure\n  that:\n\n    * The context for this PR is clear, with relevant discussion, decisions\n      and stakeholders linked/mentioned.\n\n    * Your contribution itself is clear (code comments, self-review for the\n      rest) and in its best form. Follow the [code contribution\n      guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md#code-contribution-guidelines)\n      if they apply.\n\n  Getting other community members to do a review would be great help too on\n  complex PRs (you can ask in the chats/forums). If you are unsure about\n  something, just leave us a comment.\n\n  Next steps:\n\n    * A maintainer will triage and assign priority to this PR, commenting on\n      any missing things and potentially assigning a reviewer for high\n      priority items.\n\n    * The PR gets reviews, discussed and approvals as needed.\n\n    * The PR is merged by maintainers when it has been approved and comments addressed.\n\n  We currently aim to provide initial feedback/triaging within **two business\n  days**. Please keep an eye on any labelling actions, as these will indicate\n  priorities and status of your contribution.\n\n  We are very grateful for your contribution!\n\n\n# Configuration for first-pr-merge - https://github.com/behaviorbot/first-pr-merge\n# Comment to be posted to on pull requests merged by a first time user\n# Currently disabled\n#firstPRMergeComment: \"\"\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n- package-ecosystem: npm\n  directory: \"/\"\n  schedule:\n    interval: daily\n    time: \"10:00\"\n  open-pull-requests-limit: 0\n  commit-message:\n    prefix: \"deps\"\n    prefix-development: \"deps(dev)\"\n"
  },
  {
    "path": ".github/workflows/examples.yml",
    "content": "name: Examples\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    branches:\n      - '**'\n\njobs:\n\n  build:\n    name: Build\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - uses: actions/setup-node@v2\n        with:\n          node-version: lts/*\n      - uses: ipfs/aegir/actions/cache-node-modules@master\n\n  # test-examples:\n  #   name: Test example ${{ matrix.example.name }}\n  #   needs: build\n  #   runs-on: ubuntu-latest\n  #   continue-on-error: true\n  #   strategy:\n  #     matrix:\n  #       example:\n  #         - name: ipfs browser add readable stream\n  #           repo: https://github.com/ipfs-examples/js-ipfs-browser-add-readable-stream.git\n  #           deps: ipfs-core@$PWD/packages/ipfs-core\n  #         - name: ipfs browser angular\n  #           repo: https://github.com/ipfs-examples/js-ipfs-browser-angular.git\n  #           deps: ipfs-core@$PWD/packages/ipfs-core,ipfs-core-types@$PWD/packages/ipfs-core-types\n  #         - name: ipfs browser browserify\n  #           repo: https://github.com/ipfs-examples/js-ipfs-browser-browserify.git\n  #           deps: ipfs-core@$PWD/packages/ipfs-core\n  #         - name: ipfs browser react\n  #           repo: https://github.com/ipfs-examples/js-ipfs-browser-create-react-app.git\n  #           deps: ipfs-core@$PWD/packages/ipfs-core\n  #         - name: ipfs browser exchange files\n  #           repo: https://github.com/ipfs-examples/js-ipfs-browser-exchange-files.git\n  #           deps: ipfs-core@$PWD/packages/ipfs-core,ipfs@$PWD/packages/ipfs,ipfs-core-types@$PWD/packages/ipfs-core-types,ipfs-http-client@$PWD/packages/ipfs-http-client\n  #         - name: ipfs browser ipns publish\n  #           repo: https://github.com/ipfs-examples/js-ipfs-browser-ipns-publish.git\n  #           deps: ipfs-core@$PWD/packages/ipfs-core,ipfs-http-client@$PWD/packages/ipfs-http-client\n  #         - name: ipfs browser mfs\n  #           repo: https://github.com/ipfs-examples/js-ipfs-browser-mfs.git\n  #           deps: ipfs-core@$PWD/packages/ipfs-core\n  #         # fails with No native build was found for platform=darwin arch=x64 runtime=node abi=93 uv=1 libc=glibc node=16.13.0 webpack=true\n  #         #- name: ipfs browser nextjs\n  #         #  repo: https://github.com/ipfs-examples/js-ipfs-browser-nextjs.git\n  #         #  deps: ipfs-core@$PWD/packages/ipfs-core\n  #         - name: ipfs browser parceljs\n  #           repo: https://github.com/ipfs-examples/js-ipfs-browser-parceljs.git\n  #           deps: ipfs-core@$PWD/packages/ipfs-core\n  #         - name: ipfs browser readable stream\n  #           repo: https://github.com/ipfs-examples/js-ipfs-browser-readablestream.git\n  #           deps: ipfs-core@$PWD/packages/ipfs-core\n  #         - name: ipfs browser service worker\n  #           repo: https://github.com/ipfs-examples/js-ipfs-browser-service-worker.git\n  #           deps: ipfs-core@$PWD/packages/ipfs-core,ipfs-message-port-client@$PWD/packages/ipfs-message-port-client,ipfs-message-port-protocol@$PWD/packages/ipfs-message-port-protocol,ipfs-message-port-server@$PWD/packages/ipfs-message-port-server\n  #         - name: ipfs browser sharing across tabs\n  #           repo: https://github.com/ipfs-examples/js-ipfs-browser-sharing-node-across-tabs.git\n  #           deps: ipfs-core@$PWD/packages/ipfs-core,ipfs-message-port-client@$PWD/packages/ipfs-message-port-client,ipfs-message-port-server@$PWD/packages/ipfs-message-port-server\n  #         - name: ipfs browser video streaming\n  #           repo: https://github.com/ipfs-examples/js-ipfs-browser-video-streaming.git\n  #           deps: ipfs-core@$PWD/packages/ipfs-core\n  #         - name: ipfs browser vue\n  #           repo: https://github.com/ipfs-examples/js-ipfs-browser-vue.git\n  #           deps: ipfs-core@$PWD/packages/ipfs-core\n  #         - name: ipfs browser webpack\n  #           repo: https://github.com/ipfs-examples/js-ipfs-browser-webpack.git\n  #           deps: ipfs-core@$PWD/packages/ipfs-core\n  #         - name: ipfs circuit relaying\n  #           repo: https://github.com/ipfs-examples/js-ipfs-circuit-relaying.git\n  #           deps: ipfs-core@$PWD/packages/ipfs-core,ipfs-http-client@$PWD/packages/ipfs-http-client\n  #         - name: ipfs custom ipfs repo\n  #           repo: https://github.com/ipfs-examples/js-ipfs-custom-ipfs-repo.git\n  #           deps: ipfs-core@$PWD/packages/ipfs-core\n  #         - name: ipfs custom ipld formats\n  #           repo: https://github.com/ipfs-examples/js-ipfs-custom-ipld-formats.git\n  #           deps: ipfs-core@$PWD/packages/ipfs-core,ipfs-daemon@$PWD/packages/ipfs-daemon,ipfs-http-client@$PWD/packages/ipfs-http-client\n  #         - name: ipfs custom libp2p\n  #           repo: https://github.com/ipfs-examples/js-ipfs-custom-libp2p.git\n  #           deps: ipfs-core@$PWD/packages/ipfs-core\n  #         - name: ipfs-http-client browser pubsub\n  #           repo: https://github.com/ipfs-examples/js-ipfs-http-client-browser-pubsub.git\n  #           deps: ipfs-http-client@$PWD/packages/ipfs-http-client,ipfs@$PWD/packages/ipfs\n  #         - name: ipfs-http-client bundle webpack\n  #           repo: https://github.com/ipfs-examples/js-ipfs-http-client-bundle-webpack.git\n  #           deps: ipfs-http-client@$PWD/packages/ipfs-http-client,ipfs@$PWD/packages/ipfs\n  #         - name: ipfs-http-client name api\n  #           repo: https://github.com/ipfs-examples/js-ipfs-http-client-name-api.git\n  #           deps: ipfs-http-client@$PWD/packages/ipfs-http-client\n  #         - name: ipfs-http-client upload file\n  #           repo: https://github.com/ipfs-examples/js-ipfs-http-client-upload-file.git\n  #           deps: ipfs@$PWD/packages/ipfs,ipfs-http-client@$PWD/packages/ipfs-http-client\n  #         - name: ipfs 101\n  #           repo: https://github.com/ipfs-examples/js-ipfs-101.git\n  #           deps: ipfs-core@$PWD/packages/ipfs-core\n  #         - name: ipfs-client add files\n  #           repo: https://github.com/ipfs-examples/js-ipfs-ipfs-client-add-files.git\n  #           deps: ipfs@$PWD/packages/ipfs,ipfs-client@$PWD/packages/ipfs-client\n  #         - name: ipfs electron js\n  #           repo: https://github.com/ipfs-examples/js-ipfs-run-in-electron.git\n  #           deps: ipfs-core@$PWD/packages/ipfs-core\n  #         - name: ipfs running multiple nodes\n  #           repo: https://github.com/ipfs-examples/js-ipfs-running-multiple-nodes.git\n  #           deps: ipfs@$PWD/packages/ipfs\n  #         - name: ipfs traverse ipld graphs\n  #           repo: https://github.com/ipfs-examples/js-ipfs-traverse-ipld-graphs.git\n  #           deps: ipfs-core@$PWD/packages/ipfs-core\n  #         - name: types with typescript\n  #           repo: https://github.com/ipfs-examples/js-ipfs-types-use-ipfs-from-ts.git\n  #           deps: ipfs-core@$PWD/packages/ipfs-core\n  #         - name: types with typed js\n  #           repo: https://github.com/ipfs-examples/js-ipfs-types-use-ipfs-from-typed-js.git\n  #           deps: ipfs-core@$PWD/packages/ipfs-core\n  #   steps:\n  #     - uses: actions/checkout@v2\n  #     - uses: actions/setup-node@v2\n  #       with:\n  #         node-version: lts/*\n  #     - uses: ipfs/aegir/actions/cache-node-modules@master\n  #     - uses: GabrielBB/xvfb-action@v1\n  #       name: Run npm run test:external -- -- -- ${{ matrix.example.repo }} --deps ${{ matrix.example.deps }}\n  #       with:\n  #         run: npm run test:external -- -- -- ${{ matrix.example.repo }} --deps ${{ matrix.example.deps }}\n"
  },
  {
    "path": ".github/workflows/externals.yml",
    "content": "name: Externals\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    branches:\n      - '**'\n\njobs:\n\n  build:\n    name: Build\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - uses: actions/setup-node@v2\n        with:\n          node-version: lts/*\n      - uses: ipfs/aegir/actions/cache-node-modules@master\n\n  test-externals:\n    name: Test external ${{ matrix.external.name }}\n    needs: build\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        external:\n          - name: ipfs webui\n            repo: https://github.com/ipfs-shipyard/ipfs-webui.git\n            deps: ipfs@$PWD/packages/ipfs\n          - name: ipfs companion\n            repo: https://github.com/ipfs-shipyard/ipfs-companion.git\n            deps: ipfs@$PWD/packages/ipfs\n          - name: orbit-db-io\n            repo: https://github.com/orbitdb/orbit-db-io.git\n            deps: ipfs@$PWD/packages/ipfs\n          - name: ipfs-log\n            repo: https://github.com/orbitdb/ipfs-log.git\n            deps: ipfs@$PWD/packages/ipfs,orbit-db-io@next\n    steps:\n      - uses: actions/checkout@v2\n      - uses: actions/setup-node@v2\n        with:\n          node-version: lts/*\n      - uses: ipfs/aegir/actions/cache-node-modules@master\n      - uses: GabrielBB/xvfb-action@v1\n        name: Run npm run test:external -- -- -- ${{ matrix.external.repo }} --deps ${{ matrix.external.deps }} --branch ${{ matrix.external.branch }}\n        continue-on-error: true\n        with:\n          run: npm run test:external -- -- -- ${{ matrix.external.repo }} --deps ${{ matrix.external.deps }} --branch ${{ matrix.external.branch }}\n"
  },
  {
    "path": ".github/workflows/stale.yml",
    "content": "name: Close and mark stale issue\n\non:\n  schedule:\n  - cron: '0 0 * * *'\n\njobs:\n  stale:\n\n    runs-on: ubuntu-latest\n    permissions:\n      issues: write\n      pull-requests: write\n\n    steps:\n    - uses: actions/stale@v3\n      with:\n        repo-token: ${{ secrets.GITHUB_TOKEN }}\n        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.'\n        close-issue-message: 'This issue was closed because it is missing author input.'\n        stale-issue-label: 'kind/stale'\n        any-of-labels: 'need/author-input'\n        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'\n        days-before-issue-stale: 6\n        days-before-issue-close: 7\n        enable-statistics: true\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Test\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    branches:\n      - '**'\n\njobs:\n\n  build:\n    name: Build\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - uses: actions/setup-node@v2\n        with:\n          node-version: lts/*\n      - uses: ipfs/aegir/actions/cache-node-modules@master\n\n  check:\n    name: Check\n    needs: build\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - uses: actions/setup-node@v2\n        with:\n          node-version: lts/*\n      - uses: ipfs/aegir/actions/cache-node-modules@master\n      - run: |\n          npm run lint\n          npm run dep-check -- -- -- -p\n          npm run dep-check -- -- -- -- --unused\n\n  test-node:\n    name: Unit tests node ${{ matrix.node }} ${{ matrix.os }}\n    needs: build\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os: [ubuntu-latest, macos-latest]\n        node: [lts/*]\n    steps:\n      - uses: actions/checkout@v2\n        with:\n          fetch-depth: 0\n      - uses: actions/setup-node@v2\n        with:\n          node-version: ${{ matrix.node }}\n      - uses: ipfs/aegir/actions/cache-node-modules@master\n      - run: npm run test:node\n      - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0\n        with:\n          flags: node\n\n  test-chrome:\n    name: Unit tests chrome\n    needs: build\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n        with:\n          fetch-depth: 0\n      - uses: actions/setup-node@v2\n        with:\n          node-version: lts/*\n      - uses: ipfs/aegir/actions/cache-node-modules@master\n      - run: npm run test:chrome\n      - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0\n        with:\n          flags: chrome\n\n  test-chrome-webworker:\n    name: Unit tests chrome-webworker\n    needs: build\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n        with:\n          fetch-depth: 0\n      - uses: actions/setup-node@v2\n        with:\n          node-version: lts/*\n      - uses: ipfs/aegir/actions/cache-node-modules@master\n      - run: npm run test:chrome-webworker\n      - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0\n        with:\n          flags: chrome-webworker\n\n  test-firefox:\n    name: Unit tests firefox\n    needs: build\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n        with:\n          fetch-depth: 0\n      - uses: actions/setup-node@v2\n        with:\n          node-version: lts/*\n      - uses: ipfs/aegir/actions/cache-node-modules@master\n      - run: npm run test:firefox\n      - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0\n        with:\n          flags: firefox\n\n  test-firefox-webworker:\n    name: Unit tests firefox-webworker\n    needs: build\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n        with:\n          fetch-depth: 0\n      - uses: actions/setup-node@v2\n        with:\n          node-version: lts/*\n      - uses: ipfs/aegir/actions/cache-node-modules@master\n      - run: npx playwright install --with-deps\n      - run: npm run test:firefox-webworker\n      - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0\n        with:\n          flags: firefox-webworker\n\n  test-electron-main:\n    name: Unit tests electron-main\n    needs: build\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n        with:\n          fetch-depth: 0\n      - uses: actions/setup-node@v2\n        with:\n          node-version: lts/*\n      - uses: ipfs/aegir/actions/cache-node-modules@master\n      - uses: GabrielBB/xvfb-action@v1\n        with:\n          run: npm run test:electron-main\n      - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0\n        with:\n          flags: electron-main\n\n  test-interop:\n    name: Interop tests ${{ matrix.project }} ${{ matrix.type }}\n    needs: build\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        type:\n          - node\n          - browser\n          #- electron-main\n    steps:\n      - uses: actions/checkout@v2\n        with:\n          fetch-depth: 0\n      - uses: actions/setup-node@v2\n        with:\n          node-version: lts/*\n      - uses: ipfs/aegir/actions/cache-node-modules@master\n      - run: npm run test:interop -- -- -- -t ${{ matrix.type }}\n      - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0\n        with:\n          flags: interop-${{ matrix.type }}\n\n  test-interface:\n    name: Interface tests ${{ matrix.suite }} ${{ matrix.type }}\n    needs: build\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        type:\n          - node\n          - browser\n          #- electron-main\n        suite:\n          - test:interface:core\n          - test:interface:client\n          - test:interface:http-go\n          - test:interface:http-js\n    steps:\n      - uses: actions/checkout@v2\n        with:\n          fetch-depth: 0\n      - uses: actions/setup-node@v2\n        with:\n          node-version: lts/*\n      - uses: ipfs/aegir/actions/cache-node-modules@master\n      - run: npm run ${{ matrix.suite }} -- -- -t ${{ matrix.type }}\n      - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0\n        with:\n          flags: interface-${{ matrix.type }}\n\n  test-interface-message-port-client:\n    name: Interface tests test:interface:message-port-client browser\n    needs: build\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n        with:\n          fetch-depth: 0\n      - uses: actions/setup-node@v2\n        with:\n          node-version: lts/*\n      - uses: ipfs/aegir/actions/cache-node-modules@master\n      - run: npx playwright install --with-deps\n      - run: npm run test:interface:message-port-client\n\n  release:\n    runs-on: ubuntu-latest\n    needs: [\n      test-node,\n      test-chrome,\n      test-chrome-webworker,\n      test-firefox,\n      test-firefox-webworker,\n      test-electron-main,\n      test-interop,\n      test-interface,\n      test-interface-message-port-client\n    ]\n    if: github.event_name == 'push' && github.ref == 'refs/heads/master'\n    steps:\n      - uses: GoogleCloudPlatform/release-please-action@v2\n        id: release\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n          command: manifest\n          release-type: node\n          manifest-file: .release-please-manifest.json\n          config-file: .release-please.json\n          changelog-types: |\n            [\n              { \"type\": \"feat\", \"section\": \"Features\", \"hidden\": false },\n              { \"type\": \"fix\", \"section\": \"Bug Fixes\", \"hidden\": false },\n              { \"type\": \"chore\", \"section\": \"Trivial Changes\", \"hidden\": false },\n              { \"type\": \"docs\", \"section\": \"Documentation\", \"hidden\": false },\n              { \"type\": \"deps\", \"section\": \"Dependencies\", \"hidden\": false }\n            ]\n      - uses: actions/checkout@v2\n        with:\n          fetch-depth: 0\n      - uses: actions/setup-node@v2\n        with:\n          node-version: lts/*\n          registry-url: 'https://registry.npmjs.org'\n      - uses: ipfs/aegir/actions/cache-node-modules@master\n      - uses: ipfs/aegir/actions/docker-login@master\n        with:\n          docker-token: ${{ secrets.DOCKER_TOKEN }}\n          docker-username: ${{ secrets.DOCKER_USERNAME }}\n      - if: ${{ steps.release.outputs.releases_created }}\n        name: Run release version\n        run: |\n          git update-index --assume-unchanged packages/ipfs-core/src/version.js packages/ipfs-http-server/src/version.js packages/ipfs/src/package.js\n          npm run --if-present release\n        env:\n          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}\n      - if: ${{ !steps.release.outputs.releases_created }}\n        name: Run release rc\n        run: |\n            git update-index --assume-unchanged packages/ipfs-core/src/version.js packages/ipfs-http-server/src/version.js packages/ipfs/src/package.js\n            npm run --if-present release:rc\n        env:\n          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\npackage-lock.json\nyarn.lock\ntsconfig-types.aegir.json\n\n# Coverage directory used by tools like istanbul\ncoverage\n.coverage\n.nyc_output\ntests_output\ncache\n.cache\n.parcel-cache\n\n# Dependency directory\nnode_modules\n\n# Build artefacts\ndist\nbuild\nbundle.js\ntsconfig-types.aegir.json\ntsconfig-check.aegir.json\n.tsbuildinfo\n\n# Deployment files\n.npmrc\n\n# Editor files\n.vscode\n\n# Operating system files\n.DS_Store\ntypes"
  },
  {
    "path": ".release-please-manifest.json",
    "content": "{\"packages/interface-ipfs-core\":\"0.158.1\",\"packages/ipfs\":\"0.66.1\",\"packages/ipfs-cli\":\"0.16.1\",\"packages/ipfs-client\":\"0.10.1\",\"packages/ipfs-core\":\"0.18.1\",\"packages/ipfs-core-config\":\"0.7.1\",\"packages/ipfs-core-types\":\"0.14.1\",\"packages/ipfs-core-utils\":\"0.18.1\",\"packages/ipfs-daemon\":\"0.16.1\",\"packages/ipfs-grpc-client\":\"0.13.1\",\"packages/ipfs-grpc-protocol\":\"0.8.1\",\"packages/ipfs-grpc-server\":\"0.12.1\",\"packages/ipfs-http-client\":\"60.0.1\",\"packages/ipfs-http-gateway\":\"0.13.1\",\"packages/ipfs-http-response\":\"6.0.1\",\"packages/ipfs-http-server\":\"0.15.1\",\"packages/ipfs-message-port-client\":\"0.15.1\",\"packages/ipfs-message-port-protocol\":\"0.15.1\",\"packages/ipfs-message-port-server\":\"0.15.1\"}"
  },
  {
    "path": ".release-please.json",
    "content": "{\n  \"plugins\": [\"node-workspace\"],\n  \"bump-minor-pre-major\": true,\n  \"group-pull-request-title-pattern\": \"chore: release ${component}\",\n  \"packages\": {\n    \"packages/interface-ipfs-core\": {},\n    \"packages/ipfs\": {},\n    \"packages/ipfs-cli\": {},\n    \"packages/ipfs-client\": {},\n    \"packages/ipfs-core\": {},\n    \"packages/ipfs-core-config\": {},\n    \"packages/ipfs-core-types\": {},\n    \"packages/ipfs-core-utils\": {},\n    \"packages/ipfs-daemon\": {},\n    \"packages/ipfs-grpc-client\": {},\n    \"packages/ipfs-grpc-protocol\": {},\n    \"packages/ipfs-grpc-server\": {},\n    \"packages/ipfs-http-client\": {},\n    \"packages/ipfs-http-gateway\": {},\n    \"packages/ipfs-http-response\": {},\n    \"packages/ipfs-http-server\": {},\n    \"packages/ipfs-message-port-client\": {},\n    \"packages/ipfs-message-port-protocol\": {},\n    \"packages/ipfs-message-port-server\": {}\n  }\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Change Log\n\nPlease see the individual package changelogs for what's new:\n\n* [`/packages/interface-ipfs-core/CHANGELOG.md`](./packages/interface-ipfs-core/CHANGELOG.md)\n* [`/packages/ipfs/CHANGELOG.md`](./packages/ipfs/CHANGELOG.md)\n* [`/packages/ipfs-core-utils/CHANGELOG.md`](./packages/ipfs-core-utils/CHANGELOG.md)\n* [`/packages/ipfs-http-client/CHANGELOG.md`](./packages/ipfs-http-client/CHANGELOG.md)\n* [`/packages/ipfs-http-server/CHANGELOG.md`](./packages/ipfs-http-server/CHANGELOG.md)\n* [`/packages/ipfs-message-port-client/CHANGELOG.md`](./packages/ipfs-message-port-client/CHANGELOG.md)\n* [`/packages/ipfs-message-port-protocol/CHANGELOG.md`](./packages/ipfs-message-port-protocol/CHANGELOG.md)\n* [`/packages/ipfs-message-port-server/CHANGELOG.md`](./packages/ipfs-message-port-server/CHANGELOG.md)\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Code of Conduct\n\nThe `js-ipfs` project follows the [`IPFS Community Code of Conduct`](https://github.com/ipfs/community/blob/master/code-of-conduct.md)\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing guidelines\n\nIPFS as a project, including js-ipfs and all of its modules, follows the [standard IPFS Community contributing guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md).\n\nWe also adhere to the [IPFS JavaScript Community contributing guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING_JS.md) which provide additional information of how to collaborate and contribute in the JavaScript implementation of IPFS.\n\nWe appreciate your time and attention for going over these. Please open an issue on [ipfs/community](https://github.com/ipfs/community) if you have any question.\n\nThank you.\n"
  },
  {
    "path": "COPYRIGHT",
    "content": "This project is transitioning from an MIT-only license to a dual MIT/Apache-2.0 license.\nUnless otherwise noted, all code contributed prior to 2019-11-21 and not contributed by\na user listed in [this signoff issue](https://github.com/ipfs/js-ipfs/issues/2624) is\nlicensed under MIT-only. All new contributions (and past contributions since 2019-11-21)\nare licensed under a dual MIT/Apache-2.0 license.\n"
  },
  {
    "path": "Dockerfile.latest",
    "content": "FROM node:16-alpine\n\nENV IPFS_VERSION=latest\nENV IPFS_MONITORING=1\nENV IPFS_PATH=/root/.jsipfs\nENV BUILD_DEPS='libnspr4 libnspr4-dev libnss3'\n\nRUN apk add --no-cache git python3 build-base\n\n# Hopefully remove when https://github.com/node-webrtc/node-webrtc/pull/694 is merged\nRUN npm install -g ipfs@\"$IPFS_VERSION\"\n\n# Make the image a bit smaller\nRUN npm cache clear --force\nRUN apk del build-base python3 git\n\n# Configure jsipfs\nRUN jsipfs init\n\nRUN jsipfs version\n\n# Allow connections from any host\nRUN sed -i.bak \"s/127.0.0.1/0.0.0.0/g\" $IPFS_PATH/config\n\nEXPOSE 4002\nEXPOSE 4003\nEXPOSE 5002\nEXPOSE 9090\n\nCMD jsipfs daemon\n"
  },
  {
    "path": "Dockerfile.next",
    "content": "FROM node:16-alpine\n\nENV IPFS_VERSION=next\nENV IPFS_MONITORING=1\nENV IPFS_PATH=/root/.jsipfs\nENV BUILD_DEPS='libnspr4 libnspr4-dev libnss3'\n\nRUN apk add --no-cache git python3 build-base\n\n# Hopefully remove when https://github.com/node-webrtc/node-webrtc/pull/694 is merged\nRUN npm install -g ipfs@\"$IPFS_VERSION\"\n\n# Make the image a bit smaller\nRUN npm cache clear --force\nRUN apk del build-base python3 git\n\n# Configure jsipfs\nRUN jsipfs init\n\nRUN jsipfs version\n\n# Allow connections from any host\nRUN sed -i.bak \"s/127.0.0.1/0.0.0.0/g\" $IPFS_PATH/config\n\nEXPOSE 4002\nEXPOSE 4003\nEXPOSE 5002\nEXPOSE 9090\n\nCMD jsipfs daemon\n"
  },
  {
    "path": "LICENSE",
    "content": "This project is dual licensed under MIT and Apache-2.0.\n\nMIT: https://www.opensource.org/licenses/mit\nApache-2.0: https://www.apache.org/licenses/license-2.0\n"
  },
  {
    "path": "LICENSE-APACHE",
    "content": "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\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless 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.\n"
  },
  {
    "path": "LICENSE-MIT",
    "content": "The MIT License (MIT)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "> # ⛔️ DEPRECATED: [js-IPFS](https://github.com/ipfs/js-ipfs) has been superseded by [Helia](https://github.com/ipfs/helia)\n>\n> 📚 [Learn more about this deprecation](https://github.com/ipfs/js-ipfs/issues/4336) or [how to migrate](https://github.com/ipfs/helia/wiki/Migrating-from-js-IPFS)\n>\n> ⚠️ If you continue using this repo, please note that security fixes will not be provided\n\n<p align=\"center\">\n  <a href=\"https://js.ipfs.io\" title=\"JS IPFS\">\n    <img src=\"https://ipfs.io/ipfs/Qme6KJdKcp85TYbLxuLV7oQzMiLremD7HMoXLZEmgo6Rnh/js-ipfs-sticker.png\" alt=\"IPFS in JavaScript logo\" width=\"244\" />\n  </a>\n</p>\n\n<h3 align=\"center\">The JavaScript implementation of the IPFS protocol</h3>\n\n<p align=\"center\">\n  <a href=\"https://github.com/ipfs/js-ipfs/tree/master/packages/interface-ipfs-core\"><img src=\"https://img.shields.io/badge/interface--ipfs--core-API%20Docs-blue.svg\"></a>\n  <a href=\"https://github.com/ipfs/js-ipfs/actions/workflows/test.yml?query=branch%3Amaster\"><img src=\"https://img.shields.io/github/actions/workflow/status/ipfs/js-ipfs/test.yml?branch=master\" /></a>\n  <a href=\"https://codecov.io/gh/ipfs/js-ipfs\"><img src=\"https://badgen.net/codecov/c/github/ipfs/js-ipfs\" /></a>\n  <br>\n</p>\n\n## Getting started\n\n* Read the [docs](https://github.com/ipfs/js-ipfs/tree/master/docs)\n* Ensure CORS is [correctly configured](https://github.com/ipfs/js-ipfs/blob/master/docs/CORS.md) for use with the HTTP client\n* Look into the [examples](https://github.com/ipfs-examples/js-ipfs-examples/tree/master) to learn how to spawn an IPFS node in Node.js and in the Browser\n* Consult the [Core API docs](https://github.com/ipfs/js-ipfs/tree/master/docs/core-api) to see what you can do with an IPFS node\n* Head over to https://proto.school to take the [IPFS course](https://proto.school/course/ipfs) that covers core IPFS concepts and JS APIs\n* Check out https://docs.ipfs.io for [glossary](https://docs.ipfs.io/concepts/glossary), tips, how-tos and more\n* Need help? Please ask 'How do I?' questions on https://discuss.ipfs.io\n* Find out about chat channels, the IPFS newsletter, the IPFS blog, and more in the [IPFS community space](https://docs.ipfs.io/community/).\n\n## Table of Contents <!-- omit in toc -->\n\n- [Getting started](#getting-started)\n  - [Install as a CLI user](#install-as-a-cli-user)\n  - [Install as an application developer](#install-as-an-application-developer)\n- [Documentation](#documentation)\n- [Structure](#structure)\n- [Packages](#packages)\n- [Want to hack on IPFS?](#want-to-hack-on-ipfs)\n- [License](#license)\n\n## Getting Started <!-- omit in toc -->\n\n### Install as a CLI user\n\nInstalling `ipfs` globally will give you the `jsipfs` command which you can use to start a daemon running:\n\n```console\n$ npm install -g ipfs\n$ jsipfs daemon\nInitializing IPFS daemon...\njs-ipfs version: x.x.x\nSystem version: x64/darwin\nNode.js version: x.x.x\nSwarm listening on /ip4/127.0\n.... more output\n```\n\nYou can then add a file:\n\n```console\n$ jsipfs add ./hello-world.txt\nadded QmXXY5ZxbtuYj6DnfApLiGstzPN7fvSyigrRee3hDWPCaf hello-world.txt\n```\n\n### Install as an application developer\n\nIf you do not need to run a command line daemon, use the `ipfs-core` package - it has all the features of `ipfs` but in a lighter package:\n\n```console\n$ npm install ipfs-core\n```\n\nThen start a node in your app:\n\n```javascript\nimport * as IPFS from 'ipfs-core'\n\nconst ipfs = await IPFS.create()\nconst { cid } = await ipfs.add('Hello world')\nconsole.info(cid)\n// QmXXY5ZxbtuYj6DnfApLiGstzPN7fvSyigrRee3hDWPCaf\n```\n\n## Documentation\n\n* [Concepts](https://docs.ipfs.io/concepts/)\n* [Config](./docs/CONFIG.md)\n* [Core API](./docs/core-api)\n* [Examples](https://github.com/ipfs-examples/js-ipfs-examples/tree/master/examples)\n* [Development](./docs/DEVELOPMENT.md)\n\n## Structure\n\nThis project is broken into several modules, their purposes are:\n\n* [`/packages/interface-ipfs-core`](./packages/interface-ipfs-core) Tests to ensure adherence of an implementation to the spec\n* [`/packages/ipfs`](./packages/ipfs) An aggregator module that bundles the core implementation, the CLI, HTTP API server and daemon\n* [`/packages/ipfs-cli`](./packages/ipfs-cli) A CLI to the core implementation\n* [`/packages/ipfs-core`](./packages/ipfs-core) The core implementation\n* [`/packages/ipfs-core-types`](./packages/ipfs-core-types) Typescript definitions for the core API\n* [`/packages/ipfs-core-utils`](./packages/ipfs-core-utils) Helpers and utilities common to core and the HTTP RPC API client\n* [`/packages/ipfs-daemon`](./packages/ipfs-daemon) Run js-IPFS as a background daemon\n* [`/packages/ipfs-grpc-client`](./packages/ipfs-grpc-client) A gRPC client for js-IPFS\n* [`/packages/ipfs-grpc-protocol`](./packages/ipfs-grpc-protocol) Shared module between the gRPC client and server\n* [`/packages/ipfs-grpc-server`](./packages/ipfs-grpc-server) A gRPC-over-websockets server for js-IPFS\n* [`/packages/ipfs-http-client`](./packages/ipfs-http-client) A client for the RPC-over-HTTP API presented by both js-ipfs and go-ipfs\n* [`/packages/ipfs-http-server`](./packages/ipfs-http-server) JS implementation of the [Kubo RPC HTTP API](https://docs.ipfs.io/reference/kubo/rpc/)\n* [`/packages/ipfs-http-gateway`](./packages/ipfs-http-gateway) JS implementation of the [IPFS HTTP Gateway](https://docs.ipfs.io/concepts/ipfs-gateway/)\n* [`/packages/ipfs-http-response`](./packages/ipfs-http-response) Creates a HTTP response for a given IPFS Path\n* [`/packages/ipfs-message-port-client`](./packages/ipfs-message-port-client) A client for the RPC-over-message-port API presented by js-ipfs running in a shared worker\n* [`/packages/ipfs-message-port-protocol`](./packages/ipfs-message-port-protocol) Code shared by the message port client & server\n* [`/packages/ipfs-message-port-server`](./packages/ipfs-message-port-server) The server that receives requests from ipfs-message-port-client\n\n## Packages\n\nList of the main packages that make up the IPFS ecosystem.\n\n| Package | Version | Deps | CI/Travis | Coverage | Lead Maintainer |\n| ---------|---------|---------|---------|---------|--------- |\n| **Files** |\n| [`ipfs-unixfs`](//github.com/ipfs/js-ipfs-unixfs) | [![npm](https://img.shields.io/npm/v/ipfs-unixfs.svg?maxAge=86400&style=flat-square)](//github.com/ipfs/js-ipfs-unixfs/releases) | [![Deps](https://david-dm.org/ipfs/js-ipfs-unixfs.svg?style=flat-square)](https://david-dm.org/ipfs/js-ipfs-unixfs) | [![Travis CI](https://flat.badgen.net/travis/ipfs/js-ipfs-unixfs/master)](https://travis-ci.com/ipfs/js-ipfs-unixfs) | [![codecov](https://codecov.io/gh/ipfs/js-ipfs-unixfs/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipfs-unixfs) | [Alex Potsides](mailto:alex.potsides@protocol.ai) |\n| **Repo** |\n| [`ipfs-repo`](//github.com/ipfs/js-ipfs-repo) | [![npm](https://img.shields.io/npm/v/ipfs-repo.svg?maxAge=86400&style=flat-square)](//github.com/ipfs/js-ipfs-repo/releases) | [![Deps](https://david-dm.org/ipfs/js-ipfs-repo.svg?style=flat-square)](https://david-dm.org/ipfs/js-ipfs-repo) | [![Travis CI](https://flat.badgen.net/travis/ipfs/js-ipfs-repo/master)](https://travis-ci.com/ipfs/js-ipfs-repo) | [![codecov](https://codecov.io/gh/ipfs/js-ipfs-repo/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipfs-repo) | [Alex Potsides](mailto:alex@achingbrain.net) |\n| [`ipfs-repo-migrations`](//github.com/ipfs/js-ipfs-repo-migrations) | [![npm](https://img.shields.io/npm/v/ipfs-repo-migrations.svg?maxAge=86400&style=flat-square)](//github.com/ipfs/js-ipfs-repo-migrations/releases) | [![Deps](https://david-dm.org/ipfs/js-ipfs-repo-migrations.svg?style=flat-square)](https://david-dm.org/ipfs/js-ipfs-repo-migrations) | [![Travis CI](https://flat.badgen.net/travis/ipfs/js-ipfs-repo-migrations/master)](https://travis-ci.com/ipfs/js-ipfs-repo-migrations) | [![codecov](https://codecov.io/gh/ipfs/js-ipfs-repo-migrations/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipfs-repo-migrations) | N/A |\n| **Exchange** |\n| [`ipfs-bitswap`](//github.com/ipfs/js-ipfs-bitswap) | [![npm](https://img.shields.io/npm/v/ipfs-bitswap.svg?maxAge=86400&style=flat-square)](//github.com/ipfs/js-ipfs-bitswap/releases) | [![Deps](https://david-dm.org/ipfs/js-ipfs-bitswap.svg?style=flat-square)](https://david-dm.org/ipfs/js-ipfs-bitswap) | [![Travis CI](https://flat.badgen.net/travis/ipfs/js-ipfs-bitswap/master)](https://travis-ci.com/ipfs/js-ipfs-bitswap) | [![codecov](https://codecov.io/gh/ipfs/js-ipfs-bitswap/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipfs-bitswap) | [Dirk McCormick](mailto:dirk@protocol.ai) |\n| **IPNS** |\n| [`ipns`](//github.com/ipfs/js-ipns) | [![npm](https://img.shields.io/npm/v/ipns.svg?maxAge=86400&style=flat-square)](//github.com/ipfs/js-ipns/releases) | [![Deps](https://david-dm.org/ipfs/js-ipns.svg?style=flat-square)](https://david-dm.org/ipfs/js-ipns) | [![Travis CI](https://flat.badgen.net/travis/ipfs/js-ipns/master)](https://travis-ci.com/ipfs/js-ipns) | [![codecov](https://codecov.io/gh/ipfs/js-ipns/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipns) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |\n| **Generics/Utils** |\n| [`ipfs-utils`](//github.com/ipfs/js-ipfs) | [![npm](https://img.shields.io/npm/v/ipfs-utils.svg?maxAge=86400&style=flat-square)](//github.com/ipfs/js-ipfs/releases) | [![Deps](https://david-dm.org/ipfs/js-ipfs.svg?style=flat-square)](https://david-dm.org/ipfs/js-ipfs) | [![Travis CI](https://flat.badgen.net/travis/ipfs/js-ipfs/master)](https://travis-ci.com/ipfs/js-ipfs) | [![codecov](https://codecov.io/gh/ipfs/js-ipfs/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipfs) | [Hugo Dias](mailto:hugomrdias@gmail.com) |\n| [`ipfs-http-client`](//github.com/ipfs/js-ipfs) | [![npm](https://img.shields.io/npm/v/ipfs-http-client.svg?maxAge=86400&style=flat-square)](//github.com/ipfs/js-ipfs/releases) | [![Deps](https://david-dm.org/ipfs/js-ipfs.svg?style=flat-square)](https://david-dm.org/ipfs/js-ipfs) | [![Travis CI](https://flat.badgen.net/travis/ipfs/js-ipfs/master)](https://travis-ci.com/ipfs/js-ipfs) | [![codecov](https://codecov.io/gh/ipfs/js-ipfs/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipfs) | [Alex Potsides](mailto:alex@achingbrain.net) |\n| [`ipfs-http-response`](//github.com/ipfs/js-ipfs-http-response) | [![npm](https://img.shields.io/npm/v/ipfs-http-response.svg?maxAge=86400&style=flat-square)](//github.com/ipfs/js-ipfs-http-response/releases) | [![Deps](https://david-dm.org/ipfs/js-ipfs-http-response.svg?style=flat-square)](https://david-dm.org/ipfs/js-ipfs-http-response) | [![Travis CI](https://flat.badgen.net/travis/ipfs/js-ipfs-http-response/master)](https://travis-ci.com/ipfs/js-ipfs-http-response) | [![codecov](https://codecov.io/gh/ipfs/js-ipfs-http-response/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipfs-http-response) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |\n| [`ipfsd-ctl`](//github.com/ipfs/js-ipfsd-ctl) | [![npm](https://img.shields.io/npm/v/ipfsd-ctl.svg?maxAge=86400&style=flat-square)](//github.com/ipfs/js-ipfsd-ctl/releases) | [![Deps](https://david-dm.org/ipfs/js-ipfsd-ctl.svg?style=flat-square)](https://david-dm.org/ipfs/js-ipfsd-ctl) | [![Travis CI](https://flat.badgen.net/travis/ipfs/js-ipfsd-ctl/master)](https://travis-ci.com/ipfs/js-ipfsd-ctl) | [![codecov](https://codecov.io/gh/ipfs/js-ipfsd-ctl/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipfsd-ctl) | [Hugo Dias](mailto:mail@hugodias.me) |\n| [`is-ipfs`](//github.com/ipfs/is-ipfs) | [![npm](https://img.shields.io/npm/v/is-ipfs.svg?maxAge=86400&style=flat-square)](//github.com/ipfs/is-ipfs/releases) | [![Deps](https://david-dm.org/ipfs/is-ipfs.svg?style=flat-square)](https://david-dm.org/ipfs/is-ipfs) | [![Travis CI](https://flat.badgen.net/travis/ipfs/is-ipfs/master)](https://travis-ci.com/ipfs/is-ipfs) | [![codecov](https://codecov.io/gh/ipfs/is-ipfs/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/ipfs/is-ipfs) | [Marcin Rataj](mailto:lidel@lidel.org) |\n| [`aegir`](//github.com/ipfs/aegir) | [![npm](https://img.shields.io/npm/v/aegir.svg?maxAge=86400&style=flat-square)](//github.com/ipfs/aegir/releases) | [![Deps](https://david-dm.org/ipfs/aegir.svg?style=flat-square)](https://david-dm.org/ipfs/aegir) | [![Travis CI](https://flat.badgen.net/travis/ipfs/aegir/master)](https://travis-ci.com/ipfs/aegir) | [![codecov](https://codecov.io/gh/ipfs/aegir/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/ipfs/aegir) | [Hugo Dias](mailto:hugomrdias@gmail.com) |\n| **libp2p** |\n| [`libp2p`](//github.com/libp2p/js-libp2p) | [![npm](https://img.shields.io/npm/v/libp2p.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p/master)](https://travis-ci.com/libp2p/js-libp2p) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p) | [Jacob Heun](mailto:jacobheun@gmail.com) |\n| [`peer-id`](//github.com/libp2p/js-peer-id) | [![npm](https://img.shields.io/npm/v/peer-id.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-peer-id/releases) | [![Deps](https://david-dm.org/libp2p/js-peer-id.svg?style=flat-square)](https://david-dm.org/libp2p/js-peer-id) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-peer-id/master)](https://travis-ci.com/libp2p/js-peer-id) | [![codecov](https://codecov.io/gh/libp2p/js-peer-id/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-peer-id) | [Vasco Santos](mailto:santos.vasco10@gmail.com) |\n| [`libp2p-crypto`](//github.com/libp2p/js-libp2p-crypto) | [![npm](https://img.shields.io/npm/v/libp2p-crypto.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-crypto/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-crypto.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-crypto) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-crypto/master)](https://travis-ci.com/libp2p/js-libp2p-crypto) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-crypto/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-crypto) | [Jacob Heun](mailto:jacobheun@gmail.com) |\n| [`libp2p-floodsub`](//github.com/libp2p/js-libp2p-floodsub) | [![npm](https://img.shields.io/npm/v/libp2p-floodsub.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-floodsub/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-floodsub.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-floodsub) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-floodsub/master)](https://travis-ci.com/libp2p/js-libp2p-floodsub) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-floodsub/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-floodsub) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |\n| [`libp2p-gossipsub`](//github.com/ChainSafe/gossipsub-js) | [![npm](https://img.shields.io/npm/v/libp2p-gossipsub.svg?maxAge=86400&style=flat-square)](//github.com/ChainSafe/gossipsub-js/releases) | [![Deps](https://david-dm.org/ChainSafe/gossipsub-js.svg?style=flat-square)](https://david-dm.org/ChainSafe/gossipsub-js) | [![Travis CI](https://flat.badgen.net/travis/ChainSafe/gossipsub-js/master)](https://travis-ci.com/ChainSafe/gossipsub-js) | [![codecov](https://codecov.io/gh/ChainSafe/gossipsub-js/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/ChainSafe/gossipsub-js) | [Cayman Nava](mailto:caymannava@gmail.com) |\n| [`libp2p-kad-dht`](//github.com/libp2p/js-libp2p-kad-dht) | [![npm](https://img.shields.io/npm/v/libp2p-kad-dht.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-kad-dht/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-kad-dht.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-kad-dht) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-kad-dht/master)](https://travis-ci.com/libp2p/js-libp2p-kad-dht) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-kad-dht/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-kad-dht) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |\n| [`libp2p-mdns`](//github.com/libp2p/js-libp2p-mdns) | [![npm](https://img.shields.io/npm/v/libp2p-mdns.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-mdns/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-mdns.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-mdns) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-mdns/master)](https://travis-ci.com/libp2p/js-libp2p-mdns) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-mdns/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-mdns) | [Jacob Heun](mailto:jacobheun@gmail.com) |\n| [`libp2p-bootstrap`](//github.com/libp2p/js-libp2p-bootstrap) | [![npm](https://img.shields.io/npm/v/libp2p-bootstrap.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-bootstrap/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-bootstrap.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-bootstrap) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-bootstrap/master)](https://travis-ci.com/libp2p/js-libp2p-bootstrap) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-bootstrap/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-bootstrap) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |\n| [`@chainsafe/libp2p-noise`](//github.com/ChainSafe/js-libp2p-noise) | [![npm](https://img.shields.io/npm/v/libp2p-noise.svg?maxAge=86400&style=flat-square)](//github.com/ChainSafe/js-libp2p-noise/releases) | [![Deps](https://david-dm.org/ChainSafe/js-libp2p-noise.svg?style=flat-square)](https://david-dm.org/ChainSafe/js-libp2p-noise) | [![Travis CI](https://flat.badgen.net/travis/ChainSafe/js-libp2p-noise/master)](https://travis-ci.com/ChainSafe/js-libp2p-noise) | [![codecov](https://codecov.io/gh/ChainSafe/js-libp2p-noise/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/ChainSafe/js-libp2p-noise) | N/A |\n| [`libp2p-tcp`](//github.com/libp2p/js-libp2p-tcp) | [![npm](https://img.shields.io/npm/v/libp2p-tcp.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-tcp/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-tcp.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-tcp) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-tcp/master)](https://travis-ci.com/libp2p/js-libp2p-tcp) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-tcp/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-tcp) | [Jacob Heun](mailto:jacobheun@gmail.com) |\n| [`libp2p-webrtc-star`](//github.com/libp2p/js-libp2p-webrtc-star) | [![npm](https://img.shields.io/npm/v/libp2p-webrtc-star.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-webrtc-star/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-webrtc-star.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-webrtc-star) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-webrtc-star/master)](https://travis-ci.com/libp2p/js-libp2p-webrtc-star) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-webrtc-star/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-webrtc-star) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |\n| [`libp2p-websockets`](//github.com/libp2p/js-libp2p-websockets) | [![npm](https://img.shields.io/npm/v/libp2p-websockets.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-websockets/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-websockets.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-websockets) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-websockets/master)](https://travis-ci.com/libp2p/js-libp2p-websockets) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-websockets/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-websockets) | [Jacob Heun](mailto:jacobheun@gmail.com) |\n| [`libp2p-mplex`](//github.com/libp2p/js-libp2p-mplex) | [![npm](https://img.shields.io/npm/v/libp2p-mplex.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-mplex/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-mplex.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-mplex) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-mplex/master)](https://travis-ci.com/libp2p/js-libp2p-mplex) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-mplex/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-mplex) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |\n| [`libp2p-delegated-content-routing`](//github.com/libp2p/js-libp2p-delegated-content-routing) | [![npm](https://img.shields.io/npm/v/libp2p-delegated-content-routing.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-delegated-content-routing/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-delegated-content-routing.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-delegated-content-routing) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-delegated-content-routing/master)](https://travis-ci.com/libp2p/js-libp2p-delegated-content-routing) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-delegated-content-routing/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-delegated-content-routing) | [Jacob Heun](mailto:jacobheun@gmail.com) |\n| [`libp2p-delegated-peer-routing`](//github.com/libp2p/js-libp2p-delegated-peer-routing) | [![npm](https://img.shields.io/npm/v/libp2p-delegated-peer-routing.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-delegated-peer-routing/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-delegated-peer-routing.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-delegated-peer-routing) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-delegated-peer-routing/master)](https://travis-ci.com/libp2p/js-libp2p-delegated-peer-routing) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-delegated-peer-routing/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-delegated-peer-routing) | [Jacob Heun](mailto:jacobheun@gmail.com) |\n| **IPLD** |\n| [`@ipld/dag-pb`](//github.com/ipld/js-dag-pb) | [![npm](https://img.shields.io/npm/v/@ipld/dag-pb.svg?maxAge=86400&style=flat-square)](//github.com/ipld/js-dag-pb/releases) | [![Deps](https://david-dm.org/ipld/js-dag-pb.svg?style=flat-square)](https://david-dm.org/ipld/js-dag-pb) | [![Travis CI](https://flat.badgen.net/travis/ipld/js-dag-pb/master)](https://travis-ci.com/ipld/js-dag-pb) | [![codecov](https://codecov.io/gh/ipld/js-dag-pb/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/ipld/js-dag-pb) | N/A |\n| [`@ipld/dag-cbor`](//github.com/ipld/js-dag-cbor) | [![npm](https://img.shields.io/npm/v/@ipld/dag-cbor.svg?maxAge=86400&style=flat-square)](//github.com/ipld/js-dag-cbor/releases) | [![Deps](https://david-dm.org/ipld/js-dag-cbor.svg?style=flat-square)](https://david-dm.org/ipld/js-dag-cbor) | [![Travis CI](https://flat.badgen.net/travis/ipld/js-dag-cbor/master)](https://travis-ci.com/ipld/js-dag-cbor) | [![codecov](https://codecov.io/gh/ipld/js-dag-cbor/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/ipld/js-dag-cbor) | N/A |\n| **Multiformats** |\n| [`multiformats`](//github.com/multiformats/js-multiformats) | [![npm](https://img.shields.io/npm/v/multiformats.svg?maxAge=86400&style=flat-square)](//github.com/multiformats/js-multiformats/releases) | [![Deps](https://david-dm.org/multiformats/js-multiformats.svg?style=flat-square)](https://david-dm.org/multiformats/js-multiformats) | [![Travis CI](https://flat.badgen.net/travis/multiformats/js-multiformats/master)](https://travis-ci.com/multiformats/js-multiformats) | [![codecov](https://codecov.io/gh/multiformats/js-multiformats/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/multiformats/js-multiformats) | N/A |\n| [`mafmt`](//github.com/multiformats/js-mafmt) | [![npm](https://img.shields.io/npm/v/mafmt.svg?maxAge=86400&style=flat-square)](//github.com/multiformats/js-mafmt/releases) | [![Deps](https://david-dm.org/multiformats/js-mafmt.svg?style=flat-square)](https://david-dm.org/multiformats/js-mafmt) | [![Travis CI](https://flat.badgen.net/travis/multiformats/js-mafmt/master)](https://travis-ci.com/multiformats/js-mafmt) | [![codecov](https://codecov.io/gh/multiformats/js-mafmt/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/multiformats/js-mafmt) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |\n| [`multiaddr`](//github.com/multiformats/js-multiaddr) | [![npm](https://img.shields.io/npm/v/multiaddr.svg?maxAge=86400&style=flat-square)](//github.com/multiformats/js-multiaddr/releases) | [![Deps](https://david-dm.org/multiformats/js-multiaddr.svg?style=flat-square)](https://david-dm.org/multiformats/js-multiaddr) | [![Travis CI](https://flat.badgen.net/travis/multiformats/js-multiaddr/master)](https://travis-ci.com/multiformats/js-multiaddr) | [![codecov](https://codecov.io/gh/multiformats/js-multiaddr/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/multiformats/js-multiaddr) | [Jacob Heun](mailto:jacobheun@gmail.com) |\n\n> This table is generated using the module [`package-table`](https://www.npmjs.com/package/package-table) with `package-table --data=package-list.json`.\n\n## Want to hack on IPFS?\n\n[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md)\n\nThe IPFS implementation in JavaScript needs your help! There are a few things you can do right now to help out:\n\nRead the [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md) and [JavaScript Contributing Guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING_JS.md).\n\n- **Check out existing issues** The [issue list](https://github.com/ipfs/js-ipfs/issues) has many that are marked as ['help wanted'](https://github.com/ipfs/js-ipfs/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22help+wanted%22) or ['difficulty:easy'](https://github.com/ipfs/js-ipfs/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Adifficulty%3Aeasy) which make great starting points for development, many of which can be tackled with no prior IPFS knowledge\n- **Look at the [IPFS Roadmap](https://github.com/ipfs/roadmap)** This are the high priority items being worked on right now\n- **Perform code reviews** More eyes will help\n  a. speed the project along\n  b. ensure quality, and\n  c. reduce possible future bugs.\n- **Add tests**. There can never be enough tests.\n\n## License\n\n[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fipfs%2Fjs-ipfs.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fipfs%2Fjs-ipfs?ref=badge_large)\n"
  },
  {
    "path": "docs/ARCHITECTURE.md",
    "content": "# IPFS Architecture <!-- omit in toc -->\n\n## Table of Contents <!-- omit in toc -->\n\n- [Code Architecture and folder Structure](#code-architecture-and-folder-structure)\n  - [Source code](#source-code)\n\n![](./img/architecture.png)\n\n[Annotated version](https://user-images.githubusercontent.com/1211152/47606420-b6265780-da13-11e8-923b-b365a8534e0e.png)\n\nWhat does this image explain?\n\n- IPFS uses `ipfs-repo` which picks `fs` or `indexeddb` as its storage drivers, depending if it is running in Node.js or in the Browser.\n- The exchange protocol, `bitswap`, uses the Block Service which in turn uses the Repo, offering a get and put of blocks to the IPFS implementation.\n- The DAG API (previously Object) comes from the IPLD Resolver, it can support several IPLD Formats (i.e: dag-pb, dag-cbor, etc).\n- The Files API uses `ipfs-unixfs-engine` to import and export files to and from IPFS.\n- libp2p, the network stack of IPFS, uses libp2p to dial and listen for connections, to use the DHT, for discovery mechanisms, and more.\n\n## Code Architecture and folder Structure\n\n![](./img/overview.png)\n\n### Source code\n\n```Bash\n> tree src -L 2\nsrc                 # Main source code folder\n├── cli             # Implementation of the IPFS CLI\n│   └── ...\n├── http            # The HTTP-API implementation of IPFS as defined by HTTP API spec\n├── core            # IPFS implementation, the core (what gets loaded in browser)\n│   ├── components  # Each of IPFS subcomponent\n│   └── ...\n└── ...\n```\n"
  },
  {
    "path": "docs/BROWSERS.md",
    "content": "# Using JS IPFS in the Browser <!-- omit in toc -->\n\n## Table of Contents <!-- omit in toc -->\n\n- [Limitations of the Browser Context](#limitations-of-the-browser-context)\n- [Addressing Limitations](#addressing-limitations)\n- [Best Practices](#best-practices)\n- [Code Examples](#code-examples)\n\nJS IPFS is the implementation of IPFS protocol in JavaScript. It can run on any\nevergreen browser, inside a service or web worker, browser extensions, Electron, and in Node.js.\n\n**This document provides key information about running JS IPFS in the browser.\nSave time and get familiar with common caveats and limitations of the browser context.**\n\n## Limitations of the Browser Context\n\n- Transport options are currently limited to [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) and [WebRTC](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API).\n\n  This means JS IPFS running in the browser is limited to Web APIs available on a web page.\n  There is no access to raw TCP sockets nor low-level UDP, only WebSockets, and WebRTC.\n\n- Key [Web APIs](https://developer.mozilla.org/en-US/docs/Web/API) require or are restricted by [Secure Context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts) policies.\n\n  This means JS IPFS needs to run within Secure Context (HTTPS or localhost).\n  JS IPFS running on HTTPS website requires Secure WebSockets (TLS) and won't work with unencrypted ones.\n  [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) not being available at all.\n\n- JS IPFS comes with limited support for the [DHT](https://docs.ipfs.tech/concepts/dht/) in client mode which delegates content discovery requests to other DHT nodes.\n\n  However, it's worth noting that even though you'll get results from DHT queries, most nodes in the network are not dialable from browsers because they only support TCP and/or QUIC transports.\n\n  For now, the content discovery and connectivity to other peers are achieved with a mix of DHT client requests, rendezvous and relay servers, delegated peer/content routing, and preload servers.\n\n\n## Addressing Limitations\n\nWe provide a few additional components useful for running JS IPFS in the browser:\n\n\n- [libp2p-webrtc-star](https://github.com/libp2p/js-libp2p-webrtc-star) - incorporates both a transport and a discovery service that is facilitated by the custom rendezvous server available in the repo\n  - Instructions on enabling `webrtc-star` in js-ipfs config can be found [here](https://github.com/ipfs/js-ipfs/blob/master/docs/FAQ.md#how-to-enable-webrtc-support-for-js-ipfs-in-the-browser).\n  - Make sure to [run your own rendezvous server](https://github.com/libp2p/js-libp2p-webrtc-star#rendezvous-server-aka-signalling-server).\n- [libp2p-webrtc-direct](https://github.com/libp2p/js-libp2p-webrtc-direct) - a WebRTC transport that doesn't require the set up a signaling server.\n  - Caveat: you can only establish Browser to Node.js and Node.js to Node.js connections.\n\n**Note:** those are semi-centralized solutions. We are working towards replacing `*-star` with ambient relays and [libp2p-rendezvous](https://github.com/libp2p/js-libp2p-rendezvous). Details and progress can be found [here](https://github.com/libp2p/js-libp2p/issues/385).\n\nYou can find detailed information about running js-ipfs [here](https://github.com/ipfs/js-ipfs#table-of-contents).\n\n## Best Practices\n\n- Configure nodes for using self-hosted `*-star` signalling and transport service. When in doubt, use WebSockets ones.\n- Run your own instance of `*-star` signalling service.\n  The default ones are under high load and should be used only for tests and development.\n- Make sure content added to js-ipfs running in the browser is persisted/cached somewhere on a regular long-running IPFS daemon, e.g. [kubo](https://github.com/ipfs/kubo/)\n  - Manually `pin` or preload CIDs of interest with `refs -r` beforehand.\n  - Preload content on the fly using [preload](https://github.com/ipfs/js-ipfs/blob/master/docs/MODULE.md#optionspreload) feature and/or\n    configure [delegated routing](https://github.com/ipfs/js-ipfs/blob/master/docs/DELEGATE_ROUTERS.md).\n    - Avoid public instances in production environments. Make sure preload and delegate nodes used in config are self-hosted and under your control (expose a subset of [kubo](https://github.com/ipfs/kubo/) (formerly go-ipfs) APIs via reverse proxy such as Nginx).\n- If your main goal is to provide content and files to the IPFS network from a browser and you would like to avoid running infrastructure, consider using a pinning service like [Web3.storage](https://web3.storage/).\n\n## Code Examples\n\nPrebuilt bundles are available, using JS IPFS in the browser is as simple as:\n\n```js\n<script src=\"https://cdn.jsdelivr.net/npm/ipfs/dist/index.min.js\"></script>\n<script>\ndocument.addEventListener('DOMContentLoaded', async () => {\n  const node = await Ipfs.create()\n  const results = await node.add('=^.^= meow meow')\n  const cid = results[0].hash\n  console.log('CID created via ipfs.add:', cid)\n  const data = await node.cat(cid)\n  console.log('Data read back via ipfs.cat:', new TextDecoder().decode(data))\n})\n</script>\n```\n\nMore advanced examples and tutorials can be found in the [examples](https://github.com/ipfs-examples)\n"
  },
  {
    "path": "docs/CLI.md",
    "content": "# IPFS CLI <!-- omit in toc -->\n\n## Table of contents <!-- omit in toc -->\n\n- [Overview](#overview)\n- [Configuration](#configuration)\n\n## Overview\n\nIn order to use js-ipfs as a CLI, you must install it with the `global` flag. Run the following (even if you have ipfs installed locally):\n\n```bash\nnpm install ipfs --global\n```\n\nThe CLI is available by using the command `jsipfs` in your terminal. This is aliased, instead of using `ipfs`, to make sure it does not conflict with the [Go implementation](https://github.com/ipfs/go-ipfs).\n\nOnce installed, please follow the [Getting Started Guide](https://docs.ipfs.io/introduction/usage/) to learn how to initialize your node and run the daemon.\n\n```sh\n# Install js-ipfs globally\n> jsipfs --help\nCommands:\n  bitswap               A set of commands to manipulate the bitswap agent.\n  block                 Manipulate raw IPFS blocks.\n  bootstrap             Show or edit the list of bootstrap peers.\n  commands              List all available commands\n  config <key> [value]  Get and set IPFS config values\n  daemon                Start a long-running daemon process\n# ...\n```\n\n## Configuration\n\n`js-ipfs` uses some different default config values, so that they don't clash directly with a go-ipfs node running in the same machine. These are:\n\n- default repo location: `~/.jsipfs` (can be changed with env variable `IPFS_PATH`)\n- default swarm port: `4002`\n- default API port: `5002`\n"
  },
  {
    "path": "docs/CONFIG.md",
    "content": "# The js-ipfs config file <!-- omit in toc -->\n\nThe js-ipfs config file is a JSON document located in the root directory of the js-ipfs repository.\n\n## Table of Contents <!-- omit in toc -->\n\n- [Profiles](#profiles)\n- [`Addresses`](#addresses)\n  - [`API`](#api)\n  - [`RPC`](#rpc)\n  - [`Delegates`](#delegates)\n  - [`Gateway`](#gateway)\n  - [`Swarm`](#swarm)\n  - [`Announce`](#announce)\n- [`Bootstrap`](#bootstrap)\n- [`Datastore`](#datastore)\n  - [`Spec`](#spec)\n- [`Discovery`](#discovery)\n  - [`MDNS`](#mdns)\n  - [`webRTCStar`](#webrtcstar)\n- [`Identity`](#identity)\n  - [`PeerID`](#peerid)\n  - [`PrivKey`](#privkey)\n- [`Keychain`](#keychain)\n- [`Pubsub`](#pubsub)\n  - [`Router`](#router)\n  - [`Enabled`](#enabled)\n- [`Swarm`](#swarm-1)\n  - [`ConnMgr`](#connmgr)\n  - [`DisableNatPortMap`](#disablenatportmap)\n  - [Example](#example)\n- [`API`](#api-1)\n  - [`HTTPHeaders`](#httpheaders)\n    - [`Access-Control-Allow-Origin`](#access-control-allow-origin)\n      - [Example](#example-1)\n    - [`Access-Control-Allow-Credentials`](#access-control-allow-credentials)\n      - [Example](#example-2)\n\n## Profiles\n\nConfiguration profiles allow to tweak configuration quickly. Profiles can be\napplied with `--profile` flag to `ipfs init` or with the `ipfs config profile\napply` command. When a profile is applied a backup of the configuration file\nwill be created in `$IPFS_PATH`.\n\nAvailable profiles:\n\n- `server`\n\n  Recommended for nodes with public IPv4 address (servers, VPSes, etc.),\n  disables host and content discovery in local networks.\n\n- `local-discovery`\n\n  Sets default values to fields affected by `server` profile, enables\n  discovery in local networks.\n\n- `test`\n\n  Reduces external interference, useful for running ipfs in test environments.\n  Note that with these settings node won't be able to talk to the rest of the\n  network without manual bootstrap.\n\n- `default-networking`\n\n  Restores default network settings. Inverse profile of the `test` profile.\n\n- `lowpower`\n\n  Reduces daemon overhead on the system. May affect node functionality,\n  performance of content discovery and data fetching may be degraded.\n\n- `default-power`\n\n  Inverse of \"lowpower\" profile.\n\n## `Addresses`\n\nContains information about various listener addresses to be used by this node.\n\n### `API`\n\nThe IPFS daemon exposes an HTTP API that allows to control the node and run the same commands as you can do from the command line. It is defined on the [HTTP API Spec](https://docs.ipfs.io/reference/api/http).\n\n[Multiaddr](https://github.com/multiformats/multiaddr/) or array of [Multiaddr](https://github.com/multiformats/multiaddr/) describing the address(es) to serve the HTTP API on.\n\nDefault: `/ip4/127.0.0.1/tcp/5002`\n\n### `RPC`\n\njs-IPFS has a gRPC-over-websockets server that allows it to do things that you cannot do over HTTP like bi-directional streaming.  It implements the same API as the [HTTP API Spec](https://docs.ipfs.io/reference/api/http) and can be accessed using the [ipfs-client](https://www.npmjs.com/package/ipfs-client) module.\n\nConfigure the address it listens on using this config key.\n\nDefault: `/ip4/127.0.0.1/tcp/5003`\n\n### `Delegates`\n\nDelegate peers are used to find peers and retrieve content from the network on your behalf.\n\nArray of [Multiaddr](https://github.com/multiformats/multiaddr/) describing which addresses to use as delegate nodes.\n\nDefault: `[]`\n\n### `Gateway`\n\nA gateway is exposed by the IPFS daemon, which allows an easy way to access content from IPFS, using an IPFS path.\n\n[Multiaddr](https://github.com/multiformats/multiaddr/) or array of [Multiaddr](https://github.com/multiformats/multiaddr/) describing the address(es) to serve the gateway on.\n\nDefault: `/ip4/127.0.0.1/tcp/9090`\n\n### `Swarm`\n\nArray of [Multiaddr](https://github.com/multiformats/multiaddr/) describing which addresses to listen on for p2p swarm connections.\n\nDefault:\n```json\n[\n  \"/ip4/0.0.0.0/tcp/4002\",\n  \"/ip4/127.0.0.1/tcp/4003/ws\"\n]\n```\n\n### `Announce`\n\nArray of [Multiaddr](https://github.com/multiformats/multiaddr/) describing which addresses to [announce](https://github.com/libp2p/js-libp2p/tree/master/src/address-manager#announce-addresses) over the network.\n\nDefault:\n```json\n[]\n```\n\n## `Bootstrap`\n\nBootstrap is an array of [Multiaddr](https://github.com/multiformats/multiaddr/) of trusted nodes to connect to in order to\ninitiate a connection to the network.\n\n## `Datastore`\n\nContains information related to the construction and operation of the on-disk storage system.\n\n### `Spec`\n\nSpec defines the structure of the IPFS datastore. It is a composable structure, where each datastore is represented by a JSON object. Datastores can wrap other datastores to provide extra functionality (e.g. metrics, logging, or caching).\n\nThis can be changed manually, however, if you make any changes that require a different on-disk structure, you will need to run the [ipfs-ds-convert tool](https://github.com/ipfs/ipfs-ds-convert) to migrate data into the new structures.\n\nDefault:\n```json\n{\n  \"mounts\": [\n    {\n      \"child\": {\n        \"path\": \"blocks\",\n        \"shardFunc\": \"/repo/flatfs/shard/v1/next-to-last/2\",\n        \"sync\": true,\n        \"type\": \"flatfs\"\n      },\n      \"mountpoint\": \"/blocks\",\n      \"prefix\": \"flatfs.datastore\",\n      \"type\": \"measure\"\n    },\n    {\n      \"child\": {\n        \"compression\": \"none\",\n        \"path\": \"datastore\",\n        \"type\": \"levelds\"\n      },\n      \"mountpoint\": \"/\",\n      \"prefix\": \"leveldb.datastore\",\n      \"type\": \"measure\"\n    }\n  ],\n  \"type\": \"mount\"\n}\n```\n\n## `Discovery`\n\nContains options for configuring IPFS node discovery mechanisms.\n\n### `MDNS`\n\nMulticast DNS is a discovery protocol that is able to find other peers on the local network.\n\nOptions for Multicast DNS peer discovery:\n\n- `Enabled`\n\n    A boolean value for whether or not MDNS should be active.\n\n    Default: `true`\n\n-  `Interval`\n\n\t  A number of seconds to wait between discovery checks.\n\n    Default: `10`\n\n### `webRTCStar`\n\nWebRTCStar is a discovery mechanism provided by a signalling-star that allows peer-to-peer communications in the browser.\n\nOptions for webRTCstar peer discovery:\n\n- `Enabled`\n\n    A boolean value for whether or not webRTCStar should be active.\n\n    Default: `true`\n\n## `Identity`\n\n### `PeerID`\n\nThe unique PKI identity label for this configs peer. Set on init and never read, its merely here for convenience. IPFS will always generate the peerID from its keypair at runtime.\n\n### `PrivKey`\n\nThe base64 encoded protobuf describing (and containing) the nodes private key.\n\n## `Keychain`\n\nWe can customize the key management and cryptographically protected messages by changing the Keychain options. Those options are used for generating the derived encryption key (`DEK`). The `DEK` object, along with the passPhrase, is the input to a PBKDF2 function.\n\nDefault:\n```json\n{\n  \"dek\": {\n    \"keyLength\": 512/8,\n    \"iterationCount\": 1000,\n    \"salt\": \"at least 16 characters long\",\n    \"hash\": \"sha2-512\"\n  }\n}\n```\n\nYou can check the [parameter choice for pbkdf2](https://cryptosense.com/parameter-choice-for-pbkdf2/) for more information.\n\n## `Pubsub`\n\nOptions for configuring the pubsub subsystem. It is important pointing out that this is not supported in the browser. If you want to configure a different pubsub router in the browser you must configure `libp2p.modules.pubsub` options instead.\n\n### `Router`\n\nA string value for specifying which pubsub routing protocol to use. You can either use `gossipsub` in order to use the [ChainSafe/gossipsub-js](https://github.com/ChainSafe/gossipsub-js) implementation, or `floodsub` to use the [libp2p/js-libp2p-floodsub](https://github.com/libp2p/js-libp2p-floodsub) implementation. You can read more about these implementations on the [libp2p/specs/pubsub](https://github.com/libp2p/specs/tree/master/pubsub) document.\n\nDefault: `gossipsub`\n\n### `Enabled`\n\nA boolean value for wether or not pubsub router should be active.\n\nDefault: `true`\n\n## `Swarm`\n\nOptions for configuring the swarm.\n\n### `ConnMgr`\n\nThe connection manager determines which and how many connections to keep and can be configured to keep.\n\n- `LowWater`\n\n    The minimum number of connections to maintain.\n\n    Default: `200` (both browser and node.js)\n\n- `HighWater`\n\n    The number of connections that, when exceeded, will trigger a connection GC operation.\n\n    Default: `500` (both browser and node.js)\n\nThe \"basic\" connection manager tries to keep between `LowWater` and `HighWater` connections. It works by:\n\n1. Keeping all connections until `HighWater` connections is reached.\n2. Once `HighWater` is reached, it closes connections until `LowWater` is reached.\n\n### `DisableNatPortMap`\n\nBy default when running under nodejs, libp2p will try to use [UPnP](https://en.wikipedia.org/wiki/Universal_Plug_and_Play) to open a random high port on your router for any TCP connections you have configured.\n\nSet `DisableNatPortMap` to `true` to disable this behaviour.\n\n### Example\n\n```json\n{\n  \"Swarm\": {\n    \"ConnMgr\": {\n      \"LowWater\": 100,\n      \"HighWater\": 200,\n    }\n  },\n  \"DisableNatPortMap\": false\n}\n```\n\n## `API`\n\nSettings applied to the HTTP RPC API server\n\n### `HTTPHeaders`\n\nHTTP header settings used by the HTTP RPC API server\n\n#### `Access-Control-Allow-Origin`\n\nThe RPC API endpoints running on your local node are protected by the [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) mechanism.\n\nWhen a request is made that sends an `Origin` header, that Origin must be present in the allowed origins configured for the node, otherwise the browser will disallow that request to proceed, unless `mode: 'no-cors'` is set on the request, in which case the response will be opaque.\n\nTo allow requests from web browsers, configure the `API.HTTPHeaders.Access-Control-Allow-Origin` setting.  This is an array of URL strings with safelisted Origins.\n\n##### Example\n\nIf you are running a webapp locally that you access via the URL `http://127.0.0.1:3000`, you must add it to the list of allowed origins in order to make API requests from that webapp in the browser:\n\n```json\n{\n  \"API\": {\n    \"HTTPHeaders\": {\n      \"Access-Control-Allow-Origin\": [\n        \"http://127.0.0.1:3000\"\n      ]\n    }\n  }\n}\n```\n\nNote that the origin must match exactly so `'http://127.0.0.1:3000'` is treated differently to `'http://127.0.0.1:3000/'`\n\n#### `Access-Control-Allow-Credentials`\n\nThe [Access-Control-Allow-Credentials](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials) header allows client-side JavaScript running in the browser to send and receive credentials with requests - cookies, auth headers or TLS certificates.\n\nFor most applications this will not be necessary but if you require this to be set, see the example below for how to configure it.\n\n##### Example\n\n```json\n{\n  \"API\": {\n    \"HTTPHeaders\": {\n      \"Access-Control-Allow-Credentials\": true\n    }\n  }\n}\n```\n"
  },
  {
    "path": "docs/CORS.md",
    "content": "# CORS <!-- omit in toc -->\n\n## Table of Contents <!-- omit in toc -->\n\n- [Overview](#overview)\n- [Configure CORS headers](#configure-cors-headers)\n\n## Overview\n\nCross-origin Resource Sharing is a browser security mechanism that prevents unauthorized scripts from accessing resources from different domains.\n\nBy default the HTTP RPC API of js-IPFS will cause any request sent from a CORS-respecting browser to fail.\n\n## Configure CORS headers\n\nYou can configure your node to allow requests from other domains to proceed by setting the appropriate headers in the node config:\n\n```console\n$ jsipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin  '[\"http://example.com\"]'\n$ jsipfs config --json API.HTTPHeaders.Access-Control-Allow-Methods '[\"PUT\", \"POST\", \"GET\"]'\n```\n\nRestart the daemon for the settings to take effect.\n"
  },
  {
    "path": "docs/DAEMON.md",
    "content": "\n# Running IPFS as a daemon <!-- omit in toc -->\n\n> How to run a long-lived IPFS process\n\n## Table of contents <!-- omit in toc -->\n\n- [CLI](#cli)\n- [Programmatic](#programmatic)\n\n## CLI\n\nTo start a daemon on the CLI, use the `daemon` command:\n\n```console\njsipfs daemon\n```\n\nThe IPFS Daemon exposes the API defined in the [HTTP API spec](https://docs.ipfs.io/reference/api/http/). You can use any of the IPFS HTTP-API client libraries with it, such as: [ipfs-http-client](https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-http-client).\n\n## Programmatic\n\nIf you want a programmatic way to spawn a IPFS Daemon using JavaScript, check out the [ipfsd-ctl](https://github.com/ipfs/js-ipfsd-ctl) module.\n\n```javascript\nimport { createFactory } from 'ipfsd-ctl'\nconst factory = createFactory({\n  type: 'proc' // or 'js' to run in a separate process\n})\n\nconst node = await factory.create()\n\n// print the node ide\nconsole.info(await node.id())\n```\n"
  },
  {
    "path": "docs/DELEGATE_ROUTERS.md",
    "content": "# Configuring Delegate Routers <!-- omit in toc -->\n\n- [What is it?](#what-is-it)\n- [How do I do it?](#how-do-i-do-it)\n\n## What is it?\n\nDelegate routers perform tasks on behalf of nodes that may be missing functionality, so for example they may search the DHT for peers or content providers on behalf of IPFS implementations that do not have a DHT.\n\nThe delegate node is started and the client of the delegate calls API methods using the IPFS HTTP API client.\n\n## How do I do it?\n\nIf you need to support Delegated Content and/or Peer Routing, you can enable it by specifying the multiaddrs of your delegate nodes in the config via `options.config.Addresses.Delegates`. If you need to run a delegate router we encourage you to run your own, with go-ipfs. You can see instructions for doing so in the [delegated routing example](https://github.com/libp2p/js-libp2p/tree/master/examples/delegated-routing).\n\nIf you are not able to run your own delegate router nodes, we currently have two nodes that support delegated routing. **Important**: As many people may be leveraging these nodes, performance may be affected, which is why we recommend running your own nodes in production.\n\nAvailable delegate multiaddrs are:\n\n- `/dns4/node0.delegate.ipfs.io/tcp/443/https`\n- `/dns4/node1.delegate.ipfs.io/tcp/443/https`\n- `/dns4/node2.delegate.ipfs.io/tcp/443/https`\n- `/dns4/node3.delegate.ipfs.io/tcp/443/https`\n\n**Note**: If more than 1 delegate multiaddr is specified, the actual delegate will be randomly selected on startup.\n\n**Note**: If you wish to use delegated routing and are creating your node _programmatically_ in Node.js or the browser you must `npm install libp2p-delegated-content-routing` and/or `npm install libp2p-delegated-peer-routing` and provide configured instances of them in [`options.libp2p`](./MODULE.md#optionslibp2p). See the module repos for further instructions:\n\n- https://github.com/libp2p/js-libp2p-delegated-content-routing\n- https://github.com/libp2p/js-libp2p-delegated-peer-routing\n"
  },
  {
    "path": "docs/DEVELOPMENT.md",
    "content": "# Development <!-- omit in toc -->\n\n> Getting started with development on IPFS\n\n- [Install npm@7](#install-npm7)\n- [Clone and install dependencies](#clone-and-install-dependencies)\n- [Run tests](#run-tests)\n- [Lint](#lint)\n- [Build types and minified browser bundles](#build-types-and-minified-browser-bundles)\n- [Publishing new versions](#publishing-new-versions)\n- [Using prerelease versions](#using-prerelease-versions)\n- [Testing strategy](#testing-strategy)\n  - [CLI](#cli)\n  - [HTTP API](#http-api)\n  - [Core](#core)\n  - [Non-Core](#non-core)\n\n## Install npm@7\n\nThis project uses a [workspace](https://docs.npmjs.com/cli/v7/using-npm/workspaces) structure so requires npm@7 or above.  If you are running node 15 or later you already have it, if not run:\n\n```sh\n$ npm install -g npm@latest\n```\n\n## Clone and install dependencies\n\n```sh\n> git clone https://github.com/ipfs/js-ipfs.git\n> cd js-ipfs\n> npm install\n```\n\nThis will install the dependencies of the various packages, deduping and hoisting dependencies into the root folder.\n\nIf later you add new dependencies to submodules or just wish to remove all the `node_modules`/`dist` folders and start again, run `npm run reset && npm install` from the root.\n\nSee the scripts section of the root [`package.json`](../package.json) for more commands.\n\n## Run tests\n\n```sh\n# run all the unit tests\n> npm test\n\n# run individual tests (findprovs)\n> npm run test -- --grep findprovs\n\n# run just IPFS tests in Node.js\n> npm run test -- -- -- -t node\n\n# run just IPFS tests in a headless browser\n> npm run test -- -- -- -t browser\n\n# run the interface tests against ipfs-core\n> npm run test:interface:core\n\n# run the interface tests over HTTP against js-ipfs\n> npm run test:interface:http-js\n\n# run the interface tests over HTTP against go-ipfs from a browser\n> npm run test:interface:http-go -- -- -- -t browser\n\n# run the interop tests against js-ipfs and go-ipfs on the Electron main process\n> npm run test:interop -- -- -- -t electron-main\n```\n\nMore granular test suites can be run from each submodule.\n\nPlease see the `package.json` in each submodule for available commands.\n\n## Lint\n\nPlease run the linter before submitting a PR, the build will not pass if it fails:\n\n```sh\n> npm run lint\n```\n\n## Build types and minified browser bundles\n\n```sh\n> npm run build\n```\n\n## Publishing new versions\n\n1. Ensure you have a `GH_TOKEN` env var containing a GitHub [Personal Access Token](https://github.com/settings/tokens) with `public_repo` permissions\n2. You'll also need a valid [Docker Hub](https://hub.docker.com) login with sufficient permissions to publish new Docker images to the [ipfs/js-ipfs](https://hub.docker.com/repository/docker/ipfs/js-ipfs) repository\n3. From the root of this repo run `npm run release` and follow the on screen prompts.  It will use [conventional commits](https://www.conventionalcommits.org) to work out the new package version\n\n## Using prerelease versions\n\nAny changed packages from each successful build of master are published to npm as canary builds under the npm tag `next`.\n\n## Testing strategy\n\nThis project has a number of components that have their own tests, then some components that share interface tests.\n\nWhen adding new features you may need to add tests to one or more of the test suites described below.\n\n### CLI\n\nTests live in [/packages/ipfs/test/cli](https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs/test/cli).\n\nAll interactions with IPFS core are stubbed so we just ensure that the correct arguments are passed in\n\n### HTTP API\n\nTests live in [/packages/ipfs/test/http-api](https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs/test/http-api) and are similar to the CLI tests in that we stub out core interactions and inject requests with [shot](https://www.npmjs.com/package/@hapi/shot).\n\n### Core\n\nAnything non-implementation specific should be considered part of the 'Core API'.  For example node setup code is not Core, but anything that does useful work, e.g. network/repo/etc interactions would be Core.\n\nAll Core APIs should be documented in [/docs/core-api](https://github.com/ipfs/js-ipfs/tree/master/docs/core-api).\n\nAll Core APIs should have comprehensive tests in [/packages/interface-ipfs-core](https://github.com/ipfs/js-ipfs/tree/master/packages/interface-ipfs-core).\n\n`interface-ipfs-core` should ensure API compatibility across implementations. Tests are run:\n\n1. Against [/packages/ipfs/src/core](https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs/src/core) directly\n1. Against [/packages/ipfs/src/http](https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs/src/http) over HTTP via `ipfs-http-client`\n1. Against `go-ipfs` over HTTP via `ipfs-http-client`\n\n### Non-Core\n\nAny non-core API functionality should have tests in the `tests` directory of the module in question, for example: [/packages/ipfs-http-api/tests](https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-http-client/test) and [/packages/ipfs/tests](https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs/test) for `ipfs-http-client` and `ipfs` respectively.\n"
  },
  {
    "path": "docs/DOCKER.md",
    "content": "\n# Running js-ipfs with Docker\n\nWe have automatic Docker builds setup with Docker Hub: https://hub.docker.com/r/ipfs/js-ipfs/\n\nAll branches in the Github repository maps to a tag in Docker Hub, except `master` Git branch which is mapped to `latest` Docker tag.\n\nYou can run js-ipfs like this:\n\n```\n$ docker run -it -p 4002:4002 -p 4003:4003 -p 5002:5002 -p 9090:9090 ipfs/js-ipfs:latest\n\ninitializing ipfs node at /root/.jsipfs\ngenerating 2048-bit RSA keypair...done\npeer identity: Qmbd5jx8YF1QLhvwfLbCTWXGyZLyEJHrPbtbpRESvYs4FS\nto get started, enter:\n\n         jsipfs files cat /ipfs/QmfGBRT6BbWJd7yUc2uYdaUZJBbnEFvTqehPFoSMQ6wgdr/readme\n\nInitializing daemon...\nUsing wrtc for webrtc support\nSwarm listening on /ip4/127.0.0.1/tcp/4003/ws/ipfs/Qmbd5jx8YF1QLhvwfLbCTWXGyZLyEJHrPbtbpRESvYs4FS\nSwarm listening on /ip4/172.17.0.2/tcp/4003/ws/ipfs/Qmbd5jx8YF1QLhvwfLbCTWXGyZLyEJHrPbtbpRESvYs4FS\nSwarm listening on /ip4/127.0.0.1/tcp/4002/ipfs/Qmbd5jx8YF1QLhvwfLbCTWXGyZLyEJHrPbtbpRESvYs4FS\nSwarm listening on /ip4/172.17.0.2/tcp/4002/ipfs/Qmbd5jx8YF1QLhvwfLbCTWXGyZLyEJHrPbtbpRESvYs4FS\nAPI is listening on: /ip4/0.0.0.0/tcp/5002\nGateway (readonly) is listening on: /ip4/0.0.0.0/tcp/9090\nDaemon is ready\n\n$ curl --silent localhost:5002/api/v0/id | jq .ID\n\"Qmbd5jx8YF1QLhvwfLbCTWXGyZLyEJHrPbtbpRESvYs4FS\"\n```\n"
  },
  {
    "path": "docs/EARLY_TESTERS.md",
    "content": "# Early Testers Programme <!-- omit in toc -->\n\n- [What is it?](#what-is-it)\n- [What are the expectations?](#what-are-the-expectations)\n- [Who has signed up?](#who-has-signed-up)\n- [How to sign up?](#how-to-sign-up)\n\n## What is it?\n\nThe early testers programme allows groups using js-ipfs in production to self-volunteer to help test js-ipfs release candidates to ensure that no regressions that might affect production systems make it into the final release. While we invite the _entire_ community to help test releases, members of the early testers program are expected to participate directly and actively in every release.\n\n## What are the expectations?\n\nMembers of the early tester program are expected to work closely with us to:\n\n* Provide high quality, actionable feedback.\n* Work directly with us to debug regressions in the release.\n* Help ensure a rock-solid, timely release.\n\nWe will ask early testers to participate at two points in the process:\n\n* When js-ipfs enters the second release stage, early testers will be asked to test js-ipfs on non-production infrastructure. This may involve things like:\n  - Running integration tests against the release candidate.\n  - Running simulations/benchmarks on the release candidate.\n  - Manually testing the release candidate to check for regressions.\n* When js-ipfs enters the third release stage (soft release), early testers will be asked to partially deploy the release candidate to production infrastructure. Release candidates at this stage are expected to be identical to the final release. However, this stage allows the js-ipfs team to fix any last-minute regressions without cutting an entirely new release.\n\n## Who has signed up?\n\n- [npm-on-ipfs](https://github.com/ipfs-shipyard/npm-on-ipfs) - install your dependencies via the distributed web!\n- [orbit-db](https://github.com/orbitdb/orbit-db) - Peer-to-Peer Databases for the Decentralized Web\n- [ipfs-log](https://github.com/orbitdb/ipfs-log) - Append-only log CRDT on IPFS\n- [Sidetree DID Protocol](https://github.com/decentralized-identity/sidetree) - Decentralized Identifier Layer-2 network protocol\n- [Constellation](https://julienmalard.github.io/constellation/) - Distributed scientific databases for citizen science and more\n\n## How to sign up?\n\nSimply submit a PR to this document by adding your project name and contact.\n"
  },
  {
    "path": "docs/FAQ.md",
    "content": "# FAQ <!-- omit in toc -->\n\n## Table of Contents <!-- omit in toc -->\n\n- [Why isn't there DHT support in js-IPFS?](#why-isnt-there-dht-support-in-js-ipfs)\n  - [Node.js](#nodejs)\n  - [Browser](#browser)\n- [How to enable WebRTC support for js-ipfs in the Browser](#how-to-enable-webrtc-support-for-js-ipfs-in-the-browser)\n- [Is there WebRTC support for js-ipfs with Node.js?](#is-there-webrtc-support-for-js-ipfs-with-nodejs)\n- [How can I configure an IPFS node to use a custom `signaling endpoint` for my WebRTC transport?](#how-can-i-configure-an-ipfs-node-to-use-a-custom-signaling-endpoint-for-my-webrtc-transport)\n- [I see some slowness when hopping between tabs Chrome with IPFS nodes, is there a reason why?](#i-see-some-slowness-when-hopping-between-tabs-chrome-with-ipfs-nodes-is-there-a-reason-why)\n- [Can I use IPFS in my Electron App?](#can-i-use-ipfs-in-my-electron-app)\n- [What are all these `refs?Qmfoo` HTTP errors I keep seeing in the console?](#what-are-all-these-refsqmfoo-http-errors-i-keep-seeing-in-the-console)\n- [Have more questions?](#have-more-questions)\n\n## Why isn't there DHT support in js-IPFS?\n\nThere is DHT support for js-IPFS in the form of [libp2p/js-libp2p-kad-dht](https://github.com/libp2p/js-libp2p-kad-dht) but it is not finished yet, and may not be the right solution to the problem.\n\n### Node.js\n\nTo enable DHT support, before starting your daemon run:\n\n```console\n$ jsipfs config Routing.Type dht\n```\n\nThe possible values for `Routing.Type` are:\n\n -  `'none'` the default, this means the DHT is turned off any you must manually dial other nodes\n -  `'dht'` start the node in DHT client mode, if it is discovered to be publicly dialable it will automatically switch to server mode\n -  `'dhtclient'` A DHT client is able to make DHT queries but will not respond to any\n -  `'dhtserver'` A DHT server can make and respond to DHT queries.  Please only choose this option if your node is dialable from the open Internet.\n\nAt the time of writing, only DHT client mode is supported and will be selected if `Routing.Type` is not `'none'`.\n\n### Browser\n\nIn the browser there are many constraints that mean the environment does not typically make for good DHT participants - the number of connections required is high, people do not tend to stay on a page for long enough to make or answer DHT queries, and even if they did, most nodes on the network talk TCP - the browser can neither open TCP ports on remote hosts nor accept TCP connections.\n\nA better approach may be to set up [Delegate Routing](./DELEGATE_ROUTERS.md) to use remote go-IPFS to make queries on the browsers' behalf as these do not have the same constraints.\n\nOf course, there's no reason why js on the server should not be a fully fledged DHT participant, please help out on the [libp2p/js-libp2p-kad-dht](https://github.com/libp2p/js-libp2p-kad-dht) repo to make this a reality!\n\n## How to enable WebRTC support for js-ipfs in the Browser\n\nTo add a WebRTC transport to your js-ipfs node, you must add a WebRTC multiaddr. To do that, simple override the config.Addresses.Swarm array which contains all the multiaddrs which the IPFS node will use. See below:\n\n```JavaScript\nconst node = await IPFS.create({\n  config: {\n    Addresses: {\n      Swarm: [\n        '/dns4/wrtc-star1.par.dwebops.pub/tcp/443/wss/p2p-webrtc-star',\n        '/dns4/wrtc-star2.sjc.dwebops.pub/tcp/443/wss/p2p-webrtc-star'\n      ]\n    }\n  }\n})\n\n// your instance with WebRTC is ready\n```\n\n**Important:** This transport usage is kind of unstable and several users have experienced crashes. Track development of a solution at https://github.com/ipfs/js-ipfs/issues/1088.\n\n## Is there WebRTC support for js-ipfs with Node.js?\n\nYes, however, bear in mind that there isn't a 100% stable solution to use WebRTC in Node.js, use it at your own risk. The most tested options are:\n\n- [wrtc](https://npmjs.org/wrtc) - Follow the install instructions.\n- [electron-webrtc](https://npmjs.org/electron-webrtc)\n\nTo add WebRTC support in a IPFS node instance, do:\n\n```JavaScript\nimport wrtc from 'wrtc' // or 'electron-webrtc'\nimport WebRTCStar from '@libp2p/webrtc-star'\n\nconst node = await IPFS.create({\n  repo: 'your-repo-path',\n  config: {\n    Addresses: {\n      Swarm: [\n        \"/ip4/0.0.0.0/tcp/4002\",\n        \"/ip4/127.0.0.1/tcp/4003/ws\",\n        \"/dns4/wrtc-star1.par.dwebops.pub/tcp/443/wss/p2p-webrtc-star\",\n        \"/dns4/wrtc-star2.sjc.dwebops.pub/tcp/443/wss/p2p-webrtc-star\"\n      ]\n    }\n  },\n  libp2p: {\n    modules: {\n      transport: [WebRTCStar]\n    },\n    config: {\n      peerDiscovery: {\n        webRTCStar: { // <- note the lower-case w - see https://github.com/libp2p/js-libp2p/issues/576\n          enabled: true\n        }\n      },\n      transport: {\n        WebRTCStar: { // <- note the upper-case w- see https://github.com/libp2p/js-libp2p/issues/576\n          wrtc\n        }\n      }\n    }\n  }\n})\n\n// your instance with WebRTC is ready\n```\n\nTo add WebRTC support to the IPFS daemon, you only need to install one of the WebRTC modules globally:\n\n```bash\nnpm install wrtc --global\n# or\nnpm install electron-webrtc --global\n```\n\nThen, update your IPFS Daemon config to include the multiaddr for this new transport on the `Addresses.Swarm` array. Add: `\"/dns4/wrtc-star.discovery.libp2p.io/wss/p2p-webrtc-star\"`\n\n## How can I configure an IPFS node to use a custom `signaling endpoint` for my WebRTC transport?\n\nYou'll need to execute a compatible `signaling server` ([libp2p-webrtc-star](https://github.com/libp2p/js-libp2p-webrtc-star) works) and include the correct configuration param for your IPFS node:\n\n- provide the [`multiaddr`](https://github.com/multiformats/multiaddr) for the `signaling server`\n\n```JavaScript\nconst node = await IPFS.create({\n  repo: 'your-repo-path',\n  config: {\n    Addresses: {\n      Swarm: [\n        '/ip4/127.0.0.1/tcp/9090/ws/p2p-webrtc-star'\n      ]\n    }\n  }\n})\n```\n\nThe code above assumes you are running a local `signaling server` on port `9090`. Provide the correct values accordingly.\n\n## I see some slowness when hopping between tabs Chrome with IPFS nodes, is there a reason why?\n\nYes, unfortunately, due to [Chrome aggressive resource throttling policy](https://github.com/ipfs/js-ipfs/issues/611), it cuts freezes the execution of any background tab, turning an IPFS node that was running on that webpage into a vegetable state.\n\nA way to mitigate this in Chrome, is to run your IPFS node inside a Service Worker, so that the IPFS instance runs in a background process. You can learn how to install an IPFS node as a service worker in here the repo [ipfs-service-worker](https://github.com/ipfs/ipfs-service-worker)\n\n## Can I use IPFS in my Electron App?\n\nYes you can and in many ways. Read https://github.com/ipfs/notes/issues/256 for the multiple options.\n\nWe now support Electron v5.0.0 without the need to rebuilt native modules.\nStill if you run into problems with native modules follow these instructions [here](https://electronjs.org/docs/tutorial/using-native-node-modules).\n\n## What are all these `refs?Qmfoo` HTTP errors I keep seeing in the console?\n\nIn order for content added to your node to be accessible to other nodes on the network, they need to be able to [dial](https://github.com/ipfs/js-ipfs/blob/master/docs/core-api/SWARM.md#ipfsswarmconnectaddr-options) your node. This means there needs to be some way of connecting to you from the open Internet.\n\nFrom node.js and Electron this might be done by opening a TCP port on your router and forwarding traffic to your node, while also configuring an [Announce](https://github.com/ipfs/js-ipfs/blob/master/docs/CONFIG.md#announce) address that is a combination of the forwarded port and your public IP address.\n\nBrowsers [can't open TCP sockets](https://github.com/ipfs/js-ipfs/blob/master/docs/BROWSERS.md#limitations-of-the-browser-context) so the only way right now is for your node to be connected to a WebRTC-Star signalling server - nodes interested in your content would connect to the same WebRTC-Star server and use that to negotiate a peer-to-peer connection.\n\nThis has several drawbacks - WebRTC is expensive so having lots of peers does not scale well, the maximum packet size is small so it's comparatively inefficient, browsers will frequently cull connections if you switch away from the tab and at the time of writing go-IPFS [has no WebRTC-Star transport](https://libp2p.io/implementations/#transports) so great swathes of the network will not be able to dial your node.\n\nTo make your content available, several 'preload' nodes are running. These nodes expose their [refs endpoint](https://docs.ipfs.io/reference/http/api/#api-v0-refs) over HTTP and all js-IPFS nodes connect to them as peers on startup.\n\nWhen you add content to your node, a request is sent to a preload node with the CID of the content you've just added. This causes the preload node to use [Bitswap](https://docs.ipfs.io/concepts/bitswap/) to pull the content from your node, caching it for an hour or so which then means other nodes can then access the content without having to dial your otherwise undialable node.\n\nThese nodes sometimes go down, which is why you see errors in the console. They are non-fatal and can be ignored.\n\nIf you run your own node you can [disable preloading](https://github.com/ipfs/js-ipfs/blob/master/docs/MODULE.md#optionspreload) which will make the errors go away, at the cost of your content becoming less available or not available at all.\n\n## Have more questions?\n\nAsk for help in our forum at https://discuss.ipfs.io or in IRC (#ipfs on Freenode).\n"
  },
  {
    "path": "docs/IPLD.md",
    "content": "# IPLD Codecs <!-- omit in toc -->\n\n## Table of Contents <!-- omit in toc -->\n\n- [Overview](#overview)\n- [Bundled BlockCodecs](#bundled-blockcodecs)\n- [Bundled Multihashes](#bundled-multihashes)\n- [Bundled Multibases](#bundled-multibases)\n- [Adding additional BlockCodecs, Multihashes and Multibases](#adding-additional-blockcodecs-multihashes-and-multibases)\n- [Next steps](#next-steps)\n\n## Overview\n\nThe IPFS repo contains a blockstore that holds the data that makes up the files on the IPFS network. These blocks can be thought of as a [CID][] and associated byte array.\n\nThe [CID][] contains a `code` property that lets us know how to interpret the byte array associated with it.\n\nIn order to perform that interpretation, a [BlockCodec][] must be loaded that corresponds to the `code` property of the [CID][].\n\nSimilarly implementations of [Multihash][]es or [Multibase][]s must be available to be used.\n\n## Bundled BlockCodecs\n\njs-IPFS ships with four bundled codecs, the ones that are required to create and interpret [UnixFS][] structures.\n\nThese are:\n\n1. [@ipld/dag-pb](https://github.com/ipld/js-dag-pb) - used for file and directory structures\n2. [raw](https://github.com/multiformats/js-multiformats/blob/master/src/codecs/raw.js) - used for file data where imported with `--raw-leaves=true`\n3. [@ipld/dag-cbor](https://github.com/ipld/js-dag-cbor) - used for storage of JavaScript Objects with [CID] links to other blocks\n4. [json](https://github.com/multiformats/js-multiformats/blob/master/src/codecs/json.js) - used for storage of plain JavaScript Objects\n\n## Bundled Multihashes\n\njs-IPFS ships with all multihashes [exported by js-multiformats](https://github.com/multiformats/js-multiformats/tree/master/src/hashes), including `sha2-256` and others.\n\nAdditional hashers can be configured using the `hashers` config property.\n\n## Bundled Multibases\n\njs-IPFS ships with all multibases [exported by js-multiformats](https://github.com/multiformats/js-multiformats/tree/master/src/bases), including `base58btc`, `base32` and others.\n\nAdditional bases can be configured using the `bases` config property.\n\n## Adding additional BlockCodecs, Multihashes and Multibases\n\nIf your application requires support for extra codecs, you can configure them as follows:\n\n1. Configure the [IPLD layer](https://github.com/ipfs/js-ipfs/blob/master/packages/ipfs/docs/MODULE.md#optionsipld) of your IPFS daemon to support the codec. This step is necessary so the node knows how to prepare data received over HTTP to be passed to IPLD for serialization:\n\n    ```javascript\n    import { create } from 'ipfs'\n    import customBlockCodec from 'custom-blockcodec'\n    import customMultibase from 'custom-multibase'\n    import customMultihasher from 'custom-multihasher'\n\n    const node = await create({\n      ipld: {\n        // either specify BlockCodecs as part of the `codecs` list\n        codecs: [\n          customBlockCodec\n        ],\n\n        // and/or supply a function to load them dynamically\n        loadCodec: async (codecNameOrCode) => {\n          return import(codecNameOrCode)\n        },\n\n        // either specify Multibase codecs as part of the `bases` list\n        bases: [\n          customMultibase\n        ],\n\n        // and/or supply a function to load them dynamically\n        loadBase: async (baseNameOrCode) => {\n          return import(baseNameOrCode)\n        },\n\n        // either specify Multihash hashers as part of the `hashers` list\n        hashers: [\n          customMultihasher\n        ],\n\n        // and/or supply a function to load them dynamically\n        loadHasher: async (hashNameOrCode) => {\n          return import(hashNameOrCode)\n        }\n      }\n    })\n   ```\n\n2. Configure your IPFS HTTP API Client to support the codec. This is necessary so that the client can send the data to the IPFS node over HTTP:\n\n    ```javascript\n    import { create } from 'ipfs-http-client'\n    import customBlockCodec from 'custom-blockcodec'\n    import customMultibase from 'custom-multibase'\n    import customMultihasher from 'custom-multihasher'\n\n    const client = create({\n      url: 'http://127.0.0.1:5002',\n      ipld: {\n        // either specify BlockCodecs as part of the `codecs` list\n        codecs: [\n          customBlockCodec\n        ],\n\n        // and/or supply a function to load them dynamically\n        loadCodec: async (codecNameOrCode) => {\n          return import(codecNameOrCode)\n        },\n\n        // either specify Multibase codecs as part of the `bases` list\n        bases: [\n          customMultibase\n        ],\n\n        // and/or supply a function to load them dynamically\n        loadBase: async (baseNameOrCode) => {\n          return import(baseNameOrCode)\n        },\n\n        // either specify Multihash hashers as part of the `hashers` list\n        hashers: [\n          customMultihasher\n        ],\n\n        // and/or supply a function to load them dynamically\n        loadHasher: async (hashNameOrCode) => {\n          return import(hashNameOrCode)\n        }\n      }\n    })\n    ```\n\n## Next steps\n\n* See [examples/custom-ipld-formats](https://github.com/ipfs-examples/js-ipfs-examples/tree/master/examples/custom-ipld-formats) for runnable code that demonstrates the above with in-process IPFS nodes, IPFS run as a daemon and also the http client\n* Also [examples/traverse-ipld-graphs](https://github.com/ipfs-examples/js-ipfs-examples/tree/master/examples/traverse-ipld-graphs) which uses the [ipld-format-to-blockcodec](https://www.npmjs.com/package/ipld-format-to-blockcodec) module to use older [IPLD format][]s that have not been ported over to the new [BlockCodec][] interface, as well as additional [Multihash Hashers](https://www.npmjs.com/package/multiformats#multihash-hashers).\n\n[cid]: https://docs.ipfs.io/concepts/content-addressing/\n[blockcodec]: https://www.npmjs.com/package/multiformats#multicodec-encoders--decoders--codecs\n[unixfs]: https://github.com/ipfs/specs/blob/master/UNIXFS.md\n[ipld format]: https://github.com/ipld/interface-ipld-format\n[multihash]: https://github.com/multiformats/multihash\n[multibase]: https://github.com/multiformats/multibase"
  },
  {
    "path": "docs/MIGRATION-TO-ASYNC-AWAIT.md",
    "content": "# Migrating to the new JS IPFS Core API in 0.48.0 <!-- omit in toc -->\n\nA migration guide for refactoring your application code to use the new JS IPFS core API.\n\nImpact key:\n\n* 🍏 easy - simple refactoring in application code\n* 🍋 medium - involved refactoring in application code\n* 🍊 hard - complicated refactoring in application code\n\n## Table of Contents <!-- omit in toc -->\n\n- [Migrating from callbacks](#migrating-from-callbacks)\n- [Migrating from `PeerId`](#migrating-from-peerid)\n- [Migrating from `PeerInfo`](#migrating-from-peerinfo)\n- [Migrating to Async Iterables](#migrating-to-async-iterables)\n  - [From Node.js Streams](#from-nodejs-streams)\n    - [Node.js Readable Streams](#nodejs-readable-streams)\n    - [Piping Node.js Streams](#piping-nodejs-streams)\n    - [Node.js Transform Streams](#nodejs-transform-streams)\n  - [From Pull Streams](#from-pull-streams)\n    - [Source Pull Streams](#source-pull-streams)\n    - [Pull Stream Pipelines](#pull-stream-pipelines)\n    - [Transform Pull Streams](#transform-pull-streams)\n  - [From buffering APIs](#from-buffering-apis)\n- [Migrating from `addFromFs`](#migrating-from-addfromfs)\n- [Migrating from `addFromURL`](#migrating-from-addfromurl)\n- [Migrating from `addFromStream`](#migrating-from-addfromstream)\n\n## Migrating from callbacks\n\nCallbacks are no longer supported in the API. If your application primarily uses callbacks you have two main options for migration:\n\n**Impact 🍊**\n\nSwitch to using the promise API with async/await. Instead of program continuation in a callback, continuation occurs after the async call and functions from where the call is made are changed to be async functions.\n\ne.g.\n\n```js\nfunction main () {\n  ipfs.id((err, res) => {\n    console.log(res)\n  })\n}\nmain()\n```\n\nBecomes:\n\n```js\nasync function main () {\n  const res = await ipfs.id()\n  console.log(res)\n}\nmain()\n```\n\n**Impact 🍏**\n\nAlternatively you could \"callbackify\" the API. In this case you use a module to convert the promise API to a callback API either permanently or in an interim period.\n\ne.g.\n\n```js\nfunction main () {\n  ipfs.id((err, res) => {\n    console.log(res)\n  })\n}\nmain()\n```\n\nBecomes:\n\n```js\nconst callbackify = require('callbackify')\nconst ipfsId = callbackify(ipfs.id)\n\nasync function main () {\n  ipfsId((err, res) => {\n    console.log(res)\n  })\n}\nmain()\n```\n\n## Migrating from `PeerId`\n\nLibp2p `PeerId` instances are no longer returned from the API. If your application is using the crypto capabilities of [`PeerId`](https://github.com/libp2p/js-peer-id) instances then you'll want to convert the peer ID `string` returned by the new API back into libp2p `PeerId` instances.\n\n**Impact 🍏**\n\nPeer ID strings are also CIDs so converting them is simple:\n\n```js\nconst peerId = PeerId.createFromB58String(peerIdStr)\n```\n\nYou can get hold of the `PeerId` class using npm or in a script tag:\n\n```js\nimport { PeerId } from '@libp2p/interface-peer-id'\nconst peerId = PeerId.createFromB58String(peerIdStr)\n```\n\n```html\n<script src=\"https://unpkg.com/peer-id/dist/index.min.js\"></script>\n<script>\n  const peerId = window.PeerId.createFromB58String(peerIdStr)\n</script>\n```\n\n## Migrating from `PeerInfo`\n\nLibp2p `PeerInfo` instances are no longer returned from the API. Instead, plain objects of the form `{ id: string, addrs: Multiaddr[] }` are returned. To convert these back into a `PeerInfo` instance:\n\n**Impact 🍏**\n\nInstantiate a new `PeerInfo` and add addresses to it:\n\n```js\nconst peerInfo = new PeerInfo(PeerId.createFromB58String(info.id))\ninfo.addrs.forEach(addr => peerInfo.multiaddrs.add(addr))\n```\n\nYou can get hold of the `PeerInfo` class using npm or in a script tag:\n\n```js\nconst PeerInfo = require('peer-info')\nimport { PeerId } from '@libp2p/interface-peer-id'\nconst peerInfo = new PeerInfo(PeerId.createFromB58String(info.id))\ninfo.addrs.forEach(addr => peerInfo.multiaddrs.add(addr))\n```\n\n```html\n<script src=\"https://unpkg.com/peer-info/dist/index.min.js\"></script>\n<script src=\"https://unpkg.com/peer-id/dist/index.min.js\"></script>\n<script>\n  const peerInfo = new window.PeerInfo(window.PeerId.createFromB58String(info.id))\n  info.addrs.forEach(addr => peerInfo.multiaddrs.add(addr))\n</script>\n```\n\n## Migrating to Async Iterables\n\nAsync Iterables are a language native way of streaming data. The IPFS core API has previously supported two different stream implementations - Pull Streams and Node.js Streams. Similarly to those two different implementations, streaming iterables come in different forms for different purposes:\n\n1. **source** - something that can be consumed. Analogous to a \"source\" pull stream or a \"readable\" Node.js stream\n2. **sink** - something that consumes (or drains) a source. Analogous to a \"sink\" pull stream or a \"writable\" Node.js stream\n3. **transform** - both a sink and a source where the values it consumes and the values that can be consumed from it are connected in some way. Analogous to a transform in both Pull and Node.js streams\n4. **duplex** - similar to a transform but the values it consumes are not necessarily connected to the values that can be consumed from it\n\nMore information and examples here: https://gist.github.com/alanshaw/591dc7dd54e4f99338a347ef568d6ee9\n\nList of useful modules for working with async iterables: https://github.com/alanshaw/it-awesome\n\nNote that iterables might gain many helper functions soon: https://github.com/tc39/proposal-iterator-helpers\n\n### From Node.js Streams\n\n#### Node.js Readable Streams\n\nModern Node.js readable streams are async iterable so there's no changes to any APIs that you'd normally pass a stream to. The `*ReadableStream` APIs have been removed. To migrate from `*ReadableStream` methods, there are a couple of options:\n\n**Impact 🍊**\n\nUse a [for/await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of) loop to consume an async iterable.\n\ne.g.\n\n```js\nconst readable = ipfs.catReadableStream('QmHash')\nconst decoder = new TextDecoder()\n\nreadable.on('data', chunk => {\n  console.log(decoder.decode(chunk))\n})\n\nreadable.on('end', () => {\n  console.log('done')\n})\n```\n\nBecomes:\n\n```js\nconst source = ipfs.cat('QmHash')\nconst decoder = new TextDecoder()\n\nfor await (const chunk of source) {\n  console.log(decoder.decode(chunk))\n}\n\nconsole.log('done')\n```\n\n**Impact 🍏**\n\nConvert the async iterable to a readable stream.\n\ne.g.\n\n```js\nconst readable = ipfs.catReadableStream('QmHash')\nconst decoder = new TextDecoder()\n\nreadable.on('data', chunk => {\n  console.log(decoder.decode(chunk))\n})\n\nreadable.on('end', () => {\n  console.log('done')\n})\n```\n\nBecomes:\n\n```js\nimport toStream from 'it-to-stream'\nconst readable = toStream.readable(ipfs.cat('QmHash'))\nconst decoder = new TextDecoder()\n\nreadable.on('data', chunk => {\n  console.log(decoder.decode(chunk))\n})\n\nreadable.on('end', () => {\n  console.log('done')\n})\n```\n\n#### Piping Node.js Streams\n\nSometimes applications will \"pipe\" Node.js streams together, using the `.pipe` method or the `pipeline` utility. There are 2 possible migration options:\n\n**Impact 🍊**\n\nUse `it-pipe` and a [for/await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of) loop to concat data from an async iterable.\n\ne.g.\n\n```js\nconst { pipeline, Writable } = require('stream')\nconst decoder = new TextDecoder()\n\nlet data = new Uint8Array(0)\nconst concat = new Writable({\n  write (chunk, enc, cb) {\n    data = uint8ArrayConcat([data, chunk])\n    cb()\n  }\n})\n\npipeline(\n  ipfs.catReadableStream('QmHash'),\n  concat,\n  err => {\n    console.log(decoder.decode(chunk))\n  }\n)\n```\n\nBecomes:\n\n```js\nconst pipe = require('it-pipe')\nconst decoder = new TextDecoder()\n\nlet data = new Uint8Array(0)\nconst concat = async source => {\n  for await (const chunk of source) {\n    data = uint8ArrayConcat([data, chunk])\n  }\n}\n\nconst data = await pipe(\n  ipfs.cat('QmHash'),\n  concat\n)\n\nconsole.log(decoder.decode(data))\n```\n\n...which, by the way, could more succinctly be written as:\n\n```js\nimport toBuffer from 'it-to-buffer'\nconst decoder = new TextDecoder()\nconst data = await toBuffer(ipfs.cat('QmHash'))\nconsole.log(decoder.decode(data))\n```\n\n**Impact 🍏**\n\nConvert the async iterable to a readable stream.\n\ne.g.\n\n```js\nconst { pipeline, Writable } = require('stream')\nconst decoder = new TextDecoder()\n\nlet data = new Uint8Array(0)\nconst concat = new Writable({\n  write (chunk, enc, cb) {\n    data = uint8ArrayConcat([data, chunk])\n    cb()\n  }\n})\n\npipeline(\n  ipfs.catReadableStream('QmHash'),\n  concat,\n  err => {\n    console.log(decoder.decode(data))\n  }\n)\n```\n\nBecomes:\n\n```js\nimport toStream from 'it-to-stream'\nconst { pipeline, Writable } = require('stream')\nconst decoder = new TextDecoder()\n\nlet data = new Uint8Array(0)\nconst concat = new Writable({\n  write (chunk, enc, cb) {\n    data = uint8ArrayConcat([data, chunk])\n    cb()\n  }\n})\n\npipeline(\n  toStream.readable(ipfs.cat('QmHash')),\n  concat,\n  err => {\n    console.log(decoder.decode(data))\n  }\n)\n```\n\n#### Node.js Transform Streams\n\nCommonly in Node.js you have a readable stream of a file from the filesystem that you want to add to IPFS. There are 2 possible migration options:\n\n**Impact 🍊**\n\nUse `it-pipe` and a [for/await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of) loop to collect all items from an async iterable.\n\ne.g.\n\n```js\nimport fs from 'fs'\nconst { pipeline } = require('stream')\n\nconst items = []\nconst all = new Writable({\n  objectMode: true,\n  write (chunk, enc, cb) {\n    items.push(chunk)\n    cb()\n  }\n})\n\npipeline(\n  fs.createReadStream('/path/to/file'),\n  ipfs.addReadableStream(),\n  all,\n  err => {\n    console.log(items)\n  }\n)\n```\n\nBecomes:\n\n```js\nimport fs from 'fs'\nconst pipe = require('it-pipe')\n\nconst items = []\nconst all = async source => {\n  for await (const chunk of source) {\n    items.push(chunk)\n  }\n}\n\nawait pipe(\n  fs.createReadStream('/path/to/file'), // Because Node.js streams are iterable\n  ipfs.add,\n  all\n)\n\nconsole.log(items)\n```\n\n...which, by the way, could more succinctly be written as:\n\n```js\nimport fs from 'fs'\nconst pipe = require('it-pipe')\nimport all from 'it-all'\n\nconst items = await pipe(\n  fs.createReadStream('/path/to/file'),\n  ipfs.add,\n  all\n)\n\nconsole.log(items)\n```\n\n**Impact 🍏**\n\nConvert the async iterable to a readable stream.\n\ne.g.\n\n```js\nimport fs from 'fs'\nconst { pipeline } = require('stream')\n\nconst items = []\nconst all = new Writable({\n  objectMode: true,\n  write (chunk, enc, cb) {\n    items.push(chunk)\n    cb()\n  }\n})\n\npipeline(\n  fs.createReadStream('/path/to/file'),\n  ipfs.addReadableStream(),\n  all,\n  err => {\n    console.log(items)\n  }\n)\n```\n\nBecomes:\n\n```js\nimport toStream from 'it-to-stream'\nimport fs from 'fs'\nconst { pipeline } = require('stream')\n\nconst items = []\nconst all = new Writable({\n  objectMode: true,\n  write (chunk, enc, cb) {\n    items.push(chunk)\n    cb()\n  }\n})\n\npipeline(\n  fs.createReadStream('/path/to/file'),\n  toStream.transform(ipfs.add),\n  all,\n  err => {\n    console.log(items)\n  }\n)\n```\n\n### From Pull Streams\n\n#### Source Pull Streams\n\nPull Streams can no longer be passed to IPFS API methods and the `*PullStream` APIs have been removed. To pass a pull stream directly to an IPFS API method, first convert it to an async iterable using [`pull-stream-to-async-iterator`](https://www.npmjs.com/package/pull-stream-to-async-iterator). To migrate from `*PullStream` methods, there are a couple of options:\n\n**Impact 🍊**\n\nUse a [for/await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of) loop to consume an async iterable.\n\ne.g.\n\n```js\nconst decoder = new TextDecoder()\n\npull(\n  ipfs.catPullStream('QmHash'),\n  pull.through(chunk => {\n    console.log(decoder.decode(data))\n  }),\n  pull.onEnd(err => {\n    console.log('done')\n  })\n)\n```\n\nBecomes:\n\n```js\nconst decoder = new TextDecoder()\n\nfor await (const chunk of ipfs.cat('QmHash')) {\n  console.log(decoder.decode(data))\n}\n\nconsole.log('done')\n```\n\n**Impact 🍏**\n\nConvert the async iterable to a pull stream.\n\ne.g.\n\n```js\nconst decoder = new TextDecoder()\n\npull(\n  ipfs.catPullStream('QmHash'),\n  pull.through(chunk => {\n    console.log(decoder.decode(data))\n  }),\n  pull.onEnd(err => {\n    console.log('done')\n  })\n)\n```\n\nBecomes:\n\n```js\nconst toPull = require('async-iterator-to-pull-stream')\nconst decoder = new TextDecoder()\n\npull(\n  toPull.source(ipfs.cat('QmHash')),\n  pull.through(chunk => {\n    console.log(decoder.decode(data))\n  }),\n  pull.onEnd(err => {\n    console.log('done')\n  })\n)\n```\n\n#### Pull Stream Pipelines\n\nFrequently, applications will use `pull()` to create a pipeline of pull streams.\n\n**Impact 🍊**\n\nUse `it-pipe` and `it-concat` concat data from an async iterable.\n\ne.g.\n\n```js\nconst decoder = new TextDecoder()\n\npull(\n  ipfs.catPullStream('QmHash'),\n  pull.collect((err, chunks) => {\n    console.log(decoder.decode(uint8ArrayConcat(chunks)))\n  })\n)\n```\n\nBecomes:\n\n```js\nconst pipe = require('it-pipe')\nimport concat from 'it-concat'\nconst decoder = new TextDecoder()\n\nconst data = await pipe(\n  ipfs.cat('QmHash'),\n  concat\n)\n\nconsole.log(decoder.decode(data))\n```\n\n#### Transform Pull Streams\n\nYou might have a pull stream source of a file from the filesystem that you want to add to IPFS. There are 2 possible migration options:\n\n**Impact 🍊**\n\nUse `it-pipe` and `it-all` to collect all items from an async iterable.\n\ne.g.\n\n```js\nimport fs from 'fs'\nconst toPull = require('stream-to-pull-stream')\n\npull(\n  toPull.source(fs.createReadStream('/path/to/file')),\n  ipfs.addPullStream(),\n  pull.collect((err, items) => {\n    console.log(items)\n  })\n)\n```\n\nBecomes:\n\n```js\nimport fs from 'fs'\n\nconst file = await ipfs.add(fs.createReadStream('/path/to/file'))\n\nconsole.log(file)\n```\n\n**Impact 🍏**\n\nConvert the async iterable to a pull stream.\n\ne.g.\n\n```js\nimport fs from 'fs'\nconst toPull = require('stream-to-pull-stream')\n\npull(\n  toPull.source(fs.createReadStream('/path/to/file')),\n  ipfs.addPullStream(),\n  pull.collect((err, items) => {\n    console.log(items)\n  })\n)\n```\n\nBecomes:\n\n```js\nimport fs from 'fs'\nconst streamToPull = require('stream-to-pull-stream')\nconst itToPull = require('async-iterator-to-pull-stream')\n\npull(\n  streamToPull.source(fs.createReadStream('/path/to/file')),\n  itToPull.transform(ipfs.add),\n  pull.collect((err, items) => {\n    console.log(items)\n  })\n)\n```\n\n### From buffering APIs\n\nThe old APIs like `ipfs.add`, `ipfs.cat`, `ipfs.ls` and others were \"buffering APIs\" i.e. they collect all the results into memory before returning them. The new JS core interface APIs are streaming by default in order to reduce memory usage, reduce time to first byte and to provide better feedback. The following are examples of switching from the old `ipfs.add`, `ipfs.cat` and `ipfs.ls` to the new APIs:\n\n**Impact 🍏**\n\nAdding files.\n\ne.g.\n\n```js\nconst results = await ipfs.addAll([\n  { path: 'root/1.txt', content: 'one' },\n  { path: 'root/2.txt', content: 'two' }\n])\n\n// Note that ALL files have already been added to IPFS\nresults.forEach(file => {\n  console.log(file.path)\n})\n```\n\nBecomes:\n\n```js\nconst addSource = ipfs.addAll([\n  { path: 'root/1.txt', content: 'one' },\n  { path: 'root/2.txt', content: 'two' }\n])\n\nfor await (const file of addSource) {\n  console.log(file.path) // Note these are logged out as they are added\n}\n```\n\nAlternatively you can buffer up the results using the `it-all` utility:\n\n```js\nimport all from 'it-all'\n\nconst results = await all(ipfs.addAll([\n  { path: 'root/1.txt', content: 'one' },\n  { path: 'root/2.txt', content: 'two' }\n]))\n\nresults.forEach(file => {\n  console.log(file.path)\n})\n```\n\nOften you just want the last item (the root directory entry) when adding multiple files to IPFS:\n\n```js\nconst results = await ipfs.addAll([\n  { path: 'root/1.txt', content: 'one' },\n  { path: 'root/2.txt', content: 'two' }\n])\n\nconst lastResult = results[results.length - 1]\n\nconsole.log(lastResult)\n```\n\nBecomes:\n\n```js\nconst addSource = ipfs.addAll([\n  { path: 'root/1.txt', content: 'one' },\n  { path: 'root/2.txt', content: 'two' }\n])\n\nlet lastResult\nfor await (const file of addSource) {\n  lastResult = file\n}\n\nconsole.log(lastResult)\n```\n\nAlternatively you can use the `it-last` utility:\n\n```js\nconst lastResult = await last(ipfs.addAll([\n  { path: 'root/1.txt', content: 'one' },\n  { path: 'root/2.txt', content: 'two' }\n]))\n\nconsole.log(lastResult)\n```\n\n**Impact 🍏**\n\nReading files.\n\ne.g.\n\n```js\nimport fs from 'fs'\n\nconst data = await ipfs.cat('/ipfs/QmHash')\n\n// Note that here we have read the entire file\n// i.e. `data` holds ALL the contents of the file in memory\nawait fs.writeFile('/tmp/file.iso', data)\n\nconsole.log('done')\n```\n\nBecomes:\n\n```js\nconst pipe = require('it-pipe')\nimport toIterable from 'stream-to-it'\nimport fs from 'fs'\n\n// Note that as chunks arrive they are written to the file and memory can be freed and re-used\nawait pipe(\n  ipfs.cat('/ipfs/QmHash'),\n  toIterable.sink(fs.createWriteStream('/tmp/file.iso'))\n)\n\nconsole.log('done')\n```\n\nAlternatively you can buffer up the chunks using the `it-concat` utility (not recommended!):\n\n```js\nimport fs from 'fs'\nimport concat from 'it-concat'\n\nconst data = await concat(ipfs.cat('/ipfs/QmHash'))\n\nawait fs.writeFile('/tmp/file.iso', data.slice())\n\nconsole.log('done')\n```\n\n**Impact 🍏**\n\nListing directory contents.\n\ne.g.\n\n```js\nconst files = await ipfs.ls('/ipfs/QmHash')\n\n// Note that ALL files in the directory have been read into memory\nfiles.forEach(file => {\n  console.log(file.name)\n})\n```\n\nBecomes:\n\n```js\nconst filesSource = ipfs.ls('/ipfs/QmHash')\n\nfor await (const file of filesSource) {\n  console.log(file.name) // Note these are logged out as they are retrieved from the network/disk\n}\n```\n\nAlternatively you can buffer up the directory listing using the `it-all` utility:\n\n```js\nimport all from 'it-all'\n\nconst results = await all(ipfs.ls('/ipfs/QmHash'))\n\nresults.forEach(file => {\n  console.log(file.name)\n})\n```\n\n## Migrating from `addFromFs`\n\nThe `addFromFs` API method has been removed and replaced with a helper function `globSource` that is exported from `js-ipfs`/`js-ipfs-http-client`. See the [API docs for `globSource` for more info](https://github.com/ipfs/js-ipfs-http-client/blob/f30031163b9ac4ce2cff34ad4854f24b23cbff0b/README.md#glob-source).\n\n**Impact 🍏**\n\ne.g.\n\n```js\nconst IpfsHttpClient = require('ipfs-http-client')\nconst ipfs = IpfsHttpClient()\n\nconst files = await ipfs.addFromFs('./docs', { recursive: true })\n\nfiles.forEach(file => {\n  console.log(file)\n})\n```\n\nBecomes:\n\n```js\nconst IpfsHttpClient = require('ipfs-http-client')\nconst { globSource } = IpfsHttpClient\nconst ipfs = IpfsHttpClient()\n\nfor await (const file of ipfs.addAll(globSource('./docs', { recursive: true }))) {\n  console.log(file)\n}\n```\n\n## Migrating from `addFromURL`\n\nThe `addFromURL` API method has been removed and replaced with a helper function `urlSource` that is exported from `js-ipfs`/`js-ipfs-http-client`. See the [API docs for `urlSource` for more info](https://github.com/ipfs/js-ipfs-http-client/blob/f30031163b9ac4ce2cff34ad4854f24b23cbff0b/README.md#url-source).\n\n**Impact 🍏**\n\ne.g.\n\n```js\nconst IpfsHttpClient = require('ipfs-http-client')\nconst ipfs = IpfsHttpClient()\n\nconst files = await ipfs.addFromURL('https://ipfs.io/images/ipfs-logo.svg')\n\nfiles.forEach(file => {\n  console.log(file)\n})\n```\n\nBecomes:\n\n```js\nconst IpfsHttpClient = require('ipfs-http-client')\nconst { urlSource } = IpfsHttpClient\nconst ipfs = IpfsHttpClient()\n\nconst file = await ipfs.add(urlSource('https://ipfs.io/images/ipfs-logo.svg'))\n\nconsole.log(file)\n```\n\n## Migrating from `addFromStream`\n\nThe `addFromStream` API method has been removed. This was an alias for `add`.\n\n**Impact 🍏**\n\ne.g.\n\n```js\nconst IpfsHttpClient = require('ipfs-http-client')\nconst ipfs = IpfsHttpClient()\n\nconst files = await ipfs.addFromStream(fs.createReadStream('/path/to/file.txt'))\n\nfiles.forEach(file => {\n  console.log(file)\n})\n```\n\nBecomes:\n\n```js\nimport fs from 'fs'\nconst ipfs = IpfsHttpClient()\n\nconst file = await ipfs.add(fs.createReadStream('/path/to/file.txt'))\n\nconsole.log(file)\n```\n"
  },
  {
    "path": "docs/MODULE.md",
    "content": "# IPFS Module <!-- omit in toc -->\n\nUse the IPFS module as a dependency of your project to spawn in process instances of IPFS in node.js, the browser, electron, etc.\n\n## Table of contents <!-- omit in toc -->\n\n- [Getting started](#getting-started)\n- [IPFS.create([options])](#ipfscreateoptions)\n  - [`options.repo`](#optionsrepo)\n  - [`options.repoAutoMigrate`](#optionsrepoautomigrate)\n  - [`options.init`](#optionsinit)\n  - [`options.start`](#optionsstart)\n  - [`options.pass`](#optionspass)\n  - [`options.silent`](#optionssilent)\n  - [`options.relay`](#optionsrelay)\n  - [`options.offline`](#optionsoffline)\n  - [`options.preload`](#optionspreload)\n  - [`options.EXPERIMENTAL`](#optionsexperimental)\n  - [`options.config`](#optionsconfig)\n  - [`options.ipld`](#optionsipld)\n  - [`options.libp2p`](#optionslibp2p)\n  - [Instance methods](#instance-methods)\n    - [`node.start()`](#nodestart)\n- [Static types and utils](#static-types-and-utils)\n      - [Glob source](#glob-source)\n        - [`globSource(path, pattern, [options])`](#globsourcepath-pattern-options)\n        - [Example](#example)\n      - [URL source](#url-source)\n        - [`urlSource(url)`](#urlsourceurl)\n        - [Example](#example-1)\n      - [Path](#path)\n        - [`path()`](#path-1)\n        - [Example](#example-2)\n\n## Getting started\n\nCreate a running node with:\n\n```javascript\n// Create the IPFS node instance\nconst node = await IPFS.create()\n// Your node is now ready to use \\o/\n\nawait node.stop()\n// node is now 'offline'\n```\n\nThe node returned from `IPFS.create()` supports the [IPFS Core API](https://github.com/ipfs/js-ipfs/tree/master/docs/core-api), along with some additional methods documented below.\n\n## IPFS.create([options])\n\n```js\nconst node = await IPFS.create([options])\n```\n\nCreates and returns a ready to use instance of an IPFS node.\n\nUse the `options` argument to specify advanced configuration. It is an object with any of these properties:\n\n### `options.repo`\n\n| Type | Default |\n|------|---------|\n| string or [`ipfs.Repo`](https://github.com/ipfs/js-ipfs-repo) instance | `'~/.jsipfs'` in Node.js, `'ipfs'` in browsers |\n\nThe file path at which to store the IPFS node’s data. Alternatively, you can set up a customized storage system by providing an [`ipfs.Repo`](https://github.com/ipfs/js-ipfs-repo) instance.\n\nExample:\n\n```js\n// Store data outside your user directory\nconst node = await IPFS.create({ repo: '/var/ipfs/data' })\n```\n\n### `options.repoAutoMigrate`\n\n| Type      | Default |\n| --------- | ------- |\n| `boolean` | `true`  |\n\n`js-ipfs` comes bundled with a tool that automatically migrates your IPFS repository when a new version is available.\n\n**For apps that build on top of `js-ipfs` and run in the browser environment, be aware that disabling automatic\nmigrations leaves the user with no way to run the migrations because there is no CLI in the browser. In such\na case, you should provide a way to trigger migrations manually.**\n\n### `options.init`\n\n| Type              | Default |\n| ----------------- | ------- |\n| boolean or object | `true`  |\n\nPerform repo initialization steps when creating the IPFS node.\n\nNote that *initializing* a repo is different from creating an instance of [`ipfs.Repo`](https://github.com/ipfs/js-ipfs-repo). The IPFS constructor sets many special properties when initializing a repo, so you should usually not try and call `repoInstance.init()` yourself.\n\nInstead of a boolean, you may provide an object with custom initialization options. All properties are optional:\n\n- `emptyRepo` (boolean) Whether to remove built-in assets, like the instructional tour and empty mutable file system, from the repo. (Default: `false`)\n- `algorithm` (string) The type of key to use. Supports `rsa`, `ed25519`, `secp256k1`. (Default: `rsa`)\n- `bits` (number) Number of bits to use in the generated key pair (rsa only). (Default: `2048`)\n- `privateKey` (string/PeerId) A pre-generated private key to use. Can be either a base64 string or a [PeerId](https://github.com/libp2p/js-peer-id) instance. **NOTE: This overrides `bits`.**\n    ```js\n    // Generating a Peer ID:\n    import { PeerId } from '@libp2p/interface-peer-id'\n    // Generates a new Peer ID, complete with public/private keypair\n    // See https://github.com/libp2p/js-peer-id\n    const peerId = await PeerId.create({ bits: 2048 })\n    ```\n- `pass` (string) A passphrase to encrypt keys. You should generally use the [top-level `pass` option](#optionspass) instead of the `init.pass` option (this one will take its value from the top-level option if not set).\n- `profiles` (Array) Apply profile settings to config.\n- `allowNew` (boolean, default: `true`) Set to `false` to disallow initialization if the repo does not already exist.\n\n### `options.start`\n\n| Type      | Default |\n| --------- | ------- |\n| `boolean` | `true`  |\n\nIf `false`, do not automatically start the IPFS node. Instead, you’ll need to manually call [`node.start()`](#nodestart) yourself.\n\n### `options.pass`\n\n| Type   | Default |\n| ------ | ------- |\n| string | `null`  |\n\nA passphrase to encrypt/decrypt your keys.\n\n### `options.silent`\n\n| Type    | Default |\n| ------- | ------- |\n| Boolean | `false` |\n\nPrevents all logging output from the IPFS node.\n\n### `options.relay`\n\n| Type | Default |\n|------|---------|\n| object | `{ enabled: true, hop: { enabled: false, active: false } }` |\n\nConfigure circuit relay (see the [circuit relay tutorial](https://github.com/ipfs-examples/js-ipfs-examples/tree/master/examples/circuit-relaying) to learn more).\n\n- `enabled` (boolean): Enable circuit relay dialer and listener. (Default: `true`)\n- `hop` (object)\n  - `enabled` (boolean): Make this node a relay (other nodes can connect *through* it). (Default: `false`)\n  - `active` (boolean): Make this an *active* relay node. Active relay nodes will attempt to dial a destination peer even if that peer is not yet connected to the relay. (Default: `false`)\n\n### `options.offline`\n\n| Type    | Default |\n| ------- | ------- |\n| Boolean | `false` |\n\nRun ipfs node offline. The node does not connect to the rest of the network but provides a local API.\n\n### `options.preload`\n\n| Type   | Default                               |\n| ------ | ------------------------------------- |\n| object | `{ enabled: true, addresses: [...] }` |\n\nConfigure remote preload nodes. The remote will preload content added on this node, and also attempt to preload objects requested by this node.\n\n- `enabled` (boolean): Enable content preloading (Default: `true`)\n- `addresses` (array): Multiaddr API addresses of nodes that should preload content. **NOTE:** nodes specified here should also be added to your node's bootstrap address list at [`config.Bootstrap`](#optionsconfig).\n\n### `options.EXPERIMENTAL`\n\n| Type   | Default                                  |\n| ------ | ---------------------------------------- |\n| object | `{ ipnsPubsub: false, sharding: false }` |\n\nEnable and configure experimental features.\n\n- `ipnsPubsub` (boolean): Enable pub-sub on IPNS. (Default: `false`)\n- `sharding` (boolean): Enable directory sharding. Directories that have many child objects will be represented by multiple DAG nodes instead of just one. It can improve lookup performance when a directory has several thousand files or more. (Default: `false`)\n\n### `options.config`\n\n| Type | Default |\n|------|---------|\n| object | [`config.js`](https://github.com/ipfs/js-ipfs/blob/master/packages/ipfs-core-config/src/config.js) in Node.js, [`config-browser.js`](https://github.com/ipfs/js-ipfs/blob/master/packages/ipfs-core-config/src/config.browser.js) in browsers |\n\nModify the default IPFS node config. This object will be *merged* with the default config; it will not replace it. The default config is documented in [the js-ipfs config file docs](./CONFIG.md).\n\n### `options.ipld`\n\n| Type | Default |\n|------|---------|\n| object | [`ipld.js`](https://github.com/ipfs/js-ipfs/blob/master/packages/ipfs-core-config/src/ipld.js) |\n\nModify the default IPLD config. This object will be *merged* with the default config; it will not replace it. Check IPLD [docs](https://github.com/ipld/js-ipld#ipld-constructor) for more information on the available options.\n\n> Browser config does **NOT** include by default all the IPLD formats. Only `ipld-dag-pb`, `ipld-dag-cbor` and `ipld-raw` are included.\n\nTo add support for other formats we provide two options, one sync and another async.\n\nExamples for the sync option:\n\n<details><summary>ESM Environments</summary>\n\n```js\nimport ipldGit from 'ipld-git'\nimport ipldBitcoin from 'ipld-bitcoin'\nimport { convert } from 'ipld-format-to-blockcodec'\n\nconst node = await IPFS.create({\n  ipld: {\n    codecs: [\n      convert(ipldGit),\n      convert(ipldBitcoin)\n    ]\n  }\n})\n```\n\n</details>\n<details><summary>Commonjs Environments</summary>\n\n```js\nconst IPFS = require('ipfs')\nconst ipldGit = require('ipld-git')\nconst ipldBitcoin = require('ipld-bitcoin')\nconst { convert } = require('ipld-format-to-blockcodec')\n\nconst node = await IPFS.create({\n  ipld: {\n    codecs: [\n      convert(ipldGit),\n      convert(ipldBitcoin)\n    ]\n  }\n})\n```\n\n</details>\n<details><summary>Using script tags</summary>\n\n```html\n<script src=\"https://unpkg.com/ipfs/dist/index.min.js\"></script>\n<script src=\"https://unpkg.com/ipld-git/dist/index.min.js\"></script>\n<script src=\"https://unpkg.com/ipld-bitcoin/dist/index.min.js\"></script>\n<script src=\"https://unpkg.com/ipld-format-to-blockcodec/dist/index.min.js\"></script>\n<script>\n  async function main() {\n    const node = await self.IPFS.create({\n      ipld: {\n        codecs: [\n          convert(self.ipldGit),\n          convert(self.ipldBitcoin)\n        ]\n      },\n    });\n  }\n  main();\n</script>\n```\n\n</details>\n\nExamples for the async option:\n\n<details><summary>ESM Environments</summary>\n\n```js\nconst node = await IPFS.create({\n  ipld: {\n    async loadCodec (codec) {\n      if (codec === multicodec.GIT_RAW) {\n        return convert(await import('ipld-git')) // This is a dynamic import\n      } else {\n        throw new Error('unable to load format ' + multicodec.print[codec])\n      }\n    }\n  }\n})\n```\n\n> For more information about dynamic imports please check [webpack docs](https://webpack.js.org/guides/code-splitting/#dynamic-imports) or search your bundler documention.\n\nUsing dynamic imports will tell your bundler to create a separate file (normally called *chunk*) that will **only** be requested by the browser if it's really needed. This strategy will reduce your bundle size and load times without removing any functionality.\n\nWith Webpack IPLD formats can even be grouped together using magic comments `import(/* webpackChunkName: \"ipld-formats\" */ 'ipld-git')` to produce a single file with all of them.\n\n</details>\n\n<details><summary>Commonjs Environments</summary>\n\n```js\nconst node = await IPFS.create({\n  ipld: {\n    async loadFormat (codec) {\n      if (codec === multicodec.GIT_RAW) {\n        return require('ipld-git')\n      } else {\n        throw new Error('unable to load format ' + multicodec.print[codec])\n      }\n    }\n  }\n})\n```\n\n</details>\n\n<details><summary>Using Script tags</summary>\n\n```js\n<script src=\"https://unpkg.com/ipfs/dist/index.min.js\"></script>\n<script>\nconst load = (name, url) => new Promise((resolve, reject) => {\n  const script = document.createElement('script')\n  script.src = url\n  script.onload = () => resolve(self[name])\n  script.onerror = () => reject(new Error('Failed to load ' + url))\n  document.body.appendChild(script)\n})\n\nconst node = await self.IPFS.create({\n  ipld: {\n    async loadFormat (codec) {\n      switch (codec) {\n        case multicodec.GIT_RAW:\n          return load('IpldGit', 'https://unpkg.com/ipld-git/dist/index.min.js')\n        case multicodec.BITCOIN_BLOCK:\n          return load('IpldBitcoin', 'https://unpkg.com/ipld-bitcoin/dist/index.min.js')\n        default:\n          throw new Error('Unable to load format ' + multicodec.print[codec])\n      }\n    }\n  }\n})\n</script>\n```\n\n</details>\n\n### `options.libp2p`\n\n| Type | Default |\n|------|---------|\n| object   | [`libp2p-nodejs.js`](https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-core-config/src/libp2p-nodejs.js) in Node.js, [`libp2p-browser.js`](https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-core-config/src)/libp2p-browser.js) in browsers |\n| function | [`libp2p bundle`](https://github.com/ipfs-examples/js-ipfs-examples/tree/master/examples/custom-libp2p)                                         |\n\nThe libp2p option allows you to build your libp2p node by configuration, or via a bundle function. If you are looking to just modify the below options, using the object format is the quickest way to get the default features of libp2p. If you need to create a more customized libp2p node, such as with custom transports or peer/content routers that need some of the ipfs data on startup, a custom bundle is a great way to achieve this.\n\nYou can see the bundle in action in the [custom libp2p example](https://github.com/ipfs-examples/js-ipfs-examples/tree/master/examples/custom-libp2p).\n\nPlease see [libp2p/docs/CONFIGURATION.md](https://github.com/libp2p/js-libp2p/blob/master/doc/CONFIGURATION.md) for the list of options libp2p supports.\n\n### Instance methods\n\n#### `node.start()`\n\nStart listening for connections with other IPFS nodes on the network. In most cases, you do not need to call this method — `IPFS.create()` will automatically do it for you.\n\nThis method is asynchronous and returns a promise.\n\n```js\nconst node = await IPFS.create({ start: false })\nconsole.log('Node is ready to use but not started!')\n\ntry {\n  await node.start()\n  console.log('Node started!')\n} catch (/** @type {any} */ error) {\n  console.error('Node failed to start!', error)\n}\n```\n\n## Static types and utils\n\nAside from the default export, `ipfs` exports various types and utilities that are included in the bundle:\n\n- [`crypto`](https://www.npmjs.com/package/libp2p-crypto)\n- [`isIPFS`](https://www.npmjs.com/package/is-ipfs)\n- [`Buffer`](https://www.npmjs.com/package/buffer)\n- [`PeerId`](https://docs.libp2p.io/concepts/peer-id/)\n- [`PeerInfo`](https://www.npmjs.com/package/peer-info)\n- [`multiaddr`](https://www.npmjs.com/package/multiaddr)\n- [`multibase`](https://www.npmjs.com/package/multibase)\n- [`multihash`](https://www.npmjs.com/package/multihashes)\n- [`multihashing`](https://www.npmjs.com/package/multihashing-async)\n- [`multicodec`](https://www.npmjs.com/package/multicodec)\n- [`CID`](https://docs.ipfs.io/concepts/content-addressing)\n\nThese can be accessed like this, for example:\n\n```js\nconst { CID } = require('ipfs')\n// ...or from an es-module:\nimport { CID } from 'ipfs'\n```\n\n##### Glob source\n\nA utility to allow files on the file system to be easily added to IPFS.\n\n###### `globSource(path, pattern, [options])`\n\n- `path`: A path to a single file or directory to glob from\n- `pattern`: A pattern to match files under `path`\n- `options`: Optional options\n- `options.hidden`: Hidden/dot files (files or folders starting with a `.`, for example, `.git/`) are not included by default. To add them, use the option `{ hidden: true }`.\n\nReturns an async iterable that yields `{ path, content }` objects suitable for passing to `ipfs.add`.\n\n###### Example\n\n```js\nimport { create, globSource } from 'ipfs'\n\nconst ipfs = await create()\n\nfor await (const file of ipfs.addAll(globSource('./docs', '**/*'))) {\n  console.log(file)\n}\n/*\n{\n  path: 'docs/assets/anchor.js',\n  cid: CID('QmVHxRocoWgUChLEvfEyDuuD6qJ4PhdDL2dTLcpUy3dSC2'),\n  size: 15347\n}\n{\n  path: 'docs/assets/bass-addons.css',\n  cid: CID('QmPiLWKd6yseMWDTgHegb8T7wVS7zWGYgyvfj7dGNt2viQ'),\n  size: 232\n}\n...\n*/\n```\n\n##### URL source\n\nA utility to allow content from the internet to be easily added to IPFS.\n\n###### `urlSource(url)`\n\n- `url`: A string URL or [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) instance to send HTTP GET request to\n\nReturns an async iterable that yields `{ path, content }` objects suitable for passing to `ipfs.add`.\n\n###### Example\n\n```js\nimport { create, urlSource } from 'ipfs'\n\nconst ipfs = await create()\n\nconst file = await ipfs.add(urlSource('https://ipfs.io/images/ipfs-logo.svg'))\nconsole.log(file)\n\n/*\n{\n  path: 'ipfs-logo.svg',\n  cid: CID('QmTqZhR6f7jzdhLgPArDPnsbZpvvgxzCZycXK7ywkLxSyU'),\n  size: 3243\n}\n*/\n```\n\n##### Path\n\nA function that returns the path to the js-ipfs CLI.\n\nThis is analogous to the `.path()` function exported by the [go-ipfs](https://www.npmjs.com/package/go-ipfs) module.\n\n###### `path()`\n\nReturns the path to the js-ipfs CLI\n\n###### Example\n\n```js\nimport { path } from 'ipfs'\n\nconsole.info(path()) // /foo/bar/node_modules/ipfs/src/cli.js\n```\n"
  },
  {
    "path": "docs/MONITORING.md",
    "content": "# Monitoring\n\nThe HTTP API exposed with js-ipfs can also be used for exposing metrics about the running js-ipfs node and other Node.js metrics.\n\nTo enable it, you need to set the environment variable `IPFS_MONITORING` (any value).  E.g.\n\n```console\n$ IPFS_MONITORING=true jsipfs daemon\n```\n\nOnce the environment variable is set and the js-ipfs daemon is running, you can get the metrics (in prometheus format) by making a GET request to the following endpoint:\n\n```\nhttp://localhost:5002/debug/metrics/prometheus\n```\n"
  },
  {
    "path": "docs/README.md",
    "content": "# IPFS Docs <!-- omit in toc -->\n\n- [API Docs](#api-docs)\n- [How tos and other documentation](#how-tos-and-other-documentation)\n- [Development documentation](#development-documentation)\n\n## API Docs\n\n`ipfs` can run as part of your program (an in-process node) or as a standalone daemon process that can be communicated with via an HTTP RPC API using the [`ipfs-http-client`](../packages/ipfs-http-client) module.\n\nWhether accessed directly or over HTTP, both methods support the full [Core API](#core-api).  In addition other methods are available to construct instances of each module, etc.\n\n* [Core API docs](./core-api/README.md)\n* [IPFS API](../packages/ipfs/README.md)\n* [IPFS-HTTP-CLIENT API](../packages/ipfs-http-client/README.md)\n\n## How tos and other documentation\n\n* [Architecture overview](./ARCHITECTURE.md)\n* [How to run js-IPFS in the browser](./BROWSERS.md)\n* [Running js-IPFS on the CLI](./CLI.md)\n* [js-IPFS configuration options](./CONFIG.md)\n* [How to configure CORS for use with the http client](./CORS.md)\n* [Running js-IPFS as a daemon](./DAEMON.md)\n* [Configuring Delegate Routers](./DELEGATE_ROUTERS.md)\n* [Running js-IPFS under Docker](./DOCKER.md)\n* [FAQ](./FAQ.md)\n* [How to configure additional IPLD codecs](./IPLD.md)\n* [Running js-IPFS in your application](./MODULE.md)\n* [How to get metrics out of js-IPFS](./MONITORING.md)\n\n## Development documentation\n\n* [Getting started](./DEVELOPMENT.md)\n* [Release issue template](./RELEASE_ISSUE_TEMPLATE.md)\n* [Early testers](./EARLY_TESTERS.md)\n* [Releases](./RELEASES.md)\n"
  },
  {
    "path": "docs/RELEASES.md",
    "content": "# Releases <!-- omit in toc -->\n\n## Table of Contents <!-- omit in toc -->\n\n- [Release Philosophy](#release-philosophy)\n- [Release Flow](#release-flow)\n  - [Stage 0 - Automated Testing](#stage-0---automated-testing)\n  - [Stage 1 - Internal Testing](#stage-1---internal-testing)\n  - [Stage 2 - Community Dev Testing](#stage-2---community-dev-testing)\n  - [Stage 3 - Community Prod Testing](#stage-3---community-prod-testing)\n  - [Stage 4 - Release](#stage-4---release)\n- [Release Cycle](#release-cycle)\n  - [Patch Releases](#patch-releases)\n- [Performing a Release](#performing-a-release)\n- [Release Version Numbers](#release-version-numbers)\n  - [Release Candidates](#release-candidates)\n\n## Release Philosophy\n\njs-ipfs aims to have release every six weeks, two releases per quarter. During these 6 week releases, we go through 4 different stages that gives us the opportunity to test the new version against our test environments (unit, interop, integration), QA in our current production environment, IPFS apps (e.g. Desktop and WebUI) and with our community and _early testers_<sup>[1]</sup> that have IPFS running in production.\n\nWe might expand the six week release schedule in case of:\n\n- No new updates to be added\n- In case of a large community event that takes the core team availability away (e.g. IPFS Conf, Dev Meetings, IPFS Camp, etc.)\n\n## Release Flow\n\njs-ipfs releases come in 5 stages designed to gradually roll out changes and reduce the impact of any regressions that may have been introduced. If we need to merge non-trivial<sup>[2]</sup> changes during the process, we start over at stage 0.\n\n![js-ipfs release flow cartoon](https://ipfs.io/ipfs/QmU5pwcGh38DqzLy3rK8GAHuWm2kK87oGqDAtqZYWhxjab)\n\n### Stage 0 - Automated Testing\n\nAt this stage, we expect _all_ automated tests (unit, functional, integration, interop, testlab, performance, etc.) to pass.\n\n### Stage 1 - Internal Testing\n\nAt this stage, we'll:\n\n1. Start a partial-rollout to our own infrastructure.\n2. Test against applications in the [ipfs](https://github.com/ipfs/) and [ipfs-shipyard](https://github.com/ipfs-shipyard/) organisations and a selection of other hand picked projects.\n\n**Goals:**\n\n1. Make sure we haven't introduced any obvious regressions.\n2. Test the release in an environment we can monitor and easily roll back (i.e. our own infra).\n\n### Stage 2 - Community Dev Testing\n\nAt this stage, we'll announce the impending release to the community and ask for pre-release testers.\n\n**Goal:**\n\nTest the release in as many non-production environments as possible. This is relatively low-risk but gives us a _breadth_ of testing internal testing can't.\n\n### Stage 3 - Community Prod Testing\n\nAt this stage, we consider the release to be \"production ready\" and will ask the community and our early testers to (partially) deploy the release to their production infrastructure.\n\n**Goals:**\n\n1. Test the release in some production environments with heavy workloads.\n2. Partially roll-out an upgrade to see how it affects the network.\n3. Retain the ability to ship last-minute fixes before the final release.\n\n### Stage 4 - Release\n\nAt this stage, the release is \"battle hardened\" and ready for wide deployment. A new version is published to npm, announcements are made and a blog post is published to [blog.ipfs.io](https://blog.ipfs.io).\n\n## Release Cycle\n\nA full release process should take about 3 weeks, a week per stage 1-3. We will start a new process every 6 weeks, regardless of when the previous release landed unless it's still ongoing.\n\n### Patch Releases\n\nIf we encounter a serious bug in the stable latest release, we will create a patch release based on this release. For now, bug fixes will _not_ be backported to previous releases.\n\nPatch releases will usually follow a compressed release cycle and should take 2-3 days. In a patch release:\n\n1. Automated and internal testing (stage 0 and 1) will be compressed into a few hours - ideally less than a day.\n2. Stage 2 will be skipped.\n3. Community production testing will be shortened to 1-2 days of opt-in testing in production (early testers can choose to pass).\n\nSome patch releases, especially ones fixing one or more complex bugs, may undergo the full release process.\n\n## Performing a Release\n\nThe release is managed by the \"Lead Maintainer\" for js-ipfs. It starts with the opening of an issue containing the content available on the [RELEASE_ISSUE_TEMPLATE](./RELEASE_ISSUE_TEMPLATE.md) not more than **48 hours** after the previous release.\n\nThis issue is pinned and labeled [\"release\"](https://github.com/ipfs/js-ipfs/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3Arelease). When the cycle is due to begin the 5 stages will be followed until the release is done.\n\n## Release Version Numbers\n\njs-ipfs is currently pre-1.0. In semver terms this means [anything may change at any time](https://semver.org/#spec-item-4).\n\nHowever, pre-1.0 js-ipfs reserves MINOR version increments for BREAKING CHANGES _and_ feature additions and PATCH version increments for bug fixes.\n\nPost `1.x.x` (future), MAJOR version number increments will contain BREAKING CHANGES, MINOR version increments will be reserved for backwards compatible new features and PATCH version increments for bug fixes.\n\nWe do not yet retroactively apply fixes to older releases (no Long Term Support releases for now), which means that we always recommend users to update to the latest, whenever possible.\n\n### Release Candidates\n\nEvery commit to master results in the publishing of a Release Candidate. These are made available for users who want to try out the \"bleeding edge\" and can be installed using version numbers with the form `x.y.z-rc.n` where `x`, `y`, and `z` are the usual MAJOR, MINOR and PATCH version numbers and `n` (starting at 0) which is the number of commits to master since the last full release.\n\nAlternatively the latest RC is tagged `next` on npm and can be installed using `npm install ipfs@next`.\n\n---\n\n- <sup>**[1]**</sup> - _early testers_ is an IPFS programme in which members of the community can self-volunteer to help test `js-ipfs` Release Candidates. You find more info about it at [EARLY_TESTERS.md](./EARLY_TESTERS.md)\n- <sup>**[2]**</sup> - A non-trivial change is any change that could potentially introduce an issue that could not categorically be caught by automated testing. This is up to the discretion of the Lead Maintainer but the assumption is that every change is non-trivial unless proven otherwise.\n"
  },
  {
    "path": "docs/RELEASE_ISSUE_TEMPLATE.md",
    "content": "# Release Template\n\n> short tl;dr; of the release\n\n# 🗺 What's left for release\n\n<List of items with PRs and/or Issues to be considered for this release>\n\n# 🚢 Estimated shipping date\n\n<Date this release will ship on if everything goes to plan (week beginning...)>\n\n# 🔦 Highlights\n\n<Top highlights for this release>\n\n# 🏗 API Changes\n\n<Any API changes breaking or otherwise that people should know of>\n\n# ✅ Release Checklist\n\n- [ ] **Stage 0 - Automated Testing**\n  - [ ] Feature freeze. If any \"non-trivial\" changes (see the footnotes of [docs/releases.md](https://github.com/ipfs/js-ipfs/tree/master/docs/releases.md) for a definition) get added to the release, uncheck all the checkboxes and return to this stage.\n  - [ ] Automated Testing (already tested in CI) - Ensure that all tests are passing, this includes:\n    - [ ] unit/functional/integration/e2e\n    - [ ] interop\n    - [ ] ~~sharness~~ (Does not run `js-ipfs`)\n    - [ ] all the examples run without problems\n    - [ ] IPFS application testing\n      - [ ] ~~[webui](https://github.com/ipfs-shipyard/ipfs-webui)~~ (Does not depend on `js-ipfs` or `js-ipfs-http-client`)\n      - [ ] ~~[ipfs-desktop](https://github.com/ipfs-shipyard/ipfs-desktop)~~ (Does not depend on `js-ipfs` or `js-ipfs-http-client`)\n      - [ ] [ipfs-companion](https://github.com/ipfs-shipyard/ipfs-companion)\n      - [ ] [npm-on-ipfs](https://github.com/ipfs-shipyard/npm-on-ipfs)\n      - [ ] [peer-base](https://github.com/peer-base/peer-base)\n      - [ ] [service-worker-gateway](https://github.com/ipfs-shipyard/service-worker-gateway)\n    - [ ] Third party application testing\n      - [ ] [ipfs-log](https://github.com/orbitdb/ipfs-log)\n      - [ ] [orbit-db](https://github.com/orbitdb/orbit-db)\n      - [ ] [sidetree](https://github.com/decentralized-identity/sidetree)\n- [ ] **Stage 1 - Internal Testing**\n  - [ ] Documentation\n    - [ ] Ensure that [README.md](https://github.com/ipfs/js-ipfs/tree/master/README.md) is up to date\n      - [ ] Install section\n      - [ ] API calls\n      - [ ] Packages Listing\n  - [ ] Publish a release candidate to npm\n    ```sh\n    # All successful builds of master update the `build/last-successful` branch\n    # which contains an `npm-shrinkwrap.json`.\n    # This command checks that branch out, installs it's dependencies using `npm ci`,\n    # creates a release branch (e.g. release/v0.34.x), updates the minor prerelease\n    # version (e.g. 0.33.1 -> 0.34.0-rc.0) and publishes it to npm.\n    npx aegir publish-rc\n\n    # Later we may wish to update the rc. First cherry-pick/otherwise merge the\n    # new commits into the release branch on github (e.g. not locally) and wait\n    # for CI to pass. Then update the lockfiles used by CI (n.b. one day this\n    # will be done by our ci tools) with this command:\n    npx aegir update-release-branch-lockfiles release/v0.34.x\n\n    # Then update the rc published on npm. This command pulls the specified\n    # release branch, installs it's dependencies `npm ci`, increments the\n    # prerelease version (e.g. 0.34.0-rc.0 -> 0.34.0-rc.1) and publishes it\n    # to npm.\n    npx aegir update-rc release/v0.34.x\n    ```\n  - Network Testing:\n    - test lab things - TBD\n  - Infrastructure Testing:\n    - TBD\n- [ ] **Stage 2 - Community Dev Testing**\n  - [ ] Reach out to the IPFS _early testers_ listed in [docs/EARLY_TESTERS.md](https://github.com/ipfs/js-ipfs/tree/master/docs/EARLY_TESTERS.md) for testing this release (check when no more problems have been reported). If you'd like to be added to this list, please file a PR.\n  - [ ] Reach out on IRC for additional early testers.\n- [ ] **Stage 3 - Community Prod Testing**\n  - [ ] Update [js.ipfs.io](https://js.ipfs.io) examples to use the latest js-ipfs\n  - [ ] Invite the IPFS [_early testers_](https://github.com/ipfs/js-ipfs/tree/master/docs/EARLY_TESTERS.md) to deploy the release to part of their production infrastructure.\n  - [ ] Invite the wider community (link to the release issue):\n    - [ ] [discuss.ipfs.io](https://discuss.ipfs.io/c/announcements)\n    - [ ] Twitter\n    - [ ] IRC\n- [ ] **Stage 4 - Release**\n  - [ ] Take a snapshot of everyone that has contributed to this release (including its direct dependencies in IPFS, libp2p, IPLD and multiformats) using [the js-ipfs-contributors module](https://www.npmjs.com/package/js-ipfs-contributors).\n  - [ ] Publish to npm:\n    ```sh\n    git checkout release/v0.34.x\n\n    # Re-install dependencies using lockfile (will automatically remove your\n    # node_modules folder) (Ensures the versions used for the browser build are the\n    # same that have been verified by CI)\n    npm ci\n\n    # lint, build, test, tag, publish\n    npm run release-minor\n\n    # reintegrate release branch into master\n    git rm npm-shrinkwrap.json yarn.lock\n    git commit -m 'chore: removed lock files'\n    git checkout master\n    git merge release/v0.34.x\n    git push\n    ```\n  - [ ] Publish a blog post to [github.com/ipfs/blog](https://github.com/ipfs/blog) (at minimum, a c&p of this release issue with all the highlights, API changes and thank yous)\n  - [ ] Broadcasting (link to blog post)\n    - [ ] Twitter\n    - [ ] IRC\n    - [ ] [Reddit](https://reddit.com/r/ipfs)\n    - [ ] [discuss.ipfs.io](https://discuss.ipfs.io/c/announcements)\n    - [ ] Announce it on the [IPFS Users Mailing List](https://groups.google.com/forum/#!forum/ipfs-users)\n  - [ ] Copy release notes to the [GitHub Release description](https://github.com/ipfs/js-ipfs/releases)\n\n# ❤️ Huge thank you to everyone that made this release possible\n\n<Generated contributor list>\n\n# 🙌🏽 Want to contribute?\n\nWould you like to contribute to the IPFS project and don't know how? Well, there are a few places you can get started:\n\n- Check the issues with the `help wanted` label in the [js-ipfs repo](https://github.com/ipfs/js-ipfs/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22)\n- Join an IPFS All Hands, introduce yourself and let us know where you would like to contribute - https://github.com/ipfs/team-mgmt/#weekly-ipfs-all-hands\n- Hack with IPFS and show us what you made! The All Hands call is also the perfect venue for demos, join in and show us what you built\n- Join the discussion at https://discuss.ipfs.io/ and help users finding their answers.\n- Join the [🚀 IPFS Core Implementations Weekly Sync 🛰](https://github.com/ipfs/team-mgmt/issues/992) and be part of the action!\n\n# ⁉️ Do you have questions?\n\nThe best place to ask your questions about IPFS, how it works and what you can do with it is at [discuss.ipfs.io](https://discuss.ipfs.io). We are also available at the `#ipfs` channel on Freenode.\n"
  },
  {
    "path": "docs/core-api/BITSWAP.md",
    "content": "# Bitswap API <!-- omit in toc -->\n\n- [`ipfs.bitswap.wantlist([options])`](#ipfsbitswapwantlistoptions)\n  - [Parameters](#parameters)\n  - [Options](#options)\n  - [Returns](#returns)\n  - [Example](#example)\n- [`ipfs.bitswap.wantlistForPeer(peerId, [options])`](#ipfsbitswapwantlistforpeerpeerid-options)\n  - [Parameters](#parameters-1)\n  - [Options](#options-1)\n  - [Returns](#returns-1)\n  - [Example](#example-1)\n- [`ipfs.bitswap.unwant(cids, [options])`](#ipfsbitswapunwantcids-options)\n  - [Parameters](#parameters-2)\n  - [Options](#options-2)\n  - [Returns](#returns-2)\n  - [Example](#example-2)\n- [`ipfs.bitswap.stat([options])`](#ipfsbitswapstatoptions)\n  - [Parameters](#parameters-3)\n  - [Options](#options-3)\n  - [Returns](#returns-3)\n  - [Example](#example-3)\n\n## `ipfs.bitswap.wantlist([options])`\n\n> Returns the wantlist for your node\n\n### Parameters\n\nNone\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` | Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<CID[]>` | An array of [CID][]s currently in the wantlist |\n\n### Example\n\n```JavaScript\nconst list = await ipfs.bitswap.wantlist()\nconsole.log(list)\n// [ CID('QmHash') ]\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.bitswap.wantlistForPeer(peerId, [options])`\n\n> Returns the wantlist for a connected peer\n\n### Parameters\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| peerId | [PeerId][] | A peer ID to return the wantlist for |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` | Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<CID[]>` | An array of [CID][]s currently in the wantlist |\n\n### Example\n\n```JavaScript\nconst list = await ipfs.bitswap.wantlistForPeer(peerId)\nconsole.log(list)\n// [ CID('QmHash') ]\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.bitswap.unwant(cids, [options])`\n\n> Removes one or more CIDs from the wantlist\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| cids | A [CID][] or Array of [CID][]s | The CIDs to remove from the wantlist |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` | Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<void>` | A promise that resolves once the request is complete |\n\n### Example\n\n```JavaScript\nlet list = await ipfs.bitswap.wantlist()\nconsole.log(list)\n// [ CID('QmHash') ]\n\nawait ipfs.bitswap.unwant(cid)\n\nlist = await ipfs.bitswap.wantlist()\nconsole.log(list)\n// []\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.bitswap.stat([options])`\n\n> Show diagnostic information on the bitswap agent.\n\nNote: `bitswap.stat` and `stats.bitswap` can be used interchangeably.\n\n### Parameters\n\nNone\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<Object>` | An object that contains information about the bitswap agent |\n\nThe returned object contains the following keys:\n\n- `provideBufLen` is an integer.\n- `wantlist` (array of [CID][cid]s)\n- `peers` (array of [PeerId][peerId]s)\n- `blocksReceived` is a [BigInt][1]\n- `dataReceived` is a [BigInt][1]\n- `blocksSent` is a [BigInt][1]\n- `dataSent` is a [BigInt][1]\n- `dupBlksReceived` is a [BigInt][1]\n- `dupDataReceived` is a [BigInt][1]\n\n### Example\n\n```JavaScript\nconst stats = await ipfs.bitswap.stat()\nconsole.log(stats)\n// {\n//   provideBufLen: 0,\n//   wantlist: [ CID('QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM') ],\n//   peers:\n//    [ 'QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM',\n//      'QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu',\n//      'QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd' ],\n//   blocksReceived: 0,\n//   dataReceived: 0,\n//   blocksSent: 0,\n//   dataSent: 0,\n//   dupBlksReceived: 0,\n//   dupDataReceived: 0\n// }\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n[1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt\n[examples]: https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/src/bitswap\n[cid]: https://docs.ipfs.io/concepts/content-addressing\n[peerid]: https://docs.libp2p.io/concepts/peer-id/\n[AbortSignal]: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal\n"
  },
  {
    "path": "docs/core-api/BLOCK.md",
    "content": "# Block API <!-- omit in toc -->\n\n- [`ipfs.block.get(cid, [options])`](#ipfsblockgetcid-options)\n  - [Parameters](#parameters)\n  - [Options](#options)\n  - [Returns](#returns)\n  - [Example](#example)\n- [`ipfs.block.put(block, [options])`](#ipfsblockputblock-options)\n  - [Parameters](#parameters-1)\n  - [Options](#options-1)\n  - [Returns](#returns-1)\n  - [Example](#example-1)\n- [`ipfs.block.rm(cid, [options])`](#ipfsblockrmcid-options)\n  - [Parameters](#parameters-2)\n  - [Options](#options-2)\n  - [Returns](#returns-2)\n  - [Example](#example-2)\n- [`ipfs.block.stat(cid, [options])`](#ipfsblockstatcid-options)\n  - [Parameters](#parameters-3)\n  - [Options](#options-3)\n  - [Returns](#returns-3)\n  - [Example](#example-3)\n\n## `ipfs.block.get(cid, [options])`\n\n> Get a raw IPFS block.\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| cid | [CID][], `String` or `Uint8Array` | A CID that corresponds to the desired block |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n| preload | `boolean` | `false` |  Whether to preload all blocks created during this operation |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<Uint8Array>` | A Uint8Array containing the data of the block |\n\n### Example\n\n```JavaScript\nconst block = await ipfs.block.get(cid)\nconsole.log(block)\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.block.put(block, [options])`\n\n> Stores input as an IPFS block.\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| block | `Uint8Array` | The block of data to store |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| format | `String` | `'dag-pb'` | The codec to use to create the CID |\n| mhtype | `String` | `sha2-256` | The hashing algorithm to use to create the CID |\n| mhlen | `Number` | `undefined` | The hash length (only relevant for `go-ipfs`) |\n| version | `Number` | `0` |  The version to use to create the CID |\n| pin | `boolean` | `false` |  If true, pin added blocks recursively |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` | Can be used to cancel any long running requests started as a result of this call |\n| preload | `boolean` | `false` |  Whether to preload all blocks created during this operation |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<CID>` | A [CID][CID] type object containing the hash of the block |\n\n### Example\n\n```JavaScript\n// Defaults\nconst buf = new TextEncoder().encode('a serialized object')\nconst decoder = new TextDecoder()\n\nconst block = await ipfs.block.put(buf)\n\nconsole.log(decoder.decode(block.data))\n// Logs:\n// a serialized object\nconsole.log(block.cid.toString())\n// Logs:\n// the CID of the object\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.block.rm(cid, [options])`\n\n> Remove one or more IPFS block(s).\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| cid | A [CID][] or Array of [CID][]s | Blocks corresponding to the passed CID(s) will be removed |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| force | `boolean` | `false` | Ignores nonexistent blocks |\n| quiet | `boolean` | `false` | Write minimal output |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` | Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `AsyncIterable<Object>` | An async iterable that yields objects containing hash and (potentially) error strings |\n\nEach object yielded is of the form:\n\n```js\n{\n  cid: CID,\n  error?: Error\n}\n```\n\nNote: If an error is present for a given object, the block with that cid was not removed and the `error` will contain the reason why, for example if the block was pinned.\n\n### Example\n\n```JavaScript\nfor await (const result of ipfs.block.rm(cid)) {\n  if (result.error) {\n    console.error(`Failed to remove block ${result.cid} due to ${result.error.message}`)\n  } else {\n    console.log(`Removed block ${result.cid}`)\n  }\n}\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.block.stat(cid, [options])`\n\n> Print information of a raw IPFS block.\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| cid | A [CID][] or Array of [CID][]s | The stats of the passed CID will be returned |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` | Can be used to cancel any long running requests started as a result of this call |\n| preload | `boolean` | `false` |  Whether to preload all blocks created during this operation |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<Object>` | An object containing the block's info |\n\nthe returned object has the following keys:\n\n```JavaScript\n{\n  cid: CID\n  size: number\n}\n```\n\n### Example\n\n```JavaScript\nconst multihashStr = 'QmQULBtTjNcMwMr4VMNknnVv3RpytrLSdgpvMcTnfNhrBJ'\nconst cid = CID.parse(multihashStr)\n\nconst stats = await ipfs.block.stat(cid)\nconsole.log(stats.cid.toString())\n// Logs: QmQULBtTjNcMwMr4VMNknnVv3RpytrLSdgpvMcTnfNhrBJ\nconsole.log(stat.size)\n// Logs: 3739\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n[block]: https://github.com/ipfs/js-ipfs-block\n[multihash]: https://github.com/multiformats/multihash\n[examples]: https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/src/block\n[AbortSignal]: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal\n[cid]: https://docs.ipfs.io/concepts/content-addressing\n"
  },
  {
    "path": "docs/core-api/BOOTSTRAP.md",
    "content": "# Bootstrap API <!-- omit in toc -->\n\n> Manipulates the bootstrap list, which contains the addresses of the bootstrap nodes. These are the trusted peers from which to learn about other peers in the network.\n\nWarning: your node requires bootstrappers to join the network and find other peers.\n\nIf you edit this list, you may find you have reduced or no connectivity.  If this is the case, please reset your node's bootstrapper list with `ipfs.bootstrap.reset()`.\n\n- [`ipfs.bootstrap.add(addr, [options])`](#ipfsbootstrapaddaddr-options)\n  - [Parameters](#parameters)\n  - [Options](#options)\n  - [Returns](#returns)\n  - [Example](#example)\n- [`ipfs.bootstrap.reset([options])`](#ipfsbootstrapresetoptions)\n  - [Parameters](#parameters-1)\n  - [Options](#options-1)\n  - [Returns](#returns-1)\n  - [Example](#example-1)\n- [`ipfs.bootstrap.list([options])`](#ipfsbootstraplistoptions)\n  - [Parameters](#parameters-2)\n  - [Options](#options-2)\n  - [Returns](#returns-2)\n  - [Example](#example-2)\n- [`ipfs.bootstrap.rm(addr, [options])`](#ipfsbootstraprmaddr-options)\n  - [Parameters](#parameters-3)\n  - [Options](#options-3)\n  - [Returns](#returns-3)\n  - [Example](#example-3)\n- [`ipfs.bootstrap.clear([options])`](#ipfsbootstrapclearoptions)\n  - [Parameters](#parameters-4)\n  - [Options](#options-4)\n  - [Returns](#returns-4)\n  - [Example](#example-4)\n\n## `ipfs.bootstrap.add(addr, [options])`\n\n> Add a peer address to the bootstrap list\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| addr | [MultiAddr][] | The address of a network peer |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<{ Peers: Array<MultiAddr> }>` | An object that contains an array with all the added addresses |\n\nexample of the returned object:\n\n```JavaScript\n{\n  Peers: [address1, address2, ...]\n}\n```\n\n### Example\n\n```JavaScript\nconst validIp4 = '/ip4/104....9z'\n\nconst res = await ipfs.bootstrap.add(validIp4)\nconsole.log(res.Peers)\n// Logs:\n// ['/ip4/104....9z']\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.bootstrap.reset([options])`\n\n> Reset the bootstrap list to contain only the default bootstrap nodes\n\n### Parameters\n\nNone.\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<{ Peers: Array<MultiAddr> }>` | An object that contains an array with all the added addresses |\n\nexample of the returned object:\n\n```JavaScript\n{\n  Peers: [address1, address2, ...]\n}\n```\n\n### Example\n\n```JavaScript\nconst res = await ipfs.bootstrap.reset()\nconsole.log(res.Peers)\n// Logs:\n// ['/ip4/104....9z']\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.bootstrap.list([options])`\n\n> List all peer addresses in the bootstrap list\n\n### Parameters\n\nNone\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<{ Peers: Array<MultiAddr> }>` | An object that contains an array with all the bootstrap addresses |\n\nexample of the returned object:\n\n```JavaScript\n{\n  Peers: [address1, address2, ...]\n}\n```\n\n### Example\n\n```JavaScript\nconst res = await ipfs.bootstrap.list()\nconsole.log(res.Peers)\n// Logs:\n// [address1, address2, ...]\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.bootstrap.rm(addr, [options])`\n\n> Remove a peer address from the bootstrap list\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| addr | [MultiAddr][] | The address of a network peer |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<{ Peers: Array<MultiAddr> }>` | An object that contains an array with all the removed addresses |\n\n```JavaScript\n{\n  Peers: [address1, address2, ...]\n}\n```\n\n### Example\n\n```JavaScript\nconst res = await ipfs.bootstrap.rm('address1')\nconsole.log(res.Peers)\n// Logs:\n// [address1, ...]\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.bootstrap.clear([options])`\n\n> Remove all peer addresses from the bootstrap list\n\n### Parameters\n\nNone.\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<{ Peers: Array<MultiAddr> }>` | An object that contains an array with all the removed addresses |\n\n```JavaScript\n{\n  Peers: [address1, address2, ...]\n}\n```\n\n### Example\n\n```JavaScript\nconst res = await ipfs.bootstrap.clear()\nconsole.log(res.Peers)\n// Logs:\n// [address1, address2, ...]\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n[examples]: https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/src/bootstrap\n[MultiAddr]: https://github.com/multiformats/js-multiaddr\n[AbortSignal]: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal\n"
  },
  {
    "path": "docs/core-api/CONFIG.md",
    "content": "# Config API <!-- omit in toc -->\n\n- [`ipfs.config.get(key, [options])`](#ipfsconfiggetkey-options)\n  - [Parameters](#parameters)\n  - [Options](#options)\n  - [Returns](#returns)\n  - [Example](#example)\n- [`ipfs.config.getAll([options])`](#ipfsconfiggetkey-options)\n  - [Options](#options)\n  - [Returns](#returns)\n  - [Example](#example)\n- [`ipfs.config.set(key, value, [options])`](#ipfsconfigsetkey-value-options)\n  - [Parameters](#parameters-1)\n  - [Options](#options-1)\n  - [Returns](#returns-1)\n  - [Example](#example-1)\n- [`ipfs.config.replace(config, [options])`](#ipfsconfigreplaceconfig-options)\n  - [Parameters](#parameters-2)\n  - [Options](#options-2)\n  - [Returns](#returns-2)\n  - [Example](#example-2)\n- [`ipfs.config.profiles.list([options])`](#ipfsconfigprofileslistoptions)\n  - [Parameters](#parameters-3)\n  - [Options](#options-3)\n  - [Returns](#returns-3)\n  - [Example](#example-3)\n- [`ipfs.config.profiles.apply(name, [options])`](#ipfsconfigprofilesapplyname-options)\n  - [Parameters](#parameters-4)\n  - [Options](#options-4)\n  - [Returns](#returns-4)\n  - [Example](#example-4)\n\n## `ipfs.config.get(key, [options])`\n\n> Returns the currently being used config. If the daemon is off, it returns the stored config.\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| key | `String` | The key of the value that should be fetched from the config file.  |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<Object>` | An object containing the configuration of the IPFS node |\n\n### Example\n\n```JavaScript\nconst config = await ipfs.config.get()\nconsole.log(config)\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.config.getAll([options])`\n\n> Returns the full config been used. If the daemon is off, it returns the stored config.\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<Object>` | An object containing the configuration of the IPFS node |\n\n### Example\n\n```JavaScript\nconst config = await ipfs.config.getAll()\nconsole.log(config)\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n\n## `ipfs.config.set(key, value, [options])`\n\n> Adds or replaces a config value.\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| key | `String` | The key of the value that should be added or replaced  |\n| value | any | The value to be set  |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<void>` | If action is successfully completed. Otherwise an error will be thrown |\n\nNote that this operation will **not** spark the restart of any service, i.e: if a config.replace changes the multiaddrs of the Swarm, Swarm will have to be restarted manually for the changes to take difference.\n\n### Example\n\n```JavaScript\nawait ipfs.config.set('Discovery.MDNS.Enabled', false)\n// MDNS Discovery was set to false\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.config.replace(config, [options])`\n\n> Adds or replaces a config file\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| config | Object | An object that contains the new config |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<void>` | If action is successfully completed. Otherwise an error will be thrown |\n\nNote that this operation will **not** spark the restart of any service, i.e: if a config.replace changes the multiaddrs of the Swarm, Swarm will have to be restarted manually for the changes to take difference.\n\n### Example\n\n```JavaScript\nconst newConfig = {\n  Bootstrap: []\n}\n\nawait ipfs.config.replace(newConfig)\n// config has been replaced\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.config.profiles.list([options])`\n\n> List available config profiles\n\n### Parameters\n\nNone\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<Array>` | An array with all the available config profiles |\n\n### Example\n\n```JavaScript\nconst profiles = await ipfs.config.profiles.list()\nprofiles.forEach(profile => {\n  console.info(profile.name, profile.description)\n})\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.config.profiles.apply(name, [options])`\n\n> Apply a config profile\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| name | `String` | The name of the profile to apply |\n\nCall `config.profiles.list()` for a list of valid profile names.\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| dryRun | `boolean` | false | If true does not apply the profile |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<Object>` | An object containing both the `original` and `updated` config |\n\n### Example\n\n```JavaScript\nconst diff = await ipfs.config.profiles.apply('lowpower')\nconsole.info(diff.original)\nconsole.info(diff.updated)\n```\n\nNote that you will need to restart your node for config changes to take effect.\n\nA great source of [examples][] can be found in the tests for this API.\n\n[examples]: https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/src/config\n[AbortSignal]: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal\n"
  },
  {
    "path": "docs/core-api/DAG.md",
    "content": "# DAG API <!-- omit in toc -->\n\n> The dag API comes to replace the `object API`, it supports the creation and manipulation of dag-pb object, as well as other IPLD formats (i.e dag-cbor, ethereum-block, git, etc)\n\n- [`ipfs.dag.export(cid, [options])`](#ipfsdagexportcid-options)\n  - [Parameters](#parameters)\n  - [Options](#options)\n  - [Returns](#returns)\n  - [Example](#example)\n- [`ipfs.dag.put(dagNode, [options])`](#ipfsdagputdagnode-options)\n  - [Parameters](#parameters-1)\n  - [Options](#options-1)\n  - [Returns](#returns-1)\n  - [Example](#example-1)\n- [`ipfs.dag.get(cid, [options])`](#ipfsdaggetcid-options)\n  - [Parameters](#parameters-2)\n  - [Options](#options-2)\n  - [Returns](#returns-2)\n  - [Example](#example-2)\n- [`ipfs.dag.import(source, [options])`](#ipfsdagimportsource-options)\n  - [Parameters](#parameters-3)\n  - [Options](#options-3)\n  - [Returns](#returns-3)\n  - [Example](#example-3)\n- [`ipfs.dag.resolve(ipfsPath, [options])`](#ipfsdagresolveipfspath-options)\n  - [Parameters](#parameters-4)\n  - [Options](#options-4)\n  - [Returns](#returns-4)\n  - [Example](#example-4)\n\n_Explore the DAG API through interactive coding challenges in our ProtoSchool tutorials:_\n- _[P2P data links with content addressing](https://proto.school/#/basics/) (beginner)_\n- _[Blogging on the Decentralized Web](https://proto.school/#/blog/) (intermediate)_\n\n## `ipfs.dag.export(cid, [options])`\n\n> Returns a stream of Uint8Arrays that make up a [CAR file][]\n\nExports a CAR for the entire DAG available from the given root CID. The CAR will have a single\nroot and IPFS will attempt to fetch and bundle all blocks that are linked within the connected\nDAG.\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| cid  | [CID][] | The root CID of the DAG we wish to export |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `AsyncIterable<Uint8Array>` | A stream containing the car file bytes |\n\n### Example\n\n```JavaScript\nimport { Readable } from 'stream'\n\nconst out = await ipfs.dag.export(cid)\n\nReadable.from(out).pipe(fs.createWriteStream('example.car'))\n```\n\nA great source of [examples][] can be found in the tests for this API.\n## `ipfs.dag.put(dagNode, [options])`\n\n> Store an IPLD format node\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| dagNode | `Object` | A DAG node that follows one of the supported IPLD formats |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| storeCodec | `String` | `'dag-cbor'` | The codec that the stored object will be encoded with |\n| inputCodec | `String` | `undefined` | If an already encoded object is provided (as a `Uint8Array`), the codec that the object is encoded with, otherwise it is assumed the `dagNode` argument is an object to be encoded |\n| hashAlg | `String` | `'sha2-256'` | The hash algorithm to be used over the serialized DAG node |\n| cid | [CID][] | `'dag-cbor'` | The IPLD format multicodec |\n| pin | `boolean` | `false` | Pin this node when adding to the blockstore |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` | Can be used to cancel any long running requests started as a result of this call |\n\n**Note**: You should pass `cid` or the `format` & `hashAlg` pair but _not both_.\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<CID>` | A [CID][] instance. The CID generated through the process or the one that was passed |\n\n### Example\n\n```JavaScript\nconst obj = { simple: 'object' }\nconst cid = await ipfs.dag.put(obj, { storeCodec: 'dag-cbor', hashAlg: 'sha2-512' })\n\nconsole.log(cid.toString())\n// zBwWX9ecx5F4X54WAjmFLErnBT6ByfNxStr5ovowTL7AhaUR98RWvXPS1V3HqV1qs3r5Ec5ocv7eCdbqYQREXNUfYNuKG\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.dag.get(cid, [options])`\n\n> Retrieve an IPLD format node\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| cid | [CID][] | A CID that resolves to a node to get |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| path | `String` | An optional path within the DAG to resolve |\n| localResolve | `boolean` | `false` | If set to true, it will avoid resolving through different objects |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<Object>` | An object representing an IPLD format node |\n\nThe returned object contains:\n\n- `value` - the value or node that was fetched during the get operation.\n- `remainderPath` - The remainder of the Path that the node was unable to resolve or what was left in a localResolve scenario.\n\n### Example\n\n```JavaScript\n// example obj\nconst obj = {\n  a: 1,\n  b: [1, 2, 3],\n  c: {\n    ca: [5, 6, 7],\n    cb: 'foo'\n  }\n}\n\nconst cid = await ipfs.dag.put(obj, { storeCodec: 'dag-cbor', hashAlg: 'sha2-256' })\nconsole.log(cid.toString())\n// zdpuAmtur968yprkhG9N5Zxn6MFVoqAWBbhUAkNLJs2UtkTq5\n\nasync function getAndLog(cid, path) {\n  const result = await ipfs.dag.get(cid, { path })\n  console.log(result.value)\n}\n\nawait getAndLog(cid, '/a')\n// Logs:\n// 1\n\nawait getAndLog(cid, '/b')\n// Logs:\n// [1, 2, 3]\n\nawait getAndLog(cid, '/c')\n// Logs:\n// {\n//   ca: [5, 6, 7],\n//   cb: 'foo'\n// }\n\nawait getAndLog(cid, '/c/ca/1')\n// Logs:\n// 6\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.dag.import(source, [options])`\n\n> Adds one or more [CAR file][]s full of blocks to the repo for this node\n\nImport all blocks from one or more CARs and optionally recursively pin the roots identified\nwithin the CARs.\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| sources | `AsyncIterable<Uint8Array>` | One or more [CAR file][] streams |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| pinRoots | `boolean` | `true` | Whether to recursively pin each root to the blockstore |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `AsyncIterable<{ root: { cid: CID, pinErrorMsg?: string } }>` | A stream containing all roots from the car file(s) that are pinned |\n\n### Example\n\n```JavaScript\nimport fs from 'fs'\n\nfor await (const result of ipfs.dag.import(fs.createReadStream('./path/to/archive.car'))) {\n  console.info(result)\n  // Qmfoo\n}\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.dag.resolve(ipfsPath, [options])`\n\n> Returns the CID and remaining path of the node at the end of the passed IPFS path\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| ipfsPath | `String` or [CID][] | An IPFS path, e.g. `/ipfs/bafy/dir/file.txt` or a [CID][] instance |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| path | `String` | `undefined` | If `ipfsPath` is a [CID][], you may pass a path here |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<{ cid: CID, remainderPath: String }>` | The last CID encountered during the traversal and the path to the end of the IPFS path inside the node referenced by the CID |\n\n### Example\n\n```JavaScript\n// example obj\nconst obj = {\n  a: 1,\n  b: [1, 2, 3],\n  c: {\n    ca: [5, 6, 7],\n    cb: 'foo'\n  }\n}\n\nconst cid = await ipfs.dag.put(obj, { storeCodec: 'dag-cbor', hashAlg: 'sha2-256' })\nconsole.log(cid.toString())\n// bafyreicyer3d34cutdzlsbe2nqu5ye62mesuhwkcnl2ypdwpccrsecfmjq\n\nconst result = await ipfs.dag.resolve(`${cid}/c/cb`)\nconsole.log(result)\n// Logs:\n// {\n//   cid: CID(bafyreicyer3d34cutdzlsbe2nqu5ye62mesuhwkcnl2ypdwpccrsecfmjq),\n//   remainderPath: 'c/cb'\n// }\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n[examples]: https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/src/dag\n[cid]: https://docs.ipfs.io/concepts/content-addressing\n[AbortSignal]: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal\n[CAR file]: https://ipld.io/specs/transport/car/\n"
  },
  {
    "path": "docs/core-api/DHT.md",
    "content": "# DHT API <!-- omit in toc -->\n\n- [`ipfs.dht.findPeer(peerId, [options])`](#ipfsdhtfindpeerpeerid-options)\n  - [Parameters](#parameters)\n  - [Options](#options)\n  - [Returns](#returns)\n  - [Example](#example)\n- [`ipfs.dht.findProvs(cid, [options])`](#ipfsdhtfindprovscid-options)\n  - [Parameters](#parameters-1)\n  - [Options](#options-1)\n  - [Returns](#returns-1)\n  - [Example](#example-1)\n- [`ipfs.dht.get(key, [options])`](#ipfsdhtgetkey-options)\n  - [Parameters](#parameters-2)\n  - [Options](#options-2)\n  - [Returns](#returns-2)\n  - [Example](#example-2)\n- [`ipfs.dht.provide(cid, [options])`](#ipfsdhtprovidecid-options)\n  - [Parameters](#parameters-3)\n  - [Options](#options-3)\n  - [Returns](#returns-3)\n  - [Example](#example-3)\n- [`ipfs.dht.put(key, value, [options])`](#ipfsdhtputkey-value-options)\n  - [Parameters](#parameters-4)\n  - [Options](#options-4)\n  - [Returns](#returns-4)\n  - [Example](#example-4)\n- [`ipfs.dht.query(peerId, [options])`](#ipfsdhtquerypeerid-options)\n  - [Parameters](#parameters-5)\n  - [Options](#options-5)\n  - [Returns](#returns-5)\n  - [Example](#example-5)\n\n## `ipfs.dht.findPeer(peerId, [options])`\n\n> Find the multiaddresses associated with a Peer ID\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| peerId | [PeerID][] | The Peer ID of the node to find |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<{ id: String, addrs: Multiaddr[] }>` | A promise that resolves to an object with `id` and `addrs`. `id` is a String - the peer's ID and `addrs` is an array of [Multiaddr](https://github.com/multiformats/js-multiaddr/) - addresses for the peer. |\n\n### Example\n\n```JavaScript\nconst info = await ipfs.dht.findPeer('QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt')\n\nconsole.log(info.id.toString())\n/*\nQmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt\n*/\n\ninfo.addrs.forEach(addr => console.log(addr.toString()))\n/*\n/ip4/147.75.94.115/udp/4001/quic\n/ip6/2604:1380:3000:1f00::1/udp/4001/quic\n/dnsaddr/bootstrap.libp2p.io\n/ip6/2604:1380:3000:1f00::1/tcp/4001\n/ip4/147.75.94.115/tcp/4001\n*/\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.dht.findProvs(cid, [options])`\n\n> Find peers that can provide a specific value, given a CID.\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| cid | [CID][] | The CID of the content to find |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| numProviders | `Number` | 20 | How many providers to find |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\nNote that if `options.numProviders` are not found an error will be thrown.\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `AsyncIterable<{ id: String, addrs: Multiaddr[] }>` | A async iterable that yields objects with `id` and `addrs`. `id` is a String - the peer's ID and `addrs` is an array of [Multiaddr](https://github.com/multiformats/js-multiaddr/) - addresses for the peer. |\n\n### Example\n\n```JavaScript\nimport { CID } from 'multiformats/cid'\n\nconst providers = ipfs.dht.findProvs(CID.parse('QmdPAhQRxrDKqkGPvQzBvjYe3kU8kiEEAd2J6ETEamKAD9'))\n\nfor await (const provider of providers) {\n  console.log(provider.id.toString())\n}\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.dht.get(key, [options])`\n\n> Given a key, query the routing system for its best value.\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| key | `Uint8Array` or `string` | The key associated with the value to find |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<Uint8Array>` | The value that was stored under that key |\n\n### Example\n\n```JavaScript\nconst value = await ipfs.dht.get(key)\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.dht.provide(cid, [options])`\n\n> Announce to the network that you are providing given values.\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| cid | [CID][] or Array<[CID][]> | The key associated with the value to find |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| recursive | `boolean` | false | If `true` the entire graph will be provided recursively |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `AsyncIterable<Object>` | DHT query messages. See example below for structure. |\n\nNote: You must consume the iterable to completion to complete the provide operation.\n\n### Example\n\n```JavaScript\nfor await (const message of ipfs.dht.provide('QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR')) {\n  console.log(message)\n}\n\n/*\nPrints objects like:\n\n{\n  extra: 'dial backoff',\n  id: PeerId('QmWtewmnzJiQevJPSmG9s8aC7yRfK2WXTCdRc1pCbDFu6z'),\n  responses: [\n    {\n      addrs: [\n        Multiaddr(/ip4/127.0.0.1/tcp/4001),\n        Multiaddr(/ip4/172.20.0.3/tcp/4001),\n        Multiaddr(/ip4/35.178.190.196/tcp/1024)\n      ],\n      id: PeerId('QmRz5Nth4jTFuJJKcjyb6uwvrhxWbruRvamKY2PJxwJKw8')\n    }\n  ],\n  type: 1\n}\n\nFor message `type` values, see:\nhttps://github.com/libp2p/go-libp2p-core/blob/6e566d10f4a5447317a66d64c7459954b969bdab/routing/query.go#L15-L24\n*/\n```\n\nAlternatively you can simply \"drain\" the iterable:\n\n```js\nimport drain from 'it-drain'\nawait drain(ipfs.dht.provide('QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR'))\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.dht.put(key, value, [options])`\n\n> Write a key/value pair to the routing system.\n\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| key | Uint8Array | The key to put the value as |\n| value | Uint8Array | Value to put |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `AsyncIterable<Object>` | DHT query messages. See example below for structure. |\n\n### Example\n\n```JavaScript\nfor await (const message of ipfs.dht.put(key, value)) {\n  console.log(message)\n}\n\n/*\nPrints objects like:\n\n{\n  extra: 'dial backoff',\n  id: PeerId('QmWtewmnzJiQevJPSmG9s8aC7yRfK2WXTCdRc1pCbDFu6z'),\n  responses: [\n    {\n      addrs: [\n        Multiaddr(/ip4/127.0.0.1/tcp/4001),\n        Multiaddr(/ip4/172.20.0.3/tcp/4001),\n        Multiaddr(/ip4/35.178.190.196/tcp/1024)\n      ],\n      id: PeerId('QmRz5Nth4jTFuJJKcjyb6uwvrhxWbruRvamKY2PJxwJKw8')\n    }\n  ],\n  type: 1\n}\n\nFor message `type` values, see:\nhttps://github.com/libp2p/go-libp2p-core/blob/6e566d10f4a5447317a66d64c7459954b969bdab/routing/query.go#L15-L24\n*/\n```\n\nAlternatively you can simply \"drain\" the iterable:\n\n```js\nimport drain from 'it-drain'\nawait drain(ipfs.dht.put(key, value))\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.dht.query(peerId, [options])`\n\n> Find the closest Peer IDs to a given Peer ID or CID by querying the DHT.\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| peerId | [PeerID][] or [CID][] | The peer id to query |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `AsyncIterable<Object>` | DHT query messages. See example below for structure. |\n\n### Example\n\n```JavaScript\nfor await (const info of ipfs.dht.query('QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt')) {\n  console.log(info)\n}\n\n/*\nPrints objects like:\n\n{\n  extra: 'dial backoff',\n  id: 'QmWtewmnzJiQevJPSmG9s8aC7yRfK2WXTCdRc1pCbDFu6z',\n  responses: [\n    {\n      addrs: [\n        Multiaddr(/ip4/127.0.0.1/tcp/4001),\n        Multiaddr(/ip4/172.20.0.3/tcp/4001),\n        Multiaddr(/ip4/35.178.190.196/tcp/1024)\n      ],\n      id: PeerId('QmRz5Nth4jTFuJJKcjyb6uwvrhxWbruRvamKY2PJxwJKw8')\n    }\n  ],\n  type: 1\n}\n\nFor message `type` values, see:\nhttps://github.com/libp2p/go-libp2p-core/blob/6e566d10f4a5447317a66d64c7459954b969bdab/routing/query.go#L15-L24\n*/\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n[examples]: https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/src/dht\n[peerid]: https://docs.libp2p.io/concepts/peer-id/\n[cid]: https://docs.ipfs.io/concepts/content-addressing\n[AbortSignal]: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal\n"
  },
  {
    "path": "docs/core-api/FILES.md",
    "content": "# Files API <!-- omit in toc -->\n\n> The files API enables users to use the File System abstraction of IPFS. There are two Files API, one at the top level, the original `add`, `cat`, `get` and `ls`, and another behind the [`files`, also known as MFS](https://docs.ipfs.io/guides/concepts/mfs/)\n\n_Explore the Mutable File System through interactive coding challenges in our [ProtoSchool tutorial](https://proto.school/#/mutable-file-system/)._\n\n- [The Regular API](#the-regular-api)\n  - [`ipfs.add(data, [options])`](#ipfsadddata-options)\n    - [Parameters](#parameters)\n      - [FileObject](#fileobject)\n      - [FileContent](#filecontent)\n    - [Options](#options)\n    - [Returns](#returns)\n    - [Example](#example)\n  - [`ipfs.addAll(source, [options])`](#ipfsaddallsource-options)\n    - [Parameters](#parameters-1)\n      - [FileStream](#filestream)\n    - [Options](#options-1)\n    - [Returns](#returns-1)\n    - [Example](#example-1)\n    - [Notes](#notes)\n      - [Chunking options](#chunking-options)\n      - [Hash algorithms](#hash-algorithms)\n      - [Importing files from the file system](#importing-files-from-the-file-system)\n      - [Importing a file from a URL](#importing-a-file-from-a-url)\n  - [`ipfs.cat(ipfsPath, [options])`](#ipfscatipfspath-options)\n    - [Parameters](#parameters-2)\n    - [Options](#options-2)\n    - [Returns](#returns-2)\n    - [Example](#example-2)\n  - [`ipfs.get(ipfsPath, [options])`](#ipfsgetipfspath-options)\n    - [Parameters](#parameters-3)\n    - [Options](#options-3)\n    - [Returns](#returns-3)\n    - [Example](#example-3)\n  - [`ipfs.ls(ipfsPath)`](#ipfslsipfspath)\n    - [Parameters](#parameters-4)\n    - [Options](#options-4)\n    - [Returns](#returns-4)\n    - [Example](#example-4)\n- [The Mutable Files API](#the-mutable-files-api)\n  - [`ipfs.files.chmod(path, mode, [options])`](#ipfsfileschmodpath-mode-options)\n    - [Parameters](#parameters-5)\n    - [Options](#options-5)\n    - [Returns](#returns-5)\n    - [Example](#example-5)\n  - [`ipfs.files.cp(...from, to, [options])`](#ipfsfilescpfrom-to-options)\n    - [Parameters](#parameters-6)\n    - [Options](#options-6)\n    - [Returns](#returns-6)\n    - [Example](#example-6)\n    - [Notes](#notes-1)\n  - [`ipfs.files.mkdir(path, [options])`](#ipfsfilesmkdirpath-options)\n    - [Parameters](#parameters-7)\n    - [Options](#options-7)\n    - [Returns](#returns-7)\n    - [Example](#example-7)\n  - [`ipfs.files.stat(path, [options])`](#ipfsfilesstatpath-options)\n    - [Parameters](#parameters-8)\n    - [Options](#options-8)\n    - [Returns](#returns-8)\n    - [Example](#example-8)\n  - [`ipfs.files.touch(path, [options])`](#ipfsfilestouchpath-options)\n    - [Parameters](#parameters-9)\n    - [Options](#options-9)\n    - [Returns](#returns-9)\n    - [Example](#example-9)\n  - [`ipfs.files.rm(path, [options])`](#ipfsfilesrmpath-options)\n    - [Parameters](#parameters-10)\n    - [Options](#options-10)\n    - [Returns](#returns-10)\n    - [Example](#example-10)\n  - [`ipfs.files.read(path, [options])`](#ipfsfilesreadpath-options)\n    - [Parameters](#parameters-11)\n    - [Options](#options-11)\n    - [Returns](#returns-11)\n    - [Example](#example-11)\n  - [`ipfs.files.write(path, content, [options])`](#ipfsfileswritepath-content-options)\n    - [Parameters](#parameters-12)\n    - [Options](#options-12)\n    - [Returns](#returns-12)\n    - [Example](#example-12)\n  - [`ipfs.files.mv(...from, to, [options])`](#ipfsfilesmvfrom-to-options)\n    - [Parameters](#parameters-13)\n    - [Options](#options-13)\n    - [Returns](#returns-13)\n    - [Example](#example-13)\n    - [Notes](#notes-2)\n  - [`ipfs.files.flush(path, [options])`](#ipfsfilesflushpath-options)\n    - [Parameters](#parameters-14)\n    - [Options](#options-14)\n    - [Returns](#returns-14)\n    - [Example](#example-14)\n  - [`ipfs.files.ls(path, [options])`](#ipfsfileslspath-options)\n    - [Parameters](#parameters-15)\n    - [Options](#options-15)\n    - [Returns](#returns-15)\n    - [Example](#example-15)\n\n## The Regular API\nThe regular, top-level API for add, cat, get and ls Files on IPFS\n\n### `ipfs.add(data, [options])`\n\n> Import a file or data into IPFS.\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| data | Object | Data to import (see below) |\n\n`data` may be:\n\n* `FileContent` (see below for definition)\n* `FileObject` (see below for definition)\n\n##### FileObject\n\n`FileObject` is a plain JS object of the following form:\n\n```js\n{\n  // The path you want the file to be accessible at from the root CID _after_ it has been added\n  path?: string\n  // The contents of the file (see below for definition)\n  content?: FileContent\n  // File mode to store the entry with (see https://en.wikipedia.org/wiki/File_system_permissions#Numeric_notation)\n  mode?: number | string\n  // The modification time of the entry (see below for definition)\n  mtime?: UnixTime\n}\n```\n\nIf no `path` is specified, then the item will be added to the root level and will be given a name according to it's CID.\n\nIf no `content` is passed, then the item is treated as an empty directory.\n\nOne of `path` or `content` _must_ be passed.\n\nBoth `mode` and `mtime` are optional and will result in different [CID][]s for the same file if passed.\n\n`mode` will have a default value applied if not set, see [UnixFS Metadata](https://github.com/ipfs/specs/blob/master/UNIXFS.md#metadata) for further discussion.\n\n##### FileContent\n\n`FileContent` is one of the following types:\n\n```js\nUint8Array | Blob | String | Iterable<Uint8Array> | Iterable<number> | AsyncIterable<Uint8Array> | ReadableStream<Uint8Array>\n```\n\n`UnixTime` is one of the following types:\n\n```js\nDate | { secs: number, nsecs?: number } | number[]\n```\n\nAs an object, `secs` is the number of seconds since (positive) or before (negative) the Unix Epoch began and `nsecs` is the number of nanoseconds since the last full second.\n\nAs an array of numbers, it must have two elements, as per the output of [`process.hrtime()`](https://nodejs.org/dist/latest/docs/api/process.html#process_process_hrtime_time).\n\n#### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| chunker | `String` | `'size-262144'` | chunking algorithm used to build ipfs DAGs |\n| cidVersion | `Number` | `0` | the CID version to use when storing the data |\n| hashAlg | `String` | `'sha2-256'` | multihash hashing algorithm to use |\n| onlyHash | `boolean` | `false` | If true, will not add blocks to the blockstore |\n| pin | `boolean` | `true` | pin this object when adding |\n| progress | function | `undefined` | a function that will be called with the number of bytes added as a file is added to ipfs and the path of the file being added |\n| rawLeaves | `boolean` | `false` | if true, DAG leaves will contain raw file data and not be wrapped in a protobuf |\n| trickle | `boolean` | `false` | if true will use the [trickle DAG](https://godoc.org/github.com/ipsn/go-ipfs/gxlibs/github.com/ipfs/go-unixfs/importer/trickle) format for DAG generation |\n| wrapWithDirectory | `boolean` | `false` | Adds a wrapping node around the content |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n#### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<UnixFSEntry>` | A object describing the added data |\n\nEach yielded object is of the form:\n\n```JavaScript\n{\n  path: '/tmp/myfile.txt',\n  cid: CID('QmHash'),\n  mode: Number, // implicit if not provided - 0644 for files, 0755 for directories\n  mtime?: { secs: Number, nsecs: Number },\n  size: 123\n}\n```\n\n#### Example\n\n```js\nconst file = {\n  path: '/tmp/myfile.txt',\n  content: 'ABC'\n}\n\nconst result = await ipfs.add(file)\n\nconsole.info(result)\n\n/*\nPrints:\n{\n  \"path\": \"tmp\",\n  \"cid\": CID(\"QmWXdjNC362aPDtwHPUE9o2VMqPeNeCQuTBTv1NsKtwypg\"),\n  \"mode\": 493,\n  \"mtime\": { secs: Number, nsecs: Number },\n  \"size\": 67\n}\n*/\n```\n\nNow [ipfs.io/ipfs/Qm..pg/myfile.txt](https://ipfs.io/ipfs/QmWXdjNC362aPDtwHPUE9o2VMqPeNeCQuTBTv1NsKtwypg/myfile.txt) returns the \"ABC\" string.\n\n### `ipfs.addAll(source, [options])`\n\n> Import multiple files and data into IPFS.\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| source | [FileStream<FileContent\\|FileObject>](#filestream) | Data to import (see below) |\n\n##### FileStream\n\n`FileStream` is a stream of [FileContent](#filecontent) or [FileObject](#fileobject) entries of the type:\n\n```js\nIterable<FileContent|FileObject> | AsyncIterable<FileContent|FileObject> | ReadableStream<FileContent|FileObject>\n```\n\n#### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| chunker | `string` | `'size-262144'` | chunking algorithm used to build ipfs DAGs |\n| cidVersion | `number` | `0` | the CID version to use when storing the data |\n| enableShardingExperiment | `boolean` | `false` |  allows to create directories with an unlimited number of entries currently size of unixfs directories is limited by the maximum block size. Note that this is an experimental feature |\n| hashAlg | `String` | `'sha2-256'` | multihash hashing algorithm to use |\n| onlyHash | `boolean` | `false` | If true, will not add blocks to the blockstore |\n| pin | `boolean` | `true` | pin this object when adding |\n| progress | function | `undefined` | a function that will be called with the number of bytes added as a file is added to ipfs and the path of the file being added |\n| rawLeaves | `boolean` | `false` | if true, DAG leaves will contain raw file data and not be wrapped in a protobuf |\n| shardSplitThreshold | `Number` | `1000` | Directories with more than this number of files will be created as HAMT-sharded directories |\n| trickle | `boolean` | `false` | if true will use the [trickle DAG](https://godoc.org/github.com/ipsn/go-ipfs/gxlibs/github.com/ipfs/go-unixfs/importer/trickle) format for DAG generation |\n| wrapWithDirectory | `boolean` | `false` | Adds a wrapping node around the content |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n#### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `AsyncIterable<UnixFSEntry>` | An async iterable that yields objects describing the added data |\n\nEach yielded object is of the form:\n\n```JavaScript\n{\n  path: '/tmp/myfile.txt',\n  cid: CID('QmHash'),\n  mode: Number, // implicit if not provided - 0644 for files, 0755 for directories\n  mtime?: { secs: Number, nsecs: Number },\n  size: 123\n}\n```\n\n#### Example\n\n```js\nconst files = [{\n  path: '/tmp/myfile.txt',\n  content: 'ABC'\n}]\n\nfor await (const result of ipfs.addAll(files)) {\n  console.log(result)\n}\n\n/*\nPrints out objects like:\n\n{\n  \"path\": \"tmp\",\n  \"cid\": CID(\"QmWXdjNC362aPDtwHPUE9o2VMqPeNeCQuTBTv1NsKtwypg\"),\n  \"mode\": 493,\n  \"mtime\": { secs: Number, nsecs: Number },\n  \"size\": 67\n}\n\n{\n  \"path\": \"/tmp/myfile.txt\",\n  \"cid\": CID(\"QmNz1UBzpdd4HfZ3qir3aPiRdX5a93XwTuDNyXRc6PKhWW\"),\n  \"mode\": 420,\n  \"mtime\": { secs: Number, nsecs: Number },\n  \"size\": 11\n}\n*/\n```\n\nNow [ipfs.io/ipfs/Qm...WW](https://ipfs.io/ipfs/QmNz1UBzpdd4HfZ3qir3aPiRdX5a93XwTuDNyXRc6PKhWW) returns the \"ABC\" string.\n\n#### Notes\n\n##### Chunking options\n\nThe `chunker` option can be one of the following formats:\n  - size-{size}\n  - rabin\n  - rabin-{avg}\n  - rabin-{min}-{avg}-{max}\n\n`size-*` will result in fixed-size chunks, `rabin(-*)` will use [rabin fingerprinting](https://en.wikipedia.org/wiki/Rabin_fingerprint) to potentially generate variable size chunks.\n\n##### Hash algorithms\n\nSee the [multihash](https://github.com/multiformats/js-multihash/blob/master/src/constants.js#L5-L343) module for the list of all possible values.\n\n##### Importing files from the file system\n\nBoth js-ipfs and js-ipfs-http-client export a utility to make importing files from the file system easier (Note: it not available in the browser).\n\n```js\nimport { create, globSource } from 'ipfs'\n\nconst ipfs = await create()\n\n//options specific to globSource\nconst globSourceOptions = {\n  recursive: true\n};\n\n//example options to pass to IPFS\nconst addOptions = {\n  pin: true,\n  wrapWithDirectory: true,\n  timeout: 10000\n};\n\nfor await (const file of ipfs.addAll(globSource('./docs', globSourceOptions), addOptions)) {\n  console.log(file)\n}\n\n/*\n{\n  path: 'docs/assets/anchor.js',\n  cid: CID('QmVHxRocoWgUChLEvfEyDuuD6qJ4PhdDL2dTLcpUy3dSC2'),\n  size: 15347\n}\n{\n  path: 'docs/assets/bass-addons.css',\n  hash: CID('QmPiLWKd6yseMWDTgHegb8T7wVS7zWGYgyvfj7dGNt2viQ'),\n  size: 232\n}\n...\n*/\n```\n\n##### Importing a file from a URL\n\nBoth js-ipfs and js-ipfs-http-client export a utility to make importing a file from a URL easier.\n\n```js\nimport { create, urlSource } from 'ipfs'\n\nconst ipfs = await create()\n\nconst file = await ipfs.add(urlSource('https://ipfs.io/images/ipfs-logo.svg'))\nconsole.log(file)\n\n/*\n{\n  path: 'ipfs-logo.svg',\n  cid: CID('QmTqZhR6f7jzdhLgPArDPnsbZpvvgxzCZycXK7ywkLxSyU'),\n  size: 3243\n}\n*/\n```\n\nA great source of [examples](https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/src/add.js) can be found in the tests for this API.\n\n### `ipfs.cat(ipfsPath, [options])`\n\n> Returns a file addressed by a valid IPFS Path.\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| ipfsPath | String or [CID][] | An [IPFS path][] or CID to export |\n\n#### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| offset | `Number` | `undefined` | An offset to start reading the file from |\n| length | `Number` | `undefined` | An optional max length to read from the file |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n#### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `AsyncIterable<Uint8Array>` | An async iterable that yields `Uint8Array` objects with the contents of `path` |\n\n#### Example\n\n```JavaScript\nfor await (const chunk of ipfs.cat(ipfsPath)) {\n  console.info(chunk)\n}\n```\n\nA great source of [examples](https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/src/cat.js) can be found in the tests for this API.\n\n### `ipfs.get(ipfsPath, [options])`\n\n> Fetch a file or an entire directory tree from IPFS that is addressed by a valid IPFS Path.\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| ipfsPath | String or [CID][] | An [IPFS path][] or CID to export |\n\n#### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| archive | `boolean` | `undefined` | Return the file/directory in a tarball |\n| compress | `boolean` | `false` | Gzip the returned stream |\n| compressionLevel | `Number` | `undefined` | How much compression to apply (1-9) |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n#### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `AsyncIterable<Uint8Array>` | An async iterable that yields bytes |\n\nWhat is streamed as a response depends on the options passed and what the `ipfsPath` resolves to.\n\n1. If `ipfsPath` resolves to a file:\n   * By default you will get a tarball containing the file\n   * Pass `compress: true` (and an optional `compressionLevel`) to instead get the gzipped file contents\n   * Pass `compress: true` (and an optional `compressionLevel`) AND `archive: true` to get a gzipped tarball containing the file\n2. If `ipfsPath` resolves to a directory:\n   * By default you will get a tarball containing the contents of the directory\n   * Passing `compress: true` will cause an error\n   * Pass `compress: true` (and an optional `compressionLevel`) AND `archive: true` to get a gzipped tarball containing the contents of the directory\n\n#### Example\n\n```JavaScript\nconst cid = 'QmQ2r6iMNpky5f1m4cnm3Yqw8VSvjuKpTcK1X7dBR1LkJF'\n\nfor await (const buf of ipfs.get(cid)) {\n  // do something with buf\n}\n```\n\nA great source of [examples](https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/src/get.js) can be found in the tests for this API.\n\n### `ipfs.ls(ipfsPath)`\n\n> Lists a directory from IPFS that is addressed by a valid IPFS Path.\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| ipfsPath | String or [CID][] | An [IPFS path][] or CID to list |\n\n#### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n#### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `AsyncIterable<Object>` | An async iterable that yields objects representing the files |\n\nEach yielded object is of the form:\n\n```js\n{\n  depth: 1,\n  name: 'alice.txt',\n  path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/alice.txt',\n  size: 11696,\n  cid: CID('QmZyUEQVuRK3XV7L9Dk26pg6RVSgaYkiSTEdnT2kZZdwoi'),\n  type: 'file',\n  mode: Number, // implicit if not provided - 0644 for files, 0755 for directories\n  mtime?: { secs: Number, nsecs: Number }\n}\n```\n\n#### Example\n\n```JavaScript\nconst cid = 'QmQ2r6iMNpky5f1m4cnm3Yqw8VSvjuKpTcK1X7dBR1LkJF'\n\nfor await (const file of ipfs.ls(cid)) {\n  console.log(file.path)\n}\n```\n\nA great source of [examples](https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/src/ls.js) can be found in the tests for this API.\n\n---\n\n## The Mutable Files API\n\nThe Mutable File System (MFS) is a virtual file system on top of IPFS that exposes a Unix like API over a virtual directory. It enables users to write and read from paths without having to worry about updating the graph. It enables things like [ipfs-blob-store](https://github.com/ipfs/ipfs-blob-store) to exist.\n\n### `ipfs.files.chmod(path, mode, [options])`\n\n> Change mode for files and directories\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| path | String or [CID][] | An [MFS Path][], [IPFS path][] or CID to modify |\n| mode | String or Number | An integer (e.g. `0o755` or `parseInt('0755', 8)`) or a string modification of the existing mode, e.g. `'a+x'`, `'g-w'`, etc |\n\n#### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| recursive | `boolean` | `false` | If true `mode` will be applied to the entire tree under `path` |\n| flush | `boolean` | `true` | If true the changes will be immediately flushed to disk |\n| hashAlg | `String` | `'sha2-256'` | The hash algorithm to use for any updated entries |\n| cidVersion | `Number` | `0` | The CID version to use for any updated entries |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n#### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<void>` | If action is successfully completed. Otherwise an error will be thrown |\n\n#### Example\n\n```JavaScript\n// To give a file -rwxrwxrwx permissions\nawait ipfs.files.chmod('/path/to/file.txt', parseInt('0777', 8))\n\n// Alternatively\nawait ipfs.files.chmod('/path/to/file.txt', '+rwx')\n\n// You can omit the leading `0` too\nawait ipfs.files.chmod('/path/to/file.txt', '777')\n```\n\n### `ipfs.files.cp(...from, to, [options])`\n\n> Copy files from one location to another\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| from | One or more Strings or [CID][]s | An [MFS path][], [IPFS path][] or CID |\n| to | `String` | An [MFS path][] |\n\n#### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| parents | `boolean` | `false` | If true, create intermediate directories |\n| flush | `boolean` | `true` | If true the changes will be immediately flushed to disk |\n| hashAlg | `String` | `'sha2-256'` | The hash algorithm to use for any updated entries |\n| cidVersion | `Number` | `0` | The CID version to use for any updated entries |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n#### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<void>` | If action is successfully completed. Otherwise an error will be thrown |\n\n#### Example\n\n```JavaScript\n// To copy a file\nawait ipfs.files.cp('/src-file', '/dst-file')\n\n// To copy a directory\nawait ipfs.files.cp('/src-dir', '/dst-dir')\n\n// To copy multiple files to a directory\nawait ipfs.files.cp(['/src-file1', '/src-file2'], '/dst-dir')\n```\n\n#### Notes\n\nIf `from` has multiple values then `to` must be a directory.\n\nIf `from` has a single value and `to` exists and is a directory, `from` will be copied into `to`.\n\nIf `from` has a single value and `to` exists and is a file, `from` must be a file and the contents of `to` will be replaced with the contents of `from` otherwise an error will be returned.\n\nIf `from` is an IPFS path, and an MFS path exists with the same name, the IPFS path will be chosen.\n\nIf `from` is an IPFS path and the content does not exist in your node's repo, only the root node of the source file with be retrieved from the network and linked to from the destination. The remainder of the file will be retrieved on demand.\n\n### `ipfs.files.mkdir(path, [options])`\n\n> Make a directory in your MFS\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| path | `String` | The [MFS path][] to create a directory at |\n\n#### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| parents | `boolean` | `false` | If true, create intermediate directories |\n| mode | `Number` | `undefined` | An integer that represents the file mode |\n| mtime | `Object` | `undefined` | A Date object, an object with `{ secs, nsecs }` properties where `secs` is the number of seconds since (positive) or before (negative) the Unix Epoch began and `nsecs` is the number of nanoseconds since the last full second, or the output of [`process.hrtime()`](https://nodejs.org/api/process.html#process_process_hrtime_time) |\n| flush | `boolean` | `true` | If true the changes will be immediately flushed to disk |\n| hashAlg | `String` | `'sha2-256'` | The hash algorithm to use for any updated entries |\n| cidVersion | `Number` | `0` | The CID version to use for any updated entries |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n#### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<void>` | If action is successfully completed. Otherwise an error will be thrown |\n\n#### Example\n\n```JavaScript\nawait ipfs.files.mkdir('/my/beautiful/directory')\n```\n\n### `ipfs.files.stat(path, [options])`\n\n> Get file or directory statistics\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| path | `String` | The [MFS path][] return statistics from |\n\n#### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| hash | `boolean` | `false` | If true, return only the CID |\n| size | `boolean` | `false` | If true, return only the size |\n| withLocal | `boolean` | `false` | If true, compute the amount of the DAG that is local and if possible the total size |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n#### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<Object>` | An object containing the file/directory status |\n\nthe returned object has the following keys:\n\n- `cid` a [CID][cid] instance\n- `size` is an integer with the file size in Bytes\n- `cumulativeSize` is an integer with the size of the DAGNodes making up the file in Bytes\n- `type` is a string that can be either `directory` or `file`\n- `blocks` if `type` is `directory`, this is the number of files in the directory. If it is `file` it is the number of blocks that make up the file\n- `withLocality` is a boolean to indicate if locality information is present\n- `local` is a boolean to indicate if the queried dag is fully present locally\n- `sizeLocal` is an integer indicating the cumulative size of the data present locally\n\n#### Example\n\n```JavaScript\nconst stats = await ipfs.files.stat('/file.txt')\nconsole.log(stats)\n\n// {\n//   hash: CID('QmXmJBmnYqXVuicUfn9uDCC8kxCEEzQpsAbeq1iJvLAmVs'),\n//   size: 60,\n//   cumulativeSize: 118,\n//   blocks: 1,\n//   type: 'file'\n// }\n```\n\n### `ipfs.files.touch(path, [options])`\n\n> Update the mtime of a file or directory\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| path | `String` | The [MFS path][] to update the mtime for |\n\n#### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| mtime | `Object` | Now | Either a ` Date` object, an object with `{ sec, nsecs }` properties or the output of `process.hrtime()` |\n| flush | `boolean` | `true` | If true the changes will be immediately flushed to disk |\n| hashAlg | `String` | `'sha2-256'` | The hash algorithm to use for any updated entries |\n| cidVersion | `Number` | `0` | The CID version to use for any updated entries |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n#### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<void>` | If action is successfully completed. Otherwise an error will be thrown |\n\n#### Example\n\n```JavaScript\n// set the mtime to the current time\nawait ipfs.files.touch('/path/to/file.txt')\n\n// set the mtime to a specific time\nawait ipfs.files.touch('/path/to/file.txt', {\n  mtime: new Date('May 23, 2014 14:45:14 -0700')\n})\n```\n\n### `ipfs.files.rm(path, [options])`\n\n> Remove a file or directory.\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| path | `String` or `Array<String>` | One or more [MFS path][]s to remove |\n\n#### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| recursive | `boolean` | `false` | If true all paths under the specifed path(s) will be removed |\n| flush | `boolean` | `true` | If true the changes will be immediately flushed to disk |\n| hashAlg | `String` | `'sha2-256'` | The hash algorithm to use for any updated entries |\n| cidVersion | `Number` | `0` | The CID version to use for any updated entries |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n#### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<void>` | If action is successfully completed. Otherwise an error will be thrown |\n\n#### Example\n\n```JavaScript\n// To remove a file\nawait ipfs.files.rm('/my/beautiful/file.txt')\n\n// To remove multiple files\nawait ipfs.files.rm(['/my/beautiful/file.txt', '/my/other/file.txt'])\n\n// To remove a directory\nawait ipfs.files.rm('/my/beautiful/directory', { recursive: true })\n```\n\n### `ipfs.files.read(path, [options])`\n\n> Read a file\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| path | `String` or [CID][] | An [MFS path][], [IPFS Path][] or [CID][] to read |\n\n#### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| offset | `Number` | `undefined` | An offset to start reading the file from |\n| length | `Number` | `undefined` | An optional max length to read from the file |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n#### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `AsyncIterable<Uint8Array>` | An async iterable that yields `Uint8Array` objects with the contents of `path` |\n\n#### Example\n\n```JavaScript\nconst chunks = []\n\nfor await (const chunk of ipfs.files.read('/hello-world')) {\n  chunks.push(chunk)\n}\n\nconsole.log(uint8ArrayConcat(chunks).toString())\n// Hello, World!\n```\n\n### `ipfs.files.write(path, content, [options])`\n\n> Write to an MFS path\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| path | `String` | The [MFS path] where you will write to |\n| content | `String`, `Uint8Array`, `AsyncIterable<Uint8Array>` or [`Blob`][blob] | The content to write to the path |\n\n#### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| offset | `Number` | `undefined` | An offset to start writing to file at |\n| length | `Number` | `undefined` | Optionally limit how many bytes are read from the stream |\n| create | `boolean` | `false` | Create the MFS path if it does not exist |\n| parents | `boolean` | `false` | Create intermediate MFS paths if they do not exist |\n| truncate | `boolean` | `false` | Truncate the file at the MFS path if it would have been larger than the passed `content` |\n| rawLeaves | `boolean` | `false ` | If true, DAG leaves will contain raw file data and not be wrapped in a protobuf |\n| mode | `Number` | `undefined` | An integer that represents the file mode |\n| mtime | `Object` | `undefined` | A Date object, an object with `{ secs, nsecs }` properties where `secs` is the number of seconds since (positive) or before (negative) the Unix Epoch began and `nsecs` is the number of nanoseconds since the last full second, or the output of [`process.hrtime()`](https://nodejs.org/api/process.html#process_process_hrtime_time) |\n| flush | `boolean` | `true` | If true the changes will be immediately flushed to disk |\n| hashAlg | `String` | `'sha2-256'` | The hash algorithm to use for any updated entries |\n| cidVersion | `Number` | `0` | The CID version to use for any updated entries |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n#### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<void>` | If action is successfully completed. Otherwise an error will be thrown |\n\n#### Example\n\n```JavaScript\nawait ipfs.files.write('/hello-world', new TextEncoder().encode('Hello, world!'))\n```\n\n### `ipfs.files.mv(...from, to, [options])`\n\n> Move files from one location to another\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| ...from | `String` | One or more [MFS path][]s to move |\n| to | `String` | The location to move files to |\n\n#### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| parents | `boolean` | `false` | Create intermediate MFS paths if they do not exist |\n| flush | `boolean` | `true` | If true the changes will be immediately flushed to disk |\n| hashAlg | `String` | `'sha2-256'` | The hash algorithm to use for any updated entries |\n| cidVersion | `Number` | `0` | The CID version to use for any updated entries |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n#### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<void>` | If action is successfully completed. Otherwise an error will be thrown |\n\n#### Example\n\n```JavaScript\nawait ipfs.files.mv('/src-file', '/dst-file')\n\nawait ipfs.files.mv('/src-dir', '/dst-dir')\n\nawait ipfs.files.mv(['/src-file1', '/src-file2'], '/dst-dir')\n```\n\n#### Notes\n\nIf `from` has multiple values then `to` must be a directory.\n\nIf `from` has a single value and `to` exists and is a directory, `from` will be moved into `to`.\n\nIf `from` has a single value and `to` exists and is a file, `from` must be a file and the contents of `to` will be replaced with the contents of `from` otherwise an error will be returned.\n\nIf `from` is an IPFS path, and an MFS path exists with the same name, the IPFS path will be chosen.\n\nIf `from` is an IPFS path and the content does not exist in your node's repo, only the root node of the source file with be retrieved from the network and linked to from the destination. The remainder of the file will be retrieved on demand.\n\nAll values of `from` will be removed after the operation is complete unless they are an IPFS path.\n\n### `ipfs.files.flush(path, [options])`\n\n> Flush a given path's data to the disk\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| path | `String` | The [MFS path][] to flush |\n\n#### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n#### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<CID>` | The CID of the path that has been flushed |\n\n#### Example\n\n```JavaScript\nconst cid = await ipfs.files.flush('/')\n```\n\n### `ipfs.files.ls(path, [options])`\n\n> List directories in the local mutable namespace\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| path | `String` | The [MFS path][] to list |\n\n#### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n#### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `AsyncIterable<Object>` | An async iterable that yields objects representing the files |\n\nEach object contains the following keys:\n\n- `name` which is the file's name\n- `type` which is the object's type (`directory` or `file`)\n- `size` the size of the file in bytes\n- `cid` the hash of the file (A [CID][cid] instance)\n- `mode` the UnixFS mode as a Number\n- `mtime` an objects with numeric `secs` and `nsecs` properties\n\n#### Example\n\n```JavaScript\nfor await (const file of ipfs.files.ls('/screenshots')) {\n  console.log(file.name)\n}\n// 2018-01-22T18:08:46.775Z.png\n// 2018-01-22T18:08:49.184Z.png\n```\n\n[b]: https://www.npmjs.com/package/buffer\n[file]: https://developer.mozilla.org/en-US/docs/Web/API/File\n[cid]: https://docs.ipfs.io/concepts/content-addressing\n[blob]: https://developer.mozilla.org/en-US/docs/Web/API/Blob\n[IPFS Path]: https://www.npmjs.com/package/is-ipfs#isipfspathpath\n[MFS Path]: https://docs.ipfs.io/guides/concepts/mfs/\n[AbortSignal]: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal\n"
  },
  {
    "path": "docs/core-api/KEY.md",
    "content": "# Key API <!-- omit in toc -->\n\n- [`ipfs.key.gen(name, [options])`](#ipfskeygenname-options)\n  - [Parameters](#parameters)\n  - [Options](#options)\n  - [Returns](#returns)\n  - [Example](#example)\n- [`ipfs.key.list([options])`](#ipfskeylistoptions)\n  - [Parameters](#parameters-1)\n  - [Options](#options-1)\n  - [Returns](#returns-1)\n  - [Example](#example-1)\n- [`ipfs.key.rm(name, [options])`](#ipfskeyrmname-options)\n  - [Parameters](#parameters-2)\n  - [Options](#options-2)\n  - [Returns](#returns-2)\n  - [Example](#example-2)\n- [`ipfs.key.rename(oldName, newName, [options])`](#ipfskeyrenameoldname-newname-options)\n  - [Parameters](#parameters-3)\n  - [Options](#options-3)\n  - [Returns](#returns-3)\n  - [Example](#example-3)\n- [`ipfs.key.export(name, password, [options])`](#ipfskeyexportname-password-options)\n  - [Parameters](#parameters-4)\n  - [Options](#options-4)\n  - [Returns](#returns-4)\n  - [Example](#example-4)\n- [`ipfs.key.import(name, pem, password, [options])`](#ipfskeyimportname-pem-password-options)\n  - [Parameters](#parameters-5)\n  - [Options](#options-5)\n  - [Returns](#returns-5)\n  - [Example](#example-5)\n\n## `ipfs.key.gen(name, [options])`\n\n> Generate a new key\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| name | String | The name to give the key |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| type | `String` | `'rsa'` | The key type, one of `'rsa'` or `'ed25519'` |\n| size | `Number` | `2048` | The key size in bits |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<Object>` | An object that describes the key; `name` and `id` |\n\n### Example\n\n```JavaScript\nconst key = await ipfs.key.gen('my-key', {\n    type: 'rsa',\n    size: 2048\n})\n\nconsole.log(key)\n// { id: 'QmYWqAFvLWb2G5A69JGXui2JJXzaHXiUEmQkQgor6kNNcJ',\n//  name: 'my-key' }\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.key.list([options])`\n\n> List all the keys\n\n### Parameters\n\nNone\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<Array>` | An array representing all the keys |\n\nexample of the returned array:\n\n```js\n{\n  id: 'hash',   // string - the hash of the key\n  name: 'self'  // string - the name of the key\n}\n```\n\n### Example\n\n```JavaScript\nconst keys = await ipfs.key.list()\n\nconsole.log(keys)\n// [\n//   { id: 'QmTe4tuceM2sAmuZiFsJ9tmAopA8au71NabBDdpPYDjxAb',\n//     name: 'self' },\n//   { id: 'QmWETF5QvzGnP7jKq5sPDiRjSM2fzwzNsna4wSBEzRzK6W',\n//     name: 'my-key' }\n// ]\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.key.rm(name, [options])`\n\n> Remove a key\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| name | String | The name of the key to remove |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<Object>` | An object that describes the removed key |\n\nexample of the returned object:\n\n```js\n{\n  id: 'hash',   // string - the hash of the key\n  name: 'self'  // string - the name of the key\n}\n```\n\n### Example\n\n```JavaScript\nconst key = await ipfs.key.rm('my-key')\n\nconsole.log(key)\n// { id: 'QmWETF5QvzGnP7jKq5sPDiRjSM2fzwzNsna4wSBEzRzK6W',\n//   name: 'my-key' }\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.key.rename(oldName, newName, [options])`\n\n> Rename a key\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| oldName | String | The current key name |\n| newName | String | The desired key name |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<Object>` | An object that describes the renamed key |\n\n### Example\n\n```JavaScript\nconst key = await ipfs.key.rename('my-key', 'my-new-key')\n\nconsole.log(key)\n// { id: 'Qmd4xC46Um6s24MradViGLFtMitvrR4SVexKUgPgFjMNzg',\n//   was: 'my-key',\n//   now: 'my-new-key',\n//   overwrite: false }\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.key.export(name, password, [options])`\n\n> Export a key in a PEM encoded password protected PKCS #8\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| name | String | The name of the key to export |\n| password | String | Password to set on the PEM output |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<String>` | The string representation of the key |\n\n### Example\n\n```JavaScript\nconst pem = await ipfs.key.export('self', 'password')\n\nconsole.log(pem)\n// -----BEGIN ENCRYPTED PRIVATE KEY-----\n// MIIFDTA/BgkqhkiG9w0BBQ0wMjAaBgkqhkiG9w0BBQwwDQQIpdO40RVyBwACAWQw\n// ...\n// YA==\n// -----END ENCRYPTED PRIVATE KEY-----\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.key.import(name, pem, password, [options])`\n\n> Import a PEM encoded password protected PKCS #8 key\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| name | String | The name of the key to export |\n| pem | String | The PEM encoded key |\n| password | String | The password that protects the PEM key |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<Object>` | An object that describes the new key |\n\n### Example\n\n```JavaScript\nconst key = await ipfs.key.import('clone', pem, 'password')\n\nconsole.log(key)\n// { id: 'QmQRiays958UM7norGRQUG3tmrLq8pJdmJarwYSk2eLthQ',\n//   name: 'clone' }\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n[examples]: https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/src/key\n[cid]: https://docs.ipfs.io/concepts/content-addressing\n[AbortSignal]: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal\n"
  },
  {
    "path": "docs/core-api/MISCELLANEOUS.md",
    "content": "# Miscellaneous API <!-- omit in toc -->\n\n- [`ipfs.id([options])`](#ipfsidoptions)\n  - [Parameters](#parameters)\n  - [Options](#options)\n  - [Returns](#returns)\n  - [Example](#example)\n- [`ipfs.version([options])`](#ipfsversionoptions)\n  - [Parameters](#parameters-1)\n  - [Options](#options-1)\n  - [Returns](#returns-1)\n  - [Example](#example-1)\n- [`ipfs.dns(domain, [options])`](#ipfsdnsdomain-options)\n  - [Parameters](#parameters-2)\n  - [Options](#options-2)\n  - [Returns](#returns-2)\n  - [Example](#example-2)\n- [`ipfs.stop([options])`](#ipfsstopoptions)\n  - [Parameters](#parameters-3)\n  - [Options](#options-3)\n  - [Returns](#returns-3)\n  - [Example](#example-3)\n- [`ipfs.ping(peerId, [options])`](#ipfspingpeerid-options)\n  - [Parameters](#parameters-4)\n  - [Options](#options-4)\n  - [Returns](#returns-4)\n  - [Example](#example-4)\n- [`ipfs.resolve(name, [options])`](#ipfsresolvename-options)\n  - [Parameters](#parameters-5)\n  - [Options](#options-5)\n  - [Returns](#returns-5)\n  - [Example](#example-5)\n\n## `ipfs.id([options])`\n\n> Returns the identity of the Peer\n\n### Parameters\n\nNone\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n| peerId | `string` | `undefined` | Look up the identity for this peer instead of the current node |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<Object>` | An object with the Peer identity |\n\nThe Peer identity has the following properties:\n\n- `id: String` - the Peer ID\n- `publicKey: String` - the public key of the peer as a base64 encoded string\n- `addresses: Multiaddr[]` - A list of multiaddrs this node is listening on\n- `agentVersion: String` - The agent version\n- `protocolVersion: String` - The supported protocol version\n- `protocols: String[]` - The supported protocols\n\n### Example\n\n```JavaScript\nconst identity = await ipfs.id()\nconsole.log(identity)\n```\n\nA great source of [examples](https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/src/miscellaneous/id.js) can be found in the tests for this API.\n\n## `ipfs.version([options])`\n\n> Returns the implementation version\n\n### Parameters\n\nNone\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<Object>` | An object with the version of the implementation, the commit and the Repo. `js-ipfs` instances will also return the version of `interface-ipfs-core` and `ipfs-http-client` supported by this node |\n\n### Example\n\n```JavaScript\nconst version = await ipfs.version()\nconsole.log(version)\n```\n\nA great source of [examples](https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/src/miscellaneous/version.js) can be found in the tests for this API.\n\n## `ipfs.dns(domain, [options])`\n\n> Resolve DNS links\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| domain | String | The domain to resolve |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| recursive | `boolean` | `true` | Resolve until result is not a domain name |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<String>` | A string representing the IPFS path for that domain |\n\n### Example\n\n```JavaScript\nconst path = await ipfs.dns('ipfs.io')\nconsole.log(path)\n```\n\nA great source of [examples](https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/src/miscellaneous/dns.js) can be found in the tests for this API.\n\n## `ipfs.stop([options])`\n\n> Stops the IPFS node and in case of talking with an IPFS Daemon, it stops the process.\n\n### Parameters\n\nNone\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<void>` | If action is successfully completed. Otherwise an error will be thrown |\n\n### Example\n\n```JavaScript\nawait ipfs.stop()\n```\n\nA great source of [examples](https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/src/miscellaneous/stop.js) can be found in the tests for this API.\n\n## `ipfs.ping(peerId, [options])`\n\n> Send echo request packets to IPFS hosts\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| peerId | [PeerID][] or [CID][] | The remote peer to send packets to |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| count | `Number` | `10` | The number of ping messages to send |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n\nWhere:\n\n- `peerId` (string) ID of the peer to be pinged.\n- `options` is an optional object argument that might include the following properties:\n    - `count` (integer, default 10): the number of ping messages to send\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `AsyncIterable<Object>` | An async iterable that yields ping response objects |\n\nEach yielded object is of the form:\n\n```js\n{\n  success: true,\n  time: 1234,\n  text: ''\n}\n```\n\nNote that not all ping response objects are \"pongs\". A \"pong\" message can be identified by a truthy `success` property and an empty `text` property. Other ping responses are failures or status updates.\n\n### Example\n\n```JavaScript\nfor await (const res of ipfs.ping('Qmhash')) {\n  if (res.time) {\n    console.log(`Pong received: time=${res.time} ms`)\n  } else {\n    console.log(res.text)\n  }\n}\n```\n\nA great source of [examples](https://github.com/ipfs/js-ipfs/tree/master/packages/interface-ipfs-core/src/ping) can be found in the tests for this API.\n\n## `ipfs.resolve(name, [options])`\n\n> Resolve the value of names to IPFS\n\nThere are a number of mutable name protocols that can link among themselves and into IPNS. For example IPNS references can (currently) point at an IPFS object, and DNS links can point at other DNS links, IPNS entries, or IPFS objects. This command accepts any of these identifiers and resolves them to the referenced item.\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| name | String | The name to resolve |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| recursive | `boolean` | `true` | Resolve until result is an IPFS name |\n| cidBase | `String` | `base58btc` | Multibase codec name the CID in the resolved path will be encoded with |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<String>` | A string representing the resolved name |\n\n### Example\n\nResolve the value of your identity:\n\n```JavaScript\nconst name = '/ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy'\n\nconst res = await ipfs.resolve(name)\nconsole.log(res) // /ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj\n```\n\nResolve the value of another name recursively:\n\n```JavaScript\nconst name = '/ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n'\n\n// Where:\n// /ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n\n// ...resolves to:\n// /ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy\n// ...which in turn resolves to:\n// /ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj\n\nconst res = await ipfs.resolve(name, { recursive: true })\nconsole.log(res) // /ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj\n```\n\nResolve the value of an IPFS path:\n\n```JavaScript\nconst name = '/ipfs/QmeZy1fGbwgVSrqbfh9fKQrAWgeyRnj7h8fsHS1oy3k99x/beep/boop'\n\nconst res = await ipfs.resolve(name)\nconsole.log(res) // /ipfs/QmYRMjyvAiHKN9UTi8Bzt1HUspmSRD8T8DwxfSMzLgBon1\n```\n\nA great source of [examples](https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/src/miscellaneous/resolve.js) can be found in the tests for this API.\n\n[examples]: https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/src/miscellaneous\n[rs]: https://www.npmjs.com/package/readable-stream\n[ps]: https://www.npmjs.com/package/pull-stream\n[cid]: https://docs.ipfs.io/concepts/content-addressing\n[AbortSignal]: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal\n"
  },
  {
    "path": "docs/core-api/NAME.md",
    "content": "# Name API <!-- omit in toc -->\n\n- [`ipfs.name.publish(value, [options])`](#ipfsnamepublishvalue-options)\n  - [Parameters](#parameters)\n  - [Options](#options)\n  - [Returns](#returns)\n  - [Example](#example)\n  - [Notes](#notes)\n- [`ipfs.name.pubsub.cancel(name, [options])`](#ipfsnamepubsubcancelname-options)\n  - [Parameters](#parameters-1)\n  - [Options](#options-1)\n  - [Returns](#returns-1)\n  - [Example](#example-1)\n- [`ipfs.name.pubsub.state([options])`](#ipfsnamepubsubstateoptions)\n  - [Parameters](#parameters-2)\n  - [Options](#options-2)\n  - [Returns](#returns-2)\n  - [Example](#example-2)\n- [`ipfs.name.pubsub.subs([options])`](#ipfsnamepubsubsubsoptions)\n  - [Parameters](#parameters-3)\n  - [Options](#options-3)\n  - [Returns](#returns-3)\n  - [Example](#example-3)\n- [`ipfs.name.resolve(value, [options])`](#ipfsnameresolvevalue-options)\n  - [Parameters](#parameters-4)\n  - [Options](#options-4)\n  - [Returns](#returns-4)\n  - [Example](#example-4)\n\n## `ipfs.name.publish(value, [options])`\n\n> Publish an IPNS name with a given value.\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| value | [CID][] | The content to publish |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| resolve | `boolean` | `true` | Resolve given path before publishing |\n| lifetime | `String` | `24h` | Time duration of the record |\n| ttl | `String` | `undefined` | Time duration this record should be cached |\n| key | `String` | `'self'` | Name of the key to be used |\n| allowOffline | `boolean` | `true` | When offline, save the IPNS record to the the local datastore without broadcasting to the network instead of simply failing. |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<Object>` | An object that contains the IPNS hash and the IPFS hash |\n\nexample of the returned object:\n\n```JavaScript\n{\n  name: \"/ipns/QmHash..\"\n  value: \"/ipfs/QmHash..\"\n}\n```\n\n### Example\n\nImagine you want to publish your website under IPFS. You can use the [Files API](./FILES.md) to publish your static website and then you'll get a multihash you can link to. But when you need to make a change, a problem arises: you get a new multihash because you now have a different content. And it is not possible for you to be always giving others the new address.\n\nHere's where the Name API comes in handy. With it, you can use one static multihash for your website under IPNS (InterPlanetary Name Service). This way, you can have one single multihash poiting to the newest version of your website.\n\n```JavaScript\n// The address of your files.\nconst addr = '/ipfs/QmbezGequPwcsWo8UL4wDF6a8hYwM1hmbzYv2mnKkEWaUp'\n\nconst res = await ipfs.name.publish(addr)\n// You now have a res which contains two fields:\n//   - name: the name under which the content was published.\n//   - value: the \"real\" address to which Name points.\nconsole.log(`https://gateway.ipfs.io/ipns/${res.name}`)\n```\n\nThis way, you can republish a new version of your website under the same address. By default, `ipfs.name.publish` will use the Peer ID. If you want to have multiple websites (for example) under the same IPFS module, you can always check the [key API](./KEY.md).\n\nA great source of [examples][] can be found in the tests for this API.\n\n### Notes\n\nThe `allowOffline` option is not yet implemented in js-ipfs. See tracking issue [ipfs/js-ipfs#1997](https://github.com/ipfs/js-ipfs/issues/1997).\n\n## `ipfs.name.pubsub.cancel(name, [options])`\n\n> Cancel a name subscription\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| name | `String` | The name of the subscription to cancel |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n`arg` is the name of the subscription to cancel.\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<Object>` | An object that contains the result of the operation |\n\nexample of the returned object:\n\n```JavaScript\n{\n  canceled: true\n}\n```\n\n### Example\n\n```JavaScript\nconst name = 'QmQrX8hka2BtNHa8N8arAq16TCVx5qHcb46c5yPewRycLm'\n\nconst result = await ipfs.name.pubsub.cancel(name)\nconsole.log(result.canceled)\n// true\n```\n\nA great source of [examples][examples-pubsub] can be found in the tests for this API.\n\n## `ipfs.name.pubsub.state([options])`\n\n> Query the state of IPNS pubsub\n\n### Parameters\n\nNone\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<Object>` | An object that contains the result of the operation |\n\nexample of the returned object:\n\n```JavaScript\n{\n  enabled: true\n}\n```\n\n### Example\n\n```JavaScript\nconst result = await ipfs.name.pubsub.state()\nconsole.log(result.enabled)\n// true\n```\n\nA great source of [examples][examples-pubsub] can be found in the tests for this API.\n\n## `ipfs.name.pubsub.subs([options])`\n\n> Show current name subscriptions\n\n### Parameters\n\nNone\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<Array>` | An array of subscriptions |\n\nexample of the returned array:\n\n```JavaScript\n['/ipns/QmQrX8hka2BtNHa8N8arAq16TCVx5qHcb46c5yPewRycLm']\n```\n\n### Example\n\n```JavaScript\nconst result = await ipfs.name.pubsub.subs()\nconsole.log(result)\n// ['/ipns/QmQrX8hka2BtNHa8N8arAq16TCVx5qHcb46c5yPewRycLm']\n```\n\nA great source of [examples][examples-pubsub] can be found in the tests for this API.\n\n## `ipfs.name.resolve(value, [options])`\n\n> Resolve an IPNS name.\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| value | `PeerId` or `string` | An IPNS address such as `/ipns/ipfs.io` |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| recursive | `boolean` | `false` | Resolve until the result is not an IPNS name |\n| nocache | `boolean` | `cache` | Do not use cached entries |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `AsyncIterable<String>` | An async iterable that yields strings that are increasingly more accurate resolved paths. |\n\n### Example\n\n```JavaScript\n// The IPNS address you want to resolve.\nconst addr = '/ipns/ipfs.io'\n\nfor await (const name of ipfs.name.resolve(addr)) {\n  console.log(name)\n  // /ipfs/QmQrX8hka2BtNHa8N8arAq16TCVx5qHcb46c5yPewRycLm\n}\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n[examples]: https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/src/name\n[examples-pubsub]: https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/src/name-pubsub\n[cid]: https://docs.ipfs.io/concepts/content-addressing\n[AbortSignal]: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal\n"
  },
  {
    "path": "docs/core-api/OBJECT.md",
    "content": "# Object API <!-- omit in toc -->\n\n> ⚠️ Object API is [deprecated](https://github.com/ipfs/go-ipfs/issues/7936), use [FILES](FILES.md) and [DAG](DAG.md) APIs instead.\n\n- [`ipfs.object.new([options])`](#ipfsobjectnewoptions)\n  - [Parameters](#parameters)\n  - [Options](#options)\n  - [Returns](#returns)\n  - [Example](#example)\n- [`ipfs.object.put(obj, [options])`](#ipfsobjectputobj-options)\n  - [Parameters](#parameters-1)\n  - [Options](#options-1)\n  - [Returns](#returns-1)\n  - [Example](#example-1)\n- [`ipfs.object.get(cid, [options])`](#ipfsobjectgetcid-options)\n  - [Parameters](#parameters-2)\n  - [Options](#options-2)\n  - [Returns](#returns-2)\n  - [Example](#example-2)\n- [`ipfs.object.data(cid, [options])`](#ipfsobjectdatacid-options)\n  - [Parameters](#parameters-3)\n  - [Options](#options-3)\n  - [Returns](#returns-3)\n  - [Example](#example-3)\n- [`ipfs.object.links(cid, [options])`](#ipfsobjectlinkscid-options)\n  - [Parameters](#parameters-4)\n  - [Options](#options-4)\n  - [Returns](#returns-4)\n  - [Example](#example-4)\n- [`ipfs.object.stat(cid, [options])`](#ipfsobjectstatcid-options)\n  - [Parameters](#parameters-5)\n  - [Options](#options-5)\n  - [Returns](#returns-5)\n  - [Example](#example-5)\n- [`ipfs.object.patch.addLink(cid, link, [options])`](#ipfsobjectpatchaddlinkcid-link-options)\n  - [Parameters](#parameters-6)\n  - [Options](#options-6)\n  - [Returns](#returns-6)\n  - [Example](#example-6)\n  - [Notes](#notes)\n- [`ipfs.object.patch.rmLink(cid, link, [options])`](#ipfsobjectpatchrmlinkcid-link-options)\n  - [Parameters](#parameters-7)\n  - [Options](#options-7)\n  - [Returns](#returns-7)\n  - [Example](#example-7)\n  - [Notes](#notes-1)\n- [`ipfs.object.patch.appendData(cid, data, [options])`](#ipfsobjectpatchappenddatacid-data-options)\n  - [Parameters](#parameters-8)\n  - [Options](#options-8)\n  - [Returns](#returns-8)\n  - [Example](#example-8)\n- [`ipfs.object.patch.setData(multihash, data, [options])`](#ipfsobjectpatchsetdatamultihash-data-options)\n  - [Parameters](#parameters-9)\n  - [Options](#options-9)\n  - [Returns](#returns-9)\n  - [Example](#example-9)\n\n## `ipfs.object.new([options])`\n\n> Create a new MerkleDAG node, using a specific layout. Caveat: So far, only UnixFS object layouts are supported.\n\n### Parameters\n\nNone.\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| template | `String` | If defined, must be a string `unixfs-dir` and if that is passed, the created node will be an empty unixfs style directory |\n| recursive | `boolean` | `false` | Resolve until the result is not an IPNS name |\n| nocache | `boolean` | `cache` | Do not use cached entries |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<CID>` | A [CID](https://github.com/ipfs/js-cid) instance |\n\n### Example\n\n```JavaScript\nconst cid = await ipfs.object.new({\n  template: 'unixfs-dir'\n})\nconsole.log(cid.toString())\n// Logs:\n// QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.object.put(obj, [options])`\n\n> Store a MerkleDAG node.\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| obj | `Object{ Data: <data>, Links: [] }`, `Uint8Array` or [DAGNode][] | The MerkleDAG Node to be stored |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| enc | `String` | `undefined` | The encoding of the Uint8Array (json, yml, etc), if passed a Uint8Array |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<CID>` | A [CID](https://github.com/ipfs/js-cid) instance |\n\n### Example\n\n```JavaScript\nconst obj = {\n  Data: new TextEncoder().encode('Some data'),\n  Links: []\n}\n\nconst cid = await ipfs.object.put(obj)\nconsole.log(cid.toString())\n// Logs:\n// QmPb5f92FxKPYdT3QNBd1GKiL4tZUXUrzF4Hkpdr3Gf1gK\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.object.get(cid, [options])`\n\n> Fetch a MerkleDAG node\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| cid | [CID][] | The returned [DAGNode][] will correspond to this CID |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<DAGNode>` | A MerkleDAG node of the type [DAGNode][] |\n\n### Example\n\n```JavaScript\nconst multihash = 'QmPb5f92FxKPYdT3QNBd1GKiL4tZUXUrzF4Hkpdr3Gf1gK'\n\nconst node = await ipfs.object.get(multihash)\nconsole.log(node.Data)\n// Logs:\n// some data\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.object.data(cid, [options])`\n\n> Returns the Data field of an object\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| cid | [CID][] | The returned data will be from the [DAGNode][] that corresponds to this CID |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<Uint8Array>` | An Promise that resolves to Uint8Array objects with the data that the MerkleDAG node contained |\n\n### Example\n\n```JavaScript\nconst cid = 'QmPb5f92FxKPYdT3QNBd1GKiL4tZUXUrzF4Hkpdr3Gf1gK'\n\nconst data = await ipfs.object.data(cid)\nconsole.log(data.toString())\n// Logs:\n// some data\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.object.links(cid, [options])`\n\n> Returns the Links field of an object\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| cid | [CID][] | The returned [DAGLink][]s will be from the [DAGNode][] that corresponds to this CID |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<Array>` | An Array of [DAGLink](https://github.com/ipld/js-ipld-dag-pb/blob/master/src/dag-link/dagLink.js) objects |\n\n### Example\n\n```JavaScript\nconst multihash = 'Qmc5XkteJdb337s7VwFBAGtiaoj2QCEzyxtNRy3iMudc3E'\n\nconst links = await ipfs.object.links(multihash)\nconst hashes = links.map((link) => link.Hash.toString())\nconsole.log(hashes)\n// Logs:\n// [\n//   'QmZbj5ruYneZb8FuR9wnLqJCpCXMQudhSdWhdhp5U1oPWJ',\n//   'QmSo73bmN47gBxMNqbdV6rZ4KJiqaArqJ1nu5TvFhqqj1R'\n// ]\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.object.stat(cid, [options])`\n\n> Returns stats about an Object\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| cid | [CID][] | The returned stats will be from the [DAGNode][] that corresponds to this CID |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<Object>` | An object representing the stats of the Object |\n\nthe returned object has the following format:\n\n```JavaScript\n{\n  Hash: 'QmPTkMuuL6PD8L2SwTwbcs1NPg14U8mRzerB1ZrrBrkSDD',\n  NumLinks: 0,\n  BlockSize: 10,\n  LinksSize: 2,\n  DataSize: 8,\n  CumulativeSize: 10\n}\n```\n\n### Example\n\n```JavaScript\nconst multihash = 'QmPTkMuuL6PD8L2SwTwbcs1NPg14U8mRzerB1ZrrBrkSDD'\n\nconst stats = await ipfs.object.stat(multihash, {timeout: '10s'})\nconsole.log(stats)\n// Logs:\n// {\n//   Hash: 'QmPTkMuuL6PD8L2SwTwbcs1NPg14U8mRzerB1ZrrBrkSDD',\n//   NumLinks: 0,\n//   BlockSize: 10,\n//   LinksSize: 2,\n//   DataSize: 8,\n//   CumulativeSize: 10\n// }\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.object.patch.addLink(cid, link, [options])`\n\n> Add a Link to an existing MerkleDAG Object\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| cid | [CID][] | Add a link to the [DAGNode][] that corresponds to this CID |\n| link | [DAGLink][] | The link to add |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<CID>` | An instance of [CID][] representing the new DAG node that was created due to the operation |\n\n### Example\n\n```JavaScript\n// cid is CID of the DAG node created by adding a link\nconst cid = await ipfs.object.patch.addLink(node, {\n  name: 'some-link',\n  size: 10,\n  cid: CID.parse('QmPTkMuuL6PD8L2SwTwbcs1NPg14U8mRzerB1ZrrBrkSDD')\n})\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n### Notes\n\nThe `DAGLink` to be added can also be passed as an object containing: `name`, `cid` and `size` properties:\n\n```js\nconst link = {\n  name: 'Qmef7ScwzJUCg1zUSrCmPAz45m8uP5jU7SLgt2EffjBmbL',\n  size: 37,\n  cid: CID.parse('Qmef7ScwzJUCg1zUSrCmPAz45m8uP5jU7SLgt2EffjBmbL')\n};\n```\n\nor\n\n```js\nconst link = new DAGLink(name, size, multihash)\n```\n\n## `ipfs.object.patch.rmLink(cid, link, [options])`\n\n> Remove a Link from an existing MerkleDAG Object\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| cid | [CID][] | Remove a link to the [DAGNode][] that corresponds to this CID |\n| link | [DAGLink][] | The [DAGLink][] to remove |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<CID>` | An instance of [CID][] representing the new DAG node that was created due to the operation |\n\n### Example\n\n```JavaScript\n// cid is CID of the DAG node created by removing a link\nconst cid = await ipfs.object.patch.rmLink(node, {\n  Name: 'some-link',\n  Tsize: 10,\n  Hash: CID.parse('QmPTkMuuL6PD8L2SwTwbcs1NPg14U8mRzerB1ZrrBrkSDD')\n})\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n### Notes\n\n`link` is the link to be removed on the node that is identified by the `multihash`, can be passed as:\n\n- `DAGLink`\n  ```js\n  const link = new DAGLink(name, size, multihash)\n  ```\n\n- Object containing a `name` property\n    ```js\n    const link = {\n      name: 'Qmef7ScwzJUCg1zUSrCmPAz45m8uP5jU7SLgt2EffjBmbL'\n    };\n    ```\n\n## `ipfs.object.patch.appendData(cid, data, [options])`\n\n> Append Data to the Data field of an existing node\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| cid | [CID][] | Add data to the [DAGNode][] that corresponds to this CID |\n| data | `Uint8Array` | The data to append to the `.Data` field of the node |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<CID>` | An instance of [CID][] representing the new DAG node that was created due to the operation |\n\n### Example\n\n```JavaScript\nconst cid = await ipfs.object.patch.appendData(multihash, new TextEncoder().encode('more data'))\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.object.patch.setData(multihash, data, [options])`\n\n> Overwrite the Data field of a DAGNode with new Data\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| cid | [CID][] | Replace data of the [DAGNode][] that corresponds to this CID |\n| data | `Uint8Array` | The data to overwrite with |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<CID>` | An instance of [CID][] representing the new DAG node that was created due to the operation |\n\n### Example\n\n```JavaScript\nconst cid = '/ipfs/Qmfoo'\nconst updatedCid = await ipfs.object.patch.setData(cid, new TextEncoder().encode('more data'))\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n[CID]: https://github.com/multiformats/js-cid\n[DAGNode]: https://github.com/ipld/js-ipld-dag-pb\n[DAGLink]: https://github.com/ipld/js-ipld-dag-pb\n[multihash]: http://github.com/multiformats/multihash\n[examples]: https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/src/object\n[AbortSignal]: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal\n"
  },
  {
    "path": "docs/core-api/PIN.md",
    "content": "# Pin API <!-- omit in toc -->\n\n- [`ipfs.pin.add(ipfsPath, [options])`](#ipfspinaddipfspath-options)\n  - [Parameters](#parameters)\n  - [Options](#options)\n  - [Returns](#returns)\n  - [Example](#example)\n- [`ipfs.pin.addAll(source, [options])`](#ipfspinaddallsource-options)\n  - [Parameters](#parameters-1)\n  - [Options](#options-1)\n  - [Returns](#returns-1)\n  - [Example](#example-1)\n- [`ipfs.pin.ls([options])`](#ipfspinlsoptions)\n  - [Parameters](#parameters-2)\n  - [Options](#options-2)\n  - [Returns](#returns-2)\n  - [Example](#example-2)\n- [`ipfs.pin.rm(ipfsPath, [options])`](#ipfspinrmipfspath-options)\n  - [Parameters](#parameters-3)\n  - [Options](#options-3)\n  - [Returns](#returns-3)\n  - [Example](#example-3)\n- [`ipfs.pin.rmAll(source, [options])`](#ipfspinrmallsource-options)\n  - [Parameters](#parameters-4)\n  - [Options](#options-4)\n  - [Returns](#returns-4)\n  - [Example](#example-4)\n- [`ipfs.pin.remote.service.add(name, options)`](#ipfspinremoteserviceaddname-options)\n  - [Parameters](#parameters-5)\n  - [Options](#options-5)\n  - [Returns](#returns-5)\n  - [Example](#example-5)\n- [`ipfs.pin.remote.service.ls([options])`](#ipfspinremoteservicels_options)\n  - [Options](#options-6)\n  - [Returns](#returns-6)\n  - [Example](#example-6)\n- [`ipfs.pin.remote.service.rm(name, [options])`](#ipfspinremoteservicermname-options)\n  - [Parameters](#parameters-6)\n  - [Options](#options-7)\n  - [Returns](#returns-7)\n  - [Example](#example-7)\n- [`ipfs.pin.remote.add(cid, [options])`](#ipfspinremoteaddcid-options)\n  - [Parameters](#parameters-7)\n  - [Options](#options-8)\n  - [Returns](#returns-8)\n  - [Example](#example-8)\n- [`ipfs.pin.remote.ls(options)`](#ipfspinremotelsoptions)\n  - [Options](#options-9)\n  - [Returns](#returns-9)\n  - [Example](#example-9)\n- [`ipfs.pin.remote.rm(options)`](#ipfspinremotermoptions)\n  - [Options](#options-10)\n  - [Returns](#returns-10)\n  - [Example](#example-10)\n- [`ipfs.pin.remote.rmAll(options)`](#ipfspinremotermalloptions)\n  - [Options](#options-11)\n  - [Returns](#returns-11)\n  - [Example](#example-11)\n\n## `ipfs.pin.add(ipfsPath, [options])`\n\n> Adds an IPFS object to the pinset and also stores it to the IPFS repo. pinset is the set of hashes currently pinned (not gc'able)\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| source | [CID][] or `string` | A CID or IPFS Path to pin in your repo |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| recursive | `boolean` | `true` | Recursively pin all links contained by the object |\n| timeout | `number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| [CID][] | The CID that was pinned |\n\n### Example\n\n```JavaScript\nconst cid of ipfs.pin.add(CID.parse('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u'))\nconsole.log(cid)\n// Logs:\n// CID('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u')\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.pin.addAll(source, [options])`\n\n> Adds multiple IPFS objects to the pinset and also stores it to the IPFS repo. pinset is the set of hashes currently pinned (not gc'able)\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| source | `AsyncIterable<{ cid: CID, path: string, recursive: boolean, comments: string }>` | One or more CIDs or IPFS Paths to pin in your repo |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `AsyncIterable<CID>` | An async iterable that yields the CIDs that were pinned |\n\nEach yielded object has the form:\n\n```JavaScript\n{\n  cid: CID('QmHash')\n}\n```\n\n### Example\n\n```JavaScript\nfor await (const cid of ipfs.pin.addAll(CID.parse('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u'))) {\n  console.log(cid)\n}\n// Logs:\n// CID('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u')\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.pin.ls([options])`\n\n> List all the objects pinned to local storage\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| paths | [CID][] or `Array<CID>` or `string` or `Array<string>` | CIDs or IPFS paths to search for in the pinset |\n| type | `string` | `undefined` | Filter by this type of pin (\"recursive\", \"direct\" or \"indirect\") |\n| timeout | `number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `AsyncIterable<{ cid: CID, type: string }>` | An async iterable that yields currently pinned objects with `cid` and `type` properties. `cid` is a [CID][cid] of the pinned node, `type` is the pin type (\"recursive\", \"direct\" or \"indirect\") |\n\n### Example\n\n```JavaScript\nfor await (const { cid, type } of ipfs.pin.ls()) {\n  console.log({ cid, type })\n}\n// { cid: CID(Qmc5XkteJdb337s7VwFBAGtiaoj2QCEzyxtNRy3iMudc3E), type: 'recursive' }\n// { cid: CID(QmZbj5ruYneZb8FuR9wnLqJCpCXMQudhSdWhdhp5U1oPWJ), type: 'indirect' }\n// { cid: CID(QmSo73bmN47gBxMNqbdV6rZ4KJiqaArqJ1nu5TvFhqqj1R), type: 'indirect' }\n```\n\n```JavaScript\nfor await (const { cid, type } of ipfs.pin.ls({\n  paths: [ CID.parse('Qmc5..'), CID.parse('QmZb..'), CID.parse('QmSo..') ]\n})) {\n  console.log({ cid, type })\n}\n// { cid: CID(Qmc5XkteJdb337s7VwFBAGtiaoj2QCEzyxtNRy3iMudc3E), type: 'recursive' }\n// { cid: CID(QmZbj5ruYneZb8FuR9wnLqJCpCXMQudhSdWhdhp5U1oPWJ), type: 'indirect' }\n// { cid: CID(QmSo73bmN47gBxMNqbdV6rZ4KJiqaArqJ1nu5TvFhqqj1R), type: 'indirect' }\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.pin.rm(ipfsPath, [options])`\n\n> Unpin this block from your repo\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| ipfsPath | [CID][] of string | Unpin this CID or IPFS Path |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| recursive | `boolean` | `true` | Recursively unpin the object linked |\n| timeout | `number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| [CID][] | The CIDs that was unpinned |\n\n### Example\n\n```JavaScript\nconst cid of ipfs.pin.rm(CID.parse('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u'))\nconsole.log(cid)\n// prints the CID that was unpinned\n// CID('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u')\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.pin.rmAll(source, [options])`\n\n> Unpin one or more blocks from your repo\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| source | [CID][], string or `AsyncIterable<{ cid: CID, path: string, recursive: boolean }>` | Unpin this CID |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `AsyncIterable<CID>` | An async iterable that yields the CIDs that were unpinned |\n\n### Example\n\n```JavaScript\nfor await (const cid of ipfs.pin.rmAll(CID.parse('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u'))) {\n  console.log(cid)\n}\n// prints the CIDs that were unpinned\n// CID('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u')\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.pin.remote.service.add(name, options)`\n\n> Registers remote pinning service with a given name. Errors if service with the given name is already registered.\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| name | `string` | Service name |\n\n### Options\n\nAn object which must contain following fields:\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| endpoint | `string` | Service endpoint URL |\n| key | `string` | Service key |\n\n\n\nAn object may have the following optional fields:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| ---- | -------- |\n| Promise<void> | Resolves if added successfully, or fails with error e.g. if service with such name is already registered |\n\n\n### Example\n\n```JavaScript\nawait ipfs.pin.remote.service.add('pinata', {\n  endpoint: new URL('https://api.pinata.cloud'),\n  key: 'your-pinata-key'\n})\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n\n## `ipfs.pin.remote.service.ls([options])`\n\n> List registered remote pinning services.\n\n### Options\n\nAn object may have the following optional fields:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| stat | `boolean` | `false` | If `true` will  include service stats. |\n| timeout | `number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| ---- | -------- |\n| Promise<[RemotePinService][][]> | List of registered services |\n\n#### `RemotePinService`\n\nObject contains following fields:\n\n| Name | Type | Description |\n| ---- | ---- | -------- |\n| service | `string` | Service name |\n| endpoint | `URL` | Service endpoint URL |\n| stat | [Stat][] | Is included only when `stat: true` option was passed |\n\n#### `Stat`\n\nIf stats could not be fetched from service (e.g. endpoint was unreachable) object has following form:\n\n| Name | Type | Description |\n| ---- | ---- | -------- |\n| status | `'invalid'` | Service status |\n\n\nIf stats were fetched from service successfully object has following form:\n\n| Name | Type | Description |\n| ---- | ---- | -------- |\n| status | `'valid'` | Service status |\n| pinCount | [PinCount][] | Pin counts |\n\n#### `PinCount`\n\nObject has following fields:\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| queued | `number` | Number of queued pins |\n| pinning | `number` | Number of pins that are pinning |\n| pinned | `number` | Number of pinned pins |\n| failed | `number` | Number of faield pins |\n\n\n\n### Example\n\n```JavaScript\nawait ipfs.pin.remote.service.ls()\n// [{\n//   service: 'pinata'\n//   endpoint: new URL('https://api.pinata.cloud'),\n// }]\n\nawait ipfs.pin.remote.service.ls({ stat: true })\n// [{\n//   service: 'pinata'\n//   endpoint: new URL('https://api.pinata.cloud'),\n//   stat: {\n//      status: 'valid',\n//      pinCount: {\n//        queued: 0,\n//        pinning: 0,\n//        pinned: 1,\n//        failed: 0,\n//      }\n//   }\n// }]\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n\n## `ipfs.pin.remote.service.rm(name, [options])`\n\n> Unregisteres remote pinning service with a given name (if service with such name is regisetered).\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| name | `string` | Service name |\n\n### Options\n\nAn object may have the following optional fields:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| ---- | -------- |\n| Promise<void> | Resolves on completion |\n\n\n### Example\n\n```JavaScript\nawait ipfs.pin.remote.service.rm('pinata')\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n\n## `ipfs.pin.remote.add(cid, [options])`\n\n> Pin a content with a given CID to a remote pinning service\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| cid | [CID][] | A CID to pin on a remote pinning service |\n\n### Options\n\nAn object which must contain following fields:\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| service | `string` | Name of the remote pinning service to use |\n\n\nAn object may have the following optional fields:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| name | `string` | `undefined` | Name for pinned data; can be used for lookups later (max 255 characters) |\n| origins | `Multiaddr[]` | `undefined` | List of multiaddrs known to provide the data (max 20) |\n| background | `boolean` | `false` | If true, will add to the queue on the remote service and return immediately. If false or omitted will wait until pinned on the remote service |\n| timeout | `number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| ---- | -------- |\n| [Pin][] | Pin Object |\n\n#### `Pin`\n\nObject has following fields:\n\n| Type | Description |\n| ---- | ----------- |\n| [Status][] | Pin status |\n| [CID][] | CID of the content |\n| `string | undefined` | name that was given to the pin, or `undefined` if no name was not given |\n\n#### `Status`\n\nStatus is one of the following string values:\n\n`'queued'`, `'pinning'`, `'pinned'`, `'failed'`\n\n### Example\n\n```JavaScript\nconst cid = CID.parse('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u')\nconst pin = await ipfs.pin.remote.add(cid, {\n  service: 'pinata',\n  name: 'block-party'\n})\nconsole.log(pin)\n// Logs:\n// {\n//    status: 'pinned',\n//    cid: CID('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u'),\n//    name: 'block-party'\n// }\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.pin.remote.ls(options)`\n\n> Returns a list of matching pins on the remote pinning service.\n\n\n### Options\n\nAn object which must contain following fields:\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| service | `string` | Name of the remote pinning service to use |\n\nAn object may have the following optional fields:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| cid | [CID][][] | `undefined` | If provided, will only include pin objects that have a CID from the given set. |\n| name | `string` | `undefined` | If passed, will only include pin objects with names that have this name (case-sensitive, exact match). |\n| status | [Status][][] | ['pinned'] | Return pin objects for pins that have one of the specified status values |\n| timeout | `number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| ---- | -------- |\n| AysncIterable<[Pin][]> | Pin Objects |\n\n### Example\n\n```JavaScript\nfor await (const pin of ipfs.pin.remote.ls({ service: 'pinata' })) {\n  console.log(pin)\n}\n// Logs:\n// {\n//    status: 'pinned',\n//    cid: CID('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u'),\n//    name: 'block-party'\n// }\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.pin.remote.rm(options)`\n\n> Removes a single matching pin object from the remote pinning service. Will error when multiple pins mtach, to remove all matches `rmAll` should be used instead.\n\n### Options\n\nAn object which must contain following fields:\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| service | `string` | Name of the remote pinning service to use |\n\nAn object may also contain following optional fields:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| cid | [CID][][] | `undefined` | If provided, will match pin object(s) that have a CID from the given set. |\n| name | `string` | `undefined` | If provided, will match pin object(s) with exact (case-sensitive) name. |\n| status | [Status][][] | ['pinned'] | If provided, will match pin object(s) that have a status from the given set. |\n| timeout | `number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| ---- | -------- |\n| Promise<void> | Succeeds on completion |\n\n### Example\n\n```JavaScript\nawait ipfs.pin.remote.rm({\n  service: 'pinata',\n  name: 'block-party'\n})\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.pin.remote.rmAll(options)`\n\n> Removes all the matching pin objects from the remote pinning\nservice.\n\n### Options\n\nAn object which must contain following fields:\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| service | `string` | Name of the remote pinning service to use |\n\nAn object may also contain following optional fields:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| cid | [CID][][] | `undefined` | If provided, will match pin object(s) that have a CID from the given set. |\n| name | `string` | `undefined` | If provided, will match pin object(s) with exact (case-sensitive) name. |\n| status | [Status][][] | ['pinned'] | If provided, will match pin object(s) that have a status from the given set. |\n| timeout | `number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| ---- | -------- |\n| Promise<void> | Succeeds on completion |\n\n### Example\n\n```JavaScript\n// Delete all non 'pinned' pins\nawait ipfs.pin.remote.rmAll({\n  service: 'pinata',\n  status: ['queued', 'pinning', 'failed']\n})\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n[Pin]: #pin\n[Status]: #status\n[RemotePinService]: #remotepinservice\n[Status]: #status\n[Stat]: #stat\n[PinCount]: #pincount\n[examples]: https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/src/pin\n[cid]: https://docs.ipfs.io/concepts/content-addressing\n[AbortSignal]: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal\n"
  },
  {
    "path": "docs/core-api/PUBSUB.md",
    "content": "# PubSub API <!-- omit in toc -->\n\n- [`ipfs.pubsub.subscribe(topic, handler, [options])`](#ipfspubsubsubscribetopic-handler-options)\n  - [Parameters](#parameters)\n  - [Options](#options)\n  - [Returns](#returns)\n  - [Example](#example)\n- [`ipfs.pubsub.unsubscribe(topic, handler, [options])`](#ipfspubsubunsubscribetopic-handler-options)\n  - [Parameters](#parameters-1)\n  - [Options](#options-1)\n  - [Returns](#returns-1)\n  - [Example](#example-1)\n  - [Notes](#notes)\n- [`ipfs.pubsub.publish(topic, data, [options])`](#ipfspubsubpublishtopic-data-options)\n  - [Returns](#returns-2)\n  - [Example](#example-2)\n- [`ipfs.pubsub.ls([options])`](#ipfspubsublsoptions)\n  - [Parameters](#parameters-2)\n  - [Options](#options-2)\n  - [Returns](#returns-3)\n  - [Example](#example-3)\n- [`ipfs.pubsub.peers(topic, [options])`](#ipfspubsubpeerstopic-options)\n  - [Returns](#returns-4)\n  - [Example](#example-4)\n\n## `ipfs.pubsub.subscribe(topic, handler, [options])`\n\n> Subscribe to a pubsub topic.\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| topic | `String` | The topic name |\n| handler | `Function<(msg) => {}>` | Event handler which will be called with a message object everytime one is received. The `msg` has the format `{from: PeerId, sequenceNumber: bigint, data: Uint8Array, topicIDs: Array<String>}` |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<void>` | If action is successfully completed. Otherwise an error will be thrown |\n\n### Example\n\n```JavaScript\nconst topic = 'fruit-of-the-day'\nconst receiveMsg = (msg) => console.log(new TextDecoder().decode(msg.data))\n\nawait ipfs.pubsub.subscribe(topic, receiveMsg)\nconsole.log(`subscribed to ${topic}`)\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.pubsub.unsubscribe(topic, handler, [options])`\n\n> Unsubscribes from a pubsub topic.\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| topic | `String` | The topic to unsubscribe from |\n| handler | `Function<(msg) => {}>` | The handler to remove |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<void>` | If action is successfully completed. Otherwise an error will be thrown |\n\n### Example\n\n```JavaScript\nconst topic = 'fruit-of-the-day'\nconst receiveMsg = (msg) => console.log(msg.toString())\n\nawait ipfs.pubsub.subscribe(topic, receiveMsg)\nconsole.log(`subscribed to ${topic}`)\n\nawait ipfs.pubsub.unsubscribe(topic, receiveMsg)\nconsole.log(`unsubscribed from ${topic}`)\n```\n\nOr removing all listeners:\n\n```JavaScript\nconst topic = 'fruit-of-the-day'\nconst receiveMsg = (msg) => console.log(msg.toString())\n\nawait ipfs.pubsub.subscribe(topic, receiveMsg);\n\n// Will unsubscribe ALL handlers for the given topic\nawait ipfs.pubsub.unsubscribe(topic);\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n### Notes\n\nIf the `topic` and `handler` are provided, the `handler` will no longer receive updates for the `topic`. This behaves like [EventEmitter.removeListener](https://nodejs.org/dist/latest/docs/api/events.html#events_emitter_removelistener_eventname_listener). If the `handler` is not equivalent to the `handler` provided on `subscribe`, no action will be taken.\n\nIf **only** the `topic` param is provided, unsubscribe will remove **all** handlers for the `topic`. This behaves like [EventEmitter.removeAllListeners](https://nodejs.org/dist/latest/docs/api/events.html#events_emitter_removealllisteners_eventname). Use this if you would like to no longer receive any updates for the `topic`.\n\n## `ipfs.pubsub.publish(topic, data, [options])`\n\n> Publish a data message to a pubsub topic.\n\n- `topic: String`\n- `data: Uint8Array|String` - The message to send\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<void>` | If action is successfully completed. Otherwise an error will be thrown |\n\n### Example\n\n```JavaScript\nconst topic = 'fruit-of-the-day'\nconst msg = new TextEncoder().encode('banana')\n\nawait ipfs.pubsub.publish(topic, msg)\n\n// msg was broadcasted\nconsole.log(`published to ${topic}`)\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.pubsub.ls([options])`\n\n> Returns the list of subscriptions the peer is subscribed to.\n\n### Parameters\n\nNone\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<string[]>` | An array of topicIDs that the peer is subscribed to |\n\n### Example\n\n```JavaScript\nconst topics = await ipfs.pubsub.ls()\nconsole.log(topics)\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.pubsub.peers(topic, [options])`\n\n> Returns the peers that are subscribed to one topic.\n\n- `topic: String`\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<string[]>` | An array of peer IDs subscribed to the `topic` |\n\n### Example\n\n```JavaScript\nconst topic = 'fruit-of-the-day'\n\nconst peerIds = await ipfs.pubsub.peers(topic)\nconsole.log(peerIds)\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n[examples]: https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/src/pubsub\n[AbortSignal]: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal\n"
  },
  {
    "path": "docs/core-api/README.md",
    "content": "# IPFS Core API\n\nThis directory contains the description of the core JS IPFS API. In order to be considered \"valid\", a JS IPFS core implementation must expose the API described here.\nThis abstraction allows for different implementations including:\n1. Full JavaScript native implementation\n2. Delgate implementation that invokes another IPFS implementation (e.g., Kubo)\n\nYou can use this loose spec as documentation for consuming the core APIs.\n\nIt is broken up into the following sections:\n\n* [BITSWAP.md](BITSWAP.md)\n* [BLOCK.md](BLOCK.md)\n* [BOOTSTRAP.md](BOOTSTRAP.md)\n* [CONFIG.md](CONFIG.md)\n* [DAG.md](DAG.md)\n* [DHT.md](DHT.md)\n* [FILES.md](FILES.md)\n* [KEY.md](KEY.md)\n* [MISCELLANEOUS.md](MISCELLANEOUS.md)\n* [NAME.md](NAME.md)\n* [OBJECT.md](OBJECT.md) ([deprecated](https://github.com/ipfs/go-ipfs/issues/7936), use the [DAG API](DAG.md) instead)\n* [PIN.md](PIN.md)\n* [PUBSUB.md](PUBSUB.md)\n* [REFS.md](REFS.md)\n* [REPO.md](REPO.md)\n* [STATS.md](STATS.md)\n* [SWARM.md](SWARM.md)\n\n## History\nThis API was created based off the [Kubo RPC HTTP API](https://docs.ipfs.io/reference/kubo/rpc/).  There is no guarantee they stay fully in sync.\n"
  },
  {
    "path": "docs/core-api/REFS.md",
    "content": "# Refs API <!-- omit in toc -->\n\n- [`ipfs.refs(ipfsPath, [options])`](#ipfsrefsipfspath-options)\n  - [Parameters](#parameters)\n  - [Options](#options)\n  - [Returns](#returns)\n  - [Example](#example)\n- [`ipfs.refs.local([options])`](#ipfsrefslocaloptions)\n  - [Parameters](#parameters-1)\n  - [Options](#options-1)\n  - [Returns](#returns-1)\n  - [Example](#example-1)\n\n## `ipfs.refs(ipfsPath, [options])`\n\n> Get links (references) from an object.\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| ipfsPath | [CID][] or `String` | The object to search for references |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| recursive | `boolean` | `false` | Recursively list references of child nodes |\n| unique | `boolean` | `false` | Omit duplicate references from output |\n| format | `String` | `'<dst>'` | output edges with given format. Available tokens: `<src>`, `<dst>`, `<linkname>` |\n| edges | `boolean` | `false` | output references in edge format: `\"<src> -> <dst>\"` |\n| maxDepth | `Number` | `1` | only for recursive refs, limits fetch and listing to the given depth |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `AsyncIterable<Object>` | An async iterable that yields objects representing the links (references) |\n\nEach yielded object is of the form:\n\n```js\n{\n  ref: string,\n  err: Error | null\n}\n```\n\n### Example\n\n```JavaScript\nfor await (const ref of ipfs.refs(ipfsPath, { recursive: true })) {\n  if (ref.err) {\n    console.error(ref.err)\n  } else {\n    console.log(ref.ref)\n    // output: \"QmHash\"\n  }\n}\n```\n\n## `ipfs.refs.local([options])`\n\n> Output all local references (CIDs of all blocks in the blockstore)\n\nBlocks in the blockstore are stored by multihash and not CID so yielded CIDs are v1 CIDs with the 'raw' codec. These may not match the CID originally used to store a given block, though the multihash contained within the CID will.\n\n### Parameters\n\nNone\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `AsyncIterable<Object>` | An async iterable that yields objects representing the links (references) |\n\nEach yielded object is of the form:\n\n```js\n{\n  ref: string,\n  err: Error | null\n}\n```\n\n### Example\n\n```JavaScript\nfor await (const ref of ipfs.refs.local()) {\n  if (ref.err) {\n    console.error(ref.err)\n  } else {\n    console.log(ref.ref)\n    // output: \"QmHash\"\n  }\n}\n```\n\n[examples]: https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/src/files-regular\n[b]: https://www.npmjs.com/package/buffer\n[cid]: https://docs.ipfs.io/concepts/content-addressing\n[blob]: https://developer.mozilla.org/en-US/docs/Web/API/Blob\n[AbortSignal]: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal\n"
  },
  {
    "path": "docs/core-api/REPO.md",
    "content": "# Repo API <!-- omit in toc -->\n\n- [`ipfs.repo.gc([options])`](#ipfsrepogcoptions)\n  - [Parameters](#parameters)\n  - [Options](#options)\n  - [Returns](#returns)\n  - [Example](#example)\n- [`ipfs.repo.stat([options])`](#ipfsrepostatoptions)\n  - [Parameters](#parameters-1)\n  - [Options](#options-1)\n  - [Returns](#returns-1)\n  - [Example](#example-1)\n  - [Notes](#notes)\n- [`ipfs.repo.version([options])`](#ipfsrepoversionoptions)\n  - [Parameters](#parameters-2)\n  - [Options](#options-2)\n  - [Returns](#returns-2)\n  - [Example](#example-2)\n\n## `ipfs.repo.gc([options])`\n\n> Perform a garbage collection sweep on the repo.\n\n### Parameters\n\nNone\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| quiet | `boolean` | `false` | Write minimal output |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `AsyncIterable<Object>` | An async iterable that yields objects describing nodes that were garbage collected |\n\nEach yielded object contains the following properties:\n\n- `err` is an `Error` if it was not possible to GC a particular block.\n- `cid` is the [CID][cid] of the block that was Garbage Collected.\n\n### Example\n\n```JavaScript\nfor await (const res of ipfs.repo.gc()) {\n  console.log(res)\n}\n```\n\n## `ipfs.repo.stat([options])`\n\n> Get stats for the currently used repo.\n\n### Parameters\n\nNone\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| human | `boolean` | `false` | Return storage numbers in `MiB` |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<Object>` | An object containing the repo's info |\n\nthe returned object has the following keys:\n\n- `numObjects` is a [BigInt][1].\n- `repoSize` is a [BigInt][1], in bytes.\n- `repoPath` is a string.\n- `version` is a string.\n- `storageMax` is a [BigInt][1].\n\n### Example\n\n```JavaScript\nconst stats = await ipfs.repo.stat()\nconsole.log(stats)\n\n// { numObjects: 15,\n//   repoSize: 64190,\n//   repoPath: 'C:\\\\Users\\\\henri\\\\AppData\\\\Local\\\\Temp\\\\ipfs_687c6eb3da07d3b16fe3c63ce17560e9',\n//   version: 'fs-repo@6',\n//   storageMax: 10000000000 }\n```\n\n### Notes\n\n`stats.repo` and `repo.stat` can be used interchangeably.\n\n## `ipfs.repo.version([options])`\n\n> Show the repo version.\n\n### Parameters\n\nNone\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<String>` | A String containing the repo's version |\n\n### Example\n\n```JavaScript\nconst version = await ipfs.repo.version()\nconsole.log(version)\n\n// \"6\"\n```\n\n[1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt\n[cid]: https://docs.ipfs.io/concepts/content-addressing\n[AbortSignal]: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal\n"
  },
  {
    "path": "docs/core-api/STATS.md",
    "content": "# Stats API <!-- omit in toc -->\n\n- [`ipfs.stats.bitswap([options]`](#ipfsstatsbitswapoptions)\n- [`ipfs.stats.repo([options])`](#ipfsstatsrepooptions)\n- [`ipfs.stats.bw([options])`](#ipfsstatsbwoptions)\n  - [Parameters](#parameters)\n  - [Options](#options)\n  - [Returns](#returns)\n  - [Example](#example)\n\n## `ipfs.stats.bitswap([options]`\n\n> Show diagnostic information on the bitswap agent.\n\nNote: `stats.bitswap` and `bitswap.stat` can be used interchangeably. See [`bitswap.stat`](./BITSWAP.md#bitswapstat) for more details.\n\n## `ipfs.stats.repo([options])`\n\n> Get stats for the currently used repo.\n\nNote: `stats.repo` and `repo.stat` can be used interchangeably. See [`repo.stat`](./REPO.md#repostat) for more details.\n\n## `ipfs.stats.bw([options])`\n\n> Get IPFS bandwidth information.\n\n### Parameters\n\nNone\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| peer | [PeerId][] | `undefined` | Specifies a peer to print bandwidth for |\n| proto | `String` | `undefined` | Specifies a protocol to print bandwidth for |\n| poll | `boolean` | `undefined` | Is used to yield bandwidth info at an interval |\n| interval | `Number` | `undefined` | The time interval to wait between updating output, if `poll` is `true` |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `AsyncIterable<Object>` | An async iterable that yields IPFS bandwidth information |\n\nEach yielded object contains the following keys:\n\n- `totalIn` - is a [BigInt][bigNumber], in bytes.\n- `totalOut` - is a [BigInt][bigNumber], in bytes.\n- `rateIn` - is a `float`, in bytes.\n- `rateOut` - is a `float`, in bytes.\n\n### Example\n\n```JavaScript\nfor await (const stats of ipfs.stats.bw()) {\n  console.log(stats)\n}\n// { totalIn: BigInt {...},\n//   totalOut: BigInt {...},\n//   rateIn: number {...},\n//   rateOut: number {...} }\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n[bigNumber]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt\n[examples]: https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/src/stats\n[AbortSignal]: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal\n[cid]: https://docs.ipfs.io/concepts/content-addressing\n[peerid]: https://docs.libp2p.io/concepts/peer-id/\n"
  },
  {
    "path": "docs/core-api/SWARM.md",
    "content": "# Swarm API <!-- skip in toc -->\n\n- [Swarm API](#swarm-api)\n  - [`ipfs.swarm.addrs([options])`](#ipfsswarmaddrsoptions)\n    - [Parameters](#parameters)\n    - [Options](#options)\n    - [Returns](#returns)\n    - [Example](#example)\n  - [`ipfs.swarm.connect(addr, [options])`](#ipfsswarmconnectaddr-options)\n    - [Parameters](#parameters-1)\n    - [Options](#options-1)\n    - [Returns](#returns-1)\n    - [Example](#example-1)\n  - [`ipfs.swarm.disconnect(addr, [options])`](#ipfsswarmdisconnectaddr-options)\n    - [Parameters](#parameters-2)\n    - [Options](#options-2)\n    - [Returns](#returns-2)\n    - [Example](#example-2)\n  - [`ipfs.swarm.localAddrs([options])`](#ipfsswarmlocaladdrsoptions)\n    - [Parameters](#parameters-3)\n    - [Options](#options-3)\n    - [Returns](#returns-3)\n    - [Example](#example-3)\n  - [`ipfs.swarm.peers([options])`](#ipfsswarmpeersoptions)\n    - [Parameters](#parameters-4)\n    - [Options](#options-4)\n    - [Returns](#returns-4)\n    - [Example](#example-4)\n\n## `ipfs.swarm.addrs([options])`\n\n> List of known addresses of each peer connected.\n\n### Parameters\n\nNone\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<Array<{ id: String, addrs: Multiaddr[] }>>` | A promise that resolves to an array of objects with `id` and `addrs`. `id` is a String - the peer's ID and `addrs` is an array of [Multiaddr](https://github.com/multiformats/js-multiaddr/) - addresses for the peer. |\n\n### Example\n\n```JavaScript\nconst peerInfos = await ipfs.swarm.addrs()\n\npeerInfos.forEach(info => {\n  console.log(info.id)\n  /*\n  QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt\n  */\n\n  info.addrs.forEach(addr => console.log(addr.toString()))\n  /*\n  /ip4/147.75.94.115/udp/4001/quic\n  /ip6/2604:1380:3000:1f00::1/udp/4001/quic\n  /dnsaddr/bootstrap.libp2p.io\n  /ip6/2604:1380:3000:1f00::1/tcp/4001\n  /ip4/147.75.94.115/tcp/4001\n  */\n})\n\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.swarm.connect(addr, [options])`\n\n> Open a connection to a given address.\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| addr | [MultiAddr][] or [PeerId][] | The PeerId or Multiaddr to connect to |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<void>` | If action is successfully completed. Otherwise an error will be thrown |\n\n### Example\n\n```JavaScript\nawait ipfs.swarm.connect(addr)\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.swarm.disconnect(addr, [options])`\n\n> Close a connection on a given address.\n\n### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| addr | [MultiAddr][] or [PeerId][] | The PeerId or Multiaddr to disconnect from |\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<void>` | If action is successfully completed. Otherwise an error will be thrown |\n\n### Example\n\n```JavaScript\nawait ipfs.swarm.disconnect(addr)\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.swarm.localAddrs([options])`\n\n> Local addresses this node is listening on.\n\n### Parameters\n\nNone\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<Multiaddr[]>` | An array of [`Multiaddr`](https://github.com/multiformats/js-multiaddr) representing the local addresses the node is listening |\n\n### Example\n\n```JavaScript\nconst multiAddrs = await ipfs.swarm.localAddrs()\nconsole.log(multiAddrs)\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n## `ipfs.swarm.peers([options])`\n\n> List out the peers that we have connections with.\n\n### Parameters\n\nNone\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| direction | `boolean` | `false` | If true, return connection direction information |\n| streams | `boolean` | `false` | If true, return information about open muxed streams |\n| verbose | `boolean` | `false` | If true, return all extra information |\n| latency | `boolean` | `false` | If true, return latency information |\n| timeout | `Number` | `undefined` | A timeout in ms |\n| signal | [AbortSignal][] | `undefined` |  Can be used to cancel any long running requests started as a result of this call |\n\n### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise<Object[]>` | An array with the list of peers that the node have connections with |\n\nThe returned array has the following form:\n\n- `addr: Multiaddr`\n- `peer: String`\n- `latency: String` - Only if `verbose: true`  was passed\n- `muxer: String` - The type of stream muxer the peer is usng\n- `streams: string[]` - Only if `verbose: true`, a list of currently open streams\n- `direction: number` - Inbound or outbound connection\n\nIf an error occurs trying to create an individual object, it will have the properties:\n\n- `error: Error` - the error that occurred\n- `rawPeerInfo: Object` - the raw data for the peer\n\nAll other properties may be `undefined`.\n\n### Example\n\n```JavaScript\nconst peerInfos = await ipfs.swarm.peers()\nconsole.log(peerInfos)\n```\n\nA great source of [examples][] can be found in the tests for this API.\n\n[examples]: https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/src/swarm\n[AbortSignal]: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal\n[MultiAddr]: https://github.com/multiformats/js-multiaddr\n[peerid]: https://docs.libp2p.io/concepts/peer-id/\n"
  },
  {
    "path": "docs/img/architecture.txt",
    "content": " ┌─────────────────────────────────────────────────────────────────────────────┐\n │                            The IPFS Architecture                            │\n └─────────────────────────────────────────────────────────────────────────────┘\n               ┏━   ━━   ━━   ━━   ━━   ━━   ━━   ━━   ━━   ━━   ━━   ━━   ━━\n                  ======================= IPFS Daemon =======================  ┃\n               ┃                                                               ┃\n               ┃┌────┐ ┏ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━\n                │    │     ++++++++++++++++++ IPFS Core ++++++++++++++++++    ┃\n                │    │ ┃ ┌──────────────────────────────────────────────────┐\n                │HTTP│ ┌─│                  API (Core API)                  │ ┃┃\n               ┃│Gate│ │ ├──────┬──────┬──────┬──────┬──────┬───────┬───────┤  ┃\n               ┃│way │◀┤ │ Repo │Block │ DAG  │ Pin  │Files │       │Network│ ┃\n                │    │ │ └──────┴──────┴──────┴──────┴──────┘       └───────┘\n                │    │ │     │      │      │      │      │              │     ┃\n                │    │ │     │      │ ┌────┘      │ ┌────┘         ┌────┘      ┃\n               ┃└────┘ │  ┌──┘      │ │    ┌──────┘ │              ▼          ┃┃\n               ┃       │  │┌────────┘ │    ▼        ▼   ┌────────────────────┐\n                ┌────┐ │  ││          │┌───────┐┌──────┐│ libp2p             │┃\n                │    │ │  ││          ││Pinning││Unixfs││ (Network, PubSub,  │\n                │    │ │  ││          ││Service││Engine││ Swarm, Crypto)     │┃┃\n               ┃│    │ │  ││          │└───────┘└──────┘│┌──────────────────┐│ ┃\n               ┃│HTTP│ │  ││          │    │        │   ││Connection Manager││┃\n                │RPC │ │  ││          ├────┴────────┘   │└──────────────────┘│\n┌───┐┌────────┐ │API │◀┘  ││          │                 │┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ │┃\n│CLI││ipfs-api│ │    │ ┃  ││          │                 │  Peer Reputation  ││ ┃\n└───┘└────────┘┃│    │    ││          │                 │└ ─ ─ ─ ─ ─ ─ ─ ─ ─ │┃┃\n               ┃│    │ ┃  ││       ┌──┘                 └────────────────────┘\n                │    │    ││       │                   ┌ ─ ─ ─ ─ ┐┌ ─ ─ ─ ─ ─ ┃\n                └────┘ ┃  ││       │                    Providers      GC    │\n                          ││       ▼                   │ Service ││           ┃┃\n               ┃       ┃  ││┌─────────────┐             ─ ─ ─ ─ ─  ─ ─ ─ ─ ─ ┘ ┃\n               ┃          │││Graph Service│─────┬───────────┬───────────┐     ┃\n                       ┃  ││└─────────────┘     ▼           ▼           ▼\n                          ││       │      ┌ ─ ─ ─ ─ ─ ┌ ─ ─ ─ ─ ─ ┌ ─ ─ ─ ─ ─ ┃\n                       ┃  │└───────┤       GraphSync │ GraphSyncB│ GraphSyncC│ ┃\n               ┃          │        ▼      └ ─ ─ ─ ─ ─ └ ─ ─ ─ ─ ─ └ ─ ─ ─ ─ ─ ┃┃\n               ┃       ┃  │ ┌─────────────┐\n                          │ │Block Service│─────┬───────────┬───────────┐     ┃\n                       ┃  │ └─────────────┘     ▼           ▼           ▼\n                          │        │      ┌──────────┐┌ ─ ─ ─ ─ ─ ┌ ─ ─ ─ ─ ─ ┃┃\n               ┃       ┃  └─────┬──┴──────│ Bitswap  │  BitswapB │  BitswapB │ ┃\n               ┃                ▼         └──────────┘└ ─ ─ ─ ─ ─ └ ─ ─ ─ ─ ─ ┃\n                       ┃   ┌─────────┐\n                           │  Repo   │                                        ┃\n                       ┃   └─────────┘                                         ┃\n               ┃                │                                             ┃┃\n               ┃       ┃      ┌─┴──────┬──────────┬───────┐\n                              ▼        ▼          ▼       ▼                   ┃\n                       ┃   ┌────┐┌──────────┐┌────────┐┌────┐\n                           │ fs ││indexedDB ││LevelDB ││ S3 │                 ┃┃\n               ┃       ┃   └────┘└──────────┘└────────┘└────┘                  ┃\n               ┃                                                              ┃\n                       ┗ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━\n                 ━━   ━━   ━━   ━━   ━━   ━━   ━━   ━━   ━━   ━━   ━━   ━━   ━━\n\n   ┌───────────────────────────────────────────────────────────────────────────┐\n   │ Legend                                                                    │\n   │ ┌ ─ ─ ┐                                                                   │\n   │         Planned, not yet implemented                                      │\n   │ └ ─ ─ ┘                                                                   │\n   │ ┌─────┐                                                                   │\n   │ │     │ Exist and shipped with IPFS                                       │\n   │ └─────┘                                                                   │\n   └───────────────────────────────────────────────────────────────────────────┘"
  },
  {
    "path": "docs/img/core.txt",
    "content": "┌─────────────────────────────────────────────────────────────────────────────┐\n│                                                                             │\n│                                  IPFS Core                                  │\n│                                                                             │\n├ ─ ─ ─ ─┌ ─ ─ ─ ─┌ ─ ─ ─ ─┌ ─ ─ ─ ─┌ ─ ─ ─ ─                        ┌ ─ ─ ─ ─│\n│  Repo  │ Block  │Bitswap │  DAG   │ Files  │                         Swarm  │\n│        │        │        │        │                                │        │\n└────────┴───┬────┴────────┴────────┴────────┴────────────────────────────────┘\n     │       │         │       │      │                                  │\n     │       │         │       │      ▼                                  │\n     │       │         │       │ ┌──────────────────┐                    ▼\n     │       │         │       │ │ipfs-unixfs-engine│   ┌─────────────────────┐\n     │       │         │       │ └──────┬───────────┤   │                     │\n     │       │         │       │      │ │ipfs-unixfs│   │       libp2p        │\n     │       │         │       │      │ └───────────┘   │                     │\n     │       │         │       ▼      ▼                 └─────────────────────┘\n     │       │         │     ┌─────────────┬────────┐\n     │       │         │     │ipfs-resolver│dag-pb  │\n     │       │         │     └─────────────┼────────┤\n     │       ▼         │            │      │dag-cbor│\n     │   ┌─────────────┴─────┐      │      ├────────┤\n     │   │ipfs-blocks-service│◀─────┘      │ethereum│\n     │   └─────────────┬─────┘             ├────────┤\n     │       │    │    │                   │...     │\n     │       │    │    │                   └────────┘\n     │       │    │    │\n     │       │    ▼    ▼\n     │       │  ┌────────────┐\n     ├───────┴──│ipfs-bitswap│\n     │          └────────────┘\n     ▼\n┌─────────┬─────────┐\n│         │   fs    │\n│ipfs-repo├─────────┤\n│         │IndexedDB│\n└─────────┴─────────┘"
  },
  {
    "path": "docs/img/overview.txt",
    "content": "\n                     offline mode - uses IPFS core directly\n                  ┌────────────────────────────────────────────┐\n                  │                                            │\n                  │                                            │\n                  │ online mode - uses IPFS through http-api   │\n ┌────────────┐   │                          ┌─────────────┐   │    ┌─────────┐\n │            │   │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─    │             │   │    │         │\n │    CLI     │───┴──  ipfs-http-client  ├──▶│  HTTP-API   │───┴───▶│IPFS Core│\n │            │     └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─    │             │        │         │\n └────────────┘                              └─────────────┘        └─────────┘\n        △                                           △                    △\n        ├───────────────────────────────────────────┴────────────────────┘\n        │\n ┌────────────┐\n │   Tests    │\n └────────────┘                                                                \n"
  },
  {
    "path": "docs/upgrading/v0.62-v0.63.md",
    "content": "<!--Specify versions for migration below-->\n# Migrating to ipfs@0.63 and ipfs-core@0.15 <!-- omit in toc -->\n\n> A migration guide for refactoring your application code from `ipfs@0.62.x` to `ipfs@0.63.x`\n\n## Table of Contents <!-- omit in toc -->\n\n- [ESM](#esm)\n  - [TypeScript and ESM](#typescript-and-esm)\n- [`libp2p@0.37.x`](#libp2p037x)\n- [PeerIds](#peerids)\n- [multiaddrs](#multiaddrs)\n\n## ESM\n\nThe biggest change to `ipfs@0.63.x` is that the module is now [ESM-only](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c).\n\nESM is the module system for JavaScript.  It allows us to structure our code in separate files without polluting a global namespace.\n\nOther systems have tried to fill this gap, notably CommonJS, AMD, RequireJS and others, but ESM is [the official standard format](https://tc39.es/ecma262/#sec-modules) to package JavaScript code for reuse.\n\nIf you see errors similar to `Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: No \"exports\" main defined in node_modules/ipfs/package.json` you are likely trying to load ESM code from a CJS environment via `require`. This is not possible, instead it must be loaded using `import`.\n\nIf your application is not yet ESM or you are not ready to port it to ESM, you can use the [dynamic `import` function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) to load `ipfs` at runtime from a CJS module:\n\n```js\nasync function loadIpfs () {\n  const { create } = await import('ipfs-core')\n\n  const node = await create({\n    // ... config here\n  })\n\n  return node\n}\n```\n\n### TypeScript and ESM\n\nWhen authoring typescript it can often look like you are writing ESM:\n\n```ts\nimport { create } from 'ipfs-core'\n\ncreate()\n```\n\nWhen this is transpiled to JavaScript the default settings will emit CJS which will fail at runtime:\n\n```js\n\"use strict\";\nexports.__esModule = true;\nvar ipfs_core_1 = require(\"ipfs-core\");\n(0, ipfs_core_1.create)();\n```\n\nYou may also see errors about private identifiers:\n\n```console\nnode_modules/@libp2p/interfaces/dist/src/events.d.ts:19:5 - error TS18028: Private identifiers are only available when targeting ECMAScript 2015 and higher.\n\n19     #private;\n       ~~~~~~~~\n```\n\nTo build correctly with ESM as a target, update your `tsconfig.json` to include the following:\n\n```js\n{\n  \"module\": \"es2020\", // ensures output is ESM\n  \"target\": \"es2020\", // support modern features like private identifiers\n  // other settings\n}\n```\n\nThey must both be set to `es2020` at least, more recent versions will also work.\n\nIf in doubt, examine the JavaScript files `tsc` emits and ensure that any `ipfs` modules are being loaded with `import` and not `require`.\n\n## `libp2p@0.37.x`\n\n`ipfs@0.63.x` upgrades to `libp2p@0.37.x`.  This is a significant refactor that ports the entire stack to TypeScript and publishes all modules as ESM-only code.\n\nPlease see the [libp2p 0.37.x upgrade guide](https://github.com/libp2p/js-libp2p/blob/master/doc/migrations/v0.36-v0.37.md) for how this may affect your application.\n\n## PeerIds\n\nThe core `libp2p` module and all supporting modules have now been ported to TypeScript in a complete ground-up rewrite. We took this opportunity to solve a few long-standing problems with some of the data types, particularly in how they relate to use in the browser.\n\nOne problem we have solved is that the `PeerId` objects used internally expose some cryptographic operations that require heavyweight libraries to be included in browser bundles due to there being no native web-crypto implementation of the algorithms used in those operations.\n\nWith `libp2p@0.37.x` those operations have been encapsulated in the `@libp2p/crypto` module which means `PeerId` objects become a lot more lightweight and can now be exposed/accepted as core-api types so we can use them to differentiate between different data types instead of having to treat everything as strings.\n\nThe affected methods are:\n\n```js\n// `peerId` must now be a `PeerId`, previously it was a `string`\nipfs.bitswap.wantlistForPeer(peerId, options)\n\n// Bitswp peers are now returned as `PeerId[]` instead of `string[]`\nipfs.bitswap.stat(options)\n\n// `peerId` must now be a `PeerId`\nipfs.dht.findPeer(peerId, options)\n\n// `peerIdOrCid` must now be a `PeerId` or a `CID`, previously it was a `string` or a `CID`\nipfs.dht.query(peerIdOrCid, options)\n\n// the following DHT events have their `from` field as `PeerId`, previously it was a `string`\nPeerResponseEvent\nValueEvent\nDialingPeerEvent\n\n// the following DHT events have had their `from` property removed because it is not exposed by go-ipfs so causes incompatibilities\nQueryErrorEvent\nFinalPeerEvent\n\n// the folloing DHT events have had their `to` property removed because it is not exposed by go-ipfs so causes incompatibilities\nSendingQueryEvent\n\n// the `providers` and `closer` properties (where applicable) of the following events have the `peerId` property specified as a `PeerId`, previously it was a `string`\nPeerResponseEvent\nPeerResponseEvent\n\n// `value` can now be a string or a `PeerId`.  If a string is passed it will be interpreted as a DNS address.\nipfs.name.resolve(value, options)\n\n// The return type of this method is now `Promise<PeerId[]>`, previously it was a `Promise<string[]>`\nipfs.pubsub.peers(topic, options)\n\n// `peerId` must now be a `PeerId`, previously it was a `string`\nipfs.ping(peerId, options)\n\n// the `peer` property of `options` must now be a `PeerId` when specified, previously it was a `string`\nipfs.stats.bw(options)\n\n// `multiaddrOrPeerId` must be a `Multiaddr` or `PeerId`, previously it was a `Multiaddr` or `string`\nipfs.swarm.connect(multiaddrOrPeerId, options)\n\n// `multiaddrOrPeerId` must be a `Multiaddr` or `PeerId`, previously it was a `Multiaddr` or `string`\nipfs.swarm.disconnect(multiaddrOrPeerId, options)\n```\n\n`PeerId`s can be created from strings using the `@libp2p/peer-id` module:\n\n```js\nimport { peerIdFromString } from '@libp2p/peer-id'\n\nconst peerId = peerIdFromString('Qmfoo')\n```\n\nThey can also be created using the `@libp2p/peer-id-factory` module:\n\n```js\nimport { createEd25519PeerId } from '@libp2p/peer-id-factory'\n\nconst peerId = await createEd25519PeerId()\n```\n\n## multiaddrs\n\nThe `multiaddr` module has been ported to TypeScript and is now published as ESM-only.\n\nIt has been renamed to `@multiformats/multiaddr` so please update your dependencies and replace usage in your code.\n\nThe API otherwise is compatible.\n\n"
  },
  {
    "path": "docs/upgrading/v0.63-v0.64.md",
    "content": "<!--Specify versions for migration below-->\n# Migrating to ipfs@0.64 and ipfs-core@0.16 <!-- omit in toc -->\n\n> A migration guide for refactoring your application code from `ipfs@0.63.x` to `ipfs@0.64.x`\n\n## Table of Contents <!-- omit in toc -->\n\n- [libp2p](#libp2p)\n\n## libp2p\n\nThe upgrade to `ipfs@0.64.x` incorporates an update to `libp2p@0.38.x` but no API changes.\n\nIf your application uses only the default libp2p config there is nothing to do.\n\nIf you supply a custom `libp2p` instance to the `ipfs` factory function you should consult the [`libp2p@0.38.x` upgrade guide](https://github.com/libp2p/js-libp2p/blob/master/doc/migrations/v0.37-v0.38.md) for any changes you need to make.\n"
  },
  {
    "path": "docs/upgrading/v0.64-v0.65.md",
    "content": "<!--Specify versions for migration below-->\n# Migrating to ipfs@0.65 and ipfs-core@0.17 <!-- omit in toc -->\n\n> A migration guide for refactoring your application code from `ipfs@0.64.x` to `ipfs@0.65.x`\n\n## Table of Contents <!-- omit in toc -->\n\n- [libp2p](#libp2p)\n- [multiformats](#multiformats)\n\n## libp2p\n\nThe upgrade to `ipfs@0.65.x` incorporates an update to `libp2p@0.40.x` but no API changes.\n\nIf your application uses only the default libp2p config there is nothing to do.\n\nIf you supply a custom `libp2p` instance to the `ipfs` factory function you should consult the [`libp2p@0.40.x` upgrade guide](https://github.com/libp2p/js-libp2p/blob/master/doc/migrations/v0.39-v0.40.md) for any changes you need to make.\n\n## multiformats\n\n`ipfs@0.65.x` now uses `multiformats@10.x.x`, this means instances of the `CID` class now come from that module and not `multiformats@9.x.x` so any `instanceof` checks your codebase has may break if instances are compare to the class loaded from a different module version.\n\nIf your project also has a dependency on the `multiformats` module, it should be updated to `10.x.x` in line with js-ipfs.\n"
  },
  {
    "path": "package-list.json",
    "content": "{\n  \"columns\": [\n    \"Package\",\n    \"Version\",\n    \"Deps\",\n    \"CI/Travis\",\n    \"Coverage\",\n    \"Lead Maintainer\"\n  ],\n  \"rows\": [\n    \"Files\",\n    [\"ipfs/js-ipfs-unixfs\", \"ipfs-unixfs\"],\n\n    \"Repo\",\n    [\"ipfs/js-ipfs-repo\", \"ipfs-repo\"],\n    [\"ipfs/js-ipfs-repo-migrations\", \"ipfs-repo-migrations\"],\n\n    \"Exchange\",\n    [\"ipfs/js-ipfs-bitswap\", \"ipfs-bitswap\"],\n\n    \"IPNS\",\n    [\"ipfs/js-ipns\", \"ipns\"],\n\n    \"Generics/Utils\",\n    [\"ipfs/js-ipfs\", \"ipfs-utils\"],\n    [\"ipfs/js-ipfs\", \"ipfs-http-client\"],\n    [\"ipfs/js-ipfs-http-response\", \"ipfs-http-response\"],\n    [\"ipfs/js-ipfsd-ctl\", \"ipfsd-ctl\"],\n    [\"ipfs/is-ipfs\", \"is-ipfs\"],\n    [\"ipfs/aegir\", \"aegir\"],\n\n    \"libp2p\",\n    [\"libp2p/js-libp2p\", \"libp2p\"],\n    [\"libp2p/js-peer-id\", \"peer-id\"],\n    [\"libp2p/js-libp2p-crypto\", \"libp2p-crypto\"],\n    [\"libp2p/js-libp2p-floodsub\", \"libp2p-floodsub\"],\n    [\"ChainSafe/gossipsub-js\", \"libp2p-gossipsub\"],\n    [\"libp2p/js-libp2p-kad-dht\", \"libp2p-kad-dht\"],\n    [\"libp2p/js-libp2p-mdns\", \"libp2p-mdns\"],\n    [\"libp2p/js-libp2p-bootstrap\", \"libp2p-bootstrap\"],\n    [\"ChainSafe/js-libp2p-noise\", \"libp2p-noise\"],\n    [\"libp2p/js-libp2p-tcp\", \"libp2p-tcp\"],\n    [\"libp2p/js-libp2p-webrtc-star\", \"libp2p-webrtc-star\"],\n    [\"libp2p/js-libp2p-websockets\", \"libp2p-websockets\"],\n    [\"libp2p/js-libp2p-mplex\", \"libp2p-mplex\"],\n    [\"libp2p/js-libp2p-delegated-content-routing\", \"libp2p-delegated-content-routing\"],\n    [\"libp2p/js-libp2p-delegated-peer-routing\", \"libp2p-delegated-peer-routing\"],\n\n    \"IPLD\",\n    [\"ipld/js-dag-pb\", \"@ipld/dag-pb\"],\n    [\"ipld/js-dag-cbor\", \"@ipld/dag-cbor\"],\n\n    \"Multiformats\",\n    [\"multiformats/js-multiformats\", \"multiformats\"],\n    [\"multiformats/js-mafmt\", \"mafmt\"],\n    [\"multiformats/js-multiaddr\", \"@multiformats/multiaddr\"]\n  ]\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"js-ipfs\",\n  \"version\": \"1.0.0\",\n  \"description\": \"JavaScript implementation of the IPFS specification\",\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"homepage\": \"https://github.com/ipfs/js-ipfs#readme\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ipfs/js-ipfs.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ipfs/js-ipfs/issues\"\n  },\n  \"engines\": {\n    \"node\": \">=16.0.0\",\n    \"npm\": \">=7.0.0\"\n  },\n  \"private\": true,\n  \"scripts\": {\n    \"reset\": \"aegir run clean && aegir clean packages/*/node_modules node_modules package-lock.json packages/*/package-lock.json\",\n    \"test\": \"aegir run test\",\n    \"test:node\": \"aegir run test:node\",\n    \"test:chrome\": \"aegir run test:chrome\",\n    \"test:chrome-webworker\": \"aegir run test:chrome-webworker\",\n    \"test:firefox\": \"aegir run test:firefox\",\n    \"test:firefox-webworker\": \"aegir run test:firefox-webworker\",\n    \"test:electron-main\": \"aegir run test:electron-main\",\n    \"test:external\": \"aegir run test:external\",\n    \"test:cli\": \"aegir run test:cli\",\n    \"test:interop\": \"aegir run test:interop\",\n    \"test:interface:client\": \"aegir run test:interface:client\",\n    \"test:interface:core\": \"aegir run test:interface:core\",\n    \"test:interface:http-go\": \"aegir run test:interface:http-go\",\n    \"test:interface:http-js\": \"aegir run test:interface:http-js\",\n    \"test:interface:message-port-client\": \"aegir run test:interface:message-port-client\",\n    \"coverage\": \"aegir run coverage\",\n    \"build\": \"aegir run build\",\n    \"clean\": \"aegir run clean\",\n    \"lint\": \"aegir run lint\",\n    \"dep-check\": \"aegir run dep-check\",\n    \"release\": \"run-s build npm:release docker:release\",\n    \"npm:release\": \"aegir exec npm -- publish\",\n    \"docker:release\": \"run-s docker:release:*\",\n    \"docker:release:build\": \"docker build . --no-cache --tag js-ipfs:latest --file ./Dockerfile.latest\",\n    \"docker:release:tag-latest\": \"docker tag js-ipfs:latest docker.io/ipfs/js-ipfs:latest\",\n    \"docker:release:tag-version\": \"docker tag js-ipfs:latest docker.io/ipfs/js-ipfs:v`npm show ipfs@latest version -q`\",\n    \"docker:release:push-latest\": \"docker push ipfs/js-ipfs:latest\",\n    \"docker:release:push-version\": \"docker push ipfs/js-ipfs:v`npm show ipfs@latest version -q`\",\n    \"release:rc\": \"run-s npm:rc docker:rc\",\n    \"npm:rc\": \"aegir release-rc\",\n    \"docker:rc\": \"run-s docker:rc:*\",\n    \"docker:rc:build\": \"docker build . --no-cache --tag js-ipfs:next --file ./Dockerfile.next\",\n    \"docker:rc:tag-next\": \"docker tag js-ipfs:next docker.io/ipfs/js-ipfs:next\",\n    \"docker:rc:tag-rc\": \"docker tag js-ipfs:next docker.io/ipfs/js-ipfs:v`npm show ipfs@next version -q`\",\n    \"docker:rc:push-next\": \"docker push ipfs/js-ipfs:next\",\n    \"docker:rc:push-rc\": \"docker push ipfs/js-ipfs:v`npm show ipfs@next version -q`\"\n  },\n  \"devDependencies\": {\n    \"aegir\": \"^37.11.0\",\n    \"npm-run-all\": \"^4.1.5\"\n  },\n  \"eslintConfig\": {\n    \"extends\": \"ipfs\",\n    \"ignorePatterns\": [\n      \"!.aegir.js\"\n    ]\n  },\n  \"workspaces\": [\n    \"packages/*\"\n  ]\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/.aegir.js",
    "content": "\n/** @type {import('aegir').PartialOptions} */\nexport default {\n  build: {\n    bundlesizeMax: '338kB'\n  }\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/CHANGELOG.md",
    "content": "# Change Log\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n### [0.158.1](https://www.github.com/ipfs/js-ipfs/compare/interface-ipfs-core-v0.158.0...interface-ipfs-core-v0.158.1) (2023-05-25)\n\n\n### Bug Fixes\n\n* add deprecation notice to readmes ([#4362](https://www.github.com/ipfs/js-ipfs/issues/4362)) ([7b79c1b](https://www.github.com/ipfs/js-ipfs/commit/7b79c1b8df5c818dc124b346ea28330455732d5c))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.14.0 to ^0.14.1\n\n## [0.158.0](https://www.github.com/ipfs/js-ipfs/compare/interface-ipfs-core-v0.157.0...interface-ipfs-core-v0.158.0) (2023-01-11)\n\n\n### ⚠ BREAKING CHANGES\n\n* update multiformats to v11.x.x and related depenendcies (#4277)\n\n### Bug Fixes\n\n* allow reading rawLeaves in MFS ([#4282](https://www.github.com/ipfs/js-ipfs/issues/4282)) ([0cfcaf6](https://www.github.com/ipfs/js-ipfs/commit/0cfcaf65998bdc2af0cc29ac48229bb3bc35c5b8))\n* disallow publishing pubsub messages to zero peers ([#4286](https://www.github.com/ipfs/js-ipfs/issues/4286)) ([fa578ba](https://www.github.com/ipfs/js-ipfs/commit/fa578bace93e459849a0ffcebbd6f222dc05652d))\n* mfs blob import for files larger than 262144b ([#4251](https://www.github.com/ipfs/js-ipfs/issues/4251)) ([6be5906](https://www.github.com/ipfs/js-ipfs/commit/6be59068cc99c517526bfa123ad475ae05fcbaef)), closes [#3601](https://www.github.com/ipfs/js-ipfs/issues/3601) [#3576](https://www.github.com/ipfs/js-ipfs/issues/3576)\n* update multiformats to v11.x.x and related depenendcies ([#4277](https://www.github.com/ipfs/js-ipfs/issues/4277)) ([521c84a](https://www.github.com/ipfs/js-ipfs/commit/521c84a958b04d61702577a5adce28519c1b2a3b))\n* use aegir to publish RCs ([#4284](https://www.github.com/ipfs/js-ipfs/issues/4284)) ([6d90cbf](https://www.github.com/ipfs/js-ipfs/commit/6d90cbf321a7dbf4b1084ba20f0c514dc08d8d0a))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.13.0 to ^0.14.0\n\n## [0.157.0](https://www.github.com/ipfs/js-ipfs/compare/interface-ipfs-core-v0.156.1...interface-ipfs-core-v0.157.0) (2022-10-24)\n\n\n### ⚠ BREAKING CHANGES\n\n* ipfs is now bundled with libp2p@0.40.x which has different config\n* require IPNS V2 signatures (#4207)\n\n### Features\n\n* upgrade libp2p to 0.40.x ([#4237](https://www.github.com/ipfs/js-ipfs/issues/4237)) ([0cee4a4](https://www.github.com/ipfs/js-ipfs/commit/0cee4a4c55767022584dcbade0b0b9b43326f9c9))\n\n\n### Bug Fixes\n\n* require IPNS V2 signatures ([#4207](https://www.github.com/ipfs/js-ipfs/issues/4207)) ([d1b0a8a](https://www.github.com/ipfs/js-ipfs/commit/d1b0a8a71073b4ece0dbda5a5405d76dd8d5b358))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.12.1 to ^0.13.0\n\n### [0.156.1](https://www.github.com/ipfs/js-ipfs/compare/interface-ipfs-core-v0.156.0...interface-ipfs-core-v0.156.1) (2022-09-21)\n\n\n### Bug Fixes\n\n* update @multiformats/multiadd to 11.0.0 ([2a830bf](https://www.github.com/ipfs/js-ipfs/commit/2a830bf58a5929fcce51dede871c99f62192fbda))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.12.0 to ^0.12.1\n\n## [0.156.0](https://www.github.com/ipfs/js-ipfs/compare/interface-ipfs-core-v0.155.2...interface-ipfs-core-v0.156.0) (2022-09-06)\n\n\n### ⚠ BREAKING CHANGES\n\n* update to libp2p@0.38.x (#4151)\n\n### deps\n\n* update to libp2p@0.38.x ([#4151](https://www.github.com/ipfs/js-ipfs/issues/4151)) ([39dbf70](https://www.github.com/ipfs/js-ipfs/commit/39dbf708ec31b263115e44f420651fa4e056a89e))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.11.0 to ^0.12.0\n\n### [0.155.2](https://www.github.com/ipfs/js-ipfs/compare/interface-ipfs-core-v0.155.1...interface-ipfs-core-v0.155.2) (2022-06-24)\n\n\n### Bug Fixes\n\n* make pubsub message types consistent ([#4145](https://www.github.com/ipfs/js-ipfs/issues/4145)) ([00bd3dd](https://www.github.com/ipfs/js-ipfs/commit/00bd3dd0bca7fc705e5e87272972f586d1f161e8))\n\n### [0.155.1](https://www.github.com/ipfs/js-ipfs/compare/interface-ipfs-core-v0.155.0...interface-ipfs-core-v0.155.1) (2022-06-22)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.11.0 to ^0.11.1\n\n## [0.155.0](https://www.github.com/ipfs/js-ipfs/compare/interface-ipfs-core-v0.154.3...interface-ipfs-core-v0.155.0) (2022-05-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* This module is now ESM only and there return types of some methods have changed\n\n### Features\n\n* update to libp2p 0.37.x ([#4092](https://www.github.com/ipfs/js-ipfs/issues/4092)) ([74aee8b](https://www.github.com/ipfs/js-ipfs/commit/74aee8b3d78f233c3199a3e9a6c0ac628a31a433))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.3 to ^0.11.0\n\n### [0.154.3](https://www.github.com/ipfs/js-ipfs/compare/interface-ipfs-core-v0.154.2...interface-ipfs-core-v0.154.3) (2022-04-20)\n\n\n### Bug Fixes\n\n* update car dependency for CARv2 read support ([#4085](https://www.github.com/ipfs/js-ipfs/issues/4085)) ([c367840](https://www.github.com/ipfs/js-ipfs/commit/c367840062e3fc555e696e4fc621651ed1929213))\n* upgrade dep of ipfs-utils ^9.0.2->^9.0.6 ([#4086](https://www.github.com/ipfs/js-ipfs/issues/4086)) ([8f7ce23](https://www.github.com/ipfs/js-ipfs/commit/8f7ce23c18be12bdc52b98bfccbd0a5a2a9c9f7e)), closes [#4080](https://www.github.com/ipfs/js-ipfs/issues/4080)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.2 to ^0.10.3\n\n### [0.154.2](https://www.github.com/ipfs/js-ipfs/compare/interface-ipfs-core-v0.154.1...interface-ipfs-core-v0.154.2) (2022-03-01)\n\n\n### Bug Fixes\n\n* missing files on publish ([#4056](https://www.github.com/ipfs/js-ipfs/issues/4056)) ([125d42b](https://www.github.com/ipfs/js-ipfs/commit/125d42ba72f905bf95b66489c1b593cbf0a623cb)), closes [#3976](https://www.github.com/ipfs/js-ipfs/issues/3976)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.1 to ^0.10.2\n\n### [0.154.1](https://www.github.com/ipfs/js-ipfs/compare/interface-ipfs-core-v0.154.0...interface-ipfs-core-v0.154.1) (2022-02-06)\n\n\n### Bug Fixes\n\n* **dag:** replace custom dag walk with multiformats/traversal ([#3950](https://www.github.com/ipfs/js-ipfs/issues/3950)) ([596b1f4](https://www.github.com/ipfs/js-ipfs/commit/596b1f48a014083b1736e4ad7e746c652d2583b1))\n* override hashing algorithm when importing files ([#4042](https://www.github.com/ipfs/js-ipfs/issues/4042)) ([709831f](https://www.github.com/ipfs/js-ipfs/commit/709831f61a822d28a6b8e4d6ddc2b659a836079f)), closes [#3952](https://www.github.com/ipfs/js-ipfs/issues/3952)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.0 to ^0.10.1\n\n## [0.154.0](https://www.github.com/ipfs/js-ipfs/compare/interface-ipfs-core-v0.153.0...interface-ipfs-core-v0.154.0) (2022-01-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* peerstore methods are now all async, the repo is migrated to v12\n* node 15+ is required\n\n### Features\n\n* add support for dag-jose codec ([#4028](https://www.github.com/ipfs/js-ipfs/issues/4028)) ([fbe1492](https://www.github.com/ipfs/js-ipfs/commit/fbe1492395ad98e620a872208530a3f8f61535a9))\n* libp2p async peerstore ([#4018](https://www.github.com/ipfs/js-ipfs/issues/4018)) ([a6b201a](https://www.github.com/ipfs/js-ipfs/commit/a6b201af2c3697430ab0ebe002dd573d185f1ac0))\n\n\n### Bug Fixes\n\n* remove abort-controller deps ([#4015](https://www.github.com/ipfs/js-ipfs/issues/4015)) ([902e887](https://www.github.com/ipfs/js-ipfs/commit/902e887e1acac87f607324fa7cb5ad4b14aefcf3))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.9.0 to ^0.10.0\n\n## [0.153.0](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.152.2...interface-ipfs-core@0.153.0) (2021-12-15)\n\n\n### Bug Fixes\n\n* **pubsub:** multibase in pubsub http rpc ([#3922](https://github.com/ipfs/js-ipfs/issues/3922)) ([6eeaca4](https://github.com/ipfs/js-ipfs/commit/6eeaca452c36fa13be42d704575c577e4ca938f1))\n* return nested value from dag.get ([#3966](https://github.com/ipfs/js-ipfs/issues/3966)) ([45ac973](https://github.com/ipfs/js-ipfs/commit/45ac9730d6484e8324acfbc3579fce052b8452d7)), closes [#3957](https://github.com/ipfs/js-ipfs/issues/3957)\n\n\n### chore\n\n* Bump @ipld/dag-cbor to v7 ([#3977](https://github.com/ipfs/js-ipfs/issues/3977)) ([73476f5](https://github.com/ipfs/js-ipfs/commit/73476f55e39ecfb01eb2b4880637aad658f51bc2))\n\n\n### Features\n\n* dht client ([#3947](https://github.com/ipfs/js-ipfs/issues/3947)) ([62d8ecb](https://github.com/ipfs/js-ipfs/commit/62d8ecbc723e693a2544e69172d99c576d187c23))\n* update DAG API to match go-ipfs@0.10 changes ([#3917](https://github.com/ipfs/js-ipfs/issues/3917)) ([38c01be](https://github.com/ipfs/js-ipfs/commit/38c01be03b4fd5f401cd9b698cfdb4237d835b01))\n\n\n### BREAKING CHANGES\n\n* **pubsub:** We had to make breaking changes to `pubsub` commands sent over HTTP RPC  to fix data corruption caused by topic names and payload bytes that included `\\n`. More details in https://github.com/ipfs/go-ipfs/issues/7939 and https://github.com/ipfs/go-ipfs/pull/8183\n* On decode of CBOR blocks, `undefined` values will be coerced to `null`\n* `ipfs.dag.put` no longer accepts a `format` arg, it is now `storeCodec` and `inputCodec`.  `'json'` has become `'dag-json'`, `'cbor'` has become `'dag-cbor'` and so on\n* The DHT API has been refactored to return async iterators of query events\n\n\n\n## [0.152.2](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.152.1...interface-ipfs-core@0.152.2) (2021-11-24)\n\n**Note:** Version bump only for package interface-ipfs-core\n\n\n\n\n\n## [0.152.1](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.152.0...interface-ipfs-core@0.152.1) (2021-11-19)\n\n**Note:** Version bump only for package interface-ipfs-core\n\n\n\n\n\n## [0.152.0](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.151.1...interface-ipfs-core@0.152.0) (2021-11-12)\n\n\n### Bug Fixes\n\n* do not accept single items for ipfs.add ([#3900](https://github.com/ipfs/js-ipfs/issues/3900)) ([04e3cf3](https://github.com/ipfs/js-ipfs/commit/04e3cf3f46b585c4644cba70516f375e95361f52))\n* do not lose files when writing files into subshards that contain other subshards ([#3936](https://github.com/ipfs/js-ipfs/issues/3936)) ([8a3ed19](https://github.com/ipfs/js-ipfs/commit/8a3ed19575beaafe5dfd3bce310a548950c148d0)), closes [#3921](https://github.com/ipfs/js-ipfs/issues/3921)\n\n\n### BREAKING CHANGES\n\n* errors will now be thrown if multiple items are passed to `ipfs.add` or single items to `ipfs.addAll` (n.b. you can still pass a list of a single item to `ipfs.addAll`)\n\n\n\n\n\n## [0.151.1](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.151.0...interface-ipfs-core@0.151.1) (2021-09-28)\n\n**Note:** Version bump only for package interface-ipfs-core\n\n\n\n\n\n## [0.151.0](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.150.4...interface-ipfs-core@0.151.0) (2021-09-24)\n\n\n### Features\n\n* pull in new globSource ([#3889](https://github.com/ipfs/js-ipfs/issues/3889)) ([be4a542](https://github.com/ipfs/js-ipfs/commit/be4a5428ebc4b05a2edd9a91bf9df6416c1a8c2b))\n* switch to esm ([#3879](https://github.com/ipfs/js-ipfs/issues/3879)) ([9a40109](https://github.com/ipfs/js-ipfs/commit/9a40109632e5b4837eb77a2f57dbc77fbf1fe099))\n\n\n### BREAKING CHANGES\n\n* the globSource api has changed from `globSource(dir, opts)` to `globSource(dir, pattern, opts)`\n* There are no default exports and everything is now dual published as ESM/CJS\n\n\n\n\n\n## [0.150.4](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.150.3...interface-ipfs-core@0.150.4) (2021-09-17)\n\n**Note:** Version bump only for package interface-ipfs-core\n\n\n\n\n\n## [0.150.3](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.150.2...interface-ipfs-core@0.150.3) (2021-09-17)\n\n**Note:** Version bump only for package interface-ipfs-core\n\n\n\n\n\n## [0.150.2](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.150.1...interface-ipfs-core@0.150.2) (2021-09-02)\n\n\n### Bug Fixes\n\n* declare types in .ts files ([#3840](https://github.com/ipfs/js-ipfs/issues/3840)) ([eba5fe6](https://github.com/ipfs/js-ipfs/commit/eba5fe6832858107b3e1ae02c99de674622f12b4))\n* remove client-side timeout from http rpc calls ([#3178](https://github.com/ipfs/js-ipfs/issues/3178)) ([f11220e](https://github.com/ipfs/js-ipfs/commit/f11220e00a12afed5ebbbd8b4c5134595aea735d)), closes [#3161](https://github.com/ipfs/js-ipfs/issues/3161)\n* remove use of instanceof for CID class ([#3847](https://github.com/ipfs/js-ipfs/issues/3847)) ([ebbb12d](https://github.com/ipfs/js-ipfs/commit/ebbb12db523c53ce8e4ddae5266cd9acb3504431))\n\n\n\n\n\n## [0.150.1](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.150.0...interface-ipfs-core@0.150.1) (2021-08-25)\n\n**Note:** Version bump only for package interface-ipfs-core\n\n\n\n\n\n## [0.150.0](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.149.0...interface-ipfs-core@0.150.0) (2021-08-17)\n\n\n### Bug Fixes\n\n* pin nanoid version ([#3807](https://github.com/ipfs/js-ipfs/issues/3807)) ([474523a](https://github.com/ipfs/js-ipfs/commit/474523ab8702729f697843d433a7a08baf2d101f))\n* throw error on missing input to add/addAll ([#3818](https://github.com/ipfs/js-ipfs/issues/3818)) ([1343708](https://github.com/ipfs/js-ipfs/commit/1343708f70d7298b6677555803d68ff282d89439)), closes [#3788](https://github.com/ipfs/js-ipfs/issues/3788)\n\n\n### Features\n\n* pubsub over gRPC ([#3813](https://github.com/ipfs/js-ipfs/issues/3813)) ([e7d5509](https://github.com/ipfs/js-ipfs/commit/e7d5509c87e87aed6be3c1d0b2a01ab74cdc1ed9)), closes [#3741](https://github.com/ipfs/js-ipfs/issues/3741)\n\n\n\n\n\n## [0.149.0](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.148.0...interface-ipfs-core@0.149.0) (2021-08-11)\n\n\n### Bug Fixes\n\n* return rate in/out as number ([#3798](https://github.com/ipfs/js-ipfs/issues/3798)) ([2f3df7a](https://github.com/ipfs/js-ipfs/commit/2f3df7a70fe94d6bdf20947854dc9d0b88cb759a)), closes [#3782](https://github.com/ipfs/js-ipfs/issues/3782)\n\n\n### Features\n\n* ed25519 keys by default ([#3693](https://github.com/ipfs/js-ipfs/issues/3693)) ([33fa734](https://github.com/ipfs/js-ipfs/commit/33fa7341c3baaf0926d887c071cc6fbce5ac49a8))\n* make ipfs.get output tarballs ([#3785](https://github.com/ipfs/js-ipfs/issues/3785)) ([1ad6001](https://github.com/ipfs/js-ipfs/commit/1ad60018d39d5b46c484756631e30e1989fd8eba))\n\n\n### BREAKING CHANGES\n\n* rateIn/rateOut are returned as numbers\n* the output type of `ipfs.get` has changed and the `recursive` option has been removed from `ipfs.ls` since it was not supported everywhere\n\n\n\n\n\n## [0.148.0](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.147.0...interface-ipfs-core@0.148.0) (2021-07-27)\n\n\n### Bug Fixes\n\n* fix flaky pubsub test ([#3761](https://github.com/ipfs/js-ipfs/issues/3761)) ([8bcf56f](https://github.com/ipfs/js-ipfs/commit/8bcf56fbec7324dc13d3ec5dce08806a6ef2f974))\n* flaky timeout test ([#3767](https://github.com/ipfs/js-ipfs/issues/3767)) ([55afc2f](https://github.com/ipfs/js-ipfs/commit/55afc2f8ee483f4b2807598b7371561d39229e17))\n* make \"ipfs resolve\" cli command recursive by default ([#3707](https://github.com/ipfs/js-ipfs/issues/3707)) ([399ce36](https://github.com/ipfs/js-ipfs/commit/399ce367a1dbc531b52fe228ee4212008c9a1091)), closes [#3692](https://github.com/ipfs/js-ipfs/issues/3692)\n\n\n### Features\n\n* implement dag import/export ([#3728](https://github.com/ipfs/js-ipfs/issues/3728)) ([700765b](https://github.com/ipfs/js-ipfs/commit/700765be2634fa5d2d71d8b87cf68c9cd328d2c4)), closes [#2953](https://github.com/ipfs/js-ipfs/issues/2953) [#2745](https://github.com/ipfs/js-ipfs/issues/2745)\n* upgrade to the new multiformats ([#3556](https://github.com/ipfs/js-ipfs/issues/3556)) ([d13d15f](https://github.com/ipfs/js-ipfs/commit/d13d15f022a87d04a35f0f7822142f9cb898479c))\n\n\n### BREAKING CHANGES\n\n* resolve is now recursive by default\n\nCo-authored-by: Alex Potsides <alex@achingbrain.net>\n* ipld-formats no longer supported, use multiformat BlockCodecs instead\n\nCo-authored-by: Rod Vagg <rod@vagg.org>\nCo-authored-by: achingbrain <alex@achingbrain.net>\n\n\n\n\n\n## [0.147.0](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.146.1...interface-ipfs-core@0.147.0) (2021-06-18)\n\n\n### Features\n\n* support v2 ipns signatures ([#3708](https://github.com/ipfs/js-ipfs/issues/3708)) ([ade01d1](https://github.com/ipfs/js-ipfs/commit/ade01d138bb185fda902c0a3f7fa14d5bfd48a5e))\n\n\n\n\n\n## [0.146.1](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.146.0...interface-ipfs-core@0.146.1) (2021-06-05)\n\n\n### Bug Fixes\n\n* stalling subscription on (node) http-client when daemon is stopped ([#3468](https://github.com/ipfs/js-ipfs/issues/3468)) ([0266abf](https://github.com/ipfs/js-ipfs/commit/0266abf0c4b817636172f78c6e91eb4dd5aad451)), closes [#3465](https://github.com/ipfs/js-ipfs/issues/3465)\n\n\n\n\n\n## [0.146.0](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.145.1...interface-ipfs-core@0.146.0) (2021-05-26)\n\n\n### Features\n\n* allow passing the id of a network peer to ipfs.id ([#3386](https://github.com/ipfs/js-ipfs/issues/3386)) ([00fd709](https://github.com/ipfs/js-ipfs/commit/00fd709a7b71e7cf354ea452ebce460dd7375d34))\n\n\n\n\n\n## [0.145.1](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.145.0...interface-ipfs-core@0.145.1) (2021-05-11)\n\n\n### Bug Fixes\n\n* ipfs get with raw blocks ([#3683](https://github.com/ipfs/js-ipfs/issues/3683)) ([28235b0](https://github.com/ipfs/js-ipfs/commit/28235b02558c513e1119dfd3d12b622d67546eca)), closes [#3682](https://github.com/ipfs/js-ipfs/issues/3682)\n\n\n\n\n\n## [0.145.0](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.144.2...interface-ipfs-core@0.145.0) (2021-05-10)\n\n\n### Bug Fixes\n\n* mark ipld options as partial ([#3669](https://github.com/ipfs/js-ipfs/issues/3669)) ([f98af8e](https://github.com/ipfs/js-ipfs/commit/f98af8ed24784929898bb5d33a64dc442c77074d))\n* only accept cid for ipfs.dag.get ([#3675](https://github.com/ipfs/js-ipfs/issues/3675)) ([bb8f8bc](https://github.com/ipfs/js-ipfs/commit/bb8f8bc501ffc1ee0f064ba61ec0bca4015bf6ad)), closes [#3637](https://github.com/ipfs/js-ipfs/issues/3637)\n\n\n### chore\n\n* upgrade deps with new typedefs ([#3550](https://github.com/ipfs/js-ipfs/issues/3550)) ([a418a52](https://github.com/ipfs/js-ipfs/commit/a418a521574c878d7aabd0ad2fd8d516908a3756))\n\n\n### Features\n\n* support identity hash in block.get + dag.get ([#3616](https://github.com/ipfs/js-ipfs/issues/3616)) ([28ad9ad](https://github.com/ipfs/js-ipfs/commit/28ad9ad6e50abb89a366ecd6b5301e848f0e9962))\n\n\n### BREAKING CHANGES\n\n* all core api methods now have types, some method signatures have changed, named exports are now used by the http, grpc and ipfs client modules\n\n\n\n\n\n## [0.144.2](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.144.1...interface-ipfs-core@0.144.2) (2021-03-09)\n\n\n### Bug Fixes\n\n* update to new aegir ([#3528](https://github.com/ipfs/js-ipfs/issues/3528)) ([49f7880](https://github.com/ipfs/js-ipfs/commit/49f78807d7e26483bd926b45cc7e0f797d77e41b))\n\n\n\n\n\n## [0.144.1](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.144.0...interface-ipfs-core@0.144.1) (2021-02-08)\n\n**Note:** Version bump only for package interface-ipfs-core\n\n\n\n\n\n## [0.144.0](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.143.1...interface-ipfs-core@0.144.0) (2021-02-01)\n\n\n### Bug Fixes\n\n* updates webpack example to use v5 ([#3512](https://github.com/ipfs/js-ipfs/issues/3512)) ([c7110db](https://github.com/ipfs/js-ipfs/commit/c7110db71b5c0f0f9f415f31f91b5b228341e13e)), closes [#3511](https://github.com/ipfs/js-ipfs/issues/3511)\n\n\n### chore\n\n* update deps ([#3514](https://github.com/ipfs/js-ipfs/issues/3514)) ([061d77c](https://github.com/ipfs/js-ipfs/commit/061d77cc03f40af5a3bc3590481e1e5836e7f0d8))\n\n\n### Features\n\n* support  remote pinning services in ipfs-http-client ([#3293](https://github.com/ipfs/js-ipfs/issues/3293)) ([ba240fd](https://github.com/ipfs/js-ipfs/commit/ba240fdf93edc88028315483240d7822a7ca88ed))\n\n\n### BREAKING CHANGES\n\n* ipfs-repo upgrade requires repo migration to v10\n\n\n\n\n\n## [0.143.1](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.143.0...interface-ipfs-core@0.143.1) (2021-01-20)\n\n**Note:** Version bump only for package interface-ipfs-core\n\n\n\n\n\n## [0.143.0](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.142.3...interface-ipfs-core@0.143.0) (2021-01-15)\n\n\n### chore\n\n* update libp2p to 0.30 ([#3427](https://github.com/ipfs/js-ipfs/issues/3427)) ([a39e6fb](https://github.com/ipfs/js-ipfs/commit/a39e6fb372bf9e7782462b6a4b7530a3f8c9b3f1))\n\n\n### Features\n\n* add grpc server and client ([#3403](https://github.com/ipfs/js-ipfs/issues/3403)) ([a9027e0](https://github.com/ipfs/js-ipfs/commit/a9027e0ec0cea9a4f34b4f2f52e09abb35237384)), closes [#2519](https://github.com/ipfs/js-ipfs/issues/2519) [#2838](https://github.com/ipfs/js-ipfs/issues/2838) [#2943](https://github.com/ipfs/js-ipfs/issues/2943) [#2854](https://github.com/ipfs/js-ipfs/issues/2854) [#2864](https://github.com/ipfs/js-ipfs/issues/2864)\n* allow passing a http.Agent to the grpc client ([#3477](https://github.com/ipfs/js-ipfs/issues/3477)) ([c5f0bc5](https://github.com/ipfs/js-ipfs/commit/c5f0bc5eeee15369b7d02901035b04184a8608d2)), closes [#3474](https://github.com/ipfs/js-ipfs/issues/3474)\n\n\n### BREAKING CHANGES\n\n* The websocket transport will only dial DNS+WSS addresses - see https://github.com/libp2p/js-libp2p-websockets/releases/tag/v0.15.0\n\nCo-authored-by: Hugo Dias <hugomrdias@gmail.com>\n\n\n\n\n\n## [0.142.3](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.142.2...interface-ipfs-core@0.142.3) (2020-12-16)\n\n\n### Bug Fixes\n\n* fix ipfs.ls() for a single file object ([#3440](https://github.com/ipfs/js-ipfs/issues/3440)) ([f243dd1](https://github.com/ipfs/js-ipfs/commit/f243dd1c37fcb9786d77d129cd9b238457d18a15))\n* regressions introduced by new releases of CID & multicodec ([#3442](https://github.com/ipfs/js-ipfs/issues/3442)) ([b5152d8](https://github.com/ipfs/js-ipfs/commit/b5152d8cc93ecc8d39fc353ea66d7eaf1661e3c0)), closes [/github.com/multiformats/js-cid/commit/0e11f035c9230e7f6d79c159ace9b80de88cb5eb#diff-25a6634263c1b1f6fc4697a04e2b9904ea4b042a89af59dc93ec1f5d44848a26](https://github.com//github.com/multiformats/js-cid/commit/0e11f035c9230e7f6d79c159ace9b80de88cb5eb/issues/diff-25a6634263c1b1f6fc4697a04e2b9904ea4b042a89af59dc93ec1f5d44848a26)\n\n\n\n\n\n## [0.142.2](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.142.1...interface-ipfs-core@0.142.2) (2020-11-25)\n\n**Note:** Version bump only for package interface-ipfs-core\n\n\n\n\n\n## [0.142.1](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.142.0...interface-ipfs-core@0.142.1) (2020-11-16)\n\n\n### Bug Fixes\n\n* align behaviour between go and js for content without paths ([#3385](https://github.com/ipfs/js-ipfs/issues/3385)) ([334873d](https://github.com/ipfs/js-ipfs/commit/334873d3784e2baa2b19f8f69b5aade36715ba03))\n* ensure correct progress is reported ([#3384](https://github.com/ipfs/js-ipfs/issues/3384)) ([633d870](https://github.com/ipfs/js-ipfs/commit/633d8704f74534542f54536bc6960528214339a2))\n* report ipfs.add progress over http ([#3310](https://github.com/ipfs/js-ipfs/issues/3310)) ([39cad4b](https://github.com/ipfs/js-ipfs/commit/39cad4b76b950ea6a76477fd01f8631b8bd9aa1e))\n\n\n\n\n\n## [0.142.0](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.141.0...interface-ipfs-core@0.142.0) (2020-11-09)\n\n\n### Features\n\n* pass file name to add/addAll progress handler ([#3372](https://github.com/ipfs/js-ipfs/issues/3372)) ([69681a7](https://github.com/ipfs/js-ipfs/commit/69681a7d7a8434c11f6f10e370e324f5a3d31042)), closes [ipfs/js-ipfs-unixfs#87](https://github.com/ipfs/js-ipfs-unixfs/issues/87)\n\n\n\n\n\n## [0.141.0](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.140.0...interface-ipfs-core@0.141.0) (2020-10-28)\n\n\n### Bug Fixes\n\n* files ls should return string ([#3352](https://github.com/ipfs/js-ipfs/issues/3352)) ([16ecc74](https://github.com/ipfs/js-ipfs/commit/16ecc7485dfbb1f0c827c5f804974bb804f3dafd)), closes [#3345](https://github.com/ipfs/js-ipfs/issues/3345) [#2939](https://github.com/ipfs/js-ipfs/issues/2939) [#3330](https://github.com/ipfs/js-ipfs/issues/3330) [#2948](https://github.com/ipfs/js-ipfs/issues/2948)\n* use fetch in electron renderer and electron-fetch in main ([#3251](https://github.com/ipfs/js-ipfs/issues/3251)) ([639d71f](https://github.com/ipfs/js-ipfs/commit/639d71f7ac8f66d9633e753a2a6be927e14a5af0))\n\n\n### Features\n\n* type check & generate defs from jsdoc ([#3281](https://github.com/ipfs/js-ipfs/issues/3281)) ([bbcaf34](https://github.com/ipfs/js-ipfs/commit/bbcaf34111251b142273a5675f4754ff68bd9fa0))\n\n\n### BREAKING CHANGES\n\n* types returned by `ipfs.files.ls` are now strings, in line with the docs but different to previous behaviour\n\nCo-authored-by: Geoffrey Cohler <g.cohler@computer.org>\n\n\n\n\n\n## [0.140.0](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.139.1...interface-ipfs-core@0.140.0) (2020-09-03)\n\n\n### Bug Fixes\n\n* handle progress for empty files ([#3260](https://github.com/ipfs/js-ipfs/issues/3260)) ([9c36cb8](https://github.com/ipfs/js-ipfs/commit/9c36cb8f0c122e78c3cda3d0769d66c4d380787a)), closes [#3255](https://github.com/ipfs/js-ipfs/issues/3255)\n\n\n### Features\n\n* add protocol list to ipfs id ([#3250](https://github.com/ipfs/js-ipfs/issues/3250)) ([1b6cf60](https://github.com/ipfs/js-ipfs/commit/1b6cf600a6b1348199457ca1fe6f314b6eff8c46))\n* ipns publish example ([#3207](https://github.com/ipfs/js-ipfs/issues/3207)) ([91faec6](https://github.com/ipfs/js-ipfs/commit/91faec6e3d89b0d9883b8d7815c276d44048e739))\n* store pins in datastore instead of a DAG ([#2771](https://github.com/ipfs/js-ipfs/issues/2771)) ([64b7fe4](https://github.com/ipfs/js-ipfs/commit/64b7fe41738cbe96d5a9075f0c01156c6f889c40))\n* update hapi to v20 ([#3245](https://github.com/ipfs/js-ipfs/issues/3245)) ([1aeef89](https://github.com/ipfs/js-ipfs/commit/1aeef89c73f42a2f6cceb7f0598400141ce40e23))\n\n\n\n\n\n## [0.139.1](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.139.0...interface-ipfs-core@0.139.1) (2020-08-24)\n\n\n### Bug Fixes\n\n* validate ipns records with inline public keys ([#3224](https://github.com/ipfs/js-ipfs/issues/3224)) ([5cc0e08](https://github.com/ipfs/js-ipfs/commit/5cc0e086b036e7ba40b09768b67b7067adca43c1))\n\n\n\n\n\n## [0.139.0](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.138.0...interface-ipfs-core@0.139.0) (2020-08-12)\n\n\n### Bug Fixes\n\n* support keychain without pass ([#3212](https://github.com/ipfs/js-ipfs/issues/3212)) ([7e0e85c](https://github.com/ipfs/js-ipfs/commit/7e0e85c2f003a09845b1dbe4200ca61366933b05))\n\n\n### Features\n\n* share IPFS node between browser tabs ([#3081](https://github.com/ipfs/js-ipfs/issues/3081)) ([1b8b1b8](https://github.com/ipfs/js-ipfs/commit/1b8b1b822a252498889c54972a1f57e1fedc39d0)), closes [#3022](https://github.com/ipfs/js-ipfs/issues/3022)\n\n\n### BREAKING CHANGES\n\n* remove support for key.export over the http api\n\n\n\n\n\n## [0.138.0](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.137.0...interface-ipfs-core@0.138.0) (2020-07-16)\n\n\n### Bug Fixes\n\n* optional arguments go in the options object ([#3118](https://github.com/ipfs/js-ipfs/issues/3118)) ([8cb8c73](https://github.com/ipfs/js-ipfs/commit/8cb8c73037e44894d756b70f344b3282463206f9))\n\n\n### Features\n\n* add interface and http client versions to version output ([#3125](https://github.com/ipfs/js-ipfs/issues/3125)) ([65f8b23](https://github.com/ipfs/js-ipfs/commit/65f8b23f550f939e94aaf6939894a513519e6d68)), closes [#2878](https://github.com/ipfs/js-ipfs/issues/2878)\n* store blocks by multihash instead of CID ([#3124](https://github.com/ipfs/js-ipfs/issues/3124)) ([03b17f5](https://github.com/ipfs/js-ipfs/commit/03b17f5e2d290e84aa0cb541079b79e468e7d1bd))\n\n\n\n\n\n## [0.137.0](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.136.0...interface-ipfs-core@0.137.0) (2020-06-24)\n\n\n### Features\n\n* add config.getAll ([#3071](https://github.com/ipfs/js-ipfs/issues/3071)) ([16587f1](https://github.com/ipfs/js-ipfs/commit/16587f16e1b3ae525c099b1975748510638aceee))\n\n\n\n\n\n## [0.136.0](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.135.1...interface-ipfs-core@0.136.0) (2020-06-05)\n\n\n### Features\n\n* sync with go-ipfs 0.5 ([#3013](https://github.com/ipfs/js-ipfs/issues/3013)) ([0900bb9](https://github.com/ipfs/js-ipfs/commit/0900bb9b8123edb689a137a006c5507d8503f693))\n\n\n\n\n\n## [0.135.1](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.135.0...interface-ipfs-core@0.135.1) (2020-05-29)\n\n**Note:** Version bump only for package interface-ipfs-core\n\n\n\n\n\n## [0.135.0](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.134.3...interface-ipfs-core@0.135.0) (2020-05-18)\n\n\n### Bug Fixes\n\n* fixes browser script tag example ([#3034](https://github.com/ipfs/js-ipfs/issues/3034)) ([ee8b769](https://github.com/ipfs/js-ipfs/commit/ee8b769b96f7e3c8414bbf85853ab4e21e8fd11c)), closes [#3027](https://github.com/ipfs/js-ipfs/issues/3027)\n* remove node globals ([#2932](https://github.com/ipfs/js-ipfs/issues/2932)) ([d0d2f74](https://github.com/ipfs/js-ipfs/commit/d0d2f74cef4e439c6d2baadba1f1f9f52534fcba))\n* typeof bug when passing timeout to dag.get ([#3035](https://github.com/ipfs/js-ipfs/issues/3035)) ([026a542](https://github.com/ipfs/js-ipfs/commit/026a5423e00992968840c9236afe47bdab9ee834))\n\n\n### Features\n\n* cancellable api calls ([#2993](https://github.com/ipfs/js-ipfs/issues/2993)) ([2b24f59](https://github.com/ipfs/js-ipfs/commit/2b24f590041a0df9da87b75ae2344232fe22fe3a)), closes [#3015](https://github.com/ipfs/js-ipfs/issues/3015)\n\n\n\n\n\n## [0.134.3](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.134.2...interface-ipfs-core@0.134.3) (2020-05-05)\n\n**Note:** Version bump only for package interface-ipfs-core\n\n\n\n\n\n## [0.134.2](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.134.1...interface-ipfs-core@0.134.2) (2020-05-05)\n\n\n### Bug Fixes\n\n* pass headers to request ([#3018](https://github.com/ipfs/js-ipfs/issues/3018)) ([3ba00f8](https://github.com/ipfs/js-ipfs/commit/3ba00f8c6a8a057c5776d539a671a74d9565fb29)), closes [#3017](https://github.com/ipfs/js-ipfs/issues/3017)\n\n\n\n\n\n## [0.134.1](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.134.0...interface-ipfs-core@0.134.1) (2020-04-28)\n\n\n### Bug Fixes\n\n* fix gc tests ([#3008](https://github.com/ipfs/js-ipfs/issues/3008)) ([9f7f03e](https://github.com/ipfs/js-ipfs/commit/9f7f03e1ea672834b7f984657c7d7d7c768bcd6c))\n\n\n\n\n\n## [0.134.0](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.133.1...interface-ipfs-core@0.134.0) (2020-04-16)\n\n\n### Bug Fixes\n\n* make http api only accept POST requests ([#2977](https://github.com/ipfs/js-ipfs/issues/2977)) ([943d4a8](https://github.com/ipfs/js-ipfs/commit/943d4a8cf2d4c4ff5ecd4814c59cb0aae0cfa1fd))\n* pass timeout arg to server ([#2979](https://github.com/ipfs/js-ipfs/issues/2979)) ([049f085](https://github.com/ipfs/js-ipfs/commit/049f085fd206a1afb729fa825d8df38bf7aa8549))\n\n\n### BREAKING CHANGES\n\n* Where we used to accept all and any HTTP methods, now only POST is\naccepted.  The API client will now only send POST requests too.\n\n* test: add tests to make sure we are post-only\n\n* chore: upgrade ipfs-utils\n\n* fix: return 405 instead of 404 for bad methods\n\n* fix: reject browsers that do not send an origin\n\nAlso fixes running interface tests over http in browsers against\njs-ipfs\n\n\n\n\n\n## [0.133.1](https://github.com/ipfs/js-ipfs/compare/interface-ipfs-core@0.133.0...interface-ipfs-core@0.133.1) (2020-04-08)\n\n**Note:** Version bump only for package interface-ipfs-core\n\n\n\n\n\n# 0.133.0 (2020-03-31)\n\n\n### Bug Fixes\n\n* avoid throw error when use readme code ([#2934](https://github.com/ipfs/js-ipfs/issues/2934)) ([b18f6e1](https://github.com/ipfs/js-ipfs/commit/b18f6e1a791f9c72c9f35ec78c471879bbdc1525))\n* dont include util.textencoder in the browser ([#2919](https://github.com/ipfs/js-ipfs/issues/2919)) ([3207e3b](https://github.com/ipfs/js-ipfs/commit/3207e3b35c9c250332c03dd2a066e8ebcda35e43))\n\n\n### chore\n\n* move mfs and multipart files into core ([#2811](https://github.com/ipfs/js-ipfs/issues/2811)) ([82b9e08](https://github.com/ipfs/js-ipfs/commit/82b9e085330e6c6290e6f3dd29678247984ffdce))\n* update dep version and ignore interop test for raw leaves ([#2747](https://github.com/ipfs/js-ipfs/issues/2747)) ([6376cec](https://github.com/ipfs/js-ipfs/commit/6376cec2b4beccef4751c498088f600ec7788118))\n\n\n### Features\n\n* remove ky from http-client and utils ([#2810](https://github.com/ipfs/js-ipfs/issues/2810)) ([9bc9625](https://github.com/ipfs/js-ipfs/commit/9bc96252686d0bbbfdb2a3300bb17b80eafdaf00)), closes [#2801](https://github.com/ipfs/js-ipfs/issues/2801)\n\n\n### BREAKING CHANGES\n\n* When the path passed to `ipfs.files.stat(path)` was a hamt sharded dir, the resovled\nvalue returned by js-ipfs previously had a `type` property of with a value of\n`'hamt-sharded-directory'`.  To bring it in line with go-ipfs this value is now\n`'directory'`.\n* Files that fit into one block imported with either `--cid-version=1`\nor `--raw-leaves=true` previously returned a CID that resolved to\na raw node (e.g. a buffer). Returned CIDs now resolve to a `dag-pb`\nnode that contains a UnixFS entry. This is to allow setting metadata\non small files with CIDv1.\n\n\n\n\n\n<a name=\"0.132.0\"></a>\n## [0.132.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.131.7...v0.132.0) (2020-02-09)\n\n\n### Bug Fixes\n\n* add object.stat timeout leeway ([#586](https://github.com/ipfs/interface-ipfs-core/issues/586)) ([8b45ad0](https://github.com/ipfs/interface-ipfs-core/commit/8b45ad0))\n\n\n\n<a name=\"0.131.7\"></a>\n## [0.131.7](https://github.com/ipfs/interface-ipfs-core/compare/v0.131.6...v0.131.7) (2020-02-03)\n\n\n### Bug Fixes\n\n* only expect no multiaddrs if node is in-proc webworker ([4e25b4f](https://github.com/ipfs/interface-ipfs-core/commit/4e25b4f))\n\n\n\n<a name=\"0.131.6\"></a>\n## [0.131.6](https://github.com/ipfs/interface-ipfs-core/compare/v0.131.5...v0.131.6) (2020-02-03)\n\n\n### Bug Fixes\n\n* use go for webworker tests ([3a96093](https://github.com/ipfs/interface-ipfs-core/commit/3a96093))\n\n\n\n<a name=\"0.131.5\"></a>\n## [0.131.5](https://github.com/ipfs/interface-ipfs-core/compare/v0.131.4...v0.131.5) (2020-02-03)\n\n\n### Bug Fixes\n\n* do not spawn go nodes with webrtc swarm addresses ([c633d08](https://github.com/ipfs/interface-ipfs-core/commit/c633d08))\n\n\n\n<a name=\"0.131.4\"></a>\n## [0.131.4](https://github.com/ipfs/interface-ipfs-core/compare/v0.131.3...v0.131.4) (2020-02-02)\n\n\n### Bug Fixes\n\n* use js for pubsub tests as before ([ade2145](https://github.com/ipfs/interface-ipfs-core/commit/ade2145))\n\n\n\n<a name=\"0.131.3\"></a>\n## [0.131.3](https://github.com/ipfs/interface-ipfs-core/compare/v0.131.2...v0.131.3) (2020-02-02)\n\n\n### Bug Fixes\n\n* spawn dialable nodes when testing with webworkers ([df7cb3a](https://github.com/ipfs/interface-ipfs-core/commit/df7cb3a))\n\n\n\n<a name=\"0.131.2\"></a>\n## [0.131.2](https://github.com/ipfs/interface-ipfs-core/compare/v0.131.1...v0.131.2) (2020-02-01)\n\n\n### Bug Fixes\n\n* fix swarm peer tests for electron ([ac7cedf](https://github.com/ipfs/interface-ipfs-core/commit/ac7cedf))\n\n\n\n<a name=\"0.131.1\"></a>\n## [0.131.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.131.0...v0.131.1) (2020-01-31)\n\n\n### Bug Fixes\n\n* fix up peer test ([0b80a20](https://github.com/ipfs/interface-ipfs-core/commit/0b80a20))\n\n\n\n<a name=\"0.131.0\"></a>\n## [0.131.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.130.0...v0.131.0) (2020-01-31)\n\n\n### Bug Fixes\n\n* do not assume certain implementations of ipfs are present ([#584](https://github.com/ipfs/interface-ipfs-core/issues/584)) ([3d24911](https://github.com/ipfs/interface-ipfs-core/commit/3d24911))\n\n\n\n<a name=\"0.130.0\"></a>\n## [0.130.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.129.0...v0.130.0) (2020-01-29)\n\n\n### Code Refactoring\n\n* return peer ids as strings ([#581](https://github.com/ipfs/interface-ipfs-core/issues/581)) ([153fd24](https://github.com/ipfs/interface-ipfs-core/commit/153fd24))\n\n\n### BREAKING CHANGES\n\n* Where `PeerID`s were previously [CID]s, now they are Strings\n\n- `ipfs.bitswap.stat().peers[n]` is now a String (was a CID)\n- `ipfs.dht.findPeer().id` is now a String (was a CID)\n- `ipfs.dht.findProvs()[n].id` is now a String (was a CID)\n- `ipfs.dht.provide()[n].id` is now a String (was a CID)\n- `ipfs.dht.put()[n].id` is now a String (was a CID)\n- `ipfs.dht.query()[n].id` is now a String (was a CID)\n- `ipfs.id().id` is now a String (was a CID)\n- `ipfs.id().addresses[n]` are now [Multiaddr]s (were Strings)\n\n[CID]: https://www.npmjs.com/package/cids\n[Multiaddr]: https://www.npmjs.com/package/multiaddr\n\n\n\n<a name=\"0.129.0\"></a>\n## [0.129.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.128.0...v0.129.0) (2020-01-23)\n\n\n\n<a name=\"0.128.0\"></a>\n## [0.128.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.127.0...v0.128.0) (2020-01-22)\n\n\n\n<a name=\"0.127.0\"></a>\n## [0.127.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.126.0...v0.127.0) (2020-01-11)\n\n\n\n<a name=\"0.126.0\"></a>\n## [0.126.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.125.0...v0.126.0) (2020-01-09)\n\n\n\n<a name=\"0.125.0\"></a>\n## [0.125.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.124.1...v0.125.0) (2019-12-11)\n\n\n### Bug Fixes\n\n* handle err on both start and stop echo-server ([#569](https://github.com/ipfs/interface-ipfs-core/issues/569)) ([d25c6f6](https://github.com/ipfs/interface-ipfs-core/commit/d25c6f6))\n\n\n### Features\n\n* add support for new ipfsd-ctl ([#541](https://github.com/ipfs/interface-ipfs-core/issues/541)) ([a27cfa7](https://github.com/ipfs/interface-ipfs-core/commit/a27cfa7))\n\n\n\n## [0.124.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.124.0...v0.124.1) (2019-12-10)\n\n\n\n<a name=\"0.124.0\"></a>\n## [0.124.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.123.0...v0.124.0) (2019-12-02)\n\n\n\n<a name=\"0.123.0\"></a>\n## [0.123.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.122.0...v0.123.0) (2019-11-27)\n\n\n\n<a name=\"0.122.0\"></a>\n## [0.122.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.121.0...v0.122.0) (2019-11-26)\n\n\n\n<a name=\"0.121.0\"></a>\n## [0.121.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.120.0...v0.121.0) (2019-11-19)\n\n\n### Bug Fixes\n\n* allow offline option casing ([#561](https://github.com/ipfs/interface-ipfs-core/issues/561)) ([f08b0fd](https://github.com/ipfs/interface-ipfs-core/commit/f08b0fd))\n\n\n\n<a name=\"0.120.0\"></a>\n## [0.120.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.119.0...v0.120.0) (2019-11-19)\n\n\n### Bug Fixes\n\n* parents option and ls stream flow ([#558](https://github.com/ipfs/interface-ipfs-core/issues/558)) ([b9df5fb](https://github.com/ipfs/interface-ipfs-core/commit/b9df5fb))\n\n\n\n<a name=\"0.119.0\"></a>\n## [0.119.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.118.0...v0.119.0) (2019-11-11)\n\n\n\n<a name=\"0.118.0\"></a>\n## [0.118.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.117.2...v0.118.0) (2019-11-06)\n\n\n### Features\n\n* test ipns resolve of peerid as cid ([#553](https://github.com/ipfs/interface-ipfs-core/issues/553)) ([9193957](https://github.com/ipfs/interface-ipfs-core/commit/9193957))\n\n\n\n## [0.117.2](https://github.com/ipfs/interface-ipfs-core/compare/v0.117.1...v0.117.2) (2019-10-05)\n\n\n\n## [0.117.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.117.0...v0.117.1) (2019-10-05)\n\n\n\n## [0.117.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.116.0...v0.117.0) (2019-10-04)\n\n\n### Documentation\n\n* add dry-run config test and change new/old for original/updated ([e206aa7](https://github.com/ipfs/interface-ipfs-core/commit/e206aa7))\n\n\n### BREAKING CHANGES\n\n* `ipfs.config.profiles.apply` now returns `original`/`updated` keys\nin the diff because using `new` stops us from destructuring in js.\n\n\n\n## [0.116.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.115.3...v0.116.0) (2019-10-04)\n\n\n### Features\n\n* add test for listing config profiles ([142a373](https://github.com/ipfs/interface-ipfs-core/commit/142a373))\n\n\n\n## [0.115.3](https://github.com/ipfs/interface-ipfs-core/compare/v0.115.2...v0.115.3) (2019-10-04)\n\n\n\n## [0.115.2](https://github.com/ipfs/interface-ipfs-core/compare/v0.115.1...v0.115.2) (2019-10-04)\n\n\n### Bug Fixes\n\n* configure chai for use by other modules ([77c8be9](https://github.com/ipfs/interface-ipfs-core/commit/77c8be9))\n* make invalid url actually invalid ([30a84fb](https://github.com/ipfs/interface-ipfs-core/commit/30a84fb))\n* test setting boolean configs keys on boolean fields ([d937fc1](https://github.com/ipfs/interface-ipfs-core/commit/d937fc1))\n\n\n\n<a name=\"0.115.1\"></a>\n## [0.115.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.115.0...v0.115.1) (2019-10-01)\n\n\n\n<a name=\"0.115.0\"></a>\n## [0.115.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.114.0...v0.115.0) (2019-09-25)\n\n\n\n<a name=\"0.114.0\"></a>\n## [0.114.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.113.1...v0.114.0) (2019-09-16)\n\n\n### Bug Fixes\n\n* change swarm test ([00341f9](https://github.com/ipfs/interface-ipfs-core/commit/00341f9))\n* new setup ([b724e65](https://github.com/ipfs/interface-ipfs-core/commit/b724e65))\n\n\n\n<a name=\"0.113.1\"></a>\n## [0.113.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.113.0...v0.113.1) (2019-09-13)\n\n\n### Bug Fixes\n\n* make pubsub unsubscribe tests work in electron renderer ([eedfe3d](https://github.com/ipfs/interface-ipfs-core/commit/eedfe3d))\n\n\n\n<a name=\"0.113.0\"></a>\n## [0.113.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.112.0...v0.113.0) (2019-09-05)\n\n\n### Bug Fixes\n\n* **package:** update ipfs-utils to version 0.1.0 ([#521](https://github.com/ipfs/interface-ipfs-core/issues/521)) ([56caa89](https://github.com/ipfs/interface-ipfs-core/commit/56caa89))\n\n\n\n<a name=\"0.112.0\"></a>\n## [0.112.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.111.1...v0.112.0) (2019-09-03)\n\n\n### Bug Fixes\n\n* supported add inputs ([#519](https://github.com/ipfs/interface-ipfs-core/issues/519)) ([ddc4fe7](https://github.com/ipfs/interface-ipfs-core/commit/ddc4fe7))\n\n\n\n<a name=\"0.111.1\"></a>\n## [0.111.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.111.0...v0.111.1) (2019-08-30)\n\n\n### Bug Fixes\n\n* change `cp` and `mv` tests to the current spec ([#515](https://github.com/ipfs/interface-ipfs-core/issues/515)) ([b107e57](https://github.com/ipfs/interface-ipfs-core/commit/b107e57))\n\n\n\n<a name=\"0.111.0\"></a>\n## [0.111.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.110.0...v0.111.0) (2019-08-28)\n\n\n\n<a name=\"0.110.0\"></a>\n## [0.110.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.109.1...v0.110.0) (2019-08-27)\n\n\n### Bug Fixes\n\n* reduce the number of concurrent requests in browser ([#505](https://github.com/ipfs/interface-ipfs-core/issues/505)) ([7596634](https://github.com/ipfs/interface-ipfs-core/commit/7596634))\n\n\n\n<a name=\"0.109.1\"></a>\n## [0.109.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.109.0...v0.109.1) (2019-08-06)\n\n\n\n<a name=\"0.109.0\"></a>\n## [0.109.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.108.1...v0.109.0) (2019-07-26)\n\n\n### Bug Fixes\n\n* resolve IPNS recursively test ([#507](https://github.com/ipfs/interface-ipfs-core/issues/507)) ([1db8abe](https://github.com/ipfs/interface-ipfs-core/commit/1db8abe))\n\n\n\n<a name=\"0.108.1\"></a>\n## [0.108.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.108.0...v0.108.1) (2019-07-25)\n\n\n### Bug Fixes\n\n* reword resolve test with async/await ([#504](https://github.com/ipfs/interface-ipfs-core/issues/504)) ([3f7410a](https://github.com/ipfs/interface-ipfs-core/commit/3f7410a))\n* use the correct option name for files.ls long ([#502](https://github.com/ipfs/interface-ipfs-core/issues/502)) ([ed4988d](https://github.com/ipfs/interface-ipfs-core/commit/ed4988d))\n\n\n\n<a name=\"0.108.0\"></a>\n## [0.108.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.107.3...v0.108.0) (2019-07-17)\n\n\n### Features\n\n* tests for config profile endpoint ([#488](https://github.com/ipfs/interface-ipfs-core/issues/488)) ([e45f39c](https://github.com/ipfs/interface-ipfs-core/commit/e45f39c))\n\n\n\n<a name=\"0.107.3\"></a>\n## [0.107.3](https://github.com/ipfs/interface-ipfs-core/compare/v0.107.2...v0.107.3) (2019-07-16)\n\n\n\n<a name=\"0.107.2\"></a>\n## [0.107.2](https://github.com/ipfs/interface-ipfs-core/compare/v0.107.1...v0.107.2) (2019-07-16)\n\n\n### Bug Fixes\n\n* pin.ls ignored opts when hash was present ([#375](https://github.com/ipfs/interface-ipfs-core/issues/375)) ([be72ed6](https://github.com/ipfs/interface-ipfs-core/commit/be72ed6))\n\n\n\n<a name=\"0.107.1\"></a>\n## [0.107.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.107.0...v0.107.1) (2019-07-11)\n\n\n\n<a name=\"0.107.0\"></a>\n## [0.107.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.106.0...v0.107.0) (2019-07-11)\n\n\n### Bug Fixes\n\n* repo.gc() response format ([#492](https://github.com/ipfs/interface-ipfs-core/issues/492)) ([a2ec3f6](https://github.com/ipfs/interface-ipfs-core/commit/a2ec3f6))\n\n\n\n<a name=\"0.106.0\"></a>\n## [0.106.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.105.1...v0.106.0) (2019-07-05)\n\n\n\n<a name=\"0.105.1\"></a>\n## [0.105.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.105.0...v0.105.1) (2019-07-03)\n\n\n### Bug Fixes\n\n* wait for one key to be the required key not all ([#490](https://github.com/ipfs/interface-ipfs-core/issues/490)) ([acea55f](https://github.com/ipfs/interface-ipfs-core/commit/acea55f))\n\n\n\n<a name=\"0.105.0\"></a>\n## [0.105.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.104.2...v0.105.0) (2019-06-20)\n\n\n### Features\n\n* add support to resolve dns to ipns ([#458](https://github.com/ipfs/interface-ipfs-core/issues/458)) ([cd41a3c](https://github.com/ipfs/interface-ipfs-core/commit/cd41a3c))\n\n\n\n<a name=\"0.104.2\"></a>\n## [0.104.2](https://github.com/ipfs/interface-ipfs-core/compare/v0.104.1...v0.104.2) (2019-05-31)\n\n\n\n<a name=\"0.104.1\"></a>\n## [0.104.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.104.0...v0.104.1) (2019-05-31)\n\n\n### Bug Fixes\n\n* dht tests ([#486](https://github.com/ipfs/interface-ipfs-core/issues/486)) ([2952672](https://github.com/ipfs/interface-ipfs-core/commit/2952672))\n* use cidVersion option ([#484](https://github.com/ipfs/interface-ipfs-core/issues/484)) ([e00eb4a](https://github.com/ipfs/interface-ipfs-core/commit/e00eb4a))\n* **package:** update async to version 3.0.1 ([#481](https://github.com/ipfs/interface-ipfs-core/issues/481)) ([b60fe33](https://github.com/ipfs/interface-ipfs-core/commit/b60fe33))\n\n\n\n<a name=\"0.104.0\"></a>\n## [0.104.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.103.0...v0.104.0) (2019-05-24)\n\n\n\n<a name=\"0.103.0\"></a>\n## [0.103.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.102.0...v0.103.0) (2019-05-21)\n\n\n\n<a name=\"0.102.0\"></a>\n## [0.102.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.101.1...v0.102.0) (2019-05-16)\n\n\n### Features\n\n* add tests for add data using File DOM api ([#461](https://github.com/ipfs/interface-ipfs-core/issues/461)) ([86a1f3f](https://github.com/ipfs/interface-ipfs-core/commit/86a1f3f))\n\n\n\n<a name=\"0.101.1\"></a>\n## [0.101.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.101.0...v0.101.1) (2019-05-16)\n\n\n### Bug Fixes\n\n* use fixtures for refs tests ([#471](https://github.com/ipfs/interface-ipfs-core/issues/471)) ([3f30830](https://github.com/ipfs/interface-ipfs-core/commit/3f30830))\n\n\n\n<a name=\"0.101.0\"></a>\n## [0.101.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.100.1...v0.101.0) (2019-05-15)\n\n\n\n<a name=\"0.100.1\"></a>\n## [0.100.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.100.0...v0.100.1) (2019-05-13)\n\n\n### Reverts\n\n* add test for Object.links with CBOR ([#465](https://github.com/ipfs/interface-ipfs-core/issues/465)) ([4c3d84d](https://github.com/ipfs/interface-ipfs-core/commit/4c3d84d))\n\n\n\n<a name=\"0.100.0\"></a>\n## [0.100.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.99.2...v0.100.0) (2019-05-08)\n\n\n\n<a name=\"0.99.2\"></a>\n## [0.99.2](https://github.com/ipfs/interface-ipfs-core/compare/v0.99.1...v0.99.2) (2019-04-08)\n\n\n\n<a name=\"0.99.1\"></a>\n## [0.99.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.99.0...v0.99.1) (2019-04-04)\n\n\n### Bug Fixes\n\n* swarm addrs test ([#454](https://github.com/ipfs/interface-ipfs-core/issues/454)) ([16ad830](https://github.com/ipfs/interface-ipfs-core/commit/16ad830))\n\n\n\n<a name=\"0.99.0\"></a>\n## [0.99.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.98.1...v0.99.0) (2019-03-13)\n\n\n### Bug Fixes\n\n* don't expect ipfs to preserve a leading slash ([#440](https://github.com/ipfs/interface-ipfs-core/issues/440)) ([d3ad40b](https://github.com/ipfs/interface-ipfs-core/commit/d3ad40b))\n* ls files sizes for compat with go-ipfs 0.4.19 ([#449](https://github.com/ipfs/interface-ipfs-core/issues/449)) ([2ef1480](https://github.com/ipfs/interface-ipfs-core/commit/2ef1480)), closes [#427](https://github.com/ipfs/interface-ipfs-core/issues/427)\n\n\n\n<a name=\"0.98.1\"></a>\n## [0.98.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.98.0...v0.98.1) (2019-03-13)\n\n\n\n<a name=\"0.98.0\"></a>\n## [0.98.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.97.1...v0.98.0) (2019-02-26)\n\n\n\n<a name=\"0.97.1\"></a>\n## [0.97.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.97.0...v0.97.1) (2019-02-19)\n\n\n### Bug Fixes\n\n* populate in series ([#443](https://github.com/ipfs/interface-ipfs-core/issues/443)) ([06a3980](https://github.com/ipfs/interface-ipfs-core/commit/06a3980))\n\n\n\n<a name=\"0.97.0\"></a>\n## [0.97.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.96.1...v0.97.0) (2019-02-19)\n\n\n### Bug Fixes\n\n* add new SSL certificate ([#432](https://github.com/ipfs/interface-ipfs-core/issues/432)) ([fe539e6](https://github.com/ipfs/interface-ipfs-core/commit/fe539e6))\n* add test for dag get with localResolve option ([#433](https://github.com/ipfs/interface-ipfs-core/issues/433)) ([44d4803](https://github.com/ipfs/interface-ipfs-core/commit/44d4803))\n\n\n\n<a name=\"0.96.1\"></a>\n## [0.96.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.96.0...v0.96.1) (2019-01-15)\n\n\n\n<a name=\"0.96.0\"></a>\n## [0.96.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.95.0...v0.96.0) (2019-01-14)\n\n\n\n<a name=\"0.95.0\"></a>\n## [0.95.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.94.0...v0.95.0) (2019-01-04)\n\n\n\n<a name=\"0.94.0\"></a>\n## [0.94.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.93.0...v0.94.0) (2018-12-16)\n\n\n\n<a name=\"0.93.0\"></a>\n## [0.93.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.92.0...v0.93.0) (2018-12-14)\n\n\n### Bug Fixes\n\n* allow only by object ([#407](https://github.com/ipfs/interface-ipfs-core/issues/407)) ([1766ef4](https://github.com/ipfs/interface-ipfs-core/commit/1766ef4))\n* dht find peer ([#418](https://github.com/ipfs/interface-ipfs-core/issues/418)) ([8b890b6](https://github.com/ipfs/interface-ipfs-core/commit/8b890b6))\n\n\n\n<a name=\"0.92.0\"></a>\n## [0.92.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.91.1...v0.92.0) (2018-12-12)\n\n\n### Bug Fixes\n\n* addFromURL case ([#415](https://github.com/ipfs/interface-ipfs-core/issues/415)) ([f54422d](https://github.com/ipfs/interface-ipfs-core/commit/f54422d))\n\n\n\n<a name=\"0.91.1\"></a>\n## [0.91.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.91.0...v0.91.1) (2018-12-11)\n\n\n### Bug Fixes\n\n* change find provs options test ([#416](https://github.com/ipfs/interface-ipfs-core/issues/416)) ([3c08aa2](https://github.com/ipfs/interface-ipfs-core/commit/3c08aa2))\n\n\n\n<a name=\"0.91.0\"></a>\n## [0.91.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.90.0...v0.91.0) (2018-12-10)\n\n\n### Bug Fixes\n\n* another typo ([87bcd68](https://github.com/ipfs/interface-ipfs-core/commit/87bcd68))\n* typos ([e7b8697](https://github.com/ipfs/interface-ipfs-core/commit/e7b8697))\n* update dht responses ([#389](https://github.com/ipfs/interface-ipfs-core/issues/389)) ([c4bea6f](https://github.com/ipfs/interface-ipfs-core/commit/c4bea6f))\n* Updated link in README ([#411](https://github.com/ipfs/interface-ipfs-core/issues/411)) ([81a5798](https://github.com/ipfs/interface-ipfs-core/commit/81a5798))\n\n\n\n<a name=\"0.90.0\"></a>\n## [0.90.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.89.0...v0.90.0) (2018-12-05)\n\n\n\n<a name=\"0.89.0\"></a>\n## [0.89.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.88.0...v0.89.0) (2018-12-03)\n\n\n### Bug Fixes\n\n* code blocks for the code ([36cf442](https://github.com/ipfs/interface-ipfs-core/commit/36cf442))\n* ipns over pubsub tests ([#395](https://github.com/ipfs/interface-ipfs-core/issues/395)) ([e872b8a](https://github.com/ipfs/interface-ipfs-core/commit/e872b8a))\n\n\n\n<a name=\"0.88.0\"></a>\n## [0.88.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.87.0...v0.88.0) (2018-11-27)\n\n\n\n<a name=\"0.87.0\"></a>\n## [0.87.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.86.0...v0.87.0) (2018-11-26)\n\n\n\n<a name=\"0.86.0\"></a>\n## [0.86.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.85.0...v0.86.0) (2018-11-12)\n\n\n### Features\n\n* move regular files api to top level, add addFromFs and addFromURL ([#378](https://github.com/ipfs/interface-ipfs-core/issues/378)) ([3dc7278](https://github.com/ipfs/interface-ipfs-core/commit/3dc7278))\n\n\n\n<a name=\"0.85.0\"></a>\n## [0.85.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.84.3...v0.85.0) (2018-11-12)\n\n\n### Bug Fixes\n\n* updates ipld-dag-pb dep to version without .cid properties ([#388](https://github.com/ipfs/interface-ipfs-core/issues/388)) ([b8f7b9a](https://github.com/ipfs/interface-ipfs-core/commit/b8f7b9a))\n\n\n\n<a name=\"0.84.3\"></a>\n## [0.84.3](https://github.com/ipfs/interface-ipfs-core/compare/v0.84.2...v0.84.3) (2018-10-31)\n\n\n### Bug Fixes\n\n* we cant rely on error messages yet, not standardized ([fdb4998](https://github.com/ipfs/interface-ipfs-core/commit/fdb4998))\n\n\n\n<a name=\"0.84.2\"></a>\n## [0.84.2](https://github.com/ipfs/interface-ipfs-core/compare/v0.84.1...v0.84.2) (2018-10-31)\n\n\n\n<a name=\"0.84.1\"></a>\n## [0.84.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.84.0...v0.84.1) (2018-10-31)\n\n\n\n<a name=\"0.84.0\"></a>\n## [0.84.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.83.0...v0.84.0) (2018-10-31)\n\n\n### Bug Fixes\n\n* ping tests ([cd00d5d](https://github.com/ipfs/interface-ipfs-core/commit/cd00d5d))\n* remove antipattern from ping tests ([2e822b6](https://github.com/ipfs/interface-ipfs-core/commit/2e822b6))\n\n\n\n<a name=\"0.83.0\"></a>\n## [0.83.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.82.0...v0.83.0) (2018-10-30)\n\n\n\n<a name=\"0.82.0\"></a>\n## [0.82.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.81.0...v0.82.0) (2018-10-30)\n\n\n\n<a name=\"0.81.0\"></a>\n## [0.81.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.80.0...v0.81.0) (2018-10-29)\n\n\n\n<a name=\"0.80.0\"></a>\n## [0.80.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.79.0...v0.80.0) (2018-10-18)\n\n\n\n<a name=\"0.79.0\"></a>\n## [0.79.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.78.0...v0.79.0) (2018-10-15)\n\n\n### Bug Fixes\n\n* dht find peer and providers ([#368](https://github.com/ipfs/interface-ipfs-core/issues/368)) ([40f796f](https://github.com/ipfs/interface-ipfs-core/commit/40f796f))\n\n\n\n<a name=\"0.78.0\"></a>\n## [0.78.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.77.1...v0.78.0) (2018-09-20)\n\n\n### Bug Fixes\n\n* example links in miscellaneous spec section ([#364](https://github.com/ipfs/interface-ipfs-core/issues/364)) ([45e8142](https://github.com/ipfs/interface-ipfs-core/commit/45e8142))\n* test for buffer with options ([#370](https://github.com/ipfs/interface-ipfs-core/issues/370)) ([d456245](https://github.com/ipfs/interface-ipfs-core/commit/d456245))\n\n\n\n<a name=\"0.77.1\"></a>\n## [0.77.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.77.0...v0.77.1) (2018-09-05)\n\n\n### Bug Fixes\n\n* bitswap.stat docs ([#355](https://github.com/ipfs/interface-ipfs-core/issues/355)) ([f146e1b](https://github.com/ipfs/interface-ipfs-core/commit/f146e1b))\n* block CID links ([#356](https://github.com/ipfs/interface-ipfs-core/issues/356)) ([9c4d6e1](https://github.com/ipfs/interface-ipfs-core/commit/9c4d6e1))\n* block stat return value key ([1e02740](https://github.com/ipfs/interface-ipfs-core/commit/1e02740))\n* ipfs.io now should be resolved recursively ([#362](https://github.com/ipfs/interface-ipfs-core/issues/362)) ([d80d3a3](https://github.com/ipfs/interface-ipfs-core/commit/d80d3a3))\n\n\n\n<a name=\"0.77.0\"></a>\n## [0.77.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.76.1...v0.77.0) (2018-08-28)\n\n\n### Bug Fixes\n\n* remove bitswap.unwant ([#353](https://github.com/ipfs/interface-ipfs-core/issues/353)) ([6065f63](https://github.com/ipfs/interface-ipfs-core/commit/6065f63)), closes [#339](https://github.com/ipfs/interface-ipfs-core/issues/339)\n\n\n\n<a name=\"0.76.1\"></a>\n## [0.76.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.76.0...v0.76.1) (2018-08-16)\n\n\n### Bug Fixes\n\n* allow retries for DNS test due to dependence on external services ([#352](https://github.com/ipfs/interface-ipfs-core/issues/352)) ([5b3f5a8](https://github.com/ipfs/interface-ipfs-core/commit/5b3f5a8))\n* typo ([b9dc12a](https://github.com/ipfs/interface-ipfs-core/commit/b9dc12a))\n* typo ([2fbf551](https://github.com/ipfs/interface-ipfs-core/commit/2fbf551))\n\n\n\n<a name=\"0.76.0\"></a>\n## [0.76.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.75.2...v0.76.0) (2018-08-10)\n\n\n### Features\n\n* ipns working locally ([#327](https://github.com/ipfs/interface-ipfs-core/issues/327)) ([49a4827](https://github.com/ipfs/interface-ipfs-core/commit/49a4827))\n\n\n\n<a name=\"0.75.2\"></a>\n## [0.75.2](https://github.com/ipfs/interface-ipfs-core/compare/v0.75.1...v0.75.2) (2018-08-09)\n\n\n### Bug Fixes\n\n* **spec/dag:** fix wrong example output for sha3-512 hash algorithm ([#347](https://github.com/ipfs/interface-ipfs-core/issues/347)) ([bfdda8a](https://github.com/ipfs/interface-ipfs-core/commit/bfdda8a)), closes [#307](https://github.com/ipfs/interface-ipfs-core/issues/307)\n* update error messages in line with go ([#348](https://github.com/ipfs/interface-ipfs-core/issues/348)) ([a173a42](https://github.com/ipfs/interface-ipfs-core/commit/a173a42))\n\n\n\n<a name=\"0.75.1\"></a>\n## [0.75.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.75.0...v0.75.1) (2018-08-06)\n\n\n### Bug Fixes\n\n* ensure test for resolve recursive has another node ([#346](https://github.com/ipfs/interface-ipfs-core/issues/346)) ([09c2637](https://github.com/ipfs/interface-ipfs-core/commit/09c2637))\n\n\n\n<a name=\"0.75.0\"></a>\n## [0.75.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.74.1...v0.75.0) (2018-08-06)\n\n\n### Bug Fixes\n\n* expect config to be an object ([#344](https://github.com/ipfs/interface-ipfs-core/issues/344)) ([eca00b9](https://github.com/ipfs/interface-ipfs-core/commit/eca00b9))\n* more time for CI to resolve recursively ([79b747e](https://github.com/ipfs/interface-ipfs-core/commit/79b747e))\n\n\n\n<a name=\"0.74.1\"></a>\n## [0.74.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.74.0...v0.74.1) (2018-08-06)\n\n\n### Bug Fixes\n\n* give more time for teardown after resolve ([#345](https://github.com/ipfs/interface-ipfs-core/issues/345)) ([1db498f](https://github.com/ipfs/interface-ipfs-core/commit/1db498f))\n\n\n\n<a name=\"0.74.0\"></a>\n## [0.74.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.73.0...v0.74.0) (2018-08-02)\n\n\n### Features\n\n* **dht:** add API to allow options in `findprovs()` ([#337](https://github.com/ipfs/interface-ipfs-core/issues/337)) ([99f74f5](https://github.com/ipfs/interface-ipfs-core/commit/99f74f5)), closes [/github.com/ipfs/js-ipfs/issues/1322#issuecomment-385336102](https://github.com//github.com/ipfs/js-ipfs/issues/1322/issues/issuecomment-385336102)\n\n\n\n<a name=\"0.73.0\"></a>\n## [0.73.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.72.1...v0.73.0) (2018-08-02)\n\n\n\n<a name=\"0.72.1\"></a>\n## [0.72.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.72.0...v0.72.1) (2018-07-16)\n\n\n### Bug Fixes\n\n* unsubscribe in series for go-ipfs ([#326](https://github.com/ipfs/interface-ipfs-core/issues/326)) ([8e487da](https://github.com/ipfs/interface-ipfs-core/commit/8e487da))\n\n\n\n<a name=\"0.72.0\"></a>\n## [0.72.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.71.0...v0.72.0) (2018-07-05)\n\n\n\n<a name=\"0.71.0\"></a>\n## [0.71.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.70.3...v0.71.0) (2018-07-03)\n\n\n### Bug Fixes\n\n* revert to serialized pubsub operations ([#319](https://github.com/ipfs/interface-ipfs-core/issues/319)) ([4b5534e](https://github.com/ipfs/interface-ipfs-core/commit/4b5534e))\n\n\n\n<a name=\"0.70.3\"></a>\n## [0.70.3](https://github.com/ipfs/interface-ipfs-core/compare/v0.70.2...v0.70.3) (2018-07-03)\n\n\n### Bug Fixes\n\n* allow passing only to suites with skip lists ([#321](https://github.com/ipfs/interface-ipfs-core/issues/321)) ([c47c4ce](https://github.com/ipfs/interface-ipfs-core/commit/c47c4ce))\n* allow skip with object but no reason ([#318](https://github.com/ipfs/interface-ipfs-core/issues/318)) ([ef91026](https://github.com/ipfs/interface-ipfs-core/commit/ef91026))\n* license ([#312](https://github.com/ipfs/interface-ipfs-core/issues/312)) ([8fa3e98](https://github.com/ipfs/interface-ipfs-core/commit/8fa3e98))\n\n\n\n<a name=\"0.70.2\"></a>\n## [0.70.2](https://github.com/ipfs/interface-ipfs-core/compare/v0.70.1...v0.70.2) (2018-06-29)\n\n\n\n<a name=\"0.70.1\"></a>\n## [0.70.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.70.0...v0.70.1) (2018-06-27)\n\n\n### Bug Fixes\n\n* allow null skip for subsystems ([5df855c](https://github.com/ipfs/interface-ipfs-core/commit/5df855c))\n\n\n\n<a name=\"0.70.0\"></a>\n## [0.70.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.69.1...v0.70.0) (2018-06-27)\n\n\n### Features\n\n* modularise tests by command, add tools to skip and only ([#290](https://github.com/ipfs/interface-ipfs-core/issues/290)) ([e232d8c](https://github.com/ipfs/interface-ipfs-core/commit/e232d8c))\n\n\n### BREAKING CHANGES\n\n* Consumers of this test suite now have fine grained control over what tests are run. Tests can now be skipped and \"onlyed\" (run only specific tests). This can be done on a test, command and sub-system level. See the updated usage guide for instructions: https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/README.md#usage.\n\nThis means that tests skips depending on implementation (e.g. go/js), environment (e.g. node/browser) or platform (e.g. macOS/linux/windows) that were previously present in this suite have been removed. Consumers of this library should add their own skips based on the implementation that's being tested and the environment/platform that the tests are running on.\n\nThe following other breaking changes have been made:\n\n1. The common object passed to test suites has changed. It must now be a function that returns a common object (same shape and functions as before).\n2. The `ipfs.ls` tests (not MFS `ipfs.files.ls`) is now a root level suite. You'll need to import it and use like `tests.ls(createCommon)` to have those tests run.\n3. The `generic` suite (an alias to `miscellaneous`) has been removed.\n\nSee https://github.com/ipfs/interface-ipfs-core/pull/290 for more details.\n\nLicense: MIT\nSigned-off-by: Alan Shaw <alan@tableflip.io>\n\n\n\n<a name=\"0.69.1\"></a>\n## [0.69.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.69.0...v0.69.1) (2018-06-26)\n\n\n### Bug Fixes\n\n* do not rely on discovery for ping tests ([3acd6fd](https://github.com/ipfs/interface-ipfs-core/commit/3acd6fd)), closes [#310](https://github.com/ipfs/interface-ipfs-core/issues/310)\n\n\n\n<a name=\"0.69.0\"></a>\n## [0.69.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.68.2...v0.69.0) (2018-06-22)\n\n\n\n<a name=\"0.68.2\"></a>\n## [0.68.2](https://github.com/ipfs/interface-ipfs-core/compare/v0.68.1...v0.68.2) (2018-06-19)\n\n\n### Bug Fixes\n\n* increase bitswap setup timeout for CI ([5886445](https://github.com/ipfs/interface-ipfs-core/commit/5886445))\n\n\n\n<a name=\"0.68.1\"></a>\n## [0.68.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.68.0...v0.68.1) (2018-06-18)\n\n\n### Bug Fixes\n\n* removes error code checks for bitswap offline tests ([b152856](https://github.com/ipfs/interface-ipfs-core/commit/b152856))\n\n\n\n<a name=\"0.68.0\"></a>\n## [0.68.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.67.0...v0.68.0) (2018-06-18)\n\n\n### Bug Fixes\n\n* improve bitswap wantlist and unwant docs ([7737546](https://github.com/ipfs/interface-ipfs-core/commit/7737546))\n* linting errors ([fcc834c](https://github.com/ipfs/interface-ipfs-core/commit/fcc834c))\n* removes duplicated TOC for pubsub ([a358cf7](https://github.com/ipfs/interface-ipfs-core/commit/a358cf7))\n\n\n### Features\n\n* add bitswap.unwant javascript spec ([df4e677](https://github.com/ipfs/interface-ipfs-core/commit/df4e677))\n* add bitswap.unwant javascript spec ([d75a361](https://github.com/ipfs/interface-ipfs-core/commit/d75a361))\n* add bitswap.unwant javascript spec ([c291ca9](https://github.com/ipfs/interface-ipfs-core/commit/c291ca9))\n* add peerId param to bitswap.wantlist ([9f81bcb](https://github.com/ipfs/interface-ipfs-core/commit/9f81bcb))\n\n\n\n<a name=\"0.67.0\"></a>\n## [0.67.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.66.4...v0.67.0) (2018-06-04)\n\n\n\n<a name=\"0.66.4\"></a>\n## [0.66.4](https://github.com/ipfs/interface-ipfs-core/compare/v0.66.3...v0.66.4) (2018-05-30)\n\n\n### Bug Fixes\n\n* wait for put in object.patch.addLink before hook ([31c52d1](https://github.com/ipfs/interface-ipfs-core/commit/31c52d1))\n\n\n\n<a name=\"0.66.3\"></a>\n## [0.66.3](https://github.com/ipfs/interface-ipfs-core/compare/v0.66.2...v0.66.3) (2018-05-25)\n\n\n### Bug Fixes\n\n* correctly differentiate pong responses ([688f4d7](https://github.com/ipfs/interface-ipfs-core/commit/688f4d7))\n\n\n\n<a name=\"0.66.2\"></a>\n## [0.66.2](https://github.com/ipfs/interface-ipfs-core/compare/v0.66.1...v0.66.2) (2018-05-18)\n\n\n### Bug Fixes\n\n* spawn in series ([d976699](https://github.com/ipfs/interface-ipfs-core/commit/d976699))\n\n\n\n<a name=\"0.66.1\"></a>\n## [0.66.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.66.0...v0.66.1) (2018-05-17)\n\n\n### Bug Fixes\n\n* increase timeouts ([9cba111](https://github.com/ipfs/interface-ipfs-core/commit/9cba111))\n* remove .only ([45fab1c](https://github.com/ipfs/interface-ipfs-core/commit/45fab1c))\n* wait until nodes are connected before starting ping tests ([1b60f24](https://github.com/ipfs/interface-ipfs-core/commit/1b60f24))\n* **pubsub:** clear interval on error ([d074e13](https://github.com/ipfs/interface-ipfs-core/commit/d074e13))\n\n\n\n<a name=\"0.66.0\"></a>\n## [0.66.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.65.9...v0.66.0) (2018-05-16)\n\n\n\n<a name=\"0.65.9\"></a>\n## [0.65.9](https://github.com/ipfs/interface-ipfs-core/compare/v0.65.8...v0.65.9) (2018-05-16)\n\n\n### Bug Fixes\n\n* add \"files.\" to read* headers ([8b39b12](https://github.com/ipfs/interface-ipfs-core/commit/8b39b12))\n* linting warnings ([aae31b0](https://github.com/ipfs/interface-ipfs-core/commit/aae31b0))\n\n\n### Features\n\n* add utils to spawn multiple nodes and get their ID ([e77a2f6](https://github.com/ipfs/interface-ipfs-core/commit/e77a2f6))\n\n\n\n<a name=\"0.65.8\"></a>\n## [0.65.8](https://github.com/ipfs/interface-ipfs-core/compare/v0.65.7...v0.65.8) (2018-05-15)\n\n\n\n<a name=\"0.65.7\"></a>\n## [0.65.7](https://github.com/ipfs/interface-ipfs-core/compare/v0.65.6...v0.65.7) (2018-05-15)\n\n\n\n<a name=\"0.65.6\"></a>\n## [0.65.6](https://github.com/ipfs/interface-ipfs-core/compare/v0.65.5...v0.65.6) (2018-05-15)\n\n\n\n<a name=\"0.65.5\"></a>\n## [0.65.5](https://github.com/ipfs/interface-ipfs-core/compare/v0.65.4...v0.65.5) (2018-05-12)\n\n\n\n<a name=\"0.65.4\"></a>\n## [0.65.4](https://github.com/ipfs/interface-ipfs-core/compare/v0.65.3...v0.65.4) (2018-05-11)\n\n\n\n<a name=\"0.65.3\"></a>\n## [0.65.3](https://github.com/ipfs/interface-ipfs-core/compare/v0.65.2...v0.65.3) (2018-05-11)\n\n\n\n<a name=\"0.65.2\"></a>\n## [0.65.2](https://github.com/ipfs/interface-ipfs-core/compare/v0.65.1...v0.65.2) (2018-05-11)\n\n\n\n<a name=\"0.65.1\"></a>\n## [0.65.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.65.0...v0.65.1) (2018-05-11)\n\n\n\n<a name=\"0.65.0\"></a>\n## [0.65.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.64.3...v0.65.0) (2018-05-11)\n\n\n### Bug Fixes\n\n* many fixes for pubsub tests with new async unsubscribe ([2019c45](https://github.com/ipfs/interface-ipfs-core/commit/2019c45))\n* pubsub subscribe call with options ([c43f8bc](https://github.com/ipfs/interface-ipfs-core/commit/c43f8bc))\n* remove .only ([251cffd](https://github.com/ipfs/interface-ipfs-core/commit/251cffd))\n* remove duplicate async.each ([f798597](https://github.com/ipfs/interface-ipfs-core/commit/f798597))\n\n\n\n<a name=\"0.64.3\"></a>\n## [0.64.3](https://github.com/ipfs/interface-ipfs-core/compare/v0.64.2...v0.64.3) (2018-05-06)\n\n\n### Bug Fixes\n\n* Typos on bundled libraries pull request ([2972426](https://github.com/ipfs/interface-ipfs-core/commit/2972426))\n\n\n### Features\n\n* add onlyHash option to files.add ([#259](https://github.com/ipfs/interface-ipfs-core/issues/259)) ([63179b9](https://github.com/ipfs/interface-ipfs-core/commit/63179b9))\n\n\n### Performance Improvements\n\n* **pubsub:** Change pubsub tests to do lighter load testing ([90a1520](https://github.com/ipfs/interface-ipfs-core/commit/90a1520))\n\n\n\n<a name=\"0.64.2\"></a>\n## [0.64.2](https://github.com/ipfs/interface-ipfs-core/compare/v0.64.1...v0.64.2) (2018-04-23)\n\n\n\n<a name=\"0.64.1\"></a>\n## [0.64.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.64.0...v0.64.1) (2018-04-23)\n\n\n### Bug Fixes\n\n* this.skip needs to be under a function declaration ([2545ddd](https://github.com/ipfs/interface-ipfs-core/commit/2545ddd))\n\n\n\n<a name=\"0.64.0\"></a>\n## [0.64.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.62.0...v0.64.0) (2018-04-23)\n\n\n### Features\n\n* adds pull stream tests for files.add ([d75986a](https://github.com/ipfs/interface-ipfs-core/commit/d75986a))\n* better badge ([#246](https://github.com/ipfs/interface-ipfs-core/issues/246)) ([a3869bf](https://github.com/ipfs/interface-ipfs-core/commit/a3869bf))\n\n\n\n<a name=\"0.63.0\"></a>\n## [0.63.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.62.0...v0.63.0) (2018-04-23)\n\n\n### Features\n\n* adds pull stream tests for files.add ([d75986a](https://github.com/ipfs/interface-ipfs-core/commit/d75986a))\n\n\n\n<a name=\"0.62.0\"></a>\n## [0.62.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.61.0...v0.62.0) (2018-04-14)\n\n\n\n<a name=\"0.61.0\"></a>\n## [0.61.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.60.1...v0.61.0) (2018-04-10)\n\n\n\n<a name=\"0.60.1\"></a>\n## [0.60.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.60.0...v0.60.1) (2018-04-05)\n\n\n### Bug Fixes\n\n* fix wrapWithDirectory test ([a97c087](https://github.com/ipfs/interface-ipfs-core/commit/a97c087))\n\n\n\n<a name=\"0.60.0\"></a>\n## [0.60.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.59.0...v0.60.0) (2018-04-05)\n\n\n### Features\n\n* Provide access to bundled libraries when in browser ([db83b50](https://github.com/ipfs/interface-ipfs-core/commit/db83b50))\n\n\n\n<a name=\"0.59.0\"></a>\n## [0.59.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.58.0...v0.59.0) (2018-04-03)\n\n\n### Features\n\n* add wrapWithDirectory to files.add et al ([03eec9e](https://github.com/ipfs/interface-ipfs-core/commit/03eec9e))\n\n\n\n<a name=\"0.58.0\"></a>\n## [0.58.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.57.0...v0.58.0) (2018-03-22)\n\n\n### Bug Fixes\n\n* wrong description ([bad70ac](https://github.com/ipfs/interface-ipfs-core/commit/bad70ac))\n\n\n\n<a name=\"0.57.0\"></a>\n## [0.57.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.56.6...v0.57.0) (2018-03-16)\n\n\n\n<a name=\"0.56.6\"></a>\n## [0.56.6](https://github.com/ipfs/interface-ipfs-core/compare/v0.56.5...v0.56.6) (2018-03-16)\n\n\n\n<a name=\"0.56.5\"></a>\n## [0.56.5](https://github.com/ipfs/interface-ipfs-core/compare/v0.56.4...v0.56.5) (2018-03-16)\n\n\n### Bug Fixes\n\n* go-ipfs has not shipped withLocal yet ([58b1fe2](https://github.com/ipfs/interface-ipfs-core/commit/58b1fe2))\n\n\n\n<a name=\"0.56.4\"></a>\n## [0.56.4](https://github.com/ipfs/interface-ipfs-core/compare/v0.56.3...v0.56.4) (2018-03-16)\n\n\n\n<a name=\"0.56.3\"></a>\n## [0.56.3](https://github.com/ipfs/interface-ipfs-core/compare/v0.56.2...v0.56.3) (2018-03-16)\n\n\n\n<a name=\"0.56.2\"></a>\n## [0.56.2](https://github.com/ipfs/interface-ipfs-core/compare/v0.56.1...v0.56.2) (2018-03-16)\n\n\n\n<a name=\"0.56.1\"></a>\n## [0.56.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.56.0...v0.56.1) (2018-03-16)\n\n\n### Bug Fixes\n\n* don't error to specific ([ec16016](https://github.com/ipfs/interface-ipfs-core/commit/ec16016))\n* fix broken stat tests ([#236](https://github.com/ipfs/interface-ipfs-core/issues/236)) ([fcb8341](https://github.com/ipfs/interface-ipfs-core/commit/fcb8341)), closes [/github.com/ipfs/interface-ipfs-core/commit/c4934ca0b3b43f5bfc1ff5dd38f85d945d3244de#diff-0a6449ecfa8b9e3d807f53dde24eca71R66](https://github.com//github.com/ipfs/interface-ipfs-core/commit/c4934ca0b3b43f5bfc1ff5dd38f85d945d3244de/issues/diff-0a6449ecfa8b9e3d807f53dde24eca71R66)\n\n\n\n<a name=\"0.56.0\"></a>\n## [0.56.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.55.1...v0.56.0) (2018-03-12)\n\n\n### Features\n\n* complete files.stat with the 'with-local' option ([#227](https://github.com/ipfs/interface-ipfs-core/issues/227)) ([5969fed](https://github.com/ipfs/interface-ipfs-core/commit/5969fed))\n\n\n\n<a name=\"0.55.1\"></a>\n## [0.55.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.55.0...v0.55.1) (2018-03-09)\n\n\n### Bug Fixes\n\n* files.add accepts object ([88a635a](https://github.com/ipfs/interface-ipfs-core/commit/88a635a))\n\n\n\n<a name=\"0.55.0\"></a>\n## [0.55.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.54.0...v0.55.0) (2018-03-09)\n\n\n### Bug Fixes\n\n* only skip if it is go-ipfs on Windows ([0df216f](https://github.com/ipfs/interface-ipfs-core/commit/0df216f))\n\n\n\n<a name=\"0.54.0\"></a>\n## [0.54.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.53.0...v0.54.0) (2018-03-07)\n\n\n### Bug Fixes\n\n* fixes doc and adds test assertion that peer is a PeerId in return value from swarm.peers ([#230](https://github.com/ipfs/interface-ipfs-core/issues/230)) ([db530d7](https://github.com/ipfs/interface-ipfs-core/commit/db530d7))\n\n\n\n<a name=\"0.53.0\"></a>\n## [0.53.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.52.0...v0.53.0) (2018-03-07)\n\n\n### Bug Fixes\n\n* adapt dag tests to current environment ([7a6fc5f](https://github.com/ipfs/interface-ipfs-core/commit/7a6fc5f))\n* bwPullStream example ([59bd7ac](https://github.com/ipfs/interface-ipfs-core/commit/59bd7ac))\n\n\n\n<a name=\"0.52.0\"></a>\n## [0.52.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.51.0...v0.52.0) (2018-02-15)\n\n\n### Features\n\n* allow stats tests to run on js-ipfs ([#216](https://github.com/ipfs/interface-ipfs-core/issues/216)) ([f6e5f55](https://github.com/ipfs/interface-ipfs-core/commit/f6e5f55))\n\n\n\n<a name=\"0.51.0\"></a>\n## [0.51.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.50.1...v0.51.0) (2018-02-15)\n\n\n### Bug Fixes\n\n* bootstrap add test ([df01cc5](https://github.com/ipfs/interface-ipfs-core/commit/df01cc5))\n\n\n### Features\n\n* **bootstrap:** add the spec ([427338e](https://github.com/ipfs/interface-ipfs-core/commit/427338e))\n\n\n\n<a name=\"0.50.1\"></a>\n## [0.50.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.50.0...v0.50.1) (2018-02-14)\n\n\n### Bug Fixes\n\n* add pointer to files-mfs tests ([6bc22c9](https://github.com/ipfs/interface-ipfs-core/commit/6bc22c9))\n\n\n\n<a name=\"0.50.0\"></a>\n## [0.50.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.49.2...v0.50.0) (2018-02-14)\n\n\n### Features\n\n* factor out mfs tests to separate file ([91666ca](https://github.com/ipfs/interface-ipfs-core/commit/91666ca))\n\n\n\n<a name=\"0.49.2\"></a>\n## [0.49.2](https://github.com/ipfs/interface-ipfs-core/compare/v0.49.1...v0.49.2) (2018-02-14)\n\n\n### Bug Fixes\n\n* remove unnecessary console.log ([e27d3e0](https://github.com/ipfs/interface-ipfs-core/commit/e27d3e0))\n\n\n\n<a name=\"0.49.1\"></a>\n## [0.49.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.49.0...v0.49.1) (2018-02-12)\n\n\n### Bug Fixes\n\n* remove .only ([44cdaed](https://github.com/ipfs/interface-ipfs-core/commit/44cdaed))\n\n\n\n<a name=\"0.49.0\"></a>\n## [0.49.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.48.0...v0.49.0) (2018-02-12)\n\n\n### Bug Fixes\n\n* use latest fixture loading ([#218](https://github.com/ipfs/interface-ipfs-core/issues/218)) ([e054097](https://github.com/ipfs/interface-ipfs-core/commit/e054097))\n\n\n\n<a name=\"0.48.0\"></a>\n## [0.48.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.47.0...v0.48.0) (2018-02-07)\n\n\n\n<a name=\"0.47.0\"></a>\n## [0.47.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.46.0...v0.47.0) (2018-02-07)\n\n\n### Features\n\n* add stats.bwPullStream and stats.bwReadableStream ([#211](https://github.com/ipfs/interface-ipfs-core/issues/211)) ([4421eb2](https://github.com/ipfs/interface-ipfs-core/commit/4421eb2))\n\n\n\n<a name=\"0.46.0\"></a>\n## [0.46.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.44.0...v0.46.0) (2018-02-02)\n\n\n\n<a name=\"0.45.0\"></a>\n## [0.45.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.44.0...v0.45.0) (2018-02-02)\n\n\n\n<a name=\"0.44.0\"></a>\n## [0.44.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.43.0...v0.44.0) (2018-02-02)\n\n\n### Features\n\n* ipfs.shutdown test ([#214](https://github.com/ipfs/interface-ipfs-core/issues/214)) ([e911c6c](https://github.com/ipfs/interface-ipfs-core/commit/e911c6c))\n* Link stats.repo and stats.bitswap ([#210](https://github.com/ipfs/interface-ipfs-core/issues/210)) ([0c40084](https://github.com/ipfs/interface-ipfs-core/commit/0c40084))\n* shutdown spec ([9d91267](https://github.com/ipfs/interface-ipfs-core/commit/9d91267))\n\n\n\n<a name=\"0.43.0\"></a>\n## [0.43.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.42.1...v0.43.0) (2018-01-25)\n\n\n\n<a name=\"0.42.1\"></a>\n## [0.42.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.42.0...v0.42.1) (2018-01-25)\n\n\n### Bug Fixes\n\n* stats not implemented on jsipfs ([#209](https://github.com/ipfs/interface-ipfs-core/issues/209)) ([af32ecf](https://github.com/ipfs/interface-ipfs-core/commit/af32ecf))\n\n\n\n<a name=\"0.42.0\"></a>\n## [0.42.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.41.1...v0.42.0) (2018-01-25)\n\n\n### Bug Fixes\n\n* Update PUBSUB.md ([#204](https://github.com/ipfs/interface-ipfs-core/issues/204)) ([0409e3a](https://github.com/ipfs/interface-ipfs-core/commit/0409e3a))\n\n\n### Features\n\n* add stats spec ([220483f](https://github.com/ipfs/interface-ipfs-core/commit/220483f))\n* REPO spec ([#207](https://github.com/ipfs/interface-ipfs-core/issues/207)) ([803a3ef](https://github.com/ipfs/interface-ipfs-core/commit/803a3ef))\n* spec MFS Actions ([#206](https://github.com/ipfs/interface-ipfs-core/issues/206)) ([7431098](https://github.com/ipfs/interface-ipfs-core/commit/7431098))\n\n\n\n<a name=\"0.41.1\"></a>\n## [0.41.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.41.0...v0.41.1) (2018-01-19)\n\n\n### Bug Fixes\n\n* Revert \"feat: use new ipfsd-ctl ([#186](https://github.com/ipfs/interface-ipfs-core/issues/186))\" ([#203](https://github.com/ipfs/interface-ipfs-core/issues/203)) ([67b74a3](https://github.com/ipfs/interface-ipfs-core/commit/67b74a3))\n\n\n\n<a name=\"0.41.0\"></a>\n## [0.41.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.40.0...v0.41.0) (2018-01-19)\n\n\n### Features\n\n* use new ipfsd-ctl ([#186](https://github.com/ipfs/interface-ipfs-core/issues/186)) ([4d4ef7f](https://github.com/ipfs/interface-ipfs-core/commit/4d4ef7f))\n\n\n\n<a name=\"0.40.0\"></a>\n## [0.40.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.39.0...v0.40.0) (2018-01-12)\n\n\n\n<a name=\"0.39.0\"></a>\n## [0.39.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.38.0...v0.39.0) (2018-01-10)\n\n\n\n<a name=\"0.38.0\"></a>\n## [0.38.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.37.0...v0.38.0) (2018-01-05)\n\n\n### Features\n\n* normalize KEY API ([#192](https://github.com/ipfs/interface-ipfs-core/issues/192)) ([5a21d6c](https://github.com/ipfs/interface-ipfs-core/commit/5a21d6c))\n* normalize NAME API ([#190](https://github.com/ipfs/interface-ipfs-core/issues/190)) ([9670c1a](https://github.com/ipfs/interface-ipfs-core/commit/9670c1a))\n\n\n\n<a name=\"0.37.0\"></a>\n## [0.37.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.36.16...v0.37.0) (2017-12-28)\n\n\n\n<a name=\"0.36.16\"></a>\n## [0.36.16](https://github.com/ipfs/interface-ipfs-core/compare/v0.36.15...v0.36.16) (2017-12-18)\n\n\n### Bug Fixes\n\n* key.rm test ([#185](https://github.com/ipfs/interface-ipfs-core/issues/185)) ([211e2c5](https://github.com/ipfs/interface-ipfs-core/commit/211e2c5))\n\n\n\n<a name=\"0.36.15\"></a>\n## [0.36.15](https://github.com/ipfs/interface-ipfs-core/compare/v0.36.14...v0.36.15) (2017-12-12)\n\n\n### Bug Fixes\n\n* cat not found message in go-ipfs ([#183](https://github.com/ipfs/interface-ipfs-core/issues/183)) ([8e3645e](https://github.com/ipfs/interface-ipfs-core/commit/8e3645e))\n\n\n\n<a name=\"0.36.14\"></a>\n## [0.36.14](https://github.com/ipfs/interface-ipfs-core/compare/v0.36.13...v0.36.14) (2017-12-12)\n\n\n\n<a name=\"0.36.13\"></a>\n## [0.36.13](https://github.com/ipfs/interface-ipfs-core/compare/v0.36.12...v0.36.13) (2017-12-10)\n\n\n### Features\n\n* key tests ([#180](https://github.com/ipfs/interface-ipfs-core/issues/180)) ([b75e13b](https://github.com/ipfs/interface-ipfs-core/commit/b75e13b))\n\n\n\n<a name=\"0.36.12\"></a>\n## [0.36.12](https://github.com/ipfs/interface-ipfs-core/compare/v0.36.11...v0.36.12) (2017-12-05)\n\n\n\n<a name=\"0.36.11\"></a>\n## [0.36.11](https://github.com/ipfs/interface-ipfs-core/compare/v0.36.10...v0.36.11) (2017-11-26)\n\n\n\n<a name=\"0.36.10\"></a>\n## [0.36.10](https://github.com/ipfs/interface-ipfs-core/compare/v0.36.9...v0.36.10) (2017-11-25)\n\n\n\n<a name=\"0.36.9\"></a>\n## [0.36.9](https://github.com/ipfs/interface-ipfs-core/compare/v0.36.8...v0.36.9) (2017-11-23)\n\n\n\n<a name=\"0.36.8\"></a>\n## [0.36.8](https://github.com/ipfs/interface-ipfs-core/compare/v0.36.7...v0.36.8) (2017-11-22)\n\n\n### Bug Fixes\n\n* **pubsub:** swarm connect to local servers ([#175](https://github.com/ipfs/interface-ipfs-core/issues/175)) ([09d9573](https://github.com/ipfs/interface-ipfs-core/commit/09d9573))\n\n\n\n<a name=\"0.36.7\"></a>\n## [0.36.7](https://github.com/ipfs/interface-ipfs-core/compare/v0.36.6...v0.36.7) (2017-11-20)\n\n\n\n<a name=\"0.36.6\"></a>\n## [0.36.6](https://github.com/ipfs/interface-ipfs-core/compare/v0.36.4...v0.36.6) (2017-11-20)\n\n\n\n<a name=\"0.36.5\"></a>\n## [0.36.5](https://github.com/ipfs/interface-ipfs-core/compare/v0.36.4...v0.36.5) (2017-11-20)\n\n\n\n<a name=\"0.36.4\"></a>\n## [0.36.4](https://github.com/ipfs/interface-ipfs-core/compare/v0.36.3...v0.36.4) (2017-11-17)\n\n\n\n<a name=\"0.36.3\"></a>\n## [0.36.3](https://github.com/ipfs/interface-ipfs-core/compare/v0.36.2...v0.36.3) (2017-11-17)\n\n\n\n<a name=\"0.36.2\"></a>\n## [0.36.2](https://github.com/ipfs/interface-ipfs-core/compare/v0.36.1...v0.36.2) (2017-11-17)\n\n\n\n<a name=\"0.36.1\"></a>\n## [0.36.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.36.0...v0.36.1) (2017-11-17)\n\n\n\n<a name=\"0.36.0\"></a>\n## [0.36.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.35.0...v0.36.0) (2017-11-17)\n\n\n\n<a name=\"0.35.0\"></a>\n## [0.35.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.34.3...v0.35.0) (2017-11-16)\n\n\n### Bug Fixes\n\n* **pubsub:** topicCIDs should be topicIDs ([#169](https://github.com/ipfs/interface-ipfs-core/issues/169)) ([d357f5f](https://github.com/ipfs/interface-ipfs-core/commit/d357f5f))\n\n\n\n<a name=\"0.34.3\"></a>\n## [0.34.3](https://github.com/ipfs/interface-ipfs-core/compare/v0.34.2...v0.34.3) (2017-11-14)\n\n\n\n<a name=\"0.34.2\"></a>\n## [0.34.2](https://github.com/ipfs/interface-ipfs-core/compare/v0.34.0...v0.34.2) (2017-11-13)\n\n\n\n<a name=\"0.34.1\"></a>\n## [0.34.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.34.0...v0.34.1) (2017-11-13)\n\n\n\n<a name=\"0.34.0\"></a>\n## [0.34.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.33.2...v0.34.0) (2017-11-13)\n\n\n\n<a name=\"0.33.2\"></a>\n## [0.33.2](https://github.com/ipfs/interface-ipfs-core/compare/v0.33.1...v0.33.2) (2017-11-09)\n\n\n### Bug Fixes\n\n* **package:** aegir is a dependency ([#166](https://github.com/ipfs/interface-ipfs-core/issues/166)) ([72f2f56](https://github.com/ipfs/interface-ipfs-core/commit/72f2f56))\n\n\n\n<a name=\"0.33.1\"></a>\n## [0.33.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.33.0...v0.33.1) (2017-10-22)\n\n\n\n<a name=\"0.33.0\"></a>\n## [0.33.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.32.1...v0.33.0) (2017-10-22)\n\n\n\n<a name=\"0.32.1\"></a>\n## [0.32.1](https://github.com/ipfs/interface-ipfs-core/compare/v0.32.0...v0.32.1) (2017-10-18)\n\n\n### Bug Fixes\n\n* make tests consistent across js-ipfs/go-ipfs ([#158](https://github.com/ipfs/interface-ipfs-core/issues/158)) ([a5a4c37](https://github.com/ipfs/interface-ipfs-core/commit/a5a4c37))\n\n\n\n<a name=\"0.32.0\"></a>\n## [0.32.0](https://github.com/ipfs/interface-ipfs-core/compare/v0.31.19...v0.32.0) (2017-10-18)\n\n\n### Features\n\n* add progress bar tests ([#155](https://github.com/ipfs/interface-ipfs-core/issues/155)) ([fad3fa2](https://github.com/ipfs/interface-ipfs-core/commit/fad3fa2))\n\n\n\n<a name=\"0.31.19\"></a>\n## [0.31.19](https://github.com/ipfs/interface-ipfs-core/compare/v0.31.18...v0.31.19) (2017-09-04)\n\n\n### Bug Fixes\n\n* remove superfluous console.logs ([442ea74](https://github.com/ipfs/interface-ipfs-core/commit/442ea74))"
  },
  {
    "path": "packages/interface-ipfs-core/LICENSE",
    "content": "This project is dual licensed under MIT and Apache-2.0.\n\nMIT: https://www.opensource.org/licenses/mit\nApache-2.0: https://www.apache.org/licenses/license-2.0\n"
  },
  {
    "path": "packages/interface-ipfs-core/LICENSE-APACHE",
    "content": "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\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless 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.\n"
  },
  {
    "path": "packages/interface-ipfs-core/LICENSE-MIT",
    "content": "The MIT License (MIT)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "packages/interface-ipfs-core/README.md",
    "content": "> # ⛔️ DEPRECATED: [js-IPFS](https://github.com/ipfs/js-ipfs) has been superseded by [Helia](https://github.com/ipfs/helia)\n>\n> 📚 [Learn more about this deprecation](https://github.com/ipfs/js-ipfs/issues/4336) or [how to migrate](https://github.com/ipfs/helia/wiki/Migrating-from-js-IPFS)\n>\n> ⚠️ If you continue using this repo, please note that security fixes will not be provided\n\n# interface-ipfs-core <!-- omit in toc -->\n\n[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech)\n[![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech)\n[![codecov](https://img.shields.io/codecov/c/github/ipfs/js-ipfs.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipfs)\n[![CI](https://img.shields.io/github/actions/workflow/status/ipfs/js-ipfs/test.yml?branch=master\\&style=flat-square)](https://github.com/ipfs/js-ipfs/actions/workflows/test.yml?query=branch%3Amaster)\n\n> A test suite and interface you can use to implement a IPFS core interface.\n\n## Table of contents <!-- omit in toc -->\n\n- [Install](#install)\n- [Background](#background)\n- [Core API](#core-api)\n- [Modules that implement the interface](#modules-that-implement-the-interface)\n- [Badge](#badge)\n- [Usage](#usage)\n- [Running tests](#running-tests)\n  - [Running tests by command](#running-tests-by-command)\n  - [Running only some tests](#running-only-some-tests)\n  - [Running only specific tests](#running-only-specific-tests)\n- [Skipping tests](#skipping-tests)\n  - [Skipping specific tests](#skipping-specific-tests)\n- [License](#license)\n- [Contribute](#contribute)\n\n## Install\n\n```console\n$ npm i interface-ipfs-core\n```\n\n## Background\n\nThe primary goal of this module is to define and ensure that IPFS core implementations and their respective HTTP client libraries offer the same interface, so that developers can quickly change between a local and a remote node without having to change their applications.\n\nIt offers a suite of tests that can be run in order to check if the interface is implemented as described.\n\n## Core API\n\nIn order to be considered \"valid\", an IPFS implementation must expose the Core API as described in [/docs/core-api](https://github.com/ipfs/js-ipfs/tree/master/docs/core-api). You can also use this loose spec as documentation for consuming the core APIs.\n\n## Modules that implement the interface\n\n- [JavaScript IPFS implementation](https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs)\n- [JavaScript IPFS HTTP Client Library](https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-http-client)\n- [JavaScript IPFS postMessage proxy](https://github.com/ipfs-shipyard/ipfs-postmsg-proxy)\n\nSend in a PR if you find or write one!\n\n## Badge\n\nInclude this badge in your readme if you make a new module that implements interface-ipfs-core API.\n\n![](img/badge.svg)\n\n```md\n[![IPFS Core API Compatible](https://cdn.rawgit.com/ipfs/interface-ipfs-core/master/img/badge.svg)](https://github.com/ipfs/js-ipfs/tree/master/packages/interface-ipfs-core)\n```\n\n```console\n$ npm install interface-ipfs-core\n```\n\nIf you want to run these tests against a Kubo daemon, checkout [ipfs-http-client](https://github.com/ipfs/js-ipfs-http-client) and run test tests:\n\n```console\n$ git clone https://github.com/ipfs/js-ipfs-http-client\n$ npm install\n$ npm test\n```\n\n## Usage\n\nInstall `interface-ipfs-core` as one of the dependencies of your project and as a test file. Then, using `mocha` (for Node.js) or a test runner with compatible API, do:\n\n```js\nimport * as tests from 'interface-ipfs-core'\nconst nodes = []\n\n// Create common setup and teardown\nconst createCommon = () => ({\n  // Do some setup common to all tests\n  setup: async () => {\n    // Use ipfsd-ctl or other to spawn an IPFS node for testing\n    const node = await spawnNode()\n    nodes.push(node)\n\n    return node.api\n  },\n  // Dispose of nodes created by the IPFS factory and any other teardown\n  teardown: () => {\n    return Promise.all(nodes.map(n => n.stop()))\n  }\n})\n\ntests.block(createCommon)\ntests.config(createCommon)\ntests.dag(createCommon)\n// ...etc. (see src/index.js)\n```\n\n## Running tests\n\n```js\n// run all the tests for the repo subsystem\ntests.repo(createCommon)\n```\n\n### Running tests by command\n\n```js\ntests.repo.version(createCommon)\n```\n\n### Running only some tests\n\n```js\ntests.repo.gc(createCommon, { only: true }) // pass an options object to run only these tests\n\n// OR, at the subsystem level\n\n// runs only ALL the repo.gc tests\ntests.repo(createCommon, { only: ['gc'] })\n// runs only ALL the object.patch.addLink tests\ntests.object(createCommon, { only: ['patch.addLink'] })\n```\n\n### Running only specific tests\n\n```js\ntests.repo.gc(createCommon, { only: ['should do a thing'] }) // only run these named test(s)\n\n// OR, at the subsystem level\ntests.repo(createCommon, { only: ['should do a thing'] })\n```\n\n## Skipping tests\n\n```js\ntests.repo.gc(createCommon, { skip: true }) // pass an options object to skip these tests\n\n// skips ALL the repo.gc tests\ntests.repo(createCommon, { skip: ['gc'] })\n// skips ALL the object.patch.addLink tests\ntests.object(createCommon, { skip: ['patch.addLink'] })\n```\n\n### Skipping specific tests\n\n```js\ntests.repo.gc(createCommon, { skip: ['should do a thing'] }) // named test(s) to skip\n\n// OR, at the subsystem level\ntests.repo(createCommon, { skip: ['should do a thing'] })\n\n// Optionally specify a reason\ntests.repo(createCommon, {\n  skip: [{\n    name: 'should do a thing',\n    reason: 'Thing is not implemented yet'\n  }]\n})\n```\n\n## License\n\nLicensed under either of\n\n- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / <http://www.apache.org/licenses/LICENSE-2.0>)\n- MIT ([LICENSE-MIT](LICENSE-MIT) / <http://opensource.org/licenses/MIT>)\n\n## Contribute\n\nContributions welcome! Please check out [the issues](https://github.com/ipfs/js-ipfs/issues).\n\nAlso 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.\n\nPlease 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).\n\nUnless 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.\n\n[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md)\n\n[UnixFS]: https://github.com/ipfs/specs/tree/master/unixfs\n"
  },
  {
    "path": "packages/interface-ipfs-core/maintainer.json",
    "content": "{\n  \"repoLeadMaintainer\": {\n    \"name\": \"Alan Shaw\",\n    \"email\": \"alan.shaw@protocol.ai\",\n    \"username\": \"alanshaw\"\n  },\n  \"workingGroup\": {\n    \"name\": \"JS IPFS\",\n    \"entryPoint\": \"https://github.com/ipfs/js-core\"\n  }\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/package.json",
    "content": "{\n  \"name\": \"interface-ipfs-core\",\n  \"version\": \"0.158.1\",\n  \"description\": \"A test suite and interface you can use to implement a IPFS core interface.\",\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"homepage\": \"https://github.com/ipfs/js-ipfs/tree/master/packages/interface-ipfs-core#readme\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ipfs/js-ipfs.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ipfs/js-ipfs/issues\"\n  },\n  \"keywords\": [\n    \"IPFS\"\n  ],\n  \"engines\": {\n    \"node\": \">=16.0.0\",\n    \"npm\": \">=7.0.0\"\n  },\n  \"type\": \"module\",\n  \"types\": \"./dist/src/index.d.ts\",\n  \"typesVersions\": {\n    \"*\": {\n      \"*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ],\n      \"src/*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ]\n    }\n  },\n  \"files\": [\n    \"src\",\n    \"dist\",\n    \"!dist/test\",\n    \"!**/*.tsbuildinfo\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/src/index.d.ts\",\n      \"import\": \"./src/index.js\"\n    }\n  },\n  \"eslintConfig\": {\n    \"extends\": \"ipfs\",\n    \"parserOptions\": {\n      \"sourceType\": \"module\"\n    },\n    \"ignorePatterns\": [\n      \"test/fixtures/*\"\n    ]\n  },\n  \"scripts\": {\n    \"clean\": \"aegir clean\",\n    \"build\": \"aegir build && copyfiles './test/fixtures/**/*' ./dist\",\n    \"lint\": \"aegir lint\",\n    \"dep-check\": \"aegir dep-check -i ipfs-core-types -i copyfiles -i @libp2p/interfaces\"\n  },\n  \"dependencies\": {\n    \"@ipld/car\": \"^5.0.0\",\n    \"@ipld/dag-cbor\": \"^9.0.0\",\n    \"@ipld/dag-pb\": \"^4.0.0\",\n    \"@libp2p/crypto\": \"^1.0.7\",\n    \"@libp2p/interface-peer-id\": \"^2.0.0\",\n    \"@libp2p/interfaces\": \"^3.2.0\",\n    \"@libp2p/peer-id\": \"^2.0.0\",\n    \"@libp2p/peer-id-factory\": \"^2.0.0\",\n    \"@libp2p/websockets\": \"^5.0.0\",\n    \"@multiformats/multiaddr\": \"^11.1.5\",\n    \"@types/node\": \"^18.0.0\",\n    \"@types/pako\": \"^2.0.0\",\n    \"@types/readable-stream\": \"^2.3.13\",\n    \"aegir\": \"^37.11.0\",\n    \"blockstore-core\": \"^3.0.0\",\n    \"copyfiles\": \"^2.4.1\",\n    \"dag-jose\": \"^4.0.0\",\n    \"delay\": \"^5.0.0\",\n    \"did-jwt\": \"^6.2.0\",\n    \"err-code\": \"^3.0.1\",\n    \"ipfs-core-types\": \"^0.14.1\",\n    \"ipfs-unixfs\": \"^9.0.0\",\n    \"ipfs-unixfs-importer\": \"^12.0.0\",\n    \"ipfs-utils\": \"^9.0.13\",\n    \"ipns\": \"^5.0.1\",\n    \"is-ipfs\": \"^8.0.0\",\n    \"iso-random-stream\": \"^2.0.2\",\n    \"it-all\": \"^2.0.0\",\n    \"it-buffer-stream\": \"^3.0.0\",\n    \"it-concat\": \"^3.0.1\",\n    \"it-drain\": \"^2.0.0\",\n    \"it-first\": \"^2.0.0\",\n    \"it-last\": \"^2.0.0\",\n    \"it-map\": \"^2.0.0\",\n    \"it-pipe\": \"^2.0.3\",\n    \"it-pushable\": \"^3.0.0\",\n    \"it-tar\": \"^6.0.0\",\n    \"it-to-buffer\": \"^3.0.0\",\n    \"merge-options\": \"^3.0.4\",\n    \"multiformats\": \"^11.0.0\",\n    \"nanoid\": \"^4.0.0\",\n    \"p-defer\": \"^4.0.0\",\n    \"p-map\": \"^5.3.0\",\n    \"p-retry\": \"^5.1.0\",\n    \"p-wait-for\": \"^5.0.0\",\n    \"pako\": \"^2.0.4\",\n    \"readable-stream\": \"^4.0.0\",\n    \"sinon\": \"^15.0.1\",\n    \"stream\": \"^0.0.2\",\n    \"uint8arrays\": \"^4.0.2\",\n    \"wherearewe\": \"^2.0.1\"\n  },\n  \"browser\": {\n    \"fs\": false,\n    \"os\": false,\n    \"path\": false\n  }\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/add-all.js",
    "content": "/* eslint-env mocha, browser */\n\nimport { fixtures } from './utils/index.js'\nimport { Readable } from 'readable-stream'\nimport all from 'it-all'\nimport last from 'it-last'\nimport drain from 'it-drain'\nimport { supportsFileReader } from 'ipfs-utils/src/supports.js'\nimport globSource from 'ipfs-utils/src/files/glob-source.js'\nimport { isNode } from 'ipfs-utils/src/env.js'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from './utils/mocha.js'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport bufferStream from 'it-buffer-stream'\nimport * as raw from 'multiformats/codecs/raw'\nimport * as dagPB from '@ipld/dag-pb'\nimport resolve from 'aegir/resolve'\nimport { sha256, sha512 } from 'multiformats/hashes/sha2'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n * @typedef {import('ipfs-unixfs').MtimeLike} MtimeLike\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testAddAll (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.addAll', function () {\n    this.timeout(360 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    /**\n     * @param {string | number} mode\n     * @param {number} expectedMode\n     */\n    async function testMode (mode, expectedMode) {\n      const content = String(Math.random() + Date.now())\n      const files = await all(ipfs.addAll([{\n        content: uint8ArrayFromString(content),\n        mode\n      }]))\n      expect(files).to.have.length(1)\n      expect(files).to.have.nested.property('[0].mode', expectedMode)\n\n      const stats = await ipfs.files.stat(`/ipfs/${files[0].cid}`)\n      expect(stats).to.have.property('mode', expectedMode)\n    }\n\n    /**\n     * @param {MtimeLike} mtime\n     * @param {MtimeLike} expectedMtime\n     */\n    async function testMtime (mtime, expectedMtime) {\n      const content = String(Math.random() + Date.now())\n      const files = await all(ipfs.addAll([{\n        content: uint8ArrayFromString(content),\n        mtime\n      }]))\n      expect(files).to.have.length(1)\n      expect(files).to.have.deep.nested.property('[0].mtime', expectedMtime)\n\n      const stats = await ipfs.files.stat(`/ipfs/${files[0].cid}`)\n      expect(stats).to.have.deep.property('mtime', expectedMtime)\n    }\n\n    before(async () => { ipfs = (await factory.spawn()).api })\n\n    after(() => factory.clean())\n\n    it('should add a File as array of tuples', async function () {\n      if (!supportsFileReader) {\n        return this.skip()\n      }\n\n      const tuple = {\n        path: 'filename.txt',\n        content: new self.File(['should add a File'], 'filename.txt', { type: 'text/plain' })\n      }\n\n      const filesAdded = await all(ipfs.addAll([tuple]))\n      expect(filesAdded[0].cid.toString()).to.be.eq('QmTVfLxf3qXiJgr4KwG6UBckcNvTqBp93Rwy5f7h3mHsVC')\n    })\n\n    it('should add a Uint8Array as array of tuples', async () => {\n      const tuple = { path: 'testfile.txt', content: fixtures.smallFile.data }\n\n      const filesAdded = await all(ipfs.addAll([tuple]))\n      expect(filesAdded).to.have.length(1)\n\n      const file = filesAdded[0]\n      expect(file.cid.toString()).to.equal(fixtures.smallFile.cid.toString())\n      expect(file.path).to.equal('testfile.txt')\n    })\n\n    it('should add array of objects with readable stream content', async function () {\n      if (!isNode) {\n        this.skip()\n      }\n\n      const expectedCid = 'QmVv4Wz46JaZJeH5PMV4LGbRiiMKEmszPYY3g6fjGnVXBS'\n\n      const rs = new Readable()\n      rs.push(uint8ArrayFromString('some data'))\n      rs.push(null)\n\n      const tuple = { path: 'data.txt', content: rs }\n\n      const filesAdded = await all(ipfs.addAll([tuple]))\n      expect(filesAdded).to.be.length(1)\n\n      const file = filesAdded[0]\n      expect(file.path).to.equal('data.txt')\n      expect(file.size).to.equal(17)\n      expect(file.cid.toString()).to.equal(expectedCid)\n    })\n\n    it('should add a nested directory as array of tupples', async function () {\n      /**\n       * @param {string} name\n       */\n      const content = (name) => ({\n        path: `test-folder/${name}`,\n        content: fixtures.directory.files[name]\n      })\n\n      /**\n       * @param {string} name\n       */\n      const emptyDir = (name) => ({ path: `test-folder/${name}` })\n\n      const dirs = [\n        content('pp.txt'),\n        content('holmes.txt'),\n        content('jungle.txt'),\n        content('alice.txt'),\n        emptyDir('empty-folder'),\n        content('files/hello.txt'),\n        content('files/ipfs.txt'),\n        emptyDir('files/empty')\n      ]\n\n      const root = await last(ipfs.addAll(dirs))\n\n      if (!root) {\n        throw new Error('Dirs were not loaded')\n      }\n\n      expect(root.path).to.equal('test-folder')\n      expect(root.cid.toString()).to.equal(fixtures.directory.cid.toString())\n    })\n\n    it('should add a nested directory as array of tupples with progress', async function () {\n      /**\n       * @param {string} name\n       */\n      const content = (name) => ({\n        path: `test-folder/${name}`,\n        content: fixtures.directory.files[name]\n      })\n\n      /**\n       * @param {string} name\n       */\n      const emptyDir = (name) => ({ path: `test-folder/${name}`, content: undefined })\n\n      /** @type {Record<string, number>} */\n      const progressSizes = {}\n\n      const dirs = [\n        content('pp.txt'),\n        content('holmes.txt'),\n        content('jungle.txt'),\n        content('alice.txt'),\n        emptyDir('empty-folder'),\n        content('files/hello.txt'),\n        content('files/ipfs.txt'),\n        emptyDir('files/empty')\n      ]\n\n      const total = dirs.reduce((/** @type {Record<string, number>} */ acc, curr) => {\n        if (curr.content) {\n          acc[curr.path] = curr.content.length\n        }\n\n        return acc\n      }, {})\n\n      /**\n       * @type {import('ipfs-core-types/src/root').AddProgressFn}\n       */\n      const handler = (bytes, path) => {\n        if (path) {\n          progressSizes[path] = bytes\n        }\n      }\n\n      const root = await last(ipfs.addAll(dirs, { progress: handler }))\n      expect(progressSizes).to.deep.equal(total)\n      expect(root).to.have.property('path', 'test-folder')\n      expect(root).to.have.deep.property('cid', fixtures.directory.cid)\n    })\n\n    it('should receive progress path as empty string when adding content without paths', async function () {\n      /**\n       * @param {string} name\n       */\n      const content = (name) => fixtures.directory.files[name]\n\n      /** @type {Record<string, number>} */\n      const progressSizes = {}\n\n      const dirs = [\n        content('pp.txt'),\n        content('holmes.txt'),\n        content('jungle.txt')\n      ]\n\n      const total = {\n        '': dirs.reduce((acc, curr) => acc + curr.length, 0)\n      }\n\n      /**\n       * @type {import('ipfs-core-types/src/root').AddProgressFn}\n       */\n      const handler = (bytes, path) => {\n        progressSizes[`${path}`] = bytes\n      }\n\n      await drain(ipfs.addAll(dirs, { progress: handler }))\n      expect(progressSizes).to.deep.equal(total)\n    })\n\n    it('should receive file name from progress event', async () => {\n      /** @type {string[]} */\n      const receivedNames = []\n\n      /**\n       * @type {import('ipfs-core-types/src/root').AddProgressFn}\n       */\n      function handler (p, name) {\n        if (name) {\n          receivedNames.push(name)\n        }\n      }\n\n      await drain(ipfs.addAll([{\n        content: 'hello',\n        path: 'foo.txt'\n      }, {\n        content: 'world',\n        path: 'bar.txt'\n      }], {\n        progress: handler,\n        wrapWithDirectory: true\n      }))\n\n      expect(receivedNames).to.deep.equal(['foo.txt', 'bar.txt'])\n    })\n\n    it('should add files to a directory non sequentially', async function () {\n      /**\n       * @param {string} path\n       */\n      const content = path => ({\n        path: `test-dir/${path}`,\n        content: fixtures.directory.files[path.split('/').pop() || '']\n      })\n\n      const input = [\n        content('a/pp.txt'),\n        content('a/holmes.txt'),\n        content('b/jungle.txt'),\n        content('a/alice.txt')\n      ]\n\n      const filesAdded = await all(ipfs.addAll(input))\n\n      /**\n       * @param {object} arg\n       * @param {string} [arg.path]\n       */\n      const toPath = ({ path }) => path\n      const nonSeqDirFilePaths = input.map(toPath).filter(p => p && p.includes('/a/'))\n      const filesAddedPaths = filesAdded.map(toPath)\n\n      expect(nonSeqDirFilePaths.every(p => filesAddedPaths.includes(p))).to.be.true()\n    })\n\n    it('should fail when passed invalid input', async () => {\n      const nonValid = 138\n\n      // @ts-expect-error nonValid is the wrong type\n      await expect(all(ipfs.addAll(nonValid))).to.eventually.be.rejected()\n    })\n\n    it('should fail when passed single file objects', async () => {\n      const nonValid = { content: 'hello world' }\n\n      // @ts-expect-error nonValid is non valid\n      await expect(all(ipfs.addAll(nonValid))).to.eventually.be.rejectedWith(/single item passed/)\n    })\n\n    it('should fail when passed single strings', async () => {\n      const nonValid = 'hello world'\n\n      await expect(all(ipfs.addAll(nonValid))).to.eventually.be.rejectedWith(/single item passed/)\n    })\n\n    it('should fail when passed single buffers', async () => {\n      const nonValid = uint8ArrayFromString('hello world')\n\n      // @ts-expect-error nonValid is non valid\n      await expect(all(ipfs.addAll(nonValid))).to.eventually.be.rejectedWith(/single item passed/)\n    })\n\n    it('should wrap content in a directory', async () => {\n      const data = { path: 'testfile.txt', content: fixtures.smallFile.data }\n\n      const filesAdded = await all(ipfs.addAll([data], { wrapWithDirectory: true }))\n      expect(filesAdded).to.have.length(2)\n\n      const file = filesAdded[0]\n      const wrapped = filesAdded[1]\n      expect(file.cid.toString()).to.equal(fixtures.smallFile.cid.toString())\n      expect(file.path).to.equal('testfile.txt')\n      expect(wrapped.path).to.equal('')\n    })\n\n    it('should add a directory with only-hash=true', async function () {\n      this.slow(10 * 1000)\n      const content = String(Math.random() + Date.now())\n\n      const files = await all(ipfs.addAll([{\n        path: '/foo/bar.txt',\n        content: uint8ArrayFromString(content)\n      }, {\n        path: '/foo/baz.txt',\n        content: uint8ArrayFromString(content)\n      }], { onlyHash: true }))\n      expect(files).to.have.length(3)\n\n      await Promise.all(\n        files.map(file => expect(ipfs.object.get(file.cid, { timeout: 4000 }))\n          .to.eventually.be.rejected()\n          .and.to.have.property('name').that.equals('TimeoutError')\n        )\n      )\n    })\n\n    it('should add with mode as string', async function () {\n      this.slow(10 * 1000)\n      const mode = '0777'\n      await testMode(mode, parseInt(mode, 8))\n    })\n\n    it('should add with mode as number', async function () {\n      this.slow(10 * 1000)\n      const mode = parseInt('0777', 8)\n      await testMode(mode, mode)\n    })\n\n    it('should add with mtime as Date', async function () {\n      this.slow(10 * 1000)\n      const mtime = new Date(5000)\n      await testMtime(mtime, {\n        secs: 5,\n        nsecs: 0\n      })\n    })\n\n    it('should add with mtime as { nsecs, secs }', async function () {\n      this.slow(10 * 1000)\n      const mtime = {\n        secs: 5,\n        nsecs: 0\n      }\n      await testMtime(mtime, mtime)\n    })\n\n    it('should add with mtime as timespec', async function () {\n      this.slow(10 * 1000)\n      await testMtime({\n        Seconds: 5,\n        FractionalNanoseconds: 0\n      }, {\n        secs: 5,\n        nsecs: 0\n      })\n    })\n\n    it('should add with mtime as hrtime', async function () {\n      this.slow(10 * 1000)\n      const mtime = process.hrtime()\n      await testMtime(mtime, {\n        secs: mtime[0],\n        nsecs: mtime[1]\n      })\n    })\n\n    it('should add a directory from the file system', async function () {\n      if (!isNode) this.skip()\n      const filesPath = resolve('test/fixtures/test-folder', 'interface-ipfs-core')\n\n      const result = await all(ipfs.addAll(globSource(filesPath, '**/*')))\n      expect(result.length).to.be.above(8)\n    })\n\n    it('should add a directory from the file system with an odd name', async function () {\n      if (!isNode) this.skip()\n\n      const filesPath = resolve('test/fixtures/weird name folder [v0]', 'interface-ipfs-core')\n\n      const result = await all(ipfs.addAll(globSource(filesPath, '**/*')))\n      expect(result.length).to.be.above(8)\n    })\n\n    it('should ignore a directory from the file system', async function () {\n      if (!isNode) this.skip()\n\n      const filesPath = resolve('test/fixtures/test-folder', 'interface-ipfs-core')\n\n      const result = await all(ipfs.addAll(globSource(filesPath, '@(!(files*))')))\n      expect(result.length).to.equal(6)\n    })\n\n    it('should add a file from the file system', async function () {\n      if (!isNode) this.skip()\n\n      const filePath = resolve('test/fixtures/test-folder', 'interface-ipfs-core')\n\n      const result = await all(ipfs.addAll(globSource(filePath, 'ipfs-add.js')))\n      expect(result.length).to.equal(1)\n      expect(result[0].path).to.equal('ipfs-add.js')\n    })\n\n    it('should add a hidden file in a directory from the file system', async function () {\n      if (!isNode) this.skip()\n\n      const filesPath = resolve('test/fixtures', 'interface-ipfs-core')\n\n      const result = await all(ipfs.addAll(globSource(filesPath, 'hidden-files-folder/**/*', { hidden: true })))\n      expect(result.map(object => object.path)).to.include('hidden-files-folder/.hiddenTest.txt')\n      expect(result.map(object => object.cid.toString())).to.include('QmdbAjVmLRdpFyi8FFvjPfhTGB2cVXvWLuK7Sbt38HXrtt')\n    })\n\n    it('should add a file with only-hash=true', async function () {\n      if (!isNode) this.skip()\n\n      this.slow(10 * 1000)\n\n      const out = await all(ipfs.addAll([{\n        content: uint8ArrayFromString('hello world')\n      }], { onlyHash: true }))\n\n      await expect(ipfs.object.get(out[0].cid, { timeout: 500 }))\n        .to.eventually.be.rejected()\n        .and.to.have.property('name').that.equals('TimeoutError')\n    })\n\n    it('should add all with sha2-256 by default', async function () {\n      const content = String(Math.random() + Date.now())\n\n      const files = await all(ipfs.addAll([content]))\n\n      expect(files).to.have.nested.property('[0].cid.multihash.code', sha256.code)\n    })\n\n    it('should add all with a different hashing algorithm', async function () {\n      const content = String(Math.random() + Date.now())\n\n      const files = await all(ipfs.addAll([content], { hashAlg: 'sha2-512' }))\n\n      expect(files).to.have.nested.property('[0].cid.multihash.code', sha512.code)\n    })\n\n    it('should respect raw leaves when file is smaller than one block and no metadata is present', async () => {\n      const files = await all(ipfs.addAll([Uint8Array.from([0, 1, 2])], {\n        cidVersion: 1,\n        rawLeaves: true\n      }))\n\n      expect(files.length).to.equal(1)\n      expect(files[0].cid.toString()).to.equal('bafkreifojmzibzlof6xyh5auu3r5vpu5l67brf3fitaf73isdlglqw2t7q')\n      expect(files[0].cid.code).to.equal(raw.code)\n      expect(files[0].size).to.equal(3)\n    })\n\n    it('should override raw leaves when file is smaller than one block and metadata is present', async () => {\n      const files = await all(ipfs.addAll([{\n        content: Uint8Array.from([0, 1, 2]),\n        mode: 0o123,\n        mtime: {\n          secs: 1000,\n          nsecs: 0\n        }\n      }], {\n        cidVersion: 1,\n        rawLeaves: true\n      }))\n\n      expect(files.length).to.equal(1)\n      expect(files[0].cid.toString()).to.equal('bafybeifmayxiu375ftlgydntjtffy5cssptjvxqw6vyuvtymntm37mpvua')\n      expect(files[0].cid.code).to.equal(dagPB.code)\n      expect(files[0].size).to.equal(18)\n    })\n\n    it('should add directories with metadata', async () => {\n      const files = await all(ipfs.addAll([{\n        path: '/foo',\n        mode: 0o123,\n        mtime: {\n          secs: 1000,\n          nsecs: 0\n        }\n      }]))\n\n      expect(files.length).to.equal(1)\n      expect(files[0].cid.toString()).to.equal('QmaZTosBmPwo9LQ48ESPCEcNuX2kFxkpXYy8i3rxqBdzRG')\n      expect(files[0].cid.code).to.equal(dagPB.code)\n      expect(files[0].size).to.equal(11)\n    })\n\n    it('should support bidirectional streaming', async function () {\n      let progressInvoked = false\n\n      /**\n       * @type {import('ipfs-core-types/src/root').AddProgressFn}\n       */\n      const handler = (bytes, path) => {\n        progressInvoked = true\n      }\n\n      const source = async function * () {\n        yield {\n          content: 'hello',\n          path: '/file'\n        }\n\n        await new Promise((resolve) => {\n          const interval = setInterval(() => {\n            // we've received a progress result, that means we've received some\n            // data from the server before we're done sending data to the server\n            // so the streaming is bidirectional and we can finish up\n            if (progressInvoked) {\n              clearInterval(interval)\n              resolve(null)\n            }\n          }, 10)\n        })\n      }\n\n      await drain(ipfs.addAll(source(), {\n        progress: handler,\n        fileImportConcurrency: 1\n      }))\n\n      expect(progressInvoked).to.be.true()\n    })\n\n    it('should error during add-all stream', async function () {\n      const source = async function * () {\n        yield {\n          content: 'hello',\n          path: '/file'\n        }\n\n        yield {\n          content: 'hello',\n          path: '/file'\n        }\n      }\n\n      await expect(drain(ipfs.addAll(source(), {\n        fileImportConcurrency: 1,\n        chunker: 'rabin-2048--50' // invalid chunker parameters, validated after the stream starts moving\n      }))).to.eventually.be.rejectedWith(/Chunker parameter avg must be an integer/)\n    })\n\n    it('should add big files', async function () {\n      const totalSize = 1024 * 1024 * 200\n      const chunkSize = 1024 * 1024 * 99\n\n      const source = async function * () {\n        yield {\n          path: '/dir/file-200mb-1',\n          content: bufferStream(totalSize, {\n            chunkSize\n          })\n        }\n\n        yield {\n          path: '/dir/file-200mb-2',\n          content: bufferStream(totalSize, {\n            chunkSize\n          })\n        }\n      }\n\n      const results = await all(ipfs.addAll(source()))\n\n      expect(await ipfs.files.stat(`/ipfs/${results[0].cid}`)).to.have.property('size', totalSize)\n      expect(await ipfs.files.stat(`/ipfs/${results[1].cid}`)).to.have.property('size', totalSize)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/add.js",
    "content": "/* eslint-env mocha, browser */\n\nimport { fixtures } from './utils/index.js'\nimport { Readable } from 'readable-stream'\nimport { supportsFileReader } from 'ipfs-utils/src/supports.js'\nimport urlSource from 'ipfs-utils/src/files/url-source.js'\nimport { isNode } from 'ipfs-utils/src/env.js'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from './utils/mocha.js'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport last from 'it-last'\nimport * as raw from 'multiformats/codecs/raw'\nimport * as dagPB from '@ipld/dag-pb'\nimport { sha256, sha512 } from 'multiformats/hashes/sha2'\n\nconst echoUrl = (/** @type {string} */ text) => `${process.env.ECHO_SERVER}/download?data=${encodeURIComponent(text)}`\nconst redirectUrl = (/** @type {string} */ url) => `${process.env.ECHO_SERVER}/redirect?to=${encodeURI(url)}`\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n * @typedef {import('ipfs-unixfs').MtimeLike} MtimeLike\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testAdd (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.add', function () {\n    this.timeout(1080 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    /**\n     * @param {string | number} mode\n     * @param {number} expectedMode\n     */\n    async function testMode (mode, expectedMode) {\n      const content = String(Math.random() + Date.now())\n      const file = await ipfs.add({\n        content,\n        mode\n      })\n      expect(file).to.have.property('mode', expectedMode)\n\n      const stats = await ipfs.files.stat(`/ipfs/${file.cid}`)\n      expect(stats).to.have.property('mode', expectedMode)\n    }\n\n    /**\n     * @param {MtimeLike} mtime\n     * @param {MtimeLike} expectedMtime\n     */\n    async function testMtime (mtime, expectedMtime) {\n      const content = String(Math.random() + Date.now())\n      const file = await ipfs.add({\n        content,\n        mtime\n      })\n      expect(file).to.have.deep.property('mtime', expectedMtime)\n\n      const stats = await ipfs.files.stat(`/ipfs/${file.cid}`)\n      expect(stats).to.have.deep.property('mtime', expectedMtime)\n    }\n\n    before(async () => { ipfs = (await factory.spawn()).api })\n\n    after(() => factory.clean())\n\n    it('should add a File', async function () {\n      if (!supportsFileReader) {\n        return this.skip()\n      }\n\n      const fileAdded = await ipfs.add(new File(['should add a File'], 'filename.txt', { type: 'text/plain' }))\n      expect(fileAdded.cid.toString()).to.be.eq('QmTVfLxf3qXiJgr4KwG6UBckcNvTqBp93Rwy5f7h3mHsVC')\n    })\n\n    it('should add a File as tuple', async function () {\n      if (!supportsFileReader) {\n        return this.skip()\n      }\n\n      const tuple = {\n        path: 'filename.txt',\n        content: new self.File(['should add a File'], 'filename.txt', { type: 'text/plain' })\n      }\n\n      const fileAdded = await ipfs.add(tuple)\n      expect(fileAdded.cid.toString()).to.be.eq('QmTVfLxf3qXiJgr4KwG6UBckcNvTqBp93Rwy5f7h3mHsVC')\n    })\n\n    it('should add a Uint8Array', async () => {\n      const file = await ipfs.add(fixtures.smallFile.data)\n\n      expect(file.cid.toString()).to.equal(fixtures.smallFile.cid.toString())\n      expect(file.path).to.equal(fixtures.smallFile.cid.toString())\n      // file.size counts the overhead by IPLD nodes and unixfs protobuf\n      expect(file.size).greaterThan(fixtures.smallFile.data.length)\n    })\n\n    it('should add a BIG Uint8Array', async () => {\n      const file = await ipfs.add(fixtures.bigFile.data)\n\n      expect(file.cid.toString()).to.equal(fixtures.bigFile.cid.toString())\n      expect(file.path).to.equal(fixtures.bigFile.cid.toString())\n      // file.size counts the overhead by IPLD nodes and unixfs protobuf\n      expect(file.size).greaterThan(fixtures.bigFile.data.length)\n    })\n\n    it('should add a BIG Uint8Array with progress enabled', async () => {\n      let progCalled = false\n      let accumProgress = 0\n\n      /**\n       * @type {import('ipfs-core-types/src/root').AddProgressFn}\n       */\n      function handler (p) {\n        progCalled = true\n        accumProgress = p\n      }\n\n      const file = await ipfs.add(fixtures.bigFile.data, { progress: handler })\n\n      expect(file.cid.toString()).to.equal(fixtures.bigFile.cid.toString())\n      expect(file.path).to.equal(fixtures.bigFile.cid.toString())\n      expect(progCalled).to.be.true()\n      expect(accumProgress).to.equal(fixtures.bigFile.data.length)\n    })\n\n    it('should add an empty file with progress enabled', async () => {\n      let progCalled = false\n      let accumProgress = 0\n\n      /**\n       * @type {import('ipfs-core-types/src/root').AddProgressFn}\n       */\n      function handler (p) {\n        progCalled = true\n        accumProgress = p\n      }\n\n      const file = await ipfs.add(fixtures.emptyFile.data, { progress: handler })\n\n      expect(file.cid.toString()).to.equal(fixtures.emptyFile.cid.toString())\n      expect(file.path).to.equal(fixtures.emptyFile.cid.toString())\n      expect(progCalled).to.be.true()\n      expect(accumProgress).to.equal(fixtures.emptyFile.data.length)\n    })\n\n    it('should receive file name from progress event', async () => {\n      let receivedName\n\n      /**\n       * @type {import('ipfs-core-types/src/root').AddProgressFn}\n       */\n      function handler (p, name) {\n        receivedName = name\n      }\n\n      await ipfs.add({\n        content: 'hello',\n        path: 'foo.txt'\n      }, { progress: handler })\n\n      expect(receivedName).to.equal('foo.txt')\n    })\n\n    it('should add an empty file without progress enabled', async () => {\n      const file = await ipfs.add(fixtures.emptyFile.data)\n\n      expect(file.cid.toString()).to.equal(fixtures.emptyFile.cid.toString())\n      expect(file.path).to.equal(fixtures.emptyFile.cid.toString())\n    })\n\n    it('should add a Uint8Array as tuple', async () => {\n      const tuple = { path: 'testfile.txt', content: fixtures.smallFile.data }\n\n      const file = await ipfs.add(tuple)\n\n      expect(file.cid.toString()).to.equal(fixtures.smallFile.cid.toString())\n      expect(file.path).to.equal('testfile.txt')\n    })\n\n    it('should add a string', async () => {\n      const data = 'a string'\n      const expectedCid = 'QmQFRCwEpwQZ5aQMqCsCaFbdjNLLHoyZYDjr92v1F7HeqX'\n\n      const file = await ipfs.add(data)\n\n      expect(file).to.have.property('path', expectedCid)\n      expect(file).to.have.property('size', 16)\n      expect(`${file.cid}`).to.equal(expectedCid)\n    })\n\n    it('should add a TypedArray', async () => {\n      const data = Uint8Array.from([1, 3, 8])\n      const expectedCid = 'QmRyUEkVCuHC8eKNNJS9BDM9jqorUvnQJK1DM81hfngFqd'\n\n      const file = await ipfs.add(data)\n\n      expect(file).to.have.property('path', expectedCid)\n      expect(file).to.have.property('size', 11)\n      expect(`${file.cid}`).to.equal(expectedCid)\n    })\n\n    it('should add readable stream', async function () {\n      if (!isNode) {\n        this.skip()\n      }\n      const expectedCid = 'QmVv4Wz46JaZJeH5PMV4LGbRiiMKEmszPYY3g6fjGnVXBS'\n\n      const rs = new Readable()\n      rs.push(uint8ArrayFromString('some data'))\n      rs.push(null)\n\n      const file = await ipfs.add(rs)\n\n      expect(file).to.have.property('path', expectedCid)\n      expect(file).to.have.property('size', 17)\n      expect(`${file.cid}`).to.equal(expectedCid)\n    })\n\n    it('should fail when passed invalid input', async () => {\n      const nonValid = 138\n\n      // @ts-expect-error nonValid is non valid\n      await expect(ipfs.add(nonValid)).to.eventually.be.rejected()\n    })\n\n    it('should fail when passed undefined input', async () => {\n      // @ts-expect-error undefined is non valid\n      await expect(ipfs.add(undefined)).to.eventually.be.rejected()\n    })\n\n    it('should fail when passed null input', async () => {\n      // @ts-expect-error null is non valid\n      await expect(ipfs.add(null)).to.eventually.be.rejected()\n    })\n\n    it('should fail when passed multiple file objects', async () => {\n      const nonValid = [{ content: 'hello' }, { content: 'world' }]\n\n      // @ts-expect-error nonValid is non valid\n      await expect(ipfs.add(nonValid)).to.eventually.be.rejectedWith(/multiple items passed/)\n    })\n\n    it('should wrap content in a directory', async () => {\n      const data = { path: 'testfile.txt', content: fixtures.smallFile.data }\n\n      const wrapper = await ipfs.add(data, { wrapWithDirectory: true })\n      expect(wrapper.path).to.equal('')\n\n      const stats = await ipfs.files.stat(`/ipfs/${wrapper.cid}/testfile.txt`)\n\n      expect(`${stats.cid}`).to.equal(fixtures.smallFile.cid.toString())\n    })\n\n    it('should add with only-hash=true', async function () {\n      this.slow(10 * 1000)\n      const content = String(Math.random() + Date.now())\n\n      const file = await ipfs.add(content, { onlyHash: true })\n\n      await expect(ipfs.object.get(file.cid, { timeout: 4000 }))\n        .to.eventually.be.rejected()\n        .and.to.have.property('name').that.equals('TimeoutError')\n    })\n\n    it('should add with sha2-256 by default', async function () {\n      const content = String(Math.random() + Date.now())\n\n      const file = await ipfs.add(content)\n\n      expect(file).to.have.nested.property('cid.multihash.code', sha256.code)\n    })\n\n    it('should add with a different hashing algorithm', async function () {\n      const content = String(Math.random() + Date.now())\n\n      const file = await ipfs.add(content, { hashAlg: 'sha2-512' })\n\n      expect(file).to.have.nested.property('cid.multihash.code', sha512.code)\n    })\n\n    it('should add with mode as string', async function () {\n      this.slow(10 * 1000)\n      const mode = '0777'\n      await testMode(mode, parseInt(mode, 8))\n    })\n\n    it('should add with mode as number', async function () {\n      this.slow(10 * 1000)\n      const mode = parseInt('0777', 8)\n      await testMode(mode, mode)\n    })\n\n    it('should add with mtime as Date', async function () {\n      this.slow(10 * 1000)\n      const mtime = new Date(5000)\n      await testMtime(mtime, {\n        secs: 5,\n        nsecs: 0\n      })\n    })\n\n    it('should add with mtime as { nsecs, secs }', async function () {\n      this.slow(10 * 1000)\n      const mtime = {\n        secs: 5,\n        nsecs: 0\n      }\n      await testMtime(mtime, mtime)\n    })\n\n    it('should add with mtime as timespec', async function () {\n      this.slow(10 * 1000)\n      await testMtime({\n        Seconds: 5,\n        FractionalNanoseconds: 0\n      }, {\n        secs: 5,\n        nsecs: 0\n      })\n    })\n\n    it('should add with mtime as hrtime', async function () {\n      this.slow(10 * 1000)\n      const mtime = process.hrtime()\n      await testMtime(mtime, {\n        secs: mtime[0],\n        nsecs: mtime[1]\n      })\n    })\n\n    it('should add from a HTTP URL', async () => {\n      const text = `TEST${Math.random()}`\n      const url = echoUrl(text)\n\n      const [result, expectedResult] = await Promise.all([\n        ipfs.add(urlSource(url)),\n        ipfs.add(text)\n      ])\n\n      expect(result.cid.toString()).to.equal(expectedResult.cid.toString())\n      expect(result.size).to.equal(expectedResult.size)\n    })\n\n    it('should add from a HTTP URL with redirection', async () => {\n      const text = `TEST${Math.random()}`\n      const url = echoUrl(text)\n\n      const [result, expectedResult] = await Promise.all([\n        ipfs.add(urlSource(redirectUrl(url))),\n        ipfs.add(text)\n      ])\n\n      expect(result.cid.toString()).to.equal(expectedResult.cid.toString())\n      expect(result.size).to.equal(expectedResult.size)\n    })\n\n    it('should add from a URL with only-hash=true', async function () {\n      const text = `TEST${Math.random()}`\n      const url = echoUrl(text)\n\n      const res = await ipfs.add(urlSource(url), { onlyHash: true })\n\n      await expect(ipfs.object.get(res.cid, { timeout: 500 }))\n        .to.eventually.be.rejected()\n        .and.to.have.property('name').that.equals('TimeoutError')\n    })\n\n    it('should add from a URL with wrap-with-directory=true', async () => {\n      const filename = `TEST${Date.now()}.txt` // also acts as data\n      const url = echoUrl(filename)\n      const addOpts = { wrapWithDirectory: true }\n\n      const [result, expectedResult] = await Promise.all([\n        ipfs.add(urlSource(url), addOpts),\n        ipfs.add({ path: 'download', content: filename }, addOpts)\n      ])\n      expect(result).to.deep.equal(expectedResult)\n    })\n\n    it('should add from a URL with wrap-with-directory=true and URL-escaped file name', async () => {\n      const filename = `320px-Domažlice,_Jiráskova_43_(${Date.now()}).jpg` // also acts as data\n      const url = echoUrl(filename)\n      const addOpts = { wrapWithDirectory: true }\n\n      const [result, expectedResult] = await Promise.all([\n        ipfs.add(urlSource(url), addOpts),\n        ipfs.add({ path: 'download', content: filename }, addOpts)\n      ])\n\n      expect(result).to.deep.equal(expectedResult)\n    })\n\n    it('should not add from an invalid url', () => {\n      return expect(() => ipfs.add(urlSource('123http://invalid'))).to.throw()\n    })\n\n    it('should respect raw leaves when file is smaller than one block and no metadata is present', async () => {\n      const file = await ipfs.add(Uint8Array.from([0, 1, 2]), {\n        cidVersion: 1,\n        rawLeaves: true\n      })\n\n      expect(file.cid.toString()).to.equal('bafkreifojmzibzlof6xyh5auu3r5vpu5l67brf3fitaf73isdlglqw2t7q')\n      expect(file.cid.code).to.equal(raw.code)\n      expect(file.size).to.equal(3)\n    })\n\n    it('should override raw leaves when file is smaller than one block and metadata is present', async () => {\n      const file = await ipfs.add({\n        content: Uint8Array.from([0, 1, 2]),\n        mode: 0o123,\n        mtime: {\n          secs: 1000,\n          nsecs: 0\n        }\n      }, {\n        cidVersion: 1,\n        rawLeaves: true\n      })\n\n      expect(file.cid.toString()).to.equal('bafybeifmayxiu375ftlgydntjtffy5cssptjvxqw6vyuvtymntm37mpvua')\n      expect(file.cid.code).to.equal(dagPB.code)\n      expect(file.size).to.equal(18)\n    })\n\n    it('should add a file with a v1 CID', async () => {\n      const file = await ipfs.add(Uint8Array.from([0, 1, 2]), {\n        cidVersion: 1\n      })\n\n      expect(file.cid.toString()).to.equal('bafkreifojmzibzlof6xyh5auu3r5vpu5l67brf3fitaf73isdlglqw2t7q')\n      expect(file.size).to.equal(3)\n    })\n\n    const testFiles = Array.from(Array(1005), (_, i) => ({\n      path: 'test-folder/' + i,\n      content: uint8ArrayFromString('some content ' + i)\n    }))\n\n    it('should be able to add dir without sharding', async () => {\n      const result = await last(ipfs.addAll(testFiles))\n\n      if (!result) {\n        throw new Error('No addAll result received')\n      }\n\n      const { path, cid } = result\n      expect(path).to.eql('test-folder')\n      expect(cid.toString()).to.eql('QmWWM8ZV6GPhqJ46WtKcUaBPNHN5yQaFsKDSQ1RE73w94Q')\n    })\n\n    describe('with sharding', () => {\n      /** @type {import('ipfs-core-types').IPFS} */\n      let ipfs\n\n      before(async function () {\n        const ipfsd = await factory.spawn({\n          ipfsOptions: {\n            EXPERIMENTAL: {\n              // enable sharding for js\n              sharding: true\n            },\n            config: {\n              // enable sharding for go with automatic threshold dropped to the minimum so it shards everything\n              Internal: {\n                UnixFSShardingSizeThreshold: '1B'\n              }\n            }\n          }\n        })\n        ipfs = ipfsd.api\n      })\n\n      it('should be able to add dir with sharding', async () => {\n        const result = await last(ipfs.addAll(testFiles))\n\n        if (!result) {\n          throw new Error('No addAll result received')\n        }\n\n        const { path, cid } = result\n        expect(path).to.eql('test-folder')\n        expect(cid.toString()).to.eql('Qmb3JNLq2KcvDTSGT23qNQkMrr4Y4fYMktHh6DtC7YatLa')\n      })\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/bitswap/index.js",
    "content": "import { createSuite } from '../utils/suite.js'\nimport { testStat } from './stat.js'\nimport { testWantlist } from './wantlist.js'\nimport { testWantlistForPeer } from './wantlist-for-peer.js'\nimport { testTransfer } from './transfer.js'\nimport { testUnwant } from './unwant.js'\n\nconst tests = {\n  stat: testStat,\n  wantlist: testWantlist,\n  wantlistForPeer: testWantlistForPeer,\n  transfer: testTransfer,\n  unwant: testUnwant\n}\n\nexport default createSuite(tests)\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/bitswap/stat.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { expectIsBitswap } from '../stats/utils.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testStat (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.bitswap.stat', function () {\n    this.timeout(60 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should get bitswap stats', async () => {\n      const res = await ipfs.bitswap.stat()\n      expectIsBitswap(null, res)\n    })\n\n    it('should not get bitswap stats when offline', async () => {\n      const node = await factory.spawn()\n      await node.stop()\n\n      return expect(node.api.bitswap.stat()).to.eventually.be.rejected()\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/bitswap/transfer.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { isWebWorker } from 'ipfs-utils/src/env.js'\nimport { randomBytes } from 'iso-random-stream'\nimport concat from 'it-concat'\nimport { nanoid } from 'nanoid'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport pmap from 'p-map'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n * @typedef {import('multiformats').CID} CID\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testTransfer (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('transfer blocks', function () {\n    this.timeout(540 * 1000)\n\n    afterEach(() => factory.clean())\n\n    describe('transfer a block between', () => {\n      it('2 peers', async function () {\n        // webworkers are not dialable because webrtc is not available\n        const remote = (await factory.spawn({ type: isWebWorker ? 'go' : undefined })).api\n        const remoteId = await remote.id()\n        const local = (await factory.spawn({ type: 'proc' })).api\n        await local.swarm.connect(remoteId.addresses[0])\n        const data = uint8ArrayFromString(`IPFS is awesome ${nanoid()}`)\n\n        const cid = await local.block.put(data)\n        const b = await remote.block.get(cid)\n\n        expect(b).to.equalBytes(data)\n      })\n\n      it('3 peers', async () => {\n        const blocks = Array(6).fill(0).map(() => uint8ArrayFromString(`IPFS is awesome ${nanoid()}`))\n        const remote1 = (await factory.spawn({ type: isWebWorker ? 'go' : undefined })).api\n        const remote1Id = await remote1.id()\n        const remote2 = (await factory.spawn({ type: isWebWorker ? 'go' : undefined })).api\n        const remote2Id = await remote2.id()\n        const local = (await factory.spawn({ type: 'proc' })).api\n        await local.swarm.connect(remote1Id.addresses[0])\n        await local.swarm.connect(remote2Id.addresses[0])\n        await remote1.swarm.connect(remote2Id.addresses[0])\n\n        // order is important\n        /** @type {CID[]} */\n        const cids = []\n        cids.push(await remote1.block.put(blocks[0]))\n        cids.push(await remote1.block.put(blocks[1]))\n        cids.push(await remote2.block.put(blocks[2]))\n        cids.push(await remote2.block.put(blocks[3]))\n        cids.push(await local.block.put(blocks[4]))\n        cids.push(await local.block.put(blocks[5]))\n\n        await pmap(blocks, async (block, i) => {\n          expect(await remote1.block.get(cids[i])).to.eql(block)\n          expect(await remote2.block.get(cids[i])).to.eql(block)\n          expect(await local.block.get(cids[i])).to.eql(block)\n        }, { concurrency: 3 })\n      })\n    })\n\n    describe('transfer a file between', () => {\n      it('2 peers', async () => {\n        const content = randomBytes(1024)\n        const remote = (await factory.spawn({ type: isWebWorker ? 'go' : undefined })).api\n        const remoteId = await remote.id()\n        const local = (await factory.spawn({ type: 'proc' })).api\n        local.swarm.connect(remoteId.addresses[0])\n\n        const file = await remote.add({ path: 'awesome.txt', content })\n        const data = await concat(local.cat(file.cid))\n        expect(data.slice()).to.eql(content)\n      })\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/bitswap/unwant.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testUnwant (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.bitswap.unwant', function () {\n    this.timeout(60 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should throw error for invalid CID input', async () => {\n      // @ts-expect-error input is invalid\n      await expect(ipfs.bitswap.unwant('INVALID CID')).to.eventually.be.rejected()\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/bitswap/utils.js",
    "content": "import delay from 'delay'\n\n/**\n * @typedef {import('@libp2p/interface-peer-id').PeerId} PeerId\n */\n\n/**\n * @param {import('ipfs-core-types').IPFS} ipfs\n * @param {string} key\n * @param {{ timeout?: number, interval?: number, peerId?: PeerId }} [opts]\n */\nexport async function waitForWantlistKey (ipfs, key, opts = {}) {\n  opts.timeout = opts.timeout || 10000\n  opts.interval = opts.interval || 100\n\n  const end = Date.now() + opts.timeout\n\n  while (Date.now() < end) {\n    let list\n\n    if (opts.peerId) {\n      list = await ipfs.bitswap.wantlistForPeer(opts.peerId)\n    } else {\n      list = await ipfs.bitswap.wantlist()\n    }\n\n    if (list.some(cid => cid.toString() === key)) {\n      return\n    }\n\n    await delay(opts.interval)\n  }\n\n  throw new Error(`Timed out waiting for ${key} in wantlist`)\n}\n\n/**\n * @param {import('ipfs-core-types').IPFS} ipfs\n * @param {string} key\n * @param {{ timeout?: number, interval?: number, peerId?: PeerId }} [opts]\n */\nexport async function waitForWantlistKeyToBeRemoved (ipfs, key, opts = {}) {\n  opts.timeout = opts.timeout || 10000\n  opts.interval = opts.interval || 100\n\n  const end = Date.now() + opts.timeout\n\n  while (Date.now() < end) {\n    let list\n\n    if (opts.peerId) {\n      list = await ipfs.bitswap.wantlistForPeer(opts.peerId)\n    } else {\n      list = await ipfs.bitswap.wantlist()\n    }\n\n    if (list.some(cid => cid.toString() === key)) {\n      await delay(opts.interval)\n\n      continue\n    }\n\n    return\n  }\n\n  throw new Error(`Timed out waiting for ${key} to be removed from wantlist`)\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/bitswap/wantlist-for-peer.js",
    "content": "/* eslint-env mocha */\n\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { waitForWantlistKey } from './utils.js'\nimport { isWebWorker } from 'ipfs-utils/src/env.js'\nimport { CID } from 'multiformats/cid'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testWantlistForPeer (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.bitswap.wantlistForPeer', function () {\n    this.timeout(60 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfsA\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfsB\n    const key = 'QmUBdnXXPyoDFXj3Hj39dNJ5VkN3QFRskXxcGaYFBB8CNR'\n\n    before(async () => {\n      ipfsA = (await factory.spawn({ type: 'proc' })).api\n      // webworkers are not dialable because webrtc is not available\n      ipfsB = (await factory.spawn({ type: isWebWorker ? 'go' : undefined })).api\n      // Add key to the wantlist for ipfsB\n      ipfsB.block.get(CID.parse(key)).catch(() => { /* is ok, expected on teardown */ })\n\n      const ipfsBId = await ipfsB.id()\n\n      await ipfsA.swarm.connect(ipfsBId.addresses[0])\n    })\n\n    after(() => factory.clean())\n\n    it('should get the wantlist by peer ID for a different node', async () => {\n      const ipfsBId = await ipfsB.id()\n\n      return waitForWantlistKey(ipfsA, key, {\n        peerId: ipfsBId.id,\n        timeout: 60 * 1000\n      })\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/bitswap/wantlist.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { waitForWantlistKey, waitForWantlistKeyToBeRemoved } from './utils.js'\nimport { isWebWorker } from 'ipfs-utils/src/env.js'\nimport testTimeout from '../utils/test-timeout.js'\nimport { CID } from 'multiformats/cid'\nimport delay from 'delay'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testWantlist (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.bitswap.wantlist', function () {\n    this.timeout(60 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfsA\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfsB\n    const key = 'QmUBdnXXPyoDFXj3Hj39dNJ5VkN3QFRskXxcGaYFBB8CNR'\n\n    before(async () => {\n      ipfsA = (await factory.spawn({ type: 'proc' })).api\n      // webworkers are not dialable because webrtc is not available\n      ipfsB = (await factory.spawn({ type: isWebWorker ? 'go' : undefined })).api\n      // Add key to the wantlist for ipfsB\n      ipfsB.block.get(CID.parse(key)).catch(() => { /* is ok, expected on teardown */ })\n\n      const ipfsBId = await ipfsB.id()\n\n      await ipfsA.swarm.connect(ipfsBId.addresses[0])\n    })\n\n    after(() => factory.clean())\n\n    it('should respect timeout option when getting bitswap wantlist', () => {\n      return testTimeout(() => ipfsA.bitswap.wantlist({\n        timeout: 1\n      }))\n    })\n\n    it('should get the wantlist', function () {\n      return waitForWantlistKey(ipfsB, key)\n    })\n\n    it('should not get the wantlist when offline', async () => {\n      const node = await factory.spawn()\n      await node.stop()\n\n      return expect(node.api.bitswap.stat()).to.eventually.be.rejected()\n    })\n\n    it('should remove blocks from the wantlist when requests are cancelled', async () => {\n      const controller = new AbortController()\n      const cid = CID.parse('QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KaGa')\n\n      const getPromise = ipfsA.dag.get(cid, {\n        signal: controller.signal\n      })\n\n      await waitForWantlistKey(ipfsA, cid.toString())\n\n      controller.abort()\n\n      await expect(getPromise).to.eventually.be.rejectedWith(/aborted/)\n\n      await waitForWantlistKeyToBeRemoved(ipfsA, cid.toString())\n    })\n\n    it('should keep blocks in the wantlist when only one request is cancelled', async () => {\n      const controller = new AbortController()\n      const otherController = new AbortController()\n      const cid = CID.parse('QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1Kaaa')\n\n      const getPromise = ipfsA.dag.get(cid, {\n        signal: controller.signal\n      })\n      const otherGetPromise = ipfsA.dag.get(cid, {\n        signal: otherController.signal\n      })\n\n      await waitForWantlistKey(ipfsA, cid.toString())\n\n      controller.abort()\n\n      await expect(getPromise).to.eventually.be.rejectedWith(/aborted/)\n\n      await delay(1000)\n\n      // cid should still be in the wantlist\n      await waitForWantlistKey(ipfsA, cid.toString())\n\n      otherController.abort()\n\n      await expect(otherGetPromise).to.eventually.be.rejectedWith(/aborted/)\n\n      await waitForWantlistKeyToBeRemoved(ipfsA, cid.toString())\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/block/get.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { identity } from 'multiformats/hashes/identity'\nimport { CID } from 'multiformats/cid'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport testTimeout from '../utils/test-timeout.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testGet (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.block.get', () => {\n    const data = uint8ArrayFromString('blorb')\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n    /** @type {CID} */\n    let cid\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n      cid = await ipfs.block.put(data)\n    })\n\n    after(() => factory.clean())\n\n    it('should respect timeout option when getting a block', () => {\n      return testTimeout(() => ipfs.block.get(CID.parse('QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rA3'), {\n        timeout: 1\n      }))\n    })\n\n    it('should get by CID', async () => {\n      const block = await ipfs.block.get(cid)\n\n      expect(block).to.equalBytes(uint8ArrayFromString('blorb'))\n    })\n\n    it('should get an empty block', async () => {\n      const cid = await ipfs.block.put(new Uint8Array(0), {\n        format: 'dag-pb',\n        mhtype: 'sha2-256',\n        version: 0\n      })\n\n      const block = await ipfs.block.get(cid)\n      expect(block).to.equalBytes(new Uint8Array(0))\n    })\n\n    it('should get a block added as CIDv0 with a CIDv1', async () => {\n      const input = uint8ArrayFromString(`TEST${Math.random()}`)\n\n      const cidv0 = await ipfs.block.put(input)\n      expect(cidv0.version).to.equal(0)\n\n      const cidv1 = cidv0.toV1()\n\n      const block = await ipfs.block.get(cidv1)\n      expect(block).to.equalBytes(input)\n    })\n\n    it('should get a block added as CIDv1 with a CIDv0', async () => {\n      const input = uint8ArrayFromString(`TEST${Math.random()}`)\n\n      const cidv1 = await ipfs.block.put(input, {\n        version: 1,\n        format: 'dag-pb'\n      })\n      expect(cidv1.version).to.equal(1)\n\n      const cidv0 = cidv1.toV0()\n\n      const block = await ipfs.block.get(cidv0)\n      expect(block).to.equalBytes(input)\n    })\n\n    it('should get a block with an identity CID, without putting first', async () => {\n      const identityData = uint8ArrayFromString('A16461736466190144', 'base16upper')\n      const identityHash = await identity.digest(identityData)\n      const identityCID = CID.createV1(identity.code, identityHash)\n      const block = await ipfs.block.get(identityCID)\n      expect(block).to.equalBytes(identityData)\n    })\n\n    it('should return an error for an invalid CID', () => {\n      // @ts-expect-error invalid input\n      return expect(ipfs.block.get('Non-base58 character')).to.eventually.be.rejected\n        .and.be.an.instanceOf(Error)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/block/index.js",
    "content": "import { createSuite } from '../utils/suite.js'\nimport { testGet } from './get.js'\nimport { testPut } from './put.js'\nimport { testRm } from './rm.js'\nimport { testStat } from './stat.js'\n\nconst tests = {\n  get: testGet,\n  put: testPut,\n  rm: testRm,\n  stat: testStat\n}\n\nexport default createSuite(tests)\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/block/put.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { base58btc } from 'multiformats/bases/base58'\nimport { CID } from 'multiformats/cid'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport all from 'it-all'\nimport * as raw from 'multiformats/codecs/raw'\nimport { sha512 } from 'multiformats/hashes/sha2'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testPut (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.block.put', () => {\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should put a buffer, using defaults', async () => {\n      const expectedHash = 'QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ'\n      const blob = uint8ArrayFromString('blorb')\n\n      const cid = await ipfs.block.put(blob)\n\n      expect(cid.toString()).to.equal(expectedHash)\n      expect(cid.bytes).to.equalBytes(base58btc.decode(`z${expectedHash}`))\n    })\n\n    it('should put a buffer, using options', async () => {\n      const blob = uint8ArrayFromString(`TEST${Math.random()}`)\n\n      const cid = await ipfs.block.put(blob, {\n        format: 'raw',\n        mhtype: 'sha2-512',\n        version: 1,\n        pin: true\n      })\n\n      expect(cid.version).to.equal(1)\n      expect(cid.code).to.equal(raw.code)\n      expect(cid.multihash.code).to.equal(sha512.code)\n\n      expect(await all(ipfs.pin.ls({ paths: cid }))).to.have.lengthOf(1)\n    })\n\n    it('should put a Block instance', async () => {\n      const expectedHash = 'QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ'\n      const expectedCID = CID.parse(expectedHash)\n      const b = uint8ArrayFromString('blorb')\n\n      const cid = await ipfs.block.put(b)\n\n      expect(cid.multihash.bytes).to.equalBytes(expectedCID.multihash.bytes)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/block/rm.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { nanoid } from 'nanoid'\nimport all from 'it-all'\nimport last from 'it-last'\nimport drain from 'it-drain'\nimport { CID } from 'multiformats/cid'\nimport * as raw from 'multiformats/codecs/raw'\nimport testTimeout from '../utils/test-timeout.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testRm (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.block.rm', () => {\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => { ipfs = (await factory.spawn()).api })\n\n    after(() => factory.clean())\n\n    it('should respect timeout option when removing a block', () => {\n      return testTimeout(() => drain(ipfs.block.rm(CID.parse('QmVwdDCY4SPGVFnNCiZnX5CtzwWDn6kAM98JXzKxE3kCmn'), {\n        timeout: 1\n      })))\n    })\n\n    it('should remove by CID object', async () => {\n      const cid = await ipfs.dag.put(uint8ArrayFromString(nanoid()), {\n        storeCodec: 'raw',\n        hashAlg: 'sha2-256'\n      })\n\n      // block should be present in the local store\n      const localRefs = await all(ipfs.refs.local())\n      expect(localRefs).to.have.property('length').that.is.greaterThan(0)\n      expect(localRefs.find(ref => ref.ref === CID.createV1(raw.code, cid.multihash).toString())).to.be.ok()\n\n      const result = await all(ipfs.block.rm(cid))\n      expect(result).to.be.an('array').and.to.have.lengthOf(1)\n      expect(result[0].cid.toString()).equal(cid.toString())\n      expect(result[0]).to.not.have.property('error')\n\n      // did we actually remove the block?\n      const localRefsAfterRemove = await all(ipfs.refs.local())\n      expect(localRefsAfterRemove.find(ref => ref.ref === CID.createV1(raw.code, cid.multihash).toString())).to.not.be.ok()\n    })\n\n    it('should remove multiple CIDs', async () => {\n      const cids = await Promise.all([\n        ipfs.dag.put(uint8ArrayFromString(nanoid()), {\n          storeCodec: 'raw',\n          hashAlg: 'sha2-256'\n        }),\n        ipfs.dag.put(uint8ArrayFromString(nanoid()), {\n          storeCodec: 'raw',\n          hashAlg: 'sha2-256'\n        }),\n        ipfs.dag.put(uint8ArrayFromString(nanoid()), {\n          storeCodec: 'raw',\n          hashAlg: 'sha2-256'\n        })\n      ])\n\n      const result = await all(ipfs.block.rm(cids))\n\n      expect(result).to.have.lengthOf(3)\n\n      result.forEach((res) => {\n        expect(cids.map(cid => cid.toString())).to.include(res.cid.toString())\n        expect(res).to.not.have.property('error')\n      })\n    })\n\n    it('should error when removing non-existent blocks', async () => {\n      const cid = await ipfs.dag.put(uint8ArrayFromString(nanoid()), {\n        storeCodec: 'raw',\n        hashAlg: 'sha2-256'\n      })\n\n      // remove it\n      await all(ipfs.block.rm(cid))\n\n      // remove it again\n      const result = await all(ipfs.block.rm(cid))\n\n      expect(result).to.be.an('array').and.to.have.lengthOf(1)\n      expect(result).to.have.nested.property('[0].error.message').that.includes('block not found')\n    })\n\n    it('should not error when force removing non-existent blocks', async () => {\n      const cid = await ipfs.dag.put(uint8ArrayFromString(nanoid()), {\n        storeCodec: 'raw',\n        hashAlg: 'sha2-256'\n      })\n\n      // remove it\n      await all(ipfs.block.rm(cid))\n\n      // remove it again\n      const result = await all(ipfs.block.rm(cid, { force: true }))\n\n      expect(result).to.be.an('array').and.to.have.lengthOf(1)\n      expect(result[0].cid.toString()).to.equal(cid.toString())\n      expect(result[0]).to.not.have.property('error')\n    })\n\n    it('should return empty output when removing blocks quietly', async () => {\n      const cid = await ipfs.dag.put(uint8ArrayFromString(nanoid()), {\n        storeCodec: 'raw',\n        hashAlg: 'sha2-256'\n      })\n      const result = await all(ipfs.block.rm(cid, { quiet: true }))\n\n      expect(result).to.be.an('array').and.to.have.lengthOf(0)\n    })\n\n    it('should error when removing pinned blocks', async () => {\n      const cid = await ipfs.dag.put(uint8ArrayFromString(nanoid()), {\n        storeCodec: 'raw',\n        hashAlg: 'sha2-256'\n      })\n      await ipfs.pin.add(cid)\n\n      const result = await last(ipfs.block.rm(cid))\n\n      expect(result).to.have.property('error').that.is.an('Error')\n        .with.property('message').that.includes('pinned')\n    })\n\n    it('should throw error for invalid CID input', () => {\n      // @ts-expect-error invalid input\n      return expect(all(ipfs.block.rm('INVALID CID')))\n        .to.eventually.be.rejected()\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/block/stat.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { CID } from 'multiformats/cid'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport testTimeout from '../utils/test-timeout.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testStat (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.block.stat', () => {\n    const data = uint8ArrayFromString('blorb')\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n    /** @type {CID} */\n    let cid\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n      cid = await ipfs.block.put(data)\n    })\n\n    after(() => factory.clean())\n\n    it('should respect timeout option when statting a block', () => {\n      return testTimeout(() => ipfs.block.stat(CID.parse('QmVwdDCY4SPGVFnNCiZnX5CtzwWDn6kAM98JXzKxE3kCmn'), {\n        timeout: 1\n      }))\n    })\n\n    it('should stat by CID', async () => {\n      const stats = await ipfs.block.stat(cid)\n      expect(stats.cid.toString()).to.equal(cid.toString())\n      expect(stats).to.have.property('size', data.length)\n    })\n\n    it('should return error for missing argument', () => {\n      // @ts-expect-error invalid input\n      return expect(ipfs.block.stat(null)).to.eventually.be.rejected\n        .and.be.an.instanceOf(Error)\n    })\n\n    it('should return error for invalid argument', () => {\n      // @ts-expect-error invalid input\n      return expect(ipfs.block.stat('invalid')).to.eventually.be.rejected\n        .and.be.an.instanceOf(Error)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/bootstrap/add.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { multiaddr, isMultiaddr } from '@multiformats/multiaddr'\n\nconst invalidArg = 'this/Is/So/Invalid/'\nconst validIp4 = multiaddr('/ip4/104.236.176.52/tcp/4001/p2p/QmSoLnSGccFuZQJzRadHn95W2CrSFmZuTdDWP8HXaHca9z')\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testAdd (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.bootstrap.add', function () {\n    this.timeout(100 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should return an error when called with an invalid arg', () => {\n      // @ts-expect-error invalid input\n      return expect(ipfs.bootstrap.add(invalidArg)).to.eventually.be.rejected\n        .and.be.an.instanceOf(Error)\n    })\n\n    it('should return a list containing the bootstrap peer when called with a valid arg (ip4)', async () => {\n      const res = await ipfs.bootstrap.add(validIp4)\n\n      expect(res).to.be.eql({ Peers: [validIp4] })\n      const peers = res.Peers\n      expect(peers).to.have.property('length').that.is.equal(1)\n    })\n\n    it('should prevent duplicate inserts of bootstrap peers', async () => {\n      await ipfs.bootstrap.clear()\n\n      const added = await ipfs.bootstrap.add(validIp4)\n      expect(added).to.have.property('Peers').that.deep.equals([validIp4])\n\n      const addedAgain = await ipfs.bootstrap.add(validIp4)\n      expect(addedAgain).to.have.property('Peers').that.deep.equals([validIp4])\n\n      const list = await ipfs.bootstrap.list()\n      expect(list).to.have.property('Peers').that.deep.equals([validIp4])\n    })\n\n    it('add a peer to the bootstrap list', async () => {\n      const peer = multiaddr('/ip4/111.111.111.111/tcp/1001/p2p/QmXFX2P5ammdmXQgfqGkfswtEVFsZUJ5KeHRXQYCTdiTAb')\n\n      const res = await ipfs.bootstrap.add(peer)\n      expect(res).to.be.eql({ Peers: [peer] })\n\n      const list = await ipfs.bootstrap.list()\n      expect(list.Peers).to.deep.include(peer)\n\n      expect(list.Peers.every(ma => isMultiaddr(ma))).to.be.true()\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/bootstrap/clear.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { multiaddr, isMultiaddr } from '@multiformats/multiaddr'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testClear (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  const validIp4 = multiaddr('/ip4/104.236.176.52/tcp/4001/p2p/QmSoLnSGccFuZQJzRadHn95W2CrSFmZuTdDWP8HXaHca9z')\n\n  describe('.bootstrap.clear', function () {\n    this.timeout(100 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => { ipfs = (await factory.spawn()).api })\n\n    after(() => factory.clean())\n\n    it('should return a list containing the peer removed when called with a valid arg (ip4)', async () => {\n      await ipfs.bootstrap.clear()\n\n      const addRes = await ipfs.bootstrap.add(validIp4)\n      expect(addRes).to.be.eql({ Peers: [validIp4] })\n\n      const rmRes = await ipfs.bootstrap.clear()\n      expect(rmRes).to.be.eql({ Peers: [validIp4] })\n\n      const peers = rmRes.Peers\n      expect(peers).to.have.property('length').that.is.equal(1)\n    })\n\n    it('should return a list of all peers removed when all option is passed', async () => {\n      const addRes = await ipfs.bootstrap.reset()\n      const addedPeers = addRes.Peers\n\n      const rmRes = await ipfs.bootstrap.clear()\n      const removedPeers = rmRes.Peers\n\n      expect(removedPeers.sort()).to.deep.equal(addedPeers.sort())\n\n      expect(removedPeers.every(ma => isMultiaddr(ma))).to.be.true()\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/bootstrap/index.js",
    "content": "import { createSuite } from '../utils/suite.js'\nimport { testAdd } from './add.js'\nimport { testClear } from './clear.js'\nimport { testList } from './list.js'\nimport { testReset } from './reset.js'\nimport { testRm } from './rm.js'\n\nconst tests = {\n  add: testAdd,\n  clear: testClear,\n  list: testList,\n  reset: testReset,\n  rm: testRm\n}\n\nexport default createSuite(tests)\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/bootstrap/list.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { isMultiaddr } from '@multiformats/multiaddr'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testList (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.bootstrap.list', function () {\n    this.timeout(100 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => { ipfs = (await factory.spawn()).api })\n\n    after(() => factory.clean())\n\n    it('should return a list of peers', async () => {\n      const res = await ipfs.bootstrap.list()\n\n      const peers = res.Peers\n      expect(peers).to.be.an('Array')\n      expect(peers.every(ma => isMultiaddr(ma))).to.be.true()\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/bootstrap/reset.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { isMultiaddr } from '@multiformats/multiaddr'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testReset (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.bootstrap.reset', function () {\n    this.timeout(100 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should return a list of bootstrap peers when resetting the bootstrap nodes', async () => {\n      const res = await ipfs.bootstrap.reset()\n\n      const peers = res.Peers\n      expect(peers).to.have.property('length').that.is.gt(1)\n    })\n\n    it('should return a list of all peers removed when all option is passed', async () => {\n      const addRes = await ipfs.bootstrap.reset()\n      const addedPeers = addRes.Peers\n\n      const rmRes = await ipfs.bootstrap.clear()\n      const removedPeers = rmRes.Peers\n\n      expect(removedPeers.sort()).to.deep.equal(addedPeers.sort())\n      expect(addedPeers.every(ma => isMultiaddr(ma))).to.be.true()\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/bootstrap/rm.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { multiaddr, isMultiaddr } from '@multiformats/multiaddr'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testRm (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  const invalidArg = 'this/Is/So/Invalid/'\n  const validIp4 = multiaddr('/ip4/104.236.176.52/tcp/4001/p2p/QmSoLnSGccFuZQJzRadHn95W2CrSFmZuTdDWP8HXaHca9z')\n\n  describe('.bootstrap.rm', function () {\n    this.timeout(100 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => { ipfs = (await factory.spawn()).api })\n\n    after(() => factory.clean())\n\n    it('should return an error when called with an invalid arg', () => {\n      // @ts-expect-error invalid input\n      return expect(ipfs.bootstrap.rm(invalidArg)).to.eventually.be.rejected\n        .and.be.an.instanceOf(Error)\n    })\n\n    it('should return a list containing the peer removed when called with a valid arg (ip4)', async () => {\n      const addRes = await ipfs.bootstrap.add(validIp4)\n      expect(addRes).to.be.eql({ Peers: [validIp4] })\n\n      const rmRes = await ipfs.bootstrap.rm(validIp4)\n      expect(rmRes).to.be.eql({ Peers: [validIp4] })\n\n      const peers = rmRes.Peers\n      expect(peers).to.have.property('length').that.is.equal(1)\n    })\n\n    it('removes a peer from the bootstrap list', async () => {\n      const peer = multiaddr('/ip4/111.111.111.111/tcp/1001/p2p/QmXFX2P5ammdmXQgfqGkfswtEVFsZUJ5KeHRXQYCTdiTAb')\n      await ipfs.bootstrap.add(peer)\n      let list = await ipfs.bootstrap.list()\n      expect(list.Peers).to.deep.include(peer)\n\n      const res = await ipfs.bootstrap.rm(peer)\n      expect(res).to.be.eql({ Peers: [peer] })\n\n      list = await ipfs.bootstrap.list()\n      expect(list.Peers).to.not.deep.include(peer)\n      expect(res.Peers.every(ma => isMultiaddr(ma))).to.be.true()\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/cat.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\nimport { concat as uint8ArrayConcat } from 'uint8arrays/concat'\nimport { fixtures } from './utils/index.js'\nimport { CID } from 'multiformats/cid'\nimport all from 'it-all'\nimport drain from 'it-drain'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from './utils/mocha.js'\nimport testTimeout from './utils/test-timeout.js'\nimport { importer } from 'ipfs-unixfs-importer'\nimport blockstore from './utils/blockstore-adapter.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testCat (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.cat', function () {\n    this.timeout(120 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => { ipfs = (await factory.spawn()).api })\n\n    after(() => factory.clean())\n\n    before(() => Promise.all([\n      all(importer({ content: fixtures.smallFile.data }, blockstore(ipfs))),\n      all(importer({ content: fixtures.bigFile.data }, blockstore(ipfs)))\n    ]))\n\n    it('should respect timeout option when catting files', () => {\n      return testTimeout(() => drain(ipfs.cat(CID.parse('QmPDqvcuA4AkhBLBuh2y49yhUB98rCnxPxa3eVNC1kAbS1'), {\n        timeout: 1\n      })))\n    })\n\n    it('should cat with a base58 string encoded multihash', async () => {\n      const data = uint8ArrayConcat(await all(ipfs.cat(fixtures.smallFile.cid)))\n      expect(uint8ArrayToString(data)).to.contain('Plz add me!')\n    })\n\n    it('should cat with a Uint8Array multihash', async () => {\n      const cid = fixtures.smallFile.cid\n\n      const data = uint8ArrayConcat(await all(ipfs.cat(cid)))\n      expect(uint8ArrayToString(data)).to.contain('Plz add me!')\n    })\n\n    it('should cat with a CID object', async () => {\n      const cid = fixtures.smallFile.cid\n\n      const data = uint8ArrayConcat(await all(ipfs.cat(cid)))\n      expect(uint8ArrayToString(data)).to.contain('Plz add me!')\n    })\n\n    it('should cat a file added as CIDv0 with a CIDv1', async () => {\n      const input = uint8ArrayFromString(`TEST${Math.random()}`)\n\n      const res = await all(importer([{ content: (async function * () { yield input }()) }], blockstore(ipfs)))\n\n      expect(res).to.have.nested.property('[0].cid.version', 0)\n\n      const cidv1 = res[0].cid.toV1()\n\n      const output = uint8ArrayConcat(await all(ipfs.cat(cidv1)))\n      expect(output).to.eql(input)\n    })\n\n    it('should cat a file added as CIDv1 with a CIDv0', async () => {\n      const input = uint8ArrayFromString(`TEST${Math.random()}`)\n\n      const res = await all(importer([{ content: (async function * () { yield input }()) }], blockstore(ipfs), { cidVersion: 1, rawLeaves: false }))\n\n      expect(res).to.have.nested.property('[0].cid.version', 1)\n\n      const cidv0 = res[0].cid.toV0()\n\n      const output = uint8ArrayConcat(await all(ipfs.cat(cidv0)))\n      expect(output.slice()).to.eql(input)\n    })\n\n    it('should cat a BIG file', async () => {\n      const data = uint8ArrayConcat(await all(ipfs.cat(fixtures.bigFile.cid)))\n      expect(data.length).to.equal(fixtures.bigFile.data.length)\n      expect(data.slice()).to.eql(fixtures.bigFile.data)\n    })\n\n    it('should cat with IPFS path', async () => {\n      const ipfsPath = '/ipfs/' + fixtures.smallFile.cid\n\n      const data = uint8ArrayConcat(await all(ipfs.cat(ipfsPath)))\n      expect(uint8ArrayToString(data)).to.contain('Plz add me!')\n    })\n\n    it('should cat with IPFS path, nested value', async () => {\n      const fileToAdd = { path: 'a/testfile.txt', content: fixtures.smallFile.data }\n\n      const filesAdded = await all(importer(fileToAdd, blockstore(ipfs)))\n\n      const file = await filesAdded.find((f) => f.path === 'a')\n      expect(file).to.exist()\n\n      if (!file) {\n        throw new Error('No file added')\n      }\n\n      const data = uint8ArrayConcat(await all(ipfs.cat(`/ipfs/${file.cid}/testfile.txt`)))\n\n      expect(uint8ArrayToString(data)).to.contain('Plz add me!')\n    })\n\n    it('should cat with IPFS path, deeply nested value', async () => {\n      const fileToAdd = { path: 'a/b/testfile.txt', content: fixtures.smallFile.data }\n\n      const filesAdded = await all(importer([fileToAdd], blockstore(ipfs)))\n\n      const file = filesAdded.find((f) => f.path === 'a')\n      expect(file).to.exist()\n\n      if (!file) {\n        throw new Error('No file added')\n      }\n\n      const data = uint8ArrayConcat(await all(ipfs.cat(`/ipfs/${file.cid}/b/testfile.txt`)))\n      expect(uint8ArrayToString(data)).to.contain('Plz add me!')\n    })\n\n    it('should error on invalid key', () => {\n      const invalidCid = 'somethingNotMultihash'\n\n      return expect(drain(ipfs.cat(invalidCid))).to.eventually.be.rejected()\n    })\n\n    it('should error on unknown path', () => {\n      return expect(drain(ipfs.cat(fixtures.smallFile.cid + '/does-not-exist'))).to.eventually.be.rejected()\n        .and.be.an.instanceOf(Error)\n        .and.to.have.property('message')\n        .to.be.oneOf([\n          'file does not exist',\n          'no link named \"does-not-exist\" under Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP'\n        ])\n    })\n\n    it('should error on dir path', async () => {\n      const file = { path: 'dir/testfile.txt', content: fixtures.smallFile.data }\n\n      const filesAdded = await all(importer([file], blockstore(ipfs)))\n      expect(filesAdded.length).to.equal(2)\n\n      const files = filesAdded.filter((file) => file.path === 'dir')\n      expect(files.length).to.equal(1)\n\n      const dir = files[0]\n\n      const err = await expect(drain(ipfs.cat(dir.cid))).to.eventually.be.rejected()\n      expect(err.message).to.contain('this dag node is a directory')\n    })\n\n    it('should export a chunk of a file', async () => {\n      const offset = 1\n      const length = 3\n\n      const data = uint8ArrayConcat(await all(ipfs.cat(fixtures.smallFile.cid, { offset, length })))\n      expect(uint8ArrayToString(data)).to.equal('lz ')\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/config/get.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testGet (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.config.get', function () {\n    this.timeout(30 * 1000)\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => { ipfs = (await factory.spawn()).api })\n\n    after(() => factory.clean())\n\n    it('should fail with error', async () => {\n      // @ts-expect-error missing arg\n      await expect(ipfs.config.get()).to.eventually.rejectedWith('key argument is required')\n    })\n\n    it('should retrieve a value through a key', async () => {\n      const peerId = await ipfs.config.get('Identity.PeerID')\n      expect(peerId).to.exist()\n    })\n\n    it('should retrieve a value through a nested key', async () => {\n      const swarmAddrs = await ipfs.config.get('Addresses.Swarm')\n      expect(swarmAddrs).to.exist()\n    })\n\n    it('should fail on non valid key', () => {\n      // @ts-expect-error invalid arg\n      return expect(ipfs.config.get(1234)).to.eventually.be.rejected()\n    })\n\n    it('should fail on non existent key', () => {\n      return expect(ipfs.config.get('Bananas')).to.eventually.be.rejected()\n    })\n  })\n\n  describe('.config.getAll', function () {\n    this.timeout(30 * 1000)\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => { ipfs = (await factory.spawn()).api })\n\n    after(() => factory.clean())\n\n    it('should retrieve the whole config', async () => {\n      const config = await ipfs.config.getAll()\n\n      expect(config).to.be.an('object')\n    })\n\n    it('should retrieve the whole config with options', async () => {\n      const config = await ipfs.config.getAll({ signal: undefined })\n\n      expect(config).to.be.an('object')\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/config/index.js",
    "content": "import { createSuite } from '../utils/suite.js'\nimport { testGet } from './get.js'\nimport { testSet } from './set.js'\nimport { testReplace } from './replace.js'\nimport profiles from './profiles/index.js'\n\nconst tests = {\n  get: testGet,\n  set: testSet,\n  replace: testReplace,\n  profiles\n}\n\nexport default createSuite(tests)\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/config/profiles/apply.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../../utils/mocha.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testApply (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.config.profiles.apply', function () {\n    this.timeout(30 * 1000)\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should apply a config profile', async () => {\n      const diff = await ipfs.config.profiles.apply('lowpower')\n      expect(diff.original.Swarm?.ConnMgr?.LowWater).to.not.equal(diff.updated.Swarm?.ConnMgr?.LowWater)\n\n      const newConfig = await ipfs.config.getAll()\n      expect(newConfig.Swarm?.ConnMgr?.LowWater).to.equal(diff.updated.Swarm?.ConnMgr?.LowWater)\n    })\n\n    it('should strip private key from diff output', async () => {\n      const originalConfig = await ipfs.config.getAll()\n      const diff = await ipfs.config.profiles.apply('default-networking', { dryRun: true })\n\n      // should have stripped private key from diff output\n      expect(originalConfig).to.have.nested.property('Identity.PrivKey')\n      expect(diff).to.not.have.nested.property('original.Identity.PrivKey')\n      expect(diff).to.not.have.nested.property('updated.Identity.PrivKey')\n    })\n\n    it('should not apply a config profile in dry-run mode', async () => {\n      const originalConfig = await ipfs.config.getAll()\n\n      await ipfs.config.profiles.apply('server', { dryRun: true })\n\n      const updatedConfig = await ipfs.config.getAll()\n\n      expect(updatedConfig).to.deep.equal(originalConfig)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/config/profiles/index.js",
    "content": "import { createSuite } from '../../utils/suite.js'\nimport { testApply } from './apply.js'\nimport { testList } from './list.js'\n\nconst tests = {\n  apply: testApply,\n  list: testList\n}\n\nexport default createSuite(tests, 'config')\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/config/profiles/list.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../../utils/mocha.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testList (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.config.profiles.list', function () {\n    this.timeout(30 * 1000)\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should list config profiles', async () => {\n      const profiles = await ipfs.config.profiles.list()\n\n      expect(profiles).to.be.an('array')\n      expect(profiles).not.to.be.empty()\n\n      profiles.forEach(profile => {\n        expect(profile.name).to.be.a('string')\n        expect(profile.description).to.be.a('string')\n      })\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/config/replace.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testReplace (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.config.replace', function () {\n    this.timeout(30 * 1000)\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    const config = {\n      Addresses: {\n        API: ''\n      }\n    }\n\n    it('should replace the whole config', async () => {\n      await ipfs.config.replace(config)\n\n      const _config = await ipfs.config.getAll()\n      expect(_config).to.deep.equal(config)\n    })\n\n    it('should replace to empty config', async () => {\n      await ipfs.config.replace({})\n\n      const _config = await ipfs.config.getAll()\n      expect(_config).to.deep.equal({})\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/config/set.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testSet (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.config.set', function () {\n    this.timeout(30 * 1000)\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should set a new key', async () => {\n      await ipfs.config.set('Fruit', 'banana')\n\n      const fruit = await ipfs.config.get('Fruit')\n      expect(fruit).to.equal('banana')\n    })\n\n    it('should set an already existing key', async () => {\n      await ipfs.config.set('Fruit', 'morango')\n\n      const fruit = await ipfs.config.get('Fruit')\n      expect(fruit).to.equal('morango')\n    })\n\n    it('should set a number', async () => {\n      const key = 'Discovery.MDNS.Interval'\n      const val = 11\n\n      await ipfs.config.set(key, val)\n\n      const result = await ipfs.config.get(key)\n      expect(result).to.equal(val)\n    })\n\n    it('should set a boolean', async () => {\n      const value = true\n      const key = 'Discovery.MDNS.Enabled'\n\n      await ipfs.config.set(key, value)\n      expect(await ipfs.config.get(key)).to.equal(value)\n    })\n\n    it('should set the other boolean', async () => {\n      const value = false\n      const key = 'Discovery.MDNS.Enabled'\n\n      await ipfs.config.set(key, value)\n      expect(await ipfs.config.get(key)).to.equal(value)\n    })\n\n    it('should set a JSON object', async () => {\n      const key = 'API.HTTPHeaders.Access-Control-Allow-Origin'\n      const val = ['http://example.io']\n\n      await ipfs.config.set(key, val)\n\n      const result = await ipfs.config.get(key)\n      expect(result).to.deep.equal(val)\n    })\n\n    it('should fail on non valid key', () => {\n      // @ts-expect-error invalid arg\n      return expect(ipfs.config.set(uint8ArrayFromString('heeey'), '')).to.eventually.be.rejected()\n    })\n\n    it('should fail on non valid value', () => {\n      const val = {}\n      val.val = val\n      return expect(ipfs.config.set('Fruit', val)).to.eventually.be.rejected()\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/dag/export.js",
    "content": "/* eslint-env mocha */\n\nimport all from 'it-all'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { CarReader } from '@ipld/car'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport * as dagPB from '@ipld/dag-pb'\nimport * as dagCBOR from '@ipld/dag-cbor'\nimport loadFixture from 'aegir/fixtures'\nimport toBuffer from 'it-to-buffer'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testExport (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.dag.export', () => {\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should export a car file', async () => {\n      const child = dagPB.encode({\n        Data: uint8ArrayFromString('block-' + Math.random()),\n        Links: []\n      })\n      const childCid = await ipfs.block.put(child, {\n        format: 'dag-pb',\n        version: 0\n      })\n      const parent = dagPB.encode({\n        Links: [{\n          Hash: childCid,\n          Tsize: child.length,\n          Name: ''\n        }]\n      })\n      const parentCid = await ipfs.block.put(parent, {\n        format: 'dag-pb',\n        version: 0\n      })\n      const grandParent = dagCBOR.encode({\n        parent: parentCid\n      })\n      const grandParentCid = await await ipfs.block.put(grandParent, {\n        format: 'dag-cbor',\n        version: 1\n      })\n\n      const expectedCids = [\n        grandParentCid,\n        parentCid,\n        childCid\n      ]\n\n      const reader = await CarReader.fromIterable(ipfs.dag.export(grandParentCid))\n      const cids = await all(reader.cids())\n\n      expect(cids).to.deep.equal(expectedCids)\n    })\n\n    it('export of shuffled devnet export identical to canonical original', async function () {\n      this.timeout(360000)\n\n      const input = loadFixture('test/fixtures/car/lotus_devnet_genesis.car', 'interface-ipfs-core')\n      const result = await all(ipfs.dag.import(async function * () { yield input }()))\n      const exported = await toBuffer(ipfs.dag.export(result[0].root.cid))\n\n      expect(exported).to.equalBytes(input)\n    })\n\n    it('export of shuffled testnet export identical to canonical original', async function () {\n      this.timeout(360000)\n\n      const input = loadFixture('test/fixtures/car/lotus_testnet_export_128.car', 'interface-ipfs-core')\n      const result = await all(ipfs.dag.import(async function * () { yield input }()))\n      const exported = await toBuffer(ipfs.dag.export(result[0].root.cid))\n\n      expect(exported).to.equalBytes(input)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/dag/get.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport * as dagPB from '@ipld/dag-pb'\nimport * as dagCBOR from '@ipld/dag-cbor'\nimport * as dagJOSE from 'dag-jose'\nimport { importer } from 'ipfs-unixfs-importer'\nimport { UnixFS } from 'ipfs-unixfs'\nimport all from 'it-all'\nimport { CID } from 'multiformats/cid'\nimport { sha256 } from 'multiformats/hashes/sha2'\nimport { base32 } from 'multiformats/bases/base32'\nimport { base64url } from 'multiformats/bases/base64'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport testTimeout from '../utils/test-timeout.js'\nimport { identity } from 'multiformats/hashes/identity'\nimport blockstore from '../utils/blockstore-adapter.js'\nimport { ES256KSigner, createJWS } from 'did-jwt'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testGet (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.dag.get', () => {\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n    before(async () => { ipfs = (await factory.spawn()).api })\n\n    after(() => factory.clean())\n\n    /**\n     * @type {dagPB.PBNode}\n     */\n    let pbNode\n    /**\n     * @type {any}\n     */\n    let cborNode\n    /**\n     * @type {dagJOSE.DagJWE}\n     */\n    let joseNode\n    /**\n     * @type {dagPB.PBNode}\n     */\n    let nodePb\n    /**\n     * @type {any}\n     */\n    let nodeCbor\n    /**\n     * @type {string}\n     */\n    let nodeJose\n    /**\n     * @type {CID}\n     */\n    let cidPb\n    /**\n     * @type {CID}\n     */\n    let cidCbor\n    /**\n     * @type {CID}\n     */\n    let cidJose\n\n    before(async () => {\n      const someData = uint8ArrayFromString('some other data')\n      pbNode = {\n        Data: someData,\n        Links: []\n      }\n      cborNode = {\n        data: someData\n      }\n      joseNode = {\n        protected: 'eyJhbGciOiJkaXIiLCJlbmMiOiJYQzIwUCJ9',\n        iv: 'DhVb9URR_o_85MOl-hCellwPTtQ_dj6d',\n        ciphertext: 'EtUsNJcKzEKdFM9DW5Ua5tVyaQRCKsAD',\n        tag: '-vG17pRSVB2Vycf2MZRgBA'\n      }\n\n      nodePb = {\n        Data: uint8ArrayFromString('I am inside a Protobuf'),\n        Links: []\n      }\n      cidPb = CID.createV1(dagPB.code, await sha256.digest(dagPB.encode(nodePb)))\n      nodeCbor = {\n        someData: 'I am inside a Cbor object',\n        pb: cidPb\n      }\n\n      cidCbor = CID.createV1(dagCBOR.code, await sha256.digest(dagCBOR.encode(nodeCbor)))\n\n      await ipfs.dag.put(nodePb, { storeCodec: 'dag-pb', hashAlg: 'sha2-256' })\n      await ipfs.dag.put(nodeCbor, { storeCodec: 'dag-cbor', hashAlg: 'sha2-256' })\n\n      const signer = ES256KSigner(uint8ArrayFromString('278a5de700e29faae8e40e366ec5012b5ec63d36ec77e8a2417154cc1d25383f', 'hex'))\n      nodeJose = await createJWS(base64url.encode(cidCbor.bytes).slice(1), signer)\n      cidJose = CID.createV1(dagJOSE.code, await sha256.digest(dagJOSE.encode(nodeJose)))\n      await ipfs.dag.put(nodeJose, { storeCodec: dagJOSE.name, hashAlg: 'sha2-256' })\n    })\n\n    it('should respect timeout option when getting a DAG node', () => {\n      return testTimeout(() => ipfs.dag.get(CID.parse('QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAd'), {\n        timeout: 1\n      }))\n    })\n\n    it('should get a dag-pb node', async () => {\n      const cid = await ipfs.dag.put(pbNode, {\n        storeCodec: 'dag-pb',\n        hashAlg: 'sha2-256'\n      })\n\n      const result = await ipfs.dag.get(cid)\n\n      const node = result.value\n      expect(pbNode).to.eql(node)\n    })\n\n    it('should get a dag-cbor node', async () => {\n      const cid = await ipfs.dag.put(cborNode, {\n        storeCodec: 'dag-cbor',\n        hashAlg: 'sha2-256'\n      })\n\n      const result = await ipfs.dag.get(cid)\n\n      const node = result.value\n      expect(cborNode).to.eql(node)\n    })\n\n    it('should get a dag-pb node with path', async () => {\n      const result = await ipfs.dag.get(cidPb, {\n        path: '/'\n      })\n\n      const node = result.value\n\n      const cid = CID.createV1(dagPB.code, await sha256.digest(dagPB.encode(node)))\n      expect(cid.equals(cidPb)).to.be.true()\n    })\n\n    it('should get a dag-pb node local value', async function () {\n      const result = await ipfs.dag.get(cidPb, {\n        path: 'Data'\n      })\n      expect(result.value).to.eql(uint8ArrayFromString('I am inside a Protobuf'))\n    })\n\n    it.skip('should get a dag-pb node value one level deep', (done) => {})\n    it.skip('should get a dag-pb node value two levels deep', (done) => {})\n\n    it('should get a dag-cbor node with path', async () => {\n      const result = await ipfs.dag.get(cidCbor, {\n        path: '/'\n      })\n\n      const node = result.value\n\n      const cid = CID.createV1(dagCBOR.code, await sha256.digest(dagCBOR.encode(node)))\n      expect(cid.equals(cidCbor)).to.be.true()\n    })\n\n    it('should get a dag-cbor node local value', async () => {\n      const result = await ipfs.dag.get(cidCbor, {\n        path: 'someData'\n      })\n      expect(result.value).to.eql('I am inside a Cbor object')\n    })\n\n    it.skip('should get dag-cbor node value one level deep', (done) => {})\n    it.skip('should get dag-cbor node value two levels deep', (done) => {})\n    it.skip('should get dag-cbor value via dag-pb node', (done) => {})\n\n    it('should get only a CID, due to resolving locally only', async function () {\n      const result = await ipfs.dag.get(cidCbor, {\n        path: 'pb/Data',\n        localResolve: true\n      })\n      expect(result.value.equals(cidPb)).to.be.true()\n    })\n\n    it('should get dag-pb value via dag-cbor node', async function () {\n      const result = await ipfs.dag.get(cidCbor, {\n        path: 'pb/Data'\n      })\n      expect(result.value).to.eql(uint8ArrayFromString('I am inside a Protobuf'))\n    })\n\n    it('should get by CID with path option', async function () {\n      const result = await ipfs.dag.get(cidCbor, { path: '/pb/Data' })\n      expect(result.value).to.eql(uint8ArrayFromString('I am inside a Protobuf'))\n    })\n\n    it('should get only a CID, due to resolving locally only', async function () {\n      const result = await ipfs.dag.get(cidCbor, {\n        path: 'pb/Data',\n        localResolve: true\n      })\n      expect(result.value.equals(cidPb)).to.be.true()\n    })\n\n    it('should get with options and no path', async function () {\n      const result = await ipfs.dag.get(cidCbor, { localResolve: true })\n      expect(result.value).to.deep.equal(nodeCbor)\n    })\n\n    it('should get a node added as CIDv0 with a CIDv1', async () => {\n      const input = uint8ArrayFromString(`TEST${Math.random()}`)\n\n      const node = {\n        Data: input,\n        Links: []\n      }\n\n      const cid = await ipfs.dag.put(node, {\n        storeCodec: 'dag-pb',\n        hashAlg: 'sha2-256',\n        version: 0\n      })\n      expect(cid.version).to.equal(0)\n\n      const cidv1 = cid.toV1()\n\n      const output = await ipfs.dag.get(cidv1)\n      expect(output.value.Data).to.eql(input)\n    })\n\n    it('should get a node added as CIDv1 with a CIDv0', async () => {\n      const input = uint8ArrayFromString(`TEST${Math.random()}`)\n\n      const res = await all(importer([{ content: input }], blockstore(ipfs), {\n        cidVersion: 1,\n        rawLeaves: false\n      }))\n\n      const cidv1 = res[0].cid\n      expect(cidv1.version).to.equal(1)\n\n      const cidv0 = cidv1.toV0()\n\n      const output = await ipfs.dag.get(cidv0)\n      expect(UnixFS.unmarshal(output.value.Data).data).to.eql(input)\n    })\n\n    it('should be able to get part of a dag-cbor node', async () => {\n      const cbor = {\n        foo: 'dag-cbor-bar'\n      }\n\n      const cid = await ipfs.dag.put(cbor, { storeCodec: 'dag-cbor', hashAlg: 'sha2-256' })\n      expect(cid.code).to.equal(dagCBOR.code)\n      expect(cid.toString(base32)).to.equal('bafyreic6f672hnponukaacmk2mmt7vs324zkagvu4hcww6yba6kby25zce')\n\n      const result = await ipfs.dag.get(cid, {\n        path: 'foo'\n      })\n      expect(result.value).to.equal('dag-cbor-bar')\n    })\n\n    it('should be able to traverse from one dag-cbor node to another', async () => {\n      const cbor1 = {\n        foo: 'dag-cbor-bar'\n      }\n\n      const cid1 = await ipfs.dag.put(cbor1, { storeCodec: 'dag-cbor', hashAlg: 'sha2-256' })\n      const cbor2 = { other: cid1 }\n\n      const cid2 = await ipfs.dag.put(cbor2, { storeCodec: 'dag-cbor', hashAlg: 'sha2-256' })\n\n      const result = await ipfs.dag.get(cid2, {\n        path: 'other/foo'\n      })\n      expect(result.value).to.equal('dag-cbor-bar')\n    })\n\n    it('should be able to get a DAG node with format raw', async () => {\n      const buf = Uint8Array.from([0, 1, 2, 3])\n\n      const cid = await ipfs.dag.put(buf, {\n        storeCodec: 'raw',\n        hashAlg: 'sha2-256'\n      })\n\n      const result = await ipfs.dag.get(cid)\n      expect(result.value).to.deep.equal(buf)\n    })\n\n    it('should be able to get a dag-cbor node with the identity hash', async () => {\n      const identityData = uint8ArrayFromString('A16461736466190144', 'base16upper')\n      const identityHash = await identity.digest(identityData)\n      const identityCID = CID.createV1(identity.code, identityHash)\n      const result = await ipfs.dag.get(identityCID)\n      expect(result.value).to.deep.equal(identityData)\n    })\n\n    it('should throw error for invalid string CID input', () => {\n      // @ts-expect-error invalid arg\n      return expect(ipfs.dag.get('INVALID CID'))\n        .to.eventually.be.rejected()\n    })\n\n    it('should throw error for invalid buffer CID input', () => {\n      // @ts-expect-error invalid arg\n      return expect(ipfs.dag.get(uint8ArrayFromString('INVALID CID')))\n        .to.eventually.be.rejected()\n    })\n\n    it('should return nested content when getting a CID with a path', async () => {\n      const regularContent = { test: '123' }\n      const cid1 = await ipfs.dag.put(regularContent)\n      const linkedContent = { link: cid1 }\n      const cid2 = await ipfs.dag.put(linkedContent)\n\n      const atPath = await ipfs.dag.get(cid2, { path: '/link' })\n\n      expect(atPath).to.have.deep.property('value', regularContent)\n    })\n\n    it('should not return nested content when getting a CID with a path and localResolve is true', async () => {\n      const regularContent = { test: '123' }\n      const cid1 = await ipfs.dag.put(regularContent)\n      const linkedContent = { link: cid1 }\n      const cid2 = await ipfs.dag.put(linkedContent)\n\n      const atPath = await ipfs.dag.get(cid2, { path: '/link', localResolve: true })\n\n      expect(atPath).to.have.deep.property('value').that.is.an.instanceOf(CID)\n    })\n\n    it('should get a dag-jose node', async () => {\n      const cid = await ipfs.dag.put(joseNode, {\n        storeCodec: 'dag-jose',\n        hashAlg: 'sha2-256'\n      })\n\n      const result = await ipfs.dag.get(cid)\n\n      const node = result.value\n      expect(joseNode).to.eql(node)\n    })\n\n    it('should get a dag-jose node with path', async () => {\n      const result = await ipfs.dag.get(cidJose, {\n        path: '/'\n      })\n\n      const node = result.value\n\n      const cid = CID.createV1(dagJOSE.code, await sha256.digest(dagJOSE.encode(node)))\n      expect(cid.equals(cidJose)).to.be.true()\n    })\n\n    it('should get a dag-jose node local value', async () => {\n      const result = await ipfs.dag.get(cidJose, {\n        path: 'payload'\n      })\n      const converted = dagJOSE.toGeneral(nodeJose)\n      expect(result.value).to.eql('payload' in converted && converted.payload)\n    })\n\n    it('should get dag-cbor value via dag-jose node', async function () {\n      const result = await ipfs.dag.get(cidJose, {\n        path: 'link/someData'\n      })\n      expect(result.value).to.eql('I am inside a Cbor object')\n    })\n\n    it('should get dag-cbor cid via dag-jose node if local resolve', async function () {\n      const result = await ipfs.dag.get(cidJose, {\n        path: 'link',\n        localResolve: true\n      })\n      expect(result.value).to.eql(cidCbor)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/dag/import.js",
    "content": "/* eslint-env mocha */\n\nimport all from 'it-all'\nimport drain from 'it-drain'\nimport { CID } from 'multiformats/cid'\nimport { sha256 } from 'multiformats/hashes/sha2'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { CarWriter, CarReader } from '@ipld/car'\nimport * as raw from 'multiformats/codecs/raw'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport loadFixture from 'aegir/fixtures'\n\n/**\n *\n * @param {number} num\n */\nasync function createBlocks (num) {\n  const blocks = []\n\n  for (let i = 0; i < num; i++) {\n    const bytes = uint8ArrayFromString('block-' + Math.random())\n    const digest = await sha256.digest(raw.encode(bytes))\n    const cid = CID.create(1, raw.code, digest)\n\n    blocks.push({ bytes, cid })\n  }\n\n  return blocks\n}\n\n/**\n * @param {{ cid: CID, bytes: Uint8Array }[]} blocks\n * @returns {Promise<AsyncIterable<Uint8Array>>}\n */\nasync function createCar (blocks) {\n  const rootBlock = blocks[0]\n  const { writer, out } = await CarWriter.create([rootBlock.cid])\n\n  writer.put(rootBlock)\n    .then(async () => {\n      for (const block of blocks.slice(1)) {\n        writer.put(block)\n      }\n\n      await writer.close()\n    })\n\n  return out\n}\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testImport (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.dag.import', function () {\n    this.timeout(540 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should import a car file', async () => {\n      const blocks = await createBlocks(5)\n      const car = await createCar(blocks)\n\n      const result = await all(ipfs.dag.import(car))\n      expect(result).to.have.lengthOf(1)\n      // @ts-expect-error chai types are messed up\n      expect(result).to.have.nested.deep.property('[0].root.cid', blocks[0].cid)\n\n      for (const { cid } of blocks) {\n        await expect(ipfs.block.get(cid)).to.eventually.be.ok()\n      }\n\n      await expect(all(ipfs.pin.ls({ paths: blocks[0].cid }))).to.eventually.have.lengthOf(1)\n        .and.have.nested.property('[0].type', 'recursive')\n    })\n\n    it('should import a car file without pinning the roots', async () => {\n      const blocks = await createBlocks(5)\n      const car = await createCar(blocks)\n\n      await all(ipfs.dag.import(car, {\n        pinRoots: false\n      }))\n\n      await expect(all(ipfs.pin.ls({ paths: blocks[0].cid }))).to.eventually.be.rejectedWith(/is not pinned/)\n    })\n\n    it('should import multiple car files', async () => {\n      const blocks1 = await createBlocks(5)\n      const car1 = await createCar(blocks1)\n\n      const blocks2 = await createBlocks(5)\n      const car2 = await createCar(blocks2)\n\n      const result = await all(ipfs.dag.import([car1, car2]))\n      expect(result).to.have.lengthOf(2)\n      expect(result).to.deep.include({ root: { cid: blocks1[0].cid, pinErrorMsg: '' } })\n      expect(result).to.deep.include({ root: { cid: blocks2[0].cid, pinErrorMsg: '' } })\n\n      for (const { cid } of blocks1) {\n        await expect(ipfs.block.get(cid)).to.eventually.be.ok()\n      }\n\n      for (const { cid } of blocks2) {\n        await expect(ipfs.block.get(cid)).to.eventually.be.ok()\n      }\n    })\n\n    it('should import car with roots but no blocks', async () => {\n      const input = loadFixture('test/fixtures/car/combined_naked_roots_genesis_and_128.car', 'interface-ipfs-core')\n      const reader = await CarReader.fromBytes(input)\n      const cids = await reader.getRoots()\n\n      expect(cids).to.have.lengthOf(2)\n\n      // naked roots car does not contain blocks\n      const result1 = await all(ipfs.dag.import(async function * () { yield input }()))\n      expect(result1).to.deep.include({ root: { cid: cids[0], pinErrorMsg: 'blockstore: block not found' } })\n      expect(result1).to.deep.include({ root: { cid: cids[1], pinErrorMsg: 'blockstore: block not found' } })\n\n      await drain(ipfs.dag.import(async function * () { yield loadFixture('test/fixtures/car/lotus_devnet_genesis_shuffled_nulroot.car', 'interface-ipfs-core') }()))\n\n      // have some of the blocks now, should be able to pin one root\n      const result2 = await all(ipfs.dag.import(async function * () { yield input }()))\n      expect(result2).to.deep.include({ root: { cid: cids[0], pinErrorMsg: '' } })\n      expect(result2).to.deep.include({ root: { cid: cids[1], pinErrorMsg: 'blockstore: block not found' } })\n\n      await drain(ipfs.dag.import(async function * () { yield loadFixture('test/fixtures/car/lotus_testnet_export_128.car', 'interface-ipfs-core') }()))\n\n      // have all of the blocks now, should be able to pin both\n      const result3 = await all(ipfs.dag.import(async function * () { yield input }()))\n      expect(result3).to.deep.include({ root: { cid: cids[0], pinErrorMsg: '' } })\n      expect(result3).to.deep.include({ root: { cid: cids[1], pinErrorMsg: '' } })\n    })\n\n    it('should import lotus devnet genesis shuffled nulroot', async () => {\n      const input = loadFixture('test/fixtures/car/lotus_devnet_genesis_shuffled_nulroot.car', 'interface-ipfs-core')\n      const reader = await CarReader.fromBytes(input)\n      const cids = await reader.getRoots()\n\n      expect(cids).to.have.lengthOf(1)\n      expect(cids[0].toString()).to.equal('bafkqaaa')\n\n      const result = await all(ipfs.dag.import(async function * () { yield input }()))\n      // @ts-expect-error chai types are messed up\n      expect(result).to.have.nested.deep.property('[0].root.cid', cids[0])\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/dag/index.js",
    "content": "\nimport { createSuite } from '../utils/suite.js'\nimport { testExport } from './export.js'\nimport { testGet } from './get.js'\nimport { testPut } from './put.js'\nimport { testImport } from './import.js'\nimport { testResolve } from './resolve.js'\nimport { testDagSharnessT0053 } from './sharness-t0053-dag.js'\n\nconst tests = {\n  export: testExport,\n  get: testGet,\n  put: testPut,\n  import: testImport,\n  resolve: testResolve,\n  dagSharnessT0053: testDagSharnessT0053\n}\n\nexport default createSuite(tests)\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/dag/put.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport * as dagCBOR from '@ipld/dag-cbor'\nimport { CID } from 'multiformats/cid'\nimport { sha256, sha512 } from 'multiformats/hashes/sha2'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testPut (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.dag.put', () => {\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => { ipfs = (await factory.spawn()).api })\n\n    after(() => factory.clean())\n\n    const pbNode = {\n      Data: uint8ArrayFromString('some data'),\n      Links: []\n    }\n    const cborNode = {\n      data: uint8ArrayFromString('some other data')\n    }\n    const joseNode = 'eyJhbGciOiJFUzI1NksifQ.AXESICjDGMg3fEBSX7_fpbBUYF4E61TXLysmLJgfGEpFG8Pu.z7a2MvPWLsd7leOeHyfeA1OcAFC9yy5rn1HD8xCeHz3nFrwyn_Su5xXUoaIxAre3fXhGjPkVSNiCE36AKiaMng'\n\n    it('should put dag-pb with default hash func (sha2-256)', () => {\n      return ipfs.dag.put(pbNode, {\n        storeCodec: 'dag-pb',\n        hashAlg: 'sha2-256'\n      })\n    })\n\n    it('should put dag-pb with non-default hash func (sha2-512)', () => {\n      return ipfs.dag.put(pbNode, {\n        storeCodec: 'dag-pb',\n        hashAlg: 'sha2-512'\n      })\n    })\n\n    it('should put dag-cbor with default hash func (sha2-256)', () => {\n      return ipfs.dag.put(cborNode, {\n        storeCodec: 'dag-cbor',\n        hashAlg: 'sha2-256'\n      })\n    })\n\n    it('should put dag-cbor with non-default hash func (sha2-512)', () => {\n      return ipfs.dag.put(cborNode, {\n        storeCodec: 'dag-cbor',\n        hashAlg: 'sha2-512'\n      })\n    })\n\n    it('should put dag-jose with default hash func (sha2-256)', () => {\n      return ipfs.dag.put(joseNode, {\n        storeCodec: 'dag-jose',\n        hashAlg: 'sha2-256'\n      })\n    })\n\n    it('should put dag-jose with non-default hash func (sha2-512)', () => {\n      return ipfs.dag.put(joseNode, {\n        storeCodec: 'dag-jose',\n        hashAlg: 'sha2-512'\n      })\n    })\n\n    it('should return the cid', async () => {\n      const cid = await ipfs.dag.put(cborNode, {\n        storeCodec: 'dag-cbor',\n        hashAlg: 'sha2-256'\n      })\n      expect(cid).to.exist()\n      expect(cid).to.be.an.instanceOf(CID)\n\n      const bytes = dagCBOR.encode(cborNode)\n      const hash = await sha256.digest(bytes)\n      const _cid = CID.createV1(dagCBOR.code, hash)\n\n      expect(cid.bytes).to.eql(_cid.bytes)\n    })\n\n    it('should not fail when calling put without options', () => {\n      return ipfs.dag.put(cborNode)\n    })\n\n    it('should set defaults when calling put without options', async () => {\n      const cid = await ipfs.dag.put(cborNode)\n      expect(cid.code).to.equal(dagCBOR.code)\n      expect(cid.multihash.code).to.equal(sha256.code)\n    })\n\n    it('should override hash algorithm default and resolve with it', async () => {\n      const cid = await ipfs.dag.put(cborNode, {\n        storeCodec: 'dag-cbor',\n        hashAlg: 'sha2-512'\n      })\n      expect(cid.code).to.equal(dagCBOR.code)\n      expect(cid.multihash.code).to.equal(sha512.code)\n    })\n\n    it.skip('should put by passing the cid instead of format and hashAlg', (done) => {})\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/dag/resolve.js",
    "content": "/* eslint-env mocha */\n\nimport * as dagPB from '@ipld/dag-pb'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport testTimeout from '../utils/test-timeout.js'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testResolve (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.dag.resolve', () => {\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n    before(async () => { ipfs = (await factory.spawn()).api })\n\n    after(() => factory.clean())\n\n    it('should respect timeout option when resolving a path within a DAG node', async () => {\n      const cid = await ipfs.dag.put({}, { storeCodec: 'dag-cbor', hashAlg: 'sha2-256' })\n\n      return testTimeout(() => ipfs.dag.resolve(cid, {\n        timeout: 1\n      }))\n    })\n\n    it('should resolve a path inside a cbor node', async () => {\n      const obj = {\n        a: 1,\n        b: [1, 2, 3],\n        c: {\n          ca: [5, 6, 7],\n          cb: 'foo'\n        }\n      }\n\n      const cid = await ipfs.dag.put(obj, { storeCodec: 'dag-cbor', hashAlg: 'sha2-256' })\n\n      const result = await ipfs.dag.resolve(`${cid}/c/cb`)\n      expect(result).to.have.deep.property('cid', cid)\n      expect(result).to.have.property('remainderPath', 'c/cb')\n    })\n\n    it('should resolve a path inside a cbor node by CID', async () => {\n      const obj = {\n        a: 1,\n        b: [1, 2, 3],\n        c: {\n          ca: [5, 6, 7],\n          cb: 'foo'\n        }\n      }\n\n      const cid = await ipfs.dag.put(obj, { storeCodec: 'dag-cbor', hashAlg: 'sha2-256' })\n\n      const result = await ipfs.dag.resolve(cid, { path: '/c/cb' })\n      expect(result).to.have.deep.property('cid', cid)\n      expect(result).to.have.property('remainderPath', 'c/cb')\n    })\n\n    it('should resolve a multi-node path inside a cbor node', async () => {\n      const obj0 = {\n        ca: [5, 6, 7],\n        cb: 'foo'\n      }\n      const cid0 = await ipfs.dag.put(obj0, { storeCodec: 'dag-cbor', hashAlg: 'sha2-256' })\n\n      const obj1 = {\n        a: 1,\n        b: [1, 2, 3],\n        c: cid0\n      }\n\n      const cid1 = await ipfs.dag.put(obj1, { storeCodec: 'dag-cbor', hashAlg: 'sha2-256' })\n\n      const result = await ipfs.dag.resolve(`/ipfs/${cid1}/c/cb`)\n      expect(result).to.have.deep.property('cid', cid0)\n      expect(result).to.have.property('remainderPath', 'cb')\n    })\n\n    it('should resolve a multi-node path inside a cbor node by CID', async () => {\n      const obj0 = {\n        ca: [5, 6, 7],\n        cb: 'foo'\n      }\n      const cid0 = await ipfs.dag.put(obj0, { storeCodec: 'dag-cbor', hashAlg: 'sha2-256' })\n\n      const obj1 = {\n        a: 1,\n        b: [1, 2, 3],\n        c: cid0\n      }\n\n      const cid1 = await ipfs.dag.put(obj1, { storeCodec: 'dag-cbor', hashAlg: 'sha2-256' })\n\n      const result = await ipfs.dag.resolve(cid1, { path: '/c/cb' })\n      expect(result).to.have.deep.property('cid', cid0)\n      expect(result).to.have.property('remainderPath', 'cb')\n    })\n\n    it('should resolve a raw node', async () => {\n      const node = uint8ArrayFromString('hello world')\n      const cid = await ipfs.dag.put(node, { storeCodec: 'raw', hashAlg: 'sha2-256' })\n\n      const result = await ipfs.dag.resolve(cid, { path: '/' })\n      expect(result).to.have.deep.property('cid', cid)\n      expect(result).to.have.property('remainderPath', '')\n    })\n\n    it('should resolve a path inside a dag-pb node linked to from another dag-pb node', async () => {\n      const someData = uint8ArrayFromString('some other data')\n      const childNode = {\n        Data: someData,\n        Links: []\n      }\n      const childCid = await ipfs.dag.put(childNode, { storeCodec: 'dag-pb', hashAlg: 'sha2-256' })\n\n      const linkToChildNode = {\n        Name: 'foo',\n        Tsize: dagPB.encode(childNode).length,\n        Hash: childCid\n      }\n      const parentNode = {\n        Data: uint8ArrayFromString('derp'),\n        Links: [linkToChildNode]\n      }\n      const parentCid = await ipfs.dag.put(parentNode, { storeCodec: 'dag-pb', hashAlg: 'sha2-256' })\n\n      const result = await ipfs.dag.resolve(parentCid, { path: '/foo' })\n      expect(result).to.have.deep.property('cid', childCid)\n      expect(result).to.have.property('remainderPath', '')\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/dag/sharness-t0053-dag.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { base64pad } from 'multiformats/bases/base64'\nimport { base58btc } from 'multiformats/bases/base58'\nimport { CID } from 'multiformats'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testDagSharnessT0053 (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.dag (sharness-t0053-dag)', () => {\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n    before(async () => { ipfs = (await factory.spawn()).api })\n\n    after(() => factory.clean())\n\n    /** @type {CID} */\n    let hash1\n    /** @type {CID} */\n    let hash2\n    /** @type {CID} */\n    let hash3\n    /** @type {CID} */\n    let hash4\n    /** @type {Uint8Array} */\n    let ipldObject\n    /** @type {Uint8Array} */\n    let ipldObjectDagCbor\n    /** @type {Uint8Array} */\n    let ipldObjectDagPb\n    /** @type {Uint8Array} */\n    let ipldObjectDagJson\n    const ipldHash = 'bafyreiblwimnjbqcdoeafiobk6q27jcw64ew7n2fmmhdpldd63edmjecde'\n    const ipldDagCborHash = 'bafyreieculsmrexh3ty5jentbvuku452o27mst4h2tq2rb2zntqhgcstji'\n    const ipldDagJsonHash = 'baguqeerajwksxu3lxpomdwxvosl542zl3xknhjgxtq3277gafrhl6vdw5tcq'\n    const ipldDagPbHash = 'bafybeibazl2z4vqp2tmwcfag6wirmtpnomxknqcgrauj7m2yisrz3qjbom'\n\n    before(async () => {\n      hash1 = (await ipfs.add({ content: 'foo\\n', path: 'file1' })).cid\n      hash2 = (await ipfs.add({ content: 'bar\\n', path: 'file2' })).cid\n      hash3 = (await ipfs.add({ content: 'baz\\n', path: 'file3' })).cid\n      hash4 = (await ipfs.add({ content: 'qux\\n', path: 'file4' })).cid\n\n      ipldObject = new TextEncoder().encode(`{\"hello\":\"world\",\"cats\":[{\"/\":\"${hash1}\"},{\"water\":{\"/\":\"${hash2}\"}}],\"magic\":{\"/\":\"${hash3}\"},\"sub\":{\"dict\":\"ionary\",\"beep\":[0,\"bop\"]}}`)\n      ipldObjectDagCbor = base64pad.decode('MomREYXRhRQABAgMEZUxpbmtzgA==')\n      ipldObjectDagPb = base64pad.decode('MCgUAAQIDBA==')\n      ipldObjectDagJson = new TextEncoder().encode('{\"Data\":{\"/\":{\"bytes\":\"AAECAwQ\"}},\"Links\":[]}')\n    })\n\n    it('sanity check', () => {\n      expect(hash1.toString()).to.equal('QmYNmQKp6SuaVrpgWRsPTgCQCnpxUYGq76YEKBXuj2N4H6')\n      expect(hash2.toString()).to.equal('QmTz3oc4gdpRMKP2sdGUPZTAGRngqjsi99BPoztyP53JMM')\n      expect(hash3.toString()).to.equal('QmWLdkp93sNxGRjnFHPaYg8tCQ35NBY3XPn6KiETd3Z4WR')\n      expect(hash4.toString()).to.equal('QmZCoKN8vvRbxfn4BMG9678UQTSUwPXRJsRA9jnjoucHUj')\n    })\n\n    it('can add an ipld object using defaults (dag-json to dag-cbor)', async () => {\n      // dag-json is default on CLI, force it to interpret our bytes here\n      const cid = await ipfs.dag.put(ipldObject, { inputCodec: 'dag-json' })\n      expect(cid.toString()).to.equal(ipldHash)\n    })\n\n    it('can add an ipld object using dag-json to dag-json', async () => {\n      const cid = await ipfs.dag.put(ipldObject, { inputCodec: 'dag-json', storeCodec: 'dag-json' })\n      expect(cid.toString()).to.equal('baguqeera6gviseelmbzn2ugoddo5vulxlshqs3kw5ymgsb6w4cabnoh4ldpa')\n    })\n\n    it('can add an ipld object using dag-json to dag-cbor', async () => {\n      const cid = await ipfs.dag.put(ipldObject, { inputCodec: 'dag-json', storeCodec: 'dag-cbor' })\n      expect(cid.toString()).to.equal(ipldHash)\n    })\n\n    // this is not testing what the upstream sharness is testing since we're converting it locally\n    // and not asking the CLI for it, but it's included for completeness\n    it('can add an ipld object using cid-base=base58btc', async () => {\n      const cid = await ipfs.dag.put(ipldObject, { inputCodec: 'dag-json' })\n      expect(cid.toString(base58btc)).to.equal('zdpuAoN1XJ3GsrxEzMuCbRKZzRUVJekJUCbPVgCgE4D9yYqVi')\n    })\n\n    // (1) dag-cbor input\n\n    it('can add a dag-cbor input block stored as dag-cbor', async () => {\n      const cid = await ipfs.dag.put(ipldObjectDagCbor, { inputCodec: 'dag-cbor', storeCodec: 'dag-cbor' })\n      expect(cid.toString()).to.equal(ipldDagCborHash)\n    })\n\n    it('can add a dag-cbor input block stored as dag-pb', async () => {\n      const cid = await ipfs.dag.put(ipldObjectDagCbor, { inputCodec: 'dag-cbor', storeCodec: 'dag-pb' })\n      expect(cid.toString()).to.equal(ipldDagPbHash)\n    })\n\n    it('can add a dag-cbor input block stored as dag-json', async () => {\n      const cid = await ipfs.dag.put(ipldObjectDagCbor, { inputCodec: 'dag-cbor', storeCodec: 'dag-json' })\n      expect(cid.toString()).to.equal(ipldDagJsonHash)\n    })\n\n    // (2) dag-json input\n\n    it('can add a dag-json input block stored as dag-cbor', async () => {\n      const cid = await ipfs.dag.put(ipldObjectDagJson, { inputCodec: 'dag-json', storeCodec: 'dag-cbor' })\n      expect(cid.toString()).to.equal(ipldDagCborHash)\n    })\n\n    it('can add a dag-json input block stored as dag-pb', async () => {\n      const cid = await ipfs.dag.put(ipldObjectDagJson, { inputCodec: 'dag-json', storeCodec: 'dag-pb' })\n      expect(cid.toString()).to.equal(ipldDagPbHash)\n    })\n\n    it('can add a dag-json input block stored as dag-json', async () => {\n      const cid = await ipfs.dag.put(ipldObjectDagJson, { inputCodec: 'dag-json', storeCodec: 'dag-json' })\n      expect(cid.toString()).to.equal(ipldDagJsonHash)\n    })\n\n    // (3) dag-pb input\n\n    it('can add a dag-pb input block stored as dag-cbor', async () => {\n      const cid = await ipfs.dag.put(ipldObjectDagPb, { inputCodec: 'dag-pb', storeCodec: 'dag-cbor' })\n      expect(cid.toString()).to.equal(ipldDagCborHash)\n    })\n\n    it('can add a dag-pb input block stored as dag-pb', async () => {\n      const cid = await ipfs.dag.put(ipldObjectDagPb, { inputCodec: 'dag-pb', storeCodec: 'dag-pb' })\n      expect(cid.toString()).to.equal(ipldDagPbHash)\n    })\n\n    it('can add a dag-pb input block stored as dag-json', async () => {\n      const cid = await ipfs.dag.put(ipldObjectDagPb, { inputCodec: 'dag-pb', storeCodec: 'dag-json' })\n      expect(cid.toString()).to.equal(ipldDagJsonHash)\n    })\n\n    it('can get dag-cbor, dag-json, dag-pb blocks as dag-json', async () => {\n      const resultCbor = await ipfs.dag.get(CID.parse(ipldDagCborHash))\n      const resultJson = await ipfs.dag.get(CID.parse(ipldDagJsonHash))\n      const resultPb = await ipfs.dag.get(CID.parse(ipldDagPbHash))\n      expect(resultCbor).to.deep.equal(resultJson)\n      expect(resultCbor).to.deep.equal(resultPb)\n    })\n\n    /*\n    This is illustrative only - it's not testing anything meaningful. It's supposed to test\n    `outputCodec` which isn't supported for the http client or core since we get the decoded JS\n    form of the node. But this test code as it's written is doing the encode locally and\n    asserting on that .. which is just testing the codec.\n\n    it('can get dag-pb block transcoded as dag-cbor', async () => {\n      const { value } = await ipfs.dag.get(CID.parse(ipldDagPbHash), { outputCodec: 'dag-cbor' })\n      const block = await Block.encode({ value, codec: dagCbor, hasher: sha256 })\n      expect(bytes.toHex(block.cid.multihash.bytes)).to.equal('122082a2e4c892e7dcf1d491b30d68aa73ba76bec94f87d4e1a887596ce0730a534a')\n    })\n    */\n\n    // Skipped: 'dag put and dag get transcodings match' - tests the round-trip of the above\n\n    it('resolving sub-objects works', async () => {\n      let result = await ipfs.dag.get(CID.parse(ipldHash), { path: 'hello' })\n      expect(result.value).to.equal('world')\n      result = await ipfs.dag.get(CID.parse(ipldHash), { path: 'sub' })\n      expect(result.value).to.deep.equal({ beep: [0, 'bop'], dict: 'ionary' })\n      result = await ipfs.dag.get(CID.parse(ipldHash), { path: 'sub/beep' })\n      expect(result.value).to.deep.equal([0, 'bop'])\n      result = await ipfs.dag.get(CID.parse(ipldHash), { path: 'sub/beep/0' })\n      expect(result.value).to.equal(0)\n      result = await ipfs.dag.get(CID.parse(ipldHash), { path: 'sub/beep/1' })\n      expect(result.value).to.equal('bop')\n    })\n\n    // Skipped: 'traversals using /ipld/ work' - not implemented here, yet?\n\n    // Skipped additional pin, resolve and other tests\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/dht/disabled.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport all from 'it-all'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testDisabled (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('disabled', function () {\n    this.timeout(80 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let nodeA\n    /** @type {import('ipfs-core-types').IPFS} */\n    let nodeB\n\n    before(async () => {\n      nodeA = (await factory.spawn({\n        ipfsOptions: {\n          config: {\n            Routing: {\n              Type: 'none'\n            }\n          }\n        }\n      })).api\n      nodeB = (await factory.spawn()).api\n      const nodeBId = await nodeB.id()\n      await nodeA.swarm.connect(nodeBId.addresses[0])\n    })\n\n    after(() => factory.clean())\n\n    it('should error when DHT not available', async () => {\n      await expect(all(nodeA.dht.put('/ipns/12D3KooWBD9zgsogrYf1dum1TwTwe6k5xT8acGZ5PNeYmKf72qz2', uint8ArrayFromString('hello'), { verbose: true })))\n        .to.eventually.be.rejected()\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/dht/find-peer.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport testTimeout from '../utils/test-timeout.js'\nimport drain from 'it-drain'\nimport all from 'it-all'\nimport { ensureReachable } from './utils.js'\nimport { peerIdFromString } from '@libp2p/peer-id'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testFindPeer (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.dht.findPeer', function () {\n    this.timeout(80 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let nodeA\n    /** @type {import('ipfs-core-types').IPFS} */\n    let nodeB\n\n    before(async () => {\n      nodeA = (await factory.spawn()).api\n      nodeB = (await factory.spawn()).api\n\n      await ensureReachable(nodeA, nodeB)\n    })\n\n    after(() => factory.clean())\n\n    it('should respect timeout option when finding a peer on the DHT', async () => {\n      const nodeBId = await nodeB.id()\n\n      await testTimeout(() => drain(nodeA.dht.findPeer(nodeBId.id, {\n        timeout: 1\n      })))\n    })\n\n    it('should find other peers', async () => {\n      const nodeBId = await nodeB.id()\n\n      const results = await all(nodeA.dht.findPeer(nodeBId.id))\n      const finalPeer = results.filter(event => event.name === 'FINAL_PEER').pop()\n\n      if (!finalPeer || finalPeer.name !== 'FINAL_PEER') {\n        throw new Error('No finalPeer event received')\n      }\n\n      const id = finalPeer.peer.id\n      const nodeAddresses = nodeBId.addresses.map((addr) => addr.nodeAddress())\n      const peerAddresses = finalPeer.peer.multiaddrs.map(ma => ma.nodeAddress())\n\n      expect(id.toString()).to.equal(nodeBId.id.toString())\n      expect(peerAddresses).to.deep.include(nodeAddresses[0])\n    })\n\n    it('should fail to find other peer if peer does not exist', async () => {\n      const events = await all(nodeA.dht.findPeer(peerIdFromString('Qmd7qZS4T7xXtsNFdRoK1trfMs5zU94EpokQ9WFtxdPxsZ')))\n\n      // no finalPeer events found\n      expect(events.filter(event => event.name === 'FINAL_PEER')).to.be.empty()\n\n      // queryError events found\n      expect(events.filter(event => event.name === 'QUERY_ERROR')).to.not.be.empty()\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/dht/find-provs.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport all from 'it-all'\nimport drain from 'it-drain'\nimport testTimeout from '../utils/test-timeout.js'\nimport { ensureReachable } from './utils.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testFindProvs (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.dht.findProvs', function () {\n    this.timeout(80 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let nodeA\n    /** @type {import('ipfs-core-types').IPFS} */\n    let nodeB\n    /** @type {import('ipfs-core-types').IPFS} */\n    let nodeC\n\n    before(async () => {\n      nodeA = (await factory.spawn()).api\n      nodeB = (await factory.spawn()).api\n      nodeC = (await factory.spawn()).api\n\n      await ensureReachable(nodeB, nodeA)\n      await ensureReachable(nodeC, nodeB)\n    })\n\n    after(() => factory.clean())\n\n    /**\n     * @type {import('multiformats/cid').CID}\n     */\n    let providedCid\n    before('add providers for the same cid', async function () {\n      const cids = await Promise.all([\n        nodeB.object.new('unixfs-dir'),\n        nodeC.object.new('unixfs-dir')\n      ])\n\n      providedCid = cids[0]\n\n      await Promise.all([\n        all(nodeB.dht.provide(providedCid)),\n        all(nodeC.dht.provide(providedCid))\n      ])\n    })\n\n    it('should respect timeout option when finding providers on the DHT', () => {\n      return testTimeout(() => drain(nodeA.dht.findProvs(providedCid, {\n        timeout: 1\n      })))\n    })\n\n    it('should be able to find providers', async function () {\n      /** @type {string[]} */\n      const providerIds = []\n\n      for await (const event of nodeA.dht.findProvs(providedCid)) {\n        if (event.name === 'PROVIDER') {\n          providerIds.push(...event.providers.map(prov => prov.id.toString()))\n        }\n      }\n\n      const nodeBId = await nodeB.id()\n      const nodeCId = await nodeC.id()\n\n      expect(providerIds).to.include(nodeBId.id.toString())\n      expect(providerIds).to.include(nodeCId.id.toString())\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/dht/get.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport testTimeout from '../utils/test-timeout.js'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\nimport drain from 'it-drain'\nimport all from 'it-all'\nimport { ensureReachable } from './utils.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testGet (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.dht.get', function () {\n    this.timeout(80 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let nodeA\n    /** @type {import('ipfs-core-types').IPFS} */\n    let nodeB\n\n    before(async () => {\n      nodeA = (await factory.spawn()).api\n      nodeB = (await factory.spawn()).api\n\n      await ensureReachable(nodeA, nodeB)\n    })\n\n    after(() => factory.clean())\n\n    it('should respect timeout option when getting a value from the DHT', async () => {\n      const data = await nodeA.add('should put a value to the DHT')\n      const publish = await nodeA.name.publish(data.cid)\n\n      await testTimeout(() => drain(nodeB.dht.get(`/ipns/${publish.name}`, {\n        timeout: 1\n      })))\n    })\n\n    it('should error when getting a non-existent key from the DHT', async () => {\n      const key = '/ipns/k51qzi5uqu5dl0dbfddy2wb42nvbc6anyxnkrguy5l0h0bv9kaih6j6vqdskqk'\n      const events = await all(nodeA.dht.get(key))\n\n      // no value events found\n      expect(events.filter(event => event.name === 'VALUE')).to.be.empty()\n\n      // queryError events found\n      expect(events.filter(event => event.name === 'QUERY_ERROR')).to.not.be.empty()\n    })\n\n    it('should get a value after it was put on another node', async () => {\n      const data = await nodeA.add('should put a value to the DHT')\n      const publish = await nodeA.name.publish(data.cid)\n      const events = await all(nodeA.dht.get(`/ipns/${publish.name}`))\n      const valueEvent = events.filter(event => event.name === 'VALUE').pop()\n\n      if (!valueEvent || valueEvent.name !== 'VALUE') {\n        throw new Error('Value event not found')\n      }\n\n      expect(uint8ArrayToString(valueEvent.value)).to.contain(data.cid.toString())\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/dht/index.js",
    "content": "import { createSuite } from '../utils/suite.js'\nimport { testPut } from './put.js'\nimport { testGet } from './get.js'\nimport { testFindPeer } from './find-peer.js'\nimport { testProvide } from './provide.js'\nimport { testFindProvs } from './find-provs.js'\nimport { testQuery } from './query.js'\nimport { testDisabled } from './disabled.js'\n\nconst tests = {\n  put: testPut,\n  get: testGet,\n  findPeer: testFindPeer,\n  provide: testProvide,\n  findProvs: testFindProvs,\n  query: testQuery,\n  disabled: testDisabled\n}\n\nexport default createSuite(tests)\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/dht/provide.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { CID } from 'multiformats/cid'\nimport all from 'it-all'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { ensureReachable } from './utils.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testProvide (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.dht.provide', function () {\n    this.timeout(80 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n      const nodeB = (await factory.spawn()).api\n\n      await ensureReachable(ipfs, nodeB)\n    })\n\n    after(() => factory.clean())\n\n    it('should provide local CID', async () => {\n      const res = await ipfs.add(uint8ArrayFromString('test'))\n\n      await all(ipfs.dht.provide(res.cid))\n    })\n\n    it('should not provide if block not found locally', () => {\n      const cid = CID.parse('Qmd7qZS4T7xXtsNFdRoK1trfMs5zU94EpokQ9WFtxdPxsZ')\n\n      return expect(all(ipfs.dht.provide(cid))).to.eventually.be.rejected\n        .and.be.an.instanceOf(Error)\n        .and.have.property('message')\n        .that.include('not found locally')\n    })\n\n    it('should provide a CIDv1', async () => {\n      const res = await ipfs.add(uint8ArrayFromString('test'), { cidVersion: 1 })\n      await all(ipfs.dht.provide(res.cid))\n    })\n\n    it('should error on non CID arg', async () => {\n      // @ts-expect-error invalid arg\n      return expect(all(ipfs.dht.provide({}))).to.eventually.be.rejected()\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/dht/put.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport all from 'it-all'\nimport { ensureReachable } from './utils.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testPut (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.dht.put', function () {\n    this.timeout(80 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let nodeA\n    /** @type {import('ipfs-core-types').IPFS} */\n    let nodeB\n\n    before(async () => {\n      nodeA = (await factory.spawn()).api\n      nodeB = (await factory.spawn()).api\n\n      await ensureReachable(nodeA, nodeB)\n    })\n\n    after(() => factory.clean())\n\n    it('should put a value to the DHT', async function () {\n      const { cid } = await nodeA.add('should put a value to the DHT')\n\n      const publish = await nodeA.name.publish(cid)\n      let record\n\n      for await (const event of nodeA.dht.get(`/ipns/${publish.name}`)) {\n        if (event.name === 'VALUE') {\n          record = event.value\n          break\n        }\n      }\n\n      if (!record) {\n        throw new Error('Could not find value')\n      }\n\n      const events = await all(nodeA.dht.put(`/ipns/${publish.name}`, record, { verbose: true }))\n      const peerResponse = events.filter(event => event.name === 'PEER_RESPONSE').pop()\n\n      if (!peerResponse || peerResponse.name !== 'PEER_RESPONSE') {\n        throw new Error('Did not get peer response')\n      }\n\n      const nodeBId = await nodeB.id()\n\n      expect(peerResponse.from.toString()).to.be.equal(nodeBId.id.toString())\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/dht/query.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport drain from 'it-drain'\nimport testTimeout from '../utils/test-timeout.js'\nimport { ensureReachable } from './utils.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testQuery (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.dht.query', function () {\n    this.timeout(80 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let nodeA\n    /** @type {import('ipfs-core-types').IPFS} */\n    let nodeB\n\n    before(async () => {\n      nodeA = (await factory.spawn()).api\n      nodeB = (await factory.spawn()).api\n\n      await ensureReachable(nodeA, nodeB)\n    })\n\n    after(() => factory.clean())\n\n    it('should respect timeout option when querying the DHT', async () => {\n      const nodeBId = await nodeB.id()\n\n      return testTimeout(() => drain(nodeA.dht.query(nodeBId.id, {\n        timeout: 1\n      })))\n    })\n\n    it('should return the other node in the query', async function () {\n      /** @type {string[]} */\n      const peers = []\n      const nodeBId = await nodeB.id()\n      for await (const event of nodeA.dht.query(nodeBId.id)) {\n        if (event.name === 'PEER_RESPONSE') {\n          peers.push(...event.closer.map(data => data.id.toString()))\n        }\n      }\n\n      expect(peers).to.include(nodeBId.id.toString())\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/dht/utils.js",
    "content": "\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { CID } from 'multiformats/cid'\nimport { sha256 } from 'multiformats/hashes/sha2'\nimport delay from 'delay'\n\n/**\n * @param {Uint8Array} [data]\n * @returns\n */\nexport async function fakeCid (data) {\n  const bytes = data || uint8ArrayFromString(`TEST${Math.random()}`)\n  const mh = await sha256.digest(bytes)\n  return CID.createV0(mh)\n}\n\n/**\n * @param {import('ipfs-core-types').IPFS} nodeA\n * @param {import('ipfs-core-types').IPFS} nodeB\n */\nexport async function ensureReachable (nodeA, nodeB) {\n  /**\n   * @param {import('ipfs-core-types').IPFS} source\n   * @param {import('ipfs-core-types').IPFS} target\n   */\n  async function canFindOnDHT (source, target) {\n    const { id } = await target.id()\n\n    for await (const event of source.dht.query(id)) {\n      if (event.name === 'PEER_RESPONSE' && event.from.equals(id)) {\n        return\n      }\n    }\n\n    throw new Error(`Could not find ${id} in DHT`)\n  }\n\n  const nodeBId = await nodeB.id()\n  await nodeA.swarm.connect(nodeBId.addresses[0])\n\n  while (true) {\n    try {\n      await Promise.all([\n        canFindOnDHT(nodeA, nodeB),\n        canFindOnDHT(nodeB, nodeA)\n      ])\n\n      break\n    } catch {\n      await delay(1000)\n    }\n  }\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/files/chmod.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { nanoid } from 'nanoid'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport isShardAtPath from '../utils/is-shard-at-path.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testChmod (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.files.chmod', function () {\n    this.timeout(120 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    /**\n     * @param {string} initialMode\n     * @param {string} modification\n     * @param {string} expectedFinalMode\n     */\n    async function testChmod (initialMode, modification, expectedFinalMode) {\n      const path = `/test-${nanoid()}`\n\n      await ipfs.files.write(path, uint8ArrayFromString('Hello world!'), {\n        create: true,\n        mtime: new Date(),\n        mode: initialMode\n      })\n      await ipfs.files.chmod(path, modification, {\n        flush: true\n      })\n\n      const updatedMode = (await ipfs.files.stat(path)).mode\n      expect(updatedMode).to.equal(parseInt(expectedFinalMode, 8))\n    }\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should update the mode for a file', async () => {\n      const path = `/foo-${Math.random()}`\n\n      await ipfs.files.write(path, uint8ArrayFromString('Hello world'), {\n        create: true,\n        mtime: new Date()\n      })\n      const originalMode = (await ipfs.files.stat(path)).mode\n      await ipfs.files.chmod(path, '0777', {\n        flush: true\n      })\n\n      const updatedMode = (await ipfs.files.stat(path)).mode\n      expect(updatedMode).to.not.equal(originalMode)\n      expect(updatedMode).to.equal(parseInt('0777', 8))\n    })\n\n    it('should update the mode for a directory', async () => {\n      const path = `/foo-${Math.random()}`\n\n      await ipfs.files.mkdir(path)\n      const originalMode = (await ipfs.files.stat(path)).mode\n      await ipfs.files.chmod(path, '0777', {\n        flush: true\n      })\n\n      const updatedMode = (await ipfs.files.stat(path)).mode\n      expect(updatedMode).to.not.equal(originalMode)\n      expect(updatedMode).to.equal(parseInt('0777', 8))\n    })\n\n    it('should update the mode for a hamt-sharded-directory', async () => {\n      const path = `/foo-${Math.random()}`\n\n      await ipfs.files.mkdir(path)\n      await ipfs.files.write(`${path}/foo.txt`, uint8ArrayFromString('Hello world'), {\n        create: true,\n        shardSplitThreshold: 0\n      })\n      const originalMode = (await ipfs.files.stat(path)).mode\n      await ipfs.files.chmod(path, '0777', {\n        flush: true\n      })\n\n      const updatedMode = (await ipfs.files.stat(path)).mode\n      expect(updatedMode).to.not.equal(originalMode)\n      expect(updatedMode).to.equal(parseInt('0777', 8))\n    })\n\n    it('should update modes with basic symbolic notation that adds bits', async () => {\n      await testChmod('0000', '+x', '0111')\n      await testChmod('0000', '+w', '0222')\n      await testChmod('0000', '+r', '0444')\n      await testChmod('0000', 'u+x', '0100')\n      await testChmod('0000', 'u+w', '0200')\n      await testChmod('0000', 'u+r', '0400')\n      await testChmod('0000', 'g+x', '0010')\n      await testChmod('0000', 'g+w', '0020')\n      await testChmod('0000', 'g+r', '0040')\n      await testChmod('0000', 'o+x', '0001')\n      await testChmod('0000', 'o+w', '0002')\n      await testChmod('0000', 'o+r', '0004')\n      await testChmod('0000', 'ug+x', '0110')\n      await testChmod('0000', 'ug+w', '0220')\n      await testChmod('0000', 'ug+r', '0440')\n      await testChmod('0000', 'ugo+x', '0111')\n      await testChmod('0000', 'ugo+w', '0222')\n      await testChmod('0000', 'ugo+r', '0444')\n      await testChmod('0000', 'a+x', '0111')\n      await testChmod('0000', 'a+w', '0222')\n      await testChmod('0000', 'a+r', '0444')\n    })\n\n    it('should update modes with basic symbolic notation that removes bits', async () => {\n      await testChmod('0111', '-x', '0000')\n      await testChmod('0222', '-w', '0000')\n      await testChmod('0444', '-r', '0000')\n      await testChmod('0100', 'u-x', '0000')\n      await testChmod('0200', 'u-w', '0000')\n      await testChmod('0400', 'u-r', '0000')\n      await testChmod('0010', 'g-x', '0000')\n      await testChmod('0020', 'g-w', '0000')\n      await testChmod('0040', 'g-r', '0000')\n      await testChmod('0001', 'o-x', '0000')\n      await testChmod('0002', 'o-w', '0000')\n      await testChmod('0004', 'o-r', '0000')\n      await testChmod('0110', 'ug-x', '0000')\n      await testChmod('0220', 'ug-w', '0000')\n      await testChmod('0440', 'ug-r', '0000')\n      await testChmod('0111', 'ugo-x', '0000')\n      await testChmod('0222', 'ugo-w', '0000')\n      await testChmod('0444', 'ugo-r', '0000')\n      await testChmod('0111', 'a-x', '0000')\n      await testChmod('0222', 'a-w', '0000')\n      await testChmod('0444', 'a-r', '0000')\n    })\n\n    it('should update modes with basic symbolic notation that overrides bits', async () => {\n      await testChmod('0777', '=x', '0111')\n      await testChmod('0777', '=w', '0222')\n      await testChmod('0777', '=r', '0444')\n      await testChmod('0777', 'u=x', '0177')\n      await testChmod('0777', 'u=w', '0277')\n      await testChmod('0777', 'u=r', '0477')\n      await testChmod('0777', 'g=x', '0717')\n      await testChmod('0777', 'g=w', '0727')\n      await testChmod('0777', 'g=r', '0747')\n      await testChmod('0777', 'o=x', '0771')\n      await testChmod('0777', 'o=w', '0772')\n      await testChmod('0777', 'o=r', '0774')\n      await testChmod('0777', 'ug=x', '0117')\n      await testChmod('0777', 'ug=w', '0227')\n      await testChmod('0777', 'ug=r', '0447')\n      await testChmod('0777', 'ugo=x', '0111')\n      await testChmod('0777', 'ugo=w', '0222')\n      await testChmod('0777', 'ugo=r', '0444')\n      await testChmod('0777', 'a=x', '0111')\n      await testChmod('0777', 'a=w', '0222')\n      await testChmod('0777', 'a=r', '0444')\n    })\n\n    it('should update modes with multiple symbolic notation', async () => {\n      await testChmod('0000', 'g+x,u+w', '0210')\n    })\n\n    it('should update modes with special symbolic notation', async () => {\n      await testChmod('0000', 'g+s', '2000')\n      await testChmod('0000', 'u+s', '4000')\n      await testChmod('0000', '+t', '1000')\n      await testChmod('0000', '+s', '6000')\n    })\n\n    it('should apply special execute permissions to world', async () => {\n      const path = `/foo-${Math.random()}`\n      const sub = `${path}/sub`\n      const file = `${path}/sub/foo.txt`\n      const bin = `${path}/sub/bar`\n\n      await ipfs.files.mkdir(sub, {\n        parents: true\n      })\n      await ipfs.files.touch(file)\n      await ipfs.files.touch(bin)\n\n      await ipfs.files.chmod(path, 0o644, {\n        recursive: true\n      })\n      await ipfs.files.chmod(bin, 'u+x')\n\n      await expect(ipfs.files.stat(path)).to.eventually.have.property('mode', 0o644)\n      await expect(ipfs.files.stat(sub)).to.eventually.have.property('mode', 0o644)\n      await expect(ipfs.files.stat(file)).to.eventually.have.property('mode', 0o644)\n      await expect(ipfs.files.stat(bin)).to.eventually.have.property('mode', 0o744)\n\n      await ipfs.files.chmod(path, 'a+X', {\n        recursive: true\n      })\n\n      // directories should be world-executable\n      await expect(ipfs.files.stat(path)).to.eventually.have.property('mode', 0o755)\n      await expect(ipfs.files.stat(sub)).to.eventually.have.property('mode', 0o755)\n\n      // files without prior execute bit should be untouched\n      await expect(ipfs.files.stat(file)).to.eventually.have.property('mode', 0o644)\n\n      // files with prior execute bit should now be world-executable\n      await expect(ipfs.files.stat(bin)).to.eventually.have.property('mode', 0o755)\n    })\n\n    it('should apply special execute permissions to user', async () => {\n      const path = `/foo-${Math.random()}`\n      const sub = `${path}/sub`\n      const file = `${path}/sub/foo.txt`\n      const bin = `${path}/sub/bar`\n\n      await ipfs.files.mkdir(sub, {\n        parents: true\n      })\n      await ipfs.files.touch(file)\n      await ipfs.files.touch(bin)\n\n      await ipfs.files.chmod(path, 0o644, {\n        recursive: true\n      })\n      await ipfs.files.chmod(bin, 'u+x')\n\n      await expect(ipfs.files.stat(path)).to.eventually.have.property('mode', 0o644)\n      await expect(ipfs.files.stat(sub)).to.eventually.have.property('mode', 0o644)\n      await expect(ipfs.files.stat(file)).to.eventually.have.property('mode', 0o644)\n      await expect(ipfs.files.stat(bin)).to.eventually.have.property('mode', 0o744)\n\n      await ipfs.files.chmod(path, 'u+X', {\n        recursive: true\n      })\n\n      // directories should be user executable\n      await expect(ipfs.files.stat(path)).to.eventually.have.property('mode', 0o744)\n      await expect(ipfs.files.stat(sub)).to.eventually.have.property('mode', 0o744)\n\n      // files without prior execute bit should be untouched\n      await expect(ipfs.files.stat(file)).to.eventually.have.property('mode', 0o644)\n\n      // files with prior execute bit should now be user executable\n      await expect(ipfs.files.stat(bin)).to.eventually.have.property('mode', 0o744)\n    })\n\n    it('should apply special execute permissions to user and group', async () => {\n      const path = `/foo-${Math.random()}`\n      const sub = `${path}/sub`\n      const file = `${path}/sub/foo.txt`\n      const bin = `${path}/sub/bar`\n\n      await ipfs.files.mkdir(sub, {\n        parents: true\n      })\n      await ipfs.files.touch(file)\n      await ipfs.files.touch(bin)\n\n      await ipfs.files.chmod(path, 0o644, {\n        recursive: true\n      })\n      await ipfs.files.chmod(bin, 'u+x')\n\n      await expect(ipfs.files.stat(path)).to.eventually.have.property('mode', 0o644)\n      await expect(ipfs.files.stat(sub)).to.eventually.have.property('mode', 0o644)\n      await expect(ipfs.files.stat(file)).to.eventually.have.property('mode', 0o644)\n      await expect(ipfs.files.stat(bin)).to.eventually.have.property('mode', 0o744)\n\n      await ipfs.files.chmod(path, 'ug+X', {\n        recursive: true\n      })\n\n      // directories should be user and group executable\n      await expect(ipfs.files.stat(path)).to.eventually.have.property('mode', 0o754)\n      await expect(ipfs.files.stat(sub)).to.eventually.have.property('mode', 0o754)\n\n      // files without prior execute bit should be untouched\n      await expect(ipfs.files.stat(file)).to.eventually.have.property('mode', 0o644)\n\n      // files with prior execute bit should now be user and group executable\n      await expect(ipfs.files.stat(bin)).to.eventually.have.property('mode', 0o754)\n    })\n\n    it('should apply special execute permissions to sharded directories', async () => {\n      const path = `/foo-${Math.random()}`\n      const sub = `${path}/sub`\n      const file = `${path}/sub/foo.txt`\n      const bin = `${path}/sub/bar`\n\n      await ipfs.files.mkdir(sub, {\n        parents: true,\n        shardSplitThreshold: 0\n      })\n      await ipfs.files.touch(file, {\n        shardSplitThreshold: 0\n      })\n      await ipfs.files.touch(bin, {\n        shardSplitThreshold: 0\n      })\n\n      await ipfs.files.chmod(path, 0o644, {\n        recursive: true,\n        shardSplitThreshold: 0\n      })\n      await ipfs.files.chmod(bin, 'u+x', {\n        recursive: true,\n        shardSplitThreshold: 0\n      })\n\n      await expect(ipfs.files.stat(path)).to.eventually.have.property('mode', 0o644)\n      await expect(ipfs.files.stat(sub)).to.eventually.have.property('mode', 0o644)\n      await expect(ipfs.files.stat(file)).to.eventually.have.property('mode', 0o644)\n      await expect(ipfs.files.stat(bin)).to.eventually.have.property('mode', 0o744)\n\n      await ipfs.files.chmod(path, 'ug+X', {\n        recursive: true,\n        shardSplitThreshold: 0\n      })\n\n      // directories should be user and group executable\n      await expect(isShardAtPath(path, ipfs)).to.eventually.be.true()\n      await expect(ipfs.files.stat(path)).to.eventually.include({\n        type: 'directory',\n        mode: 0o754\n      })\n      await expect(ipfs.files.stat(sub)).to.eventually.have.property('mode', 0o754)\n\n      // files without prior execute bit should be untouched\n      await expect(ipfs.files.stat(file)).to.eventually.have.property('mode', 0o644)\n\n      // files with prior execute bit should now be user and group executable\n      await expect(ipfs.files.stat(bin)).to.eventually.have.property('mode', 0o754)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/files/cp.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { concat as uint8ArrayConcat } from 'uint8arrays/concat'\nimport { nanoid } from 'nanoid'\nimport all from 'it-all'\nimport { fixtures } from '../utils/index.js'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { identity } from 'multiformats/hashes/identity'\nimport { CID } from 'multiformats/cid'\nimport { randomBytes } from 'iso-random-stream'\nimport { createShardedDirectory } from '../utils/create-sharded-directory.js'\nimport isShardAtPath from '../utils/is-shard-at-path.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testCp (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.files.cp', function () {\n    this.timeout(120 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => { ipfs = (await factory.spawn()).api })\n\n    after(() => factory.clean())\n\n    it('refuses to copy files without a source', async () => {\n      // @ts-expect-error invalid args\n      await expect(ipfs.files.cp()).to.eventually.be.rejected.with('Please supply at least one source')\n    })\n\n    it('refuses to copy files without a source, even with options', async () => {\n      // @ts-expect-error invalid args\n      await expect(ipfs.files.cp({})).to.eventually.be.rejected.with('Please supply at least one source')\n    })\n\n    it('refuses to copy files without a destination', async () => {\n      // @ts-expect-error invalid args\n      await expect(ipfs.files.cp('/source')).to.eventually.be.rejected.with('Please supply at least one source')\n    })\n\n    it('refuses to copy files without a destination, even with options', async () => {\n      // @ts-expect-error invalid args\n      await expect(ipfs.files.cp('/source', {})).to.eventually.be.rejected.with('Please supply at least one source')\n    })\n\n    it('refuses to copy a non-existent file', async () => {\n      await expect(ipfs.files.cp('/i-do-not-exist', '/destination', {})).to.eventually.be.rejected.with('does not exist')\n    })\n\n    it('refuses to copy multiple files to a non-existent child directory', async () => {\n      const src1 = `/src1-${Math.random()}`\n      const src2 = `/src2-${Math.random()}`\n      const parent = `/output-${Math.random()}`\n\n      await ipfs.files.write(src1, [], {\n        create: true\n      })\n      await ipfs.files.write(src2, [], {\n        create: true\n      })\n      await ipfs.files.mkdir(parent)\n      await expect(ipfs.files.cp([src1, src2], `${parent}/child`)).to.eventually.be.rejectedWith(Error)\n        .that.has.property('message').that.matches(/destination did not exist/)\n    })\n\n    it('refuses to copy files to an unreadable node', async () => {\n      const src1 = `/src2-${Math.random()}`\n      const parent = `/output-${Math.random()}`\n\n      const hash = await identity.digest(uint8ArrayFromString('derp'))\n      const cid = CID.createV1(identity.code, hash)\n      await ipfs.block.put(uint8ArrayFromString('derp'), {\n        mhtype: 'identity'\n      })\n      await ipfs.files.cp(`/ipfs/${cid}`, parent)\n\n      await ipfs.files.write(src1, [], {\n        create: true\n      })\n\n      await expect(ipfs.files.cp(src1, `${parent}/child`)).to.eventually.be.rejectedWith(Error)\n        .that.has.property('message').that.matches(/unsupported codec/i)\n    })\n\n    it('refuses to copy files to an exsting file', async () => {\n      const source = `/source-file-${Math.random()}.txt`\n      const destination = `/dest-file-${Math.random()}.txt`\n\n      await ipfs.files.write(source, randomBytes(100), {\n        create: true\n      })\n      await ipfs.files.write(destination, randomBytes(100), {\n        create: true\n      })\n\n      try {\n        await ipfs.files.cp(source, destination)\n        throw new Error('No error was thrown when trying to overwrite a file')\n      } catch (/** @type {any} */ err) {\n        expect(err.message).to.contain('directory already has entry by that name')\n      }\n    })\n\n    it('refuses to copy a file to itself', async () => {\n      const source = `/source-file-${Math.random()}.txt`\n\n      await ipfs.files.write(source, randomBytes(100), {\n        create: true\n      })\n\n      try {\n        await ipfs.files.cp(source, source)\n        throw new Error('No error was thrown for a non-existent file')\n      } catch (/** @type {any} */ err) {\n        expect(err.message).to.contain('directory already has entry by that name')\n      }\n    })\n\n    it('copies a file to new location', async () => {\n      const source = `/source-file-${Math.random()}.txt`\n      const destination = `/dest-file-${Math.random()}.txt`\n      const data = randomBytes(500)\n\n      await ipfs.files.write(source, data, {\n        create: true\n      })\n\n      await ipfs.files.cp(source, destination)\n\n      const bytes = uint8ArrayConcat(await all(ipfs.files.read(destination)))\n\n      expect(bytes).to.deep.equal(data)\n    })\n\n    it('copies a file to a pre-existing directory', async () => {\n      const source = `/source-file-${Math.random()}.txt`\n      const directory = `/dest-directory-${Math.random()}`\n      const destination = `${directory}${source}`\n\n      await ipfs.files.write(source, randomBytes(500), {\n        create: true\n      })\n      await ipfs.files.mkdir(directory)\n      await ipfs.files.cp(source, directory)\n\n      const stats = await ipfs.files.stat(destination)\n      expect(stats.size).to.equal(500)\n    })\n\n    it('copies directories', async () => {\n      const source = `/source-directory-${Math.random()}`\n      const destination = `/dest-directory-${Math.random()}`\n\n      await ipfs.files.mkdir(source)\n      await ipfs.files.cp(source, destination)\n\n      const stats = await ipfs.files.stat(destination)\n      expect(stats.type).to.equal('directory')\n    })\n\n    it('copies directories recursively', async () => {\n      const directory = `/source-directory-${Math.random()}`\n      const subDirectory = `/source-directory-${Math.random()}`\n      const source = `${directory}${subDirectory}`\n      const destination = `/dest-directory-${Math.random()}`\n\n      await ipfs.files.mkdir(source, {\n        parents: true\n      })\n      await ipfs.files.cp(directory, destination)\n\n      const stats = await ipfs.files.stat(destination)\n      expect(stats.type).to.equal('directory')\n\n      const subDirStats = await ipfs.files.stat(`${destination}/${subDirectory}`)\n      expect(subDirStats.type).to.equal('directory')\n    })\n\n    it('copies multiple files to new location', async () => {\n      const sources = [{\n        path: `/source-file-${Math.random()}.txt`,\n        data: randomBytes(500)\n      }, {\n        path: `/source-file-${Math.random()}.txt`,\n        data: randomBytes(500)\n      }]\n      const destination = `/dest-dir-${Math.random()}`\n\n      for (const source of sources) {\n        await ipfs.files.write(source.path, source.data, {\n          create: true\n        })\n      }\n\n      await ipfs.files.cp([sources[0].path, sources[1].path], destination, {\n        parents: true\n      })\n\n      for (const source of sources) {\n        const bytes = uint8ArrayConcat(await all(ipfs.files.read(`${destination}${source.path}`)))\n\n        expect(bytes).to.deep.equal(source.data)\n      }\n    })\n\n    it('copies files from ipfs paths', async () => {\n      const source = `/source-file-${Math.random()}.txt`\n      const destination = `/dest-file-${Math.random()}.txt`\n\n      await ipfs.files.write(source, randomBytes(100), {\n        create: true\n      })\n\n      const stats = await ipfs.files.stat(source)\n      await ipfs.files.cp(`/ipfs/${stats.cid}`, destination)\n\n      const destinationStats = await ipfs.files.stat(destination)\n      expect(destinationStats.size).to.equal(100)\n    })\n\n    it('copies files from deep ipfs paths', async () => {\n      const dir = `dir-${Math.random()}`\n      const file = `source-file-${Math.random()}.txt`\n      const source = `/${dir}/${file}`\n      const destination = `/dest-file-${Math.random()}.txt`\n\n      await ipfs.files.write(source, randomBytes(100), {\n        create: true,\n        parents: true\n      })\n\n      const stats = await ipfs.files.stat(`/${dir}`)\n      await ipfs.files.cp(`/ipfs/${stats.cid}/${file}`, destination)\n\n      const destinationStats = await ipfs.files.stat(destination)\n      expect(destinationStats.size).to.equal(100)\n    })\n\n    it('copies files to deep mfs paths and creates intermediate directories', async () => {\n      const source = `/source-file-${Math.random()}.txt`\n      const destination = `/really/deep/path/to/dest-file-${Math.random()}.txt`\n\n      await ipfs.files.write(source, randomBytes(100), {\n        create: true\n      })\n\n      await ipfs.files.cp(source, destination, {\n        parents: true\n      })\n\n      const destinationStats = await ipfs.files.stat(destination)\n      expect(destinationStats.size).to.equal(100)\n    })\n\n    it('fails to copy files to deep mfs paths when intermediate directories do not exist', async () => {\n      const source = `/source-file-${Math.random()}.txt`\n      const destination = `/really/deep/path-${Math.random()}/to-${Math.random()}/dest-file-${Math.random()}.txt`\n\n      await ipfs.files.write(source, randomBytes(100), {\n        create: true\n      })\n\n      await expect(ipfs.files.cp(source, destination)).to.eventually.be.rejected()\n    })\n\n    it('should respect metadata when copying files', async function () {\n      const testSrcPath = `/test-${nanoid()}`\n      const testDestPath = `/test-${nanoid()}`\n      const mode = parseInt('0321', 8)\n      const mtime = new Date()\n      const seconds = Math.floor(mtime.getTime() / 1000)\n      const expectedMtime = {\n        secs: seconds,\n        nsecs: (mtime.getTime() - (seconds * 1000)) * 1000\n      }\n\n      await ipfs.files.write(testSrcPath, uint8ArrayFromString('TEST'), {\n        create: true,\n        mode,\n        mtime\n      })\n      await ipfs.files.cp(testSrcPath, testDestPath)\n\n      const stats = await ipfs.files.stat(testDestPath)\n      expect(stats).to.have.deep.property('mtime', expectedMtime)\n      expect(stats).to.have.property('mode', mode)\n    })\n\n    it('should respect metadata when copying directories', async function () {\n      const testSrcPath = `/test-${nanoid()}`\n      const testDestPath = `/test-${nanoid()}`\n      const mode = parseInt('0321', 8)\n      const mtime = new Date()\n      const seconds = Math.floor(mtime.getTime() / 1000)\n      const expectedMtime = {\n        secs: seconds,\n        nsecs: (mtime.getTime() - (seconds * 1000)) * 1000\n      }\n\n      await ipfs.files.mkdir(testSrcPath, {\n        mode,\n        mtime\n      })\n      await ipfs.files.cp(testSrcPath, testDestPath, {\n        recursive: true\n      })\n\n      const stats = await ipfs.files.stat(testDestPath)\n      expect(stats).to.have.deep.property('mtime', expectedMtime)\n      expect(stats).to.have.property('mode', mode)\n    })\n\n    it('should respect metadata when copying from outside of mfs', async function () {\n      const testDestPath = `/test-${nanoid()}`\n      const mode = parseInt('0321', 8)\n      const mtime = new Date()\n      const seconds = Math.floor(mtime.getTime() / 1000)\n      const expectedMtime = {\n        secs: seconds,\n        nsecs: (mtime.getTime() - (seconds * 1000)) * 1000\n      }\n\n      const {\n        cid\n      } = await ipfs.add({\n        content: fixtures.smallFile.data,\n        mode,\n        mtime\n      })\n      await ipfs.files.cp(`/ipfs/${cid}`, testDestPath)\n\n      const stats = await ipfs.files.stat(testDestPath)\n      expect(stats).to.have.deep.property('mtime', expectedMtime)\n      expect(stats).to.have.property('mode', mode)\n    })\n\n    describe('with sharding', () => {\n      /** @type {import('ipfs-core-types').IPFS} */\n      let ipfs\n\n      before(async function () {\n        const ipfsd = await factory.spawn({\n          ipfsOptions: {\n            EXPERIMENTAL: {\n              // enable sharding for js\n              sharding: true\n            },\n            config: {\n              // enable sharding for go with automatic threshold dropped to the minimum so it shards everything\n              Internal: {\n                UnixFSShardingSizeThreshold: '1B'\n              }\n            }\n          }\n        })\n        ipfs = ipfsd.api\n      })\n\n      it('copies a sharded directory to a normal directory', async () => {\n        const shardedDirPath = await createShardedDirectory(ipfs)\n\n        const normalDir = `dir-${Math.random()}`\n        const normalDirPath = `/${normalDir}`\n\n        await ipfs.files.mkdir(normalDirPath)\n\n        await ipfs.files.cp(shardedDirPath, normalDirPath)\n\n        const finalShardedDirPath = `${normalDirPath}${shardedDirPath}`\n\n        // should still be a sharded directory\n        await expect(isShardAtPath(finalShardedDirPath, ipfs)).to.eventually.be.true()\n        expect((await ipfs.files.stat(finalShardedDirPath)).type).to.equal('directory')\n\n        const files = await all(ipfs.files.ls(finalShardedDirPath))\n\n        expect(files.length).to.be.ok()\n      })\n\n      it('copies a normal directory to a sharded directory', async () => {\n        const shardedDirPath = await createShardedDirectory(ipfs)\n\n        const normalDir = `dir-${Math.random()}`\n        const normalDirPath = `/${normalDir}`\n\n        await ipfs.files.mkdir(normalDirPath)\n\n        await ipfs.files.cp(normalDirPath, shardedDirPath)\n\n        const finalDirPath = `${shardedDirPath}${normalDirPath}`\n\n        // should still be a sharded directory\n        await expect(isShardAtPath(shardedDirPath, ipfs)).to.eventually.be.true()\n        expect((await ipfs.files.stat(shardedDirPath)).type).to.equal('directory')\n        expect((await ipfs.files.stat(finalDirPath)).type).to.equal('directory')\n      })\n\n      it('copies a file from a normal directory to a sharded directory', async () => {\n        const shardedDirPath = await createShardedDirectory(ipfs)\n\n        const file = `file-${Math.random()}.txt`\n        const filePath = `/${file}`\n        const finalFilePath = `${shardedDirPath}/${file}`\n\n        await ipfs.files.write(filePath, Uint8Array.from([0, 1, 2, 3]), {\n          create: true\n        })\n\n        await ipfs.files.cp(filePath, finalFilePath)\n\n        // should still be a sharded directory\n        await expect(isShardAtPath(shardedDirPath, ipfs)).to.eventually.be.true()\n        expect((await ipfs.files.stat(shardedDirPath)).type).to.equal('directory')\n        expect((await ipfs.files.stat(finalFilePath)).type).to.equal('file')\n      })\n\n      it('copies a file from a sharded directory to a sharded directory', async () => {\n        const shardedDirPath = await createShardedDirectory(ipfs)\n        const othershardedDirPath = await createShardedDirectory(ipfs)\n\n        const file = `file-${Math.random()}.txt`\n        const filePath = `${shardedDirPath}/${file}`\n        const finalFilePath = `${othershardedDirPath}/${file}`\n\n        await ipfs.files.write(filePath, Uint8Array.from([0, 1, 2, 3]), {\n          create: true\n        })\n\n        await ipfs.files.cp(filePath, finalFilePath)\n\n        // should still be a sharded directory\n        await expect(isShardAtPath(shardedDirPath, ipfs)).to.eventually.be.true()\n        expect((await ipfs.files.stat(shardedDirPath)).type).to.equal('directory')\n        await expect(isShardAtPath(othershardedDirPath, ipfs)).to.eventually.be.true()\n        expect((await ipfs.files.stat(othershardedDirPath)).type).to.equal('directory')\n        expect((await ipfs.files.stat(finalFilePath)).type).to.equal('file')\n      })\n\n      it('copies a file from a sharded directory to a normal directory', async () => {\n        const shardedDirPath = await createShardedDirectory(ipfs)\n        const dir = `dir-${Math.random()}`\n        const dirPath = `/${dir}`\n\n        const file = `file-${Math.random()}.txt`\n        const filePath = `${shardedDirPath}/${file}`\n        const finalFilePath = `${dirPath}/${file}`\n\n        await ipfs.files.write(filePath, Uint8Array.from([0, 1, 2, 3]), {\n          create: true\n        })\n\n        await ipfs.files.mkdir(dirPath)\n\n        await ipfs.files.cp(filePath, finalFilePath)\n\n        // should still be a sharded directory\n        await expect(isShardAtPath(shardedDirPath, ipfs)).to.eventually.be.true()\n        expect((await ipfs.files.stat(shardedDirPath)).type).to.equal('directory')\n        expect((await ipfs.files.stat(dirPath)).type).to.equal('directory')\n        expect((await ipfs.files.stat(finalFilePath)).type).to.equal('file')\n      })\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/files/flush.js",
    "content": "/* eslint-env mocha */\n\nimport { nanoid } from 'nanoid'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testFlush (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.files.flush', function () {\n    this.timeout(120 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => { ipfs = (await factory.spawn()).api })\n\n    after(() => factory.clean())\n\n    it('should not flush not found file/dir, expect error', async () => {\n      const testDir = `/test-${nanoid()}`\n\n      try {\n        await ipfs.files.flush(`${testDir}/404`)\n      } catch (/** @type {any} */ err) {\n        expect(err).to.exist()\n      }\n    })\n\n    it('should require a path', () => {\n      // @ts-expect-error invalid args\n      expect(ipfs.files.flush()).to.eventually.be.rejected()\n    })\n\n    it('should flush root', async () => {\n      const root = await ipfs.files.stat('/')\n      const flushed = await ipfs.files.flush('/')\n\n      expect(root.cid.toString()).to.equal(flushed.toString())\n    })\n\n    it('should flush specific dir', async () => {\n      const testDir = `/test-${nanoid()}`\n\n      await ipfs.files.mkdir(testDir, { parents: true })\n\n      const dirStats = await ipfs.files.stat(testDir)\n      const flushed = await ipfs.files.flush(testDir)\n\n      expect(dirStats.cid.toString()).to.equal(flushed.toString())\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/files/index.js",
    "content": "\nimport { createSuite } from '../utils/suite.js'\nimport { testChmod } from './chmod.js'\nimport { testCp } from './cp.js'\nimport { testFlush } from './flush.js'\nimport { testLs } from './ls.js'\nimport { testMkdir } from './mkdir.js'\nimport { testMv } from './mv.js'\nimport { testRead } from './read.js'\nimport { testRm } from './rm.js'\nimport { testStat } from './stat.js'\nimport { testTouch } from './touch.js'\nimport { testWrite } from './write.js'\n\nconst tests = {\n  chmod: testChmod,\n  cp: testCp,\n  flush: testFlush,\n  ls: testLs,\n  mkdir: testMkdir,\n  mv: testMv,\n  read: testRead,\n  rm: testRm,\n  stat: testStat,\n  touch: testTouch,\n  write: testWrite\n}\n\nexport default createSuite(tests)\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/files/ls.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { CID } from 'multiformats/cid'\nimport { createShardedDirectory } from '../utils/create-sharded-directory.js'\nimport all from 'it-all'\nimport { randomBytes } from 'iso-random-stream'\nimport * as raw from 'multiformats/codecs/raw'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testLs (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n  const largeFile = randomBytes(490668)\n\n  describe('.files.ls', function () {\n    this.timeout(120 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => { ipfs = (await factory.spawn()).api })\n\n    after(() => factory.clean())\n\n    it('should require a path', () => {\n      // @ts-expect-error invalid args\n      expect(all(ipfs.files.ls())).to.eventually.be.rejected()\n    })\n\n    it('lists the root directory', async () => {\n      const fileName = `small-file-${Math.random()}.txt`\n      const content = uint8ArrayFromString('Hello world')\n\n      await ipfs.files.write(`/${fileName}`, content, {\n        create: true\n      })\n\n      const files = await all(ipfs.files.ls('/'))\n\n      expect(files).to.have.lengthOf(1).and.to.containSubset([{\n        cid: CID.parse('Qmetpc7cZmN25Wcc6R27cGCAvCDqCS5GjHG4v7xABEfpmJ'),\n        name: fileName,\n        size: content.length,\n        type: 'file'\n      }])\n    })\n\n    it('refuses to lists files with an empty path', async () => {\n      await expect(all(ipfs.files.ls(''))).to.eventually.be.rejected()\n    })\n\n    it('refuses to lists files with an invalid path', async () => {\n      await expect(all(ipfs.files.ls('not-valid'))).to.eventually.be.rejected()\n    })\n\n    it('lists files in a directory', async () => {\n      const dirName = `dir-${Math.random()}`\n      const fileName = `small-file-${Math.random()}.txt`\n      const content = uint8ArrayFromString('Hello world')\n\n      await ipfs.files.write(`/${dirName}/${fileName}`, content, {\n        create: true,\n        parents: true\n      })\n\n      const files = await all(ipfs.files.ls(`/${dirName}`))\n\n      expect(files).to.have.lengthOf(1).and.to.containSubset([{\n        cid: CID.parse('Qmetpc7cZmN25Wcc6R27cGCAvCDqCS5GjHG4v7xABEfpmJ'),\n        name: fileName,\n        size: content.length,\n        type: 'file'\n      }])\n    })\n\n    it('lists a file', async () => {\n      const fileName = `small-file-${Math.random()}.txt`\n      const content = uint8ArrayFromString('Hello world')\n\n      await ipfs.files.write(`/${fileName}`, content, {\n        create: true\n      })\n\n      const files = await all(ipfs.files.ls(`/${fileName}`))\n\n      expect(files).to.have.lengthOf(1).and.to.containSubset([{\n        cid: CID.parse('Qmetpc7cZmN25Wcc6R27cGCAvCDqCS5GjHG4v7xABEfpmJ'),\n        name: fileName,\n        size: content.length,\n        type: 'file'\n      }])\n    })\n\n    it('fails to list non-existent file', async () => {\n      await expect(all(ipfs.files.ls('/i-do-not-exist'))).to.eventually.be.rejected()\n    })\n\n    it('lists a raw node', async () => {\n      const filePath = '/stat/large-file.txt'\n\n      await ipfs.files.write(filePath, largeFile, {\n        create: true,\n        parents: true,\n        rawLeaves: true\n      })\n\n      const stats = await ipfs.files.stat(filePath)\n      const { value: node } = await ipfs.dag.get(stats.cid)\n\n      expect(node).to.have.nested.property('Links[0].Hash.code', raw.code)\n\n      const child = node.Links[0]\n      const files = await all(ipfs.files.ls(`/ipfs/${child.Hash}`))\n\n      expect(files).to.have.lengthOf(1).and.to.containSubset([{\n        cid: child.Hash,\n        name: child.Hash.toString(),\n        size: 262144,\n        type: 'file'\n      }])\n    })\n\n    it('lists a raw node in an mfs directory', async () => {\n      const filePath = '/stat/large-file.txt'\n\n      await ipfs.files.write(filePath, largeFile, {\n        create: true,\n        parents: true,\n        rawLeaves: true\n      })\n\n      const stats = await ipfs.files.stat(filePath)\n      const cid = stats.cid\n      const { value: node } = await ipfs.dag.get(cid)\n\n      expect(node).to.have.nested.property('Links[0].Hash.code', raw.code)\n\n      const child = node.Links[0]\n      const dir = `/dir-with-raw-${Math.random()}`\n      const path = `${dir}/raw-${Math.random()}`\n\n      await ipfs.files.mkdir(dir)\n      await ipfs.files.cp(`/ipfs/${child.Hash}`, path)\n\n      const files = await all(ipfs.files.ls(`/ipfs/${child.Hash}`))\n\n      expect(files).to.have.lengthOf(1).and.to.containSubset([{\n        cid: child.Hash,\n        name: child.Hash.toString(),\n        size: 262144,\n        type: 'file'\n      }])\n    })\n\n    describe('with sharding', () => {\n      /** @type {import('ipfs-core-types').IPFS} */\n      let ipfs\n\n      before(async function () {\n        const ipfsd = await factory.spawn({\n          ipfsOptions: {\n            EXPERIMENTAL: {\n              // enable sharding for js\n              sharding: true\n            },\n            config: {\n              // enable sharding for go with automatic threshold dropped to the minimum so it shards everything\n              Internal: {\n                UnixFSShardingSizeThreshold: '1B'\n              }\n            }\n          }\n        })\n        ipfs = ipfsd.api\n      })\n\n      it('lists a sharded directory contents', async () => {\n        const fileCount = 1001\n        const dirPath = await createShardedDirectory(ipfs, fileCount)\n        const files = await all(ipfs.files.ls(dirPath))\n\n        expect(files.length).to.equal(fileCount)\n\n        files.forEach(file => {\n          // should be a file\n          expect(file.type).to.equal('file')\n        })\n      })\n\n      it('lists a file inside a sharded directory directly', async () => {\n        const dirPath = await createShardedDirectory(ipfs)\n        const files = await all(ipfs.files.ls(dirPath))\n        const filePath = `${dirPath}/${files[0].name}`\n\n        // should be able to ls new file directly\n        const file = await all(ipfs.files.ls(filePath))\n\n        expect(file).to.have.lengthOf(1).and.to.containSubset([files[0]])\n      })\n\n      it('lists the contents of a directory inside a sharded directory', async () => {\n        const shardedDirPath = await createShardedDirectory(ipfs)\n        const dirPath = `${shardedDirPath}/subdir-${Math.random()}`\n        const fileName = `small-file-${Math.random()}.txt`\n\n        await ipfs.files.mkdir(`${dirPath}`)\n        await ipfs.files.write(`${dirPath}/${fileName}`, Uint8Array.from([0, 1, 2, 3]), {\n          create: true\n        })\n\n        const files = await all(ipfs.files.ls(dirPath))\n\n        expect(files.length).to.equal(1)\n        expect(files.filter(file => file.name === fileName)).to.be.ok()\n      })\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/files/mkdir.js",
    "content": "/* eslint-env mocha */\n\nimport { nanoid } from 'nanoid'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { sha512 } from 'multiformats/hashes/sha2'\nimport { createShardedDirectory } from '../utils/create-sharded-directory.js'\nimport all from 'it-all'\nimport isShardAtPath from '../utils/is-shard-at-path.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testMkdir (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.files.mkdir', function () {\n    this.timeout(120 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    /**\n     * @param {number | string | undefined} mode\n     * @param {number} expectedMode\n     */\n    async function testMode (mode, expectedMode) {\n      const testPath = `/test-${nanoid()}`\n      await ipfs.files.mkdir(testPath, {\n        mode\n      })\n\n      const stats = await ipfs.files.stat(testPath)\n      expect(stats).to.have.property('mode', expectedMode)\n    }\n\n    /**\n     * @param {import('ipfs-unixfs').MtimeLike} mtime\n     * @param {import('ipfs-unixfs').MtimeLike} expectedMtime\n     */\n    async function testMtime (mtime, expectedMtime) {\n      const testPath = `/test-${nanoid()}`\n      await ipfs.files.mkdir(testPath, {\n        mtime\n      })\n\n      const stats = await ipfs.files.stat(testPath)\n      expect(stats).to.have.deep.property('mtime', expectedMtime)\n    }\n\n    before(async () => { ipfs = (await factory.spawn()).api })\n\n    after(() => factory.clean())\n\n    it('requires a directory', async () => {\n      await expect(ipfs.files.mkdir('')).to.eventually.be.rejected()\n    })\n\n    it('refuses to create a directory without a leading slash', async () => {\n      await expect(ipfs.files.mkdir('foo')).to.eventually.be.rejected()\n    })\n\n    it('refuses to recreate the root directory when -p is false', async () => {\n      await expect(ipfs.files.mkdir('/', {\n        parents: false\n      })).to.eventually.be.rejected()\n    })\n\n    it('refuses to create a nested directory when -p is false', async () => {\n      await expect(ipfs.files.mkdir('/foo/bar/baz', {\n        parents: false\n      })).to.eventually.be.rejected()\n    })\n\n    it('creates a directory', async () => {\n      const path = '/foo'\n\n      await ipfs.files.mkdir(path, {})\n\n      const stats = await ipfs.files.stat(path)\n      expect(stats.type).to.equal('directory')\n\n      const files = await all(ipfs.files.ls(path))\n\n      expect(files.length).to.equal(0)\n    })\n\n    it('refuses to create a directory that already exists', async () => {\n      const path = '/qux/quux/quuux'\n\n      await ipfs.files.mkdir(path, {\n        parents: true\n      })\n\n      await expect(ipfs.files.mkdir(path, {\n        parents: false\n      })).to.eventually.be.rejected()\n    })\n\n    it('does not error when creating a directory that already exists and parents is true', async () => {\n      const path = '/qux/quux/quuux'\n\n      await ipfs.files.mkdir(path, {\n        parents: true\n      })\n\n      await ipfs.files.mkdir(path, {\n        parents: true\n      })\n    })\n\n    it('creates a nested directory when -p is true', async () => {\n      const path = '/foo/bar/baz'\n\n      await ipfs.files.mkdir(path, {\n        parents: true\n      })\n\n      const files = await all(ipfs.files.ls(path))\n\n      expect(files.length).to.equal(0)\n    })\n\n    it('creates nested directories', async () => {\n      await ipfs.files.mkdir('/nested-dir')\n      await ipfs.files.mkdir('/nested-dir/baz')\n\n      const files = await all(ipfs.files.ls('/nested-dir'))\n\n      expect(files.length).to.equal(1)\n    })\n\n    it('creates a nested directory with a different CID version to the parent', async () => {\n      const directory = `cid-versions-${Math.random()}`\n      const directoryPath = `/${directory}`\n      const subDirectory = `cid-versions-${Math.random()}`\n      const subDirectoryPath = `${directoryPath}/${subDirectory}`\n\n      await ipfs.files.mkdir(directoryPath, {\n        cidVersion: 0\n      })\n\n      await expect(ipfs.files.stat(directoryPath)).to.eventually.have.nested.property('cid.version', 0)\n\n      await ipfs.files.mkdir(subDirectoryPath, {\n        cidVersion: 1\n      })\n\n      await expect(ipfs.files.stat(subDirectoryPath)).to.eventually.have.nested.property('cid.version', 1)\n    })\n\n    it('creates a nested directory with a different hash function to the parent', async () => {\n      const directory = `cid-versions-${Math.random()}`\n      const directoryPath = `/${directory}`\n      const subDirectory = `cid-versions-${Math.random()}`\n      const subDirectoryPath = `${directoryPath}/${subDirectory}`\n\n      await ipfs.files.mkdir(directoryPath, {\n        cidVersion: 0\n      })\n\n      await expect(ipfs.files.stat(directoryPath)).to.eventually.have.nested.property('cid.version', 0)\n\n      await ipfs.files.mkdir(subDirectoryPath, {\n        cidVersion: 1,\n        hashAlg: 'sha2-512'\n      })\n\n      await expect(ipfs.files.stat(subDirectoryPath)).to.eventually.have.nested.property('cid.multihash.code', sha512.code)\n    })\n\n    it('should make directory and have default mode', async function () {\n      await testMode(undefined, parseInt('0755', 8))\n    })\n\n    it('should make directory and specify mode as string', async function () {\n      const mode = '0321'\n      await testMode(mode, parseInt(mode, 8))\n    })\n\n    it('should make directory and specify mode as number', async function () {\n      const mode = parseInt('0321', 8)\n      await testMode(mode, mode)\n    })\n\n    it('should make directory and specify mtime as Date', async function () {\n      const mtime = new Date(5000)\n      await testMtime(mtime, {\n        secs: 5,\n        nsecs: 0\n      })\n    })\n\n    it('should make directory and specify mtime as { nsecs, secs }', async function () {\n      const mtime = {\n        secs: 5,\n        nsecs: 0\n      }\n      await testMtime(mtime, mtime)\n    })\n\n    it('should make directory and specify mtime as timespec', async function () {\n      await testMtime({\n        Seconds: 5,\n        FractionalNanoseconds: 0\n      }, {\n        secs: 5,\n        nsecs: 0\n      })\n    })\n\n    it('should make directory and specify mtime as hrtime', async function () {\n      const mtime = process.hrtime()\n      await testMtime(mtime, {\n        secs: mtime[0],\n        nsecs: mtime[1]\n      })\n    })\n\n    describe('with sharding', () => {\n      /** @type {import('ipfs-core-types').IPFS} */\n      let ipfs\n\n      before(async function () {\n        const ipfsd = await factory.spawn({\n          ipfsOptions: {\n            EXPERIMENTAL: {\n              // enable sharding for js\n              sharding: true\n            },\n            config: {\n              // enable sharding for go with automatic threshold dropped to the minimum so it shards everything\n              Internal: {\n                UnixFSShardingSizeThreshold: '1B'\n              }\n            }\n          }\n        })\n        ipfs = ipfsd.api\n      })\n\n      it('makes a directory inside a sharded directory', async () => {\n        const shardedDirPath = await createShardedDirectory(ipfs)\n        const dirPath = `${shardedDirPath}/subdir-${Math.random()}`\n\n        await ipfs.files.mkdir(`${dirPath}`)\n\n        await expect(isShardAtPath(shardedDirPath, ipfs)).to.eventually.be.true()\n        await expect(ipfs.files.stat(shardedDirPath)).to.eventually.have.property('type', 'directory')\n\n        await expect(isShardAtPath(dirPath, ipfs)).to.eventually.be.false()\n        await expect(ipfs.files.stat(dirPath)).to.eventually.have.property('type', 'directory')\n      })\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/files/mv.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { concat as uint8ArrayConcat } from 'uint8arrays/concat'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { createShardedDirectory } from '../utils/create-sharded-directory.js'\nimport { randomBytes } from 'iso-random-stream'\nimport isShardAtPath from '../utils/is-shard-at-path.js'\nimport all from 'it-all'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testMv (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.files.mv', function () {\n    this.timeout(120 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => { ipfs = (await factory.spawn()).api })\n\n    before(async () => {\n      await ipfs.files.mkdir('/test/lv1/lv2', { parents: true })\n      await ipfs.files.write('/test/a', uint8ArrayFromString('Hello, world!'), { create: true })\n    })\n    after(() => factory.clean())\n\n    it('refuses to move files without arguments', async () => {\n      // @ts-expect-error invalid args\n      await expect(ipfs.files.mv()).to.eventually.be.rejected()\n    })\n\n    it('refuses to move files without enough arguments', async () => {\n      // @ts-expect-error invalid args\n      await expect(ipfs.files.mv()).to.eventually.be.rejected()\n    })\n\n    it('moves a file', async () => {\n      const source = `/source-file-${Math.random()}.txt`\n      const destination = `/dest-file-${Math.random()}.txt`\n      const data = randomBytes(500)\n\n      await ipfs.files.write(source, data, {\n        create: true\n      })\n      await ipfs.files.mv(source, destination)\n\n      const bytes = uint8ArrayConcat(await all(ipfs.files.read(destination)))\n      expect(bytes).to.deep.equal(data)\n\n      await expect(ipfs.files.stat(source)).to.eventually.be.rejectedWith(/does not exist/)\n    })\n\n    it('moves a directory', async () => {\n      const source = `/source-directory-${Math.random()}`\n      const destination = `/dest-directory-${Math.random()}`\n\n      await ipfs.files.mkdir(source)\n      await ipfs.files.mv(source, destination, {\n        recursive: true\n      })\n      const stats = await ipfs.files.stat(destination)\n\n      expect(stats.type).to.equal('directory')\n\n      try {\n        await ipfs.files.stat(source)\n        throw new Error('Directory was copied but not removed')\n      } catch (/** @type {any} */ err) {\n        expect(err.message).to.contain('does not exist')\n      }\n    })\n\n    it('moves directories recursively', async () => {\n      const directory = `source-directory-${Math.random()}`\n      const subDirectory = `/source-directory-${Math.random()}`\n      const source = `/${directory}${subDirectory}`\n      const destination = `/dest-directory-${Math.random()}`\n\n      await ipfs.files.mkdir(source, {\n        parents: true\n      })\n      await ipfs.files.mv(`/${directory}`, destination, {\n        recursive: true\n      })\n\n      const stats = await ipfs.files.stat(destination)\n      expect(stats.type).to.equal('directory')\n\n      const subDirectoryStats = await ipfs.files.stat(`${destination}${subDirectory}`)\n      expect(subDirectoryStats.type).to.equal('directory')\n\n      try {\n        await ipfs.files.stat(source)\n        throw new Error('Directory was copied but not removed')\n      } catch (/** @type {any} */ err) {\n        expect(err.message).to.contain('does not exist')\n      }\n    })\n\n    describe('with sharding', () => {\n      /** @type {import('ipfs-core-types').IPFS} */\n      let ipfs\n\n      before(async function () {\n        const ipfsd = await factory.spawn({\n          ipfsOptions: {\n            EXPERIMENTAL: {\n              // enable sharding for js\n              sharding: true\n            },\n            config: {\n              // enable sharding for go with automatic threshold dropped to the minimum so it shards everything\n              Internal: {\n                UnixFSShardingSizeThreshold: '1B'\n              }\n            }\n          }\n        })\n        ipfs = ipfsd.api\n      })\n\n      it('moves a sharded directory to a normal directory', async () => {\n        const shardedDirPath = await createShardedDirectory(ipfs)\n        const dirPath = `/dir-${Math.random()}`\n        const finalShardedDirPath = `${dirPath}${shardedDirPath}`\n\n        await ipfs.files.mkdir(dirPath)\n        await ipfs.files.mv(shardedDirPath, dirPath)\n\n        await expect(isShardAtPath(finalShardedDirPath, ipfs)).to.eventually.be.true()\n        expect((await ipfs.files.stat(finalShardedDirPath)).type).to.equal('directory')\n        expect((await ipfs.files.stat(dirPath)).type).to.equal('directory')\n\n        try {\n          await ipfs.files.stat(shardedDirPath)\n          throw new Error('Dir was not removed')\n        } catch (/** @type {any} */ error) {\n          expect(error.message).to.contain('does not exist')\n        }\n      })\n\n      it('moves a normal directory to a sharded directory', async () => {\n        const shardedDirPath = await createShardedDirectory(ipfs)\n        const dirPath = `/dir-${Math.random()}`\n        const finalDirPath = `${shardedDirPath}${dirPath}`\n\n        await ipfs.files.mkdir(dirPath)\n        await ipfs.files.mv(dirPath, shardedDirPath)\n\n        await expect(isShardAtPath(shardedDirPath, ipfs)).to.eventually.be.true()\n        expect((await ipfs.files.stat(shardedDirPath)).type).to.equal('directory')\n        expect((await ipfs.files.stat(finalDirPath)).type).to.equal('directory')\n\n        try {\n          await ipfs.files.stat(dirPath)\n          throw new Error('Dir was not removed')\n        } catch (/** @type {any} */ error) {\n          expect(error.message).to.contain('does not exist')\n        }\n      })\n\n      it('moves a sharded directory to a sharded directory', async () => {\n        const shardedDirPath = await createShardedDirectory(ipfs)\n        const otherShardedDirPath = await createShardedDirectory(ipfs)\n        const finalShardedDirPath = `${shardedDirPath}${otherShardedDirPath}`\n\n        await ipfs.files.mv(otherShardedDirPath, shardedDirPath)\n\n        await expect(isShardAtPath(shardedDirPath, ipfs)).to.eventually.be.true()\n        expect((await ipfs.files.stat(shardedDirPath)).type).to.equal('directory')\n        await expect(isShardAtPath(finalShardedDirPath, ipfs)).to.eventually.be.true()\n        expect((await ipfs.files.stat(finalShardedDirPath)).type).to.equal('directory')\n\n        try {\n          await ipfs.files.stat(otherShardedDirPath)\n          throw new Error('Sharded dir was not removed')\n        } catch (/** @type {any} */ error) {\n          expect(error.message).to.contain('does not exist')\n        }\n      })\n\n      it('moves a file from a normal directory to a sharded directory', async () => {\n        const shardedDirPath = await createShardedDirectory(ipfs)\n        const dirPath = `/dir-${Math.random()}`\n        const file = `file-${Math.random()}.txt`\n        const filePath = `${dirPath}/${file}`\n        const finalFilePath = `${shardedDirPath}/${file}`\n\n        await ipfs.files.mkdir(dirPath)\n        await ipfs.files.write(filePath, Uint8Array.from([0, 1, 2, 3, 4]), {\n          create: true\n        })\n\n        await ipfs.files.mv(filePath, shardedDirPath)\n\n        await expect(isShardAtPath(shardedDirPath, ipfs)).to.eventually.be.true()\n        expect((await ipfs.files.stat(shardedDirPath)).type).to.equal('directory')\n        expect((await ipfs.files.stat(finalFilePath)).type).to.equal('file')\n\n        try {\n          await ipfs.files.stat(filePath)\n          throw new Error('File was not removed')\n        } catch (/** @type {any} */ error) {\n          expect(error.message).to.contain('does not exist')\n        }\n      })\n\n      it('moves a file from a sharded directory to a normal directory', async () => {\n        const shardedDirPath = await createShardedDirectory(ipfs)\n        const dirPath = `/dir-${Math.random()}`\n        const file = `file-${Math.random()}.txt`\n        const filePath = `${shardedDirPath}/${file}`\n        const finalFilePath = `${dirPath}/${file}`\n\n        await ipfs.files.mkdir(dirPath)\n        await ipfs.files.write(filePath, Uint8Array.from([0, 1, 2, 3, 4]), {\n          create: true\n        })\n\n        await ipfs.files.mv(filePath, dirPath)\n\n        await expect(isShardAtPath(shardedDirPath, ipfs)).to.eventually.be.true()\n        expect((await ipfs.files.stat(shardedDirPath)).type).to.equal('directory')\n        expect((await ipfs.files.stat(finalFilePath)).type).to.equal('file')\n        expect((await ipfs.files.stat(dirPath)).type).to.equal('directory')\n\n        try {\n          await ipfs.files.stat(filePath)\n          throw new Error('File was not removed')\n        } catch (/** @type {any} */ error) {\n          expect(error.message).to.contain('does not exist')\n        }\n      })\n\n      it('moves a file from a sharded directory to a sharded directory', async () => {\n        const shardedDirPath = await createShardedDirectory(ipfs)\n        const otherShardedDirPath = await createShardedDirectory(ipfs)\n        const file = `file-${Math.random()}.txt`\n        const filePath = `${shardedDirPath}/${file}`\n        const finalFilePath = `${otherShardedDirPath}/${file}`\n\n        await ipfs.files.write(filePath, Uint8Array.from([0, 1, 2, 3, 4]), {\n          create: true\n        })\n\n        await ipfs.files.mv(filePath, otherShardedDirPath)\n\n        await expect(isShardAtPath(shardedDirPath, ipfs)).to.eventually.be.true()\n        expect((await ipfs.files.stat(shardedDirPath)).type).to.equal('directory')\n        expect((await ipfs.files.stat(finalFilePath)).type).to.equal('file')\n        await expect(isShardAtPath(otherShardedDirPath, ipfs)).to.eventually.be.true()\n        expect((await ipfs.files.stat(otherShardedDirPath)).type).to.equal('directory')\n\n        try {\n          await ipfs.files.stat(filePath)\n          throw new Error('File was not removed')\n        } catch (/** @type {any} */ error) {\n          expect(error.message).to.contain('does not exist')\n        }\n      })\n\n      it('moves a file from a sub-shard of a sharded directory to a sharded directory', async () => {\n        const shardedDirPath = await createShardedDirectory(ipfs)\n        const otherShardedDirPath = await createShardedDirectory(ipfs)\n        const file = 'file-1a.txt'\n        const filePath = `${shardedDirPath}/${file}`\n        const finalFilePath = `${otherShardedDirPath}/${file}`\n\n        await ipfs.files.write(filePath, Uint8Array.from([0, 1, 2, 3, 4]), {\n          create: true\n        })\n\n        await ipfs.files.mv(filePath, otherShardedDirPath)\n\n        await expect(isShardAtPath(shardedDirPath, ipfs)).to.eventually.be.true()\n        expect((await ipfs.files.stat(shardedDirPath)).type).to.equal('directory')\n        expect((await ipfs.files.stat(finalFilePath)).type).to.equal('file')\n        await expect(isShardAtPath(otherShardedDirPath, ipfs)).to.eventually.be.true()\n        expect((await ipfs.files.stat(otherShardedDirPath)).type).to.equal('directory')\n\n        try {\n          await ipfs.files.stat(filePath)\n          throw new Error('File was not removed')\n        } catch (/** @type {any} */ error) {\n          expect(error.message).to.contain('does not exist')\n        }\n      })\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/files/read.js",
    "content": "/* eslint-env mocha */\n\nimport { concat as uint8ArrayConcat } from 'uint8arrays/concat'\nimport drain from 'it-drain'\nimport all from 'it-all'\nimport { fixtures } from '../utils/index.js'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { createShardedDirectory } from '../utils/create-sharded-directory.js'\nimport { randomBytes } from 'iso-random-stream'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testRead (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n  const smallFile = randomBytes(13)\n\n  describe('.files.read', function () {\n    this.timeout(120 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => { ipfs = (await factory.spawn()).api })\n\n    after(() => factory.clean())\n\n    it('reads a small file', async () => {\n      const filePath = '/small-file.txt'\n\n      await ipfs.files.write(filePath, smallFile, {\n        create: true\n      })\n\n      const bytes = uint8ArrayConcat(await all(ipfs.files.read(filePath)))\n\n      expect(bytes).to.deep.equal(smallFile)\n    })\n\n    it('reads a file with an offset', async () => {\n      const path = `/some-file-${Math.random()}.txt`\n      const data = randomBytes(100)\n      const offset = 10\n\n      await ipfs.files.write(path, data, {\n        create: true\n      })\n\n      const bytes = uint8ArrayConcat(await all(ipfs.files.read(path, {\n        offset\n      })))\n\n      expect(bytes).to.deep.equal(data.slice(offset))\n    })\n\n    it('reads a file with a length', async () => {\n      const path = `/some-file-${Math.random()}.txt`\n      const data = randomBytes(100)\n      const length = 10\n\n      await ipfs.files.write(path, data, {\n        create: true\n      })\n\n      const bytes = uint8ArrayConcat(await all(ipfs.files.read(path, {\n        length\n      })))\n\n      expect(bytes).to.deep.equal(data.slice(0, length))\n    })\n\n    it('reads a file with an offset and a length', async () => {\n      const path = `/some-file-${Math.random()}.txt`\n      const data = randomBytes(100)\n      const offset = 10\n      const length = 10\n\n      await ipfs.files.write(path, data, {\n        create: true\n      })\n\n      const buffer = uint8ArrayConcat(await all(ipfs.files.read(path, {\n        offset,\n        length\n      })))\n\n      expect(buffer).to.deep.equal(data.slice(offset, offset + length))\n    })\n\n    it('refuses to read a directory', async () => {\n      const path = '/'\n\n      await expect(drain(ipfs.files.read(path))).to.eventually.be.rejectedWith(/not a file/)\n    })\n\n    it('refuses to read a non-existent file', async () => {\n      const path = `/file-${Math.random()}.txt`\n\n      await expect(drain(ipfs.files.read(path))).to.eventually.be.rejectedWith(/does not exist/)\n    })\n\n    it('should read from outside of mfs', async () => {\n      const { cid } = await ipfs.add(fixtures.smallFile.data)\n      const testFileData = uint8ArrayConcat(await all(ipfs.files.read(`/ipfs/${cid}`)))\n      expect(testFileData).to.eql(fixtures.smallFile.data)\n    })\n\n    it('should be able to read rawLeaves files', async () => {\n      const { cid } = await ipfs.add(fixtures.smallFile.data, {\n        rawLeaves: true\n      })\n      await ipfs.files.cp(`/ipfs/${cid}`, '/raw-leaves.txt')\n      const testFileData = uint8ArrayConcat(await all(ipfs.files.read('/raw-leaves.txt')))\n      expect(testFileData).to.eql(fixtures.smallFile.data)\n    })\n\n    describe('with sharding', () => {\n      /** @type {import('ipfs-core-types').IPFS} */\n      let ipfs\n\n      before(async function () {\n        const ipfsd = await factory.spawn({\n          ipfsOptions: {\n            EXPERIMENTAL: {\n              // enable sharding for js\n              sharding: true\n            },\n            config: {\n              // enable sharding for go with automatic threshold dropped to the minimum so it shards everything\n              Internal: {\n                UnixFSShardingSizeThreshold: '1B'\n              }\n            }\n          }\n        })\n        ipfs = ipfsd.api\n      })\n\n      it('reads file from inside a sharded directory', async () => {\n        const shardedDirPath = await createShardedDirectory(ipfs)\n        const filePath = `${shardedDirPath}/file-${Math.random()}.txt`\n        const content = Uint8Array.from([0, 1, 2, 3, 4])\n\n        await ipfs.files.write(filePath, content, {\n          create: true\n        })\n\n        const bytes = uint8ArrayConcat(await all(ipfs.files.read(filePath)))\n\n        expect(bytes).to.deep.equal(content)\n      })\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/files/rm.js",
    "content": "/* eslint-env mocha */\n\nimport { nanoid } from 'nanoid'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { createShardedDirectory } from '../utils/create-sharded-directory.js'\nimport { createTwoShards } from '../utils/create-two-shards.js'\nimport { randomBytes } from 'iso-random-stream'\nimport isShardAtPath from '../utils/is-shard-at-path.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testRm (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.files.rm', function () {\n    this.timeout(300 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => { ipfs = (await factory.spawn()).api })\n\n    after(() => factory.clean())\n\n    it('should not remove not found file/dir, expect error', () => {\n      const testDir = `/test-${nanoid()}`\n\n      return expect(ipfs.files.rm(`${testDir}/a`)).to.eventually.be.rejected()\n    })\n\n    it('refuses to remove files without arguments', async () => {\n      // @ts-expect-error invalid args\n      await expect(ipfs.files.rm()).to.eventually.be.rejected()\n    })\n\n    it('refuses to remove the root path', async () => {\n      await expect(ipfs.files.rm('/')).to.eventually.be.rejected()\n    })\n\n    it('refuses to remove a directory without the recursive flag', async () => {\n      const path = `/directory-${Math.random()}.txt`\n\n      await ipfs.files.mkdir(path)\n\n      await expect(ipfs.files.rm(path)).to.eventually.be.rejectedWith(/use -r to remove directories/)\n    })\n\n    it('refuses to remove a non-existent file', async () => {\n      await expect(ipfs.files.rm(`/file-${Math.random()}`)).to.eventually.be.rejectedWith(/does not exist/)\n    })\n\n    it('removes a file', async () => {\n      const file = `/some-file-${Math.random()}.txt`\n\n      await ipfs.files.write(file, randomBytes(100), {\n        create: true,\n        parents: true\n      })\n\n      await ipfs.files.rm(file)\n\n      await expect(ipfs.files.stat(file)).to.eventually.be.rejectedWith(/does not exist/)\n    })\n\n    it('removes multiple files', async () => {\n      const file1 = `/some-file-${Math.random()}.txt`\n      const file2 = `/some-file-${Math.random()}.txt`\n\n      await ipfs.files.write(file1, randomBytes(100), {\n        create: true,\n        parents: true\n      })\n      await ipfs.files.write(file2, randomBytes(100), {\n        create: true,\n        parents: true\n      })\n      await ipfs.files.rm([file1, file2])\n\n      await expect(ipfs.files.stat(file1)).to.eventually.be.rejectedWith(/does not exist/)\n      await expect(ipfs.files.stat(file2)).to.eventually.be.rejectedWith(/does not exist/)\n    })\n\n    it('removes a directory', async () => {\n      const directory = `/directory-${Math.random()}`\n\n      await ipfs.files.mkdir(directory)\n      await ipfs.files.rm(directory, {\n        recursive: true\n      })\n\n      await expect(ipfs.files.stat(directory)).to.eventually.be.rejectedWith(/does not exist/)\n    })\n\n    it('recursively removes a directory', async () => {\n      const directory = `/directory-${Math.random()}`\n      const subdirectory = `/directory-${Math.random()}`\n      const path = `${directory}${subdirectory}`\n\n      await ipfs.files.mkdir(path, {\n        parents: true\n      })\n      await ipfs.files.rm(directory, {\n        recursive: true\n      })\n\n      await expect(ipfs.files.stat(path)).to.eventually.be.rejectedWith(/does not exist/)\n      await expect(ipfs.files.stat(directory)).to.eventually.be.rejectedWith(/does not exist/)\n    })\n\n    it('recursively removes a directory with files in', async () => {\n      const directory = `/directory-${Math.random()}`\n      const file = `${directory}/some-file-${Math.random()}.txt`\n\n      await ipfs.files.write(file, randomBytes(100), {\n        create: true,\n        parents: true\n      })\n      await ipfs.files.rm(directory, {\n        recursive: true\n      })\n\n      await expect(ipfs.files.stat(file)).to.eventually.be.rejectedWith(/does not exist/)\n      await expect(ipfs.files.stat(directory)).to.eventually.be.rejectedWith(/does not exist/)\n    })\n\n    describe('with sharding', () => {\n      /** @type {import('ipfs-core-types').IPFS} */\n      let ipfs\n\n      before(async function () {\n        const ipfsd = await factory.spawn({\n          ipfsOptions: {\n            EXPERIMENTAL: {\n              // enable sharding for js\n              sharding: true\n            },\n            config: {\n              // enable sharding for go with automatic threshold dropped to the minimum so it shards everything\n              Internal: {\n                UnixFSShardingSizeThreshold: '1B'\n              }\n            }\n          }\n        })\n        ipfs = ipfsd.api\n      })\n\n      it('recursively removes a sharded directory inside a normal directory', async () => {\n        const shardedDirPath = await createShardedDirectory(ipfs)\n        const dir = `dir-${Math.random()}`\n        const dirPath = `/${dir}`\n\n        await ipfs.files.mkdir(dirPath)\n\n        await ipfs.files.mv(shardedDirPath, dirPath)\n\n        const finalShardedDirPath = `${dirPath}${shardedDirPath}`\n\n        await expect(isShardAtPath(finalShardedDirPath, ipfs)).to.eventually.be.true()\n        expect((await ipfs.files.stat(finalShardedDirPath)).type).to.equal('directory')\n\n        await ipfs.files.rm(dirPath, {\n          recursive: true\n        })\n\n        await expect(ipfs.files.stat(dirPath)).to.eventually.be.rejectedWith(/does not exist/)\n        await expect(ipfs.files.stat(shardedDirPath)).to.eventually.be.rejectedWith(/does not exist/)\n      })\n\n      it('recursively removes a sharded directory inside a sharded directory', async () => {\n        const shardedDirPath = await createShardedDirectory(ipfs)\n        const otherDirPath = await createShardedDirectory(ipfs)\n\n        await ipfs.files.mv(shardedDirPath, otherDirPath)\n\n        const finalShardedDirPath = `${otherDirPath}${shardedDirPath}`\n\n        await expect(isShardAtPath(finalShardedDirPath, ipfs)).to.eventually.be.true()\n        expect((await ipfs.files.stat(finalShardedDirPath)).type).to.equal('directory')\n        await expect(isShardAtPath(otherDirPath, ipfs)).to.eventually.be.true()\n        expect((await ipfs.files.stat(otherDirPath)).type).to.equal('directory')\n\n        await ipfs.files.rm(otherDirPath, {\n          recursive: true\n        })\n\n        await expect(ipfs.files.stat(otherDirPath)).to.eventually.be.rejectedWith(/does not exist/)\n        await expect(ipfs.files.stat(finalShardedDirPath)).to.eventually.be.rejectedWith(/does not exist/)\n      })\n    })\n\n    it('results in the same hash as a sharded directory created by the importer when removing a file', async function () {\n      const {\n        nextFile,\n        dirWithAllFiles,\n        dirWithSomeFiles,\n        dirPath\n      } = await createTwoShards(ipfs, 1001)\n\n      await ipfs.files.cp(`/ipfs/${dirWithAllFiles}`, dirPath)\n\n      await ipfs.files.rm(nextFile.path)\n\n      const stats = await ipfs.files.stat(dirPath)\n      const updatedDirCid = stats.cid\n\n      await expect(isShardAtPath(dirPath, ipfs)).to.eventually.be.true()\n      expect((await ipfs.files.stat(dirPath)).type).to.equal('directory')\n      expect(updatedDirCid.toString()).to.deep.equal(dirWithSomeFiles.toString())\n    })\n\n    it('results in the same hash as a sharded directory created by the importer when removing a subshard', async function () {\n      const {\n        nextFile,\n        dirWithAllFiles,\n        dirWithSomeFiles,\n        dirPath\n      } = await createTwoShards(ipfs, 31)\n\n      await ipfs.files.cp(`/ipfs/${dirWithAllFiles}`, dirPath)\n\n      await ipfs.files.rm(nextFile.path)\n\n      const stats = await ipfs.files.stat(dirPath)\n      const updatedDirCid = stats.cid\n\n      await expect(isShardAtPath(dirPath, ipfs)).to.eventually.be.true()\n      expect((await ipfs.files.stat(dirPath)).type).to.equal('directory')\n      expect(updatedDirCid.toString()).to.deep.equal(dirWithSomeFiles.toString())\n    })\n\n    it('results in the same hash as a sharded directory created by the importer when removing a file from a subshard of a subshard', async function () {\n      const {\n        nextFile,\n        dirWithAllFiles,\n        dirWithSomeFiles,\n        dirPath\n      } = await createTwoShards(ipfs, 2187)\n\n      await ipfs.files.cp(`/ipfs/${dirWithAllFiles}`, dirPath)\n\n      await ipfs.files.rm(nextFile.path)\n\n      const stats = await ipfs.files.stat(dirPath)\n      const updatedDirCid = stats.cid\n\n      await expect(isShardAtPath(dirPath, ipfs)).to.eventually.be.true()\n      expect((await ipfs.files.stat(dirPath)).type).to.equal('directory')\n      expect(updatedDirCid.toString()).to.deep.equal(dirWithSomeFiles.toString())\n    })\n\n    it('results in the same hash as a sharded directory created by the importer when removing a subshard of a subshard', async function () {\n      const {\n        nextFile,\n        dirWithAllFiles,\n        dirWithSomeFiles,\n        dirPath\n      } = await createTwoShards(ipfs, 139)\n\n      await ipfs.files.cp(`/ipfs/${dirWithAllFiles}`, dirPath)\n\n      await ipfs.files.rm(nextFile.path)\n\n      const stats = await ipfs.files.stat(dirPath)\n      const updatedDirCid = stats.cid\n\n      await expect(isShardAtPath(dirPath, ipfs)).to.eventually.be.true()\n      expect((await ipfs.files.stat(dirPath)).type).to.equal('directory')\n      expect(updatedDirCid.toString()).to.deep.equal(dirWithSomeFiles.toString())\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/files/stat.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { nanoid } from 'nanoid'\nimport { fixtures } from '../utils/index.js'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { createShardedDirectory } from '../utils/create-sharded-directory.js'\nimport { CID } from 'multiformats/cid'\nimport { identity } from 'multiformats/hashes/identity'\nimport { randomBytes } from 'iso-random-stream'\nimport isShardAtPath from '../utils/is-shard-at-path.js'\nimport * as raw from 'multiformats/codecs/raw'\nimport { isBrowser } from 'wherearewe'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testStat (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n  const smallFile = randomBytes(13)\n  const largeFile = randomBytes(490668)\n\n  describe('.files.stat', function () {\n    this.timeout(120 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn({\n        args: factory.opts.type === 'go' ? [] : ['--enable-sharding-experiment']\n      })).api\n    })\n\n    before(async () => { await ipfs.add(fixtures.smallFile.data) })\n\n    after(() => factory.clean())\n\n    it('refuses to stat files with an empty path', async () => {\n      await expect(ipfs.files.stat('')).to.eventually.be.rejected()\n    })\n\n    it('refuses to lists files with an invalid path', async () => {\n      await expect(ipfs.files.stat('not-valid')).to.eventually.be.rejectedWith(/paths must start with a leading slash/)\n    })\n\n    it('fails to stat non-existent file', async () => {\n      await expect(ipfs.files.stat('/i-do-not-exist')).to.eventually.be.rejectedWith(/does not exist/)\n    })\n\n    it('stats an empty directory', async () => {\n      const path = `/directory-${Math.random()}`\n\n      await ipfs.files.mkdir(path)\n\n      await expect(ipfs.files.stat(path)).to.eventually.include({\n        size: 0,\n        cumulativeSize: 4,\n        blocks: 0,\n        type: 'directory'\n      })\n    })\n\n    it.skip('computes how much of the DAG is local', async () => {\n\n    })\n\n    it('stats a small file', async () => {\n      const filePath = `/stat-${Math.random()}/small-file-${Math.random()}.txt`\n\n      await ipfs.files.write(filePath, smallFile, {\n        create: true,\n        parents: true\n      })\n\n      await expect(ipfs.files.stat(filePath)).to.eventually.include({\n        size: smallFile.length,\n        cumulativeSize: 71,\n        blocks: 1,\n        type: 'file'\n      })\n    })\n\n    it('stats a large file', async () => {\n      const filePath = `/stat-${Math.random()}/large-file-${Math.random()}.txt`\n\n      await ipfs.files.write(filePath, largeFile, {\n        create: true,\n        parents: true\n      })\n\n      await expect(ipfs.files.stat(filePath)).to.eventually.include({\n        size: largeFile.length,\n        cumulativeSize: 490800,\n        blocks: 2,\n        type: 'file'\n      })\n    })\n\n    it('should stat a large browser File', async function () {\n      if (!isBrowser) {\n        this.skip()\n      }\n\n      const filePath = `/stat-${Math.random()}/large-file-${Math.random()}.txt`\n      const blob = new Blob([largeFile])\n\n      await ipfs.files.write(filePath, blob, {\n        create: true,\n        parents: true\n      })\n\n      await expect(ipfs.files.stat(filePath)).to.eventually.include({\n        size: largeFile.length,\n        cumulativeSize: 490800,\n        blocks: 2,\n        type: 'file'\n      })\n    })\n\n    it('stats a raw node', async () => {\n      const filePath = `/stat-${Math.random()}/large-file-${Math.random()}.txt`\n\n      await ipfs.files.write(filePath, largeFile, {\n        create: true,\n        parents: true,\n        rawLeaves: true\n      })\n\n      const stats = await ipfs.files.stat(filePath)\n      const { value: node } = await ipfs.dag.get(stats.cid)\n\n      expect(node).to.have.nested.property('Links[0].Hash.code', raw.code)\n\n      const child = node.Links[0]\n\n      const rawNodeStats = await ipfs.files.stat(`/ipfs/${child.Hash}`)\n\n      expect(rawNodeStats.cid.toString()).to.equal(child.Hash.toString())\n      expect(rawNodeStats.type).to.equal('file') // this is what go does\n    })\n\n    it('stats a raw node in an mfs directory', async () => {\n      const filePath = `/stat-${Math.random()}/large-file-${Math.random()}.txt`\n\n      await ipfs.files.write(filePath, largeFile, {\n        create: true,\n        parents: true,\n        rawLeaves: true\n      })\n\n      const stats = await ipfs.files.stat(filePath)\n      const { value: node } = await ipfs.dag.get(stats.cid)\n      const child = node.Links[0]\n\n      expect(child.Hash.code).to.equal(raw.code)\n\n      const dir = `/dir-with-raw-${Math.random()}`\n      const path = `${dir}/raw-${Math.random()}`\n\n      await ipfs.files.mkdir(dir)\n      await ipfs.files.cp(`/ipfs/${child.Hash}`, path)\n\n      const rawNodeStats = await ipfs.files.stat(path)\n\n      expect(rawNodeStats.cid.toString()).to.equal(child.Hash.toString())\n      expect(rawNodeStats.type).to.equal('file') // this is what go does\n    })\n\n    it('stats a dag-cbor node', async () => {\n      const path = '/cbor.node'\n      const node = {}\n      const cid = await ipfs.dag.put(node, {\n        storeCodec: 'dag-cbor',\n        hashAlg: 'sha2-256'\n      })\n      await ipfs.files.cp(`/ipfs/${cid}`, path)\n\n      const stats = await ipfs.files.stat(path)\n\n      expect(stats.cid.toString()).to.equal(cid.toString())\n    })\n\n    it('stats an identity CID', async () => {\n      const data = uint8ArrayFromString('derp')\n      const path = `/test-${nanoid()}/identity.node`\n      const hash = await identity.digest(data)\n      const cid = CID.createV1(identity.code, hash)\n      await ipfs.block.put(data, {\n        mhtype: 'identity'\n      })\n      await ipfs.files.cp(`/ipfs/${cid}`, path, {\n        parents: true\n      })\n\n      const stats = await ipfs.files.stat(path)\n\n      expect(stats.cid.toString()).to.equal(cid.toString())\n      expect(stats).to.have.property('size', data.length)\n    })\n\n    it('should stat file with mode', async function () {\n      const testDir = `/test-${nanoid()}`\n\n      await ipfs.files.mkdir(testDir, { parents: true })\n      await ipfs.files.write(`${testDir}/b`, uint8ArrayFromString('Hello, world!'), { create: true })\n\n      const stat = await ipfs.files.stat(`${testDir}/b`)\n\n      expect(stat).to.include({\n        mode: 0o644\n      })\n    })\n\n    it('should stat file with mtime', async function () {\n      const testDir = `/test-${nanoid()}`\n\n      await ipfs.files.mkdir(testDir, { parents: true })\n      await ipfs.files.write(`${testDir}/b`, uint8ArrayFromString('Hello, world!'), {\n        create: true,\n        mtime: {\n          secs: 5,\n          nsecs: 0\n        }\n      })\n\n      const stat = await ipfs.files.stat(`${testDir}/b`)\n\n      expect(stat).to.deep.include({\n        mtime: {\n          secs: 5,\n          nsecs: 0\n        }\n      })\n    })\n\n    it('should stat dir', async function () {\n      const testDir = `/test-${nanoid()}`\n\n      await ipfs.files.mkdir(testDir, { parents: true })\n      await ipfs.files.write(`${testDir}/a`, uint8ArrayFromString('Hello, world!'), { create: true })\n\n      const stat = await ipfs.files.stat(testDir)\n\n      expect(stat).to.include({\n        type: 'directory',\n        blocks: 1,\n        size: 0,\n        withLocality: false\n      })\n      expect(stat.local).to.be.undefined()\n      expect(stat.sizeLocal).to.be.undefined()\n    })\n\n    it('should stat dir with mode', async function () {\n      const testDir = `/test-${nanoid()}`\n\n      await ipfs.files.mkdir(testDir, { parents: true })\n      const stat = await ipfs.files.stat(testDir)\n\n      expect(stat).to.include({\n        mode: 0o755\n      })\n    })\n\n    it('should stat dir with mtime', async function () {\n      const testDir = `/test-${nanoid()}`\n\n      await ipfs.files.mkdir(testDir, {\n        parents: true,\n        mtime: {\n          secs: 5,\n          nsecs: 0\n        }\n      })\n\n      const stat = await ipfs.files.stat(testDir)\n\n      expect(stat).to.deep.include({\n        mtime: {\n          secs: 5,\n          nsecs: 0\n        }\n      })\n    })\n\n    it('should stat sharded dir with mode', async function () {\n      const testDir = `/test-${nanoid()}`\n\n      await ipfs.files.mkdir(testDir, { parents: true })\n      await ipfs.files.write(`${testDir}/a`, uint8ArrayFromString('Hello, world!'), {\n        create: true,\n        shardSplitThreshold: 0\n      })\n\n      const stat = await ipfs.files.stat(testDir)\n\n      await expect(isShardAtPath(testDir, ipfs)).to.eventually.be.true()\n      expect(stat).to.have.property('type', 'directory')\n      expect(stat).to.include({\n        mode: 0o755\n      })\n    })\n\n    it('should stat sharded dir with mtime', async function () {\n      const testDir = `/test-${nanoid()}`\n\n      await ipfs.files.mkdir(testDir, {\n        parents: true,\n        mtime: {\n          secs: 5,\n          nsecs: 0\n        }\n      })\n      await ipfs.files.write(`${testDir}/a`, uint8ArrayFromString('Hello, world!'), {\n        create: true,\n        shardSplitThreshold: 0\n      })\n\n      const stat = await ipfs.files.stat(testDir)\n\n      await expect(isShardAtPath(testDir, ipfs)).to.eventually.be.true()\n      expect(stat).to.have.property('type', 'directory')\n      expect(stat).to.deep.include({\n        mtime: {\n          secs: 5,\n          nsecs: 0\n        }\n      })\n    })\n\n    // TODO enable this test when this feature gets released on go-ipfs\n    it.skip('should stat withLocal file', async function () {\n      const stat = await ipfs.files.stat('/test/b', { withLocal: true })\n\n      expect({\n        ...stat,\n        cid: stat.cid.toString()\n      }).to.eql({\n        type: 'file',\n        blocks: 1,\n        size: 13,\n        cid: 'QmcZojhwragQr5qhTeFAmELik623Z21e3jBTpJXoQ9si1T',\n        cumulativeSize: 71,\n        withLocality: true,\n        local: true,\n        sizeLocal: 71\n      })\n    })\n\n    // TODO enable this test when this feature gets released on go-ipfs\n    it.skip('should stat withLocal dir', async function () {\n      const stat = await ipfs.files.stat('/test', { withLocal: true })\n\n      expect({\n        ...stat,\n        cid: stat.cid.toString()\n      }).to.eql({\n        type: 'directory',\n        blocks: 2,\n        size: 0,\n        cid: 'QmVrkkNurBCeJvPRohW5JTvJG4AxGrFg7FnmsZZUS6nJto',\n        cumulativeSize: 216,\n        withLocality: true,\n        local: true,\n        sizeLocal: 216\n      })\n    })\n\n    it('should stat outside of mfs', async () => {\n      const stat = await ipfs.files.stat(`/ipfs/${fixtures.smallFile.cid}`)\n\n      expect({\n        ...stat,\n        cid: stat.cid.toString()\n      }).to.include({\n        type: 'file',\n        blocks: 0,\n        size: 12,\n        cid: fixtures.smallFile.cid.toString(),\n        cumulativeSize: 20,\n        withLocality: false\n      })\n      expect(stat.local).to.be.undefined()\n      expect(stat.sizeLocal).to.be.undefined()\n    })\n\n    describe('with sharding', () => {\n      /** @type {import('ipfs-core-types').IPFS} */\n      let ipfs\n\n      before(async function () {\n        const ipfsd = await factory.spawn({\n          ipfsOptions: {\n            EXPERIMENTAL: {\n              // enable sharding for js\n              sharding: true\n            },\n            config: {\n              // enable sharding for go with automatic threshold dropped to the minimum so it shards everything\n              Internal: {\n                UnixFSShardingSizeThreshold: '1B'\n              }\n            }\n          }\n        })\n        ipfs = ipfsd.api\n      })\n\n      it('stats a sharded directory', async () => {\n        const shardedDirPath = await createShardedDirectory(ipfs)\n\n        const stats = await ipfs.files.stat(`${shardedDirPath}`)\n\n        expect(stats.type).to.equal('directory')\n        expect(stats.size).to.equal(0)\n      })\n\n      it('stats a file inside a sharded directory', async () => {\n        const shardedDirPath = await createShardedDirectory(ipfs)\n        const files = []\n\n        for await (const file of ipfs.files.ls(`${shardedDirPath}`)) {\n          files.push(file)\n        }\n\n        const stats = await ipfs.files.stat(`${shardedDirPath}/${files[0].name}`)\n\n        expect(stats.type).to.equal('file')\n        expect(stats.size).to.equal(7)\n      })\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/files/touch.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { concat as uint8ArrayConcat } from 'uint8arrays/concat'\nimport { nanoid } from 'nanoid'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport delay from 'delay'\nimport all from 'it-all'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testTouch (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.files.touch', function () {\n    this.timeout(120 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    /**\n     * @param {import('ipfs-unixfs').MtimeLike} mtime\n     * @param {import('ipfs-unixfs').MtimeLike} expectedMtime\n     */\n    async function testMtime (mtime, expectedMtime) {\n      const testPath = `/test-${nanoid()}`\n\n      await ipfs.files.write(testPath, uint8ArrayFromString('Hello, world!'), {\n        create: true\n      })\n\n      const stat = await ipfs.files.stat(testPath)\n      expect(stat).to.not.have.deep.property('mtime', expectedMtime)\n\n      await ipfs.files.touch(testPath, {\n        mtime\n      })\n\n      const stat2 = await ipfs.files.stat(testPath)\n      expect(stat2).to.have.deep.nested.property('mtime', expectedMtime)\n    }\n\n    before(async () => { ipfs = (await factory.spawn()).api })\n\n    after(() => factory.clean())\n\n    it('should have default mtime', async function () {\n      this.slow(5 * 1000)\n      const testPath = `/test-${nanoid()}`\n\n      await ipfs.files.write(testPath, uint8ArrayFromString('Hello, world!'), {\n        create: true\n      })\n\n      const stat = await ipfs.files.stat(testPath)\n      expect(stat).to.not.have.property('mtime')\n\n      await ipfs.files.touch(testPath)\n\n      const stat2 = await ipfs.files.stat(testPath)\n      expect(stat2).to.have.property('mtime').that.does.not.deep.equal({\n        secs: 0,\n        nsecs: 0\n      })\n    })\n\n    it('should update file mtime', async function () {\n      this.slow(5 * 1000)\n      const testPath = `/test-${nanoid()}`\n      const mtime = new Date()\n      const seconds = Math.floor(mtime.getTime() / 1000)\n\n      await ipfs.files.write(testPath, uint8ArrayFromString('Hello, world!'), {\n        create: true,\n        mtime\n      })\n      await delay(2000)\n      await ipfs.files.touch(testPath)\n\n      const stat = await ipfs.files.stat(testPath)\n      expect(stat).to.have.nested.property('mtime.secs').that.is.greaterThan(seconds)\n    })\n\n    it('should update directory mtime', async function () {\n      this.slow(5 * 1000)\n      const testPath = `/test-${nanoid()}`\n      const mtime = new Date()\n      const seconds = Math.floor(mtime.getTime() / 1000)\n\n      await ipfs.files.mkdir(testPath, {\n        create: true,\n        mtime\n      })\n      await delay(2000)\n      await ipfs.files.touch(testPath)\n\n      const stat2 = await ipfs.files.stat(testPath)\n      expect(stat2).to.have.nested.property('mtime.secs').that.is.greaterThan(seconds)\n    })\n\n    it('should update the mtime for a hamt-sharded-directory', async () => {\n      const path = `/foo-${Math.random()}`\n\n      await ipfs.files.mkdir(path, {\n        mtime: new Date()\n      })\n      await ipfs.files.write(`${path}/foo.txt`, uint8ArrayFromString('Hello world'), {\n        create: true,\n        shardSplitThreshold: 0\n      })\n      const originalMtime = (await ipfs.files.stat(path)).mtime\n\n      if (!originalMtime) {\n        throw new Error('No originalMtime found')\n      }\n\n      await delay(1000)\n      await ipfs.files.touch(path, {\n        flush: true\n      })\n\n      const updatedMtime = (await ipfs.files.stat(path)).mtime\n\n      if (!updatedMtime) {\n        throw new Error('No updatedMtime found')\n      }\n\n      expect(updatedMtime.secs).to.be.greaterThan(originalMtime.secs)\n    })\n\n    it('should create an empty file', async () => {\n      const path = `/foo-${Math.random()}`\n\n      await ipfs.files.touch(path, {\n        flush: true\n      })\n\n      const bytes = uint8ArrayConcat(await all(ipfs.files.read(path)))\n\n      expect(bytes.slice()).to.deep.equal(Uint8Array.from([]))\n    })\n\n    it('should set mtime as Date', async function () {\n      await testMtime(new Date(5000), {\n        secs: 5,\n        nsecs: 0\n      })\n    })\n\n    it('should set mtime as { nsecs, secs }', async function () {\n      const mtime = {\n        secs: 5,\n        nsecs: 0\n      }\n      await testMtime(mtime, mtime)\n    })\n\n    it('should set mtime as timespec', async function () {\n      await testMtime({\n        Seconds: 5,\n        FractionalNanoseconds: 0\n      }, {\n        secs: 5,\n        nsecs: 0\n      })\n    })\n\n    it('should set mtime as hrtime', async function () {\n      const mtime = process.hrtime()\n      await testMtime(mtime, {\n        secs: mtime[0],\n        nsecs: mtime[1]\n      })\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/files/write.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { concat as uint8ArrayConcat } from 'uint8arrays/concat'\nimport { nanoid } from 'nanoid'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { isNode } from 'ipfs-utils/src/env.js'\nimport { sha512 } from 'multiformats/hashes/sha2'\nimport { traverseLeafNodes } from '../utils/traverse-leaf-nodes.js'\nimport { createShardedDirectory } from '../utils/create-sharded-directory.js'\nimport { createTwoShards } from '../utils/create-two-shards.js'\nimport { randomBytes, randomStream } from 'iso-random-stream'\nimport all from 'it-all'\nimport isShardAtPath from '../utils/is-shard-at-path.js'\nimport * as raw from 'multiformats/codecs/raw'\nimport map from 'it-map'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testWrite (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n  const smallFile = randomBytes(13)\n  const largeFile = randomBytes(490668)\n\n  /**\n   * @param {(arg: { type: string, path: string, content: Uint8Array | AsyncIterable<Uint8Array>, contentSize: number }) => void} fn\n   */\n  const runTest = (fn) => {\n    const iterations = 5\n    const files = [{\n      type: 'Small file',\n      path: `/small-file-${Math.random()}.txt`,\n      content: smallFile,\n      contentSize: smallFile.length\n    }, {\n      type: 'Large file',\n      path: `/large-file-${Math.random()}.jpg`,\n      content: largeFile,\n      contentSize: largeFile.length\n    }, {\n      type: 'Really large file',\n      path: `/really-large-file-${Math.random()}.jpg`,\n      content: {\n        [Symbol.asyncIterator]: function * () {\n          for (let i = 0; i < iterations; i++) {\n            yield largeFile\n          }\n        }\n      },\n      contentSize: largeFile.length * iterations\n    }]\n\n    files.forEach((file) => {\n      fn(file)\n    })\n  }\n\n  describe('.files.write', function () {\n    this.timeout(300 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    /**\n     * @param {number | string} mode\n     * @param {number} expectedMode\n     */\n    async function testMode (mode, expectedMode) {\n      const testPath = `/test-${nanoid()}`\n\n      await ipfs.files.write(testPath, uint8ArrayFromString('Hello, world!'), {\n        create: true,\n        parents: true,\n        mode\n      })\n\n      const stats = await ipfs.files.stat(testPath)\n      expect(stats).to.have.property('mode', expectedMode)\n    }\n\n    /**\n     * @param {import('ipfs-unixfs').MtimeLike} mtime\n     * @param {import('ipfs-unixfs').MtimeLike} expectedMtime\n     */\n    async function testMtime (mtime, expectedMtime) {\n      const testPath = `/test-${nanoid()}`\n\n      await ipfs.files.write(testPath, uint8ArrayFromString('Hello, world!'), {\n        create: true,\n        parents: true,\n        mtime\n      })\n\n      const stats = await ipfs.files.stat(testPath)\n      expect(stats).to.have.deep.property('mtime', expectedMtime)\n    }\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('explodes if it cannot convert content to a source', async () => {\n      // @ts-expect-error invalid arg\n      await expect(ipfs.files.write('/foo-bad-source', -1, {\n        create: true\n      })).to.eventually.be.rejected()\n    })\n\n    it('explodes if given an invalid path', async () => {\n      // @ts-expect-error invalid arg\n      await expect(ipfs.files.write('foo-no-slash', null, {\n        create: true\n      })).to.eventually.be.rejected()\n    })\n\n    it('explodes if given a negative offset', async () => {\n      await expect(ipfs.files.write('/foo-negative-offset', uint8ArrayFromString('foo'), {\n        offset: -1\n      })).to.eventually.be.rejected()\n    })\n\n    it('explodes if given a negative length', async () => {\n      await expect(ipfs.files.write('/foo-negative-length', uint8ArrayFromString('foo'), {\n        length: -1\n      })).to.eventually.be.rejected()\n    })\n\n    it('creates a zero length file when passed a zero length', async () => {\n      const path = '/foo-zero-length'\n      await ipfs.files.write(path, uint8ArrayFromString('foo'), {\n        length: 0,\n        create: true\n      })\n\n      await expect(all(ipfs.files.ls(path))).to.eventually.have.lengthOf(1)\n        .and.to.have.nested.property('[0]').that.include({\n          name: 'foo-zero-length',\n          size: 0\n        })\n    })\n\n    it('writes a small file using a buffer', async () => {\n      const filePath = `/small-file-${Math.random()}.txt`\n\n      await ipfs.files.write(filePath, smallFile, {\n        create: true\n      })\n\n      await expect(ipfs.files.stat(filePath)).to.eventually.have.property('size', smallFile.length)\n      expect(uint8ArrayConcat(await all(ipfs.files.read(filePath)))).to.deep.equal(smallFile)\n    })\n\n    it('writes a small file using a string', async function () {\n      const filePath = `/string-${Math.random()}.txt`\n      const content = 'hello world'\n\n      await ipfs.files.write(filePath, content, {\n        create: true\n      })\n\n      await expect(ipfs.files.stat(filePath)).to.eventually.have.property('size', content.length)\n      expect(uint8ArrayConcat(await all(ipfs.files.read(filePath)))).to.deep.equal(uint8ArrayFromString(content))\n    })\n\n    it('writes part of a small file using a string', async function () {\n      const filePath = `/string-${Math.random()}.txt`\n      const content = 'hello world'\n\n      await ipfs.files.write(filePath, content, {\n        create: true,\n        length: 2\n      })\n\n      const stats = await ipfs.files.stat(filePath)\n\n      expect(stats.size).to.equal(2)\n    })\n\n    it('writes a small file using a Node stream (Node only)', async function () {\n      if (!isNode) {\n        this.skip()\n      }\n      const filePath = `/small-file-${Math.random()}.txt`\n      const stream = randomStream(1000)\n\n      await ipfs.files.write(filePath, stream, {\n        create: true\n      })\n\n      const stats = await ipfs.files.stat(filePath)\n\n      expect(stats.size).to.equal(1000)\n    })\n\n    it('writes a small file using an HTML5 Blob (Browser only)', async function () {\n      if (!global.Blob || !global.FileReader) {\n        return this.skip()\n      }\n\n      const filePath = `/small-file-${Math.random()}.txt`\n      const blob = new global.Blob([smallFile.buffer.slice(smallFile.byteOffset, smallFile.byteOffset + smallFile.byteLength)])\n\n      await ipfs.files.write(filePath, blob, {\n        create: true\n      })\n\n      const stats = await ipfs.files.stat(filePath)\n\n      expect(stats.size).to.equal(smallFile.length)\n    })\n\n    it('writes a small file with an escaped slash in the title', async () => {\n      const filePath = `/small-\\\\/file-${Math.random()}.txt`\n\n      await ipfs.files.write(filePath, smallFile, {\n        create: true\n      })\n\n      const stats = await ipfs.files.stat(filePath)\n\n      expect(stats.size).to.equal(smallFile.length)\n\n      await expect(ipfs.files.stat('/small-\\\\')).to.eventually.rejectedWith(/does not exist/)\n    })\n\n    it('writes a deeply nested small file', async () => {\n      const filePath = '/foo/bar/baz/qux/quux/garply/small-file.txt'\n\n      await ipfs.files.write(filePath, smallFile, {\n        create: true,\n        parents: true\n      })\n\n      const stats = await ipfs.files.stat(filePath)\n\n      expect(stats.size).to.equal(smallFile.length)\n    })\n\n    it('refuses to write to a file in a folder that does not exist', async () => {\n      const filePath = `/${Math.random()}/small-file.txt`\n\n      try {\n        await ipfs.files.write(filePath, smallFile, {\n          create: true\n        })\n        throw new Error('Writing a file to a non-existent folder without the --parents flag should have failed')\n      } catch (/** @type {any} */ err) {\n        expect(err.message).to.contain('does not exist')\n      }\n    })\n\n    it('refuses to write to a file that does not exist', async () => {\n      const filePath = `/small-file-${Math.random()}.txt`\n\n      try {\n        await ipfs.files.write(filePath, smallFile)\n        throw new Error('Writing a file to a non-existent file without the --create flag should have failed')\n      } catch (/** @type {any} */ err) {\n        expect(err.message).to.contain('file does not exist')\n      }\n    })\n\n    it('refuses to write to a path that has a file in it', async () => {\n      const filePath = `/small-file-${Math.random()}.txt`\n\n      await ipfs.files.write(filePath, Uint8Array.from([0, 1, 2, 3]), {\n        create: true\n      })\n\n      try {\n        await ipfs.files.write(`${filePath}/other-file-${Math.random()}.txt`, Uint8Array.from([0, 1, 2, 3]), {\n          create: true\n        })\n\n        throw new Error('Writing a path with a file in it should have failed')\n      } catch (/** @type {any} */ err) {\n        expect(err.message).to.contain('Not a directory')\n      }\n    })\n\n    runTest(({ type, path, content }) => {\n      it(`limits how many bytes to write to a file (${type})`, async () => {\n        await ipfs.files.write(path, content, {\n          create: true,\n          parents: true,\n          length: 2\n        })\n\n        const bytes = uint8ArrayConcat(await all(ipfs.files.read(path)))\n\n        expect(bytes.length).to.equal(2)\n      })\n    })\n\n    runTest(({ type, path, content, contentSize }) => {\n      it(`overwrites start of a file without truncating (${type})`, async () => {\n        const newContent = uint8ArrayFromString('Goodbye world')\n\n        await ipfs.files.write(path, content, {\n          create: true\n        })\n\n        await expect(ipfs.files.stat(path)).to.eventually.have.property('size', contentSize)\n\n        await ipfs.files.write(path, newContent)\n\n        const stats = await ipfs.files.stat(path)\n        expect(stats.size).to.equal(contentSize)\n\n        const buffer = uint8ArrayConcat(await all(ipfs.files.read(path, {\n          offset: 0,\n          length: newContent.length\n        })))\n\n        expect(buffer).to.deep.equal(newContent)\n      })\n    })\n\n    runTest(({ type, path, content, contentSize }) => {\n      it(`pads the start of a new file when an offset is specified (${type})`, async () => {\n        const offset = 10\n\n        await ipfs.files.write(path, content, {\n          offset,\n          create: true\n        })\n\n        await expect(ipfs.files.stat(path)).to.eventually.have.property('size', offset + contentSize)\n\n        const buffer = uint8ArrayConcat(await all(ipfs.files.read(path, {\n          offset: 0,\n          length: offset\n        })))\n\n        expect(buffer).to.deep.equal(new Uint8Array(offset))\n      })\n    })\n\n    runTest(({ type, path, content, contentSize }) => {\n      it(`expands a file when an offset is specified (${type})`, async () => {\n        const offset = contentSize - 1\n        const newContent = uint8ArrayFromString('Oh hai!')\n\n        await ipfs.files.write(path, content, {\n          create: true\n        })\n\n        await ipfs.files.write(path, newContent, {\n          offset\n        })\n\n        await expect(ipfs.files.stat(path)).to.eventually.have.property('size', contentSize + newContent.length - 1)\n\n        const buffer = uint8ArrayConcat(await all(ipfs.files.read(path, {\n          offset: offset\n        })))\n\n        expect(buffer).to.deep.equal(newContent)\n      })\n    })\n\n    runTest(({ type, path, content, contentSize }) => {\n      it(`expands a file when an offset is specified and the offset is longer than the file (${type})`, async () => {\n        const offset = contentSize + 5\n        const newContent = uint8ArrayFromString('Oh hai!')\n\n        await ipfs.files.write(path, content, {\n          create: true\n        })\n        await ipfs.files.write(path, newContent, {\n          offset\n        })\n\n        await expect(ipfs.files.stat(path)).to.eventually.have.property('size', newContent.length + offset)\n\n        const buffer = uint8ArrayConcat(await all(ipfs.files.read(path)))\n\n        if (!(content instanceof Uint8Array)) {\n          content = uint8ArrayConcat(await all(content))\n        }\n\n        expect(buffer).to.deep.equal(uint8ArrayConcat([content, Uint8Array.from([0, 0, 0, 0, 0]), newContent]))\n      })\n    })\n\n    runTest(({ type, path, content }) => {\n      it(`truncates a file after writing (${type})`, async () => {\n        const newContent = uint8ArrayFromString('Oh hai!')\n\n        await ipfs.files.write(path, content, {\n          create: true\n        })\n        await ipfs.files.write(path, newContent, {\n          truncate: true\n        })\n\n        await expect(ipfs.files.stat(path)).to.eventually.have.property('size', newContent.length)\n\n        const buffer = uint8ArrayConcat(await all(ipfs.files.read(path)))\n\n        expect(buffer).to.deep.equal(newContent)\n      })\n    })\n\n    runTest(({ type, path, content }) => {\n      it(`writes a file with raw blocks for newly created leaf nodes (${type})`, async () => {\n        await ipfs.files.write(path, content, {\n          create: true,\n          rawLeaves: true\n        })\n\n        const stats = await ipfs.files.stat(path)\n\n        let leafCount = 0\n\n        for await (const { cid } of traverseLeafNodes(ipfs, stats.cid)) {\n          leafCount++\n          expect(cid.code).to.equal(raw.code)\n        }\n\n        expect(leafCount).to.be.greaterThan(0)\n      })\n    })\n\n    it('supports concurrent writes', async function () {\n      /** @type {{ name: string, source: ReturnType<randomBytes>}[]} */\n      const files = []\n\n      for (let i = 0; i < 10; i++) {\n        files.push({\n          name: `source-file-${Math.random()}.txt`,\n          source: randomBytes(100)\n        })\n      }\n\n      await Promise.all(\n        files.map(({ name, source }) => ipfs.files.write(`/concurrent/${name}`, source, {\n          create: true,\n          parents: true\n        }))\n      )\n\n      const listing = await all(ipfs.files.ls('/concurrent'))\n      expect(listing.length).to.equal(files.length)\n\n      listing.forEach(listedFile => {\n        expect(files.find(file => file.name === listedFile.name))\n      })\n    })\n\n    it('rewrites really big files', async function () {\n      const initialStream = randomBytes(1024 * 300)\n      const newDataStream = randomBytes(1024 * 300)\n\n      const fileName = `/rewrite/file-${Math.random()}.txt`\n\n      await ipfs.files.write(fileName, initialStream, {\n        create: true,\n        parents: true\n      })\n\n      await ipfs.files.write(fileName, newDataStream, {\n        offset: 0\n      })\n\n      const actualBytes = uint8ArrayConcat(await all(ipfs.files.read(fileName)))\n\n      for (let i = 0; i < newDataStream.length; i++) {\n        if (newDataStream[i] !== actualBytes[i]) {\n          if (initialStream[i] === actualBytes[i]) {\n            throw new Error(`Bytes at index ${i} were not overwritten - expected ${newDataStream[i]} actual ${initialStream[i]}`)\n          }\n\n          throw new Error(`Bytes at index ${i} not equal - expected ${newDataStream[i]} actual ${actualBytes[i]}`)\n        }\n      }\n\n      expect(actualBytes).to.deep.equal(newDataStream)\n    })\n\n    it('writes a file with a different CID version to the parent', async () => {\n      const directory = `cid-versions-${Math.random()}`\n      const directoryPath = `/${directory}`\n      const fileName = `file-${Math.random()}.txt`\n      const filePath = `${directoryPath}/${fileName}`\n      const expectedBytes = Uint8Array.from([0, 1, 2, 3])\n\n      await ipfs.files.mkdir(directoryPath, {\n        cidVersion: 0\n      })\n\n      await expect(ipfs.files.stat(directoryPath)).to.eventually.have.nested.property('cid.version', 0)\n\n      await ipfs.files.write(filePath, expectedBytes, {\n        create: true,\n        cidVersion: 1\n      })\n\n      await expect(ipfs.files.stat(filePath)).to.eventually.have.nested.property('cid.version', 1)\n\n      const actualBytes = uint8ArrayConcat(await all(ipfs.files.read(filePath)))\n\n      expect(actualBytes).to.deep.equal(expectedBytes)\n    })\n\n    it('overwrites a file with a different CID version', async () => {\n      const directory = `cid-versions-${Math.random()}`\n      const directoryPath = `/${directory}`\n      const fileName = `file-${Math.random()}.txt`\n      const filePath = `${directoryPath}/${fileName}`\n      const expectedBytes = Uint8Array.from([0, 1, 2, 3])\n\n      await ipfs.files.mkdir(directoryPath, {\n        cidVersion: 0\n      })\n\n      await expect(ipfs.files.stat(directoryPath)).to.eventually.have.nested.property('cid.version', 0)\n\n      await ipfs.files.write(filePath, Uint8Array.from([5, 6]), {\n        create: true,\n        cidVersion: 0\n      })\n\n      await expect(ipfs.files.stat(filePath)).to.eventually.have.nested.property('cid.version', 0)\n\n      await ipfs.files.write(filePath, expectedBytes, {\n        cidVersion: 1\n      })\n\n      await expect(ipfs.files.stat(filePath)).to.eventually.have.nested.property('cid.version', 1)\n\n      const actualBytes = uint8ArrayConcat(await all(ipfs.files.read(filePath)))\n\n      expect(actualBytes).to.deep.equal(expectedBytes)\n    })\n\n    it('partially overwrites a file with a different CID version', async () => {\n      const directory = `cid-versions-${Math.random()}`\n      const directoryPath = `/${directory}`\n      const fileName = `file-${Math.random()}.txt`\n      const filePath = `${directoryPath}/${fileName}`\n\n      await ipfs.files.mkdir(directoryPath, {\n        cidVersion: 0\n      })\n\n      await expect(ipfs.files.stat(directoryPath)).to.eventually.have.nested.property('cid.version', 0)\n\n      await ipfs.files.write(filePath, Uint8Array.from([5, 6, 7, 8, 9, 10, 11]), {\n        create: true,\n        cidVersion: 0\n      })\n\n      await expect(ipfs.files.stat(filePath)).to.eventually.have.nested.property('cid.version', 0)\n\n      await ipfs.files.write(filePath, Uint8Array.from([0, 1, 2, 3]), {\n        cidVersion: 1,\n        offset: 1\n      })\n\n      await expect(ipfs.files.stat(filePath)).to.eventually.have.nested.property('cid.version', 1)\n\n      const actualBytes = uint8ArrayConcat(await all(ipfs.files.read(filePath)))\n\n      expect(actualBytes).to.deep.equal(Uint8Array.from([5, 0, 1, 2, 3, 10, 11]))\n    })\n\n    it('writes a file with a different hash function to the parent', async () => {\n      const directory = `cid-versions-${Math.random()}`\n      const directoryPath = `/${directory}`\n      const fileName = `file-${Math.random()}.txt`\n      const filePath = `${directoryPath}/${fileName}`\n      const expectedBytes = Uint8Array.from([0, 1, 2, 3])\n\n      await ipfs.files.mkdir(directoryPath, {\n        cidVersion: 0\n      })\n\n      await expect(ipfs.files.stat(directoryPath)).to.eventually.have.nested.property('cid.version', 0)\n\n      await ipfs.files.write(filePath, expectedBytes, {\n        create: true,\n        cidVersion: 1,\n        hashAlg: 'sha2-512'\n      })\n\n      await expect(ipfs.files.stat(filePath)).to.eventually.have.nested.property('cid.multihash.code', sha512.code)\n\n      const actualBytes = uint8ArrayConcat(await all(ipfs.files.read(filePath)))\n\n      expect(actualBytes).to.deep.equal(expectedBytes)\n    })\n\n    it('should write file and specify mode as a string', async function () {\n      const mode = '0321'\n      await testMode(mode, parseInt(mode, 8))\n    })\n\n    it('should write file and specify mode as a number', async function () {\n      const mode = parseInt('0321', 8)\n      await testMode(mode, mode)\n    })\n\n    it('should write file and specify mtime as Date', async function () {\n      const mtime = new Date()\n      const seconds = Math.floor(mtime.getTime() / 1000)\n      const expectedMtime = {\n        secs: seconds,\n        nsecs: (mtime.getTime() - (seconds * 1000)) * 1000\n      }\n      await testMtime(mtime, expectedMtime)\n    })\n\n    it('should write file and specify mtime as { nsecs, secs }', async function () {\n      const mtime = {\n        secs: 5,\n        nsecs: 0\n      }\n      await testMtime(mtime, mtime)\n    })\n\n    it('should write file and specify mtime as timespec', async function () {\n      await testMtime({\n        Seconds: 5,\n        FractionalNanoseconds: 0\n      }, {\n        secs: 5,\n        nsecs: 0\n      })\n    })\n\n    it('should write file and specify mtime as hrtime', async function () {\n      const mtime = process.hrtime()\n      await testMtime(mtime, {\n        secs: mtime[0],\n        nsecs: mtime[1]\n      })\n    })\n\n    describe('with sharding', () => {\n      /** @type {import('ipfs-core-types').IPFS} */\n      let ipfs\n\n      before(async function () {\n        const ipfsd = await factory.spawn({\n          ipfsOptions: {\n            EXPERIMENTAL: {\n              // enable sharding for js\n              sharding: true\n            },\n            config: {\n              // enable sharding for go with automatic threshold dropped to the minimum so it shards everything\n              Internal: {\n                UnixFSShardingSizeThreshold: '1B'\n              }\n            }\n          }\n        })\n        ipfs = ipfsd.api\n      })\n\n      it('shards a large directory when writing too many links to it', async () => {\n        const shardSplitThreshold = 10\n        const dirPath = `/sharded-dir-${Math.random()}`\n        const newFile = `file-${Math.random()}`\n        const newFilePath = `/${dirPath}/${newFile}`\n\n        await ipfs.files.mkdir(dirPath, {\n          shardSplitThreshold\n        })\n\n        for (let i = 0; i < shardSplitThreshold; i++) {\n          await ipfs.files.write(`/${dirPath}/file-${Math.random()}`, Uint8Array.from([0, 1, 2, 3]), {\n            create: true,\n            shardSplitThreshold\n          })\n        }\n\n        await expect(ipfs.files.stat(dirPath)).to.eventually.have.property('type', 'directory')\n\n        await ipfs.files.write(newFilePath, Uint8Array.from([0, 1, 2, 3]), {\n          create: true,\n          shardSplitThreshold\n        })\n\n        await expect(isShardAtPath(dirPath, ipfs)).to.eventually.be.true()\n        await expect(ipfs.files.stat(dirPath)).to.eventually.have.property('type', 'directory')\n\n        const files = await all(ipfs.files.ls(dirPath, {\n          long: true\n        }))\n\n        // new file should be in directory\n        expect(files.filter(file => file.name === newFile).pop()).to.be.ok()\n      })\n\n      it('results in the same hash as a sharded directory created by the importer when adding a new file', async function () {\n        const {\n          nextFile,\n          dirWithSomeFiles,\n          dirPath\n        } = await createTwoShards(ipfs, 75)\n\n        await ipfs.files.cp(`/ipfs/${dirWithSomeFiles}`, dirPath)\n\n        await ipfs.files.write(nextFile.path, nextFile.content, {\n          create: true\n        })\n\n        const stats = await ipfs.files.stat(dirPath)\n        const updatedDirCid = stats.cid\n\n        await expect(isShardAtPath(dirPath, ipfs)).to.eventually.be.true()\n        expect(stats.type).to.equal('directory')\n        expect(updatedDirCid.toString()).to.equal('QmbLw9uCrQaFgweMskqMrsVKTwwakSg94GuMT3zht1P7CQ')\n      })\n\n      it('results in the same hash as a sharded directory created by the importer when creating a new subshard', async function () {\n        const {\n          nextFile,\n          dirWithSomeFiles,\n          dirPath\n        } = await createTwoShards(ipfs, 100)\n\n        await ipfs.files.cp(`/ipfs/${dirWithSomeFiles}`, dirPath)\n\n        await ipfs.files.write(nextFile.path, nextFile.content, {\n          create: true\n        })\n\n        const stats = await ipfs.files.stat(dirPath)\n        const updatedDirCid = stats.cid\n\n        expect(updatedDirCid.toString()).to.equal('QmcGTKoaZeMxVenyxnkP2riibE8vSEPobkN1oxvcEZpBW5')\n      })\n\n      it('results in the same hash as a sharded directory created by the importer when adding a file to a subshard', async function () {\n        const {\n          nextFile,\n          dirWithSomeFiles,\n          dirPath\n        } = await createTwoShards(ipfs, 82)\n\n        await ipfs.files.cp(`/ipfs/${dirWithSomeFiles}`, dirPath)\n\n        await ipfs.files.write(nextFile.path, nextFile.content, {\n          create: true\n        })\n\n        const stats = await ipfs.files.stat(dirPath)\n        const updatedDirCid = stats.cid\n\n        await expect(isShardAtPath(dirPath, ipfs)).to.eventually.be.true()\n        expect(stats.type).to.equal('directory')\n        expect(updatedDirCid.toString()).to.deep.equal('QmXeJ4ercHcxdiX7Vxm1Hit9AwsTNXcwCw5Ad32yW2HdHR')\n      })\n\n      it('results in the same hash as a sharded directory created by the importer when adding a file to a subshard of a subshard', async function () {\n        const {\n          nextFile,\n          dirWithSomeFiles,\n          dirPath\n        } = await createTwoShards(ipfs, 2187)\n\n        await ipfs.files.cp(`/ipfs/${dirWithSomeFiles}`, dirPath)\n\n        await ipfs.files.write(nextFile.path, nextFile.content, {\n          create: true\n        })\n\n        const stats = await ipfs.files.stat(dirPath)\n        const updatedDirCid = stats.cid\n\n        await expect(isShardAtPath(dirPath, ipfs)).to.eventually.be.true()\n        expect(stats.type).to.equal('directory')\n        expect(updatedDirCid.toString()).to.deep.equal('QmY4o7GNvr5eZPnT6k6ALp5zkQ4eiUkJQ6eeUNsdSiqS4f')\n      })\n\n      it('writes a file to an already sharded directory', async () => {\n        const shardedDirPath = await createShardedDirectory(ipfs)\n\n        const newFile = `file-${Math.random()}`\n        const newFilePath = `${shardedDirPath}/${newFile}`\n\n        await ipfs.files.write(newFilePath, Uint8Array.from([0, 1, 2, 3]), {\n          create: true\n        })\n\n        // should still be a sharded directory\n        await expect(isShardAtPath(shardedDirPath, ipfs)).to.eventually.be.true()\n        await expect(ipfs.files.stat(shardedDirPath)).to.eventually.have.property('type', 'directory')\n\n        const files = await all(ipfs.files.ls(shardedDirPath, {\n          long: true\n        }))\n\n        // new file should be in the directory\n        expect(files.filter(file => file.name === newFile).pop()).to.be.ok()\n\n        // should be able to ls new file directly\n        await expect(all(ipfs.files.ls(newFilePath, {\n          long: true\n        }))).to.eventually.not.be.empty()\n      })\n\n      it('overwrites a file in a sharded directory when positions do not match', async () => {\n        const shardedDirPath = await createShardedDirectory(ipfs)\n        const newFile = 'file-0.6944395883502592'\n        const newFilePath = `${shardedDirPath}/${newFile}`\n        const newContent = Uint8Array.from([3, 2, 1, 0])\n\n        await ipfs.files.write(newFilePath, Uint8Array.from([0, 1, 2, 3]), {\n          create: true\n        })\n\n        // should still be a sharded directory\n        await expect(isShardAtPath(shardedDirPath, ipfs)).to.eventually.be.true()\n        await expect(ipfs.files.stat(shardedDirPath)).to.eventually.have.property('type', 'directory')\n\n        // overwrite the file\n        await ipfs.files.write(newFilePath, newContent, {\n          create: true\n        })\n\n        // read the file back\n        const buffer = uint8ArrayConcat(await all(ipfs.files.read(newFilePath)))\n\n        expect(buffer).to.deep.equal(newContent)\n\n        // should be able to ls new file directly\n        await expect(all(ipfs.files.ls(newFilePath, {\n          long: true\n        }))).to.eventually.not.be.empty()\n      })\n\n      it('overwrites file in a sharded directory', async () => {\n        const shardedDirPath = await createShardedDirectory(ipfs)\n        const newFile = `file-${Math.random()}`\n        const newFilePath = `${shardedDirPath}/${newFile}`\n        const newContent = Uint8Array.from([3, 2, 1, 0])\n\n        await ipfs.files.write(newFilePath, Uint8Array.from([0, 1, 2, 3]), {\n          create: true\n        })\n\n        // should still be a sharded directory\n        await expect(isShardAtPath(shardedDirPath, ipfs)).to.eventually.be.true()\n        await expect(ipfs.files.stat(shardedDirPath)).to.eventually.have.property('type', 'directory')\n\n        // overwrite the file\n        await ipfs.files.write(newFilePath, newContent, {\n          create: true\n        })\n\n        // read the file back\n        const buffer = uint8ArrayConcat(await all(ipfs.files.read(newFilePath)))\n\n        expect(buffer).to.deep.equal(newContent)\n\n        // should be able to ls new file directly\n        await expect(all(ipfs.files.ls(newFilePath, {\n          long: true\n        }))).to.eventually.not.be.empty()\n      })\n\n      it('overwrites a file in a subshard of a sharded directory', async () => {\n        const shardedDirPath = await createShardedDirectory(ipfs)\n        const newFile = 'file-1a.txt'\n        const newFilePath = `${shardedDirPath}/${newFile}`\n        const newContent = Uint8Array.from([3, 2, 1, 0])\n\n        await ipfs.files.write(newFilePath, Uint8Array.from([0, 1, 2, 3]), {\n          create: true\n        })\n\n        // should still be a sharded directory\n        await expect(isShardAtPath(shardedDirPath, ipfs)).to.eventually.be.true()\n        await expect(ipfs.files.stat(shardedDirPath)).to.eventually.have.property('type', 'directory')\n\n        // overwrite the file\n        await ipfs.files.write(newFilePath, newContent, {\n          create: true\n        })\n\n        // read the file back\n        const buffer = uint8ArrayConcat(await all(ipfs.files.read(newFilePath)))\n\n        expect(buffer).to.deep.equal(newContent)\n\n        // should be able to ls new file directly\n        await expect(all(ipfs.files.ls(newFilePath, {\n          long: true\n        }))).to.eventually.not.be.empty()\n      })\n\n      it('writes a file to a sub-shard of a shard that contains another sub-shard', async () => {\n        const data = Uint8Array.from([0, 1, 2])\n\n        await ipfs.files.mkdir('/hamttest-mfs')\n\n        const files = [\n          'file-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000-1398.txt',\n          'vivanov-sliceart',\n          'file-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000-1230.txt',\n          'methodify',\n          'fis-msprd-style-loader_0_13_1',\n          'js-form',\n          'file-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000-1181.txt',\n          'node-gr',\n          'yanvoidmodule',\n          'file-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000-1899.txt',\n          'file-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000-372.txt',\n          'file-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000-1032.txt',\n          'file-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000-1293.txt',\n          'file-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000-766.txt'\n        ]\n\n        for (const path of files) {\n          await ipfs.files.write(`/hamttest-mfs/${path}`, data, {\n            shardSplitThreshold: 0,\n            create: true\n          })\n        }\n\n        const beforeFiles = await all(map(ipfs.files.ls('/hamttest-mfs'), (entry) => entry.name))\n\n        expect(beforeFiles).to.have.lengthOf(files.length)\n\n        await ipfs.files.write('/hamttest-mfs/supermodule_test', data, {\n          shardSplitThreshold: 0,\n          create: true\n        })\n\n        const afterFiles = await all(map(ipfs.files.ls('/hamttest-mfs'), (entry) => entry.name))\n\n        expect(afterFiles).to.have.lengthOf(beforeFiles.length + 1)\n      })\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/get.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\nimport { concat as uint8ArrayConcat } from 'uint8arrays/concat'\nimport { fixtures } from './utils/index.js'\nimport { CID } from 'multiformats/cid'\nimport all from 'it-all'\nimport drain from 'it-drain'\nimport last from 'it-last'\nimport map from 'it-map'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from './utils/mocha.js'\nimport testTimeout from './utils/test-timeout.js'\nimport { importer } from 'ipfs-unixfs-importer'\nimport blockstore from './utils/blockstore-adapter.js'\nimport { extract } from 'it-tar'\nimport { pipe } from 'it-pipe'\nimport toBuffer from 'it-to-buffer'\nimport Pako from 'pako'\n\n/**\n * @typedef {import('it-stream-types').Source<Uint8Array>} Source\n */\n\n/**\n * @param {string} name\n * @param {string} [path]\n */\nconst content = (name, path) => {\n  if (!path) {\n    path = name\n  }\n\n  return {\n    path: `test-folder/${path}`,\n    content: fixtures.directory.files[name]\n  }\n}\n\n/**\n * @param {string} name\n */\nconst emptyDir = (name) => ({ path: `test-folder/${name}` })\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testGet (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.get', function () {\n    this.timeout(120 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    /**\n     * @param {Source} source\n     */\n    async function * gzipped (source) {\n      const inflator = new Pako.Inflate()\n\n      for await (const buf of source) {\n        inflator.push(buf, false)\n      }\n\n      inflator.push(new Uint8Array(0), true)\n\n      if (inflator.err) {\n        throw new Error(`Error ungzipping - message: \"${inflator.msg}\" code: ${inflator.err}`)\n      }\n\n      if (inflator.result instanceof Uint8Array) {\n        yield inflator.result\n      } else {\n        throw new Error('Unexpected gzip data type')\n      }\n    }\n\n    /**\n     * @param {Source} source\n     */\n    async function * tarballed (source) {\n      yield * pipe(\n        source,\n        extract(),\n        async function * (source) {\n          for await (const entry of source) {\n            yield {\n              ...entry,\n              body: await toBuffer(map(entry.body, (buf) => buf.slice()))\n            }\n          }\n        }\n      )\n    }\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n\n      await Promise.all([\n        all(importer({ content: fixtures.smallFile.data }, blockstore(ipfs))),\n        all(importer({ content: fixtures.bigFile.data }, blockstore(ipfs)))\n      ])\n    })\n\n    after(() => factory.clean())\n\n    it('should respect timeout option when getting files', () => {\n      return testTimeout(() => drain(ipfs.get(CID.parse('QmPDqvcuA4AkhBLBuh2y49yhUB98rCnxPxa3eVNC1kAbS1'), {\n        timeout: 1\n      })))\n    })\n\n    it('should get with a base58 encoded multihash', async () => {\n      const output = await pipe(\n        ipfs.get(fixtures.smallFile.cid),\n        tarballed,\n        (source) => all(source)\n      )\n      expect(output).to.have.lengthOf(1)\n      expect(output).to.have.nested.property('[0].header.name', fixtures.smallFile.cid.toString())\n      expect(output).to.have.nested.property('[0].body').that.equalBytes(fixtures.smallFile.data)\n    })\n\n    it('should get a file added as CIDv0 with a CIDv1', async () => {\n      const input = uint8ArrayFromString(`TEST${Math.random()}`)\n      const res = await all(importer({ content: input }, blockstore(ipfs)))\n\n      const cidv0 = res[0].cid\n      expect(cidv0.version).to.equal(0)\n\n      const cidv1 = cidv0.toV1()\n\n      const output = await pipe(\n        ipfs.get(cidv1),\n        tarballed,\n        (source) => all(source)\n      )\n      expect(output).to.have.lengthOf(1)\n      expect(output).to.have.nested.property('[0].header.name', cidv1.toString())\n      expect(output).to.have.nested.property('[0].body').that.equalBytes(input)\n    })\n\n    it('should get a file added as CIDv1 with a CIDv0', async () => {\n      const input = uint8ArrayFromString(`TEST${Math.random()}`)\n      const res = await all(importer({ content: input }, blockstore(ipfs), { cidVersion: 1, rawLeaves: false }))\n\n      const cidv1 = res[0].cid\n      expect(cidv1.version).to.equal(1)\n\n      const cidv0 = cidv1.toV0()\n\n      const output = await pipe(\n        ipfs.get(cidv0),\n        tarballed,\n        (source) => all(source)\n      )\n      expect(output).to.have.lengthOf(1)\n      expect(output).to.have.nested.property('[0].header.name', cidv0.toString())\n      expect(output).to.have.nested.property('[0].body').that.equalBytes(input)\n    })\n\n    it('should get a file added as CIDv1 with rawLeaves', async () => {\n      const input = uint8ArrayFromString(`TEST${Math.random()}`)\n      const res = await all(importer({ content: input }, blockstore(ipfs), { cidVersion: 1, rawLeaves: true }))\n\n      const cidv1 = res[0].cid\n      expect(cidv1.version).to.equal(1)\n\n      const output = await pipe(\n        ipfs.get(cidv1),\n        tarballed,\n        (source) => all(source)\n      )\n      expect(output).to.have.lengthOf(1)\n      expect(output).to.have.nested.property('[0].header.name', cidv1.toString())\n      expect(output).to.have.nested.property('[0].body').that.equalBytes(input)\n    })\n\n    it('should get a BIG file', async () => {\n      const output = await pipe(\n        ipfs.get(fixtures.bigFile.cid),\n        tarballed,\n        (source) => all(source)\n      )\n      expect(output).to.have.lengthOf(1)\n      expect(output).to.have.nested.property('[0].header.name', fixtures.bigFile.cid.toString())\n      expect(output).to.have.nested.property('[0].body').that.equalBytes(fixtures.bigFile.data)\n    })\n\n    it('should get a directory', async function () {\n      const dirs = [\n        content('pp.txt'),\n        content('holmes.txt'),\n        content('jungle.txt'),\n        content('alice.txt'),\n        emptyDir('empty-folder'),\n        content('files/hello.txt'),\n        content('files/ipfs.txt'),\n        emptyDir('files/empty')\n      ]\n\n      const res = await all(importer(dirs, blockstore(ipfs)))\n      const { cid } = res[res.length - 1]\n      expect(`${cid}`).to.equal(fixtures.directory.cid.toString())\n      const output = await pipe(\n        ipfs.get(cid),\n        tarballed,\n        (source) => all(source)\n      )\n\n      // Check paths\n      const paths = output.map((file) => { return file.header.name })\n      expect(paths).to.include.members([\n        'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP',\n        'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/alice.txt',\n        'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/empty-folder',\n        'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/files',\n        'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/files/empty',\n        'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/files/hello.txt',\n        'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/files/ipfs.txt',\n        'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/holmes.txt',\n        'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/jungle.txt',\n        'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/pp.txt'\n      ])\n\n      // Check contents\n      expect(output.map(f => uint8ArrayToString(f.body))).to.include.members([\n        uint8ArrayToString(fixtures.directory.files['alice.txt']),\n        uint8ArrayToString(fixtures.directory.files['files/hello.txt']),\n        uint8ArrayToString(fixtures.directory.files['files/ipfs.txt']),\n        uint8ArrayToString(fixtures.directory.files['holmes.txt']),\n        uint8ArrayToString(fixtures.directory.files['jungle.txt']),\n        uint8ArrayToString(fixtures.directory.files['pp.txt'])\n      ])\n    })\n\n    it('should get a nested directory', async function () {\n      const dirs = [\n        content('pp.txt', 'pp.txt'),\n        content('holmes.txt', 'foo/holmes.txt'),\n        content('jungle.txt', 'foo/bar/jungle.txt')\n      ]\n\n      const res = await all(importer(dirs, blockstore(ipfs)))\n      const { cid } = res[res.length - 1]\n      expect(`${cid}`).to.equal('QmVMXXo3c2bDPH9ayy2VKoXpykfYJHwAcU5YCJjPf7jg3g')\n      const output = await pipe(\n        ipfs.get(cid),\n        tarballed,\n        (source) => all(source)\n      )\n\n      // Check paths\n      expect(output.map((file) => { return file.header.name })).to.include.members([\n        'QmVMXXo3c2bDPH9ayy2VKoXpykfYJHwAcU5YCJjPf7jg3g',\n        'QmVMXXo3c2bDPH9ayy2VKoXpykfYJHwAcU5YCJjPf7jg3g/pp.txt',\n        'QmVMXXo3c2bDPH9ayy2VKoXpykfYJHwAcU5YCJjPf7jg3g/foo/holmes.txt',\n        'QmVMXXo3c2bDPH9ayy2VKoXpykfYJHwAcU5YCJjPf7jg3g/foo/bar/jungle.txt'\n      ])\n\n      // Check contents\n      expect(output.map(f => uint8ArrayToString(f.body))).to.include.members([\n        uint8ArrayToString(fixtures.directory.files['pp.txt']),\n        uint8ArrayToString(fixtures.directory.files['holmes.txt']),\n        uint8ArrayToString(fixtures.directory.files['jungle.txt'])\n      ])\n    })\n\n    it('should get with ipfs path, as object and nested value', async () => {\n      const file = {\n        path: 'a/testfile.txt',\n        content: fixtures.smallFile.data\n      }\n\n      const fileAdded = await last(importer([file], blockstore(ipfs)))\n\n      if (!fileAdded) {\n        throw new Error('No file was added')\n      }\n\n      expect(fileAdded).to.have.property('path', 'a')\n\n      const output = await pipe(\n        ipfs.get(`/ipfs/${fileAdded.cid}/testfile.txt`),\n        tarballed,\n        (source) => all(source)\n      )\n      expect(output).to.be.length(1)\n\n      expect(uint8ArrayToString(output[0].body)).to.equal('Plz add me!\\n')\n    })\n\n    it('should compress a file directly', async () => {\n      const output = await pipe(\n        ipfs.get(fixtures.smallFile.cid, {\n          compress: true,\n          compressionLevel: 5\n        }),\n        gzipped,\n        (source) => all(source)\n      )\n      expect(uint8ArrayConcat(output)).to.equalBytes(fixtures.smallFile.data)\n    })\n\n    it('should compress a file as a tarball', async () => {\n      const output = await pipe(\n        ipfs.get(fixtures.smallFile.cid, {\n          archive: true,\n          compress: true,\n          compressionLevel: 5\n        }),\n        gzipped,\n        tarballed,\n        (source) => all(source)\n      )\n      expect(output).to.have.nested.property('[0].body').that.equalBytes(fixtures.smallFile.data)\n    })\n\n    it('should not compress a directory', async () => {\n      const dirs = [\n        content('pp.txt'),\n        emptyDir('empty-folder'),\n        content('files/hello.txt')\n      ]\n\n      const res = await all(importer(dirs, blockstore(ipfs)))\n      const { cid } = res[res.length - 1]\n\n      await expect(drain(ipfs.get(cid, {\n        compress: true,\n        compressionLevel: 5\n      }))).to.eventually.be.rejectedWith(/file is not regular/)\n    })\n\n    it('should compress a file with invalid compression level', async () => {\n      await expect(drain(ipfs.get(fixtures.smallFile.cid, {\n        compress: true,\n        compressionLevel: 10\n      }))).to.eventually.be.rejected()\n    })\n\n    it('should compress a directory as a tarball', async () => {\n      const dirs = [\n        content('pp.txt'),\n        emptyDir('empty-folder'),\n        content('files/hello.txt')\n      ]\n\n      const res = await all(importer(dirs, blockstore(ipfs)))\n      const { cid } = res[res.length - 1]\n      const output = await pipe(\n        ipfs.get(cid, {\n          archive: true,\n          compress: true,\n          compressionLevel: 5\n        }),\n        gzipped,\n        tarballed,\n        (source) => all(source)\n      )\n\n      // Check paths\n      const paths = output.map((file) => { return file.header.name })\n      expect(paths).to.include.members([\n        'QmXpbhYKheGs5sopefFjsABsjr363QkRaJT4miRsN88ABU',\n        'QmXpbhYKheGs5sopefFjsABsjr363QkRaJT4miRsN88ABU/empty-folder',\n        'QmXpbhYKheGs5sopefFjsABsjr363QkRaJT4miRsN88ABU/files/hello.txt',\n        'QmXpbhYKheGs5sopefFjsABsjr363QkRaJT4miRsN88ABU/pp.txt'\n      ])\n\n      // Check contents\n      expect(output.map(f => uint8ArrayToString(f.body))).to.include.members([\n        uint8ArrayToString(fixtures.directory.files['files/hello.txt']),\n        uint8ArrayToString(fixtures.directory.files['pp.txt'])\n      ])\n    })\n\n    it('should error on invalid key', async () => {\n      const invalidCid = 'somethingNotMultihash'\n\n      await expect(all(ipfs.get(invalidCid))).to.eventually.be.rejected()\n    })\n\n    it('get path containing \"+\"s', async () => {\n      const filename = 'ti,c64x+mega++mod-pic.txt'\n      const subdir = 'tmp/c++files'\n      const expectedCid = 'QmPkmARcqjo5fqK1V1o8cFsuaXxWYsnwCNLJUYS4KeZyff'\n      const path = `${subdir}/${filename}`\n      const files = await all(ipfs.addAll([{\n        path,\n        content: path\n      }]))\n\n      expect(files[2].cid.toString()).to.equal(expectedCid)\n\n      const cid = 'QmPkmARcqjo5fqK1V1o8cFsuaXxWYsnwCNLJUYS4KeZyff'\n\n      const output = await pipe(\n        ipfs.get(CID.parse(cid)),\n        tarballed,\n        (source) => all(source)\n      )\n\n      expect(output).to.be.an('array').with.lengthOf(3)\n      expect(output).to.have.nested.property('[0].header.name', cid)\n      expect(output).to.have.nested.property('[1].header.name', `${cid}/c++files`)\n      expect(output).to.have.nested.property('[2].header.name', `${cid}/c++files/ti,c64x+mega++mod-pic.txt`)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/index.js",
    "content": "import { createSuite } from './utils/suite.js'\nimport { testAdd } from './add.js'\nimport { testAddAll } from './add-all.js'\nimport { testCat } from './cat.js'\nimport { testGet } from './get.js'\nimport { testLs } from './ls.js'\nimport { testRefs } from './refs.js'\nimport { testRefsLocal } from './refs-local.js'\nimport testFiles from './files/index.js'\nimport testBitswap from './bitswap/index.js'\nimport testBlock from './block/index.js'\nimport testDag from './dag/index.js'\nimport testObject from './object/index.js'\nimport testPin from './pin/index.js'\nimport testBootstrap from './bootstrap/index.js'\nimport testDht from './dht/index.js'\nimport testName from './name/index.js'\nimport testNamePubsub from './name-pubsub/index.js'\nimport testPing from './ping/index.js'\nimport testPubsub from './pubsub/index.js'\nimport testSwarm from './swarm/index.js'\nimport testConfig from './config/index.js'\nimport testKey from './key/index.js'\nimport testMiscellaneous from './miscellaneous/index.js'\nimport testRepo from './repo/index.js'\nimport testStats from './stats/index.js'\n\nexport const root = createSuite({\n  add: testAdd,\n  addAll: testAddAll,\n  cat: testCat,\n  get: testGet,\n  ls: testLs,\n  refs: testRefs,\n  refsLocal: testRefsLocal\n})\n\nexport const files = testFiles\nexport const bitswap = testBitswap\nexport const block = testBlock\nexport const dag = testDag\nexport const object = testObject\nexport const pin = testPin\nexport const bootstrap = testBootstrap\nexport const dht = testDht\nexport const name = testName\nexport const namePubsub = testNamePubsub\nexport const ping = testPing\nexport const pubsub = testPubsub\nexport const swarm = testSwarm\nexport const config = testConfig\nexport const key = testKey\nexport const miscellaneous = testMiscellaneous\nexport const repo = testRepo\nexport const stats = testStats\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/key/gen.js",
    "content": "/* eslint-env mocha */\n\nimport { nanoid } from 'nanoid'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { supportedKeys, importKey } from '@libp2p/crypto/keys'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testGen (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.key.gen', () => {\n    const keyTypes = [\n      {\n        opts: { type: 'rsa', size: 2048 },\n        expectedType: supportedKeys.rsa.RsaPrivateKey\n      },\n      {\n        opts: { type: 'ed25519' },\n        expectedType: supportedKeys.ed25519.Ed25519PrivateKey\n      },\n      {\n        opts: { },\n        expectedType: supportedKeys.ed25519.Ed25519PrivateKey\n      }\n    ]\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    keyTypes.forEach((kt) => {\n      it(`should generate a new ${kt.opts.type || 'default'} key`, async function () {\n        this.timeout(20 * 1000)\n        const name = nanoid()\n        const key = await ipfs.key.gen(name, kt.opts)\n        expect(key).to.exist()\n        expect(key).to.have.property('name', name)\n        expect(key).to.have.property('id')\n\n        try {\n          const password = nanoid() + '-' + nanoid()\n          const exported = await ipfs.key.export(name, password)\n          const imported = await importKey(exported, password)\n\n          expect(imported).to.be.an.instanceOf(kt.expectedType)\n        } catch (/** @type {any} */ err) {\n          if (err.code === 'ERR_NOT_IMPLEMENTED') {\n            // key export is not exposed over the HTTP API\n            this.skip()\n          }\n\n          throw err\n        }\n      })\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/key/import.js",
    "content": "/* eslint-env mocha */\n\nimport { nanoid } from 'nanoid'\nimport { keys } from '@libp2p/crypto'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testImport (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.key.import', () => {\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should import an exported key', async () => {\n      const password = nanoid()\n\n      const key = await keys.generateKeyPair('Ed25519')\n      const exported = await key.export(password)\n\n      const importedKey = await ipfs.key.import('clone', exported, password)\n      expect(importedKey).to.exist()\n      expect(importedKey).to.have.property('name', 'clone')\n      expect(importedKey).to.have.property('id')\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/key/index.js",
    "content": "\nimport { createSuite } from '../utils/suite.js'\nimport { testGen } from './gen.js'\nimport { testList } from './list.js'\nimport { testRename } from './rename.js'\nimport { testRm } from './rm.js'\nimport { testImport } from './import.js'\n\nconst tests = {\n  gen: testGen,\n  list: testList,\n  rename: testRename,\n  rm: testRm,\n  import: testImport\n}\n\nexport default createSuite(tests)\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/key/list.js",
    "content": "/* eslint-env mocha */\n\nimport { nanoid } from 'nanoid'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testList (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.key.list', () => {\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should list all the keys', async function () {\n      this.timeout(60 * 1000)\n\n      const keys = await Promise.all([1, 2, 3].map(() => ipfs.key.gen(nanoid(), { type: 'rsa', size: 2048 })))\n\n      const res = await ipfs.key.list()\n      expect(res).to.exist()\n      expect(res).to.be.an('array')\n      expect(res.length).to.be.above(keys.length - 1)\n\n      keys.forEach(key => {\n        const found = res.find(({ id, name }) => name === key.name && id === key.id)\n        expect(found).to.exist()\n      })\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/key/rename.js",
    "content": "/* eslint-env mocha */\n\nimport { nanoid } from 'nanoid'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testRename (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.key.rename', () => {\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should rename a key', async function () {\n      this.timeout(30 * 1000)\n\n      const oldName = nanoid()\n      const newName = nanoid()\n\n      const key = await ipfs.key.gen(oldName, { type: 'rsa', size: 2048 })\n\n      const renameRes = await ipfs.key.rename(oldName, newName)\n      expect(renameRes).to.exist()\n      expect(renameRes).to.have.property('was', oldName)\n      expect(renameRes).to.have.property('now', newName)\n      expect(renameRes).to.have.property('id', key.id)\n\n      const res = await ipfs.key.list()\n      expect(res.find(k => k.name === newName)).to.exist()\n      expect(res.find(k => k.name === oldName)).to.not.exist()\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/key/rm.js",
    "content": "/* eslint-env mocha */\n\nimport { nanoid } from 'nanoid'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testRm (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.key.rm', () => {\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should rm a key', async function () {\n      this.timeout(30 * 1000)\n\n      const key = await ipfs.key.gen(nanoid(), { type: 'rsa', size: 2048 })\n\n      const removeRes = await ipfs.key.rm(key.name)\n      expect(removeRes).to.exist()\n      expect(removeRes).to.have.property('name', key.name)\n      expect(removeRes).to.have.property('id', key.id)\n\n      const res = await ipfs.key.list()\n      expect(res.find(k => k.name === key.name)).to.not.exist()\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/ls.js",
    "content": "/* eslint-env mocha */\n\nimport { fixtures } from './utils/index.js'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from './utils/mocha.js'\nimport all from 'it-all'\nimport { CID } from 'multiformats/cid'\nimport testTimeout from './utils/test-timeout.js'\n\n/**\n * @param {string} prefix\n */\nconst randomName = prefix => `${prefix}${Math.round(Math.random() * 1000)}`\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testLs (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.ls', function () {\n    this.timeout(120 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should respect timeout option when listing files', () => {\n      return testTimeout(() => ipfs.ls(CID.parse('QmNonExistentCiD8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXg'), {\n        timeout: 1\n      }))\n    })\n\n    it('should ls with a base58 encoded CID', async function () {\n      /**\n       * @param {string} name\n       */\n      const content = (name) => ({\n        path: `test-folder/${name}`,\n        content: fixtures.directory.files[name]\n      })\n\n      /**\n       * @param {string} name\n       */\n      const emptyDir = (name) => ({ path: `test-folder/${name}` })\n\n      const dirs = [\n        content('pp.txt'),\n        content('holmes.txt'),\n        content('jungle.txt'),\n        content('alice.txt'),\n        emptyDir('empty-folder'),\n        content('files/hello.txt'),\n        content('files/ipfs.txt'),\n        emptyDir('files/empty')\n      ]\n\n      const res = await all(ipfs.addAll(dirs))\n\n      const root = res[res.length - 1]\n      expect(root.path).to.equal('test-folder')\n      expect(root.cid.toString()).to.equal(fixtures.directory.cid.toString())\n\n      const cid = 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP'\n      const output = await all(ipfs.ls(cid))\n\n      expect(output).to.have.lengthOf(6)\n      expect(output[0].name).to.equal('alice.txt')\n      expect(output[0].path).to.equal('QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/alice.txt')\n      expect(output[0].size).to.equal(11685)\n      expect(output[0].cid.toString()).to.equal('QmZyUEQVuRK3XV7L9Dk26pg6RVSgaYkiSTEdnT2kZZdwoi')\n      expect(output[0].type).to.equal('file')\n\n      expect(output[1].name).to.equal('empty-folder')\n      expect(output[1].path).to.equal('QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/empty-folder')\n      expect(output[1].size).to.equal(0)\n      expect(output[1].cid.toString()).to.equal('QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn')\n      expect(output[1].type).to.equal('dir')\n\n      expect(output[2].name).to.equal('files')\n      expect(output[2].path).to.equal('QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/files')\n      expect(output[2].size).to.equal(0)\n      expect(output[2].cid.toString()).to.equal('QmZ25UfTqXGz9RsEJFg7HUAuBcmfx5dQZDXQd2QEZ8Kj74')\n      expect(output[2].type).to.equal('dir')\n\n      expect(output[3].name).to.equal('holmes.txt')\n      expect(output[3].path).to.equal('QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/holmes.txt')\n      expect(output[3].size).to.equal(581878)\n      expect(output[3].cid.toString()).to.equal('QmR4nFjTu18TyANgC65ArNWp5Yaab1gPzQ4D8zp7Kx3vhr')\n      expect(output[3].type).to.equal('file')\n\n      expect(output[4].name).to.equal('jungle.txt')\n      expect(output[4].path).to.equal('QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/jungle.txt')\n      expect(output[4].size).to.equal(2294)\n      expect(output[4].cid.toString()).to.equal('QmT6orWioMiSqXXPGsUi71CKRRUmJ8YkuueV2DPV34E9y9')\n      expect(output[4].type).to.equal('file')\n\n      expect(output[5].name).to.equal('pp.txt')\n      expect(output[5].path).to.equal('QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/pp.txt')\n      expect(output[5].size).to.equal(4540)\n      expect(output[5].cid.toString()).to.equal('QmVwdDCY4SPGVFnNCiZnX5CtzwWDn6kAM98JXzKxE3kCmn')\n      expect(output[5].type).to.equal('file')\n    })\n\n    it('should ls files added as CIDv0 with a CIDv1', async () => {\n      const dir = randomName('DIR')\n\n      const input = [\n        { path: `${dir}/${randomName('F0')}`, content: randomName('D0') },\n        { path: `${dir}/${randomName('F1')}`, content: randomName('D1') }\n      ]\n\n      const res = await all(ipfs.addAll(input, { cidVersion: 0 }))\n\n      const cidv0 = res[res.length - 1].cid\n      expect(cidv0.version).to.equal(0)\n\n      const cidv1 = cidv0.toV1()\n\n      const output = await all(ipfs.ls(cidv1))\n      expect(output.length).to.equal(input.length)\n\n      output.forEach(({ cid }) => {\n        expect(res.find(file => file.cid.toString() === cid.toString())).to.exist()\n      })\n    })\n\n    it('should ls files added as CIDv1 with a CIDv0', async () => {\n      const dir = randomName('DIR')\n\n      const input = [\n        { path: `${dir}/${randomName('F0')}`, content: randomName('D0') },\n        { path: `${dir}/${randomName('F1')}`, content: randomName('D1') }\n      ]\n\n      const res = await all(ipfs.addAll(input, { cidVersion: 1, rawLeaves: false }))\n\n      const cidv1 = res[res.length - 1].cid\n      expect(cidv1.version).to.equal(1)\n\n      const cidv0 = cidv1.toV1()\n\n      const output = await all(ipfs.ls(cidv0))\n      expect(output.length).to.equal(input.length)\n\n      output.forEach(({ cid }) => {\n        expect(res.find(file => file.cid.toString() === cid.toString())).to.exist()\n      })\n    })\n\n    it('should correctly handle a non existing hash', () => {\n      return expect(all(ipfs.ls('surelynotavalidhashheh?'))).to.eventually.be.rejected()\n    })\n\n    it('should correctly handle a non existing path', () => {\n      return expect(all(ipfs.ls('QmRNjDeKStKGTQXnJ2NFqeQ9oW/folder_that_isnt_there'))).to.eventually.be.rejected()\n    })\n\n    it('should ls files by path', async () => {\n      const dir = randomName('DIR')\n\n      const input = [\n        { path: `${dir}/${randomName('F0')}`, content: randomName('D0') },\n        { path: `${dir}/${randomName('F1')}`, content: randomName('D1') }\n      ]\n\n      const res = await all(ipfs.addAll(input))\n      const output = await all(ipfs.ls(`/ipfs/${res[res.length - 1].cid}`))\n      expect(output.length).to.equal(input.length)\n\n      output.forEach(({ cid }) => {\n        expect(res.find(file => file.cid.toString() === cid.toString())).to.exist()\n      })\n    })\n\n    it('should ls with metadata', async () => {\n      const dir = randomName('DIR')\n      const mtime = new Date()\n      const mode = '0532'\n      const expectedMode = parseInt(mode, 8)\n      const expectedMtime = {\n        secs: Math.floor(mtime.getTime() / 1000),\n        nsecs: (mtime.getTime() - (Math.floor(mtime.getTime() / 1000) * 1000)) * 1000\n      }\n\n      const input = [\n        { path: `${dir}/${randomName('F0')}`, content: randomName('D0'), mode, mtime },\n        { path: `${dir}/${randomName('F1')}`, content: randomName('D1'), mode, mtime }\n      ]\n\n      const res = await all(ipfs.addAll(input))\n      const output = await all(ipfs.ls(`/ipfs/${res[res.length - 1].cid}`))\n\n      expect(output).to.have.lengthOf(input.length)\n      expect(output[0].mtime).to.deep.equal(expectedMtime)\n      expect(output[0].mode).to.equal(expectedMode)\n      expect(output[1].mtime).to.deep.equal(expectedMtime)\n      expect(output[1].mode).to.equal(expectedMode)\n    })\n\n    it('should ls files by subdir', async () => {\n      const dir = randomName('DIR')\n      const subdir = randomName('F0')\n      const subfile = randomName('F1')\n\n      const input = { path: `${dir}/${subdir}/${subfile}`, content: randomName('D1') }\n\n      const res = await ipfs.add(input)\n      const path = `${res.cid}/${subdir}`\n      const output = await all(ipfs.ls(path))\n\n      expect(output).to.have.lengthOf(1)\n      expect(output[0]).to.have.property('path', `${path}/${subfile}`)\n    })\n\n    it('should ls single file', async () => {\n      const dir = randomName('DIR')\n      const file = randomName('F0')\n\n      const input = { path: `${dir}/${file}`, content: randomName('D1') }\n\n      const res = await ipfs.add(input)\n      const path = `${res.cid}/${file}`\n      const output = await all(ipfs.ls(path))\n\n      expect(output).to.have.lengthOf(1)\n      expect(output[0]).to.have.property('path', path)\n    })\n\n    it('should ls single file with metadata', async () => {\n      const dir = randomName('DIR')\n      const file = randomName('F0')\n\n      const input = {\n        path: `${dir}/${file}`,\n        content: randomName('D1'),\n        mode: 0o631,\n        mtime: {\n          secs: 5000,\n          nsecs: 100\n        }\n      }\n\n      const res = await ipfs.add(input)\n      const path = `${res.cid}/${file}`\n      const output = await all(ipfs.ls(res.cid))\n\n      expect(output).to.have.lengthOf(1)\n      expect(output[0]).to.have.property('path', path)\n      expect(output[0]).to.have.property('mode', input.mode)\n      expect(output[0]).to.have.deep.property('mtime', input.mtime)\n    })\n\n    it('should ls single file without containing directory', async () => {\n      const input = { content: randomName('D1') }\n\n      const res = await ipfs.add(input)\n      const output = await all(ipfs.ls(res.cid))\n\n      expect(output).to.have.lengthOf(1)\n      expect(output[0]).to.have.property('path', res.cid.toString())\n    })\n\n    it('should ls single file without containing directory with metadata', async () => {\n      const input = {\n        content: randomName('D1'),\n        mode: 0o631,\n        mtime: {\n          secs: 5000,\n          nsecs: 100\n        }\n      }\n\n      const res = await ipfs.add(input)\n      const output = await all(ipfs.ls(res.cid))\n\n      expect(output).to.have.lengthOf(1)\n      expect(output[0]).to.have.property('path', res.cid.toString())\n      expect(output[0]).to.have.property('mode', input.mode)\n      expect(output[0]).to.have.deep.property('mtime', input.mtime)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/miscellaneous/dns.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testDns (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.dns', function () {\n    this.timeout(60 * 1000)\n    this.retries(3)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should non-recursively resolve ipfs.io', async function () {\n      const domain = 'ipfs.io'\n\n      try {\n        const res = await ipfs.dns(domain, { recursive: false })\n\n        // matches pattern /ipns/<ipnsaddress>\n        expect(res).to.match(/\\/ipns\\/.+$/)\n      } catch (/** @type {any} */ err) {\n        if (err.message.includes('could not resolve name')) {\n          return this.skip()\n        }\n\n        // happens when running tests offline\n        if (err.message.includes(`ECONNREFUSED ${domain}`)) {\n          return this.skip()\n        }\n\n        throw err\n      }\n    })\n\n    it('should recursively resolve ipfs.io', async function () {\n      const domain = 'ipfs.io'\n\n      try {\n        const res = await ipfs.dns(domain, { recursive: true })\n\n        // matches pattern /ipfs/<hash>\n        expect(res).to.match(/\\/ipfs\\/.+$/)\n      } catch (/** @type {any} */ err) {\n        if (err.message.includes('could not resolve name')) {\n          return this.skip()\n        }\n\n        // happens when running tests offline\n        if (err.message.includes(`ECONNREFUSED ${domain}`)) {\n          return this.skip()\n        }\n\n        throw err\n      }\n    })\n\n    it('should resolve subdomain docs.ipfs.io', async function () {\n      const domain = 'docs.ipfs.io'\n\n      try {\n        const res = await ipfs.dns(domain)\n\n        // matches pattern /ipfs/<hash>\n        expect(res).to.match(/\\/ipfs\\/.+$/)\n      } catch (/** @type {any} */ err) {\n        if (err.message.includes('could not resolve name')) {\n          return this.skip()\n        }\n\n        // happens when running tests offline\n        if (err.message.includes(`ECONNREFUSED ${domain}`)) {\n          return this.skip()\n        }\n\n        throw err\n      }\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/miscellaneous/id.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { isMultiaddr } from '@multiformats/multiaddr'\nimport { isWebWorker } from 'ipfs-utils/src/env.js'\nimport retry from 'p-retry'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testId (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.id', function () {\n    this.timeout(60 * 1000)\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should get the node ID', async () => {\n      const res = await ipfs.id()\n      expect(res).to.have.a.property('id')\n      expect(res).to.have.a.property('publicKey')\n      expect(res).to.have.a.property('agentVersion').that.is.a('string')\n      expect(res).to.have.a.property('protocolVersion').that.is.a('string')\n      expect(res).to.have.a.property('addresses').that.is.an('array')\n\n      for (const ma of res.addresses) {\n        expect(isMultiaddr(ma)).to.be.true()\n      }\n    })\n\n    it('should have protocols property', async () => {\n      const res = await ipfs.id()\n\n      expect(res).to.have.a.property('protocols').that.is.an('array')\n\n      expect(res.protocols).to.include.members([\n        '/ipfs/bitswap/1.2.0',\n        '/ipfs/id/1.0.0',\n        '/ipfs/id/push/1.0.0',\n        '/ipfs/lan/kad/1.0.0',\n        '/ipfs/ping/1.0.0'\n      ])\n    })\n\n    it('should return swarm ports opened after startup', async function () {\n      if (isWebWorker) {\n        // TODO: webworkers are not currently dialable\n        return this.skip()\n      }\n\n      await expect(ipfs.id()).to.eventually.have.property('addresses').that.is.not.empty()\n    })\n\n    it('should get the id of another node in the swarm', async function () {\n      if (isWebWorker) {\n        // TODO: https://github.com/libp2p/js-libp2p-websockets/issues/129\n        return this.skip()\n      }\n\n      const ipfsB = (await factory.spawn()).api\n      const ipfsBId = await ipfsB.id()\n      await ipfs.swarm.connect(ipfsBId.addresses[0])\n\n      // have to wait for identify to complete before protocols etc are available for remote hosts\n      await retry(async () => {\n        const result = await ipfs.id({\n          peerId: ipfsBId.id\n        })\n\n        expect(JSON.stringify(result, null, 2)).to.deep.equal(JSON.stringify(ipfsBId, null, 2))\n      }, { retries: 5 })\n    })\n\n    it('should get our own id when passed as an option', async function () {\n      const res = await ipfs.id()\n\n      const result = await ipfs.id({\n        peerId: res.id\n      })\n\n      expect(JSON.stringify(res, null, 2)).to.deep.equal(JSON.stringify(result, null, 2))\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/miscellaneous/index.js",
    "content": "import { createSuite } from '../utils/suite.js'\nimport { testId } from './id.js'\nimport { testVersion } from './version.js'\nimport { testStop } from './stop.js'\nimport { testResolve } from './resolve.js'\nimport { testDns } from './dns.js'\n\nconst tests = {\n  id: testId,\n  version: testVersion,\n  dns: testDns,\n  stop: testStop,\n  resolve: testResolve\n}\n\nexport default createSuite(tests)\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/miscellaneous/resolve.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport * as isIpfs from 'is-ipfs'\nimport { nanoid } from 'nanoid'\nimport { base64url } from 'multiformats/bases/base64'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport all from 'it-all'\nimport { isWebWorker } from 'ipfs-utils/src/env.js'\nimport merge from 'merge-options'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testResolve (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.resolve', function () {\n    this.timeout(60 * 1000)\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n    /** @type {import('ipfs-core-types/src/root').IDResult} */\n    let ipfsId\n\n    before(async () => {\n      ipfs = (await factory.spawn({\n        type: 'proc',\n        ipfsOptions: merge({\n          config: {\n            Routing: {\n              Type: 'none'\n            }\n          }\n        })\n      })).api\n      ipfsId = await ipfs.id()\n    })\n\n    after(() => factory.clean())\n\n    it('should resolve an IPFS hash', async () => {\n      const content = uint8ArrayFromString('Hello world')\n\n      const { cid } = await ipfs.add(content)\n      const path = await ipfs.resolve(`/ipfs/${cid}`)\n      expect(path).to.equal(`/ipfs/${cid}`)\n    })\n\n    it('should resolve an IPFS hash and return a base64url encoded CID in path', async () => {\n      const { cid } = await ipfs.add(uint8ArrayFromString('base64url encoded'), {\n        cidVersion: 1\n      })\n      const path = await ipfs.resolve(`/ipfs/${cid}`, { cidBase: 'base64url' })\n      const [,, cidStr] = path.split('/')\n\n      expect(cidStr).to.equal(cid.toString(base64url))\n    })\n\n    // Test resolve turns /ipfs/QmRootHash/path/to/file into /ipfs/QmFileHash\n    it('should resolve an IPFS path link', async () => {\n      const path = 'path/to/testfile.txt'\n      const content = uint8ArrayFromString('Hello world')\n      const [{ cid: fileCid }, , , { cid: rootCid }] = await all(ipfs.addAll([{ path, content }], { wrapWithDirectory: true }))\n      const resolve = await ipfs.resolve(`/ipfs/${rootCid}/${path}`)\n\n      expect(resolve).to.equal(`/ipfs/${fileCid}`)\n    })\n\n    it('should resolve up to the last node', async () => {\n      const content = { path: { to: { file: nanoid() } } }\n      const options = { storeCodec: 'dag-cbor', hashAlg: 'sha2-256' }\n      const cid = await ipfs.dag.put(content, options)\n      const path = `/ipfs/${cid}/path/to/file`\n      const resolved = await ipfs.resolve(path)\n\n      expect(resolved).to.equal(path)\n    })\n\n    it('should resolve up to the last node across multiple nodes', async () => {\n      const options = { storeCodec: 'dag-cbor', hashAlg: 'sha2-256' }\n      const childCid = await ipfs.dag.put({ node: { with: { file: nanoid() } } }, options)\n      const parentCid = await ipfs.dag.put({ path: { to: childCid } }, options)\n      const resolved = await ipfs.resolve(`/ipfs/${parentCid}/path/to/node/with/file`)\n\n      expect(resolved).to.equal(`/ipfs/${childCid}/node/with/file`)\n    })\n\n    // Test resolve turns /ipns/domain.com into /ipfs/QmHash\n    it('should resolve an IPNS DNS link', async function () {\n      this.retries(3)\n      const domain = 'ipfs.io'\n\n      try {\n        const resolved = await ipfs.resolve(`/ipns/${domain}`)\n\n        expect(isIpfs.ipfsPath(resolved)).to.be.true()\n      } catch (/** @type {any} */ err) {\n        // happens when running tests offline\n        if (err.message.includes(`ECONNREFUSED ${domain}`)) {\n          return this.skip()\n        }\n\n        throw err\n      }\n    })\n\n    it('should resolve IPNS link recursively by default', async function () {\n      this.timeout(20 * 1000)\n      // webworkers are not dialable because webrtc is not available\n      const node = (await factory.spawn({\n        type: isWebWorker ? 'go' : undefined,\n        ipfsOptions: {\n          config: {\n            Routing: {\n              Type: 'none'\n            }\n          }\n        }\n      })).api\n      const nodeId = await node.id()\n      await ipfs.swarm.connect(nodeId.addresses[0])\n      const { path } = await ipfs.add(uint8ArrayFromString('should resolve a record recursive === true'))\n      const { id: keyId } = await ipfs.key.gen('key-name', { type: 'rsa', size: 2048 })\n\n      await ipfs.name.publish(path, { allowOffline: true })\n      await ipfs.name.publish(`/ipns/${ipfsId.id}`, { allowOffline: true, key: 'key-name', resolve: false })\n\n      return expect(ipfs.resolve(`/ipns/${keyId}`))\n        .to.eventually.equal(`/ipfs/${path}`)\n    })\n\n    it('should resolve IPNS link non-recursively if recursive==false', async function () {\n      this.timeout(20 * 1000)\n      // webworkers are not dialable because webrtc is not available\n      const node = (await factory.spawn({\n        type: isWebWorker ? 'go' : undefined,\n        ipfsOptions: {\n          config: {\n            Routing: {\n              Type: 'none'\n            }\n          }\n        }\n      })).api\n      const nodeId = await node.id()\n      await ipfs.swarm.connect(nodeId.addresses[0])\n      const { path } = await ipfs.add(uint8ArrayFromString('should resolve an IPNS key if recursive === false'))\n      const { id: keyId } = await ipfs.key.gen('new-key-name', { type: 'rsa', size: 2048 })\n\n      await ipfs.name.publish(path, { allowOffline: true })\n      await ipfs.name.publish(`/ipns/${ipfsId.id}`, { allowOffline: true, key: 'new-key-name', resolve: false })\n\n      return expect(ipfs.resolve(`/ipns/${keyId}`, { recursive: false }))\n        .to.eventually.equal(`/ipns/${ipfsId.id}`)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/miscellaneous/stop.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testStop (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.stop', function () {\n    this.timeout(60 * 1000)\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    beforeEach(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    afterEach(() => {\n      // reset the list of controlled nodes - we've already shut down the\n      // nodes started in this test but the references hang around and the\n      // next test will call `factory.clean()` which will explode when it\n      // can't connect to the nodes started by this test.\n      factory.controllers = []\n    })\n\n    it('should stop the node', async () => {\n      // Should succeed because node is started\n      await ipfs.swarm.peers()\n\n      // Stop the node and try the call again\n      await ipfs.stop()\n\n      // Trying to use an API that requires a started node should return an error\n      return expect(ipfs.swarm.peers()).to.eventually.be.rejected()\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/miscellaneous/version.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testVersion (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.version', () => {\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should get the node version', async () => {\n      const result = await ipfs.version()\n      expect(result).to.have.a.property('version')\n      expect(result).to.have.a.property('commit')\n      expect(result).to.have.a.property('repo')\n    })\n\n    it('should include the ipfs-http-client version', async () => {\n      const result = await ipfs.version()\n      expect(result).to.have.a.property('ipfs-http-client')\n    })\n\n    it('should include the interface-ipfs-core version', async () => {\n      const result = await ipfs.version()\n      expect(result).to.have.a.property('interface-ipfs-core')\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/name/index.js",
    "content": "import { createSuite } from '../utils/suite.js'\nimport { testPublish } from './publish.js'\nimport { testResolve } from './resolve.js'\n\nconst tests = {\n  publish: testPublish,\n  resolve: testResolve\n}\n\nexport default createSuite(tests)\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/name/publish.js",
    "content": "/* eslint-env mocha */\n\nimport { nanoid } from 'nanoid'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { fixture } from './utils.js'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport last from 'it-last'\nimport { peerIdFromString } from '@libp2p/peer-id'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n * @typedef {import('@libp2p/interface-peer-id').PeerId} PeerId\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testPublish (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.name.publish offline', () => {\n    const keyName = nanoid()\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n    /** @type {PeerId} */\n    let nodeId\n\n    before(async () => {\n      ipfs = (await factory.spawn({\n        ipfsOptions: {\n          config: {\n            Routing: {\n              Type: 'none'\n            }\n          }\n        }\n      })).api\n      const peerInfo = await ipfs.id()\n      nodeId = peerInfo.id\n      await ipfs.add(fixture.data, { pin: false })\n    })\n\n    after(() => factory.clean())\n\n    it('should publish an IPNS record with the default params', async function () {\n      this.timeout(50 * 1000)\n\n      const value = fixture.cid\n      const keys = await ipfs.key.list()\n      const self = keys.find(key => key.name === 'self')\n\n      if (!self) {\n        throw new Error('No self key found')\n      }\n\n      const res = await ipfs.name.publish(value, { allowOffline: true })\n      expect(res).to.exist()\n\n      expect(peerIdFromString(res.name).toString()).to.equal(peerIdFromString(self.id).toString())\n      expect(res.value).to.equal(`/ipfs/${value}`)\n    })\n\n    it('should publish correctly with the lifetime option and resolve', async () => {\n      const { path } = await ipfs.add(uint8ArrayFromString('should publish correctly with the lifetime option and resolve'))\n      await ipfs.name.publish(path, { allowOffline: true, resolve: false, lifetime: '2h' })\n      expect(await last(ipfs.name.resolve(`/ipns/${nodeId.toString()}`))).to.eq(`/ipfs/${path}`)\n    })\n\n    it('should publish correctly when the file was not added but resolve is disabled', async function () {\n      this.timeout(50 * 1000)\n\n      const value = 'QmPFVLPmp9zv5Z5KUqLhe2EivAGccQW2r7M7jhVJGLZoZU'\n      const keys = await ipfs.key.list()\n      const self = keys.find(key => key.name === 'self')\n\n      if (!self) {\n        throw new Error('No self key found')\n      }\n\n      const options = {\n        resolve: false,\n        lifetime: '1m',\n        ttl: '10s',\n        key: 'self',\n        allowOffline: true\n      }\n\n      const res = await ipfs.name.publish(value, options)\n      expect(res).to.exist()\n      expect(peerIdFromString(res.name).toString()).to.equal(peerIdFromString(self.id).toString())\n      expect(res.value).to.equal(`/ipfs/${value}`)\n    })\n\n    it('should publish with a key received as param, instead of using the key of the node', async function () {\n      this.timeout(90 * 1000)\n\n      const value = fixture.cid\n      const options = {\n        resolve: false,\n        lifetime: '24h',\n        ttl: '10s',\n        key: keyName,\n        allowOffline: true\n      }\n\n      const key = await ipfs.key.gen(keyName, { type: 'rsa', size: 2048 })\n      const res = await ipfs.name.publish(value, options)\n\n      expect(res).to.exist()\n      expect(peerIdFromString(res.name).toString()).to.equal(peerIdFromString(key.id).toString())\n      expect(res.value).to.equal(`/ipfs/${value}`)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/name/resolve.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport delay from 'delay'\nimport last from 'it-last'\nimport { CID } from 'multiformats/cid'\nimport * as Digest from 'multiformats/hashes/digest'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n * @typedef {import('@libp2p/interface-peer-id').PeerId} PeerId\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testResolve (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.name.resolve offline', function () {\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n    /** @type {PeerId} */\n    let nodeId\n\n    before(async () => {\n      ipfs = (await factory.spawn({\n        ipfsOptions: {\n          config: {\n            Routing: {\n              Type: 'none'\n            }\n          }\n        }\n      })).api\n      const peerInfo = await ipfs.id()\n      nodeId = peerInfo.id\n    })\n\n    after(() => factory.clean())\n\n    it('should resolve a record default options', async function () {\n      this.timeout(20 * 1000)\n\n      const { path } = await ipfs.add(uint8ArrayFromString('should resolve a record default options'))\n      const { id: keyId } = await ipfs.key.gen('key-name-default', { type: 'rsa', size: 2048 })\n\n      await ipfs.name.publish(path, { allowOffline: true })\n      await ipfs.name.publish(`/ipns/${nodeId.toString()}`, { allowOffline: true, key: 'key-name-default' })\n\n      expect(await last(ipfs.name.resolve(`/ipns/${keyId}`)))\n        .to.eq(`/ipfs/${path}`)\n    })\n\n    it('should resolve a record from peerid as cidv1 in base32', async function () {\n      this.timeout(20 * 1000)\n      const { cid } = await ipfs.add(uint8ArrayFromString('should resolve a record from cidv1b32'))\n      const { id: peerId } = await ipfs.id()\n      await ipfs.name.publish(cid, { allowOffline: true })\n\n      // Represent Peer ID as CIDv1 Base32\n      // https://github.com/libp2p/specs/blob/master/RFC/0001-text-peerid-cid.md\n      const keyCid = CID.createV1(0x72, Digest.decode(peerId.toBytes()))\n      const resolvedPath = await last(ipfs.name.resolve(`/ipns/${keyCid}`))\n\n      expect(resolvedPath).to.equal(`/ipfs/${cid}`)\n    })\n\n    it('should resolve a record recursive === false', async () => {\n      const { path } = await ipfs.add(uint8ArrayFromString('should resolve a record recursive === false'))\n      await ipfs.name.publish(path, { allowOffline: true })\n      expect(await last(ipfs.name.resolve(`/ipns/${nodeId.toString()}`, { recursive: false })))\n        .to.eq(`/ipfs/${path}`)\n    })\n\n    it('should resolve a record recursive === true', async function () {\n      this.timeout(20 * 1000)\n\n      const { path } = await ipfs.add(uint8ArrayFromString('should resolve a record recursive === true'))\n      const { id: keyId } = await ipfs.key.gen('key-name', { type: 'rsa', size: 2048 })\n\n      await ipfs.name.publish(path, { allowOffline: true })\n      await ipfs.name.publish(`/ipns/${nodeId.toString()}`, { allowOffline: true, key: 'key-name' })\n\n      expect(await last(ipfs.name.resolve(`/ipns/${keyId}`, { recursive: true })))\n        .to.eq(`/ipfs/${path}`)\n    })\n\n    it('should resolve a record default options with remainder', async function () {\n      this.timeout(20 * 1000)\n\n      const { path } = await ipfs.add(uint8ArrayFromString('should resolve a record default options with remainder'))\n      const { id: keyId } = await ipfs.key.gen('key-name-remainder-default', { type: 'rsa', size: 2048 })\n\n      await ipfs.name.publish(path, { allowOffline: true })\n      await ipfs.name.publish(`/ipns/${nodeId.toString()}`, { allowOffline: true, key: 'key-name-remainder-default' })\n\n      expect(await last(ipfs.name.resolve(`/ipns/${keyId}/remainder/file.txt`)))\n        .to.eq(`/ipfs/${path}/remainder/file.txt`)\n    })\n\n    it('should resolve a record recursive === false with remainder', async () => {\n      const { path } = await ipfs.add(uint8ArrayFromString('should resolve a record recursive = false with remainder'))\n      await ipfs.name.publish(path, { allowOffline: true })\n      expect(await last(ipfs.name.resolve(`/ipns/${nodeId.toString()}/remainder/file.txt`, { recursive: false })))\n        .to.eq(`/ipfs/${path}/remainder/file.txt`)\n    })\n\n    it('should resolve a record recursive === true with remainder', async function () {\n      this.timeout(20 * 1000)\n\n      const { path } = await ipfs.add(uint8ArrayFromString('should resolve a record recursive = true with remainder'))\n      const { id: keyId } = await ipfs.key.gen('key-name-remainder', { type: 'rsa', size: 2048 })\n\n      await ipfs.name.publish(path, { allowOffline: true })\n      await ipfs.name.publish(`/ipns/${nodeId.toString()}`, { allowOffline: true, key: 'key-name-remainder' })\n\n      expect(await last(ipfs.name.resolve(`/ipns/${keyId}/remainder/file.txt`, { recursive: true })))\n        .to.eq(`/ipfs/${path}/remainder/file.txt`)\n    })\n\n    it('should not get the entry if its validity time expired', async () => {\n      const publishOptions = {\n        lifetime: '100ms',\n        ttl: '10s',\n        allowOffline: true\n      }\n\n      // we add new data instead of re-using fixture to make sure lifetime handling doesn't break\n      const { path } = await ipfs.add(uint8ArrayFromString('should not get the entry if its validity time expired'))\n      await ipfs.name.publish(path, publishOptions)\n      await delay(500)\n      // go only has 1 possible error https://github.com/ipfs/go-ipfs/blob/master/namesys/interface.go#L51\n      // so here we just expect an Error and don't match the error type to expiration\n      try {\n        await last(ipfs.name.resolve(nodeId))\n      } catch (/** @type {any} */ error) {\n        expect(error).to.exist()\n      }\n    })\n  })\n\n  describe('.name.resolve dns', function () {\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n    this.retries(5)\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should resolve /ipns/ipfs.io', async function () {\n      const domain = 'ipfs.io'\n\n      try {\n        expect(await last(ipfs.name.resolve(`/ipns/${domain}`)))\n          .to.match(/\\/ipfs\\/.+$/)\n      } catch (/** @type {any} */ err) {\n        // happens when running tests offline\n        if (err.message.includes(`ECONNREFUSED ${domain}`)) {\n          return this.skip()\n        }\n\n        throw err\n      }\n    })\n\n    it('should resolve /ipns/ipfs.io recursive === false', async function () {\n      const domain = 'ipfs.io'\n\n      try {\n        expect(await last(ipfs.name.resolve(`/ipns/${domain}`, { recursive: false })))\n          .to.match(/\\/ipns\\/.+$/)\n      } catch (/** @type {any} */ err) {\n        // happens when running tests offline\n        if (err.message.includes(`ECONNREFUSED ${domain}`)) {\n          return this.skip()\n        }\n\n        throw err\n      }\n    })\n\n    it('should resolve /ipns/ipfs.io recursive === true', async function () {\n      const domain = 'ipfs.io'\n\n      try {\n        expect(await last(ipfs.name.resolve(`/ipns/${domain}`, { recursive: true })))\n          .to.match(/\\/ipfs\\/.+$/)\n      } catch (/** @type {any} */ err) {\n        // happens when running tests offline\n        if (err.message.includes(`ECONNREFUSED ${domain}`)) {\n          return this.skip()\n        }\n\n        throw err\n      }\n    })\n\n    it('should resolve /ipns/ipfs.io with remainder', async function () {\n      const domain = 'ipfs.io'\n\n      try {\n        expect(await last(ipfs.name.resolve(`/ipns/${domain}/images/ipfs-logo.svg`)))\n          .to.match(/\\/ipfs\\/.+\\/images\\/ipfs-logo.svg$/)\n      } catch (/** @type {any} */ err) {\n        // happens when running tests offline\n        if (err.message.includes(`ECONNREFUSED ${domain}`)) {\n          return this.skip()\n        }\n\n        throw err\n      }\n    })\n\n    it('should resolve /ipns/ipfs.io with remainder recursive === false', async function () {\n      const domain = 'ipfs.io'\n\n      try {\n        expect(await last(ipfs.name.resolve(`/ipns/${domain}/images/ipfs-logo.svg`, { recursive: false })))\n          .to.match(/\\/ipns\\/.+\\/images\\/ipfs-logo.svg$/)\n      } catch (/** @type {any} */ err) {\n        // happens when running tests offline\n        if (err.message.includes(`ECONNREFUSED ${domain}`)) {\n          return this.skip()\n        }\n\n        throw err\n      }\n    })\n\n    it('should resolve /ipns/ipfs.io with remainder recursive === true', async function () {\n      const domain = 'ipfs.io'\n\n      try {\n        expect(await last(ipfs.name.resolve(`/ipns/${domain}/images/ipfs-logo.svg`, { recursive: true })))\n          .to.match(/\\/ipfs\\/.+\\/images\\/ipfs-logo.svg$/)\n      } catch (/** @type {any} */ err) {\n        // happens when running tests offline\n        if (err.message.includes(`ECONNREFUSED ${domain}`)) {\n          return this.skip()\n        }\n\n        throw err\n      }\n    })\n\n    it('should fail to resolve /ipns/ipfs.a', async () => {\n      try {\n        await last(ipfs.name.resolve('ipfs.a'))\n      } catch (/** @type {any} */ error) {\n        expect(error).to.exist()\n      }\n    })\n\n    it('should resolve ipns path with hamt-shard recursive === true', async function () {\n      const domain = 'tr.wikipedia-on-ipfs.org'\n\n      try {\n        expect(await last(ipfs.name.resolve(`/ipns/${domain}/wiki/Anasayfa.html`, { recursive: true })))\n          .to.match(/\\/ipfs\\/.+$/)\n      } catch (/** @type {any} */ err) {\n        // happens when running tests offline\n        if (err.message.includes(`ECONNREFUSED ${domain}`)) {\n          return this.skip()\n        }\n\n        throw err\n      }\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/name/utils.js",
    "content": "import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\n\nexport const fixture = Object.freeze({\n  cid: 'Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP',\n  data: uint8ArrayFromString('Plz add me!\\n')\n})\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/name-pubsub/cancel.js",
    "content": "/* eslint-env mocha */\n\nimport all from 'it-all'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { createEd25519PeerId } from '@libp2p/peer-id-factory'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testCancel (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.name.pubsub.cancel', () => {\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n    /** @type {string} */\n    let nodeId\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n      const peerInfo = await ipfs.id()\n      nodeId = peerInfo.id.toString()\n    })\n\n    after(() => factory.clean())\n\n    it('should return false when the name that is intended to cancel is not subscribed', async function () {\n      this.timeout(60 * 1000)\n\n      const res = await ipfs.name.pubsub.cancel(nodeId)\n      expect(res).to.exist()\n      expect(res).to.have.property('canceled')\n      expect(res.canceled).to.be.false()\n    })\n\n    it('should cancel a subscription correctly returning true', async function () {\n      this.timeout(300 * 1000)\n\n      const peerId = await createEd25519PeerId()\n      const id = peerId.toString()\n      const ipnsPath = `/ipns/${id}`\n\n      const subs = await ipfs.name.pubsub.subs()\n      expect(subs).to.be.an('array').that.does.not.include(ipnsPath)\n\n      await expect(all(ipfs.name.resolve(id))).to.eventually.be.rejected()\n\n      const subs1 = await ipfs.name.pubsub.subs()\n      const cancel = await ipfs.name.pubsub.cancel(ipnsPath)\n      const subs2 = await ipfs.name.pubsub.subs()\n\n      expect(subs1).to.be.an('array').that.includes(ipnsPath)\n      expect(cancel).to.have.property('canceled')\n      expect(cancel.canceled).to.be.true()\n      expect(subs2).to.be.an('array').that.does.not.include(ipnsPath)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/name-pubsub/index.js",
    "content": "import { createSuite } from '../utils/suite.js'\nimport { testCancel } from './cancel.js'\nimport { testState } from './state.js'\nimport { testSubs } from './subs.js'\nimport { testPubsub } from './pubsub.js'\n\nconst tests = {\n  cancel: testCancel,\n  state: testState,\n  subs: testSubs,\n  pubsub: testPubsub\n}\n\nexport default createSuite(tests)\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/name-pubsub/pubsub.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { peerIdFromString, peerIdFromKeys } from '@libp2p/peer-id'\nimport { isNode } from 'ipfs-utils/src/env.js'\nimport * as ipns from 'ipns'\nimport delay from 'delay'\nimport last from 'it-last'\nimport waitFor from '../utils/wait-for.js'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\nimport { ipnsValidator } from 'ipns/validator'\n\nconst namespace = '/record/'\nconst ipfsRef = '/ipfs/QmPFVLPmp9zv5Z5KUqLhe2EivAGccQW2r7M7jhVJGLZoZU'\n\nconst daemonsOptions = {\n  ipfsOptions: {\n    EXPERIMENTAL: {\n      ipnsPubsub: true\n    }\n  }\n}\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n * @typedef {import('@libp2p/interface-pubsub').Message} Message\n * @typedef {import('@libp2p/interfaces/events').EventHandler<Message>} EventHandler\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testPubsub (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.name.pubsub', () => {\n    // TODO make this work in the browser and between daemon and in-proc in nodes\n    if (!isNode) return\n\n    let nodes\n    /** @type {import('ipfs-core-types').IPFS} */\n    let nodeA\n    /** @type {import('ipfs-core-types').IPFS} */\n    let nodeB\n    /** @type {import('ipfs-core-types/src/root').IDResult} */\n    let idA\n    /** @type {import('ipfs-core-types/src/root').IDResult} */\n    let idB\n\n    before(async function () {\n      this.timeout(120 * 1000)\n\n      nodes = await Promise.all([\n        factory.spawn({ ...daemonsOptions }),\n        factory.spawn({ ...daemonsOptions })\n      ])\n\n      nodeA = nodes[0].api\n      nodeB = nodes[1].api\n\n      const ids = await Promise.all([\n        nodeA.id(),\n        nodeB.id()\n      ])\n\n      idA = ids[0]\n      idB = ids[1]\n\n      await nodeA.swarm.connect(idB.addresses[0])\n\n      await waitFor(async () => {\n        const res = await nodeB.swarm.peers()\n\n        return res.map(p => p.peer.toString()).includes(idA.id.toString())\n      }, { name: 'node A dialed node B' })\n    })\n\n    after(() => factory.clean())\n\n    it('should publish and then resolve correctly', async function () {\n      this.timeout(80 * 1000)\n\n      const routingKey = ipns.peerIdToRoutingKey(idA.id)\n      const topic = `${namespace}${uint8ArrayToString(routingKey, 'base64url')}`\n\n      await nodeB.pubsub.subscribe(topic, () => {})\n\n      // wait for nodeA to see nodeB's subscription\n      await waitFor(async () => {\n        const peers = await nodeA.pubsub.peers(topic)\n\n        return peers.map(p => p.toString()).includes(idB.id.toString())\n      })\n\n      await nodeA.name.publish(ipfsRef, { resolve: false })\n      await delay(1000) // guarantee record is written\n\n      const res = await last(nodeB.name.resolve(idA.id))\n\n      expect(res).to.equal(ipfsRef)\n    })\n\n    it('should self resolve, publish and then resolve correctly', async function () {\n      this.timeout(6000)\n      const emptyDirCid = '/ipfs/QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn'\n      const { path } = await nodeA.add(uint8ArrayFromString('pubsub records'))\n\n      const resolvesEmpty = await last(nodeB.name.resolve(idB.id))\n      expect(resolvesEmpty).to.be.eq(emptyDirCid)\n\n      const publish = await nodeB.name.publish(path)\n      expect(publish).to.be.eql({\n        name: idB.id.toString(),\n        value: `/ipfs/${path}`\n      })\n\n      const resolveB = await last(nodeB.name.resolve(idB.id))\n      expect(resolveB).to.be.eq(`/ipfs/${path}`)\n      await delay(1000)\n      const resolveA = await last(nodeA.name.resolve(idB.id))\n      expect(resolveA).to.be.eq(`/ipfs/${path}`)\n    })\n\n    it('should handle event on publish correctly', async function () {\n      this.timeout(80 * 1000)\n\n      const testAccountName = 'test-account'\n\n      /**\n       * @type {import('@libp2p/interface-pubsub').Message}\n       */\n      let publishedMessage\n\n      /**\n       * @type {EventHandler}\n       */\n      const checkMessage = (msg) => {\n        publishedMessage = msg\n      }\n\n      const alreadySubscribed = () => {\n        return Boolean(publishedMessage)\n      }\n\n      // Create account for publish\n      const testAccount = await nodeA.key.gen(testAccountName, {\n        type: 'rsa',\n        size: 2048,\n        'ipns-base': 'b58mh'\n      })\n\n      const routingKey = ipns.peerIdToRoutingKey(peerIdFromString(testAccount.id))\n      const topic = `${namespace}${uint8ArrayToString(routingKey, 'base64url')}`\n\n      await nodeB.pubsub.subscribe(topic, checkMessage)\n\n      // wait for nodeA to see nodeB's subscription\n      await waitFor(async () => {\n        const peers = await nodeA.pubsub.peers(topic)\n\n        return peers.map(p => p.toString()).includes(idB.id.toString())\n      })\n\n      await nodeA.name.publish(ipfsRef, { resolve: false, key: testAccountName })\n      await waitFor(alreadySubscribed)\n\n      // @ts-expect-error publishedMessage is set in handler\n      if (!publishedMessage) {\n        throw new Error('Pubsub handler not invoked')\n      }\n\n      const publishedMessageData = ipns.unmarshal(publishedMessage.data)\n\n      if (publishedMessage.type !== 'signed') {\n        throw new Error('Message was not signed')\n      }\n\n      if (publishedMessageData.pubKey == null) {\n        throw new Error('Public key was missing from published message data')\n      }\n\n      const messageKey = publishedMessage.from\n      const pubKeyPeerId = await peerIdFromKeys(publishedMessageData.pubKey)\n\n      expect(pubKeyPeerId.toString()).not.to.equal(messageKey.toString())\n      expect(pubKeyPeerId.toString()).to.equal(testAccount.id)\n      expect(publishedMessage.from.toString()).to.equal(idA.id.toString())\n      expect(messageKey.toString()).to.equal(idA.id.toString())\n      expect(uint8ArrayToString(publishedMessageData.value)).to.equal(ipfsRef)\n\n      // Verify the signature\n      await ipnsValidator(ipns.peerIdToRoutingKey(pubKeyPeerId), publishedMessage.data)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/name-pubsub/state.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testState (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.name.pubsub.state', () => {\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should get the current state of pubsub', async function () {\n      this.timeout(50 * 1000)\n\n      const res = await ipfs.name.pubsub.state()\n      expect(res).to.exist()\n      expect(res).to.have.property('enabled')\n      expect(res.enabled).to.be.eql(true)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/name-pubsub/subs.js",
    "content": "/* eslint-env mocha */\n\nimport all from 'it-all'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testSubs (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.name.pubsub.subs', () => {\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should get an empty array as a result of subscriptions before any resolve', async function () {\n      this.timeout(60 * 1000)\n\n      const res = await ipfs.name.pubsub.subs()\n      expect(res).to.exist()\n      expect(res).to.eql([])\n    })\n\n    it('should get the list of subscriptions updated after a resolve', async function () {\n      this.timeout(300 * 1000)\n      const id = 'QmNP1ASen5ZREtiJTtVD3jhMKhoPb1zppET1tgpjHx2NGA'\n\n      const subs = await ipfs.name.pubsub.subs()\n      expect(subs).to.eql([]) // initally empty\n\n      await expect(all(ipfs.name.resolve(id))).to.eventually.be.rejected()\n\n      const res = await ipfs.name.pubsub.subs()\n      expect(res).to.be.an('array').that.does.include(`/ipns/${id}`)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/object/data.js",
    "content": "/* eslint-env mocha */\n\nimport { nanoid } from 'nanoid'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testData (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.object.data', function () {\n    this.timeout(80 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should get data by CID', async () => {\n      const testObj = {\n        Data: uint8ArrayFromString(nanoid()),\n        Links: []\n      }\n\n      const nodeCid = await ipfs.object.put(testObj)\n\n      const data = await ipfs.object.data(nodeCid)\n      expect(testObj.Data).to.equalBytes(data)\n    })\n\n    it('returns error for request without argument', () => {\n      // @ts-expect-error invalid arg\n      return expect(ipfs.object.data(null)).to.eventually.be.rejected.and.be.an.instanceOf(Error)\n    })\n\n    it('returns error for request with invalid argument', () => {\n      // @ts-expect-error invalid arg\n      return expect(ipfs.object.data('invalid', { enc: 'base58' })).to.eventually.be.rejected.and.be.an.instanceOf(Error)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/object/get.js",
    "content": "/* eslint-env mocha */\n\nimport * as dagPB from '@ipld/dag-pb'\nimport { nanoid } from 'nanoid'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { UnixFS } from 'ipfs-unixfs'\nimport { randomBytes } from 'iso-random-stream'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { CID } from 'multiformats/cid'\nimport { sha256 } from 'multiformats/hashes/sha2'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testGet (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.object.get', function () {\n    this.timeout(80 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should get object by multihash', async () => {\n      const obj = {\n        Data: uint8ArrayFromString(nanoid()),\n        Links: []\n      }\n\n      const node1Cid = await ipfs.object.put(obj)\n      const node1 = await ipfs.object.get(node1Cid)\n      let node2 = await ipfs.object.get(node1Cid)\n\n      // because js-ipfs-api can't infer if the\n      // returned Data is Uint8Array or String\n      if (typeof node2.Data === 'string') {\n        node2 = {\n          Data: uint8ArrayFromString(node2.Data),\n          Links: node2.Links\n        }\n      }\n\n      expect(node1.Data).to.eql(node2.Data)\n      expect(node1.Links).to.eql(node2.Links)\n    })\n\n    it('should get object with links by multihash string', async () => {\n      const node1a = {\n        Data: uint8ArrayFromString('Some data 1'),\n        Links: []\n      }\n      const node2 = {\n        Data: uint8ArrayFromString('Some data 2'),\n        Links: []\n      }\n      const node2Buf = dagPB.encode(node2)\n      const link = {\n        Name: 'some-link',\n        Tsize: node2Buf.length,\n        Hash: CID.createV0(await sha256.digest(node2Buf))\n      }\n      const node1b = {\n        Data: node1a.Data,\n        Links: [link]\n      }\n\n      const node1bCid = await ipfs.object.put(node1b)\n      let node1c = await ipfs.object.get(node1bCid)\n\n      // because js-ipfs-api can't infer if the\n      // returned Data is Uint8Array or String\n      if (typeof node1c.Data === 'string') {\n        node1c = {\n          Data: uint8ArrayFromString(node1c.Data),\n          Links: node1c.Links\n        }\n      }\n\n      expect(node1a.Data).to.eql(node1c.Data)\n    })\n\n    it('should get object by base58 encoded multihash', async () => {\n      const obj = {\n        Data: uint8ArrayFromString(nanoid()),\n        Links: []\n      }\n\n      const node1aCid = await ipfs.object.put(obj)\n      const node1a = await ipfs.object.get(node1aCid)\n      let node1b = await ipfs.object.get(node1aCid, { enc: 'base58' })\n\n      // because js-ipfs-api can't infer if the\n      // returned Data is Uint8Array or String\n      if (typeof node1b.Data === 'string') {\n        node1b = {\n          Data: uint8ArrayFromString(node1b.Data),\n          Links: node1b.Links\n        }\n      }\n\n      expect(node1a.Data).to.eql(node1b.Data)\n      expect(node1a.Links).to.eql(node1b.Links)\n    })\n\n    it('should supply unaltered data', async () => {\n      // has to be big enough to span several DAGNodes\n      const data = randomBytes(1024 * 3000)\n\n      const result = await ipfs.add({\n        path: '',\n        content: data\n      })\n\n      const node = await ipfs.object.get(result.cid)\n\n      if (!node.Data) {\n        throw new Error('Node did not have data')\n      }\n\n      const meta = UnixFS.unmarshal(node.Data)\n\n      expect(meta.fileSize()).to.equal(data.length)\n    })\n\n    it('should error for request without argument', () => {\n      // @ts-expect-error invalid arg\n      return expect(ipfs.object.get(null)).to.eventually.be.rejected.and.be.an.instanceOf(Error)\n    })\n\n    it('returns error for request with invalid argument', () => {\n      // @ts-expect-error invalid arg\n      return expect(ipfs.object.get('invalid', { enc: 'base58' })).to.eventually.be.rejected.and.be.an.instanceOf(Error)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/object/index.js",
    "content": "import { createSuite } from '../utils/suite.js'\nimport { testNew } from './new.js'\nimport { testPut } from './put.js'\nimport { testGet } from './get.js'\nimport { testData } from './data.js'\nimport { testLinks } from './links.js'\nimport { testStat } from './stat.js'\nimport testPatch from './patch/index.js'\n\nconst tests = {\n  new: testNew,\n  put: testPut,\n  get: testGet,\n  data: testData,\n  links: testLinks,\n  stat: testStat,\n  patch: testPatch\n}\n\nexport default createSuite(tests)\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/object/links.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport * as dagPB from '@ipld/dag-pb'\nimport { nanoid } from 'nanoid'\nimport { CID } from 'multiformats/cid'\nimport { sha256 } from 'multiformats/hashes/sha2'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testLinks (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.object.links', function () {\n    this.timeout(80 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should get empty links by multihash', async () => {\n      const testObj = {\n        Data: uint8ArrayFromString(nanoid()),\n        Links: []\n      }\n\n      const cid = await ipfs.object.put(testObj)\n      const node = await ipfs.object.get(cid)\n      const links = await ipfs.object.links(cid)\n\n      expect(node.Links).to.eql(links)\n    })\n\n    it('should get links by multihash', async () => {\n      const node1a = {\n        Data: uint8ArrayFromString('Some data 1'),\n        Links: []\n      }\n      const node2 = {\n        Data: uint8ArrayFromString('Some data 2'),\n        Links: []\n      }\n      const node2Buf = dagPB.encode(node2)\n      const link = {\n        Name: 'some-link',\n        Tsize: node2Buf.length,\n        Hash: CID.createV0(await sha256.digest(node2Buf))\n      }\n      const node1b = {\n        Data: node1a.Data,\n        Links: [link]\n      }\n      const node1bCid = await ipfs.object.put(node1b)\n\n      const links = await ipfs.object.links(node1bCid)\n\n      expect(links).to.have.lengthOf(1)\n      expect(node1b.Links).to.deep.equal(links)\n    })\n\n    it('should get links from CBOR object', async () => {\n      const hashes = []\n\n      const res1 = await ipfs.add(uint8ArrayFromString('test data'))\n      hashes.push(res1.cid)\n\n      const res2 = await ipfs.add(uint8ArrayFromString('more test data'))\n      hashes.push(res2.cid)\n\n      const obj = {\n        some: 'data',\n        mylink: hashes[0],\n        myobj: {\n          anotherLink: hashes[1]\n        }\n      }\n      const cid = await ipfs.dag.put(obj)\n\n      const links = await ipfs.object.links(cid)\n      expect(links.length).to.eql(2)\n\n      // TODO: js-ipfs succeeds but go returns empty strings for link name\n      // const names = [links[0].name, links[1].name]\n      // expect(names).includes('mylink')\n      // expect(names).includes('myobj/anotherLink')\n\n      const cids = [links[0].Hash.toString(), links[1].Hash.toString()]\n      expect(cids).includes(hashes[0].toString())\n      expect(cids).includes(hashes[1].toString())\n    })\n\n    it('returns error for request without argument', () => {\n      // @ts-expect-error invalid arg\n      return expect(ipfs.object.links(null)).to.eventually.be.rejected.and.be.an.instanceOf(Error)\n    })\n\n    it('returns error for request with invalid argument', () => {\n      // @ts-expect-error invalid arg\n      return expect(ipfs.object.links('invalid', { enc: 'base58' })).to.eventually.be.rejected.and.be.an.instanceOf(Error)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/object/new.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testNew (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.object.new', function () {\n    this.timeout(80 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should create a new object with no template', async () => {\n      const cid = await ipfs.object.new()\n      expect(cid.toString()).to.equal('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n')\n    })\n\n    it('should create a new object with unixfs-dir template', async () => {\n      const cid = await ipfs.object.new({ template: 'unixfs-dir' })\n      expect(cid.toString()).to.equal('QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn')\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/object/patch/add-link.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport * as dagPB from '@ipld/dag-pb'\nimport { CID } from 'multiformats/cid'\nimport { sha256 } from 'multiformats/hashes/sha2'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../../utils/mocha.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testAddLink (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.object.patch.addLink', function () {\n    this.timeout(80 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should add a link to an existing node', async () => {\n      const obj = {\n        Data: uint8ArrayFromString('patch test object'),\n        Links: []\n      }\n      // link to add\n      const node2 = {\n        Data: uint8ArrayFromString('some other node'),\n        Links: []\n      }\n      // note: we need to put the linked obj, otherwise IPFS won't\n      // timeout. Reason: it needs the node to get its size\n      await ipfs.object.put(node2)\n      const node2Buf = dagPB.encode(node2)\n      const link = {\n        Name: 'link-to-node',\n        Tsize: node2Buf.length,\n        Hash: CID.createV0(await sha256.digest(node2Buf))\n      }\n\n      // manual create dag step by step\n      const node1a = {\n        Data: obj.Data,\n        Links: obj.Links\n      }\n      const node1b = {\n        Data: node1a.Data,\n        Links: [link]\n      }\n      const node1bCid = await ipfs.object.put(node1b)\n\n      // add link with patch.addLink\n      const testNodeCid = await ipfs.object.put(obj)\n      const cid = await ipfs.object.patch.addLink(testNodeCid, link)\n\n      // assert both are equal\n      expect(node1bCid).to.eql(cid)\n\n      /* TODO: revisit this assertions.\n      // note: make sure we can link js plain objects\n      const content = uint8ArrayFromString(JSON.stringify({\n        title: 'serialized object'\n      }, null, 0))\n      const result = await ipfs.add(content)\n      expect(result).to.exist()\n      expect(result).to.have.lengthOf(1)\n      const object = result.pop()\n      const node3 = {\n        name: object.hash,\n        multihash: object.hash,\n        size: object.size\n      }\n      const node = await ipfs.object.patch.addLink(testNodeWithLinkMultihash, node3)\n      expect(node).to.exist()\n      testNodeWithLinkMultihash = node.multihash\n      testLinkPlainObject = node3\n      */\n    })\n\n    it('returns error for request without arguments', () => {\n      // @ts-expect-error invalid arg\n      return expect(ipfs.object.patch.addLink(null, null, null)).to.eventually.be.rejected.and.be.an.instanceOf(Error)\n    })\n\n    it('returns error for request with only one invalid argument', () => {\n      // @ts-expect-error invalid arg\n      return expect(ipfs.object.patch.addLink('invalid', null, null)).to.eventually.be.rejected.and.be.an.instanceOf(Error)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/object/patch/append-data.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../../utils/mocha.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testAppendData (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.object.patch.appendData', function () {\n    this.timeout(80 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should append data to an existing node', async () => {\n      const obj = {\n        Data: uint8ArrayFromString('patch test object'),\n        Links: []\n      }\n\n      const nodeCid = await ipfs.object.put(obj)\n      const patchedNodeCid = await ipfs.object.patch.appendData(nodeCid, uint8ArrayFromString('append'))\n      expect(patchedNodeCid).to.not.deep.equal(nodeCid)\n    })\n\n    it('returns error for request without key & data', () => {\n      // @ts-expect-error invalid arg\n      return expect(ipfs.object.patch.appendData(null, null)).to.eventually.be.rejected.and.be.an.instanceOf(Error)\n    })\n\n    it('returns error for request without data', () => {\n      const filePath = 'test/fixtures/test-data/badnode.json'\n\n      // @ts-expect-error invalid arg\n      return expect(ipfs.object.patch.appendData(null, filePath)).to.eventually.be.rejected.and.be.an.instanceOf(Error)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/object/patch/index.js",
    "content": "import { createSuite } from '../../utils/suite.js'\nimport { testAddLink } from './add-link.js'\nimport { testRmLink } from './rm-link.js'\nimport { testAppendData } from './append-data.js'\nimport { testSetData } from './set-data.js'\n\nconst tests = {\n  addLink: testAddLink,\n  rmLink: testRmLink,\n  appendData: testAppendData,\n  setData: testSetData\n}\n\nexport default createSuite(tests, 'patch')\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/object/patch/rm-link.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport * as dagPB from '@ipld/dag-pb'\nimport { CID } from 'multiformats/cid'\nimport { sha256 } from 'multiformats/hashes/sha2'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../../utils/mocha.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testRmLink (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.object.patch.rmLink', function () {\n    this.timeout(80 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should remove a link from an existing node', async () => {\n      const obj1 = {\n        Data: uint8ArrayFromString('patch test object 1'),\n        Links: []\n      }\n\n      const obj2 = {\n        Data: uint8ArrayFromString('patch test object 2'),\n        Links: []\n      }\n\n      const nodeCid = await ipfs.object.put(obj1)\n      const childCid = await ipfs.object.put(obj2)\n      const child = await ipfs.object.get(childCid)\n      const childBuf = dagPB.encode(child)\n      const childAsDAGLink = {\n        Name: 'my-link',\n        Tsize: childBuf.length,\n        Hash: CID.createV0(await sha256.digest(childBuf))\n      }\n      const parentCid = await ipfs.object.patch.addLink(nodeCid, childAsDAGLink)\n      const withoutChildCid = await ipfs.object.patch.rmLink(parentCid, childAsDAGLink)\n\n      expect(withoutChildCid).to.not.deep.equal(parentCid)\n      expect(withoutChildCid).to.deep.equal(nodeCid)\n\n      /* TODO: revisit this assertions.\n      const node = await ipfs.object.patch.rmLink(testNodeWithLinkMultihash, testLinkPlainObject)\n      expect(node.multihash).to.not.deep.equal(testNodeWithLinkMultihash)\n      */\n    })\n\n    it('returns error for request without arguments', () => {\n      // @ts-expect-error invalid arg\n      return expect(ipfs.object.patch.rmLink(null, null)).to.eventually.be.rejected\n        .and.be.an.instanceOf(Error)\n    })\n\n    it('returns error for request only one invalid argument', () => {\n      // @ts-expect-error invalid arg\n      return expect(ipfs.object.patch.rmLink('invalid', null)).to.eventually.be.rejected\n        .and.be.an.instanceOf(Error)\n    })\n\n    it('returns error for request with invalid first argument', () => {\n      const root = ''\n      const link = 'foo'\n\n      // @ts-expect-error invalid arg\n      return expect(ipfs.object.patch.rmLink(root, link)).to.eventually.be.rejected\n        .and.be.an.instanceOf(Error)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/object/patch/set-data.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../../utils/mocha.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testSetData (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.object.patch.setData', function () {\n    this.timeout(80 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should set data for an existing node', async () => {\n      const obj = {\n        Data: uint8ArrayFromString('patch test object'),\n        Links: []\n      }\n      const patchData = uint8ArrayFromString('set')\n\n      const nodeCid = await ipfs.object.put(obj)\n      const patchedNodeCid = await ipfs.object.patch.setData(nodeCid, patchData)\n      const patchedNode = await ipfs.object.get(patchedNodeCid)\n\n      expect(nodeCid).to.not.deep.equal(patchedNodeCid)\n      expect(patchedNode.Data).to.eql(patchData)\n    })\n\n    it('returns error for request without key & data', () => {\n      // @ts-expect-error invalid arg\n      return expect(ipfs.object.patch.setData(null, null)).to.eventually.be.rejected.and.be.an.instanceOf(Error)\n    })\n\n    it('returns error for request without data', () => {\n      const filePath = 'test/fixtures/test-data/badnode.json'\n\n      // @ts-expect-error invalid arg\n      return expect(ipfs.object.patch.setData(null, filePath)).to.eventually.be.rejected.and.be.an.instanceOf(Error)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/object/put.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport * as dagPB from '@ipld/dag-pb'\nimport { nanoid } from 'nanoid'\nimport { CID } from 'multiformats/cid'\nimport { sha256 } from 'multiformats/hashes/sha2'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport first from 'it-first'\nimport drain from 'it-drain'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testPut (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.object.put', function () {\n    this.timeout(80 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should put an object', async () => {\n      const obj = {\n        Data: uint8ArrayFromString(nanoid()),\n        Links: []\n      }\n\n      const cid = await ipfs.object.put(obj)\n      const node = await ipfs.object.get(cid)\n\n      expect(node).to.deep.equal(obj)\n    })\n\n    it('should pin an object when putting', async () => {\n      const obj = {\n        Data: uint8ArrayFromString(nanoid()),\n        Links: []\n      }\n\n      const cid = await ipfs.object.put(obj, {\n        pin: true\n      })\n      const pin = await first(ipfs.pin.ls({\n        paths: cid\n      }))\n\n      expect(pin).to.have.deep.property('cid', cid)\n      expect(pin).to.have.property('type', 'recursive')\n    })\n\n    it('should not pin an object by default', async () => {\n      const obj = {\n        Data: uint8ArrayFromString(nanoid()),\n        Links: []\n      }\n\n      const cid = await ipfs.object.put(obj)\n\n      return expect(drain(ipfs.pin.ls({\n        paths: cid\n      }))).to.eventually.be.rejectedWith(/not pinned/)\n    })\n\n    it('should put a Protobuf DAGNode', async () => {\n      const dNode = {\n        Data: uint8ArrayFromString(nanoid()),\n        Links: []\n      }\n\n      const cid = await ipfs.object.put(dNode)\n      const node = await ipfs.object.get(cid)\n      expect(dNode).to.deep.equal(node)\n    })\n\n    it('should fail if a string is passed', () => {\n      // @ts-expect-error invalid arg\n      return expect(ipfs.object.put(nanoid())).to.eventually.be.rejected()\n    })\n\n    it('should put a Protobuf DAGNode with a link', async () => {\n      const node1a = {\n        Data: uint8ArrayFromString(nanoid()),\n        Links: []\n      }\n      const node2 = {\n        Data: uint8ArrayFromString(nanoid()),\n        Links: []\n      }\n      const node2Buf = dagPB.encode(node2)\n      const link = {\n        Name: 'some-link',\n        Tsize: node2Buf.length,\n        Hash: CID.createV0(await sha256.digest(node2Buf))\n      }\n      const node1b = {\n        Data: node1a.Data,\n        Links: [link]\n      }\n\n      const cid = await ipfs.object.put(node1b)\n      const node = await ipfs.object.get(cid)\n      expect(node1b.Data).to.deep.equal(node.Data)\n      expect(node1b.Links).to.deep.equal(node.Links)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/object/stat.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport * as dagPB from '@ipld/dag-pb'\nimport { nanoid } from 'nanoid'\nimport { CID } from 'multiformats/cid'\nimport { sha256 } from 'multiformats/hashes/sha2'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testStat (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.object.stat', function () {\n    this.timeout(80 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should get stats by multihash', async () => {\n      const testObj = {\n        Data: uint8ArrayFromString('get test object'),\n        Links: []\n      }\n\n      const cid = await ipfs.object.put(testObj)\n      const stats = await ipfs.object.stat(cid)\n      const expected = {\n        Hash: CID.parse('QmNggDXca24S6cMPEYHZjeuc4QRmofkRrAEqVL3Ms2sdJZ').toV1(),\n        NumLinks: 0,\n        BlockSize: 17,\n        LinksSize: 2,\n        DataSize: 15,\n        CumulativeSize: 17\n      }\n\n      expect(stats).to.deep.equal(expected)\n    })\n\n    it('should get stats for object with links by multihash', async () => {\n      const node1a = {\n        Data: uint8ArrayFromString(nanoid()),\n        Links: []\n      }\n      const node2 = {\n        Data: uint8ArrayFromString(nanoid()),\n        Links: []\n      }\n      const node2Buf = dagPB.encode(node2)\n      const link = {\n        Name: 'some-link',\n        Tsize: node2Buf.length,\n        Hash: CID.createV0(await sha256.digest(node2Buf))\n      }\n      const node1b = {\n        Data: node1a.Data,\n        Links: [link]\n      }\n      const node1bCid = await ipfs.object.put(node1b)\n\n      const stats = await ipfs.object.stat(node1bCid)\n      const expected = {\n        Hash: node1bCid,\n        NumLinks: 1,\n        BlockSize: 74,\n        LinksSize: 53,\n        DataSize: 21,\n        CumulativeSize: 97\n      }\n      expect(stats).to.deep.equal(expected)\n    })\n\n    it('returns error for request without argument', () => {\n      // @ts-expect-error invalid arg\n      return expect(ipfs.object.stat(null)).to.eventually.be.rejected.and.be.an.instanceOf(Error)\n    })\n\n    it('returns error for request with invalid argument', () => {\n      // @ts-expect-error invalid arg\n      return expect(ipfs.object.stat('invalid', { enc: 'base58' })).to.eventually.be.rejected.and.be.an.instanceOf(Error)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/pin/add-all.js",
    "content": "/* eslint-env mocha */\n\nimport { fixtures, clearPins } from './utils.js'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport all from 'it-all'\nimport drain from 'it-drain'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testAddAll (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.pin.addAll', function () {\n    this.timeout(50 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n\n      await drain(\n        ipfs.addAll(\n          fixtures.files.map(file => ({ content: file.data })), {\n            pin: false\n          }\n        )\n      )\n\n      await drain(\n        ipfs.addAll(fixtures.directory.files.map(\n          file => ({\n            path: file.path,\n            content: file.data\n          })\n        ), {\n          pin: false\n        })\n      )\n    })\n\n    after(() => factory.clean())\n\n    beforeEach(() => {\n      return clearPins(ipfs)\n    })\n\n    /**\n     *\n     * @param {Iterable<import('ipfs-core-types/src/pin').AddInput> | AsyncIterable<import('ipfs-core-types/src/pin').AddInput>} source\n     */\n    async function testAddPinInput (source) {\n      const pinset = await all(ipfs.pin.addAll(source))\n\n      expect(pinset).to.have.deep.members([\n        fixtures.files[0].cid,\n        fixtures.files[1].cid\n      ])\n    }\n\n    it('should add an array of CIDs', () => {\n      return testAddPinInput([\n        fixtures.files[0].cid,\n        fixtures.files[1].cid\n      ])\n    })\n\n    it('should add a generator of CIDs', () => {\n      return testAddPinInput(function * () {\n        yield fixtures.files[0].cid\n        yield fixtures.files[1].cid\n      }())\n    })\n\n    it('should add an async generator of CIDs', () => {\n      return testAddPinInput(async function * () { // eslint-disable-line require-await\n        yield fixtures.files[0].cid\n        yield fixtures.files[1].cid\n      }())\n    })\n\n    it('should add an array of pins with options', () => {\n      return testAddPinInput([\n        {\n          cid: fixtures.files[0].cid,\n          recursive: false\n        },\n        {\n          cid: fixtures.files[1].cid,\n          recursive: true\n        }\n      ])\n    })\n\n    it('should add a generator of pins with options', () => {\n      return testAddPinInput(function * () {\n        yield {\n          cid: fixtures.files[0].cid,\n          recursive: false\n        }\n        yield {\n          cid: fixtures.files[1].cid,\n          recursive: true\n        }\n      }())\n    })\n\n    it('should add an async generator of pins with options', () => {\n      return testAddPinInput(async function * () { // eslint-disable-line require-await\n        yield {\n          cid: fixtures.files[0].cid,\n          recursive: false\n        }\n        yield {\n          cid: fixtures.files[1].cid,\n          recursive: true\n        }\n      }())\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/pin/add.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { fixtures, clearPins, expectPinned, expectNotPinned, pinTypes } from './utils.js'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport all from 'it-all'\nimport drain from 'it-drain'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testAdd (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.pin.add', function () {\n    this.timeout(50 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n\n      await drain(\n        ipfs.addAll(\n          fixtures.files.map(file => ({ content: file.data })), {\n            pin: false\n          }\n        )\n      )\n\n      await drain(\n        ipfs.addAll(fixtures.directory.files.map(\n          file => ({\n            path: file.path,\n            content: file.data\n          })\n        ), {\n          pin: false\n        })\n      )\n    })\n\n    after(() => factory.clean())\n\n    beforeEach(() => {\n      return clearPins(ipfs)\n    })\n\n    it('should add a CID and return the added CID', async () => {\n      const cid = await ipfs.pin.add(fixtures.files[0].cid)\n      expect(cid).to.deep.equal(fixtures.files[0].cid)\n    })\n\n    it('should add a pin with options and return the added CID', async () => {\n      const cid = await ipfs.pin.add(fixtures.files[0].cid, {\n        recursive: false\n      })\n      expect(cid).to.deep.equal(fixtures.files[0].cid)\n    })\n\n    it('should add recursively', async () => {\n      await ipfs.pin.add(fixtures.directory.cid)\n      await expectPinned(ipfs, fixtures.directory.cid, pinTypes.recursive)\n\n      const pinChecks = Object.values(fixtures.directory.files).map(file => expectPinned(ipfs, file.cid))\n      return Promise.all(pinChecks)\n    })\n\n    it('should add directly', async () => {\n      await ipfs.pin.add(fixtures.directory.cid, {\n        recursive: false\n      })\n\n      await expectPinned(ipfs, fixtures.directory.cid, pinTypes.direct)\n      await expectNotPinned(ipfs, fixtures.directory.files[0].cid)\n    })\n\n    it('should recursively pin parent of direct pin', async () => {\n      await ipfs.pin.add(fixtures.directory.files[0].cid, {\n        recursive: false\n      })\n      await ipfs.pin.add(fixtures.directory.cid)\n\n      // file is pinned both directly and indirectly o.O\n      await expectPinned(ipfs, fixtures.directory.files[0].cid, pinTypes.direct)\n      await expectPinned(ipfs, fixtures.directory.files[0].cid, pinTypes.indirect)\n    })\n\n    it('should fail to directly pin a recursive pin', async () => {\n      await ipfs.pin.add(fixtures.directory.cid)\n      return expect(ipfs.pin.add(fixtures.directory.cid, {\n        recursive: false\n      }))\n        .to.eventually.be.rejectedWith(/already pinned recursively/)\n    })\n\n    it('should fail to pin a hash not in datastore', async function () {\n      this.slow(3 * 1000)\n      this.timeout(5 * 1000)\n      const falseHash = `${`${fixtures.directory.cid}`.slice(0, -2)}ss`\n\n      await expect(ipfs.pin.add(falseHash, { timeout: '2s' }))\n        .to.eventually.be.rejected().with.property('name', 'TimeoutError')\n    })\n\n    it('needs all children in datastore to pin recursively', async function () {\n      this.slow(3 * 1000)\n      this.timeout(5 * 1000)\n      await all(ipfs.block.rm(fixtures.directory.files[0].cid))\n\n      await expect(ipfs.pin.add(fixtures.directory.cid, { timeout: '2s' }))\n        .to.eventually.be.rejected().with.property('name', 'TimeoutError')\n    })\n\n    it('should pin dag-cbor', async () => {\n      const cid = await ipfs.dag.put({}, {\n        storeCodec: 'dag-cbor',\n        hashAlg: 'sha2-256'\n      })\n\n      await ipfs.pin.add(cid)\n\n      const pins = await all(ipfs.pin.ls())\n\n      expect(pins).to.deep.include({\n        type: 'recursive',\n        cid\n      })\n    })\n\n    it('should pin raw', async () => {\n      const cid = await ipfs.dag.put(new Uint8Array(0), {\n        storeCodec: 'raw',\n        hashAlg: 'sha2-256'\n      })\n\n      await ipfs.pin.add(cid)\n\n      const pins = await all(ipfs.pin.ls())\n\n      expect(pins).to.deep.include({\n        type: 'recursive',\n        cid\n      })\n    })\n\n    it('should pin dag-cbor with dag-pb child', async () => {\n      const child = await ipfs.dag.put({\n        Data: uint8ArrayFromString(`${Math.random()}`),\n        Links: []\n      }, {\n        storeCodec: 'dag-pb',\n        hashAlg: 'sha2-256'\n      })\n      const parent = await ipfs.dag.put({\n        child\n      }, {\n        storeCodec: 'dag-cbor',\n        hashAlg: 'sha2-256'\n      })\n\n      await ipfs.pin.add(parent, {\n        recursive: true\n      })\n\n      const pins = await all(ipfs.pin.ls())\n\n      expect(pins).to.deep.include({\n        cid: parent,\n        type: 'recursive'\n      })\n      expect(pins).to.deep.include({\n        cid: child,\n        type: 'indirect'\n      })\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/pin/index.js",
    "content": "import { createSuite } from '../utils/suite.js'\nimport { testAdd } from './add.js'\nimport { testAddAll } from './add-all.js'\nimport { testLs } from './ls.js'\nimport { testRm } from './rm.js'\nimport { testRmAll } from './rm-all.js'\nimport testRemote from './remote/index.js'\n\nconst tests = {\n  add: testAdd,\n  addAll: testAddAll,\n  ls: testLs,\n  rm: testRm,\n  rmAll: testRmAll,\n  remote: testRemote\n}\n\nexport default createSuite(tests)\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/pin/ls.js",
    "content": "/* eslint-env mocha */\n\nimport { fixtures } from './utils.js'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport all from 'it-all'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testLs (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.pin.ls', function () {\n    this.timeout(50 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n      // two files wrapped in directories, only root CID pinned recursively\n      const dir = fixtures.directory.files.map((file) => ({ path: file.path, content: file.data }))\n      await all(ipfs.addAll(dir, { pin: false, cidVersion: 0 }))\n      await ipfs.pin.add(fixtures.directory.cid, { recursive: true })\n      // a file (CID pinned recursively)\n      await ipfs.add(fixtures.files[0].data, { pin: false, cidVersion: 0 })\n      await ipfs.pin.add(fixtures.files[0].cid, { recursive: true })\n      // a single CID (pinned directly)\n      await ipfs.add(fixtures.files[1].data, { pin: false, cidVersion: 0 })\n      await ipfs.pin.add(fixtures.files[1].cid, { recursive: false })\n    })\n\n    after(() => factory.clean())\n\n    // 1st, because ipfs.add pins automatically\n    it('should list all recursive pins', async () => {\n      const pinset = await all(ipfs.pin.ls({ type: 'recursive' }))\n\n      expect(pinset).to.deep.include({\n        type: 'recursive',\n        cid: fixtures.files[0].cid\n      })\n      expect(pinset).to.deep.include({\n        type: 'recursive',\n        cid: fixtures.directory.cid\n      })\n    })\n\n    it('should list all indirect pins', async () => {\n      const pinset = await all(ipfs.pin.ls({ type: 'indirect' }))\n\n      expect(pinset).to.not.deep.include({\n        type: 'recursive',\n        cid: fixtures.files[0].cid\n      })\n      expect(pinset).to.not.deep.include({\n        type: 'direct',\n        cid: fixtures.files[1].cid\n      })\n      expect(pinset).to.not.deep.include({\n        type: 'recursive',\n        cid: fixtures.directory.cid\n      })\n      expect(pinset).to.deep.include({\n        type: 'indirect',\n        cid: fixtures.directory.files[0].cid\n      })\n      expect(pinset).to.deep.include({\n        type: 'indirect',\n        cid: fixtures.directory.files[1].cid\n      })\n    })\n\n    it('should list all types of pins', async () => {\n      const pinset = await all(ipfs.pin.ls())\n\n      expect(pinset).to.not.be.empty()\n      // check the three \"roots\"\n      expect(pinset).to.deep.include({\n        type: 'recursive',\n        cid: fixtures.directory.cid\n      })\n      expect(pinset).to.deep.include({\n        type: 'recursive',\n        cid: fixtures.files[0].cid\n      })\n      expect(pinset).to.deep.include({\n        type: 'direct',\n        cid: fixtures.files[1].cid\n      })\n      expect(pinset).to.deep.include({\n        type: 'indirect',\n        cid: fixtures.directory.files[0].cid\n      })\n      expect(pinset).to.deep.include({\n        type: 'indirect',\n        cid: fixtures.directory.files[1].cid\n      })\n    })\n\n    it('should list all direct pins', async () => {\n      const pinset = await all(ipfs.pin.ls({ type: 'direct' }))\n      expect(pinset).to.have.lengthOf(1)\n      expect(pinset).to.deep.include({\n        type: 'direct',\n        cid: fixtures.files[1].cid\n      })\n    })\n\n    it('should list pins for a specific hash', async () => {\n      const pinset = await all(ipfs.pin.ls({\n        paths: fixtures.files[0].cid\n      }))\n      expect(pinset).to.have.lengthOf(1)\n      expect(pinset).to.have.deep.members([{\n        type: 'recursive',\n        cid: fixtures.files[0].cid\n      }])\n    })\n\n    it('should throw an error on missing direct pins for existing path', () => {\n      // ipfs.txt is an indirect pin, so lookup for direct one should throw an error\n      return expect(all(ipfs.pin.ls({\n        paths: `/ipfs/${fixtures.directory.cid}/files/ipfs.txt`,\n        type: 'direct'\n      })))\n        .to.eventually.be.rejected\n        .and.be.an.instanceOf(Error)\n        .and.to.have.property('message', `path '/ipfs/${fixtures.directory.cid}/files/ipfs.txt' is not pinned`)\n    })\n\n    it('should throw an error on missing link for a specific path', () => {\n      return expect(all(ipfs.pin.ls({\n        paths: `/ipfs/${fixtures.directory.cid}/I-DONT-EXIST.txt`,\n        type: 'direct'\n      })))\n        .to.eventually.be.rejected\n        .and.be.an.instanceOf(Error)\n        .and.to.have.property('message', `no link named \"I-DONT-EXIST.txt\" under ${fixtures.directory.cid}`)\n    })\n\n    it('should list indirect pins for a specific path', async () => {\n      const pinset = await all(ipfs.pin.ls({\n        paths: `/ipfs/${fixtures.directory.cid}/files/ipfs.txt`,\n        type: 'indirect'\n      }))\n      expect(pinset).to.have.lengthOf(1)\n      expect(pinset).to.deep.include({\n        type: `indirect through ${fixtures.directory.cid}`,\n        cid: fixtures.directory.files[1].cid\n      })\n    })\n\n    it('should list recursive pins for a specific hash', async () => {\n      const pinset = await all(ipfs.pin.ls({\n        paths: fixtures.files[0].cid,\n        type: 'recursive'\n      }))\n      expect(pinset).to.have.lengthOf(1)\n      expect(pinset).to.deep.include({\n        type: 'recursive',\n        cid: fixtures.files[0].cid\n      })\n    })\n\n    it('should list pins for multiple CIDs', async () => {\n      const pinset = await all(ipfs.pin.ls({\n        paths: [fixtures.files[0].cid, fixtures.files[1].cid]\n      }))\n      const cids = pinset.map(p => p.cid.toString())\n      expect(cids).to.include(fixtures.files[0].cid.toString())\n      expect(cids).to.include(fixtures.files[1].cid.toString())\n    })\n\n    it('should throw error for invalid non-string pin type option', () => {\n      return expect(all(ipfs.pin.ls({ type: 6 })))\n        .to.eventually.be.rejected()\n        // TODO: go-ipfs does not return error codes\n        // .with.property('code').that.equals('ERR_INVALID_PIN_TYPE')\n    })\n\n    it('should throw error for invalid string pin type option', () => {\n      return expect(all(ipfs.pin.ls({ type: '__proto__' })))\n        .to.eventually.be.rejected()\n        // TODO: go-ipfs does not return error codes\n        // .with.property('code').that.equals('ERR_INVALID_PIN_TYPE')\n    })\n\n    it('should list pins with metadata', async () => {\n      const { cid } = await ipfs.add(`data-${Math.random()}`, {\n        pin: false\n      })\n\n      const metadata = {\n        key: 'value',\n        one: 2,\n        array: [{\n          thing: 'subthing'\n        }],\n        obj: {\n          foo: 'bar',\n          baz: ['qux']\n        }\n      }\n\n      await ipfs.pin.add(cid, {\n        recursive: false,\n        metadata\n      })\n\n      const pinset = await all(ipfs.pin.ls({\n        paths: cid\n      }))\n\n      expect(pinset).to.have.deep.members([{\n        type: 'direct',\n        cid: cid,\n        metadata\n      }])\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/pin/remote/add.js",
    "content": "/* eslint-env mocha */\n\nimport { fixtures, clearRemotePins, clearServices } from '../utils.js'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../../utils/mocha.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testAdd (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  const ENDPOINT = new URL(process.env.PINNING_SERVICE_ENDPOINT || '')\n  const KEY = `${process.env.PINNING_SERVICE_KEY}`\n  const SERVICE = 'pinbot'\n\n  describe('.pin.remote.add', function () {\n    this.timeout(50 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n      await ipfs.pin.remote.service.add(SERVICE, {\n        endpoint: ENDPOINT,\n        key: KEY\n      })\n    })\n    after(async () => {\n      await clearServices(ipfs)\n      await factory.clean()\n    })\n\n    beforeEach(async () => {\n      await clearRemotePins(ipfs)\n    })\n\n    it('should add a CID and return the added CID', async () => {\n      const pin = await ipfs.pin.remote.add(fixtures.files[0].cid, {\n        name: 'fixtures-files-0',\n        background: true,\n        service: SERVICE\n      })\n\n      expect(pin).to.deep.equal({\n        status: 'queued',\n        cid: fixtures.files[0].cid,\n        name: 'fixtures-files-0'\n      })\n    })\n\n    it('should fail if service is not provided', async () => {\n      const result = ipfs.pin.remote.add(fixtures.files[0].cid, {\n        name: 'fixtures-files-0',\n        background: true\n      })\n\n      await expect(result).to.eventually.be.rejectedWith(/service name must be passed/)\n    })\n\n    it('if name is not provided defaults to \"\"', async () => {\n      const pin = await ipfs.pin.remote.add(fixtures.files[0].cid, {\n        background: true,\n        service: SERVICE\n      })\n\n      expect(pin).to.deep.equal({\n        cid: fixtures.files[0].cid,\n        name: '',\n        status: 'queued'\n      })\n    })\n\n    it('should default to blocking pin', async () => {\n      const { cid } = fixtures.files[0]\n      const result = ipfs.pin.remote.add(cid, {\n        service: SERVICE\n      })\n\n      const timeout = {}\n\n      const winner = await Promise.race([\n        result,\n        new Promise(resolve => setTimeout(resolve, 100, timeout))\n      ])\n\n      expect(winner).to.equal(timeout)\n\n      // trigger status change on the mock service\n      ipfs.pin.remote.add(cid, {\n        service: SERVICE,\n        name: 'pinned-block'\n      })\n\n      expect(await result).to.deep.equal({\n        cid,\n        status: 'pinned',\n        name: ''\n      })\n    })\n    it('should pin dag-cbor', async () => {\n      const cid = await ipfs.dag.put({}, {\n        storeCodec: 'dag-cbor',\n        hashAlg: 'sha2-256'\n      })\n\n      const pin = await ipfs.pin.remote.add(cid, {\n        service: SERVICE,\n        name: 'cbor-pin',\n        background: true\n      })\n\n      expect(pin).to.deep.equal({\n        cid,\n        name: 'cbor-pin',\n        status: 'queued'\n      })\n    })\n\n    it('should pin raw', async () => {\n      const cid = await ipfs.dag.put(new Uint8Array(0), {\n        storeCodec: 'raw',\n        hashAlg: 'sha2-256'\n      })\n\n      const pin = await ipfs.pin.remote.add(cid, {\n        service: SERVICE,\n        background: true\n      })\n\n      expect(pin).to.deep.equal({\n        cid,\n        status: 'queued',\n        name: ''\n      })\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/pin/remote/index.js",
    "content": "import { createSuite } from '../../utils/suite.js'\nimport { testService } from './service.js'\nimport { testAdd } from './add.js'\nimport { testLs } from './ls.js'\nimport { testRm } from './rm.js'\nimport { testRmAll } from './rm-all.js'\n\nconst tests = {\n  service: testService,\n  add: testAdd,\n  ls: testLs,\n  rm: testRm,\n  rmAll: testRmAll\n}\n\nexport default createSuite(tests, 'pin')\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/pin/remote/ls.js",
    "content": "/* eslint-env mocha */\n\nimport { clearRemotePins, addRemotePins, clearServices } from '../utils.js'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../../utils/mocha.js'\nimport all from 'it-all'\nimport { CID } from 'multiformats/cid'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testLs (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  const ENDPOINT = new URL(process.env.PINNING_SERVICE_ENDPOINT || '')\n  const KEY = `${process.env.PINNING_SERVICE_KEY}`\n  const SERVICE = 'pinbot'\n\n  const cid1 = CID.parse('QmbKtKBrmeRHjNCwR4zAfCJdMVu6dgmwk9M9AE9pUM9RgG')\n  const cid2 = CID.parse('QmdFyxZXsFiP4csgfM5uPu99AvFiKH62CSPDw5TP92nr7w')\n  const cid3 = CID.parse('Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP')\n  const cid4 = CID.parse('QmY9cxiHqTFoWamkQVkpmmqzBrY3hCBEL2XNu3NtX74Fuu')\n\n  describe('.pin.remote.ls', function () {\n    this.timeout(50 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n      await ipfs.pin.remote.service.add(SERVICE, {\n        endpoint: ENDPOINT,\n        key: KEY\n      })\n    })\n    after(async () => {\n      await clearServices(ipfs)\n      await factory.clean()\n    })\n\n    beforeEach(async () => {\n      await clearRemotePins(ipfs)\n    })\n\n    it('requires service option', async () => {\n      const result = ipfs.pin.remote.ls({})\n      await expect(all(result)).to.eventually.be.rejectedWith(/service name must be passed/)\n    })\n\n    it('list no pins', async () => {\n      const result = ipfs.pin.remote.ls({ service: SERVICE })\n      const pins = await all(result)\n      expect(pins).to.deep.equal([])\n    })\n\n    describe('list pins by status', () => {\n      it('list only pinned pins by default', async () => {\n        await addRemotePins(ipfs, SERVICE, {\n          one: cid1,\n          'pinned-two': cid2,\n          'pinning-three': cid3,\n          'failed-four': cid4\n        })\n\n        const list = await all(ipfs.pin.remote.ls({\n          service: SERVICE\n        }))\n\n        expect(list).to.deep.equal([\n          {\n            status: 'pinned',\n            cid: cid2,\n            name: 'pinned-two'\n          }\n        ])\n      })\n\n      it('should list \"queued\" pins', async () => {\n        await addRemotePins(ipfs, SERVICE, {\n          one: cid1,\n          'pinned-two': cid2,\n          'pinning-three': cid3,\n          'failed-four': cid4\n        })\n\n        const list = await all(ipfs.pin.remote.ls({\n          status: ['queued'],\n          service: SERVICE\n        }))\n\n        expect(list).to.deep.equal([\n          {\n            status: 'queued',\n            cid: cid1,\n            name: 'one'\n          }\n        ])\n      })\n\n      it('should list \"pinning\" pins', async () => {\n        await addRemotePins(ipfs, SERVICE, {\n          one: cid1,\n          'pinned-two': cid2,\n          'pinning-three': cid3,\n          'failed-four': cid4\n        })\n\n        const list = await all(ipfs.pin.remote.ls({\n          status: ['pinning'],\n          service: SERVICE\n        }))\n\n        expect(list).to.deep.equal([\n          {\n            status: 'pinning',\n            cid: cid3,\n            name: 'pinning-three'\n          }\n        ])\n      })\n\n      it('should list \"failed\" pins', async () => {\n        await addRemotePins(ipfs, SERVICE, {\n          one: cid1,\n          'pinned-two': cid2,\n          'pinning-three': cid3,\n          'failed-four': cid4\n        })\n\n        const list = await all(ipfs.pin.remote.ls({\n          status: ['failed'],\n          service: SERVICE\n        }))\n\n        expect(list).to.deep.equal([\n          {\n            status: 'failed',\n            cid: cid4,\n            name: 'failed-four'\n          }\n        ])\n      })\n\n      it('should list queued+pinned pins', async () => {\n        await addRemotePins(ipfs, SERVICE, {\n          one: cid1,\n          'pinned-two': cid2,\n          'pinning-three': cid3,\n          'failed-four': cid4\n        })\n\n        const list = await all(ipfs.pin.remote.ls({\n          status: ['queued', 'pinned'],\n          service: SERVICE\n        }))\n\n        expect(list.sort(byCID)).to.deep.equal([\n          {\n            status: 'queued',\n            cid: cid1,\n            name: 'one'\n          },\n          {\n            status: 'pinned',\n            cid: cid2,\n            name: 'pinned-two'\n          }\n        ].sort(byCID))\n      })\n\n      it('should list queued+pinned+pinning pins', async () => {\n        await addRemotePins(ipfs, SERVICE, {\n          one: cid1,\n          'pinned-two': cid2,\n          'pinning-three': cid3,\n          'failed-four': cid4\n        })\n\n        const list = await all(ipfs.pin.remote.ls({\n          status: ['queued', 'pinned', 'pinning'],\n          service: SERVICE\n        }))\n\n        expect(list.sort(byCID)).to.deep.equal([\n          {\n            status: 'queued',\n            cid: cid1,\n            name: 'one'\n          },\n          {\n            status: 'pinned',\n            cid: cid2,\n            name: 'pinned-two'\n          },\n          {\n            status: 'pinning',\n            cid: cid3,\n            name: 'pinning-three'\n          }\n        ].sort(byCID))\n      })\n\n      it('should list queued+pinned+pinning+failed pins', async () => {\n        await addRemotePins(ipfs, SERVICE, {\n          one: cid1,\n          'pinned-two': cid2,\n          'pinning-three': cid3,\n          'failed-four': cid4\n        })\n\n        const list = await all(ipfs.pin.remote.ls({\n          status: ['queued', 'pinned', 'pinning', 'failed'],\n          service: SERVICE\n        }))\n\n        expect(list.sort(byCID)).to.deep.equal([\n          {\n            status: 'queued',\n            cid: cid1,\n            name: 'one'\n          },\n          {\n            status: 'pinned',\n            cid: cid2,\n            name: 'pinned-two'\n          },\n          {\n            status: 'pinning',\n            cid: cid3,\n            name: 'pinning-three'\n          },\n          {\n            status: 'failed',\n            cid: cid4,\n            name: 'failed-four'\n          }\n        ].sort(byCID))\n      })\n    })\n\n    describe('list pins by name', () => {\n      it('should list no pins when names do not match', async () => {\n        await addRemotePins(ipfs, SERVICE, {\n          a: cid1,\n          b: cid2,\n          c: cid3\n        })\n\n        const list = await all(ipfs.pin.remote.ls({\n          name: 'd',\n          status: ['queued', 'pinning', 'pinned', 'failed'],\n          service: SERVICE\n        }))\n\n        expect(list).to.deep.equal([])\n      })\n      it('should list only pins with matchin names', async () => {\n        await addRemotePins(ipfs, SERVICE, {\n          a: cid1,\n          b: cid2\n        })\n        await addRemotePins(ipfs, SERVICE, {\n          a: cid3,\n          b: cid4\n        })\n\n        const list = await all(ipfs.pin.remote.ls({\n          name: 'a',\n          status: ['queued', 'pinning', 'pinned', 'failed'],\n          service: SERVICE\n        }))\n\n        expect(list.sort(byCID)).to.deep.equal([\n          {\n            status: 'queued',\n            name: 'a',\n            cid: cid1\n          },\n          {\n            status: 'queued',\n            name: 'a',\n            cid: cid3\n          }\n        ].sort(byCID))\n      })\n\n      it('should list only pins with matchin names & status', async () => {\n        await addRemotePins(ipfs, SERVICE, {\n          a: cid1,\n          b: cid2\n        })\n        await addRemotePins(ipfs, SERVICE, {\n          a: cid3,\n          b: cid4\n        })\n        // update status\n        await addRemotePins(ipfs, SERVICE, {\n          'pinned-a': cid3\n        })\n\n        const list = await all(ipfs.pin.remote.ls({\n          name: 'a',\n          status: ['pinned'],\n          service: SERVICE\n        }))\n\n        expect(list).to.deep.equal([\n          {\n            status: 'pinned',\n            name: 'a',\n            cid: cid3\n          }\n        ])\n      })\n    })\n\n    describe('list pins by cid', () => {\n      it('should list pins with matching cid', async () => {\n        await addRemotePins(ipfs, SERVICE, {\n          a: cid1,\n          b: cid2,\n          c: cid3,\n          d: cid4\n        })\n\n        const list = await all(ipfs.pin.remote.ls({\n          cid: [cid1],\n          status: ['queued', 'pinned', 'pinning', 'failed'],\n          service: SERVICE\n        }))\n\n        expect(list).to.deep.equal([\n          {\n            status: 'queued',\n            cid: cid1,\n            name: 'a'\n          }\n        ])\n      })\n\n      it('should list pins with any matching cid', async () => {\n        await addRemotePins(ipfs, SERVICE, {\n          a: cid1,\n          b: cid2,\n          c: cid3,\n          d: cid4\n        })\n\n        const list = await all(ipfs.pin.remote.ls({\n          cid: [cid1, cid3],\n          status: ['queued', 'pinned', 'pinning', 'failed'],\n          service: SERVICE\n        }))\n\n        expect(list.sort(byCID)).to.deep.equal([\n          {\n            status: 'queued',\n            cid: cid1,\n            name: 'a'\n          },\n          {\n            status: 'queued',\n            cid: cid3,\n            name: 'c'\n          }\n        ].sort(byCID))\n      })\n\n      it('should list pins with matching cid+status', async () => {\n        await addRemotePins(ipfs, SERVICE, {\n          'pinned-a': cid1,\n          'failed-b': cid2,\n          'pinned-c': cid3,\n          d: cid4\n        })\n\n        const list = await all(ipfs.pin.remote.ls({\n          cid: [cid1, cid2],\n          status: ['pinned', 'failed'],\n          service: SERVICE\n        }))\n\n        expect(list.sort(byCID)).to.deep.equal([\n          {\n            status: 'pinned',\n            cid: cid1,\n            name: 'pinned-a'\n          },\n          {\n            status: 'failed',\n            cid: cid2,\n            name: 'failed-b'\n          }\n        ].sort(byCID))\n      })\n\n      it('should list pins with matching cid+status+name', async () => {\n        await addRemotePins(ipfs, SERVICE, {\n          'pinned-a': cid1,\n          'failed-b': cid2,\n          'pinned-c': cid3,\n          d: cid4\n        })\n\n        const list = await all(ipfs.pin.remote.ls({\n          cid: [cid4, cid1, cid2],\n          name: 'd',\n          status: ['queued', 'pinned'],\n          service: SERVICE\n        }))\n\n        expect(list).to.deep.equal([\n          {\n            status: 'queued',\n            cid: cid4,\n            name: 'd'\n          }\n        ])\n      })\n    })\n  })\n}\n\n/**\n * @param {{ cid: CID }} a\n * @param {{ cid: CID }} b\n */\nconst byCID = (a, b) => a.cid.toString() > b.cid.toString() ? 1 : -1\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/pin/remote/rm-all.js",
    "content": "/* eslint-env mocha */\n\nimport { clearRemotePins, addRemotePins, clearServices } from '../utils.js'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../../utils/mocha.js'\nimport { CID } from 'multiformats/cid'\nimport all from 'it-all'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testRmAll (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  const ENDPOINT = new URL(process.env.PINNING_SERVICE_ENDPOINT || '')\n  const KEY = `${process.env.PINNING_SERVICE_KEY}`\n  const SERVICE = 'pinbot'\n\n  const cid1 = CID.parse('QmbKtKBrmeRHjNCwR4zAfCJdMVu6dgmwk9M9AE9pUM9RgG')\n  const cid2 = CID.parse('QmdFyxZXsFiP4csgfM5uPu99AvFiKH62CSPDw5TP92nr7w')\n  const cid3 = CID.parse('Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP')\n  const cid4 = CID.parse('QmY9cxiHqTFoWamkQVkpmmqzBrY3hCBEL2XNu3NtX74Fuu')\n\n  describe('.pin.remote.rmAll', function () {\n    this.timeout(50 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n      await ipfs.pin.remote.service.add(SERVICE, {\n        endpoint: ENDPOINT,\n        key: KEY\n      })\n    })\n    after(async () => {\n      await clearServices(ipfs)\n      await factory.clean()\n    })\n\n    beforeEach(async () => {\n      await addRemotePins(ipfs, SERVICE, {\n        'queued-a': cid1,\n        'pinning-b': cid2,\n        'pinned-c': cid3,\n        'failed-d': cid4\n      })\n    })\n    afterEach(async () => {\n      await clearRemotePins(ipfs)\n    })\n\n    it('.rmAll requires service option', async () => {\n      const result = ipfs.pin.remote.rmAll({})\n      await expect(result).to.eventually.be.rejectedWith(/service name must be passed/)\n    })\n\n    it('noop if there is no match', async () => {\n      await ipfs.pin.remote.rmAll({\n        cid: [cid1],\n        status: ['pinned', 'failed'],\n        service: SERVICE\n      })\n\n      const list = await all(ipfs.pin.remote.ls({\n        status: ['queued', 'pinning', 'pinned', 'failed'],\n        service: SERVICE\n      }))\n\n      expect(list.sort(byCID)).to.deep.equal([\n        {\n          cid: cid1,\n          status: 'queued',\n          name: 'queued-a'\n        },\n        {\n          cid: cid2,\n          status: 'pinning',\n          name: 'pinning-b'\n        },\n        {\n          cid: cid3,\n          status: 'pinned',\n          name: 'pinned-c'\n        },\n        {\n          cid: cid4,\n          status: 'failed',\n          name: 'failed-d'\n        }\n      ].sort(byCID))\n    })\n\n    it('removes matching pin', async () => {\n      await ipfs.pin.remote.rmAll({\n        cid: [cid1],\n        status: ['queued', 'pinning', 'pinned', 'failed'],\n        service: SERVICE\n      })\n\n      const list = await all(ipfs.pin.remote.ls({\n        status: ['queued', 'pinning', 'pinned', 'failed'],\n        service: SERVICE\n      }))\n\n      expect(list.sort(byCID)).to.deep.equal([\n        {\n          cid: cid2,\n          status: 'pinning',\n          name: 'pinning-b'\n        },\n        {\n          cid: cid3,\n          status: 'pinned',\n          name: 'pinned-c'\n        },\n        {\n          cid: cid4,\n          status: 'failed',\n          name: 'failed-d'\n        }\n      ].sort(byCID))\n    })\n\n    it('removes multiple matches', async () => {\n      const result = ipfs.pin.remote.rmAll({\n        cid: [cid1, cid2],\n        status: ['queued', 'pinning', 'pinned', 'failed'],\n        service: SERVICE\n      })\n      await expect(result).to.eventually.be.equal(undefined)\n\n      const list = await all(ipfs.pin.remote.ls({\n        status: ['queued', 'pinning', 'pinned', 'failed'],\n        service: SERVICE\n      }))\n\n      expect(list.sort(byCID)).to.deep.equal([\n        {\n          cid: cid3,\n          status: 'pinned',\n          name: 'pinned-c'\n        },\n        {\n          cid: cid4,\n          status: 'failed',\n          name: 'failed-d'\n        }\n      ].sort(byCID))\n    })\n  })\n}\n\n/**\n * @param {{ cid: CID }} a\n * @param {{ cid: CID }} b\n */\nconst byCID = (a, b) => a.cid.toString() > b.cid.toString() ? 1 : -1\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/pin/remote/rm.js",
    "content": "/* eslint-env mocha */\n\nimport { clearRemotePins, addRemotePins, clearServices } from '../utils.js'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../../utils/mocha.js'\nimport { CID } from 'multiformats/cid'\nimport all from 'it-all'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testRm (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  const ENDPOINT = new URL(process.env.PINNING_SERVICE_ENDPOINT || '')\n  const KEY = `${process.env.PINNING_SERVICE_KEY}`\n  const SERVICE = 'pinbot'\n\n  const cid1 = CID.parse('QmbKtKBrmeRHjNCwR4zAfCJdMVu6dgmwk9M9AE9pUM9RgG')\n  const cid2 = CID.parse('QmdFyxZXsFiP4csgfM5uPu99AvFiKH62CSPDw5TP92nr7w')\n  const cid3 = CID.parse('Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP')\n  const cid4 = CID.parse('QmY9cxiHqTFoWamkQVkpmmqzBrY3hCBEL2XNu3NtX74Fuu')\n\n  describe('.pin.remote.rm', function () {\n    this.timeout(50 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n      await ipfs.pin.remote.service.add(SERVICE, {\n        endpoint: ENDPOINT,\n        key: KEY\n      })\n    })\n    after(async () => {\n      await clearServices(ipfs)\n      await factory.clean()\n    })\n\n    beforeEach(async () => {\n      await addRemotePins(ipfs, SERVICE, {\n        'queued-a': cid1,\n        'pinning-b': cid2,\n        'pinned-c': cid3,\n        'failed-d': cid4\n      })\n    })\n    afterEach(async () => {\n      await clearRemotePins(ipfs)\n    })\n\n    it('.rm requires service option', async () => {\n      const result = ipfs.pin.remote.rm({})\n      await expect(result).to.eventually.be.rejectedWith(/service name must be passed/)\n    })\n\n    it('.rmAll requires service option', async () => {\n      const result = ipfs.pin.remote.rmAll({})\n      await expect(result).to.eventually.be.rejectedWith(/service name must be passed/)\n    })\n\n    it('noop if there is no match', async () => {\n      await ipfs.pin.remote.rm({\n        cid: [cid1],\n        status: ['pinned', 'failed'],\n        service: SERVICE\n      })\n\n      const list = await all(ipfs.pin.remote.ls({\n        status: ['queued', 'pinning', 'pinned', 'failed'],\n        service: SERVICE\n      }))\n\n      expect(list.sort(byCID)).to.deep.equal([\n        {\n          cid: cid1,\n          status: 'queued',\n          name: 'queued-a'\n        },\n        {\n          cid: cid2,\n          status: 'pinning',\n          name: 'pinning-b'\n        },\n        {\n          cid: cid3,\n          status: 'pinned',\n          name: 'pinned-c'\n        },\n        {\n          cid: cid4,\n          status: 'failed',\n          name: 'failed-d'\n        }\n      ].sort(byCID))\n    })\n\n    it('removes matching pin', async () => {\n      await ipfs.pin.remote.rm({\n        cid: [cid1],\n        status: ['queued', 'pinning', 'pinned', 'failed'],\n        service: SERVICE\n      })\n\n      const list = await all(ipfs.pin.remote.ls({\n        status: ['queued', 'pinning', 'pinned', 'failed'],\n        service: SERVICE\n      }))\n\n      expect(list.sort(byCID)).to.deep.equal([\n        {\n          cid: cid2,\n          status: 'pinning',\n          name: 'pinning-b'\n        },\n        {\n          cid: cid3,\n          status: 'pinned',\n          name: 'pinned-c'\n        },\n        {\n          cid: cid4,\n          status: 'failed',\n          name: 'failed-d'\n        }\n      ].sort(byCID))\n    })\n\n    it('fails on multiple matches', async () => {\n      const result = ipfs.pin.remote.rm({\n        cid: [cid1, cid2],\n        status: ['queued', 'pinning', 'pinned', 'failed'],\n        service: SERVICE\n      })\n\n      await expect(result).to.eventually.be.rejectedWith(\n        /multiple remote pins are matching this query/\n      )\n\n      const list = await all(ipfs.pin.remote.ls({\n        status: ['queued', 'pinning', 'pinned', 'failed'],\n        service: SERVICE\n      }))\n\n      expect(list.sort(byCID)).to.deep.equal([\n        {\n          cid: cid1,\n          status: 'queued',\n          name: 'queued-a'\n        },\n        {\n          cid: cid2,\n          status: 'pinning',\n          name: 'pinning-b'\n        },\n        {\n          cid: cid3,\n          status: 'pinned',\n          name: 'pinned-c'\n        },\n        {\n          cid: cid4,\n          status: 'failed',\n          name: 'failed-d'\n        }\n      ].sort(byCID))\n    })\n  })\n}\n\n/**\n * @param {{ cid: CID }} a\n * @param {{ cid: CID }} b\n */\nconst byCID = (a, b) => a.cid.toString() > b.cid.toString() ? 1 : -1\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/pin/remote/service.js",
    "content": "/* eslint-env mocha */\n\nimport { clearServices } from '../utils.js'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../../utils/mocha.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testService (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  const ENDPOINT = new URL(process.env.PINNING_SERVICE_ENDPOINT || '')\n  const KEY = `${process.env.PINNING_SERVICE_KEY}`\n\n  describe('.pin.remote.service', function () {\n    this.timeout(50 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(async () => {\n      await factory.clean()\n    })\n    afterEach(() => clearServices(ipfs))\n\n    describe('.pin.remote.service.add', () => {\n      it('should add a service', async () => {\n        await ipfs.pin.remote.service.add('pinbot', {\n          endpoint: ENDPOINT,\n          key: KEY\n        })\n\n        const services = await ipfs.pin.remote.service.ls()\n        expect(services).to.deep.equal([{\n          service: 'pinbot',\n          endpoint: ENDPOINT\n        }])\n      })\n\n      it('service add requires endpoint', async () => {\n        // @ts-expect-error missing property\n        const result = ipfs.pin.remote.service.add('noend', { key: 'token' })\n        await expect(result).to.eventually.be.rejectedWith(/is required/)\n      })\n\n      it('service add requires key', async () => {\n        // @ts-expect-error missing property\n        const result = ipfs.pin.remote.service.add('nokey', {\n          endpoint: ENDPOINT\n        })\n\n        await expect(result).to.eventually.be.rejectedWith(/is required/)\n      })\n\n      it('add multiple services', async () => {\n        await ipfs.pin.remote.service.add('pinbot', {\n          endpoint: ENDPOINT,\n          key: KEY\n        })\n\n        await ipfs.pin.remote.service.add('pinata', {\n          endpoint: new URL('https://api.pinata.cloud'),\n          key: 'somekey'\n        })\n\n        const services = await ipfs.pin.remote.service.ls()\n        expect(services.sort(byName)).to.deep.equal([\n          {\n            service: 'pinbot',\n            endpoint: ENDPOINT\n          },\n          {\n            service: 'pinata',\n            endpoint: new URL('https://api.pinata.cloud')\n          }\n        ].sort(byName))\n      })\n\n      it('can not add service with existing name', async () => {\n        await ipfs.pin.remote.service.add('pinbot', {\n          endpoint: ENDPOINT,\n          key: KEY\n        })\n\n        const result = ipfs.pin.remote.service.add('pinbot', {\n          endpoint: new URL('http://pinbot.io/'),\n          key: KEY\n        })\n\n        await expect(result).to.eventually.be.rejectedWith(/service already present/)\n      })\n    })\n\n    describe('.pin.remote.service.ls', () => {\n      it('should list services', async () => {\n        const services = await ipfs.pin.remote.service.ls()\n        expect(services).to.deep.equal([])\n      })\n\n      it('should list added service', async () => {\n        await ipfs.pin.remote.service.add('pinbot', {\n          endpoint: ENDPOINT,\n          key: KEY\n        })\n\n        const services = await ipfs.pin.remote.service.ls()\n        expect(services).to.deep.equal([{\n          service: 'pinbot',\n          endpoint: ENDPOINT\n        }])\n      })\n\n      it('should include service stats', async () => {\n        await ipfs.pin.remote.service.add('pinbot', {\n          endpoint: ENDPOINT,\n          key: KEY\n        })\n\n        const services = await ipfs.pin.remote.service.ls({ stat: true })\n\n        expect(services).to.deep.equal([{\n          service: 'pinbot',\n          endpoint: ENDPOINT,\n          stat: {\n            status: 'valid',\n            pinCount: {\n              queued: 0,\n              pinning: 0,\n              pinned: 0,\n              failed: 0\n            }\n          }\n        }])\n      })\n\n      it('should report unreachable services', async () => {\n        await ipfs.pin.remote.service.add('pinbot', {\n          endpoint: ENDPOINT,\n          key: KEY\n        })\n        await ipfs.pin.remote.service.add('boombot', {\n          // @ts-expect-error invalid property\n          endpoint: 'http://127.0.0.1:5555',\n          key: 'boom'\n        })\n\n        const services = await ipfs.pin.remote.service.ls({ stat: true })\n\n        expect(services.sort(byName)).to.deep.equal([\n          {\n            service: 'pinbot',\n            endpoint: ENDPOINT,\n            stat: {\n              status: 'valid',\n              pinCount: {\n                queued: 0,\n                pinning: 0,\n                pinned: 0,\n                failed: 0\n              }\n            }\n          },\n          {\n            service: 'boombot',\n            endpoint: new URL('http://127.0.0.1:5555'),\n            stat: {\n              status: 'invalid'\n            }\n          }\n        ].sort(byName))\n      })\n    })\n\n    describe('.pin.remote.service.rm', () => {\n      it('should remove service', async () => {\n        await ipfs.pin.remote.service.add('pinbot', {\n          endpoint: ENDPOINT,\n          key: KEY\n        })\n\n        const services = await ipfs.pin.remote.service.ls()\n        expect(services).to.deep.equal([{\n          service: 'pinbot',\n          endpoint: ENDPOINT\n        }])\n\n        await ipfs.pin.remote.service.rm('pinbot')\n\n        expect(await ipfs.pin.remote.service.ls()).to.deep.equal([])\n      })\n\n      it('should not fail if service does not registered', async () => {\n        expect(await ipfs.pin.remote.service.ls()).to.deep.equal([])\n        expect(await ipfs.pin.remote.service.rm('pinbot')).to.equal(undefined)\n      })\n\n      it('expects service name', async () => {\n        // @ts-expect-error invalid arg\n        const result = ipfs.pin.remote.service.rm()\n        await expect(result).to.eventually.be.rejectedWith(/is required/)\n      })\n    })\n  })\n}\n\n/**\n * @param {{ service: string }} a\n * @param {{ service: string }} b\n */\nconst byName = (a, b) => a.service > b.service ? 1 : -1\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/pin/rm-all.js",
    "content": "/* eslint-env mocha */\n\nimport { fixtures, clearPins } from './utils.js'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport all from 'it-all'\nimport drain from 'it-drain'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testRmAll (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.pin.rmAll', function () {\n    this.timeout(50 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n    beforeEach(async () => {\n      ipfs = (await factory.spawn()).api\n\n      const dir = fixtures.directory.files.map((file) => ({ path: file.path, content: file.data }))\n      await all(ipfs.addAll(dir, { pin: false, cidVersion: 0 }))\n\n      await ipfs.add(fixtures.files[0].data, { pin: false })\n      await ipfs.add(fixtures.files[1].data, { pin: false })\n    })\n\n    after(() => factory.clean())\n\n    beforeEach(() => {\n      return clearPins(ipfs)\n    })\n\n    it('should pipe the output of ls to rm', async () => {\n      await ipfs.pin.add(fixtures.directory.cid)\n\n      await drain(ipfs.pin.rmAll(ipfs.pin.ls({ type: 'recursive' })))\n\n      await expect(all(ipfs.pin.ls())).to.eventually.have.lengthOf(0)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/pin/rm.js",
    "content": "/* eslint-env mocha */\n\nimport { fixtures, expectPinned, clearPins } from './utils.js'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport all from 'it-all'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testRm (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.pin.rm', function () {\n    this.timeout(50 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n    beforeEach(async () => {\n      ipfs = (await factory.spawn()).api\n      const dir = fixtures.directory.files.map((file) => ({ path: file.path, content: file.data }))\n      await all(ipfs.addAll(dir, { pin: false, cidVersion: 0 }))\n\n      await ipfs.add(fixtures.files[0].data, { pin: false })\n      await ipfs.add(fixtures.files[1].data, { pin: false })\n    })\n\n    after(() => factory.clean())\n\n    beforeEach(() => {\n      return clearPins(ipfs)\n    })\n\n    it('should remove a recursive pin', async () => {\n      await ipfs.pin.add(fixtures.directory.cid)\n\n      const unpinnedCid = await ipfs.pin.rm(fixtures.directory.cid, { recursive: true })\n      expect(unpinnedCid).to.deep.equal(fixtures.directory.cid)\n\n      const pinset = await all(ipfs.pin.ls({ type: 'recursive' }))\n      expect(pinset).to.not.deep.include({\n        cid: fixtures.directory.cid,\n        type: 'recursive'\n      })\n    })\n\n    it('should remove a direct pin', async () => {\n      await ipfs.pin.add(fixtures.directory.cid, { recursive: false })\n\n      const unpinnedCid = await ipfs.pin.rm(fixtures.directory.cid, { recursive: false })\n      expect(unpinnedCid).to.deep.equal(fixtures.directory.cid)\n\n      const pinset = await all(ipfs.pin.ls({ type: 'direct' }))\n      expect(pinset.map(p => p.cid)).to.not.deep.include(fixtures.directory.cid)\n    })\n\n    it('should fail to remove an indirect pin', async () => {\n      await ipfs.pin.add(fixtures.directory.cid, {\n        recursive: true\n      })\n\n      await expect(ipfs.pin.rm(fixtures.directory.files[0].cid))\n        .to.eventually.be.rejectedWith(/pinned indirectly/)\n      await expectPinned(ipfs, fixtures.directory.files[0].cid)\n    })\n\n    it('should fail when an item is not pinned', async () => {\n      await expect(ipfs.pin.rm(fixtures.directory.cid))\n        .to.eventually.be.rejectedWith(/not pinned/)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/pin/utils.js",
    "content": "import { expect } from 'aegir/chai'\nimport loadFixture from 'aegir/fixtures'\nimport { CID } from 'multiformats/cid'\nimport drain from 'it-drain'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport first from 'it-first'\n\nexport const pinTypes = {\n  direct: 'direct',\n  recursive: 'recursive',\n  indirect: 'indirect',\n  all: 'all'\n}\n\nexport const fixtures = Object.freeze({\n  // NOTE: files under 'directory' need to be different than standalone ones in 'files'\n  directory: Object.freeze({\n    cid: CID.parse('QmY8KdYQSYKFU5hM7F5ioZ5yYSgV5VZ1kDEdqfRL3rFgcd'),\n    files: Object.freeze([Object.freeze({\n      path: 'test-folder/ipfs-add.js',\n      data: loadFixture('test/fixtures/test-folder/ipfs-add.js', 'interface-ipfs-core'),\n      cid: CID.parse('QmbKtKBrmeRHjNCwR4zAfCJdMVu6dgmwk9M9AE9pUM9RgG')\n    }), Object.freeze({\n      path: 'test-folder/files/ipfs.txt',\n      data: loadFixture('test/fixtures/test-folder/files/ipfs.txt', 'interface-ipfs-core'),\n      cid: CID.parse('QmdFyxZXsFiP4csgfM5uPu99AvFiKH62CSPDw5TP92nr7w')\n    })])\n  }),\n  files: Object.freeze([Object.freeze({\n    data: uint8ArrayFromString('Plz add me!\\n'),\n    cid: CID.parse('Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP')\n  }), Object.freeze({\n    data: loadFixture('test/fixtures/test-folder/files/hello.txt', 'interface-ipfs-core'),\n    cid: CID.parse('QmY9cxiHqTFoWamkQVkpmmqzBrY3hCBEL2XNu3NtX74Fuu')\n  })])\n})\n\n/**\n * @param {import('ipfs-core-types').IPFS} ipfs\n */\nexport const clearPins = async (ipfs) => {\n  await drain(ipfs.pin.rmAll(ipfs.pin.ls({ type: pinTypes.recursive })))\n  await drain(ipfs.pin.rmAll(ipfs.pin.ls({ type: pinTypes.direct })))\n}\n\n/**\n * @param {import('ipfs-core-types').IPFS} ipfs\n */\nexport const clearRemotePins = async (ipfs) => {\n  for (const { service } of await ipfs.pin.remote.service.ls()) {\n    const cids = []\n    const status = ['queued', 'pinning', 'pinned', 'failed']\n    for await (const pin of ipfs.pin.remote.ls({ status, service })) {\n      cids.push(pin.cid)\n    }\n\n    if (cids.length > 0) {\n      await ipfs.pin.remote.rmAll({\n        cid: cids,\n        status,\n        service\n      })\n    }\n  }\n}\n\n/**\n * @param {import('ipfs-core-types').IPFS} ipfs\n * @param {string} service\n * @param {Record<string, CID>} pins\n */\nexport const addRemotePins = async (ipfs, service, pins) => {\n  const requests = []\n  for (const [name, cid] of Object.entries(pins)) {\n    requests.push(ipfs.pin.remote.add(cid, {\n      name,\n      service,\n      background: true\n    }))\n  }\n  await Promise.all(requests)\n}\n\n/**\n * @param {import('ipfs-core-types').IPFS} ipfs\n */\nexport const clearServices = async (ipfs) => {\n  const services = await ipfs.pin.remote.service.ls()\n  await Promise.all(services.map(({ service }) => ipfs.pin.remote.service.rm(service)))\n}\n\n/**\n * @param {import('ipfs-core-types').IPFS} ipfs\n * @param {CID} cid\n * @param {string} type\n * @param {boolean} pinned\n */\nexport const expectPinned = async (ipfs, cid, type = pinTypes.all, pinned = true) => {\n  if (typeof type === 'boolean') {\n    pinned = type\n    type = pinTypes.all\n  }\n\n  const result = await isPinnedWithType(ipfs, cid, type)\n  expect(result).to.eql(pinned)\n}\n\n/**\n * @param {import('ipfs-core-types').IPFS} ipfs\n * @param {CID} cid\n * @param {string} type\n */\nexport const expectNotPinned = (ipfs, cid, type = pinTypes.all) => {\n  return expectPinned(ipfs, cid, type, false)\n}\n\n/**\n * @param {import('ipfs-core-types').IPFS} ipfs\n * @param {CID} cid\n * @param {string} type\n */\nexport async function isPinnedWithType (ipfs, cid, type) {\n  try {\n    const res = await first(ipfs.pin.ls({ paths: cid, type }))\n\n    return Boolean(res)\n  } catch (/** @type {any} */ err) {\n    return false\n  }\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/ping/index.js",
    "content": "import { createSuite } from '../utils/suite.js'\nimport { testPing } from './ping.js'\n\nconst tests = {\n  ping: testPing\n}\n\nexport default createSuite(tests)\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/ping/ping.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport all from 'it-all'\nimport { isWebWorker } from 'ipfs-utils/src/env.js'\nimport { peerIdFromString } from '@libp2p/peer-id'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testPing (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.ping', function () {\n    this.timeout(60 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfsA\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfsB\n    /** @type {import('ipfs-core-types/src/root').IDResult} */\n    let nodeBId\n\n    before(async () => {\n      ipfsA = (await factory.spawn({ type: 'proc' })).api\n      // webworkers are not dialable because webrtc is not available\n      ipfsB = (await factory.spawn({ type: isWebWorker ? 'go' : undefined })).api\n      nodeBId = await ipfsB.id()\n      await ipfsA.swarm.connect(nodeBId.addresses[0])\n    })\n\n    after(() => factory.clean())\n\n    it('should send the specified number of packets', async () => {\n      const count = 3\n      const responses = await all(ipfsA.ping(nodeBId.id, { count }))\n      expect(responses.length).to.be.ok()\n      expect(responses[0].success).to.be.true()\n    })\n\n    it('should fail when pinging a peer that is not available', () => {\n      const notAvailablePeerId = peerIdFromString('QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn')\n      const count = 2\n\n      return expect(all(ipfsA.ping(notAvailablePeerId, { count }))).to.eventually.be.rejected()\n    })\n\n    it('can ping without options', async () => {\n      const res = await all(ipfsA.ping(nodeBId.id))\n      expect(res.length).to.be.ok()\n      expect(res[0].success).to.be.true()\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/ping/utils.js",
    "content": "import { expect } from 'aegir/chai'\n\n/**\n * @param {*} obj\n */\nexport function expectIsPingResponse (obj) {\n  expect(obj).to.have.a.property('success')\n  expect(obj).to.have.a.property('time')\n  expect(obj).to.have.a.property('text')\n  expect(obj.success).to.be.a('boolean')\n  expect(obj.time).to.be.a('number')\n  expect(obj.text).to.be.a('string')\n}\n\n/**\n * Determine if a ping response object is a pong, or something else, like a status message\n *\n * @param {*} pingResponse\n */\nexport function isPong (pingResponse) {\n  return Boolean(pingResponse && pingResponse.success && !pingResponse.text)\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/pubsub/index.js",
    "content": "import { createSuite } from '../utils/suite.js'\nimport { testPublish } from './publish.js'\nimport { testSubscribe } from './subscribe.js'\nimport { testUnsubscribe } from './unsubscribe.js'\nimport { testPeers } from './peers.js'\nimport { testLs } from './ls.js'\n\nconst tests = {\n  publish: testPublish,\n  subscribe: testSubscribe,\n  unsubscribe: testUnsubscribe,\n  peers: testPeers,\n  ls: testLs\n}\n\nexport default createSuite(tests)\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/pubsub/ls.js",
    "content": "/* eslint-env mocha */\n\nimport { getTopic } from './utils.js'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport delay from 'delay'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testLs (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.pubsub.ls', function () {\n    this.timeout(80 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n    /** @type {string[]} */\n    let subscribedTopics = []\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    afterEach(async () => {\n      for (let i = 0; i < subscribedTopics.length; i++) {\n        await ipfs.pubsub.unsubscribe(subscribedTopics[i])\n      }\n      subscribedTopics = []\n      await delay(100)\n    })\n\n    after(() => factory.clean())\n\n    it('should return an empty list when no topics are subscribed', async () => {\n      const topics = await ipfs.pubsub.ls()\n      expect(topics.length).to.equal(0)\n    })\n\n    it('should return a list with 1 subscribed topic', async () => {\n      const sub1 = () => {}\n      const topic = getTopic()\n      subscribedTopics = [topic]\n\n      await ipfs.pubsub.subscribe(topic, sub1)\n      const topics = await ipfs.pubsub.ls()\n      expect(topics).to.be.eql([topic])\n    })\n\n    it('should return a list with 3 subscribed topics', async () => {\n      const topics = [{\n        name: 'one',\n        handler () {}\n      }, {\n        name: 'two',\n        handler () {}\n      }, {\n        name: 'three',\n        handler () {}\n      }]\n\n      subscribedTopics = topics.map(t => t.name)\n\n      for (let i = 0; i < topics.length; i++) {\n        await ipfs.pubsub.subscribe(topics[i].name, topics[i].handler)\n      }\n\n      const list = await ipfs.pubsub.ls()\n      expect(list.sort()).to.eql(topics.map(t => t.name).sort())\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/pubsub/peers.js",
    "content": "/* eslint-env mocha */\n\nimport { waitForPeers, getTopic } from './utils.js'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport delay from 'delay'\nimport { isWebWorker } from 'ipfs-utils/src/env.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testPeers (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.pubsub.peers', function () {\n    this.timeout(80 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs1\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs2\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs3\n    /** @type {string[]} */\n    let subscribedTopics = []\n    /** @type {import('ipfs-core-types/src/root').IDResult} */\n    let ipfs2Id\n    /** @type {import('ipfs-core-types/src/root').IDResult} */\n    let ipfs3Id\n\n    before(async () => {\n      ipfs1 = (await factory.spawn()).api\n      // webworkers are not dialable because webrtc is not available\n      ipfs2 = (await factory.spawn({ type: isWebWorker ? 'js' : undefined })).api\n      ipfs3 = (await factory.spawn({ type: isWebWorker ? 'js' : undefined })).api\n\n      ipfs2Id = await ipfs2.id()\n      ipfs3Id = await ipfs3.id()\n\n      const ipfs2Addr = ipfs2Id.addresses\n        .find(ma => ma.nodeAddress().address === '127.0.0.1')\n      const ipfs3Addr = ipfs3Id.addresses\n        .find(ma => ma.nodeAddress().address === '127.0.0.1')\n\n      if (!ipfs2Addr || !ipfs3Addr) {\n        throw new Error('Could not find addrs')\n      }\n\n      await ipfs1.swarm.connect(ipfs2Addr)\n      await ipfs1.swarm.connect(ipfs3Addr)\n      await ipfs2.swarm.connect(ipfs3Addr)\n    })\n\n    afterEach(async () => {\n      const nodes = [ipfs1, ipfs2, ipfs3]\n      for (let i = 0; i < subscribedTopics.length; i++) {\n        const topic = subscribedTopics[i]\n        await Promise.all(nodes.map(ipfs => ipfs.pubsub.unsubscribe(topic)))\n      }\n      subscribedTopics = []\n      await delay(100)\n    })\n\n    after(() => factory.clean())\n\n    it('should not error when not subscribed to a topic', async () => {\n      const topic = getTopic()\n      const peers = await ipfs1.pubsub.peers(topic)\n      expect(peers).to.exist()\n      // Should be empty() but as mentioned below go-ipfs returns more than it should\n      // expect(peers).to.be.empty()\n    })\n\n    it('should not return extra peers', async () => {\n      // Currently go-ipfs returns peers that have not been\n      // subscribed to the topic. Enable when go-ipfs has been fixed\n      const sub1 = () => {}\n      const sub2 = () => {}\n      const sub3 = () => {}\n\n      const topic = getTopic()\n      const topicOther = topic + 'different topic'\n\n      subscribedTopics = [topic, topicOther]\n\n      await ipfs1.pubsub.subscribe(topic, sub1)\n      await ipfs2.pubsub.subscribe(topicOther, sub2)\n      await ipfs3.pubsub.subscribe(topicOther, sub3)\n\n      const peers = await ipfs1.pubsub.peers(topic)\n      expect(peers).to.be.empty()\n    })\n\n    it('should return peers for a topic - one peer', async () => {\n      // Currently go-ipfs returns peers that have not been\n      // subscribed to the topic. Enable when go-ipfs has been fixed\n      const sub1 = () => {}\n      const sub2 = () => {}\n      const sub3 = () => {}\n      const topic = getTopic()\n\n      subscribedTopics = [topic]\n\n      await ipfs1.pubsub.subscribe(topic, sub1)\n      await ipfs2.pubsub.subscribe(topic, sub2)\n      await ipfs3.pubsub.subscribe(topic, sub3)\n\n      await waitForPeers(ipfs1, topic, [ipfs2Id.id], 30000)\n    })\n\n    it('should return peers for a topic - multiple peers', async () => {\n      const sub1 = () => {}\n      const sub2 = () => {}\n      const sub3 = () => {}\n      const topic = getTopic()\n\n      subscribedTopics = [topic]\n\n      await ipfs1.pubsub.subscribe(topic, sub1)\n      await ipfs2.pubsub.subscribe(topic, sub2)\n      await ipfs3.pubsub.subscribe(topic, sub3)\n\n      await waitForPeers(ipfs1, topic, [ipfs2Id.id, ipfs3Id.id], 30000)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/pubsub/publish.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { nanoid } from 'nanoid'\nimport { getTopic } from './utils.js'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport pWaitFor from 'p-wait-for'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n * @typedef {import('ipfs-core-types').IPFS} IPFS\n */\n\n/**\n * @param {string} topic\n * @param {IPFS} ipfs\n * @param {IPFS} remote\n */\nasync function waitForRemoteToBeSubscribed (topic, ipfs, remote) {\n  await remote.pubsub.subscribe(topic, () => {})\n  const remoteId = await remote.id()\n\n  // wait for remote to be subscribed to topic\n  await pWaitFor(async () => {\n    const peers = await ipfs.pubsub.peers(topic)\n\n    return peers.map(p => p.toString()).includes(remoteId.id.toString())\n  })\n}\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testPublish (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.pubsub.publish', function () {\n    this.timeout(80 * 1000)\n\n    /** @type {IPFS} */\n    let ipfs\n\n    /** @type {IPFS} */\n    let remote\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n      remote = (await factory.spawn()).api\n\n      // ensure we have peers to allow publishing\n      const remoteId = await remote.id()\n      await ipfs.swarm.connect(remoteId.addresses[0])\n    })\n\n    after(() => factory.clean())\n\n    it('should fail with undefined msg', async () => {\n      const topic = getTopic()\n\n      await waitForRemoteToBeSubscribed(topic, ipfs, remote)\n\n      // @ts-expect-error invalid parameter\n      await expect(ipfs.pubsub.publish(topic)).to.eventually.be.rejected()\n    })\n\n    it('should publish message from buffer', async () => {\n      const topic = getTopic()\n\n      await waitForRemoteToBeSubscribed(topic, ipfs, remote)\n\n      return ipfs.pubsub.publish(topic, uint8ArrayFromString(nanoid()))\n    })\n\n    it('should publish 10 times within time limit', async () => {\n      const count = 10\n      const topic = getTopic()\n\n      await waitForRemoteToBeSubscribed(topic, ipfs, remote)\n\n      for (let i = 0; i < count; i++) {\n        await ipfs.pubsub.publish(topic, uint8ArrayFromString(nanoid()))\n      }\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/pubsub/subscribe.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\nimport { nanoid } from 'nanoid'\nimport { pushable } from 'it-pushable'\nimport all from 'it-all'\nimport { waitForPeers, getTopic } from './utils.js'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport delay from 'delay'\nimport { isWebWorker, isNode } from 'ipfs-utils/src/env.js'\nimport sinon from 'sinon'\nimport defer from 'p-defer'\nimport pWaitFor from 'p-wait-for'\nimport { isPeerId } from '@libp2p/interface-peer-id'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n * @typedef {import('@libp2p/interface-pubsub').Message} Message\n * @typedef {import('it-pushable').Pushable<Message>} Pushable\n * @typedef {import('p-defer').DeferredPromise<Message>} DeferredMessagePromise\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testSubscribe (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.pubsub.subscribe', function () {\n    this.timeout(80 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs1\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs2\n    /** @type {string} */\n    let topic\n    /** @type {string[]} */\n    let subscribedTopics = []\n    /** @type {import('ipfs-core-types/src/root').IDResult} */\n    let ipfs1Id\n    /** @type {import('ipfs-core-types/src/root').IDResult} */\n    let ipfs2Id\n\n    beforeEach(async () => {\n      ipfs1 = (await factory.spawn()).api\n\n      // webworkers are not dialable because webrtc is not available\n      ipfs2 = (await factory.spawn({ type: isWebWorker ? 'js' : undefined })).api\n\n      ipfs1Id = await ipfs1.id()\n      ipfs2Id = await ipfs2.id()\n\n      topic = getTopic()\n      subscribedTopics = [topic]\n    })\n\n    afterEach(async () => {\n      const nodes = [ipfs1, ipfs2]\n      for (let i = 0; i < subscribedTopics.length; i++) {\n        const topic = subscribedTopics[i]\n        await Promise.all(nodes.map(ipfs => ipfs.pubsub.unsubscribe(topic)))\n      }\n      subscribedTopics = []\n      await delay(100)\n\n      await factory.clean()\n    })\n\n    describe('single node', () => {\n      it('should subscribe to one topic', async () => {\n        /** @type {import('p-defer').DeferredPromise<Message>} */\n        const deferred = defer()\n\n        await ipfs1.pubsub.subscribe(topic, msg => {\n          deferred.resolve(msg)\n        })\n\n        await ipfs1.pubsub.publish(topic, uint8ArrayFromString('hi'))\n\n        const msg = await deferred.promise\n\n        if (msg.type !== 'signed') {\n          throw new Error('Message was not signed')\n        }\n\n        expect(uint8ArrayToString(msg.data)).to.equal('hi')\n        expect(msg).to.have.property('sequenceNumber')\n        expect(msg.sequenceNumber).to.be.a('BigInt')\n        expect(msg.topic).to.eq(topic)\n        expect(isPeerId(msg.from)).to.be.true()\n        expect(msg.from.toString()).to.equal(ipfs1Id.id.toString())\n      })\n\n      it('should subscribe to one topic with options', async () => {\n        const msgStream = pushable({ objectMode: true })\n\n        await ipfs1.pubsub.subscribe(topic, msg => {\n          msgStream.push(msg)\n          msgStream.end()\n        }, {})\n\n        await ipfs1.pubsub.publish(topic, uint8ArrayFromString('hi'))\n\n        for await (const msg of msgStream) {\n          expect(uint8ArrayToString(msg.data)).to.equal('hi')\n          expect(msg).to.have.property('sequenceNumber')\n          expect(msg.sequenceNumber).to.be.a('bigint')\n          expect(msg.topic).to.eq(topic)\n          expect(msg.from.toString()).to.equal(ipfs1Id.id.toString())\n        }\n      })\n\n      it('should subscribe to topic multiple times with different handlers', async () => {\n        /** @type {import('p-defer').DeferredPromise<Message>} */\n        const msgStream1 = defer()\n        /** @type {import('p-defer').DeferredPromise<Message>} */\n        const msgStream2 = defer()\n\n        /** @type {import('@libp2p/interfaces/events').EventHandler<Message>} */\n        const handler1 = msg => {\n          msgStream1.resolve(msg)\n        }\n        /** @type {import('@libp2p/interfaces/events').EventHandler<Message>} */\n        const handler2 = msg => {\n          msgStream2.resolve(msg)\n        }\n\n        await Promise.all([\n          ipfs1.pubsub.subscribe(topic, handler1),\n          ipfs1.pubsub.subscribe(topic, handler2)\n        ])\n\n        await ipfs1.pubsub.publish(topic, uint8ArrayFromString('hello'))\n\n        const handler1Msg = await msgStream1.promise\n        expect(uint8ArrayToString(handler1Msg.data)).to.eql('hello')\n\n        const handler2Msg = await msgStream2.promise\n        expect(uint8ArrayToString(handler2Msg.data)).to.eql('hello')\n\n        await ipfs1.pubsub.unsubscribe(topic, handler1)\n        await delay(100)\n\n        // Still subscribed as there is one listener left\n        expect(await ipfs1.pubsub.ls()).to.eql([topic])\n\n        await ipfs1.pubsub.unsubscribe(topic, handler2)\n        await delay(100)\n\n        // Now all listeners are gone no subscription anymore\n        expect(await ipfs1.pubsub.ls()).to.eql([])\n      })\n\n      it('should allow discover option to be passed', async () => {\n        const msgStream = pushable({ objectMode: true })\n\n        await ipfs1.pubsub.subscribe(topic, msg => {\n          msgStream.push(msg)\n          msgStream.end()\n        }, { discover: true })\n\n        await ipfs1.pubsub.publish(topic, uint8ArrayFromString('hi'))\n\n        for await (const msg of msgStream) {\n          expect(uint8ArrayToString(msg.data)).to.eql('hi')\n        }\n      })\n    })\n\n    describe('multiple connected nodes', () => {\n      beforeEach(() => {\n        if (ipfs1.pubsub.setMaxListeners) {\n          ipfs1.pubsub.setMaxListeners(100)\n        }\n\n        if (ipfs2.pubsub.setMaxListeners) {\n          ipfs2.pubsub.setMaxListeners(100)\n        }\n\n        const ipfs2Addr = ipfs2Id.addresses\n          .find(ma => ma.nodeAddress().address === '127.0.0.1')\n\n        if (!ipfs2Addr) {\n          throw new Error('No address found')\n        }\n\n        return ipfs1.swarm.connect(ipfs2Addr)\n      })\n\n      it('should receive messages from a different node with floodsub', async function () {\n        if (!isNode) {\n          return this.skip()\n        }\n\n        const expectedString = 'should receive messages from a different node with floodsub'\n        const topic = `floodsub-${nanoid()}`\n        const ipfs1 = (await factory.spawn({\n          test: true,\n          ipfsOptions: {\n            config: {\n              Pubsub: {\n                Router: 'floodsub'\n              }\n            }\n          }\n        })).api\n        const ipfs1Id = await ipfs1.id()\n        const ipfs2 = (await factory.spawn({\n          type: isWebWorker ? 'go' : undefined,\n          test: true,\n          ipfsOptions: {\n            config: {\n              Pubsub: {\n                Router: 'floodsub'\n              }\n            }\n          }\n        })).api\n        const ipfs2Id = await ipfs2.id()\n        await ipfs1.swarm.connect(ipfs2Id.addresses[0])\n\n        /** @type {DeferredMessagePromise} */\n        const msgStream1 = defer()\n        /** @type {DeferredMessagePromise} */\n        const msgStream2 = defer()\n\n        /** @type {import('@libp2p/interfaces/events').EventHandler<Message>} */\n        const sub1 = msg => {\n          msgStream1.resolve(msg)\n        }\n        /** @type {import('@libp2p/interfaces/events').EventHandler<Message>} */\n        const sub2 = msg => {\n          msgStream2.resolve(msg)\n        }\n\n        await Promise.all([\n          ipfs1.pubsub.subscribe(topic, sub1),\n          ipfs2.pubsub.subscribe(topic, sub2)\n        ])\n\n        await Promise.all([\n          waitForPeers(ipfs2, topic, [ipfs1Id.id], 30000),\n          waitForPeers(ipfs1, topic, [ipfs2Id.id], 30000)\n        ])\n\n        await ipfs2.pubsub.publish(topic, uint8ArrayFromString(expectedString))\n\n        const sub1Msg = await msgStream1.promise\n\n        if (sub1Msg.type !== 'signed') {\n          throw new Error('Message was not signed')\n        }\n\n        expect(uint8ArrayToString(sub1Msg.data)).to.be.eql(expectedString)\n        expect(sub1Msg.from.toString()).to.eql(ipfs2Id.id.toString())\n\n        const sub2Msg = await msgStream2.promise\n\n        if (sub2Msg.type !== 'signed') {\n          throw new Error('Message was not signed')\n        }\n\n        expect(uint8ArrayToString(sub2Msg.data)).to.be.eql(expectedString)\n        expect(sub2Msg.from.toString()).to.eql(ipfs2Id.id.toString())\n      })\n\n      it('should receive messages from a different node', async () => {\n        const expectedString = 'hello from the other side'\n\n        /** @type {DeferredMessagePromise} */\n        const msgStream1 = defer()\n        /** @type {DeferredMessagePromise} */\n        const msgStream2 = defer()\n\n        /** @type {import('@libp2p/interfaces/events').EventHandler<Message>} */\n        const sub1 = msg => {\n          msgStream1.resolve(msg)\n        }\n        /** @type {import('@libp2p/interfaces/events').EventHandler<Message>} */\n        const sub2 = msg => {\n          msgStream2.resolve(msg)\n        }\n\n        await Promise.all([\n          ipfs1.pubsub.subscribe(topic, sub1),\n          ipfs2.pubsub.subscribe(topic, sub2)\n        ])\n\n        await waitForPeers(ipfs2, topic, [ipfs1Id.id], 30000)\n        await delay(5000) // gossipsub need this delay https://github.com/libp2p/go-libp2p-pubsub/issues/331\n        await ipfs2.pubsub.publish(topic, uint8ArrayFromString(expectedString))\n\n        const sub1Msg = await msgStream1.promise\n\n        if (sub1Msg.type !== 'signed') {\n          throw new Error('Message was not signed')\n        }\n\n        expect(uint8ArrayToString(sub1Msg.data)).to.be.eql(expectedString)\n        expect(sub1Msg.from.toString()).to.eql(ipfs2Id.id.toString())\n\n        const sub2Msg = await msgStream2.promise\n\n        if (sub2Msg.type !== 'signed') {\n          throw new Error('Message was not signed')\n        }\n\n        expect(uint8ArrayToString(sub2Msg.data)).to.be.eql(expectedString)\n        expect(sub2Msg.from.toString()).to.eql(ipfs2Id.id.toString())\n      })\n\n      it('should round trip a non-utf8 binary buffer', async () => {\n        const expectedHex = 'a36161636179656162830103056164a16466666666f4'\n        const buffer = uint8ArrayFromString(expectedHex, 'base16')\n\n        /** @type {DeferredMessagePromise} */\n        const msgStream1 = defer()\n        /** @type {DeferredMessagePromise} */\n        const msgStream2 = defer()\n\n        /** @type {import('@libp2p/interfaces/events').EventHandler<Message>} */\n        const sub1 = msg => {\n          msgStream1.resolve(msg)\n        }\n        /** @type {import('@libp2p/interfaces/events').EventHandler<Message>} */\n        const sub2 = msg => {\n          msgStream2.resolve(msg)\n        }\n\n        await Promise.all([\n          ipfs1.pubsub.subscribe(topic, sub1),\n          ipfs2.pubsub.subscribe(topic, sub2)\n        ])\n\n        await waitForPeers(ipfs2, topic, [ipfs1Id.id], 30000)\n        await delay(5000) // gossipsub need this delay https://github.com/libp2p/go-libp2p-pubsub/issues/331\n        await ipfs2.pubsub.publish(topic, buffer)\n\n        const sub1Msg = await msgStream1.promise\n\n        if (sub1Msg.type !== 'signed') {\n          throw new Error('Message was not signed')\n        }\n\n        expect(uint8ArrayToString(sub1Msg.data, 'base16')).to.be.eql(expectedHex)\n        expect(sub1Msg.from.toString()).to.eql(ipfs2Id.id.toString())\n\n        const sub2Msg = await msgStream2.promise\n\n        if (sub2Msg.type !== 'signed') {\n          throw new Error('Message was not signed')\n        }\n\n        expect(uint8ArrayToString(sub2Msg.data, 'base16')).to.be.eql(expectedHex)\n        expect(sub2Msg.from.toString()).to.eql(ipfs2Id.id.toString())\n      })\n\n      it('should receive multiple messages', async () => {\n        const outbox = ['hello', 'world', 'this', 'is', 'pubsub']\n\n        const msgStream1 = pushable({ objectMode: true })\n        const msgStream2 = pushable({ objectMode: true })\n\n        let sub1Called = 0\n        /** @type {import('@libp2p/interfaces/events').EventHandler<Message>} */\n        const sub1 = msg => {\n          msgStream1.push(msg)\n          sub1Called++\n          if (sub1Called === outbox.length) msgStream1.end()\n        }\n\n        let sub2Called = 0\n        /** @type {import('@libp2p/interfaces/events').EventHandler<Message>} */\n        const sub2 = msg => {\n          msgStream2.push(msg)\n          sub2Called++\n          if (sub2Called === outbox.length) msgStream2.end()\n        }\n\n        await Promise.all([\n          ipfs1.pubsub.subscribe(topic, sub1),\n          ipfs2.pubsub.subscribe(topic, sub2)\n        ])\n\n        await waitForPeers(ipfs2, topic, [ipfs1Id.id], 30000)\n        await delay(5000) // gossipsub need this delay https://github.com/libp2p/go-libp2p-pubsub/issues/331\n\n        for (let i = 0; i < outbox.length; i++) {\n          await ipfs2.pubsub.publish(topic, uint8ArrayFromString(outbox[i]))\n        }\n\n        const sub1Msgs = await all(msgStream1)\n        sub1Msgs.forEach(msg => expect(msg.from.toString()).to.eql(ipfs2Id.id.toString()))\n        const inbox1 = sub1Msgs.map(msg => uint8ArrayToString(msg.data))\n        expect(inbox1.sort()).to.eql(outbox.sort())\n\n        const sub2Msgs = await all(msgStream2)\n        sub2Msgs.forEach(msg => expect(msg.from.toString()).to.eql(ipfs2Id.id.toString()))\n        const inbox2 = sub2Msgs.map(msg => uint8ArrayToString(msg.data))\n        expect(inbox2.sort()).to.eql(outbox.sort())\n      })\n\n      it.skip('should send/receive 100 messages', async function () {\n        this.timeout(2 * 60 * 1000)\n\n        const msgBase = 'msg - '\n        const count = 100\n        const msgStream = pushable({ objectMode: true })\n\n        let subCalled = 0\n        /** @type {import('@libp2p/interfaces/events').EventHandler<Message>} */\n        const sub = msg => {\n          msgStream.push(msg)\n          subCalled++\n          if (subCalled === count) msgStream.end()\n        }\n\n        await Promise.all([\n          ipfs1.pubsub.subscribe(topic, sub),\n          ipfs2.pubsub.subscribe(topic, () => {})\n        ])\n\n        await waitForPeers(ipfs1, topic, [ipfs2Id.id], 30000)\n        await delay(5000) // gossipsub need this delay https://github.com/libp2p/go-libp2p-pubsub/issues/331\n        const startTime = new Date().getTime()\n\n        for (let i = 0; i < count; i++) {\n          const msgData = uint8ArrayFromString(msgBase + i)\n          await ipfs2.pubsub.publish(topic, msgData)\n        }\n\n        const msgs = await all(msgStream)\n        const duration = new Date().getTime() - startTime\n        const opsPerSec = Math.floor(count / (duration / 1000))\n\n        // eslint-disable-next-line\n        console.log(`Send/Receive 100 messages took: ${duration} ms, ${opsPerSec} ops / s`)\n\n        msgs.forEach(msg => {\n          expect(msg.from.toString()).to.eql(ipfs2Id.id.toString())\n          expect(uint8ArrayToString(msg.data).startsWith(msgBase)).to.be.true()\n        })\n      })\n\n      it('should receive messages from a different node on lots of topics', async () => {\n        this.timeout(5 * 60 * 1000)\n\n        const numTopics = 20\n        const topics = []\n        const expectedStrings = []\n        const msgStreams = []\n\n        for (let i = 0; i < numTopics; i++) {\n          const topic = `pubsub-topic-${Math.random()}`\n          topics.push(topic)\n\n          const msgStream1 = pushable({ objectMode: true })\n          const msgStream2 = pushable({ objectMode: true })\n\n          msgStreams.push({\n            msgStream1,\n            msgStream2\n          })\n\n          /** @type {import('@libp2p/interfaces/events').EventHandler<Message>} */\n          const sub1 = msg => {\n            msgStream1.push(msg)\n            msgStream1.end()\n          }\n          /** @type {import('@libp2p/interfaces/events').EventHandler<Message>} */\n          const sub2 = msg => {\n            msgStream2.push(msg)\n            msgStream2.end()\n          }\n\n          await Promise.all([\n            ipfs1.pubsub.subscribe(topic, sub1),\n            ipfs2.pubsub.subscribe(topic, sub2)\n          ])\n\n          await waitForPeers(ipfs2, topic, [ipfs1Id.id], 30000)\n        }\n\n        await delay(5000) // gossipsub needs this delay https://github.com/libp2p/go-libp2p-pubsub/issues/331\n\n        for (let i = 0; i < numTopics; i++) {\n          const expectedString = `hello pubsub ${Math.random()}`\n          expectedStrings.push(expectedString)\n\n          await ipfs2.pubsub.publish(topics[i], uint8ArrayFromString(expectedString))\n        }\n\n        for (let i = 0; i < numTopics; i++) {\n          const [sub1Msg] = await all(msgStreams[i].msgStream1)\n          expect(uint8ArrayToString(sub1Msg.data)).to.equal(expectedStrings[i])\n          expect(sub1Msg.from.toString()).to.eql(ipfs2Id.id.toString())\n\n          const [sub2Msg] = await all(msgStreams[i].msgStream2)\n          expect(uint8ArrayToString(sub2Msg.data)).to.equal(expectedStrings[i])\n          expect(sub2Msg.from.toString()).to.eql(ipfs2Id.id.toString())\n        }\n      })\n\n      it('should unsubscribe multiple handlers', async () => {\n        this.timeout(2 * 60 * 1000)\n\n        const topic = `topic-${Math.random()}`\n\n        const handler1 = sinon.stub()\n        const handler2 = sinon.stub()\n\n        await Promise.all([\n          ipfs1.pubsub.subscribe(topic, sinon.stub()),\n          ipfs2.pubsub.subscribe(topic, handler1),\n          ipfs2.pubsub.subscribe(topic, handler2)\n        ])\n\n        await waitForPeers(ipfs1, topic, [ipfs2Id.id], 30000)\n\n        expect(handler1).to.have.property('callCount', 0)\n        expect(handler2).to.have.property('callCount', 0)\n\n        // await gossipsub heartbeat to rebalance mesh\n        await delay(2000)\n\n        await ipfs1.pubsub.publish(topic, uint8ArrayFromString('hello world 1'))\n\n        // should receive message\n        await pWaitFor(() => {\n          return handler1.callCount === 1 && handler2.callCount === 1\n        })\n\n        // both handlers should be removed\n        await ipfs2.pubsub.unsubscribe(topic)\n\n        await ipfs1.pubsub.publish(topic, uint8ArrayFromString('hello world 2'))\n\n        await delay(1000)\n\n        // should not have received message\n        expect(handler1).to.have.property('callCount', 1)\n        expect(handler2).to.have.property('callCount', 1)\n      })\n\n      it('should unsubscribe individual handlers', async () => {\n        this.timeout(2 * 60 * 1000)\n\n        const topic = `topic-${Math.random()}`\n\n        const handler1 = sinon.stub()\n        const handler2 = sinon.stub()\n\n        await Promise.all([\n          ipfs1.pubsub.subscribe(topic, sinon.stub()),\n          ipfs2.pubsub.subscribe(topic, handler1),\n          ipfs2.pubsub.subscribe(topic, handler2)\n        ])\n\n        await waitForPeers(ipfs1, topic, [ipfs2Id.id], 30000)\n\n        expect(handler1).to.have.property('callCount', 0)\n        expect(handler2).to.have.property('callCount', 0)\n\n        // await gossipsub heartbeat to rebalance mesh\n        await delay(2000)\n\n        await ipfs1.pubsub.publish(topic, uint8ArrayFromString('hello world 1'))\n\n        // should receive message\n        await pWaitFor(() => {\n          return handler1.callCount === 1 && handler2.callCount === 1\n        })\n\n        // only one handler should be removed\n        await ipfs2.pubsub.unsubscribe(topic, handler1)\n        await ipfs1.pubsub.publish(topic, uint8ArrayFromString('hello world 2'))\n\n        await delay(1000)\n\n        // one should receive message\n        await pWaitFor(() => {\n          return handler2.callCount === 2\n        })\n\n        // other should not have received message\n        expect(handler1).to.have.property('callCount', 1)\n      })\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/pubsub/unsubscribe.js",
    "content": "/* eslint-env mocha */\n\nimport { isBrowser, isWebWorker, isElectronRenderer } from 'ipfs-utils/src/env.js'\nimport { getTopic } from './utils.js'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport waitFor from '../utils/wait-for.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n * @typedef {import('@libp2p/interface-pubsub').Message} Message\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testUnsubscribe (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.pubsub.unsubscribe', function () {\n    this.timeout(80 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    // Browser/worker has max ~5 open HTTP requests to the same origin\n    const count = isBrowser || isWebWorker || isElectronRenderer ? 5 : 10\n\n    it(`should subscribe and unsubscribe ${count} times`, async () => {\n      const someTopic = getTopic()\n      /** @type {import('@libp2p/interfaces/events').EventHandler<Message>[]} */\n      const handlers = Array.from(Array(count), () => msg => {})\n\n      for (let i = 0; i < count; i++) {\n        await ipfs.pubsub.subscribe(someTopic, handlers[i])\n      }\n\n      for (let i = 0; i < count; i++) {\n        await ipfs.pubsub.unsubscribe(someTopic, handlers[i])\n      }\n\n      // Unsubscribing in the http client aborts the connection we hold open\n      // but does not wait for it to close so the subscription list sometimes\n      // takes a little time to empty\n      await waitFor(async () => {\n        const subs = await ipfs.pubsub.ls()\n\n        return subs.length === 0\n      }, {\n        interval: 1000,\n        timeout: 30000,\n        name: 'subscriptions to be empty'\n      })\n    })\n\n    it(`should subscribe ${count} handlers and unsubscribe once with no reference to the handlers`, async () => {\n      const someTopic = getTopic()\n      for (let i = 0; i < count; i++) {\n        await ipfs.pubsub.subscribe(someTopic, (msg) => {})\n      }\n      await ipfs.pubsub.unsubscribe(someTopic)\n\n      // Unsubscribing in the http client aborts the connection we hold open\n      // but does not wait for it to close so the subscription list sometimes\n      // takes a little time to empty\n      await waitFor(async () => {\n        const subs = await ipfs.pubsub.ls()\n\n        return subs.length === 0\n      }, {\n        interval: 1000,\n        timeout: 30000,\n        name: 'subscriptions to be empty'\n      })\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/pubsub/utils.js",
    "content": "import { nanoid } from 'nanoid'\nimport delay from 'delay'\n\n/**\n * @typedef {import('@libp2p/interface-peer-id').PeerId} PeerId\n */\n\n/**\n * @param {import('ipfs-core-types').IPFS} ipfs\n * @param {string} topic\n * @param {PeerId[]} peersToWait\n * @param {number} waitForMs\n * @returns\n */\nexport async function waitForPeers (ipfs, topic, peersToWait, waitForMs) {\n  const start = Date.now()\n\n  while (true) {\n    const peers = await ipfs.pubsub.peers(topic)\n    const everyPeerFound = peersToWait.every(p => peers.map(p => p.toString()).includes(p.toString()))\n\n    if (everyPeerFound) {\n      return\n    }\n\n    if (Date.now() > start + waitForMs) {\n      throw new Error(`Timed out waiting for peers to be subscribed to \"${topic}\"`)\n    }\n\n    await delay(10)\n  }\n}\n\nexport function getTopic () {\n  return 'pubsub-tests-' + nanoid()\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/refs-local.js",
    "content": "/* eslint-env mocha */\n\nimport { fixtures } from './utils/index.js'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from './utils/mocha.js'\nimport all from 'it-all'\nimport { importer } from 'ipfs-unixfs-importer'\nimport drain from 'it-drain'\nimport { CID } from 'multiformats/cid'\nimport { equals as uint8ArrayEquals } from 'uint8arrays/equals'\nimport blockstore from './utils/blockstore-adapter.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testRefsLocal (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.refs.local', function () {\n    this.timeout(60 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should get local refs', async function () {\n      /**\n       * @param {string} name\n       */\n      const content = (name) => ({\n        path: `test-folder/${name}`,\n        content: fixtures.directory.files[name]\n      })\n\n      const dirs = [\n        content('pp.txt'),\n        content('holmes.txt')\n      ]\n\n      const imported = await all(importer(dirs, blockstore(ipfs)))\n\n      // otherwise go-ipfs doesn't show them in the local refs\n      await drain(ipfs.pin.addAll(imported.map(i => ({ cid: i.cid }))))\n\n      const refs = await all(ipfs.refs.local())\n      const cids = refs.map(r => r.ref)\n\n      expect(\n        cids.find(cid => {\n          const multihash = CID.parse(cid).multihash.bytes\n\n          return uint8ArrayEquals(imported[0].cid.multihash.bytes, multihash)\n        })\n      ).to.be.ok()\n\n      expect(\n        cids.find(cid => {\n          const multihash = CID.parse(cid).multihash.bytes\n\n          return uint8ArrayEquals(imported[1].cid.multihash.bytes, multihash)\n        })\n      ).to.be.ok()\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/refs.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from './utils/mocha.js'\nimport loadFixture from 'aegir/fixtures'\nimport { CID } from 'multiformats/cid'\nimport all from 'it-all'\nimport drain from 'it-drain'\nimport testTimeout from './utils/test-timeout.js'\nimport * as dagPB from '@ipld/dag-pb'\nimport { UnixFS } from 'ipfs-unixfs'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testRefs (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.refs', function () {\n    this.timeout(60 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n    /** @type {CID} */\n    let pbRootCid\n    /** @type {CID} */\n    let dagRootCid\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    before(async function () {\n      pbRootCid = await loadPbContent(ipfs, getMockObjects())\n    })\n\n    before(async function () {\n      dagRootCid = await loadDagContent(ipfs, getMockObjects())\n    })\n\n    after(() => factory.clean())\n\n    for (const [name, options] of Object.entries(getRefsTests())) {\n      const { path, params, expected, expectError, expectTimeout } = options\n      // eslint-disable-next-line no-loop-func\n      it(name, async function () {\n        this.timeout(20 * 1000)\n\n        // Call out to IPFS\n        const p = (path ? path(pbRootCid) : pbRootCid)\n\n        if (expectTimeout) {\n          return expect(all(ipfs.refs(p, params))).to.eventually.be.rejected\n            .and.be.an.instanceOf(Error)\n            .and.to.have.property('name')\n            .to.eql('TimeoutError')\n        }\n\n        if (expectError) {\n          return expect(all(ipfs.refs(p, params))).to.be.eventually.rejected.and.be.an.instanceOf(Error)\n        }\n\n        const refs = await all(ipfs.refs(p, params))\n        // Sort the refs not to lock-in the iteration order\n        // Check there was no error and the refs match what was expected\n        expect(refs.map(r => r.ref).sort()).to.eql(expected.sort())\n      })\n    }\n\n    it('should respect timeout option when listing refs', () => {\n      return testTimeout(() => drain(ipfs.refs('/ipfs/QmPDqvcuA4AkhBLBuh2y49yhUB98rCnxPxa3eVNC1kAbS1/foo/bar/baz.txt', {\n        timeout: 1\n      })))\n    })\n\n    it('should get refs with cbor links', async function () {\n      this.timeout(20 * 1000)\n\n      // Call out to IPFS\n      const refs = await all(ipfs.refs(`/ipfs/${dagRootCid}`, { recursive: true }))\n      // Check the refs match what was expected\n      expect(refs.map(r => r.ref).sort()).to.eql([\n        'QmPDqvcuA4AkhBLBuh2y49yhUB98rCnxPxa3eVNC1kAbSC',\n        'QmVwtsLUHurA6wUirPSdGeEW5tfBEqenXpeRaqr8XN7bNY',\n        'QmXGL3ZdYV5rNLCfHe1QsFSQGekRFzgbBu1B3XGZ7DV9fd',\n        'QmcSVZRN5E814KkPy4EHnftNAR7htbFvVhRKKqFs4FBwDG',\n        'QmcSVZRN5E814KkPy4EHnftNAR7htbFvVhRKKqFs4FBwDG',\n        'QmdBcHbK7uDQav8YrHsfKju3EKn48knxjd96KRMFs3gtS9',\n        'QmeX96opBHZHLySMFoNiWS5msxjyX6rqtr3Rr1u7uxn7zJ',\n        'Qmf8MwTnY7VdcnF8WcoJ3GB24NmNd1HsGzuEWCtUYDP38x',\n        'bafyreiagelcmhfn33zuslkdo7fkes3dzcr2nju6meh75zm6vqklfqiojam',\n        'bafyreic2f6adq5tqnbrvwiqc3jkz2cf4tz3cz2rp6plpij2qaoufgsxwmi',\n        'bafyreidoqtyvflv5v4c3gd3izxvpq4flke55ayurbrnhsxh7z5wwjc6v6e',\n        'bafyreifs2ub2lnq6n2quqbi3zb5homs5iqlmm77b3am252cqzxiu7phwpy'\n      ])\n    })\n  })\n}\n\nfunction getMockObjects () {\n  return {\n    animals: {\n      land: {\n        'african.txt': loadFixture('test/fixtures/refs-test/animals/land/african.txt', 'interface-ipfs-core'),\n        'americas.txt': loadFixture('test/fixtures/refs-test/animals/land/americas.txt', 'interface-ipfs-core'),\n        'australian.txt': loadFixture('test/fixtures/refs-test/animals/land/australian.txt', 'interface-ipfs-core')\n      },\n      sea: {\n        'atlantic.txt': loadFixture('test/fixtures/refs-test/animals/sea/atlantic.txt', 'interface-ipfs-core'),\n        'indian.txt': loadFixture('test/fixtures/refs-test/animals/sea/indian.txt', 'interface-ipfs-core')\n      }\n    },\n    fruits: {\n      'tropical.txt': loadFixture('test/fixtures/refs-test/fruits/tropical.txt', 'interface-ipfs-core')\n    },\n    'atlantic-animals': loadFixture('test/fixtures/refs-test/atlantic-animals', 'interface-ipfs-core'),\n    'mushroom.txt': loadFixture('test/fixtures/refs-test/mushroom.txt', 'interface-ipfs-core')\n  }\n}\n\n/**\n * @returns {Record<string, { path?: (cid: CID) => string | string[], params: { edges?: boolean, format?: string, recursive?: boolean, unique?: boolean, maxDepth?: number, timeout?: number }, expected: string[], expectError?: boolean, expectTimeout?: boolean }>}\n */\nfunction getRefsTests () {\n  return {\n    'should print added files': {\n      params: {},\n      expected: [\n        'QmYEJ7qQNZUvBnv4SZ3rEbksagaan3sGvnUq948vSG8Z34',\n        'QmUXzZKa3xhTauLektUiK4GiogHskuz1c57CnnoP4TgYJD',\n        'QmYLvZrFn8KE2bcJ9UFhthScBVbbcXEgkJnnCBeKWYkpuQ',\n        'QmRfqT4uTUgFXhWbfBZm6eZxi2FQ8pqYK5tcWRyTZ7RcgY'\n      ]\n    },\n\n    'should print files in edges format': {\n      params: { edges: true },\n      expected: [\n        'Qmd5MhNjx3NSZm3L2QKG1TFvqkTRbtZwGJinqEfqpfHH7s -> QmYEJ7qQNZUvBnv4SZ3rEbksagaan3sGvnUq948vSG8Z34',\n        'Qmd5MhNjx3NSZm3L2QKG1TFvqkTRbtZwGJinqEfqpfHH7s -> QmUXzZKa3xhTauLektUiK4GiogHskuz1c57CnnoP4TgYJD',\n        'Qmd5MhNjx3NSZm3L2QKG1TFvqkTRbtZwGJinqEfqpfHH7s -> QmYLvZrFn8KE2bcJ9UFhthScBVbbcXEgkJnnCBeKWYkpuQ',\n        'Qmd5MhNjx3NSZm3L2QKG1TFvqkTRbtZwGJinqEfqpfHH7s -> QmRfqT4uTUgFXhWbfBZm6eZxi2FQ8pqYK5tcWRyTZ7RcgY'\n      ]\n    },\n\n    'should print files in custom format': {\n      params: { format: '<linkname>: <src> => <dst>' },\n      expected: [\n        'animals: Qmd5MhNjx3NSZm3L2QKG1TFvqkTRbtZwGJinqEfqpfHH7s => QmYEJ7qQNZUvBnv4SZ3rEbksagaan3sGvnUq948vSG8Z34',\n        'atlantic-animals: Qmd5MhNjx3NSZm3L2QKG1TFvqkTRbtZwGJinqEfqpfHH7s => QmUXzZKa3xhTauLektUiK4GiogHskuz1c57CnnoP4TgYJD',\n        'fruits: Qmd5MhNjx3NSZm3L2QKG1TFvqkTRbtZwGJinqEfqpfHH7s => QmYLvZrFn8KE2bcJ9UFhthScBVbbcXEgkJnnCBeKWYkpuQ',\n        'mushroom.txt: Qmd5MhNjx3NSZm3L2QKG1TFvqkTRbtZwGJinqEfqpfHH7s => QmRfqT4uTUgFXhWbfBZm6eZxi2FQ8pqYK5tcWRyTZ7RcgY'\n      ]\n    },\n\n    'should follow a path, <hash>/<subdir>': {\n      path: (cid) => `/ipfs/${cid}/animals`,\n      params: { format: '<linkname>' },\n      expected: [\n        'land',\n        'sea'\n      ]\n    },\n\n    'should follow a path, <hash>/<subdir>/<subdir>': {\n      path: (cid) => `/ipfs/${cid}/animals/land`,\n      params: { format: '<linkname>' },\n      expected: [\n        'african.txt',\n        'americas.txt',\n        'australian.txt'\n      ]\n    },\n\n    'should follow a path with recursion, <hash>/<subdir>': {\n      path: (cid) => `/ipfs/${cid}/animals`,\n      params: { format: '<linkname>', recursive: true },\n      expected: [\n        'land',\n        'african.txt',\n        'americas.txt',\n        'australian.txt',\n        'sea',\n        'atlantic.txt',\n        'indian.txt'\n      ]\n    },\n\n    'should recursively follows folders, -r': {\n      params: { format: '<linkname>', recursive: true },\n      expected: [\n        'animals',\n        'land',\n        'african.txt',\n        'americas.txt',\n        'australian.txt',\n        'sea',\n        'atlantic.txt',\n        'indian.txt',\n        'atlantic-animals',\n        'fruits',\n        'tropical.txt',\n        'mushroom.txt'\n      ]\n    },\n\n    'should get refs with recursive and unique option': {\n      params: { format: '<dst>', recursive: true, unique: true },\n      expected: [\n        'QmRfqT4uTUgFXhWbfBZm6eZxi2FQ8pqYK5tcWRyTZ7RcgY',\n        'QmUXzZKa3xhTauLektUiK4GiogHskuz1c57CnnoP4TgYJD',\n        'QmVX54jfjB8eRxLVxyQSod6b1FyDh7mR4mQie9j97i2Qk3',\n        'QmWEuXAjUGyndgr4MKqMBgzMW36XgPgvitt2jsXgtuc7JE',\n        'QmYEJ7qQNZUvBnv4SZ3rEbksagaan3sGvnUq948vSG8Z34',\n        'QmYLvZrFn8KE2bcJ9UFhthScBVbbcXEgkJnnCBeKWYkpuQ',\n        'Qma5z9bmwPcrWLJxX6Vj6BrcybaFg84c2riNbUKrSVf8h1',\n        'QmbrFTo4s6H23W6wmoZKQC2vSogGeQ4dYiceSqJddzrKVa',\n        'QmdHVR8M4zAdGctnTYq4fyPZjTwwzdcBpGWAfMAhAVfT9n',\n        'Qmf6MrqT2oAve9diagLTMCYFPEcSx7fnUdW3xAjhXm32vo',\n        'QmfP6D9bRV4FEYDL4EHZtZG58kDwDfnzmyjuyK5d1pvzbM'\n      ]\n    },\n\n    'should get refs with max depth of 1': {\n      params: { format: '<linkname>', recursive: true, maxDepth: 1 },\n      expected: [\n        'animals',\n        'atlantic-animals',\n        'fruits',\n        'mushroom.txt'\n      ]\n    },\n\n    'should get refs with max depth of 2': {\n      params: { format: '<linkname>', recursive: true, maxDepth: 2 },\n      expected: [\n        'animals',\n        'land',\n        'sea',\n        'atlantic-animals',\n        'fruits',\n        'tropical.txt',\n        'mushroom.txt'\n      ]\n    },\n\n    'should get refs with max depth of 3': {\n      params: { format: '<linkname>', recursive: true, maxDepth: 3 },\n      expected: [\n        'animals',\n        'land',\n        'african.txt',\n        'americas.txt',\n        'australian.txt',\n        'sea',\n        'atlantic.txt',\n        'indian.txt',\n        'atlantic-animals',\n        'fruits',\n        'tropical.txt',\n        'mushroom.txt'\n      ]\n    },\n\n    'should get refs with max depth of 0': {\n      params: { recursive: true, maxDepth: 0 },\n      expected: []\n    },\n\n    'should follow a path with max depth 1, <hash>/<subdir>': {\n      path: (cid) => `/ipfs/${cid}/animals`,\n      params: { format: '<linkname>', recursive: true, maxDepth: 1 },\n      expected: [\n        'land',\n        'sea'\n      ]\n    },\n\n    'should follow a path with max depth 2, <hash>/<subdir>': {\n      path: (cid) => `/ipfs/${cid}/animals`,\n      params: { format: '<linkname>', recursive: true, maxDepth: 2 },\n      expected: [\n        'land',\n        'african.txt',\n        'americas.txt',\n        'australian.txt',\n        'sea',\n        'atlantic.txt',\n        'indian.txt'\n      ]\n    },\n\n    'should print refs for multiple paths': {\n      path: (cid) => [`/ipfs/${cid}/animals`, `/ipfs/${cid}/fruits`],\n      params: { format: '<linkname>', recursive: true },\n      expected: [\n        'land',\n        'african.txt',\n        'americas.txt',\n        'australian.txt',\n        'sea',\n        'atlantic.txt',\n        'indian.txt',\n        'tropical.txt'\n      ]\n    },\n\n    'should not be able to specify edges and format': {\n      params: { format: '<linkname>', edges: true },\n      expected: [],\n      expectError: true\n    },\n\n    'should print nothing for non-existent hashes': {\n      path: () => 'QmYmW4HiZhotsoSqnv2o1oSssvkRM8b9RweBoH7ao5nki2',\n      params: { timeout: 2000 },\n      expected: ['']\n    }\n  }\n}\n\n/**\n * @typedef {object} Store\n * @property {(data: Uint8Array) => Promise<CID>} putData\n * @property {(links: { name: string, cid: string }[]) => Promise<CID>} putLinks\n */\n\n/**\n * @param {import('ipfs-core-types').IPFS} ipfs\n * @param {any} node\n */\nfunction loadPbContent (ipfs, node) {\n  /**\n   * @type {Store}\n   */\n  const store = {\n    putData: (data) => {\n      return ipfs.block.put(\n        dagPB.encode({\n          Data: data,\n          Links: []\n        })\n      )\n    },\n    putLinks: (links) => {\n      return ipfs.block.put(dagPB.encode({\n        Links: links.map(({ name, cid }) => {\n          return {\n            Name: name,\n            Tsize: 8,\n            Hash: CID.parse(cid)\n          }\n        })\n      }))\n    }\n  }\n  return loadContent(ipfs, store, node)\n}\n\n/**\n * @param {import('ipfs-core-types').IPFS} ipfs\n * @param {any} node\n */\nfunction loadDagContent (ipfs, node) {\n  /**\n   * @type {Store}\n   */\n  const store = {\n    putData: (data) => {\n      const inner = new UnixFS({ type: 'file', data: data })\n      const serialized = dagPB.encode({\n        Data: inner.marshal(),\n        Links: []\n      })\n      return ipfs.block.put(serialized)\n    },\n    putLinks: (links) => {\n      /** @type {Record<string, CID>} */\n      const obj = {}\n      for (const { name, cid } of links) {\n        obj[name] = CID.parse(cid)\n      }\n      return ipfs.dag.put(obj)\n    }\n  }\n  return loadContent(ipfs, store, node)\n}\n\n/**\n * @param {import('ipfs-core-types').IPFS} ipfs\n * @param {Store} store\n * @param {any} node\n * @returns {Promise<CID>}\n */\nasync function loadContent (ipfs, store, node) {\n  if (node instanceof Uint8Array) {\n    return store.putData(node)\n  }\n\n  if (typeof node === 'object') {\n    const entries = Object.entries(node)\n    const sorted = entries.sort((a, b) => {\n      if (a[0] > b[0]) {\n        return 1\n      } else if (a[0] < b[0]) {\n        return -1\n      }\n      return 0\n    })\n\n    const res = await all((async function * () {\n      for (const [name, child] of sorted) {\n        const cid = await loadContent(ipfs, store, child)\n        yield { name, cid: cid && cid.toString() }\n      }\n    })())\n\n    return store.putLinks(res)\n  }\n\n  throw new Error('Please pass either data or object')\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/repo/gc.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport all from 'it-all'\nimport drain from 'it-drain'\nimport { CID } from 'multiformats/cid'\nimport { base64 } from 'multiformats/bases/base64'\n\n/**\n * @param {import('ipfs-core-types').IPFS} ipfs\n */\nasync function getBaseEncodedMultihashes (ipfs) {\n  const refs = await all(ipfs.refs.local())\n\n  return refs.map(r => base64.encode(CID.parse(r.ref).multihash.bytes))\n}\n\n/**\n * @param {import('ipfs-core-types').IPFS} ipfs\n * @param {CID} cid\n */\nasync function shouldHaveRef (ipfs, cid) {\n  return expect(getBaseEncodedMultihashes(ipfs)).to.eventually.include(base64.encode(cid.multihash.bytes))\n}\n\n/**\n * @param {import('ipfs-core-types').IPFS} ipfs\n * @param {CID} cid\n */\nasync function shouldNotHaveRef (ipfs, cid) {\n  return expect(getBaseEncodedMultihashes(ipfs)).to.eventually.not.include(base64.encode(cid.multihash.bytes))\n}\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testGc (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.repo.gc', () => {\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should run garbage collection', async () => {\n      const res = await ipfs.add(uint8ArrayFromString('apples'))\n\n      const pinset = await all(ipfs.pin.ls())\n      expect(pinset.map(obj => obj.cid.toString())).includes(res.cid.toString())\n\n      await ipfs.pin.rm(res.cid)\n      await all(ipfs.repo.gc())\n\n      const finalPinset = await all(ipfs.pin.ls())\n      expect(finalPinset.map(obj => obj.cid.toString())).not.includes(res.cid.toString())\n    })\n\n    it('should clean up unpinned data', async () => {\n      // Add some data. Note: this will implicitly pin the data, which causes\n      // some blocks to be added for the data itself and for the pinning\n      // information that refers to the blocks\n      const addRes = await ipfs.add(uint8ArrayFromString('apples'))\n      const cid = addRes.cid\n\n      // Get the list of local blocks after the add, should be bigger than\n      // the initial list and contain hash\n      await shouldHaveRef(ipfs, cid)\n\n      // Run garbage collection\n      await drain(ipfs.repo.gc())\n\n      // Get the list of local blocks after GC, should still contain the hash,\n      // because the file is still pinned\n      await shouldHaveRef(ipfs, cid)\n\n      // Unpin the data\n      await ipfs.pin.rm(cid)\n\n      // Run garbage collection\n      await all(ipfs.repo.gc())\n\n      // The list of local blocks should no longer contain the hash\n      await shouldNotHaveRef(ipfs, cid)\n    })\n\n    it('should clean up removed MFS files', async () => {\n      // Add a file to MFS\n      await ipfs.files.write('/test', uint8ArrayFromString('oranges'), { create: true })\n      const stats = await ipfs.files.stat('/test')\n      expect(stats.type).to.equal('file')\n\n      // Get the list of local blocks after the add, should be bigger than\n      // the initial list and contain hash\n      await shouldHaveRef(ipfs, stats.cid)\n\n      // Run garbage collection\n      await drain(ipfs.repo.gc())\n\n      // Get the list of local blocks after GC, should still contain the hash,\n      // because the file is in MFS\n      await shouldHaveRef(ipfs, stats.cid)\n\n      // Remove the file\n      await ipfs.files.rm('/test')\n\n      // Run garbage collection\n      await drain(ipfs.repo.gc())\n\n      // The list of local blocks should no longer contain the hash\n      await shouldNotHaveRef(ipfs, stats.cid)\n    })\n\n    it('should clean up block only after unpinned and removed from MFS', async () => {\n      // Add a file to MFS\n      await ipfs.files.write('/test', uint8ArrayFromString('peaches'), { create: true })\n      const stats = await ipfs.files.stat('/test')\n      expect(stats.type).to.equal('file')\n      const mfsFileCid = stats.cid\n\n      // Get the CID of the data in the file\n      const block = await ipfs.block.get(mfsFileCid)\n\n      // Add the data to IPFS (which implicitly pins the data)\n      const addRes = await ipfs.add(block)\n      const dataCid = addRes.cid\n\n      // Get the list of local blocks after the add, should be bigger than\n      // the initial list and contain the data hash\n      await shouldHaveRef(ipfs, dataCid)\n\n      // Run garbage collection\n      await drain(ipfs.repo.gc())\n\n      // Get the list of local blocks after GC, should still contain the hash,\n      // because the file is pinned and in MFS\n      await shouldHaveRef(ipfs, dataCid)\n\n      // Remove the file\n      await ipfs.files.rm('/test')\n\n      // Run garbage collection\n      await drain(ipfs.repo.gc())\n\n      // Get the list of local blocks after GC, should still contain the hash,\n      // because the file is still pinned\n      await shouldNotHaveRef(ipfs, mfsFileCid)\n      await shouldHaveRef(ipfs, dataCid)\n\n      // Unpin the data\n      await ipfs.pin.rm(dataCid)\n\n      // Run garbage collection\n      await drain(ipfs.repo.gc())\n\n      // The list of local blocks should no longer contain the hashes\n      await shouldNotHaveRef(ipfs, mfsFileCid)\n      await shouldNotHaveRef(ipfs, dataCid)\n    })\n\n    it('should clean up indirectly pinned data after recursive pin removal', async () => {\n      // Add some data\n      const addRes = await ipfs.add(uint8ArrayFromString('pears'))\n      const dataCid = addRes.cid\n\n      // Unpin the data\n      await ipfs.pin.rm(dataCid)\n\n      // Create a link to the data from an object\n      const obj = {\n        Data: uint8ArrayFromString('fruit'),\n        Links: [{\n          Name: 'p',\n          Hash: dataCid,\n          Tsize: addRes.size\n        }]\n      }\n\n      // Put the object into IPFS\n      const objCid = await ipfs.object.put(obj)\n\n      // Putting an object doesn't pin it\n      expect((await all(ipfs.pin.ls())).map(p => p.cid.toString())).not.includes(objCid.toString())\n\n      // Get the list of local blocks after the add, should be bigger than\n      // the initial list and contain data and object hash\n      await shouldHaveRef(ipfs, objCid)\n      await shouldHaveRef(ipfs, dataCid)\n\n      // Recursively pin the object\n      await ipfs.pin.add(objCid, { recursive: true })\n\n      // The data should now be indirectly pinned\n      const pins = await all(ipfs.pin.ls())\n      expect(pins.find(p => p.cid.toString() === dataCid.toString())).to.have.property('type', 'indirect')\n\n      // Run garbage collection\n      await drain(ipfs.repo.gc())\n\n      // Get the list of local blocks after GC, should still contain the data\n      // hash, because the data is still (indirectly) pinned\n      await shouldHaveRef(ipfs, objCid)\n      await shouldHaveRef(ipfs, dataCid)\n\n      // Recursively unpin the object\n      await ipfs.pin.rm(objCid.toString())\n\n      // Run garbage collection\n      await drain(ipfs.repo.gc())\n\n      // The list of local blocks should no longer contain the hashes\n      await shouldNotHaveRef(ipfs, objCid)\n      await shouldNotHaveRef(ipfs, dataCid)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/repo/index.js",
    "content": "import { createSuite } from '../utils/suite.js'\nimport { testVersion } from './version.js'\nimport { testStat } from './stat.js'\nimport { testGc } from './gc.js'\n\nconst tests = {\n  version: testVersion,\n  stat: testStat,\n  gc: testGc\n}\n\nexport default createSuite(tests)\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/repo/stat.js",
    "content": "/* eslint-env mocha */\n\nimport { expectIsRepo } from '../stats/utils.js'\nimport { getDescribe, getIt } from '../utils/mocha.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testStat (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.repo.stat', () => {\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should get repo stats', async () => {\n      const res = await ipfs.repo.stat()\n      expectIsRepo(null, res)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/repo/version.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testVersion (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.repo.version', () => {\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should get the repo version', async () => {\n      const version = await ipfs.repo.version()\n      expect(version).to.exist()\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/stats/bitswap.js",
    "content": "/* eslint-env mocha */\n\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { expectIsBitswap } from './utils.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testBitswap (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.stats.bitswap', () => {\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should get bitswap stats', async () => {\n      const res = await ipfs.stats.bitswap()\n      expectIsBitswap(null, res)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/stats/bw.js",
    "content": "/* eslint-env mocha */\n\nimport { expectIsBandwidth } from './utils.js'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport last from 'it-last'\nimport all from 'it-all'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testBw (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.stats.bw', () => {\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should get bandwidth stats ', async () => {\n      const res = await last(ipfs.stats.bw())\n\n      if (!res) {\n        throw new Error('No bw stats returned')\n      }\n\n      expectIsBandwidth(null, res)\n    })\n\n    it('should throw error for invalid interval option', async () => {\n      await expect(all(ipfs.stats.bw({ poll: true, interval: 'INVALID INTERVAL' })))\n        .to.eventually.be.rejected()\n        .with.property('message').that.matches(/invalid duration/)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/stats/index.js",
    "content": "import { createSuite } from '../utils/suite.js'\nimport { testBitswap } from './bitswap.js'\nimport { testBw } from './bw.js'\nimport { testRepo } from './repo.js'\n\nconst tests = {\n  bitswap: testBitswap,\n  bw: testBw,\n  repo: testRepo\n}\n\nexport default createSuite(tests)\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/stats/repo.js",
    "content": "/* eslint-env mocha */\n\nimport { expectIsRepo } from './utils.js'\nimport { getDescribe, getIt } from '../utils/mocha.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testRepo (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.stats.repo', () => {\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should get repo stats', async () => {\n      const res = await ipfs.stats.repo()\n      expectIsRepo(null, res)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/stats/utils.js",
    "content": "import { expect } from 'aegir/chai'\n\n/**\n * @param {any} n\n */\nconst isBigInt = (n) => {\n  return typeof n === 'bigint'\n}\n\n/**\n * @param {Error | null} err\n * @param {import('ipfs-core-types/src/bitswap').Stats} stats\n */\nexport function expectIsBitswap (err, stats) {\n  expect(err).to.not.exist()\n  expect(stats).to.exist()\n  expect(stats).to.have.a.property('provideBufLen')\n  expect(stats).to.have.a.property('wantlist')\n  expect(stats).to.have.a.property('peers')\n  expect(stats).to.have.a.property('blocksReceived')\n  expect(stats).to.have.a.property('dataReceived')\n  expect(stats).to.have.a.property('blocksSent')\n  expect(stats).to.have.a.property('dataSent')\n  expect(stats).to.have.a.property('dupBlksReceived')\n  expect(stats).to.have.a.property('dupDataReceived')\n\n  expect(stats.provideBufLen).to.a('number')\n  expect(stats.wantlist).to.be.an('array')\n  expect(stats.peers).to.be.an('array')\n  expect(isBigInt(stats.blocksReceived)).to.eql(true)\n  expect(isBigInt(stats.dataReceived)).to.eql(true)\n  expect(isBigInt(stats.blocksSent)).to.eql(true)\n  expect(isBigInt(stats.dataSent)).to.eql(true)\n  expect(isBigInt(stats.dupBlksReceived)).to.eql(true)\n  expect(isBigInt(stats.dupDataReceived)).to.eql(true)\n}\n\n/**\n * @param {Error | null} err\n * @param {import('ipfs-core-types/src/stats').BWResult} stats\n */\nexport function expectIsBandwidth (err, stats) {\n  expect(err).to.not.exist()\n  expect(stats).to.exist()\n  expect(stats).to.have.a.property('totalIn')\n  expect(stats).to.have.a.property('totalOut')\n  expect(stats).to.have.a.property('rateIn')\n  expect(stats).to.have.a.property('rateOut')\n  expect(isBigInt(stats.totalIn)).to.eql(true)\n  expect(isBigInt(stats.totalOut)).to.eql(true)\n  expect(stats.rateIn).to.be.a('number')\n  expect(stats.rateOut).to.be.a('number')\n}\n\n/**\n * @param {Error | null} err\n * @param {import('ipfs-core-types/src/repo').StatResult} res\n */\nexport function expectIsRepo (err, res) {\n  expect(err).to.not.exist()\n  expect(res).to.exist()\n  expect(res).to.have.a.property('numObjects')\n  expect(res).to.have.a.property('repoSize')\n  expect(res).to.have.a.property('repoPath')\n  expect(res).to.have.a.property('version')\n  expect(res).to.have.a.property('storageMax')\n  expect(isBigInt(res.numObjects)).to.eql(true)\n  expect(isBigInt(res.repoSize)).to.eql(true)\n  expect(isBigInt(res.storageMax)).to.eql(true)\n  expect(res.repoPath).to.be.a('string')\n  expect(res.version).to.be.a('string')\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/swarm/addrs.js",
    "content": "/* eslint-env mocha */\n\nimport { isMultiaddr } from '@multiformats/multiaddr'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { isWebWorker } from 'ipfs-utils/src/env.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testAddrs (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.swarm.addrs', function () {\n    this.timeout(80 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfsA\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfsB\n    /** @type {import('ipfs-core-types/src/root').IDResult} */\n    let ipfsBId\n\n    before(async () => {\n      ipfsA = (await factory.spawn({ type: 'proc' })).api\n      // webworkers are not dialable because webrtc is not available\n      ipfsB = (await factory.spawn({ type: isWebWorker ? 'go' : undefined })).api\n      ipfsBId = await ipfsB.id()\n      await ipfsA.swarm.connect(ipfsBId.addresses[0])\n    })\n\n    after(() => factory.clean())\n\n    it('should get a list of node addresses', async () => {\n      const peers = await ipfsA.swarm.addrs()\n      expect(peers).to.not.be.empty()\n      expect(peers).to.be.an('array')\n\n      for (const peer of peers) {\n        expect(peer.id).to.be.ok()\n        expect(peer).to.have.a.property('addrs').that.is.an('array')\n\n        for (const ma of peer.addrs) {\n          expect(isMultiaddr(ma)).to.be.true()\n        }\n      }\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/swarm/connect.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { isWebWorker } from 'ipfs-utils/src/env.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testConnect (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.swarm.connect', function () {\n    this.timeout(80 * 1000)\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfsA\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfsB\n    /** @type {import('ipfs-core-types/src/root').IDResult} */\n    let ipfsBId\n\n    before(async () => {\n      ipfsA = (await factory.spawn({ type: 'proc' })).api\n      // webworkers are not dialable because webrtc is not available\n      ipfsB = (await factory.spawn({ type: isWebWorker ? 'go' : undefined })).api\n      ipfsBId = await ipfsB.id()\n    })\n\n    after(() => factory.clean())\n\n    it('should connect to a peer', async () => {\n      let peers\n\n      peers = await ipfsA.swarm.peers()\n      expect(peers.map(p => p.peer.toString())).to.not.include(ipfsBId.id.toString())\n\n      await ipfsA.swarm.connect(ipfsBId.addresses[0])\n\n      peers = await ipfsA.swarm.peers()\n      expect(peers.map(p => p.peer.toString())).to.include(ipfsBId.id.toString())\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/swarm/disconnect.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { isWebWorker } from 'ipfs-utils/src/env.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testDisconnect (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.swarm.disconnect', function () {\n    this.timeout(80 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfsA\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfsB\n    /** @type {import('ipfs-core-types/src/root').IDResult} */\n    let ipfsBId\n\n    before(async () => {\n      ipfsA = (await factory.spawn({ type: 'proc' })).api\n      // webworkers are not dialable because webrtc is not available\n      ipfsB = (await factory.spawn({ type: isWebWorker ? 'go' : undefined })).api\n      ipfsBId = await ipfsB.id()\n    })\n\n    beforeEach(async () => {\n      await ipfsA.swarm.connect(ipfsBId.addresses[0])\n    })\n\n    after(() => factory.clean())\n\n    it('should disconnect from a peer', async () => {\n      let peers\n\n      peers = await ipfsA.swarm.peers()\n      expect(peers).to.have.length.above(0)\n\n      await ipfsA.swarm.disconnect(ipfsBId.addresses[0])\n\n      peers = await ipfsA.swarm.peers()\n      expect(peers).to.have.length(0)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/swarm/index.js",
    "content": "import { createSuite } from '../utils/suite.js'\nimport { testConnect } from './connect.js'\nimport { testPeers } from './peers.js'\nimport { testAddrs } from './addrs.js'\nimport { testLocalAddrs } from './local-addrs.js'\nimport { testDisconnect } from './disconnect.js'\n\nconst tests = {\n  connect: testConnect,\n  peers: testPeers,\n  addrs: testAddrs,\n  localAddrs: testLocalAddrs,\n  disconnect: testDisconnect\n}\n\nexport default createSuite(tests)\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/swarm/local-addrs.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\nimport { isWebWorker } from 'ipfs-utils/src/env.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testLocalAddrs (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.swarm.localAddrs', function () {\n    this.timeout(80 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n\n    before(async () => {\n      ipfs = (await factory.spawn()).api\n    })\n\n    after(() => factory.clean())\n\n    it('should list local addresses the node is listening on', async () => {\n      const multiaddrs = await ipfs.swarm.localAddrs()\n\n      expect(multiaddrs).to.be.an.instanceOf(Array)\n\n      if (isWebWorker && factory.opts.type === 'proc') {\n        expect(multiaddrs).to.have.lengthOf(0)\n      } else {\n        expect(multiaddrs).to.not.be.empty()\n      }\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/swarm/peers.js",
    "content": "/* eslint-env mocha */\n\nimport { isMultiaddr } from '@multiformats/multiaddr'\nimport delay from 'delay'\nimport { isBrowser, isWebWorker } from 'ipfs-utils/src/env.js'\nimport { expect } from 'aegir/chai'\nimport { getDescribe, getIt } from '../utils/mocha.js'\n\n/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n */\n\n/**\n * @param {import('ipfs-core-types/src/swarm').PeersResult[]} peers\n */\nfunction peersAreUnique (peers) {\n  const peerSet = new Set()\n\n  peers.forEach(peer => {\n    peerSet.add(peer.peer.toString())\n  })\n\n  expect(peerSet).to.have.lengthOf(peers.length)\n}\n\n/**\n * @param {Factory} factory\n * @param {object} options\n */\nexport function testPeers (factory, options) {\n  const describe = getDescribe(options)\n  const it = getIt(options)\n\n  describe('.swarm.peers', function () {\n    this.timeout(80 * 1000)\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfsA\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfsB\n    /** @type {import('ipfs-core-types/src/root').IDResult} */\n    let ipfsBId\n\n    before(async () => {\n      ipfsA = (await factory.spawn({ type: 'proc' })).api\n      ipfsB = (await factory.spawn({ type: isWebWorker ? 'go' : undefined })).api\n      ipfsBId = await ipfsB.id()\n      await ipfsA.swarm.connect(ipfsBId.addresses[0])\n      /* TODO: Seen if we still need this after this is fixed\n         https://github.com/ipfs/js-ipfs/issues/2601 gets resolved */\n      // await delay(60 * 1000) // wait for open streams in the connection available\n    })\n\n    after(() => factory.clean())\n\n    it('should list peers this node is connected to', async () => {\n      const peers = await ipfsA.swarm.peers()\n      expect(peers).to.have.length.above(0)\n\n      const peer = peers[0]\n\n      expect(peer).to.have.a.property('addr')\n      expect(isMultiaddr(peer.addr)).to.equal(true)\n      expect(peer).to.have.a.property('peer')\n      expect(peer.peer).to.be.ok()\n      expect(peer).to.not.have.a.property('latency')\n\n      /* TODO: These assertions must be uncommented as soon as\n         https://github.com/ipfs/js-ipfs/issues/2601 gets resolved */\n      // expect(peer).to.have.a.property('muxer')\n      // expect(peer).to.not.have.a.property('streams')\n    })\n\n    it('should list peers this node is connected to with verbose option', async () => {\n      const peers = await ipfsA.swarm.peers({ verbose: true })\n      expect(peers).to.have.length.above(0)\n\n      const peer = peers[0]\n      expect(peer).to.have.a.property('addr')\n      expect(isMultiaddr(peer.addr)).to.equal(true)\n      expect(peer).to.have.a.property('peer')\n      expect(peer).to.have.a.property('latency')\n      expect(peer.latency).to.match(/n\\/a|[0-9]+[mµ]?s/) // n/a or 3ms or 3µs or 3s\n\n      /* TODO: These assertions must be uncommented as soon as\n         https://github.com/ipfs/js-ipfs/issues/2601 gets resolved */\n      // expect(peer).to.have.a.property('muxer')\n      // expect(peer).to.have.a.property('streams')\n    })\n\n    /**\n     * @param {string | string[]} addrs\n     * @returns\n     */\n    function getConfig (addrs) {\n      addrs = Array.isArray(addrs) ? addrs : [addrs]\n\n      return {\n        Addresses: {\n          Swarm: addrs,\n          API: '/ip4/127.0.0.1/tcp/0',\n          Gateway: '/ip4/127.0.0.1/tcp/0'\n        },\n        Bootstrap: [],\n        Discovery: {\n          MDNS: {\n            Enabled: false\n          }\n        }\n      }\n    }\n\n    it('should list peers only once', async () => {\n      const nodeA = (await factory.spawn({ type: 'proc' })).api\n      const nodeB = (await factory.spawn({ type: isWebWorker ? 'go' : undefined })).api\n      const nodeBId = await nodeB.id()\n      await nodeA.swarm.connect(nodeBId.addresses[0])\n      await delay(1000)\n\n      peersAreUnique(await nodeA.swarm.peers())\n      peersAreUnique(await nodeB.swarm.peers())\n    })\n\n    it('should list peers only once even if they have multiple addresses', async () => {\n      // TODO: Change to port 0, needs: https://github.com/ipfs/interface-ipfs-core/issues/152\n      const config = getConfig(isBrowser && factory.opts.type !== 'go'\n        ? [\n            `${process.env.SIGNALA_SERVER}`,\n            `${process.env.SIGNALB_SERVER}`\n          ]\n        : [\n            '/ip4/127.0.0.1/tcp/26545/ws',\n            '/ip4/127.0.0.1/tcp/26546/ws'\n          ])\n\n      const nodeA = (await factory.spawn({\n        // browser nodes have webrtc-star addresses which can't be dialled by go so make the other\n        // peer a js-ipfs node to get a tcp address that can be dialled. Also, webworkers are not\n        // diable so don't use a in-proc node for webworkers\n        type: ((isBrowser && factory.opts.type === 'go') || isWebWorker) ? 'js' : 'proc'\n      })).api\n      const nodeAId = await nodeA.id()\n      const nodeB = (await factory.spawn({\n        type: isWebWorker ? 'go' : undefined,\n        ipfsOptions: {\n          config\n        }\n      })).api\n\n      await nodeB.swarm.connect(nodeAId.addresses[0])\n\n      await delay(1000)\n\n      peersAreUnique(await nodeA.swarm.peers())\n      peersAreUnique(await nodeB.swarm.peers())\n    })\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/utils/blockstore-adapter.js",
    "content": "import { BaseBlockstore } from 'blockstore-core/base'\nimport * as raw from 'multiformats/codecs/raw'\nimport * as dagPB from '@ipld/dag-pb'\nimport * as dagCBOR from '@ipld/dag-cbor'\nimport { sha256 } from 'multiformats/hashes/sha2'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\n\n/**\n * @type {Record<number, string>}\n */\nconst formats = {\n  [raw.code]: raw.name,\n  [dagPB.code]: dagPB.name,\n  [dagCBOR.code]: dagCBOR.name\n}\n\n/**\n * @type {Record<number, string>}\n */\nconst hashes = {\n  [sha256.code]: sha256.name\n}\n\nclass IPFSBlockstore extends BaseBlockstore {\n  /**\n   * @param {import('ipfs-core-types').IPFS} ipfs\n   */\n  constructor (ipfs) {\n    super()\n\n    this.ipfs = ipfs\n  }\n\n  /**\n   * @param {import('multiformats/cid').CID} cid\n   * @param {Uint8Array} buf\n   */\n  async put (cid, buf) {\n    const c = await this.ipfs.block.put(buf, {\n      format: formats[cid.code],\n      mhtype: hashes[cid.multihash.code],\n      version: cid.version\n    })\n\n    if (uint8ArrayToString(c.multihash.bytes, 'base64') !== uint8ArrayToString(cid.multihash.bytes, 'base64')) {\n      throw new Error('Multihashes of stored blocks did not match')\n    }\n  }\n}\n\n/**\n * @param {import('ipfs-core-types').IPFS} ipfs\n */\nexport default function createBlockstore (ipfs) {\n  return new IPFSBlockstore(ipfs)\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/utils/create-sharded-directory.js",
    "content": "import { expect } from 'aegir/chai'\nimport isShardAtPath from './is-shard-at-path.js'\nimport last from 'it-last'\n\n/**\n * @param {import('ipfs-core-types').IPFS} ipfs\n * @param {number} [files]\n */\nexport async function createShardedDirectory (ipfs, files = 1001) {\n  const dirPath = `/sharded-dir-${Math.random()}`\n\n  const result = await last(ipfs.addAll((function * () {\n    for (let i = 0; i < files; i++) {\n      yield {\n        path: `${dirPath}/file-${i}`,\n        content: Uint8Array.from([0, 1, 2, 3, 4, 5, i])\n      }\n    }\n  }()), {\n    preload: false,\n    pin: false\n  }))\n\n  if (!result) {\n    throw new Error('No result received from ipfs.addAll')\n  }\n\n  await ipfs.files.cp(`/ipfs/${result.cid}`, dirPath)\n\n  await expect(isShardAtPath(dirPath, ipfs)).to.eventually.be.true('Tried to create a shared directory but the result was not a shard')\n\n  return dirPath\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/utils/create-two-shards.js",
    "content": "import { expect } from 'aegir/chai'\nimport isShardAtPath from './is-shard-at-path.js'\nimport last from 'it-last'\n\n/**\n * @param {import('ipfs-core-types').IPFS} ipfs\n * @param {number} fileCount\n */\nexport async function createTwoShards (ipfs, fileCount) {\n  const dirPath = `/sharded-dir-${Math.random()}`\n  const files = new Array(fileCount).fill(0).map((_, index) => ({\n    path: `${dirPath}/file-${index}`,\n    content: Uint8Array.from([0, 1, 2, 3, 4, index])\n  }))\n  files[files.length - 1].path = `${dirPath}/file-${fileCount - 1}`\n\n  const allFiles = files.map(file => ({\n    ...file\n  }))\n  const someFiles = files.map(file => ({\n    ...file\n  }))\n  const nextFile = someFiles.pop()\n\n  if (!nextFile) {\n    throw new Error('No nextFile found')\n  }\n\n  const res1 = await last(ipfs.addAll(allFiles, {\n    // for js-ipfs - go-ipfs shards everything when sharding is turned on\n    shardSplitThreshold: files.length - 1,\n    preload: false,\n    pin: false\n  }))\n\n  if (!res1) {\n    throw new Error('No result received from ipfs.addAll')\n  }\n\n  const { cid: dirWithAllFiles } = res1\n  const res2 = await last(ipfs.addAll(someFiles, {\n    // for js-ipfs - go-ipfs shards everything when sharding is turned on\n    shardSplitThreshold: files.length - 1,\n    preload: false,\n    pin: false\n  }))\n\n  if (!res2) {\n    throw new Error('No result received from ipfs.addAll')\n  }\n\n  const { cid: dirWithSomeFiles } = res2\n\n  await expect(isShardAtPath(`/ipfs/${dirWithAllFiles}`, ipfs)).to.eventually.be.true()\n  await expect(isShardAtPath(`/ipfs/${dirWithSomeFiles}`, ipfs)).to.eventually.be.true()\n\n  return {\n    nextFile,\n    dirWithAllFiles,\n    dirWithSomeFiles,\n    dirPath\n  }\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/utils/dump-shard.js",
    "content": "import { UnixFS } from 'ipfs-unixfs'\n\n/**\n * @param {string} path\n * @param {import('ipfs-core-types').IPFS} ipfs\n */\nexport default async function dumpShard (path, ipfs) {\n  const stats = await ipfs.files.stat(path)\n  const { value: node } = await ipfs.dag.get(stats.cid)\n  const entry = UnixFS.unmarshal(node.Data)\n\n  if (entry.type !== 'hamt-sharded-directory') {\n    throw new Error('Not a shard')\n  }\n\n  await dumpSubShard(stats.cid, ipfs)\n}\n\n/**\n * @param {import('multiformats/cid').CID} cid\n * @param {import('ipfs-core-types').IPFS} ipfs\n * @param {string} prefix\n */\nasync function dumpSubShard (cid, ipfs, prefix = '') {\n  const { value: node } = await ipfs.dag.get(cid)\n  const entry = UnixFS.unmarshal(node.Data)\n\n  if (entry.type !== 'hamt-sharded-directory') {\n    throw new Error('Not a shard')\n  }\n\n  for (const link of node.Links) {\n    const { value: subNode } = await ipfs.dag.get(link.Hash)\n    const subEntry = UnixFS.unmarshal(subNode.Data)\n    console.info(`${prefix}${link.Name}`, ' ', subEntry.type) // eslint-disable-line no-console\n\n    if (link.Name.length === 2) {\n      await dumpSubShard(link.Hash, ipfs, `${prefix}  `)\n    }\n  }\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/utils/index.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport loadFixture from 'aegir/fixtures'\n\nconst ONE_MEG = Math.pow(2, 20)\n\nexport const fixtures = Object.freeze({\n  directory: Object.freeze({\n    cid: CID.parse('QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP'),\n    /** @type {Record<string, Uint8Array>} */\n    files: Object.freeze({\n      'pp.txt': loadFixture('test/fixtures/test-folder/pp.txt', 'interface-ipfs-core'),\n      'holmes.txt': loadFixture('test/fixtures/test-folder/holmes.txt', 'interface-ipfs-core'),\n      'jungle.txt': loadFixture('test/fixtures/test-folder/jungle.txt', 'interface-ipfs-core'),\n      'alice.txt': loadFixture('test/fixtures/test-folder/alice.txt', 'interface-ipfs-core'),\n      'files/hello.txt': loadFixture('test/fixtures/test-folder/files/hello.txt', 'interface-ipfs-core'),\n      'files/ipfs.txt': loadFixture('test/fixtures/test-folder/files/ipfs.txt', 'interface-ipfs-core')\n    })\n  }),\n  smallFile: Object.freeze({\n    cid: CID.parse('Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP'),\n    data: uint8ArrayFromString('Plz add me!\\n')\n  }),\n  bigFile: Object.freeze({\n    cid: CID.parse('QmcKEs7mbxbGPPc2zo77E6CPwgaSbY4SmD2MFh16AqaR9e'),\n    data: Uint8Array.from(new Array(ONE_MEG * 15).fill(0))\n  }),\n  emptyFile: Object.freeze({\n    cid: CID.parse('QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH'),\n    data: new Uint8Array(0)\n  })\n})\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/utils/ipfs-options-websockets-filter-all.js",
    "content": "import { webSockets } from '@libp2p/websockets'\nimport { all } from '@libp2p/websockets/filters'\n\nexport function ipfsOptionsWebsocketsFilterAll () {\n  return {\n    libp2p: {\n      config: {\n        transports: [\n          webSockets({\n            filter: all\n          })\n        ]\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/utils/is-shard-at-path.js",
    "content": "import { UnixFS } from 'ipfs-unixfs'\n\n/**\n * @param {string} path\n * @param {import('ipfs-core-types').IPFS} ipfs\n */\nexport default async function isShardAtPath (path, ipfs) {\n  const stats = await ipfs.files.stat(path)\n  const { value: node } = await ipfs.dag.get(stats.cid)\n  const entry = UnixFS.unmarshal(node.Data)\n\n  return entry.type === 'hamt-sharded-directory'\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/utils/mocha.js",
    "content": "/* eslint-env mocha */\n\n/**\n * @typedef {object} Skip\n * @property {string} [name]\n * @property {string} [reason]\n */\n\n/**\n * @param {any} o\n * @returns {o is Skip}\n */\nconst isSkip = (o) => Object.prototype.toString.call(o) === '[object Object]' && (o.name || o.reason)\n\n/**\n * Get a \"describe\" function that is optionally 'skipped' or 'onlyed'\n * If skip/only are boolean true, or an object with a reason property, then we\n * want to skip/only the whole suite\n *\n * @param {object} [config]\n * @param {boolean | Skip | (string | Skip)[]} [config.skip]\n * @param {boolean} [config.only]\n */\nexport function getDescribe (config) {\n  if (config) {\n    if (config.skip === true) {\n      return describe.skip\n    }\n\n    if (config.only === true) {\n      return describe.only // eslint-disable-line\n    }\n\n    if (Array.isArray(config.skip)) {\n      const skipArr = config.skip\n\n      /**\n       * @param {string} name\n       * @param {*} impl\n       */\n      const _describe = (name, impl) => {\n        const skip = skipArr.find(skip => {\n          if (typeof skip === 'string') {\n            return skip === name\n          }\n\n          return skip.name === name\n        })\n\n        if (skip) {\n          return describe.skip(`${name} (${typeof skip === 'string' ? '🤷' : skip.reason})`, impl)\n        }\n\n        describe(name, impl)\n      }\n\n      _describe.skip = describe.skip\n      _describe.only = describe.only // eslint-disable-line\n\n      return _describe\n    } else if (isSkip(config.skip)) {\n      const skip = config.skip\n\n      if (!skip.reason) {\n        return describe.skip\n      }\n\n      /**\n       * @param {string} name\n       * @param {*} impl\n       */\n      const _describe = (name, impl) => {\n        describe.skip(`${name} (${skip.reason})`, impl)\n      }\n\n      _describe.skip = describe.skip\n      _describe.only = describe.only // eslint-disable-line\n\n      return _describe\n    }\n  }\n\n  return describe\n}\n\n/**\n * Get an \"it\" function that is optionally 'skipped' or 'onlyed'\n * If skip/only is an array, then we _might_ want to skip/only the specific\n * test if one of the items in the array is the same as the test name or if one\n * of the items in the array is an object with a name property that is the same\n * as the test name.\n *\n * @param {object} [config]\n * @param {boolean | Skip | (string | Skip)[]} [config.skip]\n * @param {boolean} [config.only]\n * @returns {Mocha.TestFunction}\n */\nexport function getIt (config) {\n  if (!config) return it\n\n  /**\n   * @param {string | Mocha.Func} name\n   * @param {Mocha.Func | Mocha.AsyncFunc} [impl]\n   */\n  const _it = (name, impl) => {\n    if (typeof name !== 'string') {\n      throw new Error('Pass test name as first argument to it')\n    }\n\n    if (Array.isArray(config.skip)) {\n      const skip = config.skip\n        .map((s) => isSkip(s) ? s : { name: s, reason: '🤷' })\n        .find((s) => s.name === name)\n\n      if (skip) {\n        if (skip.reason) name = `${name} (${skip.reason})`\n        return it.skip(name, impl)\n      }\n    }\n\n    if (Array.isArray(config.only)) {\n      const only = config.only\n        .map((o) => isSkip(o) ? o : { name: o, reason: '🤷' })\n        .find((o) => o.name === name)\n\n      if (only) {\n        if (only.reason) name = `${name} (${only.reason})`\n        return it.only(name, impl) // eslint-disable-line no-only-tests/no-only-tests\n      }\n    }\n\n    return it(name, impl)\n  }\n\n  _it.retries = it.retries\n  _it.skip = it.skip\n  _it.only = it.only // eslint-disable-line no-only-tests/no-only-tests\n\n  return _it\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/utils/suite.js",
    "content": "/**\n * @typedef {import('ipfsd-ctl').Factory} Factory\n * @typedef {object} Skip\n * @property {string} [name]\n * @property {string} [reason]\n */\n\n/**\n * @param {any} o\n * @returns {o is Skip}\n */\nconst isSkip = (o) => Object.prototype.toString.call(o) === '[object Object]' && (o.name || o.reason)\n\n/**\n * @param {*} tests\n * @param {*} [parent]\n */\nexport function createSuite (tests, parent) {\n  /**\n   * @param {Factory} factory\n   * @param {object} [options]\n   * @param {boolean | Skip | (string | Skip)[]} [options.skip]\n   * @param {boolean} [options.only]\n   */\n  const suite = (factory, options = {}) => {\n    Object.keys(tests).forEach(t => {\n      const opts = Object.assign({}, options)\n      const suiteName = parent ? `${parent}.${t}` : t\n\n      if (Array.isArray(opts.skip)) {\n        const skip = opts.skip\n          .map((s) => isSkip(s) ? s : { name: s, reason: '🤷' })\n          .find((s) => s.name === suiteName)\n\n        if (skip) {\n          opts.skip = skip\n        }\n      }\n\n      if (Array.isArray(opts.only)) {\n        if (opts.only.includes(suiteName)) {\n          opts.only = true\n        }\n      }\n\n      tests[t](factory, opts)\n    })\n  }\n\n  return Object.assign(suite, tests)\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/utils/test-timeout.js",
    "content": "import drain from 'it-drain'\n\n/**\n * @param {*} fn\n * @returns {Promise<void>}\n */\nexport default function testTimeout (fn) {\n  return new Promise((resolve, reject) => {\n    // some operations are either synchronous so cannot time out, or complete during\n    // processing of the microtask queue so the timeout timer doesn't fire.  If this\n    // is the case this is more of a best-effort test..\n    setTimeout(() => {\n      const start = Date.now()\n      let res = fn()\n\n      if (res[Symbol.asyncIterator]) {\n        res = drain(res)\n      }\n\n      res.then((/** @type {*} */ result) => {\n        const timeTaken = Date.now() - start\n\n        if (timeTaken < 100) {\n          // the implementation may be too fast to measure a time out reliably on node\n          // due to the event loop being blocked.  if it took longer than 100ms though,\n          // it almost certainly did not time out\n          return resolve()\n        }\n\n        reject(new Error(`API call did not time out after ${timeTaken}ms, got ${JSON.stringify(result, null, 2)}`))\n      }, (/** @type {Error} */ err) => {\n        if (err.toString().includes('Timeout')) {\n          return resolve()\n        }\n\n        const timeTaken = Date.now() - start\n\n        reject(new Error(`Expected TimeoutError after ${timeTaken}ms, got ${err.stack}`))\n      })\n    }, 10)\n  })\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/utils/traverse-leaf-nodes.js",
    "content": "/**\n * @typedef {import('@ipld/dag-pb').PBNode} PBNode\n * @typedef {import('multiformats/cid').CID} CID\n */\n\n/**\n * @param {import('ipfs-core-types').IPFS} ipfs\n * @param {CID} cid\n */\nexport async function * traverseLeafNodes (ipfs, cid) {\n  /**\n   * @param {import('multiformats/cid').CID} cid\n   * @returns {AsyncIterable<{ node: PBNode, cid: CID }>}\n   */\n  async function * traverse (cid) {\n    const { value: node } = await ipfs.dag.get(cid)\n\n    if (node instanceof Uint8Array || !node.Links.length) {\n      yield {\n        node,\n        cid\n      }\n\n      return\n    }\n\n    for (const link of node.Links) {\n      yield * traverse(link.Hash)\n    }\n  }\n\n  yield * traverse(cid)\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/src/utils/wait-for.js",
    "content": "import delay from 'delay'\nimport errCode from 'err-code'\n\n/**\n * Wait for async function `test` to resolve true or timeout after options.timeout milliseconds.\n *\n * @param {() => Promise<boolean> | boolean} test\n * @param {object} [options]\n * @param {number} [options.timeout]\n * @param {number} [options.interval]\n * @param {string} [options.name]\n */\nexport default async function waitFor (test, options) {\n  const opts = Object.assign({ timeout: 5000, interval: 0, name: 'event' }, options)\n  const start = Date.now()\n\n  while (true) {\n    if (await test()) {\n      return\n    }\n\n    if (Date.now() > start + opts.timeout) {\n      throw errCode(new Error(`Timed out waiting for ${opts.name}`), 'ERR_TIMEOUT')\n    }\n\n    await delay(opts.interval)\n  }\n}\n"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/.gitattributes",
    "content": "* -text\n"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/hidden-files-folder/.hiddenTest.txt",
    "content": "Aha! You found me!\n"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/hidden-files-folder/alice.txt",
    "content": "CHAPTER XII. Alice's Evidence\n\n\n'Here!' cried Alice, quite forgetting in the flurry of the moment how\nlarge she had grown in the last few minutes, and she jumped up in such\na hurry that she tipped over the jury-box with the edge of her skirt,\nupsetting all the jurymen on to the heads of the crowd below, and there\nthey lay sprawling about, reminding her very much of a globe of goldfish\nshe had accidentally upset the week before.\n\n'Oh, I BEG your pardon!' she exclaimed in a tone of great dismay, and\nbegan picking them up again as quickly as she could, for the accident of\nthe goldfish kept running in her head, and she had a vague sort of idea\nthat they must be collected at once and put back into the jury-box, or\nthey would die.\n\n'The trial cannot proceed,' said the King in a very grave voice, 'until\nall the jurymen are back in their proper places--ALL,' he repeated with\ngreat emphasis, looking hard at Alice as he said do.\n\nAlice looked at the jury-box, and saw that, in her haste, she had put\nthe Lizard in head downwards, and the poor little thing was waving its\ntail about in a melancholy way, being quite unable to move. She soon got\nit out again, and put it right; 'not that it signifies much,' she said\nto herself; 'I should think it would be QUITE as much use in the trial\none way up as the other.'\n\nAs soon as the jury had a little recovered from the shock of being\nupset, and their slates and pencils had been found and handed back to\nthem, they set to work very diligently to write out a history of the\naccident, all except the Lizard, who seemed too much overcome to do\nanything but sit with its mouth open, gazing up into the roof of the\ncourt.\n\n'What do you know about this business?' the King said to Alice.\n\n'Nothing,' said Alice.\n\n'Nothing WHATEVER?' persisted the King.\n\n'Nothing whatever,' said Alice.\n\n'That's very important,' the King said, turning to the jury. They were\njust beginning to write this down on their slates, when the White Rabbit\ninterrupted: 'UNimportant, your Majesty means, of course,' he said in a\nvery respectful tone, but frowning and making faces at him as he spoke.\n\n'UNimportant, of course, I meant,' the King hastily said, and went on\nto himself in an undertone,\n\n'important--unimportant--unimportant--important--' as if he were trying\nwhich word sounded best.\n\nSome of the jury wrote it down 'important,' and some 'unimportant.'\nAlice could see this, as she was near enough to look over their slates;\n'but it doesn't matter a bit,' she thought to herself.\n\nAt this moment the King, who had been for some time busily writing in\nhis note-book, cackled out 'Silence!' and read out from his book, 'Rule\nForty-two. ALL PERSONS MORE THAN A MILE HIGH TO LEAVE THE COURT.'\n\nEverybody looked at Alice.\n\n'I'M not a mile high,' said Alice.\n\n'You are,' said the King.\n\n'Nearly two miles high,' added the Queen.\n\n'Well, I shan't go, at any rate,' said Alice: 'besides, that's not a\nregular rule: you invented it just now.'\n\n'It's the oldest rule in the book,' said the King.\n\n'Then it ought to be Number One,' said Alice.\n\nThe King turned pale, and shut his note-book hastily. 'Consider your\nverdict,' he said to the jury, in a low, trembling voice.\n\n'There's more evidence to come yet, please your Majesty,' said the White\nRabbit, jumping up in a great hurry; 'this paper has just been picked\nup.'\n\n'What's in it?' said the Queen.\n\n'I haven't opened it yet,' said the White Rabbit, 'but it seems to be a\nletter, written by the prisoner to--to somebody.'\n\n'It must have been that,' said the King, 'unless it was written to\nnobody, which isn't usual, you know.'\n\n'Who is it directed to?' said one of the jurymen.\n\n'It isn't directed at all,' said the White Rabbit; 'in fact, there's\nnothing written on the OUTSIDE.' He unfolded the paper as he spoke, and\nadded 'It isn't a letter, after all: it's a set of verses.'\n\n'Are they in the prisoner's handwriting?' asked another of the jurymen.\n\n'No, they're not,' said the White Rabbit, 'and that's the queerest thing\nabout it.' (The jury all looked puzzled.)\n\n'He must have imitated somebody else's hand,' said the King. (The jury\nall brightened up again.)\n\n'Please your Majesty,' said the Knave, 'I didn't write it, and they\ncan't prove I did: there's no name signed at the end.'\n\n'If you didn't sign it,' said the King, 'that only makes the matter\nworse. You MUST have meant some mischief, or else you'd have signed your\nname like an honest man.'\n\nThere was a general clapping of hands at this: it was the first really\nclever thing the King had said that day.\n\n'That PROVES his guilt,' said the Queen.\n\n'It proves nothing of the sort!' said Alice. 'Why, you don't even know\nwhat they're about!'\n\n'Read them,' said the King.\n\nThe White Rabbit put on his spectacles. 'Where shall I begin, please\nyour Majesty?' he asked.\n\n'Begin at the beginning,' the King said gravely, 'and go on till you\ncome to the end: then stop.'\n\nThese were the verses the White Rabbit read:--\n\n   'They told me you had been to her,\n    And mentioned me to him:\n   She gave me a good character,\n    But said I could not swim.\n\n   He sent them word I had not gone\n    (We know it to be true):\n   If she should push the matter on,\n    What would become of you?\n\n   I gave her one, they gave him two,\n    You gave us three or more;\n   They all returned from him to you,\n    Though they were mine before.\n\n   If I or she should chance to be\n    Involved in this affair,\n   He trusts to you to set them free,\n    Exactly as we were.\n\n   My notion was that you had been\n    (Before she had this fit)\n   An obstacle that came between\n    Him, and ourselves, and it.\n\n   Don't let him know she liked them best,\n    For this must ever be\n   A secret, kept from all the rest,\n    Between yourself and me.'\n\n'That's the most important piece of evidence we've heard yet,' said the\nKing, rubbing his hands; 'so now let the jury--'\n\n'If any one of them can explain it,' said Alice, (she had grown so large\nin the last few minutes that she wasn't a bit afraid of interrupting\nhim,) 'I'll give him sixpence. _I_ don't believe there's an atom of\nmeaning in it.'\n\nThe jury all wrote down on their slates, 'SHE doesn't believe there's an\natom of meaning in it,' but none of them attempted to explain the paper.\n\n'If there's no meaning in it,' said the King, 'that saves a world of\ntrouble, you know, as we needn't try to find any. And yet I don't know,'\nhe went on, spreading out the verses on his knee, and looking at them\nwith one eye; 'I seem to see some meaning in them, after all. \"--SAID\nI COULD NOT SWIM--\" you can't swim, can you?' he added, turning to the\nKnave.\n\nThe Knave shook his head sadly. 'Do I look like it?' he said. (Which he\ncertainly did NOT, being made entirely of cardboard.)\n\n'All right, so far,' said the King, and he went on muttering over\nthe verses to himself: '\"WE KNOW IT TO BE TRUE--\" that's the jury, of\ncourse--\"I GAVE HER ONE, THEY GAVE HIM TWO--\" why, that must be what he\ndid with the tarts, you know--'\n\n'But, it goes on \"THEY ALL RETURNED FROM HIM TO YOU,\"' said Alice.\n\n'Why, there they are!' said the King triumphantly, pointing to the tarts\non the table. 'Nothing can be clearer than THAT. Then again--\"BEFORE SHE\nHAD THIS FIT--\" you never had fits, my dear, I think?' he said to the\nQueen.\n\n'Never!' said the Queen furiously, throwing an inkstand at the Lizard\nas she spoke. (The unfortunate little Bill had left off writing on his\nslate with one finger, as he found it made no mark; but he now hastily\nbegan again, using the ink, that was trickling down his face, as long as\nit lasted.)\n\n'Then the words don't FIT you,' said the King, looking round the court\nwith a smile. There was a dead silence.\n\n'It's a pun!' the King added in an offended tone, and everybody laughed,\n'Let the jury consider their verdict,' the King said, for about the\ntwentieth time that day.\n\n'No, no!' said the Queen. 'Sentence first--verdict afterwards.'\n\n'Stuff and nonsense!' said Alice loudly. 'The idea of having the\nsentence first!'\n\n'Hold your tongue!' said the Queen, turning purple.\n\n'I won't!' said Alice.\n\n'Off with her head!' the Queen shouted at the top of her voice. Nobody\nmoved.\n\n'Who cares for you?' said Alice, (she had grown to her full size by this\ntime.) 'You're nothing but a pack of cards!'\n\nAt this the whole pack rose up into the air, and came flying down upon\nher: she gave a little scream, half of fright and half of anger, and\ntried to beat them off, and found herself lying on the bank, with her\nhead in the lap of her sister, who was gently brushing away some dead\nleaves that had fluttered down from the trees upon her face.\n\n'Wake up, Alice dear!' said her sister; 'Why, what a long sleep you've\nhad!'\n\n'Oh, I've had such a curious dream!' said Alice, and she told her\nsister, as well as she could remember them, all these strange Adventures\nof hers that you have just been reading about; and when she had\nfinished, her sister kissed her, and said, 'It WAS a curious dream,\ndear, certainly: but now run in to your tea; it's getting late.' So\nAlice got up and ran off, thinking while she ran, as well she might,\nwhat a wonderful dream it had been.\n\nBut her sister sat still just as she left her, leaning her head on her\nhand, watching the setting sun, and thinking of little Alice and all her\nwonderful Adventures, till she too began dreaming after a fashion, and\nthis was her dream:--\n\nFirst, she dreamed of little Alice herself, and once again the tiny\nhands were clasped upon her knee, and the bright eager eyes were looking\nup into hers--she could hear the very tones of her voice, and see that\nqueer little toss of her head to keep back the wandering hair that\nWOULD always get into her eyes--and still as she listened, or seemed to\nlisten, the whole place around her became alive with the strange creatures\nof her little sister's dream.\n\nThe long grass rustled at her feet as the White Rabbit hurried by--the\nfrightened Mouse splashed his way through the neighbouring pool--she\ncould hear the rattle of the teacups as the March Hare and his friends\nshared their never-ending meal, and the shrill voice of the Queen\nordering off her unfortunate guests to execution--once more the pig-baby\nwas sneezing on the Duchess's knee, while plates and dishes crashed\naround it--once more the shriek of the Gryphon, the squeaking of the\nLizard's slate-pencil, and the choking of the suppressed guinea-pigs,\nfilled the air, mixed up with the distant sobs of the miserable Mock\nTurtle.\n\nSo she sat on, with closed eyes, and half believed herself in\nWonderland, though she knew she had but to open them again, and all\nwould change to dull reality--the grass would be only rustling in the\nwind, and the pool rippling to the waving of the reeds--the rattling\nteacups would change to tinkling sheep-bells, and the Queen's shrill\ncries to the voice of the shepherd boy--and the sneeze of the baby, the\nshriek of the Gryphon, and all the other queer noises, would change (she\nknew) to the confused clamour of the busy farm-yard--while the lowing\nof the cattle in the distance would take the place of the Mock Turtle's\nheavy sobs.\n\nLastly, she pictured to herself how this same little sister of hers\nwould, in the after-time, be herself a grown woman; and how she would\nkeep, through all her riper years, the simple and loving heart of her\nchildhood: and how she would gather about her other little children, and\nmake THEIR eyes bright and eager with many a strange tale, perhaps even\nwith the dream of Wonderland of long ago: and how she would feel with\nall their simple sorrows, and find a pleasure in all their simple joys,\nremembering her own child-life, and the happy summer days.\n\n              THE END\n"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/hidden-files-folder/files/hello.txt",
    "content": "Hello\n"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/hidden-files-folder/files/ipfs.txt",
    "content": "IPFS\n"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/hidden-files-folder/hello-link",
    "content": "Hello\n"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/hidden-files-folder/holmes.txt",
    "content": "Project Gutenberg's The Adventures of Sherlock Holmes, by Arthur Conan Doyle\n\nThis eBook is for the use of anyone anywhere at no cost and with\nalmost no restrictions whatsoever.  You may copy it, give it away or\nre-use it under the terms of the Project Gutenberg License included\nwith this eBook or online at www.gutenberg.net\n\n\nTitle: The Adventures of Sherlock Holmes\n\nAuthor: Arthur Conan Doyle\n\nPosting Date: April 18, 2011 [EBook #1661]\nFirst Posted: November 29, 2002\n\nLanguage: English\n\n\n*** START OF THIS PROJECT GUTENBERG EBOOK THE ADVENTURES OF SHERLOCK HOLMES ***\n\n\n\n\nProduced by an anonymous Project Gutenberg volunteer and Jose Menendez\n\n\n\n\n\n\n\n\n\nTHE ADVENTURES OF SHERLOCK HOLMES\n\nby\n\nSIR ARTHUR CONAN DOYLE\n\n\n\n   I. A Scandal in Bohemia\n  II. The Red-headed League\n III. A Case of Identity\n  IV. The Boscombe Valley Mystery\n   V. The Five Orange Pips\n  VI. The Man with the Twisted Lip\n VII. The Adventure of the Blue Carbuncle\nVIII. The Adventure of the Speckled Band\n  IX. The Adventure of the Engineer's Thumb\n   X. The Adventure of the Noble Bachelor\n  XI. The Adventure of the Beryl Coronet\n XII. The Adventure of the Copper Beeches\n\n\n\n\nADVENTURE I. A SCANDAL IN BOHEMIA\n\nI.\n\nTo Sherlock Holmes she is always THE woman. I have seldom heard\nhim mention her under any other name. In his eyes she eclipses\nand predominates the whole of her sex. It was not that he felt\nany emotion akin to love for Irene Adler. All emotions, and that\none particularly, were abhorrent to his cold, precise but\nadmirably balanced mind. He was, I take it, the most perfect\nreasoning and observing machine that the world has seen, but as a\nlover he would have placed himself in a false position. He never\nspoke of the softer passions, save with a gibe and a sneer. They\nwere admirable things for the observer--excellent for drawing the\nveil from men's motives and actions. But for the trained reasoner\nto admit such intrusions into his own delicate and finely\nadjusted temperament was to introduce a distracting factor which\nmight throw a doubt upon all his mental results. Grit in a\nsensitive instrument, or a crack in one of his own high-power\nlenses, would not be more disturbing than a strong emotion in a\nnature such as his. And yet there was but one woman to him, and\nthat woman was the late Irene Adler, of dubious and questionable\nmemory.\n\nI had seen little of Holmes lately. My marriage had drifted us\naway from each other. My own complete happiness, and the\nhome-centred interests which rise up around the man who first\nfinds himself master of his own establishment, were sufficient to\nabsorb all my attention, while Holmes, who loathed every form of\nsociety with his whole Bohemian soul, remained in our lodgings in\nBaker Street, buried among his old books, and alternating from\nweek to week between cocaine and ambition, the drowsiness of the\ndrug, and the fierce energy of his own keen nature. He was still,\nas ever, deeply attracted by the study of crime, and occupied his\nimmense faculties and extraordinary powers of observation in\nfollowing out those clues, and clearing up those mysteries which\nhad been abandoned as hopeless by the official police. From time\nto time I heard some vague account of his doings: of his summons\nto Odessa in the case of the Trepoff murder, of his clearing up\nof the singular tragedy of the Atkinson brothers at Trincomalee,\nand finally of the mission which he had accomplished so\ndelicately and successfully for the reigning family of Holland.\nBeyond these signs of his activity, however, which I merely\nshared with all the readers of the daily press, I knew little of\nmy former friend and companion.\n\nOne night--it was on the twentieth of March, 1888--I was\nreturning from a journey to a patient (for I had now returned to\ncivil practice), when my way led me through Baker Street. As I\npassed the well-remembered door, which must always be associated\nin my mind with my wooing, and with the dark incidents of the\nStudy in Scarlet, I was seized with a keen desire to see Holmes\nagain, and to know how he was employing his extraordinary powers.\nHis rooms were brilliantly lit, and, even as I looked up, I saw\nhis tall, spare figure pass twice in a dark silhouette against\nthe blind. He was pacing the room swiftly, eagerly, with his head\nsunk upon his chest and his hands clasped behind him. To me, who\nknew his every mood and habit, his attitude and manner told their\nown story. He was at work again. He had risen out of his\ndrug-created dreams and was hot upon the scent of some new\nproblem. I rang the bell and was shown up to the chamber which\nhad formerly been in part my own.\n\nHis manner was not effusive. It seldom was; but he was glad, I\nthink, to see me. With hardly a word spoken, but with a kindly\neye, he waved me to an armchair, threw across his case of cigars,\nand indicated a spirit case and a gasogene in the corner. Then he\nstood before the fire and looked me over in his singular\nintrospective fashion.\n\n\"Wedlock suits you,\" he remarked. \"I think, Watson, that you have\nput on seven and a half pounds since I saw you.\"\n\n\"Seven!\" I answered.\n\n\"Indeed, I should have thought a little more. Just a trifle more,\nI fancy, Watson. And in practice again, I observe. You did not\ntell me that you intended to go into harness.\"\n\n\"Then, how do you know?\"\n\n\"I see it, I deduce it. How do I know that you have been getting\nyourself very wet lately, and that you have a most clumsy and\ncareless servant girl?\"\n\n\"My dear Holmes,\" said I, \"this is too much. You would certainly\nhave been burned, had you lived a few centuries ago. It is true\nthat I had a country walk on Thursday and came home in a dreadful\nmess, but as I have changed my clothes I can't imagine how you\ndeduce it. As to Mary Jane, she is incorrigible, and my wife has\ngiven her notice, but there, again, I fail to see how you work it\nout.\"\n\nHe chuckled to himself and rubbed his long, nervous hands\ntogether.\n\n\"It is simplicity itself,\" said he; \"my eyes tell me that on the\ninside of your left shoe, just where the firelight strikes it,\nthe leather is scored by six almost parallel cuts. Obviously they\nhave been caused by someone who has very carelessly scraped round\nthe edges of the sole in order to remove crusted mud from it.\nHence, you see, my double deduction that you had been out in vile\nweather, and that you had a particularly malignant boot-slitting\nspecimen of the London slavey. As to your practice, if a\ngentleman walks into my rooms smelling of iodoform, with a black\nmark of nitrate of silver upon his right forefinger, and a bulge\non the right side of his top-hat to show where he has secreted\nhis stethoscope, I must be dull, indeed, if I do not pronounce\nhim to be an active member of the medical profession.\"\n\nI could not help laughing at the ease with which he explained his\nprocess of deduction. \"When I hear you give your reasons,\" I\nremarked, \"the thing always appears to me to be so ridiculously\nsimple that I could easily do it myself, though at each\nsuccessive instance of your reasoning I am baffled until you\nexplain your process. And yet I believe that my eyes are as good\nas yours.\"\n\n\"Quite so,\" he answered, lighting a cigarette, and throwing\nhimself down into an armchair. \"You see, but you do not observe.\nThe distinction is clear. For example, you have frequently seen\nthe steps which lead up from the hall to this room.\"\n\n\"Frequently.\"\n\n\"How often?\"\n\n\"Well, some hundreds of times.\"\n\n\"Then how many are there?\"\n\n\"How many? I don't know.\"\n\n\"Quite so! You have not observed. And yet you have seen. That is\njust my point. Now, I know that there are seventeen steps,\nbecause I have both seen and observed. By-the-way, since you are\ninterested in these little problems, and since you are good\nenough to chronicle one or two of my trifling experiences, you\nmay be interested in this.\" He threw over a sheet of thick,\npink-tinted note-paper which had been lying open upon the table.\n\"It came by the last post,\" said he. \"Read it aloud.\"\n\nThe note was undated, and without either signature or address.\n\n\"There will call upon you to-night, at a quarter to eight\no'clock,\" it said, \"a gentleman who desires to consult you upon a\nmatter of the very deepest moment. Your recent services to one of\nthe royal houses of Europe have shown that you are one who may\nsafely be trusted with matters which are of an importance which\ncan hardly be exaggerated. This account of you we have from all\nquarters received. Be in your chamber then at that hour, and do\nnot take it amiss if your visitor wear a mask.\"\n\n\"This is indeed a mystery,\" I remarked. \"What do you imagine that\nit means?\"\n\n\"I have no data yet. It is a capital mistake to theorize before\none has data. Insensibly one begins to twist facts to suit\ntheories, instead of theories to suit facts. But the note itself.\nWhat do you deduce from it?\"\n\nI carefully examined the writing, and the paper upon which it was\nwritten.\n\n\"The man who wrote it was presumably well to do,\" I remarked,\nendeavouring to imitate my companion's processes. \"Such paper\ncould not be bought under half a crown a packet. It is peculiarly\nstrong and stiff.\"\n\n\"Peculiar--that is the very word,\" said Holmes. \"It is not an\nEnglish paper at all. Hold it up to the light.\"\n\nI did so, and saw a large \"E\" with a small \"g,\" a \"P,\" and a\nlarge \"G\" with a small \"t\" woven into the texture of the paper.\n\n\"What do you make of that?\" asked Holmes.\n\n\"The name of the maker, no doubt; or his monogram, rather.\"\n\n\"Not at all. The 'G' with the small 't' stands for\n'Gesellschaft,' which is the German for 'Company.' It is a\ncustomary contraction like our 'Co.' 'P,' of course, stands for\n'Papier.' Now for the 'Eg.' Let us glance at our Continental\nGazetteer.\" He took down a heavy brown volume from his shelves.\n\"Eglow, Eglonitz--here we are, Egria. It is in a German-speaking\ncountry--in Bohemia, not far from Carlsbad. 'Remarkable as being\nthe scene of the death of Wallenstein, and for its numerous\nglass-factories and paper-mills.' Ha, ha, my boy, what do you\nmake of that?\" His eyes sparkled, and he sent up a great blue\ntriumphant cloud from his cigarette.\n\n\"The paper was made in Bohemia,\" I said.\n\n\"Precisely. And the man who wrote the note is a German. Do you\nnote the peculiar construction of the sentence--'This account of\nyou we have from all quarters received.' A Frenchman or Russian\ncould not have written that. It is the German who is so\nuncourteous to his verbs. It only remains, therefore, to discover\nwhat is wanted by this German who writes upon Bohemian paper and\nprefers wearing a mask to showing his face. And here he comes, if\nI am not mistaken, to resolve all our doubts.\"\n\nAs he spoke there was the sharp sound of horses' hoofs and\ngrating wheels against the curb, followed by a sharp pull at the\nbell. Holmes whistled.\n\n\"A pair, by the sound,\" said he. \"Yes,\" he continued, glancing\nout of the window. \"A nice little brougham and a pair of\nbeauties. A hundred and fifty guineas apiece. There's money in\nthis case, Watson, if there is nothing else.\"\n\n\"I think that I had better go, Holmes.\"\n\n\"Not a bit, Doctor. Stay where you are. I am lost without my\nBoswell. And this promises to be interesting. It would be a pity\nto miss it.\"\n\n\"But your client--\"\n\n\"Never mind him. I may want your help, and so may he. Here he\ncomes. Sit down in that armchair, Doctor, and give us your best\nattention.\"\n\nA slow and heavy step, which had been heard upon the stairs and\nin the passage, paused immediately outside the door. Then there\nwas a loud and authoritative tap.\n\n\"Come in!\" said Holmes.\n\nA man entered who could hardly have been less than six feet six\ninches in height, with the chest and limbs of a Hercules. His\ndress was rich with a richness which would, in England, be looked\nupon as akin to bad taste. Heavy bands of astrakhan were slashed\nacross the sleeves and fronts of his double-breasted coat, while\nthe deep blue cloak which was thrown over his shoulders was lined\nwith flame-coloured silk and secured at the neck with a brooch\nwhich consisted of a single flaming beryl. Boots which extended\nhalfway up his calves, and which were trimmed at the tops with\nrich brown fur, completed the impression of barbaric opulence\nwhich was suggested by his whole appearance. He carried a\nbroad-brimmed hat in his hand, while he wore across the upper\npart of his face, extending down past the cheekbones, a black\nvizard mask, which he had apparently adjusted that very moment,\nfor his hand was still raised to it as he entered. From the lower\npart of the face he appeared to be a man of strong character,\nwith a thick, hanging lip, and a long, straight chin suggestive\nof resolution pushed to the length of obstinacy.\n\n\"You had my note?\" he asked with a deep harsh voice and a\nstrongly marked German accent. \"I told you that I would call.\" He\nlooked from one to the other of us, as if uncertain which to\naddress.\n\n\"Pray take a seat,\" said Holmes. \"This is my friend and\ncolleague, Dr. Watson, who is occasionally good enough to help me\nin my cases. Whom have I the honour to address?\"\n\n\"You may address me as the Count Von Kramm, a Bohemian nobleman.\nI understand that this gentleman, your friend, is a man of honour\nand discretion, whom I may trust with a matter of the most\nextreme importance. If not, I should much prefer to communicate\nwith you alone.\"\n\nI rose to go, but Holmes caught me by the wrist and pushed me\nback into my chair. \"It is both, or none,\" said he. \"You may say\nbefore this gentleman anything which you may say to me.\"\n\nThe Count shrugged his broad shoulders. \"Then I must begin,\" said\nhe, \"by binding you both to absolute secrecy for two years; at\nthe end of that time the matter will be of no importance. At\npresent it is not too much to say that it is of such weight it\nmay have an influence upon European history.\"\n\n\"I promise,\" said Holmes.\n\n\"And I.\"\n\n\"You will excuse this mask,\" continued our strange visitor. \"The\naugust person who employs me wishes his agent to be unknown to\nyou, and I may confess at once that the title by which I have\njust called myself is not exactly my own.\"\n\n\"I was aware of it,\" said Holmes dryly.\n\n\"The circumstances are of great delicacy, and every precaution\nhas to be taken to quench what might grow to be an immense\nscandal and seriously compromise one of the reigning families of\nEurope. To speak plainly, the matter implicates the great House\nof Ormstein, hereditary kings of Bohemia.\"\n\n\"I was also aware of that,\" murmured Holmes, settling himself\ndown in his armchair and closing his eyes.\n\nOur visitor glanced with some apparent surprise at the languid,\nlounging figure of the man who had been no doubt depicted to him\nas the most incisive reasoner and most energetic agent in Europe.\nHolmes slowly reopened his eyes and looked impatiently at his\ngigantic client.\n\n\"If your Majesty would condescend to state your case,\" he\nremarked, \"I should be better able to advise you.\"\n\nThe man sprang from his chair and paced up and down the room in\nuncontrollable agitation. Then, with a gesture of desperation, he\ntore the mask from his face and hurled it upon the ground. \"You\nare right,\" he cried; \"I am the King. Why should I attempt to\nconceal it?\"\n\n\"Why, indeed?\" murmured Holmes. \"Your Majesty had not spoken\nbefore I was aware that I was addressing Wilhelm Gottsreich\nSigismond von Ormstein, Grand Duke of Cassel-Felstein, and\nhereditary King of Bohemia.\"\n\n\"But you can understand,\" said our strange visitor, sitting down\nonce more and passing his hand over his high white forehead, \"you\ncan understand that I am not accustomed to doing such business in\nmy own person. Yet the matter was so delicate that I could not\nconfide it to an agent without putting myself in his power. I\nhave come incognito from Prague for the purpose of consulting\nyou.\"\n\n\"Then, pray consult,\" said Holmes, shutting his eyes once more.\n\n\"The facts are briefly these: Some five years ago, during a\nlengthy visit to Warsaw, I made the acquaintance of the well-known\nadventuress, Irene Adler. The name is no doubt familiar to you.\"\n\n\"Kindly look her up in my index, Doctor,\" murmured Holmes without\nopening his eyes. For many years he had adopted a system of\ndocketing all paragraphs concerning men and things, so that it\nwas difficult to name a subject or a person on which he could not\nat once furnish information. In this case I found her biography\nsandwiched in between that of a Hebrew rabbi and that of a\nstaff-commander who had written a monograph upon the deep-sea\nfishes.\n\n\"Let me see!\" said Holmes. \"Hum! Born in New Jersey in the year\n1858. Contralto--hum! La Scala, hum! Prima donna Imperial Opera\nof Warsaw--yes! Retired from operatic stage--ha! Living in\nLondon--quite so! Your Majesty, as I understand, became entangled\nwith this young person, wrote her some compromising letters, and\nis now desirous of getting those letters back.\"\n\n\"Precisely so. But how--\"\n\n\"Was there a secret marriage?\"\n\n\"None.\"\n\n\"No legal papers or certificates?\"\n\n\"None.\"\n\n\"Then I fail to follow your Majesty. If this young person should\nproduce her letters for blackmailing or other purposes, how is\nshe to prove their authenticity?\"\n\n\"There is the writing.\"\n\n\"Pooh, pooh! Forgery.\"\n\n\"My private note-paper.\"\n\n\"Stolen.\"\n\n\"My own seal.\"\n\n\"Imitated.\"\n\n\"My photograph.\"\n\n\"Bought.\"\n\n\"We were both in the photograph.\"\n\n\"Oh, dear! That is very bad! Your Majesty has indeed committed an\nindiscretion.\"\n\n\"I was mad--insane.\"\n\n\"You have compromised yourself seriously.\"\n\n\"I was only Crown Prince then. I was young. I am but thirty now.\"\n\n\"It must be recovered.\"\n\n\"We have tried and failed.\"\n\n\"Your Majesty must pay. It must be bought.\"\n\n\"She will not sell.\"\n\n\"Stolen, then.\"\n\n\"Five attempts have been made. Twice burglars in my pay ransacked\nher house. Once we diverted her luggage when she travelled. Twice\nshe has been waylaid. There has been no result.\"\n\n\"No sign of it?\"\n\n\"Absolutely none.\"\n\nHolmes laughed. \"It is quite a pretty little problem,\" said he.\n\n\"But a very serious one to me,\" returned the King reproachfully.\n\n\"Very, indeed. And what does she propose to do with the\nphotograph?\"\n\n\"To ruin me.\"\n\n\"But how?\"\n\n\"I am about to be married.\"\n\n\"So I have heard.\"\n\n\"To Clotilde Lothman von Saxe-Meningen, second daughter of the\nKing of Scandinavia. You may know the strict principles of her\nfamily. She is herself the very soul of delicacy. A shadow of a\ndoubt as to my conduct would bring the matter to an end.\"\n\n\"And Irene Adler?\"\n\n\"Threatens to send them the photograph. And she will do it. I\nknow that she will do it. You do not know her, but she has a soul\nof steel. She has the face of the most beautiful of women, and\nthe mind of the most resolute of men. Rather than I should marry\nanother woman, there are no lengths to which she would not\ngo--none.\"\n\n\"You are sure that she has not sent it yet?\"\n\n\"I am sure.\"\n\n\"And why?\"\n\n\"Because she has said that she would send it on the day when the\nbetrothal was publicly proclaimed. That will be next Monday.\"\n\n\"Oh, then we have three days yet,\" said Holmes with a yawn. \"That\nis very fortunate, as I have one or two matters of importance to\nlook into just at present. Your Majesty will, of course, stay in\nLondon for the present?\"\n\n\"Certainly. You will find me at the Langham under the name of the\nCount Von Kramm.\"\n\n\"Then I shall drop you a line to let you know how we progress.\"\n\n\"Pray do so. I shall be all anxiety.\"\n\n\"Then, as to money?\"\n\n\"You have carte blanche.\"\n\n\"Absolutely?\"\n\n\"I tell you that I would give one of the provinces of my kingdom\nto have that photograph.\"\n\n\"And for present expenses?\"\n\nThe King took a heavy chamois leather bag from under his cloak\nand laid it on the table.\n\n\"There are three hundred pounds in gold and seven hundred in\nnotes,\" he said.\n\nHolmes scribbled a receipt upon a sheet of his note-book and\nhanded it to him.\n\n\"And Mademoiselle's address?\" he asked.\n\n\"Is Briony Lodge, Serpentine Avenue, St. John's Wood.\"\n\nHolmes took a note of it. \"One other question,\" said he. \"Was the\nphotograph a cabinet?\"\n\n\"It was.\"\n\n\"Then, good-night, your Majesty, and I trust that we shall soon\nhave some good news for you. And good-night, Watson,\" he added,\nas the wheels of the royal brougham rolled down the street. \"If\nyou will be good enough to call to-morrow afternoon at three\no'clock I should like to chat this little matter over with you.\"\n\n\nII.\n\nAt three o'clock precisely I was at Baker Street, but Holmes had\nnot yet returned. The landlady informed me that he had left the\nhouse shortly after eight o'clock in the morning. I sat down\nbeside the fire, however, with the intention of awaiting him,\nhowever long he might be. I was already deeply interested in his\ninquiry, for, though it was surrounded by none of the grim and\nstrange features which were associated with the two crimes which\nI have already recorded, still, the nature of the case and the\nexalted station of his client gave it a character of its own.\nIndeed, apart from the nature of the investigation which my\nfriend had on hand, there was something in his masterly grasp of\na situation, and his keen, incisive reasoning, which made it a\npleasure to me to study his system of work, and to follow the\nquick, subtle methods by which he disentangled the most\ninextricable mysteries. So accustomed was I to his invariable\nsuccess that the very possibility of his failing had ceased to\nenter into my head.\n\nIt was close upon four before the door opened, and a\ndrunken-looking groom, ill-kempt and side-whiskered, with an\ninflamed face and disreputable clothes, walked into the room.\nAccustomed as I was to my friend's amazing powers in the use of\ndisguises, I had to look three times before I was certain that it\nwas indeed he. With a nod he vanished into the bedroom, whence he\nemerged in five minutes tweed-suited and respectable, as of old.\nPutting his hands into his pockets, he stretched out his legs in\nfront of the fire and laughed heartily for some minutes.\n\n\"Well, really!\" he cried, and then he choked and laughed again\nuntil he was obliged to lie back, limp and helpless, in the\nchair.\n\n\"What is it?\"\n\n\"It's quite too funny. I am sure you could never guess how I\nemployed my morning, or what I ended by doing.\"\n\n\"I can't imagine. I suppose that you have been watching the\nhabits, and perhaps the house, of Miss Irene Adler.\"\n\n\"Quite so; but the sequel was rather unusual. I will tell you,\nhowever. I left the house a little after eight o'clock this\nmorning in the character of a groom out of work. There is a\nwonderful sympathy and freemasonry among horsey men. Be one of\nthem, and you will know all that there is to know. I soon found\nBriony Lodge. It is a bijou villa, with a garden at the back, but\nbuilt out in front right up to the road, two stories. Chubb lock\nto the door. Large sitting-room on the right side, well\nfurnished, with long windows almost to the floor, and those\npreposterous English window fasteners which a child could open.\nBehind there was nothing remarkable, save that the passage window\ncould be reached from the top of the coach-house. I walked round\nit and examined it closely from every point of view, but without\nnoting anything else of interest.\n\n\"I then lounged down the street and found, as I expected, that\nthere was a mews in a lane which runs down by one wall of the\ngarden. I lent the ostlers a hand in rubbing down their horses,\nand received in exchange twopence, a glass of half and half, two\nfills of shag tobacco, and as much information as I could desire\nabout Miss Adler, to say nothing of half a dozen other people in\nthe neighbourhood in whom I was not in the least interested, but\nwhose biographies I was compelled to listen to.\"\n\n\"And what of Irene Adler?\" I asked.\n\n\"Oh, she has turned all the men's heads down in that part. She is\nthe daintiest thing under a bonnet on this planet. So say the\nSerpentine-mews, to a man. She lives quietly, sings at concerts,\ndrives out at five every day, and returns at seven sharp for\ndinner. Seldom goes out at other times, except when she sings.\nHas only one male visitor, but a good deal of him. He is dark,\nhandsome, and dashing, never calls less than once a day, and\noften twice. He is a Mr. Godfrey Norton, of the Inner Temple. See\nthe advantages of a cabman as a confidant. They had driven him\nhome a dozen times from Serpentine-mews, and knew all about him.\nWhen I had listened to all they had to tell, I began to walk up\nand down near Briony Lodge once more, and to think over my plan\nof campaign.\n\n\"This Godfrey Norton was evidently an important factor in the\nmatter. He was a lawyer. That sounded ominous. What was the\nrelation between them, and what the object of his repeated\nvisits? Was she his client, his friend, or his mistress? If the\nformer, she had probably transferred the photograph to his\nkeeping. If the latter, it was less likely. On the issue of this\nquestion depended whether I should continue my work at Briony\nLodge, or turn my attention to the gentleman's chambers in the\nTemple. It was a delicate point, and it widened the field of my\ninquiry. I fear that I bore you with these details, but I have to\nlet you see my little difficulties, if you are to understand the\nsituation.\"\n\n\"I am following you closely,\" I answered.\n\n\"I was still balancing the matter in my mind when a hansom cab\ndrove up to Briony Lodge, and a gentleman sprang out. He was a\nremarkably handsome man, dark, aquiline, and moustached--evidently\nthe man of whom I had heard. He appeared to be in a\ngreat hurry, shouted to the cabman to wait, and brushed past the\nmaid who opened the door with the air of a man who was thoroughly\nat home.\n\n\"He was in the house about half an hour, and I could catch\nglimpses of him in the windows of the sitting-room, pacing up and\ndown, talking excitedly, and waving his arms. Of her I could see\nnothing. Presently he emerged, looking even more flurried than\nbefore. As he stepped up to the cab, he pulled a gold watch from\nhis pocket and looked at it earnestly, 'Drive like the devil,' he\nshouted, 'first to Gross & Hankey's in Regent Street, and then to\nthe Church of St. Monica in the Edgeware Road. Half a guinea if\nyou do it in twenty minutes!'\n\n\"Away they went, and I was just wondering whether I should not do\nwell to follow them when up the lane came a neat little landau,\nthe coachman with his coat only half-buttoned, and his tie under\nhis ear, while all the tags of his harness were sticking out of\nthe buckles. It hadn't pulled up before she shot out of the hall\ndoor and into it. I only caught a glimpse of her at the moment,\nbut she was a lovely woman, with a face that a man might die for.\n\n\"'The Church of St. Monica, John,' she cried, 'and half a\nsovereign if you reach it in twenty minutes.'\n\n\"This was quite too good to lose, Watson. I was just balancing\nwhether I should run for it, or whether I should perch behind her\nlandau when a cab came through the street. The driver looked\ntwice at such a shabby fare, but I jumped in before he could\nobject. 'The Church of St. Monica,' said I, 'and half a sovereign\nif you reach it in twenty minutes.' It was twenty-five minutes to\ntwelve, and of course it was clear enough what was in the wind.\n\n\"My cabby drove fast. I don't think I ever drove faster, but the\nothers were there before us. The cab and the landau with their\nsteaming horses were in front of the door when I arrived. I paid\nthe man and hurried into the church. There was not a soul there\nsave the two whom I had followed and a surpliced clergyman, who\nseemed to be expostulating with them. They were all three\nstanding in a knot in front of the altar. I lounged up the side\naisle like any other idler who has dropped into a church.\nSuddenly, to my surprise, the three at the altar faced round to\nme, and Godfrey Norton came running as hard as he could towards\nme.\n\n\"'Thank God,' he cried. 'You'll do. Come! Come!'\n\n\"'What then?' I asked.\n\n\"'Come, man, come, only three minutes, or it won't be legal.'\n\n\"I was half-dragged up to the altar, and before I knew where I was\nI found myself mumbling responses which were whispered in my ear,\nand vouching for things of which I knew nothing, and generally\nassisting in the secure tying up of Irene Adler, spinster, to\nGodfrey Norton, bachelor. It was all done in an instant, and\nthere was the gentleman thanking me on the one side and the lady\non the other, while the clergyman beamed on me in front. It was\nthe most preposterous position in which I ever found myself in my\nlife, and it was the thought of it that started me laughing just\nnow. It seems that there had been some informality about their\nlicense, that the clergyman absolutely refused to marry them\nwithout a witness of some sort, and that my lucky appearance\nsaved the bridegroom from having to sally out into the streets in\nsearch of a best man. The bride gave me a sovereign, and I mean\nto wear it on my watch-chain in memory of the occasion.\"\n\n\"This is a very unexpected turn of affairs,\" said I; \"and what\nthen?\"\n\n\"Well, I found my plans very seriously menaced. It looked as if\nthe pair might take an immediate departure, and so necessitate\nvery prompt and energetic measures on my part. At the church\ndoor, however, they separated, he driving back to the Temple, and\nshe to her own house. 'I shall drive out in the park at five as\nusual,' she said as she left him. I heard no more. They drove\naway in different directions, and I went off to make my own\narrangements.\"\n\n\"Which are?\"\n\n\"Some cold beef and a glass of beer,\" he answered, ringing the\nbell. \"I have been too busy to think of food, and I am likely to\nbe busier still this evening. By the way, Doctor, I shall want\nyour co-operation.\"\n\n\"I shall be delighted.\"\n\n\"You don't mind breaking the law?\"\n\n\"Not in the least.\"\n\n\"Nor running a chance of arrest?\"\n\n\"Not in a good cause.\"\n\n\"Oh, the cause is excellent!\"\n\n\"Then I am your man.\"\n\n\"I was sure that I might rely on you.\"\n\n\"But what is it you wish?\"\n\n\"When Mrs. Turner has brought in the tray I will make it clear to\nyou. Now,\" he said as he turned hungrily on the simple fare that\nour landlady had provided, \"I must discuss it while I eat, for I\nhave not much time. It is nearly five now. In two hours we must\nbe on the scene of action. Miss Irene, or Madame, rather, returns\nfrom her drive at seven. We must be at Briony Lodge to meet her.\"\n\n\"And what then?\"\n\n\"You must leave that to me. I have already arranged what is to\noccur. There is only one point on which I must insist. You must\nnot interfere, come what may. You understand?\"\n\n\"I am to be neutral?\"\n\n\"To do nothing whatever. There will probably be some small\nunpleasantness. Do not join in it. It will end in my being\nconveyed into the house. Four or five minutes afterwards the\nsitting-room window will open. You are to station yourself close\nto that open window.\"\n\n\"Yes.\"\n\n\"You are to watch me, for I will be visible to you.\"\n\n\"Yes.\"\n\n\"And when I raise my hand--so--you will throw into the room what\nI give you to throw, and will, at the same time, raise the cry of\nfire. You quite follow me?\"\n\n\"Entirely.\"\n\n\"It is nothing very formidable,\" he said, taking a long cigar-shaped\nroll from his pocket. \"It is an ordinary plumber's smoke-rocket,\nfitted with a cap at either end to make it self-lighting.\nYour task is confined to that. When you raise your cry of fire,\nit will be taken up by quite a number of people. You may then\nwalk to the end of the street, and I will rejoin you in ten\nminutes. I hope that I have made myself clear?\"\n\n\"I am to remain neutral, to get near the window, to watch you,\nand at the signal to throw in this object, then to raise the cry\nof fire, and to wait you at the corner of the street.\"\n\n\"Precisely.\"\n\n\"Then you may entirely rely on me.\"\n\n\"That is excellent. I think, perhaps, it is almost time that I\nprepare for the new role I have to play.\"\n\nHe disappeared into his bedroom and returned in a few minutes in\nthe character of an amiable and simple-minded Nonconformist\nclergyman. His broad black hat, his baggy trousers, his white\ntie, his sympathetic smile, and general look of peering and\nbenevolent curiosity were such as Mr. John Hare alone could have\nequalled. It was not merely that Holmes changed his costume. His\nexpression, his manner, his very soul seemed to vary with every\nfresh part that he assumed. The stage lost a fine actor, even as\nscience lost an acute reasoner, when he became a specialist in\ncrime.\n\nIt was a quarter past six when we left Baker Street, and it still\nwanted ten minutes to the hour when we found ourselves in\nSerpentine Avenue. It was already dusk, and the lamps were just\nbeing lighted as we paced up and down in front of Briony Lodge,\nwaiting for the coming of its occupant. The house was just such\nas I had pictured it from Sherlock Holmes' succinct description,\nbut the locality appeared to be less private than I expected. On\nthe contrary, for a small street in a quiet neighbourhood, it was\nremarkably animated. There was a group of shabbily dressed men\nsmoking and laughing in a corner, a scissors-grinder with his\nwheel, two guardsmen who were flirting with a nurse-girl, and\nseveral well-dressed young men who were lounging up and down with\ncigars in their mouths.\n\n\"You see,\" remarked Holmes, as we paced to and fro in front of\nthe house, \"this marriage rather simplifies matters. The\nphotograph becomes a double-edged weapon now. The chances are\nthat she would be as averse to its being seen by Mr. Godfrey\nNorton, as our client is to its coming to the eyes of his\nprincess. Now the question is, Where are we to find the\nphotograph?\"\n\n\"Where, indeed?\"\n\n\"It is most unlikely that she carries it about with her. It is\ncabinet size. Too large for easy concealment about a woman's\ndress. She knows that the King is capable of having her waylaid\nand searched. Two attempts of the sort have already been made. We\nmay take it, then, that she does not carry it about with her.\"\n\n\"Where, then?\"\n\n\"Her banker or her lawyer. There is that double possibility. But\nI am inclined to think neither. Women are naturally secretive,\nand they like to do their own secreting. Why should she hand it\nover to anyone else? She could trust her own guardianship, but\nshe could not tell what indirect or political influence might be\nbrought to bear upon a business man. Besides, remember that she\nhad resolved to use it within a few days. It must be where she\ncan lay her hands upon it. It must be in her own house.\"\n\n\"But it has twice been burgled.\"\n\n\"Pshaw! They did not know how to look.\"\n\n\"But how will you look?\"\n\n\"I will not look.\"\n\n\"What then?\"\n\n\"I will get her to show me.\"\n\n\"But she will refuse.\"\n\n\"She will not be able to. But I hear the rumble of wheels. It is\nher carriage. Now carry out my orders to the letter.\"\n\nAs he spoke the gleam of the side-lights of a carriage came round\nthe curve of the avenue. It was a smart little landau which\nrattled up to the door of Briony Lodge. As it pulled up, one of\nthe loafing men at the corner dashed forward to open the door in\nthe hope of earning a copper, but was elbowed away by another\nloafer, who had rushed up with the same intention. A fierce\nquarrel broke out, which was increased by the two guardsmen, who\ntook sides with one of the loungers, and by the scissors-grinder,\nwho was equally hot upon the other side. A blow was struck, and\nin an instant the lady, who had stepped from her carriage, was\nthe centre of a little knot of flushed and struggling men, who\nstruck savagely at each other with their fists and sticks. Holmes\ndashed into the crowd to protect the lady; but just as he reached\nher he gave a cry and dropped to the ground, with the blood\nrunning freely down his face. At his fall the guardsmen took to\ntheir heels in one direction and the loungers in the other, while\na number of better-dressed people, who had watched the scuffle\nwithout taking part in it, crowded in to help the lady and to\nattend to the injured man. Irene Adler, as I will still call her,\nhad hurried up the steps; but she stood at the top with her\nsuperb figure outlined against the lights of the hall, looking\nback into the street.\n\n\"Is the poor gentleman much hurt?\" she asked.\n\n\"He is dead,\" cried several voices.\n\n\"No, no, there's life in him!\" shouted another. \"But he'll be\ngone before you can get him to hospital.\"\n\n\"He's a brave fellow,\" said a woman. \"They would have had the\nlady's purse and watch if it hadn't been for him. They were a\ngang, and a rough one, too. Ah, he's breathing now.\"\n\n\"He can't lie in the street. May we bring him in, marm?\"\n\n\"Surely. Bring him into the sitting-room. There is a comfortable\nsofa. This way, please!\"\n\nSlowly and solemnly he was borne into Briony Lodge and laid out\nin the principal room, while I still observed the proceedings\nfrom my post by the window. The lamps had been lit, but the\nblinds had not been drawn, so that I could see Holmes as he lay\nupon the couch. I do not know whether he was seized with\ncompunction at that moment for the part he was playing, but I\nknow that I never felt more heartily ashamed of myself in my life\nthan when I saw the beautiful creature against whom I was\nconspiring, or the grace and kindliness with which she waited\nupon the injured man. And yet it would be the blackest treachery\nto Holmes to draw back now from the part which he had intrusted\nto me. I hardened my heart, and took the smoke-rocket from under\nmy ulster. After all, I thought, we are not injuring her. We are\nbut preventing her from injuring another.\n\nHolmes had sat up upon the couch, and I saw him motion like a man\nwho is in need of air. A maid rushed across and threw open the\nwindow. At the same instant I saw him raise his hand and at the\nsignal I tossed my rocket into the room with a cry of \"Fire!\" The\nword was no sooner out of my mouth than the whole crowd of\nspectators, well dressed and ill--gentlemen, ostlers, and\nservant-maids--joined in a general shriek of \"Fire!\" Thick clouds\nof smoke curled through the room and out at the open window. I\ncaught a glimpse of rushing figures, and a moment later the voice\nof Holmes from within assuring them that it was a false alarm.\nSlipping through the shouting crowd I made my way to the corner\nof the street, and in ten minutes was rejoiced to find my\nfriend's arm in mine, and to get away from the scene of uproar.\nHe walked swiftly and in silence for some few minutes until we\nhad turned down one of the quiet streets which lead towards the\nEdgeware Road.\n\n\"You did it very nicely, Doctor,\" he remarked. \"Nothing could\nhave been better. It is all right.\"\n\n\"You have the photograph?\"\n\n\"I know where it is.\"\n\n\"And how did you find out?\"\n\n\"She showed me, as I told you she would.\"\n\n\"I am still in the dark.\"\n\n\"I do not wish to make a mystery,\" said he, laughing. \"The matter\nwas perfectly simple. You, of course, saw that everyone in the\nstreet was an accomplice. They were all engaged for the evening.\"\n\n\"I guessed as much.\"\n\n\"Then, when the row broke out, I had a little moist red paint in\nthe palm of my hand. I rushed forward, fell down, clapped my hand\nto my face, and became a piteous spectacle. It is an old trick.\"\n\n\"That also I could fathom.\"\n\n\"Then they carried me in. She was bound to have me in. What else\ncould she do? And into her sitting-room, which was the very room\nwhich I suspected. It lay between that and her bedroom, and I was\ndetermined to see which. They laid me on a couch, I motioned for\nair, they were compelled to open the window, and you had your\nchance.\"\n\n\"How did that help you?\"\n\n\"It was all-important. When a woman thinks that her house is on\nfire, her instinct is at once to rush to the thing which she\nvalues most. It is a perfectly overpowering impulse, and I have\nmore than once taken advantage of it. In the case of the\nDarlington substitution scandal it was of use to me, and also in\nthe Arnsworth Castle business. A married woman grabs at her baby;\nan unmarried one reaches for her jewel-box. Now it was clear to\nme that our lady of to-day had nothing in the house more precious\nto her than what we are in quest of. She would rush to secure it.\nThe alarm of fire was admirably done. The smoke and shouting were\nenough to shake nerves of steel. She responded beautifully. The\nphotograph is in a recess behind a sliding panel just above the\nright bell-pull. She was there in an instant, and I caught a\nglimpse of it as she half-drew it out. When I cried out that it\nwas a false alarm, she replaced it, glanced at the rocket, rushed\nfrom the room, and I have not seen her since. I rose, and, making\nmy excuses, escaped from the house. I hesitated whether to\nattempt to secure the photograph at once; but the coachman had\ncome in, and as he was watching me narrowly it seemed safer to\nwait. A little over-precipitance may ruin all.\"\n\n\"And now?\" I asked.\n\n\"Our quest is practically finished. I shall call with the King\nto-morrow, and with you, if you care to come with us. We will be\nshown into the sitting-room to wait for the lady, but it is\nprobable that when she comes she may find neither us nor the\nphotograph. It might be a satisfaction to his Majesty to regain\nit with his own hands.\"\n\n\"And when will you call?\"\n\n\"At eight in the morning. She will not be up, so that we shall\nhave a clear field. Besides, we must be prompt, for this marriage\nmay mean a complete change in her life and habits. I must wire to\nthe King without delay.\"\n\nWe had reached Baker Street and had stopped at the door. He was\nsearching his pockets for the key when someone passing said:\n\n\"Good-night, Mister Sherlock Holmes.\"\n\nThere were several people on the pavement at the time, but the\ngreeting appeared to come from a slim youth in an ulster who had\nhurried by.\n\n\"I've heard that voice before,\" said Holmes, staring down the\ndimly lit street. \"Now, I wonder who the deuce that could have\nbeen.\"\n\n\nIII.\n\nI slept at Baker Street that night, and we were engaged upon our\ntoast and coffee in the morning when the King of Bohemia rushed\ninto the room.\n\n\"You have really got it!\" he cried, grasping Sherlock Holmes by\neither shoulder and looking eagerly into his face.\n\n\"Not yet.\"\n\n\"But you have hopes?\"\n\n\"I have hopes.\"\n\n\"Then, come. I am all impatience to be gone.\"\n\n\"We must have a cab.\"\n\n\"No, my brougham is waiting.\"\n\n\"Then that will simplify matters.\" We descended and started off\nonce more for Briony Lodge.\n\n\"Irene Adler is married,\" remarked Holmes.\n\n\"Married! When?\"\n\n\"Yesterday.\"\n\n\"But to whom?\"\n\n\"To an English lawyer named Norton.\"\n\n\"But she could not love him.\"\n\n\"I am in hopes that she does.\"\n\n\"And why in hopes?\"\n\n\"Because it would spare your Majesty all fear of future\nannoyance. If the lady loves her husband, she does not love your\nMajesty. If she does not love your Majesty, there is no reason\nwhy she should interfere with your Majesty's plan.\"\n\n\"It is true. And yet--Well! I wish she had been of my own\nstation! What a queen she would have made!\" He relapsed into a\nmoody silence, which was not broken until we drew up in\nSerpentine Avenue.\n\nThe door of Briony Lodge was open, and an elderly woman stood\nupon the steps. She watched us with a sardonic eye as we stepped\nfrom the brougham.\n\n\"Mr. Sherlock Holmes, I believe?\" said she.\n\n\"I am Mr. Holmes,\" answered my companion, looking at her with a\nquestioning and rather startled gaze.\n\n\"Indeed! My mistress told me that you were likely to call. She\nleft this morning with her husband by the 5:15 train from Charing\nCross for the Continent.\"\n\n\"What!\" Sherlock Holmes staggered back, white with chagrin and\nsurprise. \"Do you mean that she has left England?\"\n\n\"Never to return.\"\n\n\"And the papers?\" asked the King hoarsely. \"All is lost.\"\n\n\"We shall see.\" He pushed past the servant and rushed into the\ndrawing-room, followed by the King and myself. The furniture was\nscattered about in every direction, with dismantled shelves and\nopen drawers, as if the lady had hurriedly ransacked them before\nher flight. Holmes rushed at the bell-pull, tore back a small\nsliding shutter, and, plunging in his hand, pulled out a\nphotograph and a letter. The photograph was of Irene Adler\nherself in evening dress, the letter was superscribed to\n\"Sherlock Holmes, Esq. To be left till called for.\" My friend\ntore it open and we all three read it together. It was dated at\nmidnight of the preceding night and ran in this way:\n\n\"MY DEAR MR. SHERLOCK HOLMES,--You really did it very well. You\ntook me in completely. Until after the alarm of fire, I had not a\nsuspicion. But then, when I found how I had betrayed myself, I\nbegan to think. I had been warned against you months ago. I had\nbeen told that if the King employed an agent it would certainly\nbe you. And your address had been given me. Yet, with all this,\nyou made me reveal what you wanted to know. Even after I became\nsuspicious, I found it hard to think evil of such a dear, kind\nold clergyman. But, you know, I have been trained as an actress\nmyself. Male costume is nothing new to me. I often take advantage\nof the freedom which it gives. I sent John, the coachman, to\nwatch you, ran up stairs, got into my walking-clothes, as I call\nthem, and came down just as you departed.\n\n\"Well, I followed you to your door, and so made sure that I was\nreally an object of interest to the celebrated Mr. Sherlock\nHolmes. Then I, rather imprudently, wished you good-night, and\nstarted for the Temple to see my husband.\n\n\"We both thought the best resource was flight, when pursued by\nso formidable an antagonist; so you will find the nest empty when\nyou call to-morrow. As to the photograph, your client may rest in\npeace. I love and am loved by a better man than he. The King may\ndo what he will without hindrance from one whom he has cruelly\nwronged. I keep it only to safeguard myself, and to preserve a\nweapon which will always secure me from any steps which he might\ntake in the future. I leave a photograph which he might care to\npossess; and I remain, dear Mr. Sherlock Holmes,\n\n                                      \"Very truly yours,\n                                   \"IRENE NORTON, née ADLER.\"\n\n\"What a woman--oh, what a woman!\" cried the King of Bohemia, when\nwe had all three read this epistle. \"Did I not tell you how quick\nand resolute she was? Would she not have made an admirable queen?\nIs it not a pity that she was not on my level?\"\n\n\"From what I have seen of the lady she seems indeed to be on a\nvery different level to your Majesty,\" said Holmes coldly. \"I am\nsorry that I have not been able to bring your Majesty's business\nto a more successful conclusion.\"\n\n\"On the contrary, my dear sir,\" cried the King; \"nothing could be\nmore successful. I know that her word is inviolate. The\nphotograph is now as safe as if it were in the fire.\"\n\n\"I am glad to hear your Majesty say so.\"\n\n\"I am immensely indebted to you. Pray tell me in what way I can\nreward you. This ring--\" He slipped an emerald snake ring from\nhis finger and held it out upon the palm of his hand.\n\n\"Your Majesty has something which I should value even more\nhighly,\" said Holmes.\n\n\"You have but to name it.\"\n\n\"This photograph!\"\n\nThe King stared at him in amazement.\n\n\"Irene's photograph!\" he cried. \"Certainly, if you wish it.\"\n\n\"I thank your Majesty. Then there is no more to be done in the\nmatter. I have the honour to wish you a very good-morning.\" He\nbowed, and, turning away without observing the hand which the\nKing had stretched out to him, he set off in my company for his\nchambers.\n\nAnd that was how a great scandal threatened to affect the kingdom\nof Bohemia, and how the best plans of Mr. Sherlock Holmes were\nbeaten by a woman's wit. He used to make merry over the\ncleverness of women, but I have not heard him do it of late. And\nwhen he speaks of Irene Adler, or when he refers to her\nphotograph, it is always under the honourable title of the woman.\n\n\n\nADVENTURE II. THE RED-HEADED LEAGUE\n\nI had called upon my friend, Mr. Sherlock Holmes, one day in the\nautumn of last year and found him in deep conversation with a\nvery stout, florid-faced, elderly gentleman with fiery red hair.\nWith an apology for my intrusion, I was about to withdraw when\nHolmes pulled me abruptly into the room and closed the door\nbehind me.\n\n\"You could not possibly have come at a better time, my dear\nWatson,\" he said cordially.\n\n\"I was afraid that you were engaged.\"\n\n\"So I am. Very much so.\"\n\n\"Then I can wait in the next room.\"\n\n\"Not at all. This gentleman, Mr. Wilson, has been my partner and\nhelper in many of my most successful cases, and I have no\ndoubt that he will be of the utmost use to me in yours also.\"\n\nThe stout gentleman half rose from his chair and gave a bob of\ngreeting, with a quick little questioning glance from his small\nfat-encircled eyes.\n\n\"Try the settee,\" said Holmes, relapsing into his armchair and\nputting his fingertips together, as was his custom when in\njudicial moods. \"I know, my dear Watson, that you share my love\nof all that is bizarre and outside the conventions and humdrum\nroutine of everyday life. You have shown your relish for it by\nthe enthusiasm which has prompted you to chronicle, and, if you\nwill excuse my saying so, somewhat to embellish so many of my own\nlittle adventures.\"\n\n\"Your cases have indeed been of the greatest interest to me,\" I\nobserved.\n\n\"You will remember that I remarked the other day, just before we\nwent into the very simple problem presented by Miss Mary\nSutherland, that for strange effects and extraordinary\ncombinations we must go to life itself, which is always far more\ndaring than any effort of the imagination.\"\n\n\"A proposition which I took the liberty of doubting.\"\n\n\"You did, Doctor, but none the less you must come round to my\nview, for otherwise I shall keep on piling fact upon fact on you\nuntil your reason breaks down under them and acknowledges me to\nbe right. Now, Mr. Jabez Wilson here has been good enough to call\nupon me this morning, and to begin a narrative which promises to\nbe one of the most singular which I have listened to for some\ntime. You have heard me remark that the strangest and most unique\nthings are very often connected not with the larger but with the\nsmaller crimes, and occasionally, indeed, where there is room for\ndoubt whether any positive crime has been committed. As far as I\nhave heard it is impossible for me to say whether the present\ncase is an instance of crime or not, but the course of events is\ncertainly among the most singular that I have ever listened to.\nPerhaps, Mr. Wilson, you would have the great kindness to\nrecommence your narrative. I ask you not merely because my friend\nDr. Watson has not heard the opening part but also because the\npeculiar nature of the story makes me anxious to have every\npossible detail from your lips. As a rule, when I have heard some\nslight indication of the course of events, I am able to guide\nmyself by the thousands of other similar cases which occur to my\nmemory. In the present instance I am forced to admit that the\nfacts are, to the best of my belief, unique.\"\n\nThe portly client puffed out his chest with an appearance of some\nlittle pride and pulled a dirty and wrinkled newspaper from the\ninside pocket of his greatcoat. As he glanced down the\nadvertisement column, with his head thrust forward and the paper\nflattened out upon his knee, I took a good look at the man and\nendeavoured, after the fashion of my companion, to read the\nindications which might be presented by his dress or appearance.\n\nI did not gain very much, however, by my inspection. Our visitor\nbore every mark of being an average commonplace British\ntradesman, obese, pompous, and slow. He wore rather baggy grey\nshepherd's check trousers, a not over-clean black frock-coat,\nunbuttoned in the front, and a drab waistcoat with a heavy brassy\nAlbert chain, and a square pierced bit of metal dangling down as\nan ornament. A frayed top-hat and a faded brown overcoat with a\nwrinkled velvet collar lay upon a chair beside him. Altogether,\nlook as I would, there was nothing remarkable about the man save\nhis blazing red head, and the expression of extreme chagrin and\ndiscontent upon his features.\n\nSherlock Holmes' quick eye took in my occupation, and he shook\nhis head with a smile as he noticed my questioning glances.\n\"Beyond the obvious facts that he has at some time done manual\nlabour, that he takes snuff, that he is a Freemason, that he has\nbeen in China, and that he has done a considerable amount of\nwriting lately, I can deduce nothing else.\"\n\nMr. Jabez Wilson started up in his chair, with his forefinger\nupon the paper, but his eyes upon my companion.\n\n\"How, in the name of good-fortune, did you know all that, Mr.\nHolmes?\" he asked. \"How did you know, for example, that I did\nmanual labour. It's as true as gospel, for I began as a ship's\ncarpenter.\"\n\n\"Your hands, my dear sir. Your right hand is quite a size larger\nthan your left. You have worked with it, and the muscles are more\ndeveloped.\"\n\n\"Well, the snuff, then, and the Freemasonry?\"\n\n\"I won't insult your intelligence by telling you how I read that,\nespecially as, rather against the strict rules of your order, you\nuse an arc-and-compass breastpin.\"\n\n\"Ah, of course, I forgot that. But the writing?\"\n\n\"What else can be indicated by that right cuff so very shiny for\nfive inches, and the left one with the smooth patch near the\nelbow where you rest it upon the desk?\"\n\n\"Well, but China?\"\n\n\"The fish that you have tattooed immediately above your right\nwrist could only have been done in China. I have made a small\nstudy of tattoo marks and have even contributed to the literature\nof the subject. That trick of staining the fishes' scales of a\ndelicate pink is quite peculiar to China. When, in addition, I\nsee a Chinese coin hanging from your watch-chain, the matter\nbecomes even more simple.\"\n\nMr. Jabez Wilson laughed heavily. \"Well, I never!\" said he. \"I\nthought at first that you had done something clever, but I see\nthat there was nothing in it, after all.\"\n\n\"I begin to think, Watson,\" said Holmes, \"that I make a mistake\nin explaining. 'Omne ignotum pro magnifico,' you know, and my\npoor little reputation, such as it is, will suffer shipwreck if I\nam so candid. Can you not find the advertisement, Mr. Wilson?\"\n\n\"Yes, I have got it now,\" he answered with his thick red finger\nplanted halfway down the column. \"Here it is. This is what began\nit all. You just read it for yourself, sir.\"\n\nI took the paper from him and read as follows:\n\n\"TO THE RED-HEADED LEAGUE: On account of the bequest of the late\nEzekiah Hopkins, of Lebanon, Pennsylvania, U. S. A., there is now\nanother vacancy open which entitles a member of the League to a\nsalary of 4 pounds a week for purely nominal services. All\nred-headed men who are sound in body and mind and above the age\nof twenty-one years, are eligible. Apply in person on Monday, at\neleven o'clock, to Duncan Ross, at the offices of the League, 7\nPope's Court, Fleet Street.\"\n\n\"What on earth does this mean?\" I ejaculated after I had twice\nread over the extraordinary announcement.\n\nHolmes chuckled and wriggled in his chair, as was his habit when\nin high spirits. \"It is a little off the beaten track, isn't it?\"\nsaid he. \"And now, Mr. Wilson, off you go at scratch and tell us\nall about yourself, your household, and the effect which this\nadvertisement had upon your fortunes. You will first make a note,\nDoctor, of the paper and the date.\"\n\n\"It is The Morning Chronicle of April 27, 1890. Just two months\nago.\"\n\n\"Very good. Now, Mr. Wilson?\"\n\n\"Well, it is just as I have been telling you, Mr. Sherlock\nHolmes,\" said Jabez Wilson, mopping his forehead; \"I have a small\npawnbroker's business at Coburg Square, near the City. It's not a\nvery large affair, and of late years it has not done more than\njust give me a living. I used to be able to keep two assistants,\nbut now I only keep one; and I would have a job to pay him but\nthat he is willing to come for half wages so as to learn the\nbusiness.\"\n\n\"What is the name of this obliging youth?\" asked Sherlock Holmes.\n\n\"His name is Vincent Spaulding, and he's not such a youth,\neither. It's hard to say his age. I should not wish a smarter\nassistant, Mr. Holmes; and I know very well that he could better\nhimself and earn twice what I am able to give him. But, after\nall, if he is satisfied, why should I put ideas in his head?\"\n\n\"Why, indeed? You seem most fortunate in having an employé who\ncomes under the full market price. It is not a common experience\namong employers in this age. I don't know that your assistant is\nnot as remarkable as your advertisement.\"\n\n\"Oh, he has his faults, too,\" said Mr. Wilson. \"Never was such a\nfellow for photography. Snapping away with a camera when he ought\nto be improving his mind, and then diving down into the cellar\nlike a rabbit into its hole to develop his pictures. That is his\nmain fault, but on the whole he's a good worker. There's no vice\nin him.\"\n\n\"He is still with you, I presume?\"\n\n\"Yes, sir. He and a girl of fourteen, who does a bit of simple\ncooking and keeps the place clean--that's all I have in the\nhouse, for I am a widower and never had any family. We live very\nquietly, sir, the three of us; and we keep a roof over our heads\nand pay our debts, if we do nothing more.\n\n\"The first thing that put us out was that advertisement.\nSpaulding, he came down into the office just this day eight\nweeks, with this very paper in his hand, and he says:\n\n\"'I wish to the Lord, Mr. Wilson, that I was a red-headed man.'\n\n\"'Why that?' I asks.\n\n\"'Why,' says he, 'here's another vacancy on the League of the\nRed-headed Men. It's worth quite a little fortune to any man who\ngets it, and I understand that there are more vacancies than\nthere are men, so that the trustees are at their wits' end what\nto do with the money. If my hair would only change colour, here's\na nice little crib all ready for me to step into.'\n\n\"'Why, what is it, then?' I asked. You see, Mr. Holmes, I am a\nvery stay-at-home man, and as my business came to me instead of\nmy having to go to it, I was often weeks on end without putting\nmy foot over the door-mat. In that way I didn't know much of what\nwas going on outside, and I was always glad of a bit of news.\n\n\"'Have you never heard of the League of the Red-headed Men?' he\nasked with his eyes open.\n\n\"'Never.'\n\n\"'Why, I wonder at that, for you are eligible yourself for one\nof the vacancies.'\n\n\"'And what are they worth?' I asked.\n\n\"'Oh, merely a couple of hundred a year, but the work is slight,\nand it need not interfere very much with one's other\noccupations.'\n\n\"Well, you can easily think that that made me prick up my ears,\nfor the business has not been over-good for some years, and an\nextra couple of hundred would have been very handy.\n\n\"'Tell me all about it,' said I.\n\n\"'Well,' said he, showing me the advertisement, 'you can see for\nyourself that the League has a vacancy, and there is the address\nwhere you should apply for particulars. As far as I can make out,\nthe League was founded by an American millionaire, Ezekiah\nHopkins, who was very peculiar in his ways. He was himself\nred-headed, and he had a great sympathy for all red-headed men;\nso when he died it was found that he had left his enormous\nfortune in the hands of trustees, with instructions to apply the\ninterest to the providing of easy berths to men whose hair is of\nthat colour. From all I hear it is splendid pay and very little to\ndo.'\n\n\"'But,' said I, 'there would be millions of red-headed men who\nwould apply.'\n\n\"'Not so many as you might think,' he answered. 'You see it is\nreally confined to Londoners, and to grown men. This American had\nstarted from London when he was young, and he wanted to do the\nold town a good turn. Then, again, I have heard it is no use your\napplying if your hair is light red, or dark red, or anything but\nreal bright, blazing, fiery red. Now, if you cared to apply, Mr.\nWilson, you would just walk in; but perhaps it would hardly be\nworth your while to put yourself out of the way for the sake of a\nfew hundred pounds.'\n\n\"Now, it is a fact, gentlemen, as you may see for yourselves,\nthat my hair is of a very full and rich tint, so that it seemed\nto me that if there was to be any competition in the matter I\nstood as good a chance as any man that I had ever met. Vincent\nSpaulding seemed to know so much about it that I thought he might\nprove useful, so I just ordered him to put up the shutters for\nthe day and to come right away with me. He was very willing to\nhave a holiday, so we shut the business up and started off for\nthe address that was given us in the advertisement.\n\n\"I never hope to see such a sight as that again, Mr. Holmes. From\nnorth, south, east, and west every man who had a shade of red in\nhis hair had tramped into the city to answer the advertisement.\nFleet Street was choked with red-headed folk, and Pope's Court\nlooked like a coster's orange barrow. I should not have thought\nthere were so many in the whole country as were brought together\nby that single advertisement. Every shade of colour they\nwere--straw, lemon, orange, brick, Irish-setter, liver, clay;\nbut, as Spaulding said, there were not many who had the real\nvivid flame-coloured tint. When I saw how many were waiting, I\nwould have given it up in despair; but Spaulding would not hear\nof it. How he did it I could not imagine, but he pushed and\npulled and butted until he got me through the crowd, and right up\nto the steps which led to the office. There was a double stream\nupon the stair, some going up in hope, and some coming back\ndejected; but we wedged in as well as we could and soon found\nourselves in the office.\"\n\n\"Your experience has been a most entertaining one,\" remarked\nHolmes as his client paused and refreshed his memory with a huge\npinch of snuff. \"Pray continue your very interesting statement.\"\n\n\"There was nothing in the office but a couple of wooden chairs\nand a deal table, behind which sat a small man with a head that\nwas even redder than mine. He said a few words to each candidate\nas he came up, and then he always managed to find some fault in\nthem which would disqualify them. Getting a vacancy did not seem\nto be such a very easy matter, after all. However, when our turn\ncame the little man was much more favourable to me than to any of\nthe others, and he closed the door as we entered, so that he\nmight have a private word with us.\n\n\"'This is Mr. Jabez Wilson,' said my assistant, 'and he is\nwilling to fill a vacancy in the League.'\n\n\"'And he is admirably suited for it,' the other answered. 'He has\nevery requirement. I cannot recall when I have seen anything so\nfine.' He took a step backward, cocked his head on one side, and\ngazed at my hair until I felt quite bashful. Then suddenly he\nplunged forward, wrung my hand, and congratulated me warmly on my\nsuccess.\n\n\"'It would be injustice to hesitate,' said he. 'You will,\nhowever, I am sure, excuse me for taking an obvious precaution.'\nWith that he seized my hair in both his hands, and tugged until I\nyelled with the pain. 'There is water in your eyes,' said he as\nhe released me. 'I perceive that all is as it should be. But we\nhave to be careful, for we have twice been deceived by wigs and\nonce by paint. I could tell you tales of cobbler's wax which\nwould disgust you with human nature.' He stepped over to the\nwindow and shouted through it at the top of his voice that the\nvacancy was filled. A groan of disappointment came up from below,\nand the folk all trooped away in different directions until there\nwas not a red-head to be seen except my own and that of the\nmanager.\n\n\"'My name,' said he, 'is Mr. Duncan Ross, and I am myself one of\nthe pensioners upon the fund left by our noble benefactor. Are\nyou a married man, Mr. Wilson? Have you a family?'\n\n\"I answered that I had not.\n\n\"His face fell immediately.\n\n\"'Dear me!' he said gravely, 'that is very serious indeed! I am\nsorry to hear you say that. The fund was, of course, for the\npropagation and spread of the red-heads as well as for their\nmaintenance. It is exceedingly unfortunate that you should be a\nbachelor.'\n\n\"My face lengthened at this, Mr. Holmes, for I thought that I was\nnot to have the vacancy after all; but after thinking it over for\na few minutes he said that it would be all right.\n\n\"'In the case of another,' said he, 'the objection might be\nfatal, but we must stretch a point in favour of a man with such a\nhead of hair as yours. When shall you be able to enter upon your\nnew duties?'\n\n\"'Well, it is a little awkward, for I have a business already,'\nsaid I.\n\n\"'Oh, never mind about that, Mr. Wilson!' said Vincent Spaulding.\n'I should be able to look after that for you.'\n\n\"'What would be the hours?' I asked.\n\n\"'Ten to two.'\n\n\"Now a pawnbroker's business is mostly done of an evening, Mr.\nHolmes, especially Thursday and Friday evening, which is just\nbefore pay-day; so it would suit me very well to earn a little in\nthe mornings. Besides, I knew that my assistant was a good man,\nand that he would see to anything that turned up.\n\n\"'That would suit me very well,' said I. 'And the pay?'\n\n\"'Is 4 pounds a week.'\n\n\"'And the work?'\n\n\"'Is purely nominal.'\n\n\"'What do you call purely nominal?'\n\n\"'Well, you have to be in the office, or at least in the\nbuilding, the whole time. If you leave, you forfeit your whole\nposition forever. The will is very clear upon that point. You\ndon't comply with the conditions if you budge from the office\nduring that time.'\n\n\"'It's only four hours a day, and I should not think of leaving,'\nsaid I.\n\n\"'No excuse will avail,' said Mr. Duncan Ross; 'neither sickness\nnor business nor anything else. There you must stay, or you lose\nyour billet.'\n\n\"'And the work?'\n\n\"'Is to copy out the \"Encyclopaedia Britannica.\" There is the first\nvolume of it in that press. You must find your own ink, pens, and\nblotting-paper, but we provide this table and chair. Will you be\nready to-morrow?'\n\n\"'Certainly,' I answered.\n\n\"'Then, good-bye, Mr. Jabez Wilson, and let me congratulate you\nonce more on the important position which you have been fortunate\nenough to gain.' He bowed me out of the room and I went home with\nmy assistant, hardly knowing what to say or do, I was so pleased\nat my own good fortune.\n\n\"Well, I thought over the matter all day, and by evening I was in\nlow spirits again; for I had quite persuaded myself that the\nwhole affair must be some great hoax or fraud, though what its\nobject might be I could not imagine. It seemed altogether past\nbelief that anyone could make such a will, or that they would pay\nsuch a sum for doing anything so simple as copying out the\n'Encyclopaedia Britannica.' Vincent Spaulding did what he could to\ncheer me up, but by bedtime I had reasoned myself out of the\nwhole thing. However, in the morning I determined to have a look\nat it anyhow, so I bought a penny bottle of ink, and with a\nquill-pen, and seven sheets of foolscap paper, I started off for\nPope's Court.\n\n\"Well, to my surprise and delight, everything was as right as\npossible. The table was set out ready for me, and Mr. Duncan Ross\nwas there to see that I got fairly to work. He started me off\nupon the letter A, and then he left me; but he would drop in from\ntime to time to see that all was right with me. At two o'clock he\nbade me good-day, complimented me upon the amount that I had\nwritten, and locked the door of the office after me.\n\n\"This went on day after day, Mr. Holmes, and on Saturday the\nmanager came in and planked down four golden sovereigns for my\nweek's work. It was the same next week, and the same the week\nafter. Every morning I was there at ten, and every afternoon I\nleft at two. By degrees Mr. Duncan Ross took to coming in only\nonce of a morning, and then, after a time, he did not come in at\nall. Still, of course, I never dared to leave the room for an\ninstant, for I was not sure when he might come, and the billet\nwas such a good one, and suited me so well, that I would not risk\nthe loss of it.\n\n\"Eight weeks passed away like this, and I had written about\nAbbots and Archery and Armour and Architecture and Attica, and\nhoped with diligence that I might get on to the B's before very\nlong. It cost me something in foolscap, and I had pretty nearly\nfilled a shelf with my writings. And then suddenly the whole\nbusiness came to an end.\"\n\n\"To an end?\"\n\n\"Yes, sir. And no later than this morning. I went to my work as\nusual at ten o'clock, but the door was shut and locked, with a\nlittle square of cardboard hammered on to the middle of the\npanel with a tack. Here it is, and you can read for yourself.\"\n\nHe held up a piece of white cardboard about the size of a sheet\nof note-paper. It read in this fashion:\n\n                  THE RED-HEADED LEAGUE\n\n                           IS\n\n                        DISSOLVED.\n\n                     October 9, 1890.\n\nSherlock Holmes and I surveyed this curt announcement and the\nrueful face behind it, until the comical side of the affair so\ncompletely overtopped every other consideration that we both\nburst out into a roar of laughter.\n\n\"I cannot see that there is anything very funny,\" cried our\nclient, flushing up to the roots of his flaming head. \"If you can\ndo nothing better than laugh at me, I can go elsewhere.\"\n\n\"No, no,\" cried Holmes, shoving him back into the chair from\nwhich he had half risen. \"I really wouldn't miss your case for\nthe world. It is most refreshingly unusual. But there is, if you\nwill excuse my saying so, something just a little funny about it.\nPray what steps did you take when you found the card upon the\ndoor?\"\n\n\"I was staggered, sir. I did not know what to do. Then I called\nat the offices round, but none of them seemed to know anything\nabout it. Finally, I went to the landlord, who is an accountant\nliving on the ground-floor, and I asked him if he could tell me\nwhat had become of the Red-headed League. He said that he had\nnever heard of any such body. Then I asked him who Mr. Duncan\nRoss was. He answered that the name was new to him.\n\n\"'Well,' said I, 'the gentleman at No. 4.'\n\n\"'What, the red-headed man?'\n\n\"'Yes.'\n\n\"'Oh,' said he, 'his name was William Morris. He was a solicitor\nand was using my room as a temporary convenience until his new\npremises were ready. He moved out yesterday.'\n\n\"'Where could I find him?'\n\n\"'Oh, at his new offices. He did tell me the address. Yes, 17\nKing Edward Street, near St. Paul's.'\n\n\"I started off, Mr. Holmes, but when I got to that address it was\na manufactory of artificial knee-caps, and no one in it had ever\nheard of either Mr. William Morris or Mr. Duncan Ross.\"\n\n\"And what did you do then?\" asked Holmes.\n\n\"I went home to Saxe-Coburg Square, and I took the advice of my\nassistant. But he could not help me in any way. He could only say\nthat if I waited I should hear by post. But that was not quite\ngood enough, Mr. Holmes. I did not wish to lose such a place\nwithout a struggle, so, as I had heard that you were good enough\nto give advice to poor folk who were in need of it, I came right\naway to you.\"\n\n\"And you did very wisely,\" said Holmes. \"Your case is an\nexceedingly remarkable one, and I shall be happy to look into it.\nFrom what you have told me I think that it is possible that\ngraver issues hang from it than might at first sight appear.\"\n\n\"Grave enough!\" said Mr. Jabez Wilson. \"Why, I have lost four\npound a week.\"\n\n\"As far as you are personally concerned,\" remarked Holmes, \"I do\nnot see that you have any grievance against this extraordinary\nleague. On the contrary, you are, as I understand, richer by some\n30 pounds, to say nothing of the minute knowledge which you have\ngained on every subject which comes under the letter A. You have\nlost nothing by them.\"\n\n\"No, sir. But I want to find out about them, and who they are,\nand what their object was in playing this prank--if it was a\nprank--upon me. It was a pretty expensive joke for them, for it\ncost them two and thirty pounds.\"\n\n\"We shall endeavour to clear up these points for you. And, first,\none or two questions, Mr. Wilson. This assistant of yours who\nfirst called your attention to the advertisement--how long had he\nbeen with you?\"\n\n\"About a month then.\"\n\n\"How did he come?\"\n\n\"In answer to an advertisement.\"\n\n\"Was he the only applicant?\"\n\n\"No, I had a dozen.\"\n\n\"Why did you pick him?\"\n\n\"Because he was handy and would come cheap.\"\n\n\"At half-wages, in fact.\"\n\n\"Yes.\"\n\n\"What is he like, this Vincent Spaulding?\"\n\n\"Small, stout-built, very quick in his ways, no hair on his face,\nthough he's not short of thirty. Has a white splash of acid upon\nhis forehead.\"\n\nHolmes sat up in his chair in considerable excitement. \"I thought\nas much,\" said he. \"Have you ever observed that his ears are\npierced for earrings?\"\n\n\"Yes, sir. He told me that a gipsy had done it for him when he\nwas a lad.\"\n\n\"Hum!\" said Holmes, sinking back in deep thought. \"He is still\nwith you?\"\n\n\"Oh, yes, sir; I have only just left him.\"\n\n\"And has your business been attended to in your absence?\"\n\n\"Nothing to complain of, sir. There's never very much to do of a\nmorning.\"\n\n\"That will do, Mr. Wilson. I shall be happy to give you an\nopinion upon the subject in the course of a day or two. To-day is\nSaturday, and I hope that by Monday we may come to a conclusion.\"\n\n\"Well, Watson,\" said Holmes when our visitor had left us, \"what\ndo you make of it all?\"\n\n\"I make nothing of it,\" I answered frankly. \"It is a most\nmysterious business.\"\n\n\"As a rule,\" said Holmes, \"the more bizarre a thing is the less\nmysterious it proves to be. It is your commonplace, featureless\ncrimes which are really puzzling, just as a commonplace face is\nthe most difficult to identify. But I must be prompt over this\nmatter.\"\n\n\"What are you going to do, then?\" I asked.\n\n\"To smoke,\" he answered. \"It is quite a three pipe problem, and I\nbeg that you won't speak to me for fifty minutes.\" He curled\nhimself up in his chair, with his thin knees drawn up to his\nhawk-like nose, and there he sat with his eyes closed and his\nblack clay pipe thrusting out like the bill of some strange bird.\nI had come to the conclusion that he had dropped asleep, and\nindeed was nodding myself, when he suddenly sprang out of his\nchair with the gesture of a man who has made up his mind and put\nhis pipe down upon the mantelpiece.\n\n\"Sarasate plays at the St. James's Hall this afternoon,\" he\nremarked. \"What do you think, Watson? Could your patients spare\nyou for a few hours?\"\n\n\"I have nothing to do to-day. My practice is never very\nabsorbing.\"\n\n\"Then put on your hat and come. I am going through the City\nfirst, and we can have some lunch on the way. I observe that\nthere is a good deal of German music on the programme, which is\nrather more to my taste than Italian or French. It is\nintrospective, and I want to introspect. Come along!\"\n\nWe travelled by the Underground as far as Aldersgate; and a short\nwalk took us to Saxe-Coburg Square, the scene of the singular\nstory which we had listened to in the morning. It was a poky,\nlittle, shabby-genteel place, where four lines of dingy\ntwo-storied brick houses looked out into a small railed-in\nenclosure, where a lawn of weedy grass and a few clumps of faded\nlaurel-bushes made a hard fight against a smoke-laden and\nuncongenial atmosphere. Three gilt balls and a brown board with\n\"JABEZ WILSON\" in white letters, upon a corner house, announced\nthe place where our red-headed client carried on his business.\nSherlock Holmes stopped in front of it with his head on one side\nand looked it all over, with his eyes shining brightly between\npuckered lids. Then he walked slowly up the street, and then down\nagain to the corner, still looking keenly at the houses. Finally\nhe returned to the pawnbroker's, and, having thumped vigorously\nupon the pavement with his stick two or three times, he went up\nto the door and knocked. It was instantly opened by a\nbright-looking, clean-shaven young fellow, who asked him to step\nin.\n\n\"Thank you,\" said Holmes, \"I only wished to ask you how you would\ngo from here to the Strand.\"\n\n\"Third right, fourth left,\" answered the assistant promptly,\nclosing the door.\n\n\"Smart fellow, that,\" observed Holmes as we walked away. \"He is,\nin my judgment, the fourth smartest man in London, and for daring\nI am not sure that he has not a claim to be third. I have known\nsomething of him before.\"\n\n\"Evidently,\" said I, \"Mr. Wilson's assistant counts for a good\ndeal in this mystery of the Red-headed League. I am sure that you\ninquired your way merely in order that you might see him.\"\n\n\"Not him.\"\n\n\"What then?\"\n\n\"The knees of his trousers.\"\n\n\"And what did you see?\"\n\n\"What I expected to see.\"\n\n\"Why did you beat the pavement?\"\n\n\"My dear doctor, this is a time for observation, not for talk. We\nare spies in an enemy's country. We know something of Saxe-Coburg\nSquare. Let us now explore the parts which lie behind it.\"\n\nThe road in which we found ourselves as we turned round the\ncorner from the retired Saxe-Coburg Square presented as great a\ncontrast to it as the front of a picture does to the back. It was\none of the main arteries which conveyed the traffic of the City\nto the north and west. The roadway was blocked with the immense\nstream of commerce flowing in a double tide inward and outward,\nwhile the footpaths were black with the hurrying swarm of\npedestrians. It was difficult to realise as we looked at the line\nof fine shops and stately business premises that they really\nabutted on the other side upon the faded and stagnant square\nwhich we had just quitted.\n\n\"Let me see,\" said Holmes, standing at the corner and glancing\nalong the line, \"I should like just to remember the order of the\nhouses here. It is a hobby of mine to have an exact knowledge of\nLondon. There is Mortimer's, the tobacconist, the little\nnewspaper shop, the Coburg branch of the City and Suburban Bank,\nthe Vegetarian Restaurant, and McFarlane's carriage-building\ndepot. That carries us right on to the other block. And now,\nDoctor, we've done our work, so it's time we had some play. A\nsandwich and a cup of coffee, and then off to violin-land, where\nall is sweetness and delicacy and harmony, and there are no\nred-headed clients to vex us with their conundrums.\"\n\nMy friend was an enthusiastic musician, being himself not only a\nvery capable performer but a composer of no ordinary merit. All\nthe afternoon he sat in the stalls wrapped in the most perfect\nhappiness, gently waving his long, thin fingers in time to the\nmusic, while his gently smiling face and his languid, dreamy eyes\nwere as unlike those of Holmes the sleuth-hound, Holmes the\nrelentless, keen-witted, ready-handed criminal agent, as it was\npossible to conceive. In his singular character the dual nature\nalternately asserted itself, and his extreme exactness and\nastuteness represented, as I have often thought, the reaction\nagainst the poetic and contemplative mood which occasionally\npredominated in him. The swing of his nature took him from\nextreme languor to devouring energy; and, as I knew well, he was\nnever so truly formidable as when, for days on end, he had been\nlounging in his armchair amid his improvisations and his\nblack-letter editions. Then it was that the lust of the chase\nwould suddenly come upon him, and that his brilliant reasoning\npower would rise to the level of intuition, until those who were\nunacquainted with his methods would look askance at him as on a\nman whose knowledge was not that of other mortals. When I saw him\nthat afternoon so enwrapped in the music at St. James's Hall I\nfelt that an evil time might be coming upon those whom he had set\nhimself to hunt down.\n\n\"You want to go home, no doubt, Doctor,\" he remarked as we\nemerged.\n\n\"Yes, it would be as well.\"\n\n\"And I have some business to do which will take some hours. This\nbusiness at Coburg Square is serious.\"\n\n\"Why serious?\"\n\n\"A considerable crime is in contemplation. I have every reason to\nbelieve that we shall be in time to stop it. But to-day being\nSaturday rather complicates matters. I shall want your help\nto-night.\"\n\n\"At what time?\"\n\n\"Ten will be early enough.\"\n\n\"I shall be at Baker Street at ten.\"\n\n\"Very well. And, I say, Doctor, there may be some little danger,\nso kindly put your army revolver in your pocket.\" He waved his\nhand, turned on his heel, and disappeared in an instant among the\ncrowd.\n\nI trust that I am not more dense than my neighbours, but I was\nalways oppressed with a sense of my own stupidity in my dealings\nwith Sherlock Holmes. Here I had heard what he had heard, I had\nseen what he had seen, and yet from his words it was evident that\nhe saw clearly not only what had happened but what was about to\nhappen, while to me the whole business was still confused and\ngrotesque. As I drove home to my house in Kensington I thought\nover it all, from the extraordinary story of the red-headed\ncopier of the \"Encyclopaedia\" down to the visit to Saxe-Coburg\nSquare, and the ominous words with which he had parted from me.\nWhat was this nocturnal expedition, and why should I go armed?\nWhere were we going, and what were we to do? I had the hint from\nHolmes that this smooth-faced pawnbroker's assistant was a\nformidable man--a man who might play a deep game. I tried to\npuzzle it out, but gave it up in despair and set the matter aside\nuntil night should bring an explanation.\n\nIt was a quarter-past nine when I started from home and made my\nway across the Park, and so through Oxford Street to Baker\nStreet. Two hansoms were standing at the door, and as I entered\nthe passage I heard the sound of voices from above. On entering\nhis room I found Holmes in animated conversation with two men,\none of whom I recognised as Peter Jones, the official police\nagent, while the other was a long, thin, sad-faced man, with a\nvery shiny hat and oppressively respectable frock-coat.\n\n\"Ha! Our party is complete,\" said Holmes, buttoning up his\npea-jacket and taking his heavy hunting crop from the rack.\n\"Watson, I think you know Mr. Jones, of Scotland Yard? Let me\nintroduce you to Mr. Merryweather, who is to be our companion in\nto-night's adventure.\"\n\n\"We're hunting in couples again, Doctor, you see,\" said Jones in\nhis consequential way. \"Our friend here is a wonderful man for\nstarting a chase. All he wants is an old dog to help him to do\nthe running down.\"\n\n\"I hope a wild goose may not prove to be the end of our chase,\"\nobserved Mr. Merryweather gloomily.\n\n\"You may place considerable confidence in Mr. Holmes, sir,\" said\nthe police agent loftily. \"He has his own little methods, which\nare, if he won't mind my saying so, just a little too theoretical\nand fantastic, but he has the makings of a detective in him. It\nis not too much to say that once or twice, as in that business of\nthe Sholto murder and the Agra treasure, he has been more nearly\ncorrect than the official force.\"\n\n\"Oh, if you say so, Mr. Jones, it is all right,\" said the\nstranger with deference. \"Still, I confess that I miss my rubber.\nIt is the first Saturday night for seven-and-twenty years that I\nhave not had my rubber.\"\n\n\"I think you will find,\" said Sherlock Holmes, \"that you will\nplay for a higher stake to-night than you have ever done yet, and\nthat the play will be more exciting. For you, Mr. Merryweather,\nthe stake will be some 30,000 pounds; and for you, Jones, it will\nbe the man upon whom you wish to lay your hands.\"\n\n\"John Clay, the murderer, thief, smasher, and forger. He's a\nyoung man, Mr. Merryweather, but he is at the head of his\nprofession, and I would rather have my bracelets on him than on\nany criminal in London. He's a remarkable man, is young John\nClay. His grandfather was a royal duke, and he himself has been\nto Eton and Oxford. His brain is as cunning as his fingers, and\nthough we meet signs of him at every turn, we never know where to\nfind the man himself. He'll crack a crib in Scotland one week,\nand be raising money to build an orphanage in Cornwall the next.\nI've been on his track for years and have never set eyes on him\nyet.\"\n\n\"I hope that I may have the pleasure of introducing you to-night.\nI've had one or two little turns also with Mr. John Clay, and I\nagree with you that he is at the head of his profession. It is\npast ten, however, and quite time that we started. If you two\nwill take the first hansom, Watson and I will follow in the\nsecond.\"\n\nSherlock Holmes was not very communicative during the long drive\nand lay back in the cab humming the tunes which he had heard in\nthe afternoon. We rattled through an endless labyrinth of gas-lit\nstreets until we emerged into Farrington Street.\n\n\"We are close there now,\" my friend remarked. \"This fellow\nMerryweather is a bank director, and personally interested in the\nmatter. I thought it as well to have Jones with us also. He is\nnot a bad fellow, though an absolute imbecile in his profession.\nHe has one positive virtue. He is as brave as a bulldog and as\ntenacious as a lobster if he gets his claws upon anyone. Here we\nare, and they are waiting for us.\"\n\nWe had reached the same crowded thoroughfare in which we had\nfound ourselves in the morning. Our cabs were dismissed, and,\nfollowing the guidance of Mr. Merryweather, we passed down a\nnarrow passage and through a side door, which he opened for us.\nWithin there was a small corridor, which ended in a very massive\niron gate. This also was opened, and led down a flight of winding\nstone steps, which terminated at another formidable gate. Mr.\nMerryweather stopped to light a lantern, and then conducted us\ndown a dark, earth-smelling passage, and so, after opening a\nthird door, into a huge vault or cellar, which was piled all\nround with crates and massive boxes.\n\n\"You are not very vulnerable from above,\" Holmes remarked as he\nheld up the lantern and gazed about him.\n\n\"Nor from below,\" said Mr. Merryweather, striking his stick upon\nthe flags which lined the floor. \"Why, dear me, it sounds quite\nhollow!\" he remarked, looking up in surprise.\n\n\"I must really ask you to be a little more quiet!\" said Holmes\nseverely. \"You have already imperilled the whole success of our\nexpedition. Might I beg that you would have the goodness to sit\ndown upon one of those boxes, and not to interfere?\"\n\nThe solemn Mr. Merryweather perched himself upon a crate, with a\nvery injured expression upon his face, while Holmes fell upon his\nknees upon the floor and, with the lantern and a magnifying lens,\nbegan to examine minutely the cracks between the stones. A few\nseconds sufficed to satisfy him, for he sprang to his feet again\nand put his glass in his pocket.\n\n\"We have at least an hour before us,\" he remarked, \"for they can\nhardly take any steps until the good pawnbroker is safely in bed.\nThen they will not lose a minute, for the sooner they do their\nwork the longer time they will have for their escape. We are at\npresent, Doctor--as no doubt you have divined--in the cellar of\nthe City branch of one of the principal London banks. Mr.\nMerryweather is the chairman of directors, and he will explain to\nyou that there are reasons why the more daring criminals of\nLondon should take a considerable interest in this cellar at\npresent.\"\n\n\"It is our French gold,\" whispered the director. \"We have had\nseveral warnings that an attempt might be made upon it.\"\n\n\"Your French gold?\"\n\n\"Yes. We had occasion some months ago to strengthen our resources\nand borrowed for that purpose 30,000 napoleons from the Bank of\nFrance. It has become known that we have never had occasion to\nunpack the money, and that it is still lying in our cellar. The\ncrate upon which I sit contains 2,000 napoleons packed between\nlayers of lead foil. Our reserve of bullion is much larger at\npresent than is usually kept in a single branch office, and the\ndirectors have had misgivings upon the subject.\"\n\n\"Which were very well justified,\" observed Holmes. \"And now it is\ntime that we arranged our little plans. I expect that within an\nhour matters will come to a head. In the meantime Mr.\nMerryweather, we must put the screen over that dark lantern.\"\n\n\"And sit in the dark?\"\n\n\"I am afraid so. I had brought a pack of cards in my pocket, and\nI thought that, as we were a partie carrée, you might have your\nrubber after all. But I see that the enemy's preparations have\ngone so far that we cannot risk the presence of a light. And,\nfirst of all, we must choose our positions. These are daring men,\nand though we shall take them at a disadvantage, they may do us\nsome harm unless we are careful. I shall stand behind this crate,\nand do you conceal yourselves behind those. Then, when I flash a\nlight upon them, close in swiftly. If they fire, Watson, have no\ncompunction about shooting them down.\"\n\nI placed my revolver, cocked, upon the top of the wooden case\nbehind which I crouched. Holmes shot the slide across the front\nof his lantern and left us in pitch darkness--such an absolute\ndarkness as I have never before experienced. The smell of hot\nmetal remained to assure us that the light was still there, ready\nto flash out at a moment's notice. To me, with my nerves worked\nup to a pitch of expectancy, there was something depressing and\nsubduing in the sudden gloom, and in the cold dank air of the\nvault.\n\n\"They have but one retreat,\" whispered Holmes. \"That is back\nthrough the house into Saxe-Coburg Square. I hope that you have\ndone what I asked you, Jones?\"\n\n\"I have an inspector and two officers waiting at the front door.\"\n\n\"Then we have stopped all the holes. And now we must be silent\nand wait.\"\n\nWhat a time it seemed! From comparing notes afterwards it was but\nan hour and a quarter, yet it appeared to me that the night must\nhave almost gone and the dawn be breaking above us. My limbs\nwere weary and stiff, for I feared to change my position; yet my\nnerves were worked up to the highest pitch of tension, and my\nhearing was so acute that I could not only hear the gentle\nbreathing of my companions, but I could distinguish the deeper,\nheavier in-breath of the bulky Jones from the thin, sighing note\nof the bank director. From my position I could look over the case\nin the direction of the floor. Suddenly my eyes caught the glint\nof a light.\n\nAt first it was but a lurid spark upon the stone pavement. Then\nit lengthened out until it became a yellow line, and then,\nwithout any warning or sound, a gash seemed to open and a hand\nappeared, a white, almost womanly hand, which felt about in the\ncentre of the little area of light. For a minute or more the\nhand, with its writhing fingers, protruded out of the floor. Then\nit was withdrawn as suddenly as it appeared, and all was dark\nagain save the single lurid spark which marked a chink between\nthe stones.\n\nIts disappearance, however, was but momentary. With a rending,\ntearing sound, one of the broad, white stones turned over upon\nits side and left a square, gaping hole, through which streamed\nthe light of a lantern. Over the edge there peeped a clean-cut,\nboyish face, which looked keenly about it, and then, with a hand\non either side of the aperture, drew itself shoulder-high and\nwaist-high, until one knee rested upon the edge. In another\ninstant he stood at the side of the hole and was hauling after\nhim a companion, lithe and small like himself, with a pale face\nand a shock of very red hair.\n\n\"It's all clear,\" he whispered. \"Have you the chisel and the\nbags? Great Scott! Jump, Archie, jump, and I'll swing for it!\"\n\nSherlock Holmes had sprung out and seized the intruder by the\ncollar. The other dived down the hole, and I heard the sound of\nrending cloth as Jones clutched at his skirts. The light flashed\nupon the barrel of a revolver, but Holmes' hunting crop came\ndown on the man's wrist, and the pistol clinked upon the stone\nfloor.\n\n\"It's no use, John Clay,\" said Holmes blandly. \"You have no\nchance at all.\"\n\n\"So I see,\" the other answered with the utmost coolness. \"I fancy\nthat my pal is all right, though I see you have got his\ncoat-tails.\"\n\n\"There are three men waiting for him at the door,\" said Holmes.\n\n\"Oh, indeed! You seem to have done the thing very completely. I\nmust compliment you.\"\n\n\"And I you,\" Holmes answered. \"Your red-headed idea was very new\nand effective.\"\n\n\"You'll see your pal again presently,\" said Jones. \"He's quicker\nat climbing down holes than I am. Just hold out while I fix the\nderbies.\"\n\n\"I beg that you will not touch me with your filthy hands,\"\nremarked our prisoner as the handcuffs clattered upon his wrists.\n\"You may not be aware that I have royal blood in my veins. Have\nthe goodness, also, when you address me always to say 'sir' and\n'please.'\"\n\n\"All right,\" said Jones with a stare and a snigger. \"Well, would\nyou please, sir, march upstairs, where we can get a cab to carry\nyour Highness to the police-station?\"\n\n\"That is better,\" said John Clay serenely. He made a sweeping bow\nto the three of us and walked quietly off in the custody of the\ndetective.\n\n\"Really, Mr. Holmes,\" said Mr. Merryweather as we followed them\nfrom the cellar, \"I do not know how the bank can thank you or\nrepay you. There is no doubt that you have detected and defeated\nin the most complete manner one of the most determined attempts\nat bank robbery that have ever come within my experience.\"\n\n\"I have had one or two little scores of my own to settle with Mr.\nJohn Clay,\" said Holmes. \"I have been at some small expense over\nthis matter, which I shall expect the bank to refund, but beyond\nthat I am amply repaid by having had an experience which is in\nmany ways unique, and by hearing the very remarkable narrative of\nthe Red-headed League.\"\n\n\n\"You see, Watson,\" he explained in the early hours of the morning\nas we sat over a glass of whisky and soda in Baker Street, \"it\nwas perfectly obvious from the first that the only possible\nobject of this rather fantastic business of the advertisement of\nthe League, and the copying of the 'Encyclopaedia,' must be to get\nthis not over-bright pawnbroker out of the way for a number of\nhours every day. It was a curious way of managing it, but,\nreally, it would be difficult to suggest a better. The method was\nno doubt suggested to Clay's ingenious mind by the colour of his\naccomplice's hair. The 4 pounds a week was a lure which must draw\nhim, and what was it to them, who were playing for thousands?\nThey put in the advertisement, one rogue has the temporary\noffice, the other rogue incites the man to apply for it, and\ntogether they manage to secure his absence every morning in the\nweek. From the time that I heard of the assistant having come for\nhalf wages, it was obvious to me that he had some strong motive\nfor securing the situation.\"\n\n\"But how could you guess what the motive was?\"\n\n\"Had there been women in the house, I should have suspected a\nmere vulgar intrigue. That, however, was out of the question. The\nman's business was a small one, and there was nothing in his\nhouse which could account for such elaborate preparations, and\nsuch an expenditure as they were at. It must, then, be something\nout of the house. What could it be? I thought of the assistant's\nfondness for photography, and his trick of vanishing into the\ncellar. The cellar! There was the end of this tangled clue. Then\nI made inquiries as to this mysterious assistant and found that I\nhad to deal with one of the coolest and most daring criminals in\nLondon. He was doing something in the cellar--something which\ntook many hours a day for months on end. What could it be, once\nmore? I could think of nothing save that he was running a tunnel\nto some other building.\n\n\"So far I had got when we went to visit the scene of action. I\nsurprised you by beating upon the pavement with my stick. I was\nascertaining whether the cellar stretched out in front or behind.\nIt was not in front. Then I rang the bell, and, as I hoped, the\nassistant answered it. We have had some skirmishes, but we had\nnever set eyes upon each other before. I hardly looked at his\nface. His knees were what I wished to see. You must yourself have\nremarked how worn, wrinkled, and stained they were. They spoke of\nthose hours of burrowing. The only remaining point was what they\nwere burrowing for. I walked round the corner, saw the City and\nSuburban Bank abutted on our friend's premises, and felt that I\nhad solved my problem. When you drove home after the concert I\ncalled upon Scotland Yard and upon the chairman of the bank\ndirectors, with the result that you have seen.\"\n\n\"And how could you tell that they would make their attempt\nto-night?\" I asked.\n\n\"Well, when they closed their League offices that was a sign that\nthey cared no longer about Mr. Jabez Wilson's presence--in other\nwords, that they had completed their tunnel. But it was essential\nthat they should use it soon, as it might be discovered, or the\nbullion might be removed. Saturday would suit them better than\nany other day, as it would give them two days for their escape.\nFor all these reasons I expected them to come to-night.\"\n\n\"You reasoned it out beautifully,\" I exclaimed in unfeigned\nadmiration. \"It is so long a chain, and yet every link rings\ntrue.\"\n\n\"It saved me from ennui,\" he answered, yawning. \"Alas! I already\nfeel it closing in upon me. My life is spent in one long effort\nto escape from the commonplaces of existence. These little\nproblems help me to do so.\"\n\n\"And you are a benefactor of the race,\" said I.\n\nHe shrugged his shoulders. \"Well, perhaps, after all, it is of\nsome little use,\" he remarked. \"'L'homme c'est rien--l'oeuvre\nc'est tout,' as Gustave Flaubert wrote to George Sand.\"\n\n\n\nADVENTURE III. A CASE OF IDENTITY\n\n\"My dear fellow,\" said Sherlock Holmes as we sat on either side\nof the fire in his lodgings at Baker Street, \"life is infinitely\nstranger than anything which the mind of man could invent. We\nwould not dare to conceive the things which are really mere\ncommonplaces of existence. If we could fly out of that window\nhand in hand, hover over this great city, gently remove the\nroofs, and peep in at the queer things which are going on, the\nstrange coincidences, the plannings, the cross-purposes, the\nwonderful chains of events, working through generations, and\nleading to the most outré results, it would make all fiction with\nits conventionalities and foreseen conclusions most stale and\nunprofitable.\"\n\n\"And yet I am not convinced of it,\" I answered. \"The cases which\ncome to light in the papers are, as a rule, bald enough, and\nvulgar enough. We have in our police reports realism pushed to\nits extreme limits, and yet the result is, it must be confessed,\nneither fascinating nor artistic.\"\n\n\"A certain selection and discretion must be used in producing a\nrealistic effect,\" remarked Holmes. \"This is wanting in the\npolice report, where more stress is laid, perhaps, upon the\nplatitudes of the magistrate than upon the details, which to an\nobserver contain the vital essence of the whole matter. Depend\nupon it, there is nothing so unnatural as the commonplace.\"\n\nI smiled and shook my head. \"I can quite understand your thinking\nso,\" I said. \"Of course, in your position of unofficial adviser\nand helper to everybody who is absolutely puzzled, throughout\nthree continents, you are brought in contact with all that is\nstrange and bizarre. But here\"--I picked up the morning paper\nfrom the ground--\"let us put it to a practical test. Here is the\nfirst heading upon which I come. 'A husband's cruelty to his\nwife.' There is half a column of print, but I know without\nreading it that it is all perfectly familiar to me. There is, of\ncourse, the other woman, the drink, the push, the blow, the\nbruise, the sympathetic sister or landlady. The crudest of\nwriters could invent nothing more crude.\"\n\n\"Indeed, your example is an unfortunate one for your argument,\"\nsaid Holmes, taking the paper and glancing his eye down it. \"This\nis the Dundas separation case, and, as it happens, I was engaged\nin clearing up some small points in connection with it. The\nhusband was a teetotaler, there was no other woman, and the\nconduct complained of was that he had drifted into the habit of\nwinding up every meal by taking out his false teeth and hurling\nthem at his wife, which, you will allow, is not an action likely\nto occur to the imagination of the average story-teller. Take a\npinch of snuff, Doctor, and acknowledge that I have scored over\nyou in your example.\"\n\nHe held out his snuffbox of old gold, with a great amethyst in\nthe centre of the lid. Its splendour was in such contrast to his\nhomely ways and simple life that I could not help commenting upon\nit.\n\n\"Ah,\" said he, \"I forgot that I had not seen you for some weeks.\nIt is a little souvenir from the King of Bohemia in return for my\nassistance in the case of the Irene Adler papers.\"\n\n\"And the ring?\" I asked, glancing at a remarkable brilliant which\nsparkled upon his finger.\n\n\"It was from the reigning family of Holland, though the matter in\nwhich I served them was of such delicacy that I cannot confide it\neven to you, who have been good enough to chronicle one or two of\nmy little problems.\"\n\n\"And have you any on hand just now?\" I asked with interest.\n\n\"Some ten or twelve, but none which present any feature of\ninterest. They are important, you understand, without being\ninteresting. Indeed, I have found that it is usually in\nunimportant matters that there is a field for the observation,\nand for the quick analysis of cause and effect which gives the\ncharm to an investigation. The larger crimes are apt to be the\nsimpler, for the bigger the crime the more obvious, as a rule, is\nthe motive. In these cases, save for one rather intricate matter\nwhich has been referred to me from Marseilles, there is nothing\nwhich presents any features of interest. It is possible, however,\nthat I may have something better before very many minutes are\nover, for this is one of my clients, or I am much mistaken.\"\n\nHe had risen from his chair and was standing between the parted\nblinds gazing down into the dull neutral-tinted London street.\nLooking over his shoulder, I saw that on the pavement opposite\nthere stood a large woman with a heavy fur boa round her neck,\nand a large curling red feather in a broad-brimmed hat which was\ntilted in a coquettish Duchess of Devonshire fashion over her\near. From under this great panoply she peeped up in a nervous,\nhesitating fashion at our windows, while her body oscillated\nbackward and forward, and her fingers fidgeted with her glove\nbuttons. Suddenly, with a plunge, as of the swimmer who leaves\nthe bank, she hurried across the road, and we heard the sharp\nclang of the bell.\n\n\"I have seen those symptoms before,\" said Holmes, throwing his\ncigarette into the fire. \"Oscillation upon the pavement always\nmeans an affaire de coeur. She would like advice, but is not sure\nthat the matter is not too delicate for communication. And yet\neven here we may discriminate. When a woman has been seriously\nwronged by a man she no longer oscillates, and the usual symptom\nis a broken bell wire. Here we may take it that there is a love\nmatter, but that the maiden is not so much angry as perplexed, or\ngrieved. But here she comes in person to resolve our doubts.\"\n\nAs he spoke there was a tap at the door, and the boy in buttons\nentered to announce Miss Mary Sutherland, while the lady herself\nloomed behind his small black figure like a full-sailed\nmerchant-man behind a tiny pilot boat. Sherlock Holmes welcomed\nher with the easy courtesy for which he was remarkable, and,\nhaving closed the door and bowed her into an armchair, he looked\nher over in the minute and yet abstracted fashion which was\npeculiar to him.\n\n\"Do you not find,\" he said, \"that with your short sight it is a\nlittle trying to do so much typewriting?\"\n\n\"I did at first,\" she answered, \"but now I know where the letters\nare without looking.\" Then, suddenly realising the full purport\nof his words, she gave a violent start and looked up, with fear\nand astonishment upon her broad, good-humoured face. \"You've\nheard about me, Mr. Holmes,\" she cried, \"else how could you know\nall that?\"\n\n\"Never mind,\" said Holmes, laughing; \"it is my business to know\nthings. Perhaps I have trained myself to see what others\noverlook. If not, why should you come to consult me?\"\n\n\"I came to you, sir, because I heard of you from Mrs. Etherege,\nwhose husband you found so easy when the police and everyone had\ngiven him up for dead. Oh, Mr. Holmes, I wish you would do as\nmuch for me. I'm not rich, but still I have a hundred a year in\nmy own right, besides the little that I make by the machine, and\nI would give it all to know what has become of Mr. Hosmer Angel.\"\n\n\"Why did you come away to consult me in such a hurry?\" asked\nSherlock Holmes, with his finger-tips together and his eyes to\nthe ceiling.\n\nAgain a startled look came over the somewhat vacuous face of Miss\nMary Sutherland. \"Yes, I did bang out of the house,\" she said,\n\"for it made me angry to see the easy way in which Mr.\nWindibank--that is, my father--took it all. He would not go to\nthe police, and he would not go to you, and so at last, as he\nwould do nothing and kept on saying that there was no harm done,\nit made me mad, and I just on with my things and came right away\nto you.\"\n\n\"Your father,\" said Holmes, \"your stepfather, surely, since the\nname is different.\"\n\n\"Yes, my stepfather. I call him father, though it sounds funny,\ntoo, for he is only five years and two months older than myself.\"\n\n\"And your mother is alive?\"\n\n\"Oh, yes, mother is alive and well. I wasn't best pleased, Mr.\nHolmes, when she married again so soon after father's death, and\na man who was nearly fifteen years younger than herself. Father\nwas a plumber in the Tottenham Court Road, and he left a tidy\nbusiness behind him, which mother carried on with Mr. Hardy, the\nforeman; but when Mr. Windibank came he made her sell the\nbusiness, for he was very superior, being a traveller in wines.\nThey got 4700 pounds for the goodwill and interest, which wasn't\nnear as much as father could have got if he had been alive.\"\n\nI had expected to see Sherlock Holmes impatient under this\nrambling and inconsequential narrative, but, on the contrary, he\nhad listened with the greatest concentration of attention.\n\n\"Your own little income,\" he asked, \"does it come out of the\nbusiness?\"\n\n\"Oh, no, sir. It is quite separate and was left me by my uncle\nNed in Auckland. It is in New Zealand stock, paying 4 1/2 per\ncent. Two thousand five hundred pounds was the amount, but I can\nonly touch the interest.\"\n\n\"You interest me extremely,\" said Holmes. \"And since you draw so\nlarge a sum as a hundred a year, with what you earn into the\nbargain, you no doubt travel a little and indulge yourself in\nevery way. I believe that a single lady can get on very nicely\nupon an income of about 60 pounds.\"\n\n\"I could do with much less than that, Mr. Holmes, but you\nunderstand that as long as I live at home I don't wish to be a\nburden to them, and so they have the use of the money just while\nI am staying with them. Of course, that is only just for the\ntime. Mr. Windibank draws my interest every quarter and pays it\nover to mother, and I find that I can do pretty well with what I\nearn at typewriting. It brings me twopence a sheet, and I can\noften do from fifteen to twenty sheets in a day.\"\n\n\"You have made your position very clear to me,\" said Holmes.\n\"This is my friend, Dr. Watson, before whom you can speak as\nfreely as before myself. Kindly tell us now all about your\nconnection with Mr. Hosmer Angel.\"\n\nA flush stole over Miss Sutherland's face, and she picked\nnervously at the fringe of her jacket. \"I met him first at the\ngasfitters' ball,\" she said. \"They used to send father tickets\nwhen he was alive, and then afterwards they remembered us, and\nsent them to mother. Mr. Windibank did not wish us to go. He\nnever did wish us to go anywhere. He would get quite mad if I\nwanted so much as to join a Sunday-school treat. But this time I\nwas set on going, and I would go; for what right had he to\nprevent? He said the folk were not fit for us to know, when all\nfather's friends were to be there. And he said that I had nothing\nfit to wear, when I had my purple plush that I had never so much\nas taken out of the drawer. At last, when nothing else would do,\nhe went off to France upon the business of the firm, but we went,\nmother and I, with Mr. Hardy, who used to be our foreman, and it\nwas there I met Mr. Hosmer Angel.\"\n\n\"I suppose,\" said Holmes, \"that when Mr. Windibank came back from\nFrance he was very annoyed at your having gone to the ball.\"\n\n\"Oh, well, he was very good about it. He laughed, I remember, and\nshrugged his shoulders, and said there was no use denying\nanything to a woman, for she would have her way.\"\n\n\"I see. Then at the gasfitters' ball you met, as I understand, a\ngentleman called Mr. Hosmer Angel.\"\n\n\"Yes, sir. I met him that night, and he called next day to ask if\nwe had got home all safe, and after that we met him--that is to\nsay, Mr. Holmes, I met him twice for walks, but after that father\ncame back again, and Mr. Hosmer Angel could not come to the house\nany more.\"\n\n\"No?\"\n\n\"Well, you know father didn't like anything of the sort. He\nwouldn't have any visitors if he could help it, and he used to\nsay that a woman should be happy in her own family circle. But\nthen, as I used to say to mother, a woman wants her own circle to\nbegin with, and I had not got mine yet.\"\n\n\"But how about Mr. Hosmer Angel? Did he make no attempt to see\nyou?\"\n\n\"Well, father was going off to France again in a week, and Hosmer\nwrote and said that it would be safer and better not to see each\nother until he had gone. We could write in the meantime, and he\nused to write every day. I took the letters in in the morning, so\nthere was no need for father to know.\"\n\n\"Were you engaged to the gentleman at this time?\"\n\n\"Oh, yes, Mr. Holmes. We were engaged after the first walk that\nwe took. Hosmer--Mr. Angel--was a cashier in an office in\nLeadenhall Street--and--\"\n\n\"What office?\"\n\n\"That's the worst of it, Mr. Holmes, I don't know.\"\n\n\"Where did he live, then?\"\n\n\"He slept on the premises.\"\n\n\"And you don't know his address?\"\n\n\"No--except that it was Leadenhall Street.\"\n\n\"Where did you address your letters, then?\"\n\n\"To the Leadenhall Street Post Office, to be left till called\nfor. He said that if they were sent to the office he would be\nchaffed by all the other clerks about having letters from a lady,\nso I offered to typewrite them, like he did his, but he wouldn't\nhave that, for he said that when I wrote them they seemed to come\nfrom me, but when they were typewritten he always felt that the\nmachine had come between us. That will just show you how fond he\nwas of me, Mr. Holmes, and the little things that he would think\nof.\"\n\n\"It was most suggestive,\" said Holmes. \"It has long been an axiom\nof mine that the little things are infinitely the most important.\nCan you remember any other little things about Mr. Hosmer Angel?\"\n\n\"He was a very shy man, Mr. Holmes. He would rather walk with me\nin the evening than in the daylight, for he said that he hated to\nbe conspicuous. Very retiring and gentlemanly he was. Even his\nvoice was gentle. He'd had the quinsy and swollen glands when he\nwas young, he told me, and it had left him with a weak throat,\nand a hesitating, whispering fashion of speech. He was always\nwell dressed, very neat and plain, but his eyes were weak, just\nas mine are, and he wore tinted glasses against the glare.\"\n\n\"Well, and what happened when Mr. Windibank, your stepfather,\nreturned to France?\"\n\n\"Mr. Hosmer Angel came to the house again and proposed that we\nshould marry before father came back. He was in dreadful earnest\nand made me swear, with my hands on the Testament, that whatever\nhappened I would always be true to him. Mother said he was quite\nright to make me swear, and that it was a sign of his passion.\nMother was all in his favour from the first and was even fonder\nof him than I was. Then, when they talked of marrying within the\nweek, I began to ask about father; but they both said never to\nmind about father, but just to tell him afterwards, and mother\nsaid she would make it all right with him. I didn't quite like\nthat, Mr. Holmes. It seemed funny that I should ask his leave, as\nhe was only a few years older than me; but I didn't want to do\nanything on the sly, so I wrote to father at Bordeaux, where the\ncompany has its French offices, but the letter came back to me on\nthe very morning of the wedding.\"\n\n\"It missed him, then?\"\n\n\"Yes, sir; for he had started to England just before it arrived.\"\n\n\"Ha! that was unfortunate. Your wedding was arranged, then, for\nthe Friday. Was it to be in church?\"\n\n\"Yes, sir, but very quietly. It was to be at St. Saviour's, near\nKing's Cross, and we were to have breakfast afterwards at the St.\nPancras Hotel. Hosmer came for us in a hansom, but as there were\ntwo of us he put us both into it and stepped himself into a\nfour-wheeler, which happened to be the only other cab in the\nstreet. We got to the church first, and when the four-wheeler\ndrove up we waited for him to step out, but he never did, and\nwhen the cabman got down from the box and looked there was no one\nthere! The cabman said that he could not imagine what had become\nof him, for he had seen him get in with his own eyes. That was\nlast Friday, Mr. Holmes, and I have never seen or heard anything\nsince then to throw any light upon what became of him.\"\n\n\"It seems to me that you have been very shamefully treated,\" said\nHolmes.\n\n\"Oh, no, sir! He was too good and kind to leave me so. Why, all\nthe morning he was saying to me that, whatever happened, I was to\nbe true; and that even if something quite unforeseen occurred to\nseparate us, I was always to remember that I was pledged to him,\nand that he would claim his pledge sooner or later. It seemed\nstrange talk for a wedding-morning, but what has happened since\ngives a meaning to it.\"\n\n\"Most certainly it does. Your own opinion is, then, that some\nunforeseen catastrophe has occurred to him?\"\n\n\"Yes, sir. I believe that he foresaw some danger, or else he\nwould not have talked so. And then I think that what he foresaw\nhappened.\"\n\n\"But you have no notion as to what it could have been?\"\n\n\"None.\"\n\n\"One more question. How did your mother take the matter?\"\n\n\"She was angry, and said that I was never to speak of the matter\nagain.\"\n\n\"And your father? Did you tell him?\"\n\n\"Yes; and he seemed to think, with me, that something had\nhappened, and that I should hear of Hosmer again. As he said,\nwhat interest could anyone have in bringing me to the doors of\nthe church, and then leaving me? Now, if he had borrowed my\nmoney, or if he had married me and got my money settled on him,\nthere might be some reason, but Hosmer was very independent about\nmoney and never would look at a shilling of mine. And yet, what\ncould have happened? And why could he not write? Oh, it drives me\nhalf-mad to think of it, and I can't sleep a wink at night.\" She\npulled a little handkerchief out of her muff and began to sob\nheavily into it.\n\n\"I shall glance into the case for you,\" said Holmes, rising, \"and\nI have no doubt that we shall reach some definite result. Let the\nweight of the matter rest upon me now, and do not let your mind\ndwell upon it further. Above all, try to let Mr. Hosmer Angel\nvanish from your memory, as he has done from your life.\"\n\n\"Then you don't think I'll see him again?\"\n\n\"I fear not.\"\n\n\"Then what has happened to him?\"\n\n\"You will leave that question in my hands. I should like an\naccurate description of him and any letters of his which you can\nspare.\"\n\n\"I advertised for him in last Saturday's Chronicle,\" said she.\n\"Here is the slip and here are four letters from him.\"\n\n\"Thank you. And your address?\"\n\n\"No. 31 Lyon Place, Camberwell.\"\n\n\"Mr. Angel's address you never had, I understand. Where is your\nfather's place of business?\"\n\n\"He travels for Westhouse & Marbank, the great claret importers\nof Fenchurch Street.\"\n\n\"Thank you. You have made your statement very clearly. You will\nleave the papers here, and remember the advice which I have given\nyou. Let the whole incident be a sealed book, and do not allow it\nto affect your life.\"\n\n\"You are very kind, Mr. Holmes, but I cannot do that. I shall be\ntrue to Hosmer. He shall find me ready when he comes back.\"\n\nFor all the preposterous hat and the vacuous face, there was\nsomething noble in the simple faith of our visitor which\ncompelled our respect. She laid her little bundle of papers upon\nthe table and went her way, with a promise to come again whenever\nshe might be summoned.\n\nSherlock Holmes sat silent for a few minutes with his fingertips\nstill pressed together, his legs stretched out in front of him,\nand his gaze directed upward to the ceiling. Then he took down\nfrom the rack the old and oily clay pipe, which was to him as a\ncounsellor, and, having lit it, he leaned back in his chair, with\nthe thick blue cloud-wreaths spinning up from him, and a look of\ninfinite languor in his face.\n\n\"Quite an interesting study, that maiden,\" he observed. \"I found\nher more interesting than her little problem, which, by the way,\nis rather a trite one. You will find parallel cases, if you\nconsult my index, in Andover in '77, and there was something of\nthe sort at The Hague last year. Old as is the idea, however,\nthere were one or two details which were new to me. But the\nmaiden herself was most instructive.\"\n\n\"You appeared to read a good deal upon her which was quite\ninvisible to me,\" I remarked.\n\n\"Not invisible but unnoticed, Watson. You did not know where to\nlook, and so you missed all that was important. I can never bring\nyou to realise the importance of sleeves, the suggestiveness of\nthumb-nails, or the great issues that may hang from a boot-lace.\nNow, what did you gather from that woman's appearance? Describe\nit.\"\n\n\"Well, she had a slate-coloured, broad-brimmed straw hat, with a\nfeather of a brickish red. Her jacket was black, with black beads\nsewn upon it, and a fringe of little black jet ornaments. Her\ndress was brown, rather darker than coffee colour, with a little\npurple plush at the neck and sleeves. Her gloves were greyish and\nwere worn through at the right forefinger. Her boots I didn't\nobserve. She had small round, hanging gold earrings, and a\ngeneral air of being fairly well-to-do in a vulgar, comfortable,\neasy-going way.\"\n\nSherlock Holmes clapped his hands softly together and chuckled.\n\n\"'Pon my word, Watson, you are coming along wonderfully. You have\nreally done very well indeed. It is true that you have missed\neverything of importance, but you have hit upon the method, and\nyou have a quick eye for colour. Never trust to general\nimpressions, my boy, but concentrate yourself upon details. My\nfirst glance is always at a woman's sleeve. In a man it is\nperhaps better first to take the knee of the trouser. As you\nobserve, this woman had plush upon her sleeves, which is a most\nuseful material for showing traces. The double line a little\nabove the wrist, where the typewritist presses against the table,\nwas beautifully defined. The sewing-machine, of the hand type,\nleaves a similar mark, but only on the left arm, and on the side\nof it farthest from the thumb, instead of being right across the\nbroadest part, as this was. I then glanced at her face, and,\nobserving the dint of a pince-nez at either side of her nose, I\nventured a remark upon short sight and typewriting, which seemed\nto surprise her.\"\n\n\"It surprised me.\"\n\n\"But, surely, it was obvious. I was then much surprised and\ninterested on glancing down to observe that, though the boots\nwhich she was wearing were not unlike each other, they were\nreally odd ones; the one having a slightly decorated toe-cap, and\nthe other a plain one. One was buttoned only in the two lower\nbuttons out of five, and the other at the first, third, and\nfifth. Now, when you see that a young lady, otherwise neatly\ndressed, has come away from home with odd boots, half-buttoned,\nit is no great deduction to say that she came away in a hurry.\"\n\n\"And what else?\" I asked, keenly interested, as I always was, by\nmy friend's incisive reasoning.\n\n\"I noted, in passing, that she had written a note before leaving\nhome but after being fully dressed. You observed that her right\nglove was torn at the forefinger, but you did not apparently see\nthat both glove and finger were stained with violet ink. She had\nwritten in a hurry and dipped her pen too deep. It must have been\nthis morning, or the mark would not remain clear upon the finger.\nAll this is amusing, though rather elementary, but I must go back\nto business, Watson. Would you mind reading me the advertised\ndescription of Mr. Hosmer Angel?\"\n\nI held the little printed slip to the light.\n\n\"Missing,\" it said, \"on the morning of the fourteenth, a gentleman\nnamed Hosmer Angel. About five ft. seven in. in height;\nstrongly built, sallow complexion, black hair, a little bald in\nthe centre, bushy, black side-whiskers and moustache; tinted\nglasses, slight infirmity of speech. Was dressed, when last seen,\nin black frock-coat faced with silk, black waistcoat, gold Albert\nchain, and grey Harris tweed trousers, with brown gaiters over\nelastic-sided boots. Known to have been employed in an office in\nLeadenhall Street. Anybody bringing--\"\n\n\"That will do,\" said Holmes. \"As to the letters,\" he continued,\nglancing over them, \"they are very commonplace. Absolutely no\nclue in them to Mr. Angel, save that he quotes Balzac once. There\nis one remarkable point, however, which will no doubt strike\nyou.\"\n\n\"They are typewritten,\" I remarked.\n\n\"Not only that, but the signature is typewritten. Look at the\nneat little 'Hosmer Angel' at the bottom. There is a date, you\nsee, but no superscription except Leadenhall Street, which is\nrather vague. The point about the signature is very suggestive--in\nfact, we may call it conclusive.\"\n\n\"Of what?\"\n\n\"My dear fellow, is it possible you do not see how strongly it\nbears upon the case?\"\n\n\"I cannot say that I do unless it were that he wished to be able\nto deny his signature if an action for breach of promise were\ninstituted.\"\n\n\"No, that was not the point. However, I shall write two letters,\nwhich should settle the matter. One is to a firm in the City, the\nother is to the young lady's stepfather, Mr. Windibank, asking\nhim whether he could meet us here at six o'clock tomorrow\nevening. It is just as well that we should do business with the\nmale relatives. And now, Doctor, we can do nothing until the\nanswers to those letters come, so we may put our little problem\nupon the shelf for the interim.\"\n\nI had had so many reasons to believe in my friend's subtle powers\nof reasoning and extraordinary energy in action that I felt that\nhe must have some solid grounds for the assured and easy\ndemeanour with which he treated the singular mystery which he had\nbeen called upon to fathom. Once only had I known him to fail, in\nthe case of the King of Bohemia and of the Irene Adler\nphotograph; but when I looked back to the weird business of the\nSign of Four, and the extraordinary circumstances connected with\nthe Study in Scarlet, I felt that it would be a strange tangle\nindeed which he could not unravel.\n\nI left him then, still puffing at his black clay pipe, with the\nconviction that when I came again on the next evening I would\nfind that he held in his hands all the clues which would lead up\nto the identity of the disappearing bridegroom of Miss Mary\nSutherland.\n\nA professional case of great gravity was engaging my own\nattention at the time, and the whole of next day I was busy at\nthe bedside of the sufferer. It was not until close upon six\no'clock that I found myself free and was able to spring into a\nhansom and drive to Baker Street, half afraid that I might be too\nlate to assist at the dénouement of the little mystery. I found\nSherlock Holmes alone, however, half asleep, with his long, thin\nform curled up in the recesses of his armchair. A formidable\narray of bottles and test-tubes, with the pungent cleanly smell\nof hydrochloric acid, told me that he had spent his day in the\nchemical work which was so dear to him.\n\n\"Well, have you solved it?\" I asked as I entered.\n\n\"Yes. It was the bisulphate of baryta.\"\n\n\"No, no, the mystery!\" I cried.\n\n\"Oh, that! I thought of the salt that I have been working upon.\nThere was never any mystery in the matter, though, as I said\nyesterday, some of the details are of interest. The only drawback\nis that there is no law, I fear, that can touch the scoundrel.\"\n\n\"Who was he, then, and what was his object in deserting Miss\nSutherland?\"\n\nThe question was hardly out of my mouth, and Holmes had not yet\nopened his lips to reply, when we heard a heavy footfall in the\npassage and a tap at the door.\n\n\"This is the girl's stepfather, Mr. James Windibank,\" said\nHolmes. \"He has written to me to say that he would be here at\nsix. Come in!\"\n\nThe man who entered was a sturdy, middle-sized fellow, some\nthirty years of age, clean-shaven, and sallow-skinned, with a\nbland, insinuating manner, and a pair of wonderfully sharp and\npenetrating grey eyes. He shot a questioning glance at each of\nus, placed his shiny top-hat upon the sideboard, and with a\nslight bow sidled down into the nearest chair.\n\n\"Good-evening, Mr. James Windibank,\" said Holmes. \"I think that\nthis typewritten letter is from you, in which you made an\nappointment with me for six o'clock?\"\n\n\"Yes, sir. I am afraid that I am a little late, but I am not\nquite my own master, you know. I am sorry that Miss Sutherland\nhas troubled you about this little matter, for I think it is far\nbetter not to wash linen of the sort in public. It was quite\nagainst my wishes that she came, but she is a very excitable,\nimpulsive girl, as you may have noticed, and she is not easily\ncontrolled when she has made up her mind on a point. Of course, I\ndid not mind you so much, as you are not connected with the\nofficial police, but it is not pleasant to have a family\nmisfortune like this noised abroad. Besides, it is a useless\nexpense, for how could you possibly find this Hosmer Angel?\"\n\n\"On the contrary,\" said Holmes quietly; \"I have every reason to\nbelieve that I will succeed in discovering Mr. Hosmer Angel.\"\n\nMr. Windibank gave a violent start and dropped his gloves. \"I am\ndelighted to hear it,\" he said.\n\n\"It is a curious thing,\" remarked Holmes, \"that a typewriter has\nreally quite as much individuality as a man's handwriting. Unless\nthey are quite new, no two of them write exactly alike. Some\nletters get more worn than others, and some wear only on one\nside. Now, you remark in this note of yours, Mr. Windibank, that\nin every case there is some little slurring over of the 'e,' and\na slight defect in the tail of the 'r.' There are fourteen other\ncharacteristics, but those are the more obvious.\"\n\n\"We do all our correspondence with this machine at the office,\nand no doubt it is a little worn,\" our visitor answered, glancing\nkeenly at Holmes with his bright little eyes.\n\n\"And now I will show you what is really a very interesting study,\nMr. Windibank,\" Holmes continued. \"I think of writing another\nlittle monograph some of these days on the typewriter and its\nrelation to crime. It is a subject to which I have devoted some\nlittle attention. I have here four letters which purport to come\nfrom the missing man. They are all typewritten. In each case, not\nonly are the 'e's' slurred and the 'r's' tailless, but you will\nobserve, if you care to use my magnifying lens, that the fourteen\nother characteristics to which I have alluded are there as well.\"\n\nMr. Windibank sprang out of his chair and picked up his hat. \"I\ncannot waste time over this sort of fantastic talk, Mr. Holmes,\"\nhe said. \"If you can catch the man, catch him, and let me know\nwhen you have done it.\"\n\n\"Certainly,\" said Holmes, stepping over and turning the key in\nthe door. \"I let you know, then, that I have caught him!\"\n\n\"What! where?\" shouted Mr. Windibank, turning white to his lips\nand glancing about him like a rat in a trap.\n\n\"Oh, it won't do--really it won't,\" said Holmes suavely. \"There\nis no possible getting out of it, Mr. Windibank. It is quite too\ntransparent, and it was a very bad compliment when you said that\nit was impossible for me to solve so simple a question. That's\nright! Sit down and let us talk it over.\"\n\nOur visitor collapsed into a chair, with a ghastly face and a\nglitter of moisture on his brow. \"It--it's not actionable,\" he\nstammered.\n\n\"I am very much afraid that it is not. But between ourselves,\nWindibank, it was as cruel and selfish and heartless a trick in a\npetty way as ever came before me. Now, let me just run over the\ncourse of events, and you will contradict me if I go wrong.\"\n\nThe man sat huddled up in his chair, with his head sunk upon his\nbreast, like one who is utterly crushed. Holmes stuck his feet up\non the corner of the mantelpiece and, leaning back with his hands\nin his pockets, began talking, rather to himself, as it seemed,\nthan to us.\n\n\"The man married a woman very much older than himself for her\nmoney,\" said he, \"and he enjoyed the use of the money of the\ndaughter as long as she lived with them. It was a considerable\nsum, for people in their position, and the loss of it would have\nmade a serious difference. It was worth an effort to preserve it.\nThe daughter was of a good, amiable disposition, but affectionate\nand warm-hearted in her ways, so that it was evident that with\nher fair personal advantages, and her little income, she would\nnot be allowed to remain single long. Now her marriage would\nmean, of course, the loss of a hundred a year, so what does her\nstepfather do to prevent it? He takes the obvious course of\nkeeping her at home and forbidding her to seek the company of\npeople of her own age. But soon he found that that would not\nanswer forever. She became restive, insisted upon her rights, and\nfinally announced her positive intention of going to a certain\nball. What does her clever stepfather do then? He conceives an\nidea more creditable to his head than to his heart. With the\nconnivance and assistance of his wife he disguised himself,\ncovered those keen eyes with tinted glasses, masked the face with\na moustache and a pair of bushy whiskers, sunk that clear voice\ninto an insinuating whisper, and doubly secure on account of the\ngirl's short sight, he appears as Mr. Hosmer Angel, and keeps off\nother lovers by making love himself.\"\n\n\"It was only a joke at first,\" groaned our visitor. \"We never\nthought that she would have been so carried away.\"\n\n\"Very likely not. However that may be, the young lady was very\ndecidedly carried away, and, having quite made up her mind that\nher stepfather was in France, the suspicion of treachery never\nfor an instant entered her mind. She was flattered by the\ngentleman's attentions, and the effect was increased by the\nloudly expressed admiration of her mother. Then Mr. Angel began\nto call, for it was obvious that the matter should be pushed as\nfar as it would go if a real effect were to be produced. There\nwere meetings, and an engagement, which would finally secure the\ngirl's affections from turning towards anyone else. But the\ndeception could not be kept up forever. These pretended journeys\nto France were rather cumbrous. The thing to do was clearly to\nbring the business to an end in such a dramatic manner that it\nwould leave a permanent impression upon the young lady's mind and\nprevent her from looking upon any other suitor for some time to\ncome. Hence those vows of fidelity exacted upon a Testament, and\nhence also the allusions to a possibility of something happening\non the very morning of the wedding. James Windibank wished Miss\nSutherland to be so bound to Hosmer Angel, and so uncertain as to\nhis fate, that for ten years to come, at any rate, she would not\nlisten to another man. As far as the church door he brought her,\nand then, as he could go no farther, he conveniently vanished\naway by the old trick of stepping in at one door of a\nfour-wheeler and out at the other. I think that was the chain of\nevents, Mr. Windibank!\"\n\nOur visitor had recovered something of his assurance while Holmes\nhad been talking, and he rose from his chair now with a cold\nsneer upon his pale face.\n\n\"It may be so, or it may not, Mr. Holmes,\" said he, \"but if you\nare so very sharp you ought to be sharp enough to know that it is\nyou who are breaking the law now, and not me. I have done nothing\nactionable from the first, but as long as you keep that door\nlocked you lay yourself open to an action for assault and illegal\nconstraint.\"\n\n\"The law cannot, as you say, touch you,\" said Holmes, unlocking\nand throwing open the door, \"yet there never was a man who\ndeserved punishment more. If the young lady has a brother or a\nfriend, he ought to lay a whip across your shoulders. By Jove!\"\nhe continued, flushing up at the sight of the bitter sneer upon\nthe man's face, \"it is not part of my duties to my client, but\nhere's a hunting crop handy, and I think I shall just treat\nmyself to--\" He took two swift steps to the whip, but before he\ncould grasp it there was a wild clatter of steps upon the stairs,\nthe heavy hall door banged, and from the window we could see Mr.\nJames Windibank running at the top of his speed down the road.\n\n\"There's a cold-blooded scoundrel!\" said Holmes, laughing, as he\nthrew himself down into his chair once more. \"That fellow will\nrise from crime to crime until he does something very bad, and\nends on a gallows. The case has, in some respects, been not\nentirely devoid of interest.\"\n\n\"I cannot now entirely see all the steps of your reasoning,\" I\nremarked.\n\n\"Well, of course it was obvious from the first that this Mr.\nHosmer Angel must have some strong object for his curious\nconduct, and it was equally clear that the only man who really\nprofited by the incident, as far as we could see, was the\nstepfather. Then the fact that the two men were never together,\nbut that the one always appeared when the other was away, was\nsuggestive. So were the tinted spectacles and the curious voice,\nwhich both hinted at a disguise, as did the bushy whiskers. My\nsuspicions were all confirmed by his peculiar action in\ntypewriting his signature, which, of course, inferred that his\nhandwriting was so familiar to her that she would recognise even\nthe smallest sample of it. You see all these isolated facts,\ntogether with many minor ones, all pointed in the same\ndirection.\"\n\n\"And how did you verify them?\"\n\n\"Having once spotted my man, it was easy to get corroboration. I\nknew the firm for which this man worked. Having taken the printed\ndescription. I eliminated everything from it which could be the\nresult of a disguise--the whiskers, the glasses, the voice, and I\nsent it to the firm, with a request that they would inform me\nwhether it answered to the description of any of their\ntravellers. I had already noticed the peculiarities of the\ntypewriter, and I wrote to the man himself at his business\naddress asking him if he would come here. As I expected, his\nreply was typewritten and revealed the same trivial but\ncharacteristic defects. The same post brought me a letter from\nWesthouse & Marbank, of Fenchurch Street, to say that the\ndescription tallied in every respect with that of their employé,\nJames Windibank. Voilà tout!\"\n\n\"And Miss Sutherland?\"\n\n\"If I tell her she will not believe me. You may remember the old\nPersian saying, 'There is danger for him who taketh the tiger\ncub, and danger also for whoso snatches a delusion from a woman.'\nThere is as much sense in Hafiz as in Horace, and as much\nknowledge of the world.\"\n\n\n\nADVENTURE IV. THE BOSCOMBE VALLEY MYSTERY\n\nWe were seated at breakfast one morning, my wife and I, when the\nmaid brought in a telegram. It was from Sherlock Holmes and ran\nin this way:\n\n\"Have you a couple of days to spare? Have just been wired for from\nthe west of England in connection with Boscombe Valley tragedy.\nShall be glad if you will come with me. Air and scenery perfect.\nLeave Paddington by the 11:15.\"\n\n\"What do you say, dear?\" said my wife, looking across at me.\n\"Will you go?\"\n\n\"I really don't know what to say. I have a fairly long list at\npresent.\"\n\n\"Oh, Anstruther would do your work for you. You have been looking\na little pale lately. I think that the change would do you good,\nand you are always so interested in Mr. Sherlock Holmes' cases.\"\n\n\"I should be ungrateful if I were not, seeing what I gained\nthrough one of them,\" I answered. \"But if I am to go, I must pack\nat once, for I have only half an hour.\"\n\nMy experience of camp life in Afghanistan had at least had the\neffect of making me a prompt and ready traveller. My wants were\nfew and simple, so that in less than the time stated I was in a\ncab with my valise, rattling away to Paddington Station. Sherlock\nHolmes was pacing up and down the platform, his tall, gaunt\nfigure made even gaunter and taller by his long grey\ntravelling-cloak and close-fitting cloth cap.\n\n\"It is really very good of you to come, Watson,\" said he. \"It\nmakes a considerable difference to me, having someone with me on\nwhom I can thoroughly rely. Local aid is always either worthless\nor else biassed. If you will keep the two corner seats I shall\nget the tickets.\"\n\nWe had the carriage to ourselves save for an immense litter of\npapers which Holmes had brought with him. Among these he rummaged\nand read, with intervals of note-taking and of meditation, until\nwe were past Reading. Then he suddenly rolled them all into a\ngigantic ball and tossed them up onto the rack.\n\n\"Have you heard anything of the case?\" he asked.\n\n\"Not a word. I have not seen a paper for some days.\"\n\n\"The London press has not had very full accounts. I have just\nbeen looking through all the recent papers in order to master the\nparticulars. It seems, from what I gather, to be one of those\nsimple cases which are so extremely difficult.\"\n\n\"That sounds a little paradoxical.\"\n\n\"But it is profoundly true. Singularity is almost invariably a\nclue. The more featureless and commonplace a crime is, the more\ndifficult it is to bring it home. In this case, however, they\nhave established a very serious case against the son of the\nmurdered man.\"\n\n\"It is a murder, then?\"\n\n\"Well, it is conjectured to be so. I shall take nothing for\ngranted until I have the opportunity of looking personally into\nit. I will explain the state of things to you, as far as I have\nbeen able to understand it, in a very few words.\n\n\"Boscombe Valley is a country district not very far from Ross, in\nHerefordshire. The largest landed proprietor in that part is a\nMr. John Turner, who made his money in Australia and returned\nsome years ago to the old country. One of the farms which he\nheld, that of Hatherley, was let to Mr. Charles McCarthy, who was\nalso an ex-Australian. The men had known each other in the\ncolonies, so that it was not unnatural that when they came to\nsettle down they should do so as near each other as possible.\nTurner was apparently the richer man, so McCarthy became his\ntenant but still remained, it seems, upon terms of perfect\nequality, as they were frequently together. McCarthy had one son,\na lad of eighteen, and Turner had an only daughter of the same\nage, but neither of them had wives living. They appear to have\navoided the society of the neighbouring English families and to\nhave led retired lives, though both the McCarthys were fond of\nsport and were frequently seen at the race-meetings of the\nneighbourhood. McCarthy kept two servants--a man and a girl.\nTurner had a considerable household, some half-dozen at the\nleast. That is as much as I have been able to gather about the\nfamilies. Now for the facts.\n\n\"On June 3rd, that is, on Monday last, McCarthy left his house at\nHatherley about three in the afternoon and walked down to the\nBoscombe Pool, which is a small lake formed by the spreading out\nof the stream which runs down the Boscombe Valley. He had been\nout with his serving-man in the morning at Ross, and he had told\nthe man that he must hurry, as he had an appointment of\nimportance to keep at three. From that appointment he never came\nback alive.\n\n\"From Hatherley Farm-house to the Boscombe Pool is a quarter of a\nmile, and two people saw him as he passed over this ground. One\nwas an old woman, whose name is not mentioned, and the other was\nWilliam Crowder, a game-keeper in the employ of Mr. Turner. Both\nthese witnesses depose that Mr. McCarthy was walking alone. The\ngame-keeper adds that within a few minutes of his seeing Mr.\nMcCarthy pass he had seen his son, Mr. James McCarthy, going the\nsame way with a gun under his arm. To the best of his belief, the\nfather was actually in sight at the time, and the son was\nfollowing him. He thought no more of the matter until he heard in\nthe evening of the tragedy that had occurred.\n\n\"The two McCarthys were seen after the time when William Crowder,\nthe game-keeper, lost sight of them. The Boscombe Pool is thickly\nwooded round, with just a fringe of grass and of reeds round the\nedge. A girl of fourteen, Patience Moran, who is the daughter of\nthe lodge-keeper of the Boscombe Valley estate, was in one of the\nwoods picking flowers. She states that while she was there she\nsaw, at the border of the wood and close by the lake, Mr.\nMcCarthy and his son, and that they appeared to be having a\nviolent quarrel. She heard Mr. McCarthy the elder using very\nstrong language to his son, and she saw the latter raise up his\nhand as if to strike his father. She was so frightened by their\nviolence that she ran away and told her mother when she reached\nhome that she had left the two McCarthys quarrelling near\nBoscombe Pool, and that she was afraid that they were going to\nfight. She had hardly said the words when young Mr. McCarthy came\nrunning up to the lodge to say that he had found his father dead\nin the wood, and to ask for the help of the lodge-keeper. He was\nmuch excited, without either his gun or his hat, and his right\nhand and sleeve were observed to be stained with fresh blood. On\nfollowing him they found the dead body stretched out upon the\ngrass beside the pool. The head had been beaten in by repeated\nblows of some heavy and blunt weapon. The injuries were such as\nmight very well have been inflicted by the butt-end of his son's\ngun, which was found lying on the grass within a few paces of the\nbody. Under these circumstances the young man was instantly\narrested, and a verdict of 'wilful murder' having been returned\nat the inquest on Tuesday, he was on Wednesday brought before the\nmagistrates at Ross, who have referred the case to the next\nAssizes. Those are the main facts of the case as they came out\nbefore the coroner and the police-court.\"\n\n\"I could hardly imagine a more damning case,\" I remarked. \"If\never circumstantial evidence pointed to a criminal it does so\nhere.\"\n\n\"Circumstantial evidence is a very tricky thing,\" answered Holmes\nthoughtfully. \"It may seem to point very straight to one thing,\nbut if you shift your own point of view a little, you may find it\npointing in an equally uncompromising manner to something\nentirely different. It must be confessed, however, that the case\nlooks exceedingly grave against the young man, and it is very\npossible that he is indeed the culprit. There are several people\nin the neighbourhood, however, and among them Miss Turner, the\ndaughter of the neighbouring landowner, who believe in his\ninnocence, and who have retained Lestrade, whom you may recollect\nin connection with the Study in Scarlet, to work out the case in\nhis interest. Lestrade, being rather puzzled, has referred the\ncase to me, and hence it is that two middle-aged gentlemen are\nflying westward at fifty miles an hour instead of quietly\ndigesting their breakfasts at home.\"\n\n\"I am afraid,\" said I, \"that the facts are so obvious that you\nwill find little credit to be gained out of this case.\"\n\n\"There is nothing more deceptive than an obvious fact,\" he\nanswered, laughing. \"Besides, we may chance to hit upon some\nother obvious facts which may have been by no means obvious to\nMr. Lestrade. You know me too well to think that I am boasting\nwhen I say that I shall either confirm or destroy his theory by\nmeans which he is quite incapable of employing, or even of\nunderstanding. To take the first example to hand, I very clearly\nperceive that in your bedroom the window is upon the right-hand\nside, and yet I question whether Mr. Lestrade would have noted\neven so self-evident a thing as that.\"\n\n\"How on earth--\"\n\n\"My dear fellow, I know you well. I know the military neatness\nwhich characterises you. You shave every morning, and in this\nseason you shave by the sunlight; but since your shaving is less\nand less complete as we get farther back on the left side, until\nit becomes positively slovenly as we get round the angle of the\njaw, it is surely very clear that that side is less illuminated\nthan the other. I could not imagine a man of your habits looking\nat himself in an equal light and being satisfied with such a\nresult. I only quote this as a trivial example of observation and\ninference. Therein lies my métier, and it is just possible that\nit may be of some service in the investigation which lies before\nus. There are one or two minor points which were brought out in\nthe inquest, and which are worth considering.\"\n\n\"What are they?\"\n\n\"It appears that his arrest did not take place at once, but after\nthe return to Hatherley Farm. On the inspector of constabulary\ninforming him that he was a prisoner, he remarked that he was not\nsurprised to hear it, and that it was no more than his deserts.\nThis observation of his had the natural effect of removing any\ntraces of doubt which might have remained in the minds of the\ncoroner's jury.\"\n\n\"It was a confession,\" I ejaculated.\n\n\"No, for it was followed by a protestation of innocence.\"\n\n\"Coming on the top of such a damning series of events, it was at\nleast a most suspicious remark.\"\n\n\"On the contrary,\" said Holmes, \"it is the brightest rift which I\ncan at present see in the clouds. However innocent he might be,\nhe could not be such an absolute imbecile as not to see that the\ncircumstances were very black against him. Had he appeared\nsurprised at his own arrest, or feigned indignation at it, I\nshould have looked upon it as highly suspicious, because such\nsurprise or anger would not be natural under the circumstances,\nand yet might appear to be the best policy to a scheming man. His\nfrank acceptance of the situation marks him as either an innocent\nman, or else as a man of considerable self-restraint and\nfirmness. As to his remark about his deserts, it was also not\nunnatural if you consider that he stood beside the dead body of\nhis father, and that there is no doubt that he had that very day\nso far forgotten his filial duty as to bandy words with him, and\neven, according to the little girl whose evidence is so\nimportant, to raise his hand as if to strike him. The\nself-reproach and contrition which are displayed in his remark\nappear to me to be the signs of a healthy mind rather than of a\nguilty one.\"\n\nI shook my head. \"Many men have been hanged on far slighter\nevidence,\" I remarked.\n\n\"So they have. And many men have been wrongfully hanged.\"\n\n\"What is the young man's own account of the matter?\"\n\n\"It is, I am afraid, not very encouraging to his supporters,\nthough there are one or two points in it which are suggestive.\nYou will find it here, and may read it for yourself.\"\n\nHe picked out from his bundle a copy of the local Herefordshire\npaper, and having turned down the sheet he pointed out the\nparagraph in which the unfortunate young man had given his own\nstatement of what had occurred. I settled myself down in the\ncorner of the carriage and read it very carefully. It ran in this\nway:\n\n\"Mr. James McCarthy, the only son of the deceased, was then called\nand gave evidence as follows: 'I had been away from home for\nthree days at Bristol, and had only just returned upon the\nmorning of last Monday, the 3rd. My father was absent from home at\nthe time of my arrival, and I was informed by the maid that he\nhad driven over to Ross with John Cobb, the groom. Shortly after\nmy return I heard the wheels of his trap in the yard, and,\nlooking out of my window, I saw him get out and walk rapidly out\nof the yard, though I was not aware in which direction he was\ngoing. I then took my gun and strolled out in the direction of\nthe Boscombe Pool, with the intention of visiting the rabbit\nwarren which is upon the other side. On my way I saw William\nCrowder, the game-keeper, as he had stated in his evidence; but\nhe is mistaken in thinking that I was following my father. I had\nno idea that he was in front of me. When about a hundred yards\nfrom the pool I heard a cry of \"Cooee!\" which was a usual signal\nbetween my father and myself. I then hurried forward, and found\nhim standing by the pool. He appeared to be much surprised at\nseeing me and asked me rather roughly what I was doing there. A\nconversation ensued which led to high words and almost to blows,\nfor my father was a man of a very violent temper. Seeing that his\npassion was becoming ungovernable, I left him and returned\ntowards Hatherley Farm. I had not gone more than 150 yards,\nhowever, when I heard a hideous outcry behind me, which caused me\nto run back again. I found my father expiring upon the ground,\nwith his head terribly injured. I dropped my gun and held him in\nmy arms, but he almost instantly expired. I knelt beside him for\nsome minutes, and then made my way to Mr. Turner's lodge-keeper,\nhis house being the nearest, to ask for assistance. I saw no one\nnear my father when I returned, and I have no idea how he came by\nhis injuries. He was not a popular man, being somewhat cold and\nforbidding in his manners, but he had, as far as I know, no\nactive enemies. I know nothing further of the matter.'\n\n\"The Coroner: Did your father make any statement to you before\nhe died?\n\n\"Witness: He mumbled a few words, but I could only catch some\nallusion to a rat.\n\n\"The Coroner: What did you understand by that?\n\n\"Witness: It conveyed no meaning to me. I thought that he was\ndelirious.\n\n\"The Coroner: What was the point upon which you and your father\nhad this final quarrel?\n\n\"Witness: I should prefer not to answer.\n\n\"The Coroner: I am afraid that I must press it.\n\n\"Witness: It is really impossible for me to tell you. I can\nassure you that it has nothing to do with the sad tragedy which\nfollowed.\n\n\"The Coroner: That is for the court to decide. I need not point\nout to you that your refusal to answer will prejudice your case\nconsiderably in any future proceedings which may arise.\n\n\"Witness: I must still refuse.\n\n\"The Coroner: I understand that the cry of 'Cooee' was a common\nsignal between you and your father?\n\n\"Witness: It was.\n\n\"The Coroner: How was it, then, that he uttered it before he saw\nyou, and before he even knew that you had returned from Bristol?\n\n\"Witness (with considerable confusion): I do not know.\n\n\"A Juryman: Did you see nothing which aroused your suspicions\nwhen you returned on hearing the cry and found your father\nfatally injured?\n\n\"Witness: Nothing definite.\n\n\"The Coroner: What do you mean?\n\n\"Witness: I was so disturbed and excited as I rushed out into\nthe open, that I could think of nothing except of my father. Yet\nI have a vague impression that as I ran forward something lay\nupon the ground to the left of me. It seemed to me to be\nsomething grey in colour, a coat of some sort, or a plaid perhaps.\nWhen I rose from my father I looked round for it, but it was\ngone.\n\n\"'Do you mean that it disappeared before you went for help?'\n\n\"'Yes, it was gone.'\n\n\"'You cannot say what it was?'\n\n\"'No, I had a feeling something was there.'\n\n\"'How far from the body?'\n\n\"'A dozen yards or so.'\n\n\"'And how far from the edge of the wood?'\n\n\"'About the same.'\n\n\"'Then if it was removed it was while you were within a dozen\nyards of it?'\n\n\"'Yes, but with my back towards it.'\n\n\"This concluded the examination of the witness.\"\n\n\"I see,\" said I as I glanced down the column, \"that the coroner\nin his concluding remarks was rather severe upon young McCarthy.\nHe calls attention, and with reason, to the discrepancy about his\nfather having signalled to him before seeing him, also to his\nrefusal to give details of his conversation with his father, and\nhis singular account of his father's dying words. They are all,\nas he remarks, very much against the son.\"\n\nHolmes laughed softly to himself and stretched himself out upon\nthe cushioned seat. \"Both you and the coroner have been at some\npains,\" said he, \"to single out the very strongest points in the\nyoung man's favour. Don't you see that you alternately give him\ncredit for having too much imagination and too little? Too\nlittle, if he could not invent a cause of quarrel which would\ngive him the sympathy of the jury; too much, if he evolved from\nhis own inner consciousness anything so outré as a dying\nreference to a rat, and the incident of the vanishing cloth. No,\nsir, I shall approach this case from the point of view that what\nthis young man says is true, and we shall see whither that\nhypothesis will lead us. And now here is my pocket Petrarch, and\nnot another word shall I say of this case until we are on the\nscene of action. We lunch at Swindon, and I see that we shall be\nthere in twenty minutes.\"\n\nIt was nearly four o'clock when we at last, after passing through\nthe beautiful Stroud Valley, and over the broad gleaming Severn,\nfound ourselves at the pretty little country-town of Ross. A\nlean, ferret-like man, furtive and sly-looking, was waiting for\nus upon the platform. In spite of the light brown dustcoat and\nleather-leggings which he wore in deference to his rustic\nsurroundings, I had no difficulty in recognising Lestrade, of\nScotland Yard. With him we drove to the Hereford Arms where a\nroom had already been engaged for us.\n\n\"I have ordered a carriage,\" said Lestrade as we sat over a cup\nof tea. \"I knew your energetic nature, and that you would not be\nhappy until you had been on the scene of the crime.\"\n\n\"It was very nice and complimentary of you,\" Holmes answered. \"It\nis entirely a question of barometric pressure.\"\n\nLestrade looked startled. \"I do not quite follow,\" he said.\n\n\"How is the glass? Twenty-nine, I see. No wind, and not a cloud\nin the sky. I have a caseful of cigarettes here which need\nsmoking, and the sofa is very much superior to the usual country\nhotel abomination. I do not think that it is probable that I\nshall use the carriage to-night.\"\n\nLestrade laughed indulgently. \"You have, no doubt, already formed\nyour conclusions from the newspapers,\" he said. \"The case is as\nplain as a pikestaff, and the more one goes into it the plainer\nit becomes. Still, of course, one can't refuse a lady, and such a\nvery positive one, too. She has heard of you, and would have your\nopinion, though I repeatedly told her that there was nothing\nwhich you could do which I had not already done. Why, bless my\nsoul! here is her carriage at the door.\"\n\nHe had hardly spoken before there rushed into the room one of the\nmost lovely young women that I have ever seen in my life. Her\nviolet eyes shining, her lips parted, a pink flush upon her\ncheeks, all thought of her natural reserve lost in her\noverpowering excitement and concern.\n\n\"Oh, Mr. Sherlock Holmes!\" she cried, glancing from one to the\nother of us, and finally, with a woman's quick intuition,\nfastening upon my companion, \"I am so glad that you have come. I\nhave driven down to tell you so. I know that James didn't do it.\nI know it, and I want you to start upon your work knowing it,\ntoo. Never let yourself doubt upon that point. We have known each\nother since we were little children, and I know his faults as no\none else does; but he is too tender-hearted to hurt a fly. Such a\ncharge is absurd to anyone who really knows him.\"\n\n\"I hope we may clear him, Miss Turner,\" said Sherlock Holmes.\n\"You may rely upon my doing all that I can.\"\n\n\"But you have read the evidence. You have formed some conclusion?\nDo you not see some loophole, some flaw? Do you not yourself\nthink that he is innocent?\"\n\n\"I think that it is very probable.\"\n\n\"There, now!\" she cried, throwing back her head and looking\ndefiantly at Lestrade. \"You hear! He gives me hopes.\"\n\nLestrade shrugged his shoulders. \"I am afraid that my colleague\nhas been a little quick in forming his conclusions,\" he said.\n\n\"But he is right. Oh! I know that he is right. James never did\nit. And about his quarrel with his father, I am sure that the\nreason why he would not speak about it to the coroner was because\nI was concerned in it.\"\n\n\"In what way?\" asked Holmes.\n\n\"It is no time for me to hide anything. James and his father had\nmany disagreements about me. Mr. McCarthy was very anxious that\nthere should be a marriage between us. James and I have always\nloved each other as brother and sister; but of course he is young\nand has seen very little of life yet, and--and--well, he\nnaturally did not wish to do anything like that yet. So there\nwere quarrels, and this, I am sure, was one of them.\"\n\n\"And your father?\" asked Holmes. \"Was he in favour of such a\nunion?\"\n\n\"No, he was averse to it also. No one but Mr. McCarthy was in\nfavour of it.\" A quick blush passed over her fresh young face as\nHolmes shot one of his keen, questioning glances at her.\n\n\"Thank you for this information,\" said he. \"May I see your father\nif I call to-morrow?\"\n\n\"I am afraid the doctor won't allow it.\"\n\n\"The doctor?\"\n\n\"Yes, have you not heard? Poor father has never been strong for\nyears back, but this has broken him down completely. He has taken\nto his bed, and Dr. Willows says that he is a wreck and that his\nnervous system is shattered. Mr. McCarthy was the only man alive\nwho had known dad in the old days in Victoria.\"\n\n\"Ha! In Victoria! That is important.\"\n\n\"Yes, at the mines.\"\n\n\"Quite so; at the gold-mines, where, as I understand, Mr. Turner\nmade his money.\"\n\n\"Yes, certainly.\"\n\n\"Thank you, Miss Turner. You have been of material assistance to\nme.\"\n\n\"You will tell me if you have any news to-morrow. No doubt you\nwill go to the prison to see James. Oh, if you do, Mr. Holmes, do\ntell him that I know him to be innocent.\"\n\n\"I will, Miss Turner.\"\n\n\"I must go home now, for dad is very ill, and he misses me so if\nI leave him. Good-bye, and God help you in your undertaking.\" She\nhurried from the room as impulsively as she had entered, and we\nheard the wheels of her carriage rattle off down the street.\n\n\"I am ashamed of you, Holmes,\" said Lestrade with dignity after a\nfew minutes' silence. \"Why should you raise up hopes which you\nare bound to disappoint? I am not over-tender of heart, but I\ncall it cruel.\"\n\n\"I think that I see my way to clearing James McCarthy,\" said\nHolmes. \"Have you an order to see him in prison?\"\n\n\"Yes, but only for you and me.\"\n\n\"Then I shall reconsider my resolution about going out. We have\nstill time to take a train to Hereford and see him to-night?\"\n\n\"Ample.\"\n\n\"Then let us do so. Watson, I fear that you will find it very\nslow, but I shall only be away a couple of hours.\"\n\nI walked down to the station with them, and then wandered through\nthe streets of the little town, finally returning to the hotel,\nwhere I lay upon the sofa and tried to interest myself in a\nyellow-backed novel. The puny plot of the story was so thin,\nhowever, when compared to the deep mystery through which we were\ngroping, and I found my attention wander so continually from the\naction to the fact, that I at last flung it across the room and\ngave myself up entirely to a consideration of the events of the\nday. Supposing that this unhappy young man's story were\nabsolutely true, then what hellish thing, what absolutely\nunforeseen and extraordinary calamity could have occurred between\nthe time when he parted from his father, and the moment when,\ndrawn back by his screams, he rushed into the glade? It was\nsomething terrible and deadly. What could it be? Might not the\nnature of the injuries reveal something to my medical instincts?\nI rang the bell and called for the weekly county paper, which\ncontained a verbatim account of the inquest. In the surgeon's\ndeposition it was stated that the posterior third of the left\nparietal bone and the left half of the occipital bone had been\nshattered by a heavy blow from a blunt weapon. I marked the spot\nupon my own head. Clearly such a blow must have been struck from\nbehind. That was to some extent in favour of the accused, as when\nseen quarrelling he was face to face with his father. Still, it\ndid not go for very much, for the older man might have turned his\nback before the blow fell. Still, it might be worth while to call\nHolmes' attention to it. Then there was the peculiar dying\nreference to a rat. What could that mean? It could not be\ndelirium. A man dying from a sudden blow does not commonly become\ndelirious. No, it was more likely to be an attempt to explain how\nhe met his fate. But what could it indicate? I cudgelled my\nbrains to find some possible explanation. And then the incident\nof the grey cloth seen by young McCarthy. If that were true the\nmurderer must have dropped some part of his dress, presumably his\novercoat, in his flight, and must have had the hardihood to\nreturn and to carry it away at the instant when the son was\nkneeling with his back turned not a dozen paces off. What a\ntissue of mysteries and improbabilities the whole thing was! I\ndid not wonder at Lestrade's opinion, and yet I had so much faith\nin Sherlock Holmes' insight that I could not lose hope as long\nas every fresh fact seemed to strengthen his conviction of young\nMcCarthy's innocence.\n\nIt was late before Sherlock Holmes returned. He came back alone,\nfor Lestrade was staying in lodgings in the town.\n\n\"The glass still keeps very high,\" he remarked as he sat down.\n\"It is of importance that it should not rain before we are able\nto go over the ground. On the other hand, a man should be at his\nvery best and keenest for such nice work as that, and I did not\nwish to do it when fagged by a long journey. I have seen young\nMcCarthy.\"\n\n\"And what did you learn from him?\"\n\n\"Nothing.\"\n\n\"Could he throw no light?\"\n\n\"None at all. I was inclined to think at one time that he knew\nwho had done it and was screening him or her, but I am convinced\nnow that he is as puzzled as everyone else. He is not a very\nquick-witted youth, though comely to look at and, I should think,\nsound at heart.\"\n\n\"I cannot admire his taste,\" I remarked, \"if it is indeed a fact\nthat he was averse to a marriage with so charming a young lady as\nthis Miss Turner.\"\n\n\"Ah, thereby hangs a rather painful tale. This fellow is madly,\ninsanely, in love with her, but some two years ago, when he was\nonly a lad, and before he really knew her, for she had been away\nfive years at a boarding-school, what does the idiot do but get\ninto the clutches of a barmaid in Bristol and marry her at a\nregistry office? No one knows a word of the matter, but you can\nimagine how maddening it must be to him to be upbraided for not\ndoing what he would give his very eyes to do, but what he knows\nto be absolutely impossible. It was sheer frenzy of this sort\nwhich made him throw his hands up into the air when his father,\nat their last interview, was goading him on to propose to Miss\nTurner. On the other hand, he had no means of supporting himself,\nand his father, who was by all accounts a very hard man, would\nhave thrown him over utterly had he known the truth. It was with\nhis barmaid wife that he had spent the last three days in\nBristol, and his father did not know where he was. Mark that\npoint. It is of importance. Good has come out of evil, however,\nfor the barmaid, finding from the papers that he is in serious\ntrouble and likely to be hanged, has thrown him over utterly and\nhas written to him to say that she has a husband already in the\nBermuda Dockyard, so that there is really no tie between them. I\nthink that that bit of news has consoled young McCarthy for all\nthat he has suffered.\"\n\n\"But if he is innocent, who has done it?\"\n\n\"Ah! who? I would call your attention very particularly to two\npoints. One is that the murdered man had an appointment with\nsomeone at the pool, and that the someone could not have been his\nson, for his son was away, and he did not know when he would\nreturn. The second is that the murdered man was heard to cry\n'Cooee!' before he knew that his son had returned. Those are the\ncrucial points upon which the case depends. And now let us talk\nabout George Meredith, if you please, and we shall leave all\nminor matters until to-morrow.\"\n\nThere was no rain, as Holmes had foretold, and the morning broke\nbright and cloudless. At nine o'clock Lestrade called for us with\nthe carriage, and we set off for Hatherley Farm and the Boscombe\nPool.\n\n\"There is serious news this morning,\" Lestrade observed. \"It is\nsaid that Mr. Turner, of the Hall, is so ill that his life is\ndespaired of.\"\n\n\"An elderly man, I presume?\" said Holmes.\n\n\"About sixty; but his constitution has been shattered by his life\nabroad, and he has been in failing health for some time. This\nbusiness has had a very bad effect upon him. He was an old friend\nof McCarthy's, and, I may add, a great benefactor to him, for I\nhave learned that he gave him Hatherley Farm rent free.\"\n\n\"Indeed! That is interesting,\" said Holmes.\n\n\"Oh, yes! In a hundred other ways he has helped him. Everybody\nabout here speaks of his kindness to him.\"\n\n\"Really! Does it not strike you as a little singular that this\nMcCarthy, who appears to have had little of his own, and to have\nbeen under such obligations to Turner, should still talk of\nmarrying his son to Turner's daughter, who is, presumably,\nheiress to the estate, and that in such a very cocksure manner,\nas if it were merely a case of a proposal and all else would\nfollow? It is the more strange, since we know that Turner himself\nwas averse to the idea. The daughter told us as much. Do you not\ndeduce something from that?\"\n\n\"We have got to the deductions and the inferences,\" said\nLestrade, winking at me. \"I find it hard enough to tackle facts,\nHolmes, without flying away after theories and fancies.\"\n\n\"You are right,\" said Holmes demurely; \"you do find it very hard\nto tackle the facts.\"\n\n\"Anyhow, I have grasped one fact which you seem to find it\ndifficult to get hold of,\" replied Lestrade with some warmth.\n\n\"And that is--\"\n\n\"That McCarthy senior met his death from McCarthy junior and that\nall theories to the contrary are the merest moonshine.\"\n\n\"Well, moonshine is a brighter thing than fog,\" said Holmes,\nlaughing. \"But I am very much mistaken if this is not Hatherley\nFarm upon the left.\"\n\n\"Yes, that is it.\" It was a widespread, comfortable-looking\nbuilding, two-storied, slate-roofed, with great yellow blotches\nof lichen upon the grey walls. The drawn blinds and the smokeless\nchimneys, however, gave it a stricken look, as though the weight\nof this horror still lay heavy upon it. We called at the door,\nwhen the maid, at Holmes' request, showed us the boots which her\nmaster wore at the time of his death, and also a pair of the\nson's, though not the pair which he had then had. Having measured\nthese very carefully from seven or eight different points, Holmes\ndesired to be led to the court-yard, from which we all followed\nthe winding track which led to Boscombe Pool.\n\nSherlock Holmes was transformed when he was hot upon such a scent\nas this. Men who had only known the quiet thinker and logician of\nBaker Street would have failed to recognise him. His face flushed\nand darkened. His brows were drawn into two hard black lines,\nwhile his eyes shone out from beneath them with a steely glitter.\nHis face was bent downward, his shoulders bowed, his lips\ncompressed, and the veins stood out like whipcord in his long,\nsinewy neck. His nostrils seemed to dilate with a purely animal\nlust for the chase, and his mind was so absolutely concentrated\nupon the matter before him that a question or remark fell\nunheeded upon his ears, or, at the most, only provoked a quick,\nimpatient snarl in reply. Swiftly and silently he made his way\nalong the track which ran through the meadows, and so by way of\nthe woods to the Boscombe Pool. It was damp, marshy ground, as is\nall that district, and there were marks of many feet, both upon\nthe path and amid the short grass which bounded it on either\nside. Sometimes Holmes would hurry on, sometimes stop dead, and\nonce he made quite a little detour into the meadow. Lestrade and\nI walked behind him, the detective indifferent and contemptuous,\nwhile I watched my friend with the interest which sprang from the\nconviction that every one of his actions was directed towards a\ndefinite end.\n\nThe Boscombe Pool, which is a little reed-girt sheet of water\nsome fifty yards across, is situated at the boundary between the\nHatherley Farm and the private park of the wealthy Mr. Turner.\nAbove the woods which lined it upon the farther side we could see\nthe red, jutting pinnacles which marked the site of the rich\nlandowner's dwelling. On the Hatherley side of the pool the woods\ngrew very thick, and there was a narrow belt of sodden grass\ntwenty paces across between the edge of the trees and the reeds\nwhich lined the lake. Lestrade showed us the exact spot at which\nthe body had been found, and, indeed, so moist was the ground,\nthat I could plainly see the traces which had been left by the\nfall of the stricken man. To Holmes, as I could see by his eager\nface and peering eyes, very many other things were to be read\nupon the trampled grass. He ran round, like a dog who is picking\nup a scent, and then turned upon my companion.\n\n\"What did you go into the pool for?\" he asked.\n\n\"I fished about with a rake. I thought there might be some weapon\nor other trace. But how on earth--\"\n\n\"Oh, tut, tut! I have no time! That left foot of yours with its\ninward twist is all over the place. A mole could trace it, and\nthere it vanishes among the reeds. Oh, how simple it would all\nhave been had I been here before they came like a herd of buffalo\nand wallowed all over it. Here is where the party with the\nlodge-keeper came, and they have covered all tracks for six or\neight feet round the body. But here are three separate tracks of\nthe same feet.\" He drew out a lens and lay down upon his\nwaterproof to have a better view, talking all the time rather to\nhimself than to us. \"These are young McCarthy's feet. Twice he\nwas walking, and once he ran swiftly, so that the soles are\ndeeply marked and the heels hardly visible. That bears out his\nstory. He ran when he saw his father on the ground. Then here are\nthe father's feet as he paced up and down. What is this, then? It\nis the butt-end of the gun as the son stood listening. And this?\nHa, ha! What have we here? Tiptoes! tiptoes! Square, too, quite\nunusual boots! They come, they go, they come again--of course\nthat was for the cloak. Now where did they come from?\" He ran up\nand down, sometimes losing, sometimes finding the track until we\nwere well within the edge of the wood and under the shadow of a\ngreat beech, the largest tree in the neighbourhood. Holmes traced\nhis way to the farther side of this and lay down once more upon\nhis face with a little cry of satisfaction. For a long time he\nremained there, turning over the leaves and dried sticks,\ngathering up what seemed to me to be dust into an envelope and\nexamining with his lens not only the ground but even the bark of\nthe tree as far as he could reach. A jagged stone was lying among\nthe moss, and this also he carefully examined and retained. Then\nhe followed a pathway through the wood until he came to the\nhighroad, where all traces were lost.\n\n\"It has been a case of considerable interest,\" he remarked,\nreturning to his natural manner. \"I fancy that this grey house on\nthe right must be the lodge. I think that I will go in and have a\nword with Moran, and perhaps write a little note. Having done\nthat, we may drive back to our luncheon. You may walk to the cab,\nand I shall be with you presently.\"\n\nIt was about ten minutes before we regained our cab and drove\nback into Ross, Holmes still carrying with him the stone which he\nhad picked up in the wood.\n\n\"This may interest you, Lestrade,\" he remarked, holding it out.\n\"The murder was done with it.\"\n\n\"I see no marks.\"\n\n\"There are none.\"\n\n\"How do you know, then?\"\n\n\"The grass was growing under it. It had only lain there a few\ndays. There was no sign of a place whence it had been taken. It\ncorresponds with the injuries. There is no sign of any other\nweapon.\"\n\n\"And the murderer?\"\n\n\"Is a tall man, left-handed, limps with the right leg, wears\nthick-soled shooting-boots and a grey cloak, smokes Indian\ncigars, uses a cigar-holder, and carries a blunt pen-knife in his\npocket. There are several other indications, but these may be\nenough to aid us in our search.\"\n\nLestrade laughed. \"I am afraid that I am still a sceptic,\" he\nsaid. \"Theories are all very well, but we have to deal with a\nhard-headed British jury.\"\n\n\"Nous verrons,\" answered Holmes calmly. \"You work your own\nmethod, and I shall work mine. I shall be busy this afternoon,\nand shall probably return to London by the evening train.\"\n\n\"And leave your case unfinished?\"\n\n\"No, finished.\"\n\n\"But the mystery?\"\n\n\"It is solved.\"\n\n\"Who was the criminal, then?\"\n\n\"The gentleman I describe.\"\n\n\"But who is he?\"\n\n\"Surely it would not be difficult to find out. This is not such a\npopulous neighbourhood.\"\n\nLestrade shrugged his shoulders. \"I am a practical man,\" he said,\n\"and I really cannot undertake to go about the country looking\nfor a left-handed gentleman with a game leg. I should become the\nlaughing-stock of Scotland Yard.\"\n\n\"All right,\" said Holmes quietly. \"I have given you the chance.\nHere are your lodgings. Good-bye. I shall drop you a line before\nI leave.\"\n\nHaving left Lestrade at his rooms, we drove to our hotel, where\nwe found lunch upon the table. Holmes was silent and buried in\nthought with a pained expression upon his face, as one who finds\nhimself in a perplexing position.\n\n\"Look here, Watson,\" he said when the cloth was cleared \"just sit\ndown in this chair and let me preach to you for a little. I don't\nknow quite what to do, and I should value your advice. Light a\ncigar and let me expound.\"\n\n \"Pray do so.\"\n\n\"Well, now, in considering this case there are two points about\nyoung McCarthy's narrative which struck us both instantly,\nalthough they impressed me in his favour and you against him. One\nwas the fact that his father should, according to his account,\ncry 'Cooee!' before seeing him. The other was his singular dying\nreference to a rat. He mumbled several words, you understand, but\nthat was all that caught the son's ear. Now from this double\npoint our research must commence, and we will begin it by\npresuming that what the lad says is absolutely true.\"\n\n\"What of this 'Cooee!' then?\"\n\n\"Well, obviously it could not have been meant for the son. The\nson, as far as he knew, was in Bristol. It was mere chance that\nhe was within earshot. The 'Cooee!' was meant to attract the\nattention of whoever it was that he had the appointment with. But\n'Cooee' is a distinctly Australian cry, and one which is used\nbetween Australians. There is a strong presumption that the\nperson whom McCarthy expected to meet him at Boscombe Pool was\nsomeone who had been in Australia.\"\n\n\"What of the rat, then?\"\n\nSherlock Holmes took a folded paper from his pocket and flattened\nit out on the table. \"This is a map of the Colony of Victoria,\"\nhe said. \"I wired to Bristol for it last night.\" He put his hand\nover part of the map. \"What do you read?\"\n\n\"ARAT,\" I read.\n\n\"And now?\" He raised his hand.\n\n\"BALLARAT.\"\n\n\"Quite so. That was the word the man uttered, and of which his\nson only caught the last two syllables. He was trying to utter\nthe name of his murderer. So and so, of Ballarat.\"\n\n\"It is wonderful!\" I exclaimed.\n\n\"It is obvious. And now, you see, I had narrowed the field down\nconsiderably. The possession of a grey garment was a third point\nwhich, granting the son's statement to be correct, was a\ncertainty. We have come now out of mere vagueness to the definite\nconception of an Australian from Ballarat with a grey cloak.\"\n\n\"Certainly.\"\n\n\"And one who was at home in the district, for the pool can only\nbe approached by the farm or by the estate, where strangers could\nhardly wander.\"\n\n\"Quite so.\"\n\n\"Then comes our expedition of to-day. By an examination of the\nground I gained the trifling details which I gave to that\nimbecile Lestrade, as to the personality of the criminal.\"\n\n\"But how did you gain them?\"\n\n\"You know my method. It is founded upon the observation of\ntrifles.\"\n\n\"His height I know that you might roughly judge from the length\nof his stride. His boots, too, might be told from their traces.\"\n\n\"Yes, they were peculiar boots.\"\n\n\"But his lameness?\"\n\n\"The impression of his right foot was always less distinct than\nhis left. He put less weight upon it. Why? Because he limped--he\nwas lame.\"\n\n\"But his left-handedness.\"\n\n\"You were yourself struck by the nature of the injury as recorded\nby the surgeon at the inquest. The blow was struck from\nimmediately behind, and yet was upon the left side. Now, how can\nthat be unless it were by a left-handed man? He had stood behind\nthat tree during the interview between the father and son. He had\neven smoked there. I found the ash of a cigar, which my special\nknowledge of tobacco ashes enables me to pronounce as an Indian\ncigar. I have, as you know, devoted some attention to this, and\nwritten a little monograph on the ashes of 140 different\nvarieties of pipe, cigar, and cigarette tobacco. Having found the\nash, I then looked round and discovered the stump among the moss\nwhere he had tossed it. It was an Indian cigar, of the variety\nwhich are rolled in Rotterdam.\"\n\n\"And the cigar-holder?\"\n\n\"I could see that the end had not been in his mouth. Therefore he\nused a holder. The tip had been cut off, not bitten off, but the\ncut was not a clean one, so I deduced a blunt pen-knife.\"\n\n\"Holmes,\" I said, \"you have drawn a net round this man from which\nhe cannot escape, and you have saved an innocent human life as\ntruly as if you had cut the cord which was hanging him. I see the\ndirection in which all this points. The culprit is--\"\n\n\"Mr. John Turner,\" cried the hotel waiter, opening the door of\nour sitting-room, and ushering in a visitor.\n\nThe man who entered was a strange and impressive figure. His\nslow, limping step and bowed shoulders gave the appearance of\ndecrepitude, and yet his hard, deep-lined, craggy features, and\nhis enormous limbs showed that he was possessed of unusual\nstrength of body and of character. His tangled beard, grizzled\nhair, and outstanding, drooping eyebrows combined to give an air\nof dignity and power to his appearance, but his face was of an\nashen white, while his lips and the corners of his nostrils were\ntinged with a shade of blue. It was clear to me at a glance that\nhe was in the grip of some deadly and chronic disease.\n\n\"Pray sit down on the sofa,\" said Holmes gently. \"You had my\nnote?\"\n\n\"Yes, the lodge-keeper brought it up. You said that you wished to\nsee me here to avoid scandal.\"\n\n\"I thought people would talk if I went to the Hall.\"\n\n\"And why did you wish to see me?\" He looked across at my\ncompanion with despair in his weary eyes, as though his question\nwas already answered.\n\n\"Yes,\" said Holmes, answering the look rather than the words. \"It\nis so. I know all about McCarthy.\"\n\nThe old man sank his face in his hands. \"God help me!\" he cried.\n\"But I would not have let the young man come to harm. I give you\nmy word that I would have spoken out if it went against him at\nthe Assizes.\"\n\n\"I am glad to hear you say so,\" said Holmes gravely.\n\n\"I would have spoken now had it not been for my dear girl. It\nwould break her heart--it will break her heart when she hears\nthat I am arrested.\"\n\n\"It may not come to that,\" said Holmes.\n\n\"What?\"\n\n\"I am no official agent. I understand that it was your daughter\nwho required my presence here, and I am acting in her interests.\nYoung McCarthy must be got off, however.\"\n\n\"I am a dying man,\" said old Turner. \"I have had diabetes for\nyears. My doctor says it is a question whether I shall live a\nmonth. Yet I would rather die under my own roof than in a gaol.\"\n\nHolmes rose and sat down at the table with his pen in his hand\nand a bundle of paper before him. \"Just tell us the truth,\" he\nsaid. \"I shall jot down the facts. You will sign it, and Watson\nhere can witness it. Then I could produce your confession at the\nlast extremity to save young McCarthy. I promise you that I shall\nnot use it unless it is absolutely needed.\"\n\n\"It's as well,\" said the old man; \"it's a question whether I\nshall live to the Assizes, so it matters little to me, but I\nshould wish to spare Alice the shock. And now I will make the\nthing clear to you; it has been a long time in the acting, but\nwill not take me long to tell.\n\n\"You didn't know this dead man, McCarthy. He was a devil\nincarnate. I tell you that. God keep you out of the clutches of\nsuch a man as he. His grip has been upon me these twenty years,\nand he has blasted my life. I'll tell you first how I came to be\nin his power.\n\n\"It was in the early '60's at the diggings. I was a young chap\nthen, hot-blooded and reckless, ready to turn my hand at\nanything; I got among bad companions, took to drink, had no luck\nwith my claim, took to the bush, and in a word became what you\nwould call over here a highway robber. There were six of us, and\nwe had a wild, free life of it, sticking up a station from time\nto time, or stopping the wagons on the road to the diggings.\nBlack Jack of Ballarat was the name I went under, and our party\nis still remembered in the colony as the Ballarat Gang.\n\n\"One day a gold convoy came down from Ballarat to Melbourne, and\nwe lay in wait for it and attacked it. There were six troopers\nand six of us, so it was a close thing, but we emptied four of\ntheir saddles at the first volley. Three of our boys were killed,\nhowever, before we got the swag. I put my pistol to the head of\nthe wagon-driver, who was this very man McCarthy. I wish to the\nLord that I had shot him then, but I spared him, though I saw his\nwicked little eyes fixed on my face, as though to remember every\nfeature. We got away with the gold, became wealthy men, and made\nour way over to England without being suspected. There I parted\nfrom my old pals and determined to settle down to a quiet and\nrespectable life. I bought this estate, which chanced to be in\nthe market, and I set myself to do a little good with my money,\nto make up for the way in which I had earned it. I married, too,\nand though my wife died young she left me my dear little Alice.\nEven when she was just a baby her wee hand seemed to lead me down\nthe right path as nothing else had ever done. In a word, I turned\nover a new leaf and did my best to make up for the past. All was\ngoing well when McCarthy laid his grip upon me.\n\n\"I had gone up to town about an investment, and I met him in\nRegent Street with hardly a coat to his back or a boot to his\nfoot.\n\n\"'Here we are, Jack,' says he, touching me on the arm; 'we'll be\nas good as a family to you. There's two of us, me and my son, and\nyou can have the keeping of us. If you don't--it's a fine,\nlaw-abiding country is England, and there's always a policeman\nwithin hail.'\n\n\"Well, down they came to the west country, there was no shaking\nthem off, and there they have lived rent free on my best land\never since. There was no rest for me, no peace, no forgetfulness;\nturn where I would, there was his cunning, grinning face at my\nelbow. It grew worse as Alice grew up, for he soon saw I was more\nafraid of her knowing my past than of the police. Whatever he\nwanted he must have, and whatever it was I gave him without\nquestion, land, money, houses, until at last he asked a thing\nwhich I could not give. He asked for Alice.\n\n\"His son, you see, had grown up, and so had my girl, and as I was\nknown to be in weak health, it seemed a fine stroke to him that\nhis lad should step into the whole property. But there I was\nfirm. I would not have his cursed stock mixed with mine; not that\nI had any dislike to the lad, but his blood was in him, and that\nwas enough. I stood firm. McCarthy threatened. I braved him to do\nhis worst. We were to meet at the pool midway between our houses\nto talk it over.\n\n\"When I went down there I found him talking with his son, so I\nsmoked a cigar and waited behind a tree until he should be alone.\nBut as I listened to his talk all that was black and bitter in\nme seemed to come uppermost. He was urging his son to marry my\ndaughter with as little regard for what she might think as if she\nwere a slut from off the streets. It drove me mad to think that I\nand all that I held most dear should be in the power of such a\nman as this. Could I not snap the bond? I was already a dying and\na desperate man. Though clear of mind and fairly strong of limb,\nI knew that my own fate was sealed. But my memory and my girl!\nBoth could be saved if I could but silence that foul tongue. I\ndid it, Mr. Holmes. I would do it again. Deeply as I have sinned,\nI have led a life of martyrdom to atone for it. But that my girl\nshould be entangled in the same meshes which held me was more\nthan I could suffer. I struck him down with no more compunction\nthan if he had been some foul and venomous beast. His cry brought\nback his son; but I had gained the cover of the wood, though I\nwas forced to go back to fetch the cloak which I had dropped in\nmy flight. That is the true story, gentlemen, of all that\noccurred.\"\n\n\"Well, it is not for me to judge you,\" said Holmes as the old man\nsigned the statement which had been drawn out. \"I pray that we\nmay never be exposed to such a temptation.\"\n\n\"I pray not, sir. And what do you intend to do?\"\n\n\"In view of your health, nothing. You are yourself aware that you\nwill soon have to answer for your deed at a higher court than the\nAssizes. I will keep your confession, and if McCarthy is\ncondemned I shall be forced to use it. If not, it shall never be\nseen by mortal eye; and your secret, whether you be alive or\ndead, shall be safe with us.\"\n\n\"Farewell, then,\" said the old man solemnly. \"Your own deathbeds,\nwhen they come, will be the easier for the thought of the peace\nwhich you have given to mine.\" Tottering and shaking in all his\ngiant frame, he stumbled slowly from the room.\n\n\"God help us!\" said Holmes after a long silence. \"Why does fate\nplay such tricks with poor, helpless worms? I never hear of such\na case as this that I do not think of Baxter's words, and say,\n'There, but for the grace of God, goes Sherlock Holmes.'\"\n\nJames McCarthy was acquitted at the Assizes on the strength of a\nnumber of objections which had been drawn out by Holmes and\nsubmitted to the defending counsel. Old Turner lived for seven\nmonths after our interview, but he is now dead; and there is\nevery prospect that the son and daughter may come to live happily\ntogether in ignorance of the black cloud which rests upon their\npast.\n\n\n\nADVENTURE V. THE FIVE ORANGE PIPS\n\nWhen I glance over my notes and records of the Sherlock Holmes\ncases between the years '82 and '90, I am faced by so many which\npresent strange and interesting features that it is no easy\nmatter to know which to choose and which to leave. Some, however,\nhave already gained publicity through the papers, and others have\nnot offered a field for those peculiar qualities which my friend\npossessed in so high a degree, and which it is the object of\nthese papers to illustrate. Some, too, have baffled his\nanalytical skill, and would be, as narratives, beginnings without\nan ending, while others have been but partially cleared up, and\nhave their explanations founded rather upon conjecture and\nsurmise than on that absolute logical proof which was so dear to\nhim. There is, however, one of these last which was so remarkable\nin its details and so startling in its results that I am tempted\nto give some account of it in spite of the fact that there are\npoints in connection with it which never have been, and probably\nnever will be, entirely cleared up.\n\nThe year '87 furnished us with a long series of cases of greater\nor less interest, of which I retain the records. Among my\nheadings under this one twelve months I find an account of the\nadventure of the Paradol Chamber, of the Amateur Mendicant\nSociety, who held a luxurious club in the lower vault of a\nfurniture warehouse, of the facts connected with the loss of the\nBritish barque \"Sophy Anderson\", of the singular adventures of the\nGrice Patersons in the island of Uffa, and finally of the\nCamberwell poisoning case. In the latter, as may be remembered,\nSherlock Holmes was able, by winding up the dead man's watch, to\nprove that it had been wound up two hours before, and that\ntherefore the deceased had gone to bed within that time--a\ndeduction which was of the greatest importance in clearing up the\ncase. All these I may sketch out at some future date, but none of\nthem present such singular features as the strange train of\ncircumstances which I have now taken up my pen to describe.\n\nIt was in the latter days of September, and the equinoctial gales\nhad set in with exceptional violence. All day the wind had\nscreamed and the rain had beaten against the windows, so that\neven here in the heart of great, hand-made London we were forced\nto raise our minds for the instant from the routine of life and\nto recognise the presence of those great elemental forces which\nshriek at mankind through the bars of his civilisation, like\nuntamed beasts in a cage. As evening drew in, the storm grew\nhigher and louder, and the wind cried and sobbed like a child in\nthe chimney. Sherlock Holmes sat moodily at one side of the\nfireplace cross-indexing his records of crime, while I at the\nother was deep in one of Clark Russell's fine sea-stories until\nthe howl of the gale from without seemed to blend with the text,\nand the splash of the rain to lengthen out into the long swash of\nthe sea waves. My wife was on a visit to her mother's, and for a\nfew days I was a dweller once more in my old quarters at Baker\nStreet.\n\n\"Why,\" said I, glancing up at my companion, \"that was surely the\nbell. Who could come to-night? Some friend of yours, perhaps?\"\n\n\"Except yourself I have none,\" he answered. \"I do not encourage\nvisitors.\"\n\n\"A client, then?\"\n\n\"If so, it is a serious case. Nothing less would bring a man out\non such a day and at such an hour. But I take it that it is more\nlikely to be some crony of the landlady's.\"\n\nSherlock Holmes was wrong in his conjecture, however, for there\ncame a step in the passage and a tapping at the door. He\nstretched out his long arm to turn the lamp away from himself and\ntowards the vacant chair upon which a newcomer must sit.\n\n\"Come in!\" said he.\n\nThe man who entered was young, some two-and-twenty at the\noutside, well-groomed and trimly clad, with something of\nrefinement and delicacy in his bearing. The streaming umbrella\nwhich he held in his hand, and his long shining waterproof told\nof the fierce weather through which he had come. He looked about\nhim anxiously in the glare of the lamp, and I could see that his\nface was pale and his eyes heavy, like those of a man who is\nweighed down with some great anxiety.\n\n\"I owe you an apology,\" he said, raising his golden pince-nez to\nhis eyes. \"I trust that I am not intruding. I fear that I have\nbrought some traces of the storm and rain into your snug\nchamber.\"\n\n\"Give me your coat and umbrella,\" said Holmes. \"They may rest\nhere on the hook and will be dry presently. You have come up from\nthe south-west, I see.\"\n\n\"Yes, from Horsham.\"\n\n\"That clay and chalk mixture which I see upon your toe caps is\nquite distinctive.\"\n\n\"I have come for advice.\"\n\n\"That is easily got.\"\n\n\"And help.\"\n\n\"That is not always so easy.\"\n\n\"I have heard of you, Mr. Holmes. I heard from Major Prendergast\nhow you saved him in the Tankerville Club scandal.\"\n\n\"Ah, of course. He was wrongfully accused of cheating at cards.\"\n\n\"He said that you could solve anything.\"\n\n\"He said too much.\"\n\n\"That you are never beaten.\"\n\n\"I have been beaten four times--three times by men, and once by a\nwoman.\"\n\n\"But what is that compared with the number of your successes?\"\n\n\"It is true that I have been generally successful.\"\n\n\"Then you may be so with me.\"\n\n\"I beg that you will draw your chair up to the fire and favour me\nwith some details as to your case.\"\n\n\"It is no ordinary one.\"\n\n\"None of those which come to me are. I am the last court of\nappeal.\"\n\n\"And yet I question, sir, whether, in all your experience, you\nhave ever listened to a more mysterious and inexplicable chain of\nevents than those which have happened in my own family.\"\n\n\"You fill me with interest,\" said Holmes. \"Pray give us the\nessential facts from the commencement, and I can afterwards\nquestion you as to those details which seem to me to be most\nimportant.\"\n\nThe young man pulled his chair up and pushed his wet feet out\ntowards the blaze.\n\n\"My name,\" said he, \"is John Openshaw, but my own affairs have,\nas far as I can understand, little to do with this awful\nbusiness. It is a hereditary matter; so in order to give you an\nidea of the facts, I must go back to the commencement of the\naffair.\n\n\"You must know that my grandfather had two sons--my uncle Elias\nand my father Joseph. My father had a small factory at Coventry,\nwhich he enlarged at the time of the invention of bicycling. He\nwas a patentee of the Openshaw unbreakable tire, and his business\nmet with such success that he was able to sell it and to retire\nupon a handsome competence.\n\n\"My uncle Elias emigrated to America when he was a young man and\nbecame a planter in Florida, where he was reported to have done\nvery well. At the time of the war he fought in Jackson's army,\nand afterwards under Hood, where he rose to be a colonel. When\nLee laid down his arms my uncle returned to his plantation, where\nhe remained for three or four years. About 1869 or 1870 he came\nback to Europe and took a small estate in Sussex, near Horsham.\nHe had made a very considerable fortune in the States, and his\nreason for leaving them was his aversion to the negroes, and his\ndislike of the Republican policy in extending the franchise to\nthem. He was a singular man, fierce and quick-tempered, very\nfoul-mouthed when he was angry, and of a most retiring\ndisposition. During all the years that he lived at Horsham, I\ndoubt if ever he set foot in the town. He had a garden and two or\nthree fields round his house, and there he would take his\nexercise, though very often for weeks on end he would never leave\nhis room. He drank a great deal of brandy and smoked very\nheavily, but he would see no society and did not want any\nfriends, not even his own brother.\n\n\"He didn't mind me; in fact, he took a fancy to me, for at the\ntime when he saw me first I was a youngster of twelve or so. This\nwould be in the year 1878, after he had been eight or nine years\nin England. He begged my father to let me live with him and he\nwas very kind to me in his way. When he was sober he used to be\nfond of playing backgammon and draughts with me, and he would\nmake me his representative both with the servants and with the\ntradespeople, so that by the time that I was sixteen I was quite\nmaster of the house. I kept all the keys and could go where I\nliked and do what I liked, so long as I did not disturb him in\nhis privacy. There was one singular exception, however, for he\nhad a single room, a lumber-room up among the attics, which was\ninvariably locked, and which he would never permit either me or\nanyone else to enter. With a boy's curiosity I have peeped\nthrough the keyhole, but I was never able to see more than such a\ncollection of old trunks and bundles as would be expected in such\na room.\n\n\"One day--it was in March, 1883--a letter with a foreign stamp\nlay upon the table in front of the colonel's plate. It was not a\ncommon thing for him to receive letters, for his bills were all\npaid in ready money, and he had no friends of any sort. 'From\nIndia!' said he as he took it up, 'Pondicherry postmark! What can\nthis be?' Opening it hurriedly, out there jumped five little\ndried orange pips, which pattered down upon his plate. I began to\nlaugh at this, but the laugh was struck from my lips at the sight\nof his face. His lip had fallen, his eyes were protruding, his\nskin the colour of putty, and he glared at the envelope which he\nstill held in his trembling hand, 'K. K. K.!' he shrieked, and\nthen, 'My God, my God, my sins have overtaken me!'\n\n\"'What is it, uncle?' I cried.\n\n\"'Death,' said he, and rising from the table he retired to his\nroom, leaving me palpitating with horror. I took up the envelope\nand saw scrawled in red ink upon the inner flap, just above the\ngum, the letter K three times repeated. There was nothing else\nsave the five dried pips. What could be the reason of his\noverpowering terror? I left the breakfast-table, and as I\nascended the stair I met him coming down with an old rusty key,\nwhich must have belonged to the attic, in one hand, and a small\nbrass box, like a cashbox, in the other.\n\n\"'They may do what they like, but I'll checkmate them still,'\nsaid he with an oath. 'Tell Mary that I shall want a fire in my\nroom to-day, and send down to Fordham, the Horsham lawyer.'\n\n\"I did as he ordered, and when the lawyer arrived I was asked to\nstep up to the room. The fire was burning brightly, and in the\ngrate there was a mass of black, fluffy ashes, as of burned\npaper, while the brass box stood open and empty beside it. As I\nglanced at the box I noticed, with a start, that upon the lid was\nprinted the treble K which I had read in the morning upon the\nenvelope.\n\n\"'I wish you, John,' said my uncle, 'to witness my will. I leave\nmy estate, with all its advantages and all its disadvantages, to\nmy brother, your father, whence it will, no doubt, descend to\nyou. If you can enjoy it in peace, well and good! If you find you\ncannot, take my advice, my boy, and leave it to your deadliest\nenemy. I am sorry to give you such a two-edged thing, but I can't\nsay what turn things are going to take. Kindly sign the paper\nwhere Mr. Fordham shows you.'\n\n\"I signed the paper as directed, and the lawyer took it away with\nhim. The singular incident made, as you may think, the deepest\nimpression upon me, and I pondered over it and turned it every\nway in my mind without being able to make anything of it. Yet I\ncould not shake off the vague feeling of dread which it left\nbehind, though the sensation grew less keen as the weeks passed\nand nothing happened to disturb the usual routine of our lives. I\ncould see a change in my uncle, however. He drank more than ever,\nand he was less inclined for any sort of society. Most of his\ntime he would spend in his room, with the door locked upon the\ninside, but sometimes he would emerge in a sort of drunken frenzy\nand would burst out of the house and tear about the garden with a\nrevolver in his hand, screaming out that he was afraid of no man,\nand that he was not to be cooped up, like a sheep in a pen, by\nman or devil. When these hot fits were over, however, he would\nrush tumultuously in at the door and lock and bar it behind him,\nlike a man who can brazen it out no longer against the terror\nwhich lies at the roots of his soul. At such times I have seen\nhis face, even on a cold day, glisten with moisture, as though it\nwere new raised from a basin.\n\n\"Well, to come to an end of the matter, Mr. Holmes, and not to\nabuse your patience, there came a night when he made one of those\ndrunken sallies from which he never came back. We found him, when\nwe went to search for him, face downward in a little\ngreen-scummed pool, which lay at the foot of the garden. There\nwas no sign of any violence, and the water was but two feet deep,\nso that the jury, having regard to his known eccentricity,\nbrought in a verdict of 'suicide.' But I, who knew how he winced\nfrom the very thought of death, had much ado to persuade myself\nthat he had gone out of his way to meet it. The matter passed,\nhowever, and my father entered into possession of the estate, and\nof some 14,000 pounds, which lay to his credit at the bank.\"\n\n\"One moment,\" Holmes interposed, \"your statement is, I foresee,\none of the most remarkable to which I have ever listened. Let me\nhave the date of the reception by your uncle of the letter, and\nthe date of his supposed suicide.\"\n\n\"The letter arrived on March 10, 1883. His death was seven weeks\nlater, upon the night of May 2nd.\"\n\n\"Thank you. Pray proceed.\"\n\n\"When my father took over the Horsham property, he, at my\nrequest, made a careful examination of the attic, which had been\nalways locked up. We found the brass box there, although its\ncontents had been destroyed. On the inside of the cover was a\npaper label, with the initials of K. K. K. repeated upon it, and\n'Letters, memoranda, receipts, and a register' written beneath.\nThese, we presume, indicated the nature of the papers which had\nbeen destroyed by Colonel Openshaw. For the rest, there was\nnothing of much importance in the attic save a great many\nscattered papers and note-books bearing upon my uncle's life in\nAmerica. Some of them were of the war time and showed that he had\ndone his duty well and had borne the repute of a brave soldier.\nOthers were of a date during the reconstruction of the Southern\nstates, and were mostly concerned with politics, for he had\nevidently taken a strong part in opposing the carpet-bag\npoliticians who had been sent down from the North.\n\n\"Well, it was the beginning of '84 when my father came to live at\nHorsham, and all went as well as possible with us until the\nJanuary of '85. On the fourth day after the new year I heard my\nfather give a sharp cry of surprise as we sat together at the\nbreakfast-table. There he was, sitting with a newly opened\nenvelope in one hand and five dried orange pips in the\noutstretched palm of the other one. He had always laughed at what\nhe called my cock-and-bull story about the colonel, but he looked\nvery scared and puzzled now that the same thing had come upon\nhimself.\n\n\"'Why, what on earth does this mean, John?' he stammered.\n\n\"My heart had turned to lead. 'It is K. K. K.,' said I.\n\n\"He looked inside the envelope. 'So it is,' he cried. 'Here are\nthe very letters. But what is this written above them?'\n\n\"'Put the papers on the sundial,' I read, peeping over his\nshoulder.\n\n\"'What papers? What sundial?' he asked.\n\n\"'The sundial in the garden. There is no other,' said I; 'but the\npapers must be those that are destroyed.'\n\n\"'Pooh!' said he, gripping hard at his courage. 'We are in a\ncivilised land here, and we can't have tomfoolery of this kind.\nWhere does the thing come from?'\n\n\"'From Dundee,' I answered, glancing at the postmark.\n\n\"'Some preposterous practical joke,' said he. 'What have I to do\nwith sundials and papers? I shall take no notice of such\nnonsense.'\n\n\"'I should certainly speak to the police,' I said.\n\n\"'And be laughed at for my pains. Nothing of the sort.'\n\n\"'Then let me do so?'\n\n\"'No, I forbid you. I won't have a fuss made about such\nnonsense.'\n\n\"It was in vain to argue with him, for he was a very obstinate\nman. I went about, however, with a heart which was full of\nforebodings.\n\n\"On the third day after the coming of the letter my father went\nfrom home to visit an old friend of his, Major Freebody, who is\nin command of one of the forts upon Portsdown Hill. I was glad\nthat he should go, for it seemed to me that he was farther from\ndanger when he was away from home. In that, however, I was in\nerror. Upon the second day of his absence I received a telegram\nfrom the major, imploring me to come at once. My father had\nfallen over one of the deep chalk-pits which abound in the\nneighbourhood, and was lying senseless, with a shattered skull. I\nhurried to him, but he passed away without having ever recovered\nhis consciousness. He had, as it appears, been returning from\nFareham in the twilight, and as the country was unknown to him,\nand the chalk-pit unfenced, the jury had no hesitation in\nbringing in a verdict of 'death from accidental causes.'\nCarefully as I examined every fact connected with his death, I\nwas unable to find anything which could suggest the idea of\nmurder. There were no signs of violence, no footmarks, no\nrobbery, no record of strangers having been seen upon the roads.\nAnd yet I need not tell you that my mind was far from at ease,\nand that I was well-nigh certain that some foul plot had been\nwoven round him.\n\n\"In this sinister way I came into my inheritance. You will ask me\nwhy I did not dispose of it? I answer, because I was well\nconvinced that our troubles were in some way dependent upon an\nincident in my uncle's life, and that the danger would be as\npressing in one house as in another.\n\n\"It was in January, '85, that my poor father met his end, and two\nyears and eight months have elapsed since then. During that time\nI have lived happily at Horsham, and I had begun to hope that\nthis curse had passed away from the family, and that it had ended\nwith the last generation. I had begun to take comfort too soon,\nhowever; yesterday morning the blow fell in the very shape in\nwhich it had come upon my father.\"\n\nThe young man took from his waistcoat a crumpled envelope, and\nturning to the table he shook out upon it five little dried\norange pips.\n\n\"This is the envelope,\" he continued. \"The postmark is\nLondon--eastern division. Within are the very words which were\nupon my father's last message: 'K. K. K.'; and then 'Put the\npapers on the sundial.'\"\n\n\"What have you done?\" asked Holmes.\n\n\"Nothing.\"\n\n\"Nothing?\"\n\n\"To tell the truth\"--he sank his face into his thin, white\nhands--\"I have felt helpless. I have felt like one of those poor\nrabbits when the snake is writhing towards it. I seem to be in\nthe grasp of some resistless, inexorable evil, which no foresight\nand no precautions can guard against.\"\n\n\"Tut! tut!\" cried Sherlock Holmes. \"You must act, man, or you are\nlost. Nothing but energy can save you. This is no time for\ndespair.\"\n\n\"I have seen the police.\"\n\n\"Ah!\"\n\n\"But they listened to my story with a smile. I am convinced that\nthe inspector has formed the opinion that the letters are all\npractical jokes, and that the deaths of my relations were really\naccidents, as the jury stated, and were not to be connected with\nthe warnings.\"\n\nHolmes shook his clenched hands in the air. \"Incredible\nimbecility!\" he cried.\n\n\"They have, however, allowed me a policeman, who may remain in\nthe house with me.\"\n\n\"Has he come with you to-night?\"\n\n\"No. His orders were to stay in the house.\"\n\nAgain Holmes raved in the air.\n\n\"Why did you come to me,\" he cried, \"and, above all, why did you\nnot come at once?\"\n\n\"I did not know. It was only to-day that I spoke to Major\nPrendergast about my troubles and was advised by him to come to\nyou.\"\n\n\"It is really two days since you had the letter. We should have\nacted before this. You have no further evidence, I suppose, than\nthat which you have placed before us--no suggestive detail which\nmight help us?\"\n\n\"There is one thing,\" said John Openshaw. He rummaged in his coat\npocket, and, drawing out a piece of discoloured, blue-tinted\npaper, he laid it out upon the table. \"I have some remembrance,\"\nsaid he, \"that on the day when my uncle burned the papers I\nobserved that the small, unburned margins which lay amid the\nashes were of this particular colour. I found this single sheet\nupon the floor of his room, and I am inclined to think that it\nmay be one of the papers which has, perhaps, fluttered out from\namong the others, and in that way has escaped destruction. Beyond\nthe mention of pips, I do not see that it helps us much. I think\nmyself that it is a page from some private diary. The writing is\nundoubtedly my uncle's.\"\n\nHolmes moved the lamp, and we both bent over the sheet of paper,\nwhich showed by its ragged edge that it had indeed been torn from\na book. It was headed, \"March, 1869,\" and beneath were the\nfollowing enigmatical notices:\n\n\"4th. Hudson came. Same old platform.\n\n\"7th. Set the pips on McCauley, Paramore, and\n      John Swain, of St. Augustine.\n\n\"9th. McCauley cleared.\n\n\"10th. John Swain cleared.\n\n\"12th. Visited Paramore. All well.\"\n\n\"Thank you!\" said Holmes, folding up the paper and returning it\nto our visitor. \"And now you must on no account lose another\ninstant. We cannot spare time even to discuss what you have told\nme. You must get home instantly and act.\"\n\n\"What shall I do?\"\n\n\"There is but one thing to do. It must be done at once. You must\nput this piece of paper which you have shown us into the brass\nbox which you have described. You must also put in a note to say\nthat all the other papers were burned by your uncle, and that\nthis is the only one which remains. You must assert that in such\nwords as will carry conviction with them. Having done this, you\nmust at once put the box out upon the sundial, as directed. Do\nyou understand?\"\n\n\"Entirely.\"\n\n\"Do not think of revenge, or anything of the sort, at present. I\nthink that we may gain that by means of the law; but we have our\nweb to weave, while theirs is already woven. The first\nconsideration is to remove the pressing danger which threatens\nyou. The second is to clear up the mystery and to punish the\nguilty parties.\"\n\n\"I thank you,\" said the young man, rising and pulling on his\novercoat. \"You have given me fresh life and hope. I shall\ncertainly do as you advise.\"\n\n\"Do not lose an instant. And, above all, take care of yourself in\nthe meanwhile, for I do not think that there can be a doubt that\nyou are threatened by a very real and imminent danger. How do you\ngo back?\"\n\n\"By train from Waterloo.\"\n\n\"It is not yet nine. The streets will be crowded, so I trust that\nyou may be in safety. And yet you cannot guard yourself too\nclosely.\"\n\n\"I am armed.\"\n\n\"That is well. To-morrow I shall set to work upon your case.\"\n\n\"I shall see you at Horsham, then?\"\n\n\"No, your secret lies in London. It is there that I shall seek\nit.\"\n\n\"Then I shall call upon you in a day, or in two days, with news\nas to the box and the papers. I shall take your advice in every\nparticular.\" He shook hands with us and took his leave. Outside\nthe wind still screamed and the rain splashed and pattered\nagainst the windows. This strange, wild story seemed to have come\nto us from amid the mad elements--blown in upon us like a sheet\nof sea-weed in a gale--and now to have been reabsorbed by them\nonce more.\n\nSherlock Holmes sat for some time in silence, with his head sunk\nforward and his eyes bent upon the red glow of the fire. Then he\nlit his pipe, and leaning back in his chair he watched the blue\nsmoke-rings as they chased each other up to the ceiling.\n\n\"I think, Watson,\" he remarked at last, \"that of all our cases we\nhave had none more fantastic than this.\"\n\n\"Save, perhaps, the Sign of Four.\"\n\n\"Well, yes. Save, perhaps, that. And yet this John Openshaw seems\nto me to be walking amid even greater perils than did the\nSholtos.\"\n\n\"But have you,\" I asked, \"formed any definite conception as to\nwhat these perils are?\"\n\n\"There can be no question as to their nature,\" he answered.\n\n\"Then what are they? Who is this K. K. K., and why does he pursue\nthis unhappy family?\"\n\nSherlock Holmes closed his eyes and placed his elbows upon the\narms of his chair, with his finger-tips together. \"The ideal\nreasoner,\" he remarked, \"would, when he had once been shown a\nsingle fact in all its bearings, deduce from it not only all the\nchain of events which led up to it but also all the results which\nwould follow from it. As Cuvier could correctly describe a whole\nanimal by the contemplation of a single bone, so the observer who\nhas thoroughly understood one link in a series of incidents\nshould be able to accurately state all the other ones, both\nbefore and after. We have not yet grasped the results which the\nreason alone can attain to. Problems may be solved in the study\nwhich have baffled all those who have sought a solution by the\naid of their senses. To carry the art, however, to its highest\npitch, it is necessary that the reasoner should be able to\nutilise all the facts which have come to his knowledge; and this\nin itself implies, as you will readily see, a possession of all\nknowledge, which, even in these days of free education and\nencyclopaedias, is a somewhat rare accomplishment. It is not so\nimpossible, however, that a man should possess all knowledge\nwhich is likely to be useful to him in his work, and this I have\nendeavoured in my case to do. If I remember rightly, you on one\noccasion, in the early days of our friendship, defined my limits\nin a very precise fashion.\"\n\n\"Yes,\" I answered, laughing. \"It was a singular document.\nPhilosophy, astronomy, and politics were marked at zero, I\nremember. Botany variable, geology profound as regards the\nmud-stains from any region within fifty miles of town, chemistry\neccentric, anatomy unsystematic, sensational literature and crime\nrecords unique, violin-player, boxer, swordsman, lawyer, and\nself-poisoner by cocaine and tobacco. Those, I think, were the\nmain points of my analysis.\"\n\nHolmes grinned at the last item. \"Well,\" he said, \"I say now, as\nI said then, that a man should keep his little brain-attic\nstocked with all the furniture that he is likely to use, and the\nrest he can put away in the lumber-room of his library, where he\ncan get it if he wants it. Now, for such a case as the one which\nhas been submitted to us to-night, we need certainly to muster\nall our resources. Kindly hand me down the letter K of the\n'American Encyclopaedia' which stands upon the shelf beside you.\nThank you. Now let us consider the situation and see what may be\ndeduced from it. In the first place, we may start with a strong\npresumption that Colonel Openshaw had some very strong reason for\nleaving America. Men at his time of life do not change all their\nhabits and exchange willingly the charming climate of Florida for\nthe lonely life of an English provincial town. His extreme love\nof solitude in England suggests the idea that he was in fear of\nsomeone or something, so we may assume as a working hypothesis\nthat it was fear of someone or something which drove him from\nAmerica. As to what it was he feared, we can only deduce that by\nconsidering the formidable letters which were received by himself\nand his successors. Did you remark the postmarks of those\nletters?\"\n\n\"The first was from Pondicherry, the second from Dundee, and the\nthird from London.\"\n\n\"From East London. What do you deduce from that?\"\n\n\"They are all seaports. That the writer was on board of a ship.\"\n\n\"Excellent. We have already a clue. There can be no doubt that\nthe probability--the strong probability--is that the writer was\non board of a ship. And now let us consider another point. In the\ncase of Pondicherry, seven weeks elapsed between the threat and\nits fulfilment, in Dundee it was only some three or four days.\nDoes that suggest anything?\"\n\n\"A greater distance to travel.\"\n\n\"But the letter had also a greater distance to come.\"\n\n\"Then I do not see the point.\"\n\n\"There is at least a presumption that the vessel in which the man\nor men are is a sailing-ship. It looks as if they always send\ntheir singular warning or token before them when starting upon\ntheir mission. You see how quickly the deed followed the sign\nwhen it came from Dundee. If they had come from Pondicherry in a\nsteamer they would have arrived almost as soon as their letter.\nBut, as a matter of fact, seven weeks elapsed. I think that those\nseven weeks represented the difference between the mail-boat which\nbrought the letter and the sailing vessel which brought the\nwriter.\"\n\n\"It is possible.\"\n\n\"More than that. It is probable. And now you see the deadly\nurgency of this new case, and why I urged young Openshaw to\ncaution. The blow has always fallen at the end of the time which\nit would take the senders to travel the distance. But this one\ncomes from London, and therefore we cannot count upon delay.\"\n\n\"Good God!\" I cried. \"What can it mean, this relentless\npersecution?\"\n\n\"The papers which Openshaw carried are obviously of vital\nimportance to the person or persons in the sailing-ship. I think\nthat it is quite clear that there must be more than one of them.\nA single man could not have carried out two deaths in such a way\nas to deceive a coroner's jury. There must have been several in\nit, and they must have been men of resource and determination.\nTheir papers they mean to have, be the holder of them who it may.\nIn this way you see K. K. K. ceases to be the initials of an\nindividual and becomes the badge of a society.\"\n\n\"But of what society?\"\n\n\"Have you never--\" said Sherlock Holmes, bending forward and\nsinking his voice--\"have you never heard of the Ku Klux Klan?\"\n\n\"I never have.\"\n\nHolmes turned over the leaves of the book upon his knee. \"Here it\nis,\" said he presently:\n\n\"'Ku Klux Klan. A name derived from the fanciful resemblance to\nthe sound produced by cocking a rifle. This terrible secret\nsociety was formed by some ex-Confederate soldiers in the\nSouthern states after the Civil War, and it rapidly formed local\nbranches in different parts of the country, notably in Tennessee,\nLouisiana, the Carolinas, Georgia, and Florida. Its power was\nused for political purposes, principally for the terrorising of\nthe negro voters and the murdering and driving from the country\nof those who were opposed to its views. Its outrages were usually\npreceded by a warning sent to the marked man in some fantastic\nbut generally recognised shape--a sprig of oak-leaves in some\nparts, melon seeds or orange pips in others. On receiving this\nthe victim might either openly abjure his former ways, or might\nfly from the country. If he braved the matter out, death would\nunfailingly come upon him, and usually in some strange and\nunforeseen manner. So perfect was the organisation of the\nsociety, and so systematic its methods, that there is hardly a\ncase upon record where any man succeeded in braving it with\nimpunity, or in which any of its outrages were traced home to the\nperpetrators. For some years the organisation flourished in spite\nof the efforts of the United States government and of the better\nclasses of the community in the South. Eventually, in the year\n1869, the movement rather suddenly collapsed, although there have\nbeen sporadic outbreaks of the same sort since that date.'\n\n\"You will observe,\" said Holmes, laying down the volume, \"that\nthe sudden breaking up of the society was coincident with the\ndisappearance of Openshaw from America with their papers. It may\nwell have been cause and effect. It is no wonder that he and his\nfamily have some of the more implacable spirits upon their track.\nYou can understand that this register and diary may implicate\nsome of the first men in the South, and that there may be many\nwho will not sleep easy at night until it is recovered.\"\n\n\"Then the page we have seen--\"\n\n\"Is such as we might expect. It ran, if I remember right, 'sent\nthe pips to A, B, and C'--that is, sent the society's warning to\nthem. Then there are successive entries that A and B cleared, or\nleft the country, and finally that C was visited, with, I fear, a\nsinister result for C. Well, I think, Doctor, that we may let\nsome light into this dark place, and I believe that the only\nchance young Openshaw has in the meantime is to do what I have\ntold him. There is nothing more to be said or to be done\nto-night, so hand me over my violin and let us try to forget for\nhalf an hour the miserable weather and the still more miserable\nways of our fellow-men.\"\n\n\nIt had cleared in the morning, and the sun was shining with a\nsubdued brightness through the dim veil which hangs over the\ngreat city. Sherlock Holmes was already at breakfast when I came\ndown.\n\n\"You will excuse me for not waiting for you,\" said he; \"I have, I\nforesee, a very busy day before me in looking into this case of\nyoung Openshaw's.\"\n\n\"What steps will you take?\" I asked.\n\n\"It will very much depend upon the results of my first inquiries.\nI may have to go down to Horsham, after all.\"\n\n\"You will not go there first?\"\n\n\"No, I shall commence with the City. Just ring the bell and the\nmaid will bring up your coffee.\"\n\nAs I waited, I lifted the unopened newspaper from the table and\nglanced my eye over it. It rested upon a heading which sent a\nchill to my heart.\n\n\"Holmes,\" I cried, \"you are too late.\"\n\n\"Ah!\" said he, laying down his cup, \"I feared as much. How was it\ndone?\" He spoke calmly, but I could see that he was deeply moved.\n\n\"My eye caught the name of Openshaw, and the heading 'Tragedy\nNear Waterloo Bridge.' Here is the account:\n\n\"Between nine and ten last night Police-Constable Cook, of the H\nDivision, on duty near Waterloo Bridge, heard a cry for help and\na splash in the water. The night, however, was extremely dark and\nstormy, so that, in spite of the help of several passers-by, it\nwas quite impossible to effect a rescue. The alarm, however, was\ngiven, and, by the aid of the water-police, the body was\neventually recovered. It proved to be that of a young gentleman\nwhose name, as it appears from an envelope which was found in his\npocket, was John Openshaw, and whose residence is near Horsham.\nIt is conjectured that he may have been hurrying down to catch\nthe last train from Waterloo Station, and that in his haste and\nthe extreme darkness he missed his path and walked over the edge\nof one of the small landing-places for river steamboats. The body\nexhibited no traces of violence, and there can be no doubt that\nthe deceased had been the victim of an unfortunate accident,\nwhich should have the effect of calling the attention of the\nauthorities to the condition of the riverside landing-stages.\"\n\nWe sat in silence for some minutes, Holmes more depressed and\nshaken than I had ever seen him.\n\n\"That hurts my pride, Watson,\" he said at last. \"It is a petty\nfeeling, no doubt, but it hurts my pride. It becomes a personal\nmatter with me now, and, if God sends me health, I shall set my\nhand upon this gang. That he should come to me for help, and that\nI should send him away to his death--!\" He sprang from his chair\nand paced about the room in uncontrollable agitation, with a\nflush upon his sallow cheeks and a nervous clasping and\nunclasping of his long thin hands.\n\n\"They must be cunning devils,\" he exclaimed at last. \"How could\nthey have decoyed him down there? The Embankment is not on the\ndirect line to the station. The bridge, no doubt, was too\ncrowded, even on such a night, for their purpose. Well, Watson,\nwe shall see who will win in the long run. I am going out now!\"\n\n\"To the police?\"\n\n\"No; I shall be my own police. When I have spun the web they may\ntake the flies, but not before.\"\n\nAll day I was engaged in my professional work, and it was late in\nthe evening before I returned to Baker Street. Sherlock Holmes\nhad not come back yet. It was nearly ten o'clock before he\nentered, looking pale and worn. He walked up to the sideboard,\nand tearing a piece from the loaf he devoured it voraciously,\nwashing it down with a long draught of water.\n\n\"You are hungry,\" I remarked.\n\n\"Starving. It had escaped my memory. I have had nothing since\nbreakfast.\"\n\n\"Nothing?\"\n\n\"Not a bite. I had no time to think of it.\"\n\n\"And how have you succeeded?\"\n\n\"Well.\"\n\n\"You have a clue?\"\n\n\"I have them in the hollow of my hand. Young Openshaw shall not\nlong remain unavenged. Why, Watson, let us put their own devilish\ntrade-mark upon them. It is well thought of!\"\n\n\"What do you mean?\"\n\nHe took an orange from the cupboard, and tearing it to pieces he\nsqueezed out the pips upon the table. Of these he took five and\nthrust them into an envelope. On the inside of the flap he wrote\n\"S. H. for J. O.\" Then he sealed it and addressed it to \"Captain\nJames Calhoun, Barque 'Lone Star,' Savannah, Georgia.\"\n\n\"That will await him when he enters port,\" said he, chuckling.\n\"It may give him a sleepless night. He will find it as sure a\nprecursor of his fate as Openshaw did before him.\"\n\n\"And who is this Captain Calhoun?\"\n\n\"The leader of the gang. I shall have the others, but he first.\"\n\n\"How did you trace it, then?\"\n\nHe took a large sheet of paper from his pocket, all covered with\ndates and names.\n\n\"I have spent the whole day,\" said he, \"over Lloyd's registers\nand files of the old papers, following the future career of every\nvessel which touched at Pondicherry in January and February in\n'83. There were thirty-six ships of fair tonnage which were\nreported there during those months. Of these, one, the 'Lone Star,'\ninstantly attracted my attention, since, although it was reported\nas having cleared from London, the name is that which is given to\none of the states of the Union.\"\n\n\"Texas, I think.\"\n\n\"I was not and am not sure which; but I knew that the ship must\nhave an American origin.\"\n\n\"What then?\"\n\n\"I searched the Dundee records, and when I found that the barque\n'Lone Star' was there in January, '85, my suspicion became a\ncertainty. I then inquired as to the vessels which lay at present\nin the port of London.\"\n\n\"Yes?\"\n\n\"The 'Lone Star' had arrived here last week. I went down to the\nAlbert Dock and found that she had been taken down the river by\nthe early tide this morning, homeward bound to Savannah. I wired\nto Gravesend and learned that she had passed some time ago, and\nas the wind is easterly I have no doubt that she is now past the\nGoodwins and not very far from the Isle of Wight.\"\n\n\"What will you do, then?\"\n\n\"Oh, I have my hand upon him. He and the two mates, are as I\nlearn, the only native-born Americans in the ship. The others are\nFinns and Germans. I know, also, that they were all three away\nfrom the ship last night. I had it from the stevedore who has\nbeen loading their cargo. By the time that their sailing-ship\nreaches Savannah the mail-boat will have carried this letter, and\nthe cable will have informed the police of Savannah that these\nthree gentlemen are badly wanted here upon a charge of murder.\"\n\nThere is ever a flaw, however, in the best laid of human plans,\nand the murderers of John Openshaw were never to receive the\norange pips which would show them that another, as cunning and as\nresolute as themselves, was upon their track. Very long and very\nsevere were the equinoctial gales that year. We waited long for\nnews of the \"Lone Star\" of Savannah, but none ever reached us. We\ndid at last hear that somewhere far out in the Atlantic a\nshattered stern-post of a boat was seen swinging in the trough\nof a wave, with the letters \"L. S.\" carved upon it, and that is\nall which we shall ever know of the fate of the \"Lone Star.\"\n\n\n\nADVENTURE VI. THE MAN WITH THE TWISTED LIP\n\nIsa Whitney, brother of the late Elias Whitney, D.D., Principal\nof the Theological College of St. George's, was much addicted to\nopium. The habit grew upon him, as I understand, from some\nfoolish freak when he was at college; for having read De\nQuincey's description of his dreams and sensations, he had\ndrenched his tobacco with laudanum in an attempt to produce the\nsame effects. He found, as so many more have done, that the\npractice is easier to attain than to get rid of, and for many\nyears he continued to be a slave to the drug, an object of\nmingled horror and pity to his friends and relatives. I can see\nhim now, with yellow, pasty face, drooping lids, and pin-point\npupils, all huddled in a chair, the wreck and ruin of a noble\nman.\n\nOne night--it was in June, '89--there came a ring to my bell,\nabout the hour when a man gives his first yawn and glances at the\nclock. I sat up in my chair, and my wife laid her needle-work\ndown in her lap and made a little face of disappointment.\n\n\"A patient!\" said she. \"You'll have to go out.\"\n\nI groaned, for I was newly come back from a weary day.\n\nWe heard the door open, a few hurried words, and then quick steps\nupon the linoleum. Our own door flew open, and a lady, clad in\nsome dark-coloured stuff, with a black veil, entered the room.\n\n\"You will excuse my calling so late,\" she began, and then,\nsuddenly losing her self-control, she ran forward, threw her arms\nabout my wife's neck, and sobbed upon her shoulder. \"Oh, I'm in\nsuch trouble!\" she cried; \"I do so want a little help.\"\n\n\"Why,\" said my wife, pulling up her veil, \"it is Kate Whitney.\nHow you startled me, Kate! I had not an idea who you were when\nyou came in.\"\n\n\"I didn't know what to do, so I came straight to you.\" That was\nalways the way. Folk who were in grief came to my wife like birds\nto a light-house.\n\n\"It was very sweet of you to come. Now, you must have some wine\nand water, and sit here comfortably and tell us all about it. Or\nshould you rather that I sent James off to bed?\"\n\n\"Oh, no, no! I want the doctor's advice and help, too. It's about\nIsa. He has not been home for two days. I am so frightened about\nhim!\"\n\nIt was not the first time that she had spoken to us of her\nhusband's trouble, to me as a doctor, to my wife as an old friend\nand school companion. We soothed and comforted her by such words\nas we could find. Did she know where her husband was? Was it\npossible that we could bring him back to her?\n\nIt seems that it was. She had the surest information that of late\nhe had, when the fit was on him, made use of an opium den in the\nfarthest east of the City. Hitherto his orgies had always been\nconfined to one day, and he had come back, twitching and\nshattered, in the evening. But now the spell had been upon him\neight-and-forty hours, and he lay there, doubtless among the\ndregs of the docks, breathing in the poison or sleeping off the\neffects. There he was to be found, she was sure of it, at the Bar\nof Gold, in Upper Swandam Lane. But what was she to do? How could\nshe, a young and timid woman, make her way into such a place and\npluck her husband out from among the ruffians who surrounded him?\n\nThere was the case, and of course there was but one way out of\nit. Might I not escort her to this place? And then, as a second\nthought, why should she come at all? I was Isa Whitney's medical\nadviser, and as such I had influence over him. I could manage it\nbetter if I were alone. I promised her on my word that I would\nsend him home in a cab within two hours if he were indeed at the\naddress which she had given me. And so in ten minutes I had left\nmy armchair and cheery sitting-room behind me, and was speeding\neastward in a hansom on a strange errand, as it seemed to me at\nthe time, though the future only could show how strange it was to\nbe.\n\nBut there was no great difficulty in the first stage of my\nadventure. Upper Swandam Lane is a vile alley lurking behind the\nhigh wharves which line the north side of the river to the east\nof London Bridge. Between a slop-shop and a gin-shop, approached\nby a steep flight of steps leading down to a black gap like the\nmouth of a cave, I found the den of which I was in search.\nOrdering my cab to wait, I passed down the steps, worn hollow in\nthe centre by the ceaseless tread of drunken feet; and by the\nlight of a flickering oil-lamp above the door I found the latch\nand made my way into a long, low room, thick and heavy with the\nbrown opium smoke, and terraced with wooden berths, like the\nforecastle of an emigrant ship.\n\nThrough the gloom one could dimly catch a glimpse of bodies lying\nin strange fantastic poses, bowed shoulders, bent knees, heads\nthrown back, and chins pointing upward, with here and there a\ndark, lack-lustre eye turned upon the newcomer. Out of the black\nshadows there glimmered little red circles of light, now bright,\nnow faint, as the burning poison waxed or waned in the bowls of\nthe metal pipes. The most lay silent, but some muttered to\nthemselves, and others talked together in a strange, low,\nmonotonous voice, their conversation coming in gushes, and then\nsuddenly tailing off into silence, each mumbling out his own\nthoughts and paying little heed to the words of his neighbour. At\nthe farther end was a small brazier of burning charcoal, beside\nwhich on a three-legged wooden stool there sat a tall, thin old\nman, with his jaw resting upon his two fists, and his elbows upon\nhis knees, staring into the fire.\n\nAs I entered, a sallow Malay attendant had hurried up with a pipe\nfor me and a supply of the drug, beckoning me to an empty berth.\n\n\"Thank you. I have not come to stay,\" said I. \"There is a friend\nof mine here, Mr. Isa Whitney, and I wish to speak with him.\"\n\nThere was a movement and an exclamation from my right, and\npeering through the gloom, I saw Whitney, pale, haggard, and\nunkempt, staring out at me.\n\n\"My God! It's Watson,\" said he. He was in a pitiable state of\nreaction, with every nerve in a twitter. \"I say, Watson, what\no'clock is it?\"\n\n\"Nearly eleven.\"\n\n\"Of what day?\"\n\n\"Of Friday, June 19th.\"\n\n\"Good heavens! I thought it was Wednesday. It is Wednesday. What\nd'you want to frighten a chap for?\" He sank his face onto his\narms and began to sob in a high treble key.\n\n\"I tell you that it is Friday, man. Your wife has been waiting\nthis two days for you. You should be ashamed of yourself!\"\n\n\"So I am. But you've got mixed, Watson, for I have only been here\na few hours, three pipes, four pipes--I forget how many. But I'll\ngo home with you. I wouldn't frighten Kate--poor little Kate.\nGive me your hand! Have you a cab?\"\n\n\"Yes, I have one waiting.\"\n\n\"Then I shall go in it. But I must owe something. Find what I\nowe, Watson. I am all off colour. I can do nothing for myself.\"\n\nI walked down the narrow passage between the double row of\nsleepers, holding my breath to keep out the vile, stupefying\nfumes of the drug, and looking about for the manager. As I passed\nthe tall man who sat by the brazier I felt a sudden pluck at my\nskirt, and a low voice whispered, \"Walk past me, and then look\nback at me.\" The words fell quite distinctly upon my ear. I\nglanced down. They could only have come from the old man at my\nside, and yet he sat now as absorbed as ever, very thin, very\nwrinkled, bent with age, an opium pipe dangling down from between\nhis knees, as though it had dropped in sheer lassitude from his\nfingers. I took two steps forward and looked back. It took all my\nself-control to prevent me from breaking out into a cry of\nastonishment. He had turned his back so that none could see him\nbut I. His form had filled out, his wrinkles were gone, the dull\neyes had regained their fire, and there, sitting by the fire and\ngrinning at my surprise, was none other than Sherlock Holmes. He\nmade a slight motion to me to approach him, and instantly, as he\nturned his face half round to the company once more, subsided\ninto a doddering, loose-lipped senility.\n\n\"Holmes!\" I whispered, \"what on earth are you doing in this den?\"\n\n\"As low as you can,\" he answered; \"I have excellent ears. If you\nwould have the great kindness to get rid of that sottish friend\nof yours I should be exceedingly glad to have a little talk with\nyou.\"\n\n\"I have a cab outside.\"\n\n\"Then pray send him home in it. You may safely trust him, for he\nappears to be too limp to get into any mischief. I should\nrecommend you also to send a note by the cabman to your wife to\nsay that you have thrown in your lot with me. If you will wait\noutside, I shall be with you in five minutes.\"\n\nIt was difficult to refuse any of Sherlock Holmes' requests, for\nthey were always so exceedingly definite, and put forward with\nsuch a quiet air of mastery. I felt, however, that when Whitney\nwas once confined in the cab my mission was practically\naccomplished; and for the rest, I could not wish anything better\nthan to be associated with my friend in one of those singular\nadventures which were the normal condition of his existence. In a\nfew minutes I had written my note, paid Whitney's bill, led him\nout to the cab, and seen him driven through the darkness. In a\nvery short time a decrepit figure had emerged from the opium den,\nand I was walking down the street with Sherlock Holmes. For two\nstreets he shuffled along with a bent back and an uncertain foot.\nThen, glancing quickly round, he straightened himself out and\nburst into a hearty fit of laughter.\n\n\"I suppose, Watson,\" said he, \"that you imagine that I have added\nopium-smoking to cocaine injections, and all the other little\nweaknesses on which you have favoured me with your medical\nviews.\"\n\n\"I was certainly surprised to find you there.\"\n\n\"But not more so than I to find you.\"\n\n\"I came to find a friend.\"\n\n\"And I to find an enemy.\"\n\n\"An enemy?\"\n\n\"Yes; one of my natural enemies, or, shall I say, my natural\nprey. Briefly, Watson, I am in the midst of a very remarkable\ninquiry, and I have hoped to find a clue in the incoherent\nramblings of these sots, as I have done before now. Had I been\nrecognised in that den my life would not have been worth an\nhour's purchase; for I have used it before now for my own\npurposes, and the rascally Lascar who runs it has sworn to have\nvengeance upon me. There is a trap-door at the back of that\nbuilding, near the corner of Paul's Wharf, which could tell some\nstrange tales of what has passed through it upon the moonless\nnights.\"\n\n\"What! You do not mean bodies?\"\n\n\"Ay, bodies, Watson. We should be rich men if we had 1000 pounds\nfor every poor devil who has been done to death in that den. It\nis the vilest murder-trap on the whole riverside, and I fear that\nNeville St. Clair has entered it never to leave it more. But our\ntrap should be here.\" He put his two forefingers between his\nteeth and whistled shrilly--a signal which was answered by a\nsimilar whistle from the distance, followed shortly by the rattle\nof wheels and the clink of horses' hoofs.\n\n\"Now, Watson,\" said Holmes, as a tall dog-cart dashed up through\nthe gloom, throwing out two golden tunnels of yellow light from\nits side lanterns. \"You'll come with me, won't you?\"\n\n\"If I can be of use.\"\n\n\"Oh, a trusty comrade is always of use; and a chronicler still\nmore so. My room at The Cedars is a double-bedded one.\"\n\n\"The Cedars?\"\n\n\"Yes; that is Mr. St. Clair's house. I am staying there while I\nconduct the inquiry.\"\n\n\"Where is it, then?\"\n\n\"Near Lee, in Kent. We have a seven-mile drive before us.\"\n\n\"But I am all in the dark.\"\n\n\"Of course you are. You'll know all about it presently. Jump up\nhere. All right, John; we shall not need you. Here's half a\ncrown. Look out for me to-morrow, about eleven. Give her her\nhead. So long, then!\"\n\nHe flicked the horse with his whip, and we dashed away through\nthe endless succession of sombre and deserted streets, which\nwidened gradually, until we were flying across a broad\nbalustraded bridge, with the murky river flowing sluggishly\nbeneath us. Beyond lay another dull wilderness of bricks and\nmortar, its silence broken only by the heavy, regular footfall of\nthe policeman, or the songs and shouts of some belated party of\nrevellers. A dull wrack was drifting slowly across the sky, and a\nstar or two twinkled dimly here and there through the rifts of\nthe clouds. Holmes drove in silence, with his head sunk upon his\nbreast, and the air of a man who is lost in thought, while I sat\nbeside him, curious to learn what this new quest might be which\nseemed to tax his powers so sorely, and yet afraid to break in\nupon the current of his thoughts. We had driven several miles,\nand were beginning to get to the fringe of the belt of suburban\nvillas, when he shook himself, shrugged his shoulders, and lit up\nhis pipe with the air of a man who has satisfied himself that he\nis acting for the best.\n\n\"You have a grand gift of silence, Watson,\" said he. \"It makes\nyou quite invaluable as a companion. 'Pon my word, it is a great\nthing for me to have someone to talk to, for my own thoughts are\nnot over-pleasant. I was wondering what I should say to this dear\nlittle woman to-night when she meets me at the door.\"\n\n\"You forget that I know nothing about it.\"\n\n\"I shall just have time to tell you the facts of the case before\nwe get to Lee. It seems absurdly simple, and yet, somehow I can\nget nothing to go upon. There's plenty of thread, no doubt, but I\ncan't get the end of it into my hand. Now, I'll state the case\nclearly and concisely to you, Watson, and maybe you can see a\nspark where all is dark to me.\"\n\n\"Proceed, then.\"\n\n\"Some years ago--to be definite, in May, 1884--there came to Lee\na gentleman, Neville St. Clair by name, who appeared to have\nplenty of money. He took a large villa, laid out the grounds very\nnicely, and lived generally in good style. By degrees he made\nfriends in the neighbourhood, and in 1887 he married the daughter\nof a local brewer, by whom he now has two children. He had no\noccupation, but was interested in several companies and went into\ntown as a rule in the morning, returning by the 5:14 from Cannon\nStreet every night. Mr. St. Clair is now thirty-seven years of\nage, is a man of temperate habits, a good husband, a very\naffectionate father, and a man who is popular with all who know\nhim. I may add that his whole debts at the present moment, as far\nas we have been able to ascertain, amount to 88 pounds 10s., while\nhe has 220 pounds standing to his credit in the Capital and\nCounties Bank. There is no reason, therefore, to think that money\ntroubles have been weighing upon his mind.\n\n\"Last Monday Mr. Neville St. Clair went into town rather earlier\nthan usual, remarking before he started that he had two important\ncommissions to perform, and that he would bring his little boy\nhome a box of bricks. Now, by the merest chance, his wife\nreceived a telegram upon this same Monday, very shortly after his\ndeparture, to the effect that a small parcel of considerable\nvalue which she had been expecting was waiting for her at the\noffices of the Aberdeen Shipping Company. Now, if you are well up\nin your London, you will know that the office of the company is\nin Fresno Street, which branches out of Upper Swandam Lane, where\nyou found me to-night. Mrs. St. Clair had her lunch, started for\nthe City, did some shopping, proceeded to the company's office,\ngot her packet, and found herself at exactly 4:35 walking through\nSwandam Lane on her way back to the station. Have you followed me\nso far?\"\n\n\"It is very clear.\"\n\n\"If you remember, Monday was an exceedingly hot day, and Mrs. St.\nClair walked slowly, glancing about in the hope of seeing a cab,\nas she did not like the neighbourhood in which she found herself.\nWhile she was walking in this way down Swandam Lane, she suddenly\nheard an ejaculation or cry, and was struck cold to see her\nhusband looking down at her and, as it seemed to her, beckoning\nto her from a second-floor window. The window was open, and she\ndistinctly saw his face, which she describes as being terribly\nagitated. He waved his hands frantically to her, and then\nvanished from the window so suddenly that it seemed to her that\nhe had been plucked back by some irresistible force from behind.\nOne singular point which struck her quick feminine eye was that\nalthough he wore some dark coat, such as he had started to town\nin, he had on neither collar nor necktie.\n\n\"Convinced that something was amiss with him, she rushed down the\nsteps--for the house was none other than the opium den in which\nyou found me to-night--and running through the front room she\nattempted to ascend the stairs which led to the first floor. At\nthe foot of the stairs, however, she met this Lascar scoundrel of\nwhom I have spoken, who thrust her back and, aided by a Dane, who\nacts as assistant there, pushed her out into the street. Filled\nwith the most maddening doubts and fears, she rushed down the\nlane and, by rare good-fortune, met in Fresno Street a number of\nconstables with an inspector, all on their way to their beat. The\ninspector and two men accompanied her back, and in spite of the\ncontinued resistance of the proprietor, they made their way to\nthe room in which Mr. St. Clair had last been seen. There was no\nsign of him there. In fact, in the whole of that floor there was\nno one to be found save a crippled wretch of hideous aspect, who,\nit seems, made his home there. Both he and the Lascar stoutly\nswore that no one else had been in the front room during the\nafternoon. So determined was their denial that the inspector was\nstaggered, and had almost come to believe that Mrs. St. Clair had\nbeen deluded when, with a cry, she sprang at a small deal box\nwhich lay upon the table and tore the lid from it. Out there fell\na cascade of children's bricks. It was the toy which he had\npromised to bring home.\n\n\"This discovery, and the evident confusion which the cripple\nshowed, made the inspector realise that the matter was serious.\nThe rooms were carefully examined, and results all pointed to an\nabominable crime. The front room was plainly furnished as a\nsitting-room and led into a small bedroom, which looked out upon\nthe back of one of the wharves. Between the wharf and the bedroom\nwindow is a narrow strip, which is dry at low tide but is covered\nat high tide with at least four and a half feet of water. The\nbedroom window was a broad one and opened from below. On\nexamination traces of blood were to be seen upon the windowsill,\nand several scattered drops were visible upon the wooden floor of\nthe bedroom. Thrust away behind a curtain in the front room were\nall the clothes of Mr. Neville St. Clair, with the exception of\nhis coat. His boots, his socks, his hat, and his watch--all were\nthere. There were no signs of violence upon any of these\ngarments, and there were no other traces of Mr. Neville St.\nClair. Out of the window he must apparently have gone for no\nother exit could be discovered, and the ominous bloodstains upon\nthe sill gave little promise that he could save himself by\nswimming, for the tide was at its very highest at the moment of\nthe tragedy.\n\n\"And now as to the villains who seemed to be immediately\nimplicated in the matter. The Lascar was known to be a man of the\nvilest antecedents, but as, by Mrs. St. Clair's story, he was\nknown to have been at the foot of the stair within a very few\nseconds of her husband's appearance at the window, he could\nhardly have been more than an accessory to the crime. His defence\nwas one of absolute ignorance, and he protested that he had no\nknowledge as to the doings of Hugh Boone, his lodger, and that he\ncould not account in any way for the presence of the missing\ngentleman's clothes.\n\n\"So much for the Lascar manager. Now for the sinister cripple who\nlives upon the second floor of the opium den, and who was\ncertainly the last human being whose eyes rested upon Neville St.\nClair. His name is Hugh Boone, and his hideous face is one which\nis familiar to every man who goes much to the City. He is a\nprofessional beggar, though in order to avoid the police\nregulations he pretends to a small trade in wax vestas. Some\nlittle distance down Threadneedle Street, upon the left-hand\nside, there is, as you may have remarked, a small angle in the\nwall. Here it is that this creature takes his daily seat,\ncross-legged with his tiny stock of matches on his lap, and as he\nis a piteous spectacle a small rain of charity descends into the\ngreasy leather cap which lies upon the pavement beside him. I\nhave watched the fellow more than once before ever I thought of\nmaking his professional acquaintance, and I have been surprised\nat the harvest which he has reaped in a short time. His\nappearance, you see, is so remarkable that no one can pass him\nwithout observing him. A shock of orange hair, a pale face\ndisfigured by a horrible scar, which, by its contraction, has\nturned up the outer edge of his upper lip, a bulldog chin, and a\npair of very penetrating dark eyes, which present a singular\ncontrast to the colour of his hair, all mark him out from amid\nthe common crowd of mendicants and so, too, does his wit, for he\nis ever ready with a reply to any piece of chaff which may be\nthrown at him by the passers-by. This is the man whom we now\nlearn to have been the lodger at the opium den, and to have been\nthe last man to see the gentleman of whom we are in quest.\"\n\n\"But a cripple!\" said I. \"What could he have done single-handed\nagainst a man in the prime of life?\"\n\n\"He is a cripple in the sense that he walks with a limp; but in\nother respects he appears to be a powerful and well-nurtured man.\nSurely your medical experience would tell you, Watson, that\nweakness in one limb is often compensated for by exceptional\nstrength in the others.\"\n\n\"Pray continue your narrative.\"\n\n\"Mrs. St. Clair had fainted at the sight of the blood upon the\nwindow, and she was escorted home in a cab by the police, as her\npresence could be of no help to them in their investigations.\nInspector Barton, who had charge of the case, made a very careful\nexamination of the premises, but without finding anything which\nthrew any light upon the matter. One mistake had been made in not\narresting Boone instantly, as he was allowed some few minutes\nduring which he might have communicated with his friend the\nLascar, but this fault was soon remedied, and he was seized and\nsearched, without anything being found which could incriminate\nhim. There were, it is true, some blood-stains upon his right\nshirt-sleeve, but he pointed to his ring-finger, which had been\ncut near the nail, and explained that the bleeding came from\nthere, adding that he had been to the window not long before, and\nthat the stains which had been observed there came doubtless from\nthe same source. He denied strenuously having ever seen Mr.\nNeville St. Clair and swore that the presence of the clothes in\nhis room was as much a mystery to him as to the police. As to\nMrs. St. Clair's assertion that she had actually seen her husband\nat the window, he declared that she must have been either mad or\ndreaming. He was removed, loudly protesting, to the\npolice-station, while the inspector remained upon the premises in\nthe hope that the ebbing tide might afford some fresh clue.\n\n\"And it did, though they hardly found upon the mud-bank what they\nhad feared to find. It was Neville St. Clair's coat, and not\nNeville St. Clair, which lay uncovered as the tide receded. And\nwhat do you think they found in the pockets?\"\n\n\"I cannot imagine.\"\n\n\"No, I don't think you would guess. Every pocket stuffed with\npennies and half-pennies--421 pennies and 270 half-pennies. It\nwas no wonder that it had not been swept away by the tide. But a\nhuman body is a different matter. There is a fierce eddy between\nthe wharf and the house. It seemed likely enough that the\nweighted coat had remained when the stripped body had been sucked\naway into the river.\"\n\n\"But I understand that all the other clothes were found in the\nroom. Would the body be dressed in a coat alone?\"\n\n\"No, sir, but the facts might be met speciously enough. Suppose\nthat this man Boone had thrust Neville St. Clair through the\nwindow, there is no human eye which could have seen the deed.\nWhat would he do then? It would of course instantly strike him\nthat he must get rid of the tell-tale garments. He would seize\nthe coat, then, and be in the act of throwing it out, when it\nwould occur to him that it would swim and not sink. He has little\ntime, for he has heard the scuffle downstairs when the wife tried\nto force her way up, and perhaps he has already heard from his\nLascar confederate that the police are hurrying up the street.\nThere is not an instant to be lost. He rushes to some secret\nhoard, where he has accumulated the fruits of his beggary, and he\nstuffs all the coins upon which he can lay his hands into the\npockets to make sure of the coat's sinking. He throws it out, and\nwould have done the same with the other garments had not he heard\nthe rush of steps below, and only just had time to close the\nwindow when the police appeared.\"\n\n\"It certainly sounds feasible.\"\n\n\"Well, we will take it as a working hypothesis for want of a\nbetter. Boone, as I have told you, was arrested and taken to the\nstation, but it could not be shown that there had ever before\nbeen anything against him. He had for years been known as a\nprofessional beggar, but his life appeared to have been a very\nquiet and innocent one. There the matter stands at present, and\nthe questions which have to be solved--what Neville St. Clair was\ndoing in the opium den, what happened to him when there, where is\nhe now, and what Hugh Boone had to do with his disappearance--are\nall as far from a solution as ever. I confess that I cannot\nrecall any case within my experience which looked at the first\nglance so simple and yet which presented such difficulties.\"\n\nWhile Sherlock Holmes had been detailing this singular series of\nevents, we had been whirling through the outskirts of the great\ntown until the last straggling houses had been left behind, and\nwe rattled along with a country hedge upon either side of us.\nJust as he finished, however, we drove through two scattered\nvillages, where a few lights still glimmered in the windows.\n\n\"We are on the outskirts of Lee,\" said my companion. \"We have\ntouched on three English counties in our short drive, starting in\nMiddlesex, passing over an angle of Surrey, and ending in Kent.\nSee that light among the trees? That is The Cedars, and beside\nthat lamp sits a woman whose anxious ears have already, I have\nlittle doubt, caught the clink of our horse's feet.\"\n\n\"But why are you not conducting the case from Baker Street?\" I\nasked.\n\n\"Because there are many inquiries which must be made out here.\nMrs. St. Clair has most kindly put two rooms at my disposal, and\nyou may rest assured that she will have nothing but a welcome for\nmy friend and colleague. I hate to meet her, Watson, when I have\nno news of her husband. Here we are. Whoa, there, whoa!\"\n\nWe had pulled up in front of a large villa which stood within its\nown grounds. A stable-boy had run out to the horse's head, and\nspringing down, I followed Holmes up the small, winding\ngravel-drive which led to the house. As we approached, the door\nflew open, and a little blonde woman stood in the opening, clad\nin some sort of light mousseline de soie, with a touch of fluffy\npink chiffon at her neck and wrists. She stood with her figure\noutlined against the flood of light, one hand upon the door, one\nhalf-raised in her eagerness, her body slightly bent, her head\nand face protruded, with eager eyes and parted lips, a standing\nquestion.\n\n\"Well?\" she cried, \"well?\" And then, seeing that there were two\nof us, she gave a cry of hope which sank into a groan as she saw\nthat my companion shook his head and shrugged his shoulders.\n\n\"No good news?\"\n\n\"None.\"\n\n\"No bad?\"\n\n\"No.\"\n\n\"Thank God for that. But come in. You must be weary, for you have\nhad a long day.\"\n\n\"This is my friend, Dr. Watson. He has been of most vital use to\nme in several of my cases, and a lucky chance has made it\npossible for me to bring him out and associate him with this\ninvestigation.\"\n\n\"I am delighted to see you,\" said she, pressing my hand warmly.\n\"You will, I am sure, forgive anything that may be wanting in our\narrangements, when you consider the blow which has come so\nsuddenly upon us.\"\n\n\"My dear madam,\" said I, \"I am an old campaigner, and if I were\nnot I can very well see that no apology is needed. If I can be of\nany assistance, either to you or to my friend here, I shall be\nindeed happy.\"\n\n\"Now, Mr. Sherlock Holmes,\" said the lady as we entered a\nwell-lit dining-room, upon the table of which a cold supper had\nbeen laid out, \"I should very much like to ask you one or two\nplain questions, to which I beg that you will give a plain\nanswer.\"\n\n\"Certainly, madam.\"\n\n\"Do not trouble about my feelings. I am not hysterical, nor given\nto fainting. I simply wish to hear your real, real opinion.\"\n\n\"Upon what point?\"\n\n\"In your heart of hearts, do you think that Neville is alive?\"\n\nSherlock Holmes seemed to be embarrassed by the question.\n\"Frankly, now!\" she repeated, standing upon the rug and looking\nkeenly down at him as he leaned back in a basket-chair.\n\n\"Frankly, then, madam, I do not.\"\n\n\"You think that he is dead?\"\n\n\"I do.\"\n\n\"Murdered?\"\n\n\"I don't say that. Perhaps.\"\n\n\"And on what day did he meet his death?\"\n\n\"On Monday.\"\n\n\"Then perhaps, Mr. Holmes, you will be good enough to explain how\nit is that I have received a letter from him to-day.\"\n\nSherlock Holmes sprang out of his chair as if he had been\ngalvanised.\n\n\"What!\" he roared.\n\n\"Yes, to-day.\" She stood smiling, holding up a little slip of\npaper in the air.\n\n\"May I see it?\"\n\n\"Certainly.\"\n\nHe snatched it from her in his eagerness, and smoothing it out\nupon the table he drew over the lamp and examined it intently. I\nhad left my chair and was gazing at it over his shoulder. The\nenvelope was a very coarse one and was stamped with the Gravesend\npostmark and with the date of that very day, or rather of the day\nbefore, for it was considerably after midnight.\n\n\"Coarse writing,\" murmured Holmes. \"Surely this is not your\nhusband's writing, madam.\"\n\n\"No, but the enclosure is.\"\n\n\"I perceive also that whoever addressed the envelope had to go\nand inquire as to the address.\"\n\n\"How can you tell that?\"\n\n\"The name, you see, is in perfectly black ink, which has dried\nitself. The rest is of the greyish colour, which shows that\nblotting-paper has been used. If it had been written straight\noff, and then blotted, none would be of a deep black shade. This\nman has written the name, and there has then been a pause before\nhe wrote the address, which can only mean that he was not\nfamiliar with it. It is, of course, a trifle, but there is\nnothing so important as trifles. Let us now see the letter. Ha!\nthere has been an enclosure here!\"\n\n\"Yes, there was a ring. His signet-ring.\"\n\n\"And you are sure that this is your husband's hand?\"\n\n\"One of his hands.\"\n\n\"One?\"\n\n\"His hand when he wrote hurriedly. It is very unlike his usual\nwriting, and yet I know it well.\"\n\n\"'Dearest do not be frightened. All will come well. There is a\nhuge error which it may take some little time to rectify.\nWait in patience.--NEVILLE.' Written in pencil upon the fly-leaf\nof a book, octavo size, no water-mark. Hum! Posted to-day in\nGravesend by a man with a dirty thumb. Ha! And the flap has been\ngummed, if I am not very much in error, by a person who had been\nchewing tobacco. And you have no doubt that it is your husband's\nhand, madam?\"\n\n\"None. Neville wrote those words.\"\n\n\"And they were posted to-day at Gravesend. Well, Mrs. St. Clair,\nthe clouds lighten, though I should not venture to say that the\ndanger is over.\"\n\n\"But he must be alive, Mr. Holmes.\"\n\n\"Unless this is a clever forgery to put us on the wrong scent.\nThe ring, after all, proves nothing. It may have been taken from\nhim.\"\n\n\"No, no; it is, it is his very own writing!\"\n\n\"Very well. It may, however, have been written on Monday and only\nposted to-day.\"\n\n\"That is possible.\"\n\n\"If so, much may have happened between.\"\n\n\"Oh, you must not discourage me, Mr. Holmes. I know that all is\nwell with him. There is so keen a sympathy between us that I\nshould know if evil came upon him. On the very day that I saw him\nlast he cut himself in the bedroom, and yet I in the dining-room\nrushed upstairs instantly with the utmost certainty that\nsomething had happened. Do you think that I would respond to such\na trifle and yet be ignorant of his death?\"\n\n\"I have seen too much not to know that the impression of a woman\nmay be more valuable than the conclusion of an analytical\nreasoner. And in this letter you certainly have a very strong\npiece of evidence to corroborate your view. But if your husband\nis alive and able to write letters, why should he remain away\nfrom you?\"\n\n\"I cannot imagine. It is unthinkable.\"\n\n\"And on Monday he made no remarks before leaving you?\"\n\n\"No.\"\n\n\"And you were surprised to see him in Swandam Lane?\"\n\n\"Very much so.\"\n\n\"Was the window open?\"\n\n\"Yes.\"\n\n\"Then he might have called to you?\"\n\n\"He might.\"\n\n\"He only, as I understand, gave an inarticulate cry?\"\n\n\"Yes.\"\n\n\"A call for help, you thought?\"\n\n\"Yes. He waved his hands.\"\n\n\"But it might have been a cry of surprise. Astonishment at the\nunexpected sight of you might cause him to throw up his hands?\"\n\n\"It is possible.\"\n\n\"And you thought he was pulled back?\"\n\n\"He disappeared so suddenly.\"\n\n\"He might have leaped back. You did not see anyone else in the\nroom?\"\n\n\"No, but this horrible man confessed to having been there, and\nthe Lascar was at the foot of the stairs.\"\n\n\"Quite so. Your husband, as far as you could see, had his\nordinary clothes on?\"\n\n\"But without his collar or tie. I distinctly saw his bare\nthroat.\"\n\n\"Had he ever spoken of Swandam Lane?\"\n\n\"Never.\"\n\n\"Had he ever showed any signs of having taken opium?\"\n\n\"Never.\"\n\n\"Thank you, Mrs. St. Clair. Those are the principal points about\nwhich I wished to be absolutely clear. We shall now have a little\nsupper and then retire, for we may have a very busy day\nto-morrow.\"\n\nA large and comfortable double-bedded room had been placed at our\ndisposal, and I was quickly between the sheets, for I was weary\nafter my night of adventure. Sherlock Holmes was a man, however,\nwho, when he had an unsolved problem upon his mind, would go for\ndays, and even for a week, without rest, turning it over,\nrearranging his facts, looking at it from every point of view\nuntil he had either fathomed it or convinced himself that his\ndata were insufficient. It was soon evident to me that he was now\npreparing for an all-night sitting. He took off his coat and\nwaistcoat, put on a large blue dressing-gown, and then wandered\nabout the room collecting pillows from his bed and cushions from\nthe sofa and armchairs. With these he constructed a sort of\nEastern divan, upon which he perched himself cross-legged, with\nan ounce of shag tobacco and a box of matches laid out in front\nof him. In the dim light of the lamp I saw him sitting there, an\nold briar pipe between his lips, his eyes fixed vacantly upon the\ncorner of the ceiling, the blue smoke curling up from him,\nsilent, motionless, with the light shining upon his strong-set\naquiline features. So he sat as I dropped off to sleep, and so he\nsat when a sudden ejaculation caused me to wake up, and I found\nthe summer sun shining into the apartment. The pipe was still\nbetween his lips, the smoke still curled upward, and the room was\nfull of a dense tobacco haze, but nothing remained of the heap of\nshag which I had seen upon the previous night.\n\n\"Awake, Watson?\" he asked.\n\n\"Yes.\"\n\n\"Game for a morning drive?\"\n\n\"Certainly.\"\n\n\"Then dress. No one is stirring yet, but I know where the\nstable-boy sleeps, and we shall soon have the trap out.\" He\nchuckled to himself as he spoke, his eyes twinkled, and he seemed\na different man to the sombre thinker of the previous night.\n\nAs I dressed I glanced at my watch. It was no wonder that no one\nwas stirring. It was twenty-five minutes past four. I had hardly\nfinished when Holmes returned with the news that the boy was\nputting in the horse.\n\n\"I want to test a little theory of mine,\" said he, pulling on his\nboots. \"I think, Watson, that you are now standing in the\npresence of one of the most absolute fools in Europe. I deserve\nto be kicked from here to Charing Cross. But I think I have the\nkey of the affair now.\"\n\n\"And where is it?\" I asked, smiling.\n\n\"In the bathroom,\" he answered. \"Oh, yes, I am not joking,\" he\ncontinued, seeing my look of incredulity. \"I have just been\nthere, and I have taken it out, and I have got it in this\nGladstone bag. Come on, my boy, and we shall see whether it will\nnot fit the lock.\"\n\nWe made our way downstairs as quietly as possible, and out into\nthe bright morning sunshine. In the road stood our horse and\ntrap, with the half-clad stable-boy waiting at the head. We both\nsprang in, and away we dashed down the London Road. A few country\ncarts were stirring, bearing in vegetables to the metropolis, but\nthe lines of villas on either side were as silent and lifeless as\nsome city in a dream.\n\n\"It has been in some points a singular case,\" said Holmes,\nflicking the horse on into a gallop. \"I confess that I have been\nas blind as a mole, but it is better to learn wisdom late than\nnever to learn it at all.\"\n\nIn town the earliest risers were just beginning to look sleepily\nfrom their windows as we drove through the streets of the Surrey\nside. Passing down the Waterloo Bridge Road we crossed over the\nriver, and dashing up Wellington Street wheeled sharply to the\nright and found ourselves in Bow Street. Sherlock Holmes was well\nknown to the force, and the two constables at the door saluted\nhim. One of them held the horse's head while the other led us in.\n\n\"Who is on duty?\" asked Holmes.\n\n\"Inspector Bradstreet, sir.\"\n\n\"Ah, Bradstreet, how are you?\" A tall, stout official had come\ndown the stone-flagged passage, in a peaked cap and frogged\njacket. \"I wish to have a quiet word with you, Bradstreet.\"\n\"Certainly, Mr. Holmes. Step into my room here.\" It was a small,\noffice-like room, with a huge ledger upon the table, and a\ntelephone projecting from the wall. The inspector sat down at his\ndesk.\n\n\"What can I do for you, Mr. Holmes?\"\n\n\"I called about that beggarman, Boone--the one who was charged\nwith being concerned in the disappearance of Mr. Neville St.\nClair, of Lee.\"\n\n\"Yes. He was brought up and remanded for further inquiries.\"\n\n\"So I heard. You have him here?\"\n\n\"In the cells.\"\n\n\"Is he quiet?\"\n\n\"Oh, he gives no trouble. But he is a dirty scoundrel.\"\n\n\"Dirty?\"\n\n\"Yes, it is all we can do to make him wash his hands, and his\nface is as black as a tinker's. Well, when once his case has been\nsettled, he will have a regular prison bath; and I think, if you\nsaw him, you would agree with me that he needed it.\"\n\n\"I should like to see him very much.\"\n\n\"Would you? That is easily done. Come this way. You can leave\nyour bag.\"\n\n\"No, I think that I'll take it.\"\n\n\"Very good. Come this way, if you please.\" He led us down a\npassage, opened a barred door, passed down a winding stair, and\nbrought us to a whitewashed corridor with a line of doors on each\nside.\n\n\"The third on the right is his,\" said the inspector. \"Here it\nis!\" He quietly shot back a panel in the upper part of the door\nand glanced through.\n\n\"He is asleep,\" said he. \"You can see him very well.\"\n\nWe both put our eyes to the grating. The prisoner lay with his\nface towards us, in a very deep sleep, breathing slowly and\nheavily. He was a middle-sized man, coarsely clad as became his\ncalling, with a coloured shirt protruding through the rent in his\ntattered coat. He was, as the inspector had said, extremely\ndirty, but the grime which covered his face could not conceal its\nrepulsive ugliness. A broad wheal from an old scar ran right\nacross it from eye to chin, and by its contraction had turned up\none side of the upper lip, so that three teeth were exposed in a\nperpetual snarl. A shock of very bright red hair grew low over\nhis eyes and forehead.\n\n\"He's a beauty, isn't he?\" said the inspector.\n\n\"He certainly needs a wash,\" remarked Holmes. \"I had an idea that\nhe might, and I took the liberty of bringing the tools with me.\"\nHe opened the Gladstone bag as he spoke, and took out, to my\nastonishment, a very large bath-sponge.\n\n\"He! he! You are a funny one,\" chuckled the inspector.\n\n\"Now, if you will have the great goodness to open that door very\nquietly, we will soon make him cut a much more respectable\nfigure.\"\n\n\"Well, I don't know why not,\" said the inspector. \"He doesn't\nlook a credit to the Bow Street cells, does he?\" He slipped his\nkey into the lock, and we all very quietly entered the cell. The\nsleeper half turned, and then settled down once more into a deep\nslumber. Holmes stooped to the water-jug, moistened his sponge,\nand then rubbed it twice vigorously across and down the\nprisoner's face.\n\n\"Let me introduce you,\" he shouted, \"to Mr. Neville St. Clair, of\nLee, in the county of Kent.\"\n\nNever in my life have I seen such a sight. The man's face peeled\noff under the sponge like the bark from a tree. Gone was the\ncoarse brown tint! Gone, too, was the horrid scar which had\nseamed it across, and the twisted lip which had given the\nrepulsive sneer to the face! A twitch brought away the tangled\nred hair, and there, sitting up in his bed, was a pale,\nsad-faced, refined-looking man, black-haired and smooth-skinned,\nrubbing his eyes and staring about him with sleepy bewilderment.\nThen suddenly realising the exposure, he broke into a scream and\nthrew himself down with his face to the pillow.\n\n\"Great heavens!\" cried the inspector, \"it is, indeed, the missing\nman. I know him from the photograph.\"\n\nThe prisoner turned with the reckless air of a man who abandons\nhimself to his destiny. \"Be it so,\" said he. \"And pray what am I\ncharged with?\"\n\n\"With making away with Mr. Neville St.-- Oh, come, you can't be\ncharged with that unless they make a case of attempted suicide of\nit,\" said the inspector with a grin. \"Well, I have been\ntwenty-seven years in the force, but this really takes the cake.\"\n\n\"If I am Mr. Neville St. Clair, then it is obvious that no crime\nhas been committed, and that, therefore, I am illegally\ndetained.\"\n\n\"No crime, but a very great error has been committed,\" said\nHolmes. \"You would have done better to have trusted your wife.\"\n\n\"It was not the wife; it was the children,\" groaned the prisoner.\n\"God help me, I would not have them ashamed of their father. My\nGod! What an exposure! What can I do?\"\n\nSherlock Holmes sat down beside him on the couch and patted him\nkindly on the shoulder.\n\n\"If you leave it to a court of law to clear the matter up,\" said\nhe, \"of course you can hardly avoid publicity. On the other hand,\nif you convince the police authorities that there is no possible\ncase against you, I do not know that there is any reason that the\ndetails should find their way into the papers. Inspector\nBradstreet would, I am sure, make notes upon anything which you\nmight tell us and submit it to the proper authorities. The case\nwould then never go into court at all.\"\n\n\"God bless you!\" cried the prisoner passionately. \"I would have\nendured imprisonment, ay, even execution, rather than have left\nmy miserable secret as a family blot to my children.\n\n\"You are the first who have ever heard my story. My father was a\nschoolmaster in Chesterfield, where I received an excellent\neducation. I travelled in my youth, took to the stage, and\nfinally became a reporter on an evening paper in London. One day\nmy editor wished to have a series of articles upon begging in the\nmetropolis, and I volunteered to supply them. There was the point\nfrom which all my adventures started. It was only by trying\nbegging as an amateur that I could get the facts upon which to\nbase my articles. When an actor I had, of course, learned all the\nsecrets of making up, and had been famous in the green-room for\nmy skill. I took advantage now of my attainments. I painted my\nface, and to make myself as pitiable as possible I made a good\nscar and fixed one side of my lip in a twist by the aid of a\nsmall slip of flesh-coloured plaster. Then with a red head of\nhair, and an appropriate dress, I took my station in the business\npart of the city, ostensibly as a match-seller but really as a\nbeggar. For seven hours I plied my trade, and when I returned\nhome in the evening I found to my surprise that I had received no\nless than 26s. 4d.\n\n\"I wrote my articles and thought little more of the matter until,\nsome time later, I backed a bill for a friend and had a writ\nserved upon me for 25 pounds. I was at my wit's end where to get\nthe money, but a sudden idea came to me. I begged a fortnight's\ngrace from the creditor, asked for a holiday from my employers,\nand spent the time in begging in the City under my disguise. In\nten days I had the money and had paid the debt.\n\n\"Well, you can imagine how hard it was to settle down to arduous\nwork at 2 pounds a week when I knew that I could earn as much in\na day by smearing my face with a little paint, laying my cap on\nthe ground, and sitting still. It was a long fight between my\npride and the money, but the dollars won at last, and I threw up\nreporting and sat day after day in the corner which I had first\nchosen, inspiring pity by my ghastly face and filling my pockets\nwith coppers. Only one man knew my secret. He was the keeper of a\nlow den in which I used to lodge in Swandam Lane, where I could\nevery morning emerge as a squalid beggar and in the evenings\ntransform myself into a well-dressed man about town. This fellow,\na Lascar, was well paid by me for his rooms, so that I knew that\nmy secret was safe in his possession.\n\n\"Well, very soon I found that I was saving considerable sums of\nmoney. I do not mean that any beggar in the streets of London\ncould earn 700 pounds a year--which is less than my average\ntakings--but I had exceptional advantages in my power of making\nup, and also in a facility of repartee, which improved by\npractice and made me quite a recognised character in the City.\nAll day a stream of pennies, varied by silver, poured in upon me,\nand it was a very bad day in which I failed to take 2 pounds.\n\n\"As I grew richer I grew more ambitious, took a house in the\ncountry, and eventually married, without anyone having a\nsuspicion as to my real occupation. My dear wife knew that I had\nbusiness in the City. She little knew what.\n\n\"Last Monday I had finished for the day and was dressing in my\nroom above the opium den when I looked out of my window and saw,\nto my horror and astonishment, that my wife was standing in the\nstreet, with her eyes fixed full upon me. I gave a cry of\nsurprise, threw up my arms to cover my face, and, rushing to my\nconfidant, the Lascar, entreated him to prevent anyone from\ncoming up to me. I heard her voice downstairs, but I knew that\nshe could not ascend. Swiftly I threw off my clothes, pulled on\nthose of a beggar, and put on my pigments and wig. Even a wife's\neyes could not pierce so complete a disguise. But then it\noccurred to me that there might be a search in the room, and that\nthe clothes might betray me. I threw open the window, reopening\nby my violence a small cut which I had inflicted upon myself in\nthe bedroom that morning. Then I seized my coat, which was\nweighted by the coppers which I had just transferred to it from\nthe leather bag in which I carried my takings. I hurled it out of\nthe window, and it disappeared into the Thames. The other clothes\nwould have followed, but at that moment there was a rush of\nconstables up the stair, and a few minutes after I found, rather,\nI confess, to my relief, that instead of being identified as Mr.\nNeville St. Clair, I was arrested as his murderer.\n\n\"I do not know that there is anything else for me to explain. I\nwas determined to preserve my disguise as long as possible, and\nhence my preference for a dirty face. Knowing that my wife would\nbe terribly anxious, I slipped off my ring and confided it to the\nLascar at a moment when no constable was watching me, together\nwith a hurried scrawl, telling her that she had no cause to\nfear.\"\n\n\"That note only reached her yesterday,\" said Holmes.\n\n\"Good God! What a week she must have spent!\"\n\n\"The police have watched this Lascar,\" said Inspector Bradstreet,\n\"and I can quite understand that he might find it difficult to\npost a letter unobserved. Probably he handed it to some sailor\ncustomer of his, who forgot all about it for some days.\"\n\n\"That was it,\" said Holmes, nodding approvingly; \"I have no doubt\nof it. But have you never been prosecuted for begging?\"\n\n\"Many times; but what was a fine to me?\"\n\n\"It must stop here, however,\" said Bradstreet. \"If the police are\nto hush this thing up, there must be no more of Hugh Boone.\"\n\n\"I have sworn it by the most solemn oaths which a man can take.\"\n\n\"In that case I think that it is probable that no further steps\nmay be taken. But if you are found again, then all must come out.\nI am sure, Mr. Holmes, that we are very much indebted to you for\nhaving cleared the matter up. I wish I knew how you reach your\nresults.\"\n\n\"I reached this one,\" said my friend, \"by sitting upon five\npillows and consuming an ounce of shag. I think, Watson, that if\nwe drive to Baker Street we shall just be in time for breakfast.\"\n\n\n\nVII. THE ADVENTURE OF THE BLUE CARBUNCLE\n\nI had called upon my friend Sherlock Holmes upon the second\nmorning after Christmas, with the intention of wishing him the\ncompliments of the season. He was lounging upon the sofa in a\npurple dressing-gown, a pipe-rack within his reach upon the\nright, and a pile of crumpled morning papers, evidently newly\nstudied, near at hand. Beside the couch was a wooden chair, and\non the angle of the back hung a very seedy and disreputable\nhard-felt hat, much the worse for wear, and cracked in several\nplaces. A lens and a forceps lying upon the seat of the chair\nsuggested that the hat had been suspended in this manner for the\npurpose of examination.\n\n\"You are engaged,\" said I; \"perhaps I interrupt you.\"\n\n\"Not at all. I am glad to have a friend with whom I can discuss\nmy results. The matter is a perfectly trivial one\"--he jerked his\nthumb in the direction of the old hat--\"but there are points in\nconnection with it which are not entirely devoid of interest and\neven of instruction.\"\n\nI seated myself in his armchair and warmed my hands before his\ncrackling fire, for a sharp frost had set in, and the windows\nwere thick with the ice crystals. \"I suppose,\" I remarked, \"that,\nhomely as it looks, this thing has some deadly story linked on to\nit--that it is the clue which will guide you in the solution of\nsome mystery and the punishment of some crime.\"\n\n\"No, no. No crime,\" said Sherlock Holmes, laughing. \"Only one of\nthose whimsical little incidents which will happen when you have\nfour million human beings all jostling each other within the\nspace of a few square miles. Amid the action and reaction of so\ndense a swarm of humanity, every possible combination of events\nmay be expected to take place, and many a little problem will be\npresented which may be striking and bizarre without being\ncriminal. We have already had experience of such.\"\n\n\"So much so,\" I remarked, \"that of the last six cases which I\nhave added to my notes, three have been entirely free of any\nlegal crime.\"\n\n\"Precisely. You allude to my attempt to recover the Irene Adler\npapers, to the singular case of Miss Mary Sutherland, and to the\nadventure of the man with the twisted lip. Well, I have no doubt\nthat this small matter will fall into the same innocent category.\nYou know Peterson, the commissionaire?\"\n\n\"Yes.\"\n\n\"It is to him that this trophy belongs.\"\n\n\"It is his hat.\"\n\n\"No, no, he found it. Its owner is unknown. I beg that you will\nlook upon it not as a battered billycock but as an intellectual\nproblem. And, first, as to how it came here. It arrived upon\nChristmas morning, in company with a good fat goose, which is, I\nhave no doubt, roasting at this moment in front of Peterson's\nfire. The facts are these: about four o'clock on Christmas\nmorning, Peterson, who, as you know, is a very honest fellow, was\nreturning from some small jollification and was making his way\nhomeward down Tottenham Court Road. In front of him he saw, in\nthe gaslight, a tallish man, walking with a slight stagger, and\ncarrying a white goose slung over his shoulder. As he reached the\ncorner of Goodge Street, a row broke out between this stranger\nand a little knot of roughs. One of the latter knocked off the\nman's hat, on which he raised his stick to defend himself and,\nswinging it over his head, smashed the shop window behind him.\nPeterson had rushed forward to protect the stranger from his\nassailants; but the man, shocked at having broken the window, and\nseeing an official-looking person in uniform rushing towards him,\ndropped his goose, took to his heels, and vanished amid the\nlabyrinth of small streets which lie at the back of Tottenham\nCourt Road. The roughs had also fled at the appearance of\nPeterson, so that he was left in possession of the field of\nbattle, and also of the spoils of victory in the shape of this\nbattered hat and a most unimpeachable Christmas goose.\"\n\n\"Which surely he restored to their owner?\"\n\n\"My dear fellow, there lies the problem. It is true that 'For\nMrs. Henry Baker' was printed upon a small card which was tied to\nthe bird's left leg, and it is also true that the initials 'H.\nB.' are legible upon the lining of this hat, but as there are\nsome thousands of Bakers, and some hundreds of Henry Bakers in\nthis city of ours, it is not easy to restore lost property to any\none of them.\"\n\n\"What, then, did Peterson do?\"\n\n\"He brought round both hat and goose to me on Christmas morning,\nknowing that even the smallest problems are of interest to me.\nThe goose we retained until this morning, when there were signs\nthat, in spite of the slight frost, it would be well that it\nshould be eaten without unnecessary delay. Its finder has carried\nit off, therefore, to fulfil the ultimate destiny of a goose,\nwhile I continue to retain the hat of the unknown gentleman who\nlost his Christmas dinner.\"\n\n\"Did he not advertise?\"\n\n\"No.\"\n\n\"Then, what clue could you have as to his identity?\"\n\n\"Only as much as we can deduce.\"\n\n\"From his hat?\"\n\n\"Precisely.\"\n\n\"But you are joking. What can you gather from this old battered\nfelt?\"\n\n\"Here is my lens. You know my methods. What can you gather\nyourself as to the individuality of the man who has worn this\narticle?\"\n\nI took the tattered object in my hands and turned it over rather\nruefully. It was a very ordinary black hat of the usual round\nshape, hard and much the worse for wear. The lining had been of\nred silk, but was a good deal discoloured. There was no maker's\nname; but, as Holmes had remarked, the initials \"H. B.\" were\nscrawled upon one side. It was pierced in the brim for a\nhat-securer, but the elastic was missing. For the rest, it was\ncracked, exceedingly dusty, and spotted in several places,\nalthough there seemed to have been some attempt to hide the\ndiscoloured patches by smearing them with ink.\n\n\"I can see nothing,\" said I, handing it back to my friend.\n\n\"On the contrary, Watson, you can see everything. You fail,\nhowever, to reason from what you see. You are too timid in\ndrawing your inferences.\"\n\n\"Then, pray tell me what it is that you can infer from this hat?\"\n\nHe picked it up and gazed at it in the peculiar introspective\nfashion which was characteristic of him. \"It is perhaps less\nsuggestive than it might have been,\" he remarked, \"and yet there\nare a few inferences which are very distinct, and a few others\nwhich represent at least a strong balance of probability. That\nthe man was highly intellectual is of course obvious upon the\nface of it, and also that he was fairly well-to-do within the\nlast three years, although he has now fallen upon evil days. He\nhad foresight, but has less now than formerly, pointing to a\nmoral retrogression, which, when taken with the decline of his\nfortunes, seems to indicate some evil influence, probably drink,\nat work upon him. This may account also for the obvious fact that\nhis wife has ceased to love him.\"\n\n\"My dear Holmes!\"\n\n\"He has, however, retained some degree of self-respect,\" he\ncontinued, disregarding my remonstrance. \"He is a man who leads a\nsedentary life, goes out little, is out of training entirely, is\nmiddle-aged, has grizzled hair which he has had cut within the\nlast few days, and which he anoints with lime-cream. These are\nthe more patent facts which are to be deduced from his hat. Also,\nby the way, that it is extremely improbable that he has gas laid\non in his house.\"\n\n\"You are certainly joking, Holmes.\"\n\n\"Not in the least. Is it possible that even now, when I give you\nthese results, you are unable to see how they are attained?\"\n\n\"I have no doubt that I am very stupid, but I must confess that I\nam unable to follow you. For example, how did you deduce that\nthis man was intellectual?\"\n\nFor answer Holmes clapped the hat upon his head. It came right\nover the forehead and settled upon the bridge of his nose. \"It is\na question of cubic capacity,\" said he; \"a man with so large a\nbrain must have something in it.\"\n\n\"The decline of his fortunes, then?\"\n\n\"This hat is three years old. These flat brims curled at the edge\ncame in then. It is a hat of the very best quality. Look at the\nband of ribbed silk and the excellent lining. If this man could\nafford to buy so expensive a hat three years ago, and has had no\nhat since, then he has assuredly gone down in the world.\"\n\n\"Well, that is clear enough, certainly. But how about the\nforesight and the moral retrogression?\"\n\nSherlock Holmes laughed. \"Here is the foresight,\" said he putting\nhis finger upon the little disc and loop of the hat-securer.\n\"They are never sold upon hats. If this man ordered one, it is a\nsign of a certain amount of foresight, since he went out of his\nway to take this precaution against the wind. But since we see\nthat he has broken the elastic and has not troubled to replace\nit, it is obvious that he has less foresight now than formerly,\nwhich is a distinct proof of a weakening nature. On the other\nhand, he has endeavoured to conceal some of these stains upon the\nfelt by daubing them with ink, which is a sign that he has not\nentirely lost his self-respect.\"\n\n\"Your reasoning is certainly plausible.\"\n\n\"The further points, that he is middle-aged, that his hair is\ngrizzled, that it has been recently cut, and that he uses\nlime-cream, are all to be gathered from a close examination of the\nlower part of the lining. The lens discloses a large number of\nhair-ends, clean cut by the scissors of the barber. They all\nappear to be adhesive, and there is a distinct odour of\nlime-cream. This dust, you will observe, is not the gritty, grey\ndust of the street but the fluffy brown dust of the house,\nshowing that it has been hung up indoors most of the time, while\nthe marks of moisture upon the inside are proof positive that the\nwearer perspired very freely, and could therefore, hardly be in\nthe best of training.\"\n\n\"But his wife--you said that she had ceased to love him.\"\n\n\"This hat has not been brushed for weeks. When I see you, my dear\nWatson, with a week's accumulation of dust upon your hat, and\nwhen your wife allows you to go out in such a state, I shall fear\nthat you also have been unfortunate enough to lose your wife's\naffection.\"\n\n\"But he might be a bachelor.\"\n\n\"Nay, he was bringing home the goose as a peace-offering to his\nwife. Remember the card upon the bird's leg.\"\n\n\"You have an answer to everything. But how on earth do you deduce\nthat the gas is not laid on in his house?\"\n\n\"One tallow stain, or even two, might come by chance; but when I\nsee no less than five, I think that there can be little doubt\nthat the individual must be brought into frequent contact with\nburning tallow--walks upstairs at night probably with his hat in\none hand and a guttering candle in the other. Anyhow, he never\ngot tallow-stains from a gas-jet. Are you satisfied?\"\n\n\"Well, it is very ingenious,\" said I, laughing; \"but since, as\nyou said just now, there has been no crime committed, and no harm\ndone save the loss of a goose, all this seems to be rather a\nwaste of energy.\"\n\nSherlock Holmes had opened his mouth to reply, when the door flew\nopen, and Peterson, the commissionaire, rushed into the apartment\nwith flushed cheeks and the face of a man who is dazed with\nastonishment.\n\n\"The goose, Mr. Holmes! The goose, sir!\" he gasped.\n\n\"Eh? What of it, then? Has it returned to life and flapped off\nthrough the kitchen window?\" Holmes twisted himself round upon\nthe sofa to get a fairer view of the man's excited face.\n\n\"See here, sir! See what my wife found in its crop!\" He held out\nhis hand and displayed upon the centre of the palm a brilliantly\nscintillating blue stone, rather smaller than a bean in size, but\nof such purity and radiance that it twinkled like an electric\npoint in the dark hollow of his hand.\n\nSherlock Holmes sat up with a whistle. \"By Jove, Peterson!\" said\nhe, \"this is treasure trove indeed. I suppose you know what you\nhave got?\"\n\n\"A diamond, sir? A precious stone. It cuts into glass as though\nit were putty.\"\n\n\"It's more than a precious stone. It is the precious stone.\"\n\n\"Not the Countess of Morcar's blue carbuncle!\" I ejaculated.\n\n\"Precisely so. I ought to know its size and shape, seeing that I\nhave read the advertisement about it in The Times every day\nlately. It is absolutely unique, and its value can only be\nconjectured, but the reward offered of 1000 pounds is certainly\nnot within a twentieth part of the market price.\"\n\n\"A thousand pounds! Great Lord of mercy!\" The commissionaire\nplumped down into a chair and stared from one to the other of us.\n\n\"That is the reward, and I have reason to know that there are\nsentimental considerations in the background which would induce\nthe Countess to part with half her fortune if she could but\nrecover the gem.\"\n\n\"It was lost, if I remember aright, at the Hotel Cosmopolitan,\" I\nremarked.\n\n\"Precisely so, on December 22nd, just five days ago. John Horner,\na plumber, was accused of having abstracted it from the lady's\njewel-case. The evidence against him was so strong that the case\nhas been referred to the Assizes. I have some account of the\nmatter here, I believe.\" He rummaged amid his newspapers,\nglancing over the dates, until at last he smoothed one out,\ndoubled it over, and read the following paragraph:\n\n\"Hotel Cosmopolitan Jewel Robbery. John Horner, 26, plumber, was\nbrought up upon the charge of having upon the 22nd inst.,\nabstracted from the jewel-case of the Countess of Morcar the\nvaluable gem known as the blue carbuncle. James Ryder,\nupper-attendant at the hotel, gave his evidence to the effect\nthat he had shown Horner up to the dressing-room of the Countess\nof Morcar upon the day of the robbery in order that he might\nsolder the second bar of the grate, which was loose. He had\nremained with Horner some little time, but had finally been\ncalled away. On returning, he found that Horner had disappeared,\nthat the bureau had been forced open, and that the small morocco\ncasket in which, as it afterwards transpired, the Countess was\naccustomed to keep her jewel, was lying empty upon the\ndressing-table. Ryder instantly gave the alarm, and Horner was\narrested the same evening; but the stone could not be found\neither upon his person or in his rooms. Catherine Cusack, maid to\nthe Countess, deposed to having heard Ryder's cry of dismay on\ndiscovering the robbery, and to having rushed into the room,\nwhere she found matters as described by the last witness.\nInspector Bradstreet, B division, gave evidence as to the arrest\nof Horner, who struggled frantically, and protested his innocence\nin the strongest terms. Evidence of a previous conviction for\nrobbery having been given against the prisoner, the magistrate\nrefused to deal summarily with the offence, but referred it to\nthe Assizes. Horner, who had shown signs of intense emotion\nduring the proceedings, fainted away at the conclusion and was\ncarried out of court.\"\n\n\"Hum! So much for the police-court,\" said Holmes thoughtfully,\ntossing aside the paper. \"The question for us now to solve is the\nsequence of events leading from a rifled jewel-case at one end to\nthe crop of a goose in Tottenham Court Road at the other. You\nsee, Watson, our little deductions have suddenly assumed a much\nmore important and less innocent aspect. Here is the stone; the\nstone came from the goose, and the goose came from Mr. Henry\nBaker, the gentleman with the bad hat and all the other\ncharacteristics with which I have bored you. So now we must set\nourselves very seriously to finding this gentleman and\nascertaining what part he has played in this little mystery. To\ndo this, we must try the simplest means first, and these lie\nundoubtedly in an advertisement in all the evening papers. If\nthis fail, I shall have recourse to other methods.\"\n\n\"What will you say?\"\n\n\"Give me a pencil and that slip of paper. Now, then: 'Found at\nthe corner of Goodge Street, a goose and a black felt hat. Mr.\nHenry Baker can have the same by applying at 6:30 this evening at\n221B, Baker Street.' That is clear and concise.\"\n\n\"Very. But will he see it?\"\n\n\"Well, he is sure to keep an eye on the papers, since, to a poor\nman, the loss was a heavy one. He was clearly so scared by his\nmischance in breaking the window and by the approach of Peterson\nthat he thought of nothing but flight, but since then he must\nhave bitterly regretted the impulse which caused him to drop his\nbird. Then, again, the introduction of his name will cause him to\nsee it, for everyone who knows him will direct his attention to\nit. Here you are, Peterson, run down to the advertising agency\nand have this put in the evening papers.\"\n\n\"In which, sir?\"\n\n\"Oh, in the Globe, Star, Pall Mall, St. James's, Evening News,\nStandard, Echo, and any others that occur to you.\"\n\n\"Very well, sir. And this stone?\"\n\n\"Ah, yes, I shall keep the stone. Thank you. And, I say,\nPeterson, just buy a goose on your way back and leave it here\nwith me, for we must have one to give to this gentleman in place\nof the one which your family is now devouring.\"\n\nWhen the commissionaire had gone, Holmes took up the stone and\nheld it against the light. \"It's a bonny thing,\" said he. \"Just\nsee how it glints and sparkles. Of course it is a nucleus and\nfocus of crime. Every good stone is. They are the devil's pet\nbaits. In the larger and older jewels every facet may stand for a\nbloody deed. This stone is not yet twenty years old. It was found\nin the banks of the Amoy River in southern China and is remarkable\nin having every characteristic of the carbuncle, save that it is\nblue in shade instead of ruby red. In spite of its youth, it has\nalready a sinister history. There have been two murders, a\nvitriol-throwing, a suicide, and several robberies brought about\nfor the sake of this forty-grain weight of crystallised charcoal.\nWho would think that so pretty a toy would be a purveyor to the\ngallows and the prison? I'll lock it up in my strong box now and\ndrop a line to the Countess to say that we have it.\"\n\n\"Do you think that this man Horner is innocent?\"\n\n\"I cannot tell.\"\n\n\"Well, then, do you imagine that this other one, Henry Baker, had\nanything to do with the matter?\"\n\n\"It is, I think, much more likely that Henry Baker is an\nabsolutely innocent man, who had no idea that the bird which he\nwas carrying was of considerably more value than if it were made\nof solid gold. That, however, I shall determine by a very simple\ntest if we have an answer to our advertisement.\"\n\n\"And you can do nothing until then?\"\n\n\"Nothing.\"\n\n\"In that case I shall continue my professional round. But I shall\ncome back in the evening at the hour you have mentioned, for I\nshould like to see the solution of so tangled a business.\"\n\n\"Very glad to see you. I dine at seven. There is a woodcock, I\nbelieve. By the way, in view of recent occurrences, perhaps I\nought to ask Mrs. Hudson to examine its crop.\"\n\nI had been delayed at a case, and it was a little after half-past\nsix when I found myself in Baker Street once more. As I\napproached the house I saw a tall man in a Scotch bonnet with a\ncoat which was buttoned up to his chin waiting outside in the\nbright semicircle which was thrown from the fanlight. Just as I\narrived the door was opened, and we were shown up together to\nHolmes' room.\n\n\"Mr. Henry Baker, I believe,\" said he, rising from his armchair\nand greeting his visitor with the easy air of geniality which he\ncould so readily assume. \"Pray take this chair by the fire, Mr.\nBaker. It is a cold night, and I observe that your circulation is\nmore adapted for summer than for winter. Ah, Watson, you have\njust come at the right time. Is that your hat, Mr. Baker?\"\n\n\"Yes, sir, that is undoubtedly my hat.\"\n\nHe was a large man with rounded shoulders, a massive head, and a\nbroad, intelligent face, sloping down to a pointed beard of\ngrizzled brown. A touch of red in nose and cheeks, with a slight\ntremor of his extended hand, recalled Holmes' surmise as to his\nhabits. His rusty black frock-coat was buttoned right up in\nfront, with the collar turned up, and his lank wrists protruded\nfrom his sleeves without a sign of cuff or shirt. He spoke in a\nslow staccato fashion, choosing his words with care, and gave the\nimpression generally of a man of learning and letters who had had\nill-usage at the hands of fortune.\n\n\"We have retained these things for some days,\" said Holmes,\n\"because we expected to see an advertisement from you giving your\naddress. I am at a loss to know now why you did not advertise.\"\n\nOur visitor gave a rather shamefaced laugh. \"Shillings have not\nbeen so plentiful with me as they once were,\" he remarked. \"I had\nno doubt that the gang of roughs who assaulted me had carried off\nboth my hat and the bird. I did not care to spend more money in a\nhopeless attempt at recovering them.\"\n\n\"Very naturally. By the way, about the bird, we were compelled to\neat it.\"\n\n\"To eat it!\" Our visitor half rose from his chair in his\nexcitement.\n\n\"Yes, it would have been of no use to anyone had we not done so.\nBut I presume that this other goose upon the sideboard, which is\nabout the same weight and perfectly fresh, will answer your\npurpose equally well?\"\n\n\"Oh, certainly, certainly,\" answered Mr. Baker with a sigh of\nrelief.\n\n\"Of course, we still have the feathers, legs, crop, and so on of\nyour own bird, so if you wish--\"\n\nThe man burst into a hearty laugh. \"They might be useful to me as\nrelics of my adventure,\" said he, \"but beyond that I can hardly\nsee what use the disjecta membra of my late acquaintance are\ngoing to be to me. No, sir, I think that, with your permission, I\nwill confine my attentions to the excellent bird which I perceive\nupon the sideboard.\"\n\nSherlock Holmes glanced sharply across at me with a slight shrug\nof his shoulders.\n\n\"There is your hat, then, and there your bird,\" said he. \"By the\nway, would it bore you to tell me where you got the other one\nfrom? I am somewhat of a fowl fancier, and I have seldom seen a\nbetter grown goose.\"\n\n\"Certainly, sir,\" said Baker, who had risen and tucked his newly\ngained property under his arm. \"There are a few of us who\nfrequent the Alpha Inn, near the Museum--we are to be found in\nthe Museum itself during the day, you understand. This year our\ngood host, Windigate by name, instituted a goose club, by which,\non consideration of some few pence every week, we were each to\nreceive a bird at Christmas. My pence were duly paid, and the\nrest is familiar to you. I am much indebted to you, sir, for a\nScotch bonnet is fitted neither to my years nor my gravity.\" With\na comical pomposity of manner he bowed solemnly to both of us and\nstrode off upon his way.\n\n\"So much for Mr. Henry Baker,\" said Holmes when he had closed the\ndoor behind him. \"It is quite certain that he knows nothing\nwhatever about the matter. Are you hungry, Watson?\"\n\n\"Not particularly.\"\n\n\"Then I suggest that we turn our dinner into a supper and follow\nup this clue while it is still hot.\"\n\n\"By all means.\"\n\nIt was a bitter night, so we drew on our ulsters and wrapped\ncravats about our throats. Outside, the stars were shining coldly\nin a cloudless sky, and the breath of the passers-by blew out\ninto smoke like so many pistol shots. Our footfalls rang out\ncrisply and loudly as we swung through the doctors' quarter,\nWimpole Street, Harley Street, and so through Wigmore Street into\nOxford Street. In a quarter of an hour we were in Bloomsbury at\nthe Alpha Inn, which is a small public-house at the corner of one\nof the streets which runs down into Holborn. Holmes pushed open\nthe door of the private bar and ordered two glasses of beer from\nthe ruddy-faced, white-aproned landlord.\n\n\"Your beer should be excellent if it is as good as your geese,\"\nsaid he.\n\n\"My geese!\" The man seemed surprised.\n\n\"Yes. I was speaking only half an hour ago to Mr. Henry Baker,\nwho was a member of your goose club.\"\n\n\"Ah! yes, I see. But you see, sir, them's not our geese.\"\n\n\"Indeed! Whose, then?\"\n\n\"Well, I got the two dozen from a salesman in Covent Garden.\"\n\n\"Indeed? I know some of them. Which was it?\"\n\n\"Breckinridge is his name.\"\n\n\"Ah! I don't know him. Well, here's your good health landlord,\nand prosperity to your house. Good-night.\"\n\n\"Now for Mr. Breckinridge,\" he continued, buttoning up his coat\nas we came out into the frosty air. \"Remember, Watson that though\nwe have so homely a thing as a goose at one end of this chain, we\nhave at the other a man who will certainly get seven years' penal\nservitude unless we can establish his innocence. It is possible\nthat our inquiry may but confirm his guilt; but, in any case, we\nhave a line of investigation which has been missed by the police,\nand which a singular chance has placed in our hands. Let us\nfollow it out to the bitter end. Faces to the south, then, and\nquick march!\"\n\nWe passed across Holborn, down Endell Street, and so through a\nzigzag of slums to Covent Garden Market. One of the largest\nstalls bore the name of Breckinridge upon it, and the proprietor\na horsey-looking man, with a sharp face and trim side-whiskers was\nhelping a boy to put up the shutters.\n\n\"Good-evening. It's a cold night,\" said Holmes.\n\nThe salesman nodded and shot a questioning glance at my\ncompanion.\n\n\"Sold out of geese, I see,\" continued Holmes, pointing at the\nbare slabs of marble.\n\n\"Let you have five hundred to-morrow morning.\"\n\n\"That's no good.\"\n\n\"Well, there are some on the stall with the gas-flare.\"\n\n\"Ah, but I was recommended to you.\"\n\n\"Who by?\"\n\n\"The landlord of the Alpha.\"\n\n\"Oh, yes; I sent him a couple of dozen.\"\n\n\"Fine birds they were, too. Now where did you get them from?\"\n\nTo my surprise the question provoked a burst of anger from the\nsalesman.\n\n\"Now, then, mister,\" said he, with his head cocked and his arms\nakimbo, \"what are you driving at? Let's have it straight, now.\"\n\n\"It is straight enough. I should like to know who sold you the\ngeese which you supplied to the Alpha.\"\n\n\"Well then, I shan't tell you. So now!\"\n\n\"Oh, it is a matter of no importance; but I don't know why you\nshould be so warm over such a trifle.\"\n\n\"Warm! You'd be as warm, maybe, if you were as pestered as I am.\nWhen I pay good money for a good article there should be an end\nof the business; but it's 'Where are the geese?' and 'Who did you\nsell the geese to?' and 'What will you take for the geese?' One\nwould think they were the only geese in the world, to hear the\nfuss that is made over them.\"\n\n\"Well, I have no connection with any other people who have been\nmaking inquiries,\" said Holmes carelessly. \"If you won't tell us\nthe bet is off, that is all. But I'm always ready to back my\nopinion on a matter of fowls, and I have a fiver on it that the\nbird I ate is country bred.\"\n\n\"Well, then, you've lost your fiver, for it's town bred,\" snapped\nthe salesman.\n\n\"It's nothing of the kind.\"\n\n\"I say it is.\"\n\n\"I don't believe it.\"\n\n\"D'you think you know more about fowls than I, who have handled\nthem ever since I was a nipper? I tell you, all those birds that\nwent to the Alpha were town bred.\"\n\n\"You'll never persuade me to believe that.\"\n\n\"Will you bet, then?\"\n\n\"It's merely taking your money, for I know that I am right. But\nI'll have a sovereign on with you, just to teach you not to be\nobstinate.\"\n\nThe salesman chuckled grimly. \"Bring me the books, Bill,\" said\nhe.\n\nThe small boy brought round a small thin volume and a great\ngreasy-backed one, laying them out together beneath the hanging\nlamp.\n\n\"Now then, Mr. Cocksure,\" said the salesman, \"I thought that I\nwas out of geese, but before I finish you'll find that there is\nstill one left in my shop. You see this little book?\"\n\n\"Well?\"\n\n\"That's the list of the folk from whom I buy. D'you see? Well,\nthen, here on this page are the country folk, and the numbers\nafter their names are where their accounts are in the big ledger.\nNow, then! You see this other page in red ink? Well, that is a\nlist of my town suppliers. Now, look at that third name. Just\nread it out to me.\"\n\n\"Mrs. Oakshott, 117, Brixton Road--249,\" read Holmes.\n\n\"Quite so. Now turn that up in the ledger.\"\n\nHolmes turned to the page indicated. \"Here you are, 'Mrs.\nOakshott, 117, Brixton Road, egg and poultry supplier.'\"\n\n\"Now, then, what's the last entry?\"\n\n\"'December 22nd. Twenty-four geese at 7s. 6d.'\"\n\n\"Quite so. There you are. And underneath?\"\n\n\"'Sold to Mr. Windigate of the Alpha, at 12s.'\"\n\n\"What have you to say now?\"\n\nSherlock Holmes looked deeply chagrined. He drew a sovereign from\nhis pocket and threw it down upon the slab, turning away with the\nair of a man whose disgust is too deep for words. A few yards off\nhe stopped under a lamp-post and laughed in the hearty, noiseless\nfashion which was peculiar to him.\n\n\"When you see a man with whiskers of that cut and the 'Pink 'un'\nprotruding out of his pocket, you can always draw him by a bet,\"\nsaid he. \"I daresay that if I had put 100 pounds down in front of\nhim, that man would not have given me such complete information\nas was drawn from him by the idea that he was doing me on a\nwager. Well, Watson, we are, I fancy, nearing the end of our\nquest, and the only point which remains to be determined is\nwhether we should go on to this Mrs. Oakshott to-night, or\nwhether we should reserve it for to-morrow. It is clear from what\nthat surly fellow said that there are others besides ourselves\nwho are anxious about the matter, and I should--\"\n\nHis remarks were suddenly cut short by a loud hubbub which broke\nout from the stall which we had just left. Turning round we saw a\nlittle rat-faced fellow standing in the centre of the circle of\nyellow light which was thrown by the swinging lamp, while\nBreckinridge, the salesman, framed in the door of his stall, was\nshaking his fists fiercely at the cringing figure.\n\n\"I've had enough of you and your geese,\" he shouted. \"I wish you\nwere all at the devil together. If you come pestering me any more\nwith your silly talk I'll set the dog at you. You bring Mrs.\nOakshott here and I'll answer her, but what have you to do with\nit? Did I buy the geese off you?\"\n\n\"No; but one of them was mine all the same,\" whined the little\nman.\n\n\"Well, then, ask Mrs. Oakshott for it.\"\n\n\"She told me to ask you.\"\n\n\"Well, you can ask the King of Proosia, for all I care. I've had\nenough of it. Get out of this!\" He rushed fiercely forward, and\nthe inquirer flitted away into the darkness.\n\n\"Ha! this may save us a visit to Brixton Road,\" whispered Holmes.\n\"Come with me, and we will see what is to be made of this\nfellow.\" Striding through the scattered knots of people who\nlounged round the flaring stalls, my companion speedily overtook\nthe little man and touched him upon the shoulder. He sprang\nround, and I could see in the gas-light that every vestige of\ncolour had been driven from his face.\n\n\"Who are you, then? What do you want?\" he asked in a quavering\nvoice.\n\n\"You will excuse me,\" said Holmes blandly, \"but I could not help\noverhearing the questions which you put to the salesman just now.\nI think that I could be of assistance to you.\"\n\n\"You? Who are you? How could you know anything of the matter?\"\n\n\"My name is Sherlock Holmes. It is my business to know what other\npeople don't know.\"\n\n\"But you can know nothing of this?\"\n\n\"Excuse me, I know everything of it. You are endeavouring to\ntrace some geese which were sold by Mrs. Oakshott, of Brixton\nRoad, to a salesman named Breckinridge, by him in turn to Mr.\nWindigate, of the Alpha, and by him to his club, of which Mr.\nHenry Baker is a member.\"\n\n\"Oh, sir, you are the very man whom I have longed to meet,\" cried\nthe little fellow with outstretched hands and quivering fingers.\n\"I can hardly explain to you how interested I am in this matter.\"\n\nSherlock Holmes hailed a four-wheeler which was passing. \"In that\ncase we had better discuss it in a cosy room rather than in this\nwind-swept market-place,\" said he. \"But pray tell me, before we\ngo farther, who it is that I have the pleasure of assisting.\"\n\nThe man hesitated for an instant. \"My name is John Robinson,\" he\nanswered with a sidelong glance.\n\n\"No, no; the real name,\" said Holmes sweetly. \"It is always\nawkward doing business with an alias.\"\n\nA flush sprang to the white cheeks of the stranger. \"Well then,\"\nsaid he, \"my real name is James Ryder.\"\n\n\"Precisely so. Head attendant at the Hotel Cosmopolitan. Pray\nstep into the cab, and I shall soon be able to tell you\neverything which you would wish to know.\"\n\nThe little man stood glancing from one to the other of us with\nhalf-frightened, half-hopeful eyes, as one who is not sure\nwhether he is on the verge of a windfall or of a catastrophe.\nThen he stepped into the cab, and in half an hour we were back in\nthe sitting-room at Baker Street. Nothing had been said during\nour drive, but the high, thin breathing of our new companion, and\nthe claspings and unclaspings of his hands, spoke of the nervous\ntension within him.\n\n\"Here we are!\" said Holmes cheerily as we filed into the room.\n\"The fire looks very seasonable in this weather. You look cold,\nMr. Ryder. Pray take the basket-chair. I will just put on my\nslippers before we settle this little matter of yours. Now, then!\nYou want to know what became of those geese?\"\n\n\"Yes, sir.\"\n\n\"Or rather, I fancy, of that goose. It was one bird, I imagine in\nwhich you were interested--white, with a black bar across the\ntail.\"\n\nRyder quivered with emotion. \"Oh, sir,\" he cried, \"can you tell\nme where it went to?\"\n\n\"It came here.\"\n\n\"Here?\"\n\n\"Yes, and a most remarkable bird it proved. I don't wonder that\nyou should take an interest in it. It laid an egg after it was\ndead--the bonniest, brightest little blue egg that ever was seen.\nI have it here in my museum.\"\n\nOur visitor staggered to his feet and clutched the mantelpiece\nwith his right hand. Holmes unlocked his strong-box and held up\nthe blue carbuncle, which shone out like a star, with a cold,\nbrilliant, many-pointed radiance. Ryder stood glaring with a\ndrawn face, uncertain whether to claim or to disown it.\n\n\"The game's up, Ryder,\" said Holmes quietly. \"Hold up, man, or\nyou'll be into the fire! Give him an arm back into his chair,\nWatson. He's not got blood enough to go in for felony with\nimpunity. Give him a dash of brandy. So! Now he looks a little\nmore human. What a shrimp it is, to be sure!\"\n\nFor a moment he had staggered and nearly fallen, but the brandy\nbrought a tinge of colour into his cheeks, and he sat staring\nwith frightened eyes at his accuser.\n\n\"I have almost every link in my hands, and all the proofs which I\ncould possibly need, so there is little which you need tell me.\nStill, that little may as well be cleared up to make the case\ncomplete. You had heard, Ryder, of this blue stone of the\nCountess of Morcar's?\"\n\n\"It was Catherine Cusack who told me of it,\" said he in a\ncrackling voice.\n\n\"I see--her ladyship's waiting-maid. Well, the temptation of\nsudden wealth so easily acquired was too much for you, as it has\nbeen for better men before you; but you were not very scrupulous\nin the means you used. It seems to me, Ryder, that there is the\nmaking of a very pretty villain in you. You knew that this man\nHorner, the plumber, had been concerned in some such matter\nbefore, and that suspicion would rest the more readily upon him.\nWhat did you do, then? You made some small job in my lady's\nroom--you and your confederate Cusack--and you managed that he\nshould be the man sent for. Then, when he had left, you rifled\nthe jewel-case, raised the alarm, and had this unfortunate man\narrested. You then--\"\n\nRyder threw himself down suddenly upon the rug and clutched at my\ncompanion's knees. \"For God's sake, have mercy!\" he shrieked.\n\"Think of my father! Of my mother! It would break their hearts. I\nnever went wrong before! I never will again. I swear it. I'll\nswear it on a Bible. Oh, don't bring it into court! For Christ's\nsake, don't!\"\n\n\"Get back into your chair!\" said Holmes sternly. \"It is very well\nto cringe and crawl now, but you thought little enough of this\npoor Horner in the dock for a crime of which he knew nothing.\"\n\n\"I will fly, Mr. Holmes. I will leave the country, sir. Then the\ncharge against him will break down.\"\n\n\"Hum! We will talk about that. And now let us hear a true account\nof the next act. How came the stone into the goose, and how came\nthe goose into the open market? Tell us the truth, for there lies\nyour only hope of safety.\"\n\nRyder passed his tongue over his parched lips. \"I will tell you\nit just as it happened, sir,\" said he. \"When Horner had been\narrested, it seemed to me that it would be best for me to get\naway with the stone at once, for I did not know at what moment\nthe police might not take it into their heads to search me and my\nroom. There was no place about the hotel where it would be safe.\nI went out, as if on some commission, and I made for my sister's\nhouse. She had married a man named Oakshott, and lived in Brixton\nRoad, where she fattened fowls for the market. All the way there\nevery man I met seemed to me to be a policeman or a detective;\nand, for all that it was a cold night, the sweat was pouring down\nmy face before I came to the Brixton Road. My sister asked me\nwhat was the matter, and why I was so pale; but I told her that I\nhad been upset by the jewel robbery at the hotel. Then I went\ninto the back yard and smoked a pipe and wondered what it would\nbe best to do.\n\n\"I had a friend once called Maudsley, who went to the bad, and\nhas just been serving his time in Pentonville. One day he had met\nme, and fell into talk about the ways of thieves, and how they\ncould get rid of what they stole. I knew that he would be true to\nme, for I knew one or two things about him; so I made up my mind\nto go right on to Kilburn, where he lived, and take him into my\nconfidence. He would show me how to turn the stone into money.\nBut how to get to him in safety? I thought of the agonies I had\ngone through in coming from the hotel. I might at any moment be\nseized and searched, and there would be the stone in my waistcoat\npocket. I was leaning against the wall at the time and looking at\nthe geese which were waddling about round my feet, and suddenly\nan idea came into my head which showed me how I could beat the\nbest detective that ever lived.\n\n\"My sister had told me some weeks before that I might have the\npick of her geese for a Christmas present, and I knew that she\nwas always as good as her word. I would take my goose now, and in\nit I would carry my stone to Kilburn. There was a little shed in\nthe yard, and behind this I drove one of the birds--a fine big\none, white, with a barred tail. I caught it, and prying its bill\nopen, I thrust the stone down its throat as far as my finger\ncould reach. The bird gave a gulp, and I felt the stone pass\nalong its gullet and down into its crop. But the creature flapped\nand struggled, and out came my sister to know what was the\nmatter. As I turned to speak to her the brute broke loose and\nfluttered off among the others.\n\n\"'Whatever were you doing with that bird, Jem?' says she.\n\n\"'Well,' said I, 'you said you'd give me one for Christmas, and I\nwas feeling which was the fattest.'\n\n\"'Oh,' says she, 'we've set yours aside for you--Jem's bird, we\ncall it. It's the big white one over yonder. There's twenty-six\nof them, which makes one for you, and one for us, and two dozen\nfor the market.'\n\n\"'Thank you, Maggie,' says I; 'but if it is all the same to you,\nI'd rather have that one I was handling just now.'\n\n\"'The other is a good three pound heavier,' said she, 'and we\nfattened it expressly for you.'\n\n\"'Never mind. I'll have the other, and I'll take it now,' said I.\n\n\"'Oh, just as you like,' said she, a little huffed. 'Which is it\nyou want, then?'\n\n\"'That white one with the barred tail, right in the middle of the\nflock.'\n\n\"'Oh, very well. Kill it and take it with you.'\n\n\"Well, I did what she said, Mr. Holmes, and I carried the bird\nall the way to Kilburn. I told my pal what I had done, for he was\na man that it was easy to tell a thing like that to. He laughed\nuntil he choked, and we got a knife and opened the goose. My\nheart turned to water, for there was no sign of the stone, and I\nknew that some terrible mistake had occurred. I left the bird,\nrushed back to my sister's, and hurried into the back yard. There\nwas not a bird to be seen there.\n\n\"'Where are they all, Maggie?' I cried.\n\n\"'Gone to the dealer's, Jem.'\n\n\"'Which dealer's?'\n\n\"'Breckinridge, of Covent Garden.'\n\n\"'But was there another with a barred tail?' I asked, 'the same\nas the one I chose?'\n\n\"'Yes, Jem; there were two barred-tailed ones, and I could never\ntell them apart.'\n\n\"Well, then, of course I saw it all, and I ran off as hard as my\nfeet would carry me to this man Breckinridge; but he had sold the\nlot at once, and not one word would he tell me as to where they\nhad gone. You heard him yourselves to-night. Well, he has always\nanswered me like that. My sister thinks that I am going mad.\nSometimes I think that I am myself. And now--and now I am myself\na branded thief, without ever having touched the wealth for which\nI sold my character. God help me! God help me!\" He burst into\nconvulsive sobbing, with his face buried in his hands.\n\nThere was a long silence, broken only by his heavy breathing and\nby the measured tapping of Sherlock Holmes' finger-tips upon the\nedge of the table. Then my friend rose and threw open the door.\n\n\"Get out!\" said he.\n\n\"What, sir! Oh, Heaven bless you!\"\n\n\"No more words. Get out!\"\n\nAnd no more words were needed. There was a rush, a clatter upon\nthe stairs, the bang of a door, and the crisp rattle of running\nfootfalls from the street.\n\n\"After all, Watson,\" said Holmes, reaching up his hand for his\nclay pipe, \"I am not retained by the police to supply their\ndeficiencies. If Horner were in danger it would be another thing;\nbut this fellow will not appear against him, and the case must\ncollapse. I suppose that I am commuting a felony, but it is just\npossible that I am saving a soul. This fellow will not go wrong\nagain; he is too terribly frightened. Send him to gaol now, and\nyou make him a gaol-bird for life. Besides, it is the season of\nforgiveness. Chance has put in our way a most singular and\nwhimsical problem, and its solution is its own reward. If you\nwill have the goodness to touch the bell, Doctor, we will begin\nanother investigation, in which, also a bird will be the chief\nfeature.\"\n\n\n\nVIII. THE ADVENTURE OF THE SPECKLED BAND\n\nOn glancing over my notes of the seventy odd cases in which I\nhave during the last eight years studied the methods of my friend\nSherlock Holmes, I find many tragic, some comic, a large number\nmerely strange, but none commonplace; for, working as he did\nrather for the love of his art than for the acquirement of\nwealth, he refused to associate himself with any investigation\nwhich did not tend towards the unusual, and even the fantastic.\nOf all these varied cases, however, I cannot recall any which\npresented more singular features than that which was associated\nwith the well-known Surrey family of the Roylotts of Stoke Moran.\nThe events in question occurred in the early days of my\nassociation with Holmes, when we were sharing rooms as bachelors\nin Baker Street. It is possible that I might have placed them\nupon record before, but a promise of secrecy was made at the\ntime, from which I have only been freed during the last month by\nthe untimely death of the lady to whom the pledge was given. It\nis perhaps as well that the facts should now come to light, for I\nhave reasons to know that there are widespread rumours as to the\ndeath of Dr. Grimesby Roylott which tend to make the matter even\nmore terrible than the truth.\n\nIt was early in April in the year '83 that I woke one morning to\nfind Sherlock Holmes standing, fully dressed, by the side of my\nbed. He was a late riser, as a rule, and as the clock on the\nmantelpiece showed me that it was only a quarter-past seven, I\nblinked up at him in some surprise, and perhaps just a little\nresentment, for I was myself regular in my habits.\n\n\"Very sorry to knock you up, Watson,\" said he, \"but it's the\ncommon lot this morning. Mrs. Hudson has been knocked up, she\nretorted upon me, and I on you.\"\n\n\"What is it, then--a fire?\"\n\n\"No; a client. It seems that a young lady has arrived in a\nconsiderable state of excitement, who insists upon seeing me. She\nis waiting now in the sitting-room. Now, when young ladies wander\nabout the metropolis at this hour of the morning, and knock\nsleepy people up out of their beds, I presume that it is\nsomething very pressing which they have to communicate. Should it\nprove to be an interesting case, you would, I am sure, wish to\nfollow it from the outset. I thought, at any rate, that I should\ncall you and give you the chance.\"\n\n\"My dear fellow, I would not miss it for anything.\"\n\nI had no keener pleasure than in following Holmes in his\nprofessional investigations, and in admiring the rapid\ndeductions, as swift as intuitions, and yet always founded on a\nlogical basis with which he unravelled the problems which were\nsubmitted to him. I rapidly threw on my clothes and was ready in\na few minutes to accompany my friend down to the sitting-room. A\nlady dressed in black and heavily veiled, who had been sitting in\nthe window, rose as we entered.\n\n\"Good-morning, madam,\" said Holmes cheerily. \"My name is Sherlock\nHolmes. This is my intimate friend and associate, Dr. Watson,\nbefore whom you can speak as freely as before myself. Ha! I am\nglad to see that Mrs. Hudson has had the good sense to light the\nfire. Pray draw up to it, and I shall order you a cup of hot\ncoffee, for I observe that you are shivering.\"\n\n\"It is not cold which makes me shiver,\" said the woman in a low\nvoice, changing her seat as requested.\n\n\"What, then?\"\n\n\"It is fear, Mr. Holmes. It is terror.\" She raised her veil as\nshe spoke, and we could see that she was indeed in a pitiable\nstate of agitation, her face all drawn and grey, with restless\nfrightened eyes, like those of some hunted animal. Her features\nand figure were those of a woman of thirty, but her hair was shot\nwith premature grey, and her expression was weary and haggard.\nSherlock Holmes ran her over with one of his quick,\nall-comprehensive glances.\n\n\"You must not fear,\" said he soothingly, bending forward and\npatting her forearm. \"We shall soon set matters right, I have no\ndoubt. You have come in by train this morning, I see.\"\n\n\"You know me, then?\"\n\n\"No, but I observe the second half of a return ticket in the palm\nof your left glove. You must have started early, and yet you had\na good drive in a dog-cart, along heavy roads, before you reached\nthe station.\"\n\nThe lady gave a violent start and stared in bewilderment at my\ncompanion.\n\n\"There is no mystery, my dear madam,\" said he, smiling. \"The left\narm of your jacket is spattered with mud in no less than seven\nplaces. The marks are perfectly fresh. There is no vehicle save a\ndog-cart which throws up mud in that way, and then only when you\nsit on the left-hand side of the driver.\"\n\n\"Whatever your reasons may be, you are perfectly correct,\" said\nshe. \"I started from home before six, reached Leatherhead at\ntwenty past, and came in by the first train to Waterloo. Sir, I\ncan stand this strain no longer; I shall go mad if it continues.\nI have no one to turn to--none, save only one, who cares for me,\nand he, poor fellow, can be of little aid. I have heard of you,\nMr. Holmes; I have heard of you from Mrs. Farintosh, whom you\nhelped in the hour of her sore need. It was from her that I had\nyour address. Oh, sir, do you not think that you could help me,\ntoo, and at least throw a little light through the dense darkness\nwhich surrounds me? At present it is out of my power to reward\nyou for your services, but in a month or six weeks I shall be\nmarried, with the control of my own income, and then at least you\nshall not find me ungrateful.\"\n\nHolmes turned to his desk and, unlocking it, drew out a small\ncase-book, which he consulted.\n\n\"Farintosh,\" said he. \"Ah yes, I recall the case; it was\nconcerned with an opal tiara. I think it was before your time,\nWatson. I can only say, madam, that I shall be happy to devote\nthe same care to your case as I did to that of your friend. As to\nreward, my profession is its own reward; but you are at liberty\nto defray whatever expenses I may be put to, at the time which\nsuits you best. And now I beg that you will lay before us\neverything that may help us in forming an opinion upon the\nmatter.\"\n\n\"Alas!\" replied our visitor, \"the very horror of my situation\nlies in the fact that my fears are so vague, and my suspicions\ndepend so entirely upon small points, which might seem trivial to\nanother, that even he to whom of all others I have a right to\nlook for help and advice looks upon all that I tell him about it\nas the fancies of a nervous woman. He does not say so, but I can\nread it from his soothing answers and averted eyes. But I have\nheard, Mr. Holmes, that you can see deeply into the manifold\nwickedness of the human heart. You may advise me how to walk amid\nthe dangers which encompass me.\"\n\n\"I am all attention, madam.\"\n\n\"My name is Helen Stoner, and I am living with my stepfather, who\nis the last survivor of one of the oldest Saxon families in\nEngland, the Roylotts of Stoke Moran, on the western border of\nSurrey.\"\n\nHolmes nodded his head. \"The name is familiar to me,\" said he.\n\n\"The family was at one time among the richest in England, and the\nestates extended over the borders into Berkshire in the north,\nand Hampshire in the west. In the last century, however, four\nsuccessive heirs were of a dissolute and wasteful disposition,\nand the family ruin was eventually completed by a gambler in the\ndays of the Regency. Nothing was left save a few acres of ground,\nand the two-hundred-year-old house, which is itself crushed under\na heavy mortgage. The last squire dragged out his existence\nthere, living the horrible life of an aristocratic pauper; but\nhis only son, my stepfather, seeing that he must adapt himself to\nthe new conditions, obtained an advance from a relative, which\nenabled him to take a medical degree and went out to Calcutta,\nwhere, by his professional skill and his force of character, he\nestablished a large practice. In a fit of anger, however, caused\nby some robberies which had been perpetrated in the house, he\nbeat his native butler to death and narrowly escaped a capital\nsentence. As it was, he suffered a long term of imprisonment and\nafterwards returned to England a morose and disappointed man.\n\n\"When Dr. Roylott was in India he married my mother, Mrs. Stoner,\nthe young widow of Major-General Stoner, of the Bengal Artillery.\nMy sister Julia and I were twins, and we were only two years old\nat the time of my mother's re-marriage. She had a considerable\nsum of money--not less than 1000 pounds a year--and this she\nbequeathed to Dr. Roylott entirely while we resided with him,\nwith a provision that a certain annual sum should be allowed to\neach of us in the event of our marriage. Shortly after our return\nto England my mother died--she was killed eight years ago in a\nrailway accident near Crewe. Dr. Roylott then abandoned his\nattempts to establish himself in practice in London and took us\nto live with him in the old ancestral house at Stoke Moran. The\nmoney which my mother had left was enough for all our wants, and\nthere seemed to be no obstacle to our happiness.\n\n\"But a terrible change came over our stepfather about this time.\nInstead of making friends and exchanging visits with our\nneighbours, who had at first been overjoyed to see a Roylott of\nStoke Moran back in the old family seat, he shut himself up in\nhis house and seldom came out save to indulge in ferocious\nquarrels with whoever might cross his path. Violence of temper\napproaching to mania has been hereditary in the men of the\nfamily, and in my stepfather's case it had, I believe, been\nintensified by his long residence in the tropics. A series of\ndisgraceful brawls took place, two of which ended in the\npolice-court, until at last he became the terror of the village,\nand the folks would fly at his approach, for he is a man of\nimmense strength, and absolutely uncontrollable in his anger.\n\n\"Last week he hurled the local blacksmith over a parapet into a\nstream, and it was only by paying over all the money which I\ncould gather together that I was able to avert another public\nexposure. He had no friends at all save the wandering gipsies,\nand he would give these vagabonds leave to encamp upon the few\nacres of bramble-covered land which represent the family estate,\nand would accept in return the hospitality of their tents,\nwandering away with them sometimes for weeks on end. He has a\npassion also for Indian animals, which are sent over to him by a\ncorrespondent, and he has at this moment a cheetah and a baboon,\nwhich wander freely over his grounds and are feared by the\nvillagers almost as much as their master.\n\n\"You can imagine from what I say that my poor sister Julia and I\nhad no great pleasure in our lives. No servant would stay with\nus, and for a long time we did all the work of the house. She was\nbut thirty at the time of her death, and yet her hair had already\nbegun to whiten, even as mine has.\"\n\n\"Your sister is dead, then?\"\n\n\"She died just two years ago, and it is of her death that I wish\nto speak to you. You can understand that, living the life which I\nhave described, we were little likely to see anyone of our own\nage and position. We had, however, an aunt, my mother's maiden\nsister, Miss Honoria Westphail, who lives near Harrow, and we\nwere occasionally allowed to pay short visits at this lady's\nhouse. Julia went there at Christmas two years ago, and met there\na half-pay major of marines, to whom she became engaged. My\nstepfather learned of the engagement when my sister returned and\noffered no objection to the marriage; but within a fortnight of\nthe day which had been fixed for the wedding, the terrible event\noccurred which has deprived me of my only companion.\"\n\nSherlock Holmes had been leaning back in his chair with his eyes\nclosed and his head sunk in a cushion, but he half opened his\nlids now and glanced across at his visitor.\n\n\"Pray be precise as to details,\" said he.\n\n\"It is easy for me to be so, for every event of that dreadful\ntime is seared into my memory. The manor-house is, as I have\nalready said, very old, and only one wing is now inhabited. The\nbedrooms in this wing are on the ground floor, the sitting-rooms\nbeing in the central block of the buildings. Of these bedrooms\nthe first is Dr. Roylott's, the second my sister's, and the third\nmy own. There is no communication between them, but they all open\nout into the same corridor. Do I make myself plain?\"\n\n\"Perfectly so.\"\n\n\"The windows of the three rooms open out upon the lawn. That\nfatal night Dr. Roylott had gone to his room early, though we\nknew that he had not retired to rest, for my sister was troubled\nby the smell of the strong Indian cigars which it was his custom\nto smoke. She left her room, therefore, and came into mine, where\nshe sat for some time, chatting about her approaching wedding. At\neleven o'clock she rose to leave me, but she paused at the door\nand looked back.\n\n\"'Tell me, Helen,' said she, 'have you ever heard anyone whistle\nin the dead of the night?'\n\n\"'Never,' said I.\n\n\"'I suppose that you could not possibly whistle, yourself, in\nyour sleep?'\n\n\"'Certainly not. But why?'\n\n\"'Because during the last few nights I have always, about three\nin the morning, heard a low, clear whistle. I am a light sleeper,\nand it has awakened me. I cannot tell where it came from--perhaps\nfrom the next room, perhaps from the lawn. I thought that I would\njust ask you whether you had heard it.'\n\n\"'No, I have not. It must be those wretched gipsies in the\nplantation.'\n\n\"'Very likely. And yet if it were on the lawn, I wonder that you\ndid not hear it also.'\n\n\"'Ah, but I sleep more heavily than you.'\n\n\"'Well, it is of no great consequence, at any rate.' She smiled\nback at me, closed my door, and a few moments later I heard her\nkey turn in the lock.\"\n\n\"Indeed,\" said Holmes. \"Was it your custom always to lock\nyourselves in at night?\"\n\n\"Always.\"\n\n\"And why?\"\n\n\"I think that I mentioned to you that the doctor kept a cheetah\nand a baboon. We had no feeling of security unless our doors were\nlocked.\"\n\n\"Quite so. Pray proceed with your statement.\"\n\n\"I could not sleep that night. A vague feeling of impending\nmisfortune impressed me. My sister and I, you will recollect,\nwere twins, and you know how subtle are the links which bind two\nsouls which are so closely allied. It was a wild night. The wind\nwas howling outside, and the rain was beating and splashing\nagainst the windows. Suddenly, amid all the hubbub of the gale,\nthere burst forth the wild scream of a terrified woman. I knew\nthat it was my sister's voice. I sprang from my bed, wrapped a\nshawl round me, and rushed into the corridor. As I opened my door\nI seemed to hear a low whistle, such as my sister described, and\na few moments later a clanging sound, as if a mass of metal had\nfallen. As I ran down the passage, my sister's door was unlocked,\nand revolved slowly upon its hinges. I stared at it\nhorror-stricken, not knowing what was about to issue from it. By\nthe light of the corridor-lamp I saw my sister appear at the\nopening, her face blanched with terror, her hands groping for\nhelp, her whole figure swaying to and fro like that of a\ndrunkard. I ran to her and threw my arms round her, but at that\nmoment her knees seemed to give way and she fell to the ground.\nShe writhed as one who is in terrible pain, and her limbs were\ndreadfully convulsed. At first I thought that she had not\nrecognised me, but as I bent over her she suddenly shrieked out\nin a voice which I shall never forget, 'Oh, my God! Helen! It was\nthe band! The speckled band!' There was something else which she\nwould fain have said, and she stabbed with her finger into the\nair in the direction of the doctor's room, but a fresh convulsion\nseized her and choked her words. I rushed out, calling loudly for\nmy stepfather, and I met him hastening from his room in his\ndressing-gown. When he reached my sister's side she was\nunconscious, and though he poured brandy down her throat and sent\nfor medical aid from the village, all efforts were in vain, for\nshe slowly sank and died without having recovered her\nconsciousness. Such was the dreadful end of my beloved sister.\"\n\n\"One moment,\" said Holmes, \"are you sure about this whistle and\nmetallic sound? Could you swear to it?\"\n\n\"That was what the county coroner asked me at the inquiry. It is\nmy strong impression that I heard it, and yet, among the crash of\nthe gale and the creaking of an old house, I may possibly have\nbeen deceived.\"\n\n\"Was your sister dressed?\"\n\n\"No, she was in her night-dress. In her right hand was found the\ncharred stump of a match, and in her left a match-box.\"\n\n\"Showing that she had struck a light and looked about her when\nthe alarm took place. That is important. And what conclusions did\nthe coroner come to?\"\n\n\"He investigated the case with great care, for Dr. Roylott's\nconduct had long been notorious in the county, but he was unable\nto find any satisfactory cause of death. My evidence showed that\nthe door had been fastened upon the inner side, and the windows\nwere blocked by old-fashioned shutters with broad iron bars,\nwhich were secured every night. The walls were carefully sounded,\nand were shown to be quite solid all round, and the flooring was\nalso thoroughly examined, with the same result. The chimney is\nwide, but is barred up by four large staples. It is certain,\ntherefore, that my sister was quite alone when she met her end.\nBesides, there were no marks of any violence upon her.\"\n\n\"How about poison?\"\n\n\"The doctors examined her for it, but without success.\"\n\n\"What do you think that this unfortunate lady died of, then?\"\n\n\"It is my belief that she died of pure fear and nervous shock,\nthough what it was that frightened her I cannot imagine.\"\n\n\"Were there gipsies in the plantation at the time?\"\n\n\"Yes, there are nearly always some there.\"\n\n\"Ah, and what did you gather from this allusion to a band--a\nspeckled band?\"\n\n\"Sometimes I have thought that it was merely the wild talk of\ndelirium, sometimes that it may have referred to some band of\npeople, perhaps to these very gipsies in the plantation. I do not\nknow whether the spotted handkerchiefs which so many of them wear\nover their heads might have suggested the strange adjective which\nshe used.\"\n\nHolmes shook his head like a man who is far from being satisfied.\n\n\"These are very deep waters,\" said he; \"pray go on with your\nnarrative.\"\n\n\"Two years have passed since then, and my life has been until\nlately lonelier than ever. A month ago, however, a dear friend,\nwhom I have known for many years, has done me the honour to ask\nmy hand in marriage. His name is Armitage--Percy Armitage--the\nsecond son of Mr. Armitage, of Crane Water, near Reading. My\nstepfather has offered no opposition to the match, and we are to\nbe married in the course of the spring. Two days ago some repairs\nwere started in the west wing of the building, and my bedroom\nwall has been pierced, so that I have had to move into the\nchamber in which my sister died, and to sleep in the very bed in\nwhich she slept. Imagine, then, my thrill of terror when last\nnight, as I lay awake, thinking over her terrible fate, I\nsuddenly heard in the silence of the night the low whistle which\nhad been the herald of her own death. I sprang up and lit the\nlamp, but nothing was to be seen in the room. I was too shaken to\ngo to bed again, however, so I dressed, and as soon as it was\ndaylight I slipped down, got a dog-cart at the Crown Inn, which\nis opposite, and drove to Leatherhead, from whence I have come on\nthis morning with the one object of seeing you and asking your\nadvice.\"\n\n\"You have done wisely,\" said my friend. \"But have you told me\nall?\"\n\n\"Yes, all.\"\n\n\"Miss Roylott, you have not. You are screening your stepfather.\"\n\n\"Why, what do you mean?\"\n\nFor answer Holmes pushed back the frill of black lace which\nfringed the hand that lay upon our visitor's knee. Five little\nlivid spots, the marks of four fingers and a thumb, were printed\nupon the white wrist.\n\n\"You have been cruelly used,\" said Holmes.\n\nThe lady coloured deeply and covered over her injured wrist. \"He\nis a hard man,\" she said, \"and perhaps he hardly knows his own\nstrength.\"\n\nThere was a long silence, during which Holmes leaned his chin\nupon his hands and stared into the crackling fire.\n\n\"This is a very deep business,\" he said at last. \"There are a\nthousand details which I should desire to know before I decide\nupon our course of action. Yet we have not a moment to lose. If\nwe were to come to Stoke Moran to-day, would it be possible for\nus to see over these rooms without the knowledge of your\nstepfather?\"\n\n\"As it happens, he spoke of coming into town to-day upon some\nmost important business. It is probable that he will be away all\nday, and that there would be nothing to disturb you. We have a\nhousekeeper now, but she is old and foolish, and I could easily\nget her out of the way.\"\n\n\"Excellent. You are not averse to this trip, Watson?\"\n\n\"By no means.\"\n\n\"Then we shall both come. What are you going to do yourself?\"\n\n\"I have one or two things which I would wish to do now that I am\nin town. But I shall return by the twelve o'clock train, so as to\nbe there in time for your coming.\"\n\n\"And you may expect us early in the afternoon. I have myself some\nsmall business matters to attend to. Will you not wait and\nbreakfast?\"\n\n\"No, I must go. My heart is lightened already since I have\nconfided my trouble to you. I shall look forward to seeing you\nagain this afternoon.\" She dropped her thick black veil over her\nface and glided from the room.\n\n\"And what do you think of it all, Watson?\" asked Sherlock Holmes,\nleaning back in his chair.\n\n\"It seems to me to be a most dark and sinister business.\"\n\n\"Dark enough and sinister enough.\"\n\n\"Yet if the lady is correct in saying that the flooring and walls\nare sound, and that the door, window, and chimney are impassable,\nthen her sister must have been undoubtedly alone when she met her\nmysterious end.\"\n\n\"What becomes, then, of these nocturnal whistles, and what of the\nvery peculiar words of the dying woman?\"\n\n\"I cannot think.\"\n\n\"When you combine the ideas of whistles at night, the presence of\na band of gipsies who are on intimate terms with this old doctor,\nthe fact that we have every reason to believe that the doctor has\nan interest in preventing his stepdaughter's marriage, the dying\nallusion to a band, and, finally, the fact that Miss Helen Stoner\nheard a metallic clang, which might have been caused by one of\nthose metal bars that secured the shutters falling back into its\nplace, I think that there is good ground to think that the\nmystery may be cleared along those lines.\"\n\n\"But what, then, did the gipsies do?\"\n\n\"I cannot imagine.\"\n\n\"I see many objections to any such theory.\"\n\n\"And so do I. It is precisely for that reason that we are going\nto Stoke Moran this day. I want to see whether the objections are\nfatal, or if they may be explained away. But what in the name of\nthe devil!\"\n\nThe ejaculation had been drawn from my companion by the fact that\nour door had been suddenly dashed open, and that a huge man had\nframed himself in the aperture. His costume was a peculiar\nmixture of the professional and of the agricultural, having a\nblack top-hat, a long frock-coat, and a pair of high gaiters,\nwith a hunting-crop swinging in his hand. So tall was he that his\nhat actually brushed the cross bar of the doorway, and his\nbreadth seemed to span it across from side to side. A large face,\nseared with a thousand wrinkles, burned yellow with the sun, and\nmarked with every evil passion, was turned from one to the other\nof us, while his deep-set, bile-shot eyes, and his high, thin,\nfleshless nose, gave him somewhat the resemblance to a fierce old\nbird of prey.\n\n\"Which of you is Holmes?\" asked this apparition.\n\n\"My name, sir; but you have the advantage of me,\" said my\ncompanion quietly.\n\n\"I am Dr. Grimesby Roylott, of Stoke Moran.\"\n\n\"Indeed, Doctor,\" said Holmes blandly. \"Pray take a seat.\"\n\n\"I will do nothing of the kind. My stepdaughter has been here. I\nhave traced her. What has she been saying to you?\"\n\n\"It is a little cold for the time of the year,\" said Holmes.\n\n\"What has she been saying to you?\" screamed the old man\nfuriously.\n\n\"But I have heard that the crocuses promise well,\" continued my\ncompanion imperturbably.\n\n\"Ha! You put me off, do you?\" said our new visitor, taking a step\nforward and shaking his hunting-crop. \"I know you, you scoundrel!\nI have heard of you before. You are Holmes, the meddler.\"\n\nMy friend smiled.\n\n\"Holmes, the busybody!\"\n\nHis smile broadened.\n\n\"Holmes, the Scotland Yard Jack-in-office!\"\n\nHolmes chuckled heartily. \"Your conversation is most\nentertaining,\" said he. \"When you go out close the door, for\nthere is a decided draught.\"\n\n\"I will go when I have said my say. Don't you dare to meddle with\nmy affairs. I know that Miss Stoner has been here. I traced her!\nI am a dangerous man to fall foul of! See here.\" He stepped\nswiftly forward, seized the poker, and bent it into a curve with\nhis huge brown hands.\n\n\"See that you keep yourself out of my grip,\" he snarled, and\nhurling the twisted poker into the fireplace he strode out of the\nroom.\n\n\"He seems a very amiable person,\" said Holmes, laughing. \"I am\nnot quite so bulky, but if he had remained I might have shown him\nthat my grip was not much more feeble than his own.\" As he spoke\nhe picked up the steel poker and, with a sudden effort,\nstraightened it out again.\n\n\"Fancy his having the insolence to confound me with the official\ndetective force! This incident gives zest to our investigation,\nhowever, and I only trust that our little friend will not suffer\nfrom her imprudence in allowing this brute to trace her. And now,\nWatson, we shall order breakfast, and afterwards I shall walk\ndown to Doctors' Commons, where I hope to get some data which may\nhelp us in this matter.\"\n\n\nIt was nearly one o'clock when Sherlock Holmes returned from his\nexcursion. He held in his hand a sheet of blue paper, scrawled\nover with notes and figures.\n\n\"I have seen the will of the deceased wife,\" said he. \"To\ndetermine its exact meaning I have been obliged to work out the\npresent prices of the investments with which it is concerned. The\ntotal income, which at the time of the wife's death was little\nshort of 1100 pounds, is now, through the fall in agricultural\nprices, not more than 750 pounds. Each daughter can claim an\nincome of 250 pounds, in case of marriage. It is evident,\ntherefore, that if both girls had married, this beauty would have\nhad a mere pittance, while even one of them would cripple him to\na very serious extent. My morning's work has not been wasted,\nsince it has proved that he has the very strongest motives for\nstanding in the way of anything of the sort. And now, Watson,\nthis is too serious for dawdling, especially as the old man is\naware that we are interesting ourselves in his affairs; so if you\nare ready, we shall call a cab and drive to Waterloo. I should be\nvery much obliged if you would slip your revolver into your\npocket. An Eley's No. 2 is an excellent argument with gentlemen\nwho can twist steel pokers into knots. That and a tooth-brush\nare, I think, all that we need.\"\n\nAt Waterloo we were fortunate in catching a train for\nLeatherhead, where we hired a trap at the station inn and drove\nfor four or five miles through the lovely Surrey lanes. It was a\nperfect day, with a bright sun and a few fleecy clouds in the\nheavens. The trees and wayside hedges were just throwing out\ntheir first green shoots, and the air was full of the pleasant\nsmell of the moist earth. To me at least there was a strange\ncontrast between the sweet promise of the spring and this\nsinister quest upon which we were engaged. My companion sat in\nthe front of the trap, his arms folded, his hat pulled down over\nhis eyes, and his chin sunk upon his breast, buried in the\ndeepest thought. Suddenly, however, he started, tapped me on the\nshoulder, and pointed over the meadows.\n\n\"Look there!\" said he.\n\nA heavily timbered park stretched up in a gentle slope,\nthickening into a grove at the highest point. From amid the\nbranches there jutted out the grey gables and high roof-tree of a\nvery old mansion.\n\n\"Stoke Moran?\" said he.\n\n\"Yes, sir, that be the house of Dr. Grimesby Roylott,\" remarked\nthe driver.\n\n\"There is some building going on there,\" said Holmes; \"that is\nwhere we are going.\"\n\n\"There's the village,\" said the driver, pointing to a cluster of\nroofs some distance to the left; \"but if you want to get to the\nhouse, you'll find it shorter to get over this stile, and so by\nthe foot-path over the fields. There it is, where the lady is\nwalking.\"\n\n\"And the lady, I fancy, is Miss Stoner,\" observed Holmes, shading\nhis eyes. \"Yes, I think we had better do as you suggest.\"\n\nWe got off, paid our fare, and the trap rattled back on its way\nto Leatherhead.\n\n\"I thought it as well,\" said Holmes as we climbed the stile,\n\"that this fellow should think we had come here as architects, or\non some definite business. It may stop his gossip.\nGood-afternoon, Miss Stoner. You see that we have been as good as\nour word.\"\n\nOur client of the morning had hurried forward to meet us with a\nface which spoke her joy. \"I have been waiting so eagerly for\nyou,\" she cried, shaking hands with us warmly. \"All has turned\nout splendidly. Dr. Roylott has gone to town, and it is unlikely\nthat he will be back before evening.\"\n\n\"We have had the pleasure of making the doctor's acquaintance,\"\nsaid Holmes, and in a few words he sketched out what had\noccurred. Miss Stoner turned white to the lips as she listened.\n\n\"Good heavens!\" she cried, \"he has followed me, then.\"\n\n\"So it appears.\"\n\n\"He is so cunning that I never know when I am safe from him. What\nwill he say when he returns?\"\n\n\"He must guard himself, for he may find that there is someone\nmore cunning than himself upon his track. You must lock yourself\nup from him to-night. If he is violent, we shall take you away to\nyour aunt's at Harrow. Now, we must make the best use of our\ntime, so kindly take us at once to the rooms which we are to\nexamine.\"\n\nThe building was of grey, lichen-blotched stone, with a high\ncentral portion and two curving wings, like the claws of a crab,\nthrown out on each side. In one of these wings the windows were\nbroken and blocked with wooden boards, while the roof was partly\ncaved in, a picture of ruin. The central portion was in little\nbetter repair, but the right-hand block was comparatively modern,\nand the blinds in the windows, with the blue smoke curling up\nfrom the chimneys, showed that this was where the family resided.\nSome scaffolding had been erected against the end wall, and the\nstone-work had been broken into, but there were no signs of any\nworkmen at the moment of our visit. Holmes walked slowly up and\ndown the ill-trimmed lawn and examined with deep attention the\noutsides of the windows.\n\n\"This, I take it, belongs to the room in which you used to sleep,\nthe centre one to your sister's, and the one next to the main\nbuilding to Dr. Roylott's chamber?\"\n\n\"Exactly so. But I am now sleeping in the middle one.\"\n\n\"Pending the alterations, as I understand. By the way, there does\nnot seem to be any very pressing need for repairs at that end\nwall.\"\n\n\"There were none. I believe that it was an excuse to move me from\nmy room.\"\n\n\"Ah! that is suggestive. Now, on the other side of this narrow\nwing runs the corridor from which these three rooms open. There\nare windows in it, of course?\"\n\n\"Yes, but very small ones. Too narrow for anyone to pass\nthrough.\"\n\n\"As you both locked your doors at night, your rooms were\nunapproachable from that side. Now, would you have the kindness\nto go into your room and bar your shutters?\"\n\nMiss Stoner did so, and Holmes, after a careful examination\nthrough the open window, endeavoured in every way to force the\nshutter open, but without success. There was no slit through\nwhich a knife could be passed to raise the bar. Then with his\nlens he tested the hinges, but they were of solid iron, built\nfirmly into the massive masonry. \"Hum!\" said he, scratching his\nchin in some perplexity, \"my theory certainly presents some\ndifficulties. No one could pass these shutters if they were\nbolted. Well, we shall see if the inside throws any light upon\nthe matter.\"\n\nA small side door led into the whitewashed corridor from which\nthe three bedrooms opened. Holmes refused to examine the third\nchamber, so we passed at once to the second, that in which Miss\nStoner was now sleeping, and in which her sister had met with her\nfate. It was a homely little room, with a low ceiling and a\ngaping fireplace, after the fashion of old country-houses. A\nbrown chest of drawers stood in one corner, a narrow\nwhite-counterpaned bed in another, and a dressing-table on the\nleft-hand side of the window. These articles, with two small\nwicker-work chairs, made up all the furniture in the room save\nfor a square of Wilton carpet in the centre. The boards round and\nthe panelling of the walls were of brown, worm-eaten oak, so old\nand discoloured that it may have dated from the original building\nof the house. Holmes drew one of the chairs into a corner and sat\nsilent, while his eyes travelled round and round and up and down,\ntaking in every detail of the apartment.\n\n\"Where does that bell communicate with?\" he asked at last\npointing to a thick bell-rope which hung down beside the bed, the\ntassel actually lying upon the pillow.\n\n\"It goes to the housekeeper's room.\"\n\n\"It looks newer than the other things?\"\n\n\"Yes, it was only put there a couple of years ago.\"\n\n\"Your sister asked for it, I suppose?\"\n\n\"No, I never heard of her using it. We used always to get what we\nwanted for ourselves.\"\n\n\"Indeed, it seemed unnecessary to put so nice a bell-pull there.\nYou will excuse me for a few minutes while I satisfy myself as to\nthis floor.\" He threw himself down upon his face with his lens in\nhis hand and crawled swiftly backward and forward, examining\nminutely the cracks between the boards. Then he did the same with\nthe wood-work with which the chamber was panelled. Finally he\nwalked over to the bed and spent some time in staring at it and\nin running his eye up and down the wall. Finally he took the\nbell-rope in his hand and gave it a brisk tug.\n\n\"Why, it's a dummy,\" said he.\n\n\"Won't it ring?\"\n\n\"No, it is not even attached to a wire. This is very interesting.\nYou can see now that it is fastened to a hook just above where\nthe little opening for the ventilator is.\"\n\n\"How very absurd! I never noticed that before.\"\n\n\"Very strange!\" muttered Holmes, pulling at the rope. \"There are\none or two very singular points about this room. For example,\nwhat a fool a builder must be to open a ventilator into another\nroom, when, with the same trouble, he might have communicated\nwith the outside air!\"\n\n\"That is also quite modern,\" said the lady.\n\n\"Done about the same time as the bell-rope?\" remarked Holmes.\n\n\"Yes, there were several little changes carried out about that\ntime.\"\n\n\"They seem to have been of a most interesting character--dummy\nbell-ropes, and ventilators which do not ventilate. With your\npermission, Miss Stoner, we shall now carry our researches into\nthe inner apartment.\"\n\nDr. Grimesby Roylott's chamber was larger than that of his\nstep-daughter, but was as plainly furnished. A camp-bed, a small\nwooden shelf full of books, mostly of a technical character, an\narmchair beside the bed, a plain wooden chair against the wall, a\nround table, and a large iron safe were the principal things\nwhich met the eye. Holmes walked slowly round and examined each\nand all of them with the keenest interest.\n\n\"What's in here?\" he asked, tapping the safe.\n\n\"My stepfather's business papers.\"\n\n\"Oh! you have seen inside, then?\"\n\n\"Only once, some years ago. I remember that it was full of\npapers.\"\n\n\"There isn't a cat in it, for example?\"\n\n\"No. What a strange idea!\"\n\n\"Well, look at this!\" He took up a small saucer of milk which\nstood on the top of it.\n\n\"No; we don't keep a cat. But there is a cheetah and a baboon.\"\n\n\"Ah, yes, of course! Well, a cheetah is just a big cat, and yet a\nsaucer of milk does not go very far in satisfying its wants, I\ndaresay. There is one point which I should wish to determine.\" He\nsquatted down in front of the wooden chair and examined the seat\nof it with the greatest attention.\n\n\"Thank you. That is quite settled,\" said he, rising and putting\nhis lens in his pocket. \"Hullo! Here is something interesting!\"\n\nThe object which had caught his eye was a small dog lash hung on\none corner of the bed. The lash, however, was curled upon itself\nand tied so as to make a loop of whipcord.\n\n\"What do you make of that, Watson?\"\n\n\"It's a common enough lash. But I don't know why it should be\ntied.\"\n\n\"That is not quite so common, is it? Ah, me! it's a wicked world,\nand when a clever man turns his brains to crime it is the worst\nof all. I think that I have seen enough now, Miss Stoner, and\nwith your permission we shall walk out upon the lawn.\"\n\nI had never seen my friend's face so grim or his brow so dark as\nit was when we turned from the scene of this investigation. We\nhad walked several times up and down the lawn, neither Miss\nStoner nor myself liking to break in upon his thoughts before he\nroused himself from his reverie.\n\n\"It is very essential, Miss Stoner,\" said he, \"that you should\nabsolutely follow my advice in every respect.\"\n\n\"I shall most certainly do so.\"\n\n\"The matter is too serious for any hesitation. Your life may\ndepend upon your compliance.\"\n\n\"I assure you that I am in your hands.\"\n\n\"In the first place, both my friend and I must spend the night in\nyour room.\"\n\nBoth Miss Stoner and I gazed at him in astonishment.\n\n\"Yes, it must be so. Let me explain. I believe that that is the\nvillage inn over there?\"\n\n\"Yes, that is the Crown.\"\n\n\"Very good. Your windows would be visible from there?\"\n\n\"Certainly.\"\n\n\"You must confine yourself to your room, on pretence of a\nheadache, when your stepfather comes back. Then when you hear him\nretire for the night, you must open the shutters of your window,\nundo the hasp, put your lamp there as a signal to us, and then\nwithdraw quietly with everything which you are likely to want\ninto the room which you used to occupy. I have no doubt that, in\nspite of the repairs, you could manage there for one night.\"\n\n\"Oh, yes, easily.\"\n\n\"The rest you will leave in our hands.\"\n\n\"But what will you do?\"\n\n\"We shall spend the night in your room, and we shall investigate\nthe cause of this noise which has disturbed you.\"\n\n\"I believe, Mr. Holmes, that you have already made up your mind,\"\nsaid Miss Stoner, laying her hand upon my companion's sleeve.\n\n\"Perhaps I have.\"\n\n\"Then, for pity's sake, tell me what was the cause of my sister's\ndeath.\"\n\n\"I should prefer to have clearer proofs before I speak.\"\n\n\"You can at least tell me whether my own thought is correct, and\nif she died from some sudden fright.\"\n\n\"No, I do not think so. I think that there was probably some more\ntangible cause. And now, Miss Stoner, we must leave you for if\nDr. Roylott returned and saw us our journey would be in vain.\nGood-bye, and be brave, for if you will do what I have told you,\nyou may rest assured that we shall soon drive away the dangers\nthat threaten you.\"\n\nSherlock Holmes and I had no difficulty in engaging a bedroom and\nsitting-room at the Crown Inn. They were on the upper floor, and\nfrom our window we could command a view of the avenue gate, and\nof the inhabited wing of Stoke Moran Manor House. At dusk we saw\nDr. Grimesby Roylott drive past, his huge form looming up beside\nthe little figure of the lad who drove him. The boy had some\nslight difficulty in undoing the heavy iron gates, and we heard\nthe hoarse roar of the doctor's voice and saw the fury with which\nhe shook his clinched fists at him. The trap drove on, and a few\nminutes later we saw a sudden light spring up among the trees as\nthe lamp was lit in one of the sitting-rooms.\n\n\"Do you know, Watson,\" said Holmes as we sat together in the\ngathering darkness, \"I have really some scruples as to taking you\nto-night. There is a distinct element of danger.\"\n\n\"Can I be of assistance?\"\n\n\"Your presence might be invaluable.\"\n\n\"Then I shall certainly come.\"\n\n\"It is very kind of you.\"\n\n\"You speak of danger. You have evidently seen more in these rooms\nthan was visible to me.\"\n\n\"No, but I fancy that I may have deduced a little more. I imagine\nthat you saw all that I did.\"\n\n\"I saw nothing remarkable save the bell-rope, and what purpose\nthat could answer I confess is more than I can imagine.\"\n\n\"You saw the ventilator, too?\"\n\n\"Yes, but I do not think that it is such a very unusual thing to\nhave a small opening between two rooms. It was so small that a\nrat could hardly pass through.\"\n\n\"I knew that we should find a ventilator before ever we came to\nStoke Moran.\"\n\n\"My dear Holmes!\"\n\n\"Oh, yes, I did. You remember in her statement she said that her\nsister could smell Dr. Roylott's cigar. Now, of course that\nsuggested at once that there must be a communication between the\ntwo rooms. It could only be a small one, or it would have been\nremarked upon at the coroner's inquiry. I deduced a ventilator.\"\n\n\"But what harm can there be in that?\"\n\n\"Well, there is at least a curious coincidence of dates. A\nventilator is made, a cord is hung, and a lady who sleeps in the\nbed dies. Does not that strike you?\"\n\n\"I cannot as yet see any connection.\"\n\n\"Did you observe anything very peculiar about that bed?\"\n\n\"No.\"\n\n\"It was clamped to the floor. Did you ever see a bed fastened\nlike that before?\"\n\n\"I cannot say that I have.\"\n\n\"The lady could not move her bed. It must always be in the same\nrelative position to the ventilator and to the rope--or so we may\ncall it, since it was clearly never meant for a bell-pull.\"\n\n\"Holmes,\" I cried, \"I seem to see dimly what you are hinting at.\nWe are only just in time to prevent some subtle and horrible\ncrime.\"\n\n\"Subtle enough and horrible enough. When a doctor does go wrong\nhe is the first of criminals. He has nerve and he has knowledge.\nPalmer and Pritchard were among the heads of their profession.\nThis man strikes even deeper, but I think, Watson, that we shall\nbe able to strike deeper still. But we shall have horrors enough\nbefore the night is over; for goodness' sake let us have a quiet\npipe and turn our minds for a few hours to something more\ncheerful.\"\n\n\nAbout nine o'clock the light among the trees was extinguished,\nand all was dark in the direction of the Manor House. Two hours\npassed slowly away, and then, suddenly, just at the stroke of\neleven, a single bright light shone out right in front of us.\n\n\"That is our signal,\" said Holmes, springing to his feet; \"it\ncomes from the middle window.\"\n\nAs we passed out he exchanged a few words with the landlord,\nexplaining that we were going on a late visit to an acquaintance,\nand that it was possible that we might spend the night there. A\nmoment later we were out on the dark road, a chill wind blowing\nin our faces, and one yellow light twinkling in front of us\nthrough the gloom to guide us on our sombre errand.\n\nThere was little difficulty in entering the grounds, for\nunrepaired breaches gaped in the old park wall. Making our way\namong the trees, we reached the lawn, crossed it, and were about\nto enter through the window when out from a clump of laurel\nbushes there darted what seemed to be a hideous and distorted\nchild, who threw itself upon the grass with writhing limbs and\nthen ran swiftly across the lawn into the darkness.\n\n\"My God!\" I whispered; \"did you see it?\"\n\nHolmes was for the moment as startled as I. His hand closed like\na vice upon my wrist in his agitation. Then he broke into a low\nlaugh and put his lips to my ear.\n\n\"It is a nice household,\" he murmured. \"That is the baboon.\"\n\nI had forgotten the strange pets which the doctor affected. There\nwas a cheetah, too; perhaps we might find it upon our shoulders\nat any moment. I confess that I felt easier in my mind when,\nafter following Holmes' example and slipping off my shoes, I\nfound myself inside the bedroom. My companion noiselessly closed\nthe shutters, moved the lamp onto the table, and cast his eyes\nround the room. All was as we had seen it in the daytime. Then\ncreeping up to me and making a trumpet of his hand, he whispered\ninto my ear again so gently that it was all that I could do to\ndistinguish the words:\n\n\"The least sound would be fatal to our plans.\"\n\nI nodded to show that I had heard.\n\n\"We must sit without light. He would see it through the\nventilator.\"\n\nI nodded again.\n\n\"Do not go asleep; your very life may depend upon it. Have your\npistol ready in case we should need it. I will sit on the side of\nthe bed, and you in that chair.\"\n\nI took out my revolver and laid it on the corner of the table.\n\nHolmes had brought up a long thin cane, and this he placed upon\nthe bed beside him. By it he laid the box of matches and the\nstump of a candle. Then he turned down the lamp, and we were left\nin darkness.\n\nHow shall I ever forget that dreadful vigil? I could not hear a\nsound, not even the drawing of a breath, and yet I knew that my\ncompanion sat open-eyed, within a few feet of me, in the same\nstate of nervous tension in which I was myself. The shutters cut\noff the least ray of light, and we waited in absolute darkness.\n\nFrom outside came the occasional cry of a night-bird, and once at\nour very window a long drawn catlike whine, which told us that\nthe cheetah was indeed at liberty. Far away we could hear the\ndeep tones of the parish clock, which boomed out every quarter of\nan hour. How long they seemed, those quarters! Twelve struck, and\none and two and three, and still we sat waiting silently for\nwhatever might befall.\n\nSuddenly there was the momentary gleam of a light up in the\ndirection of the ventilator, which vanished immediately, but was\nsucceeded by a strong smell of burning oil and heated metal.\nSomeone in the next room had lit a dark-lantern. I heard a gentle\nsound of movement, and then all was silent once more, though the\nsmell grew stronger. For half an hour I sat with straining ears.\nThen suddenly another sound became audible--a very gentle,\nsoothing sound, like that of a small jet of steam escaping\ncontinually from a kettle. The instant that we heard it, Holmes\nsprang from the bed, struck a match, and lashed furiously with\nhis cane at the bell-pull.\n\n\"You see it, Watson?\" he yelled. \"You see it?\"\n\nBut I saw nothing. At the moment when Holmes struck the light I\nheard a low, clear whistle, but the sudden glare flashing into my\nweary eyes made it impossible for me to tell what it was at which\nmy friend lashed so savagely. I could, however, see that his face\nwas deadly pale and filled with horror and loathing. He had\nceased to strike and was gazing up at the ventilator when\nsuddenly there broke from the silence of the night the most\nhorrible cry to which I have ever listened. It swelled up louder\nand louder, a hoarse yell of pain and fear and anger all mingled\nin the one dreadful shriek. They say that away down in the\nvillage, and even in the distant parsonage, that cry raised the\nsleepers from their beds. It struck cold to our hearts, and I\nstood gazing at Holmes, and he at me, until the last echoes of it\nhad died away into the silence from which it rose.\n\n\"What can it mean?\" I gasped.\n\n\"It means that it is all over,\" Holmes answered. \"And perhaps,\nafter all, it is for the best. Take your pistol, and we will\nenter Dr. Roylott's room.\"\n\nWith a grave face he lit the lamp and led the way down the\ncorridor. Twice he struck at the chamber door without any reply\nfrom within. Then he turned the handle and entered, I at his\nheels, with the cocked pistol in my hand.\n\nIt was a singular sight which met our eyes. On the table stood a\ndark-lantern with the shutter half open, throwing a brilliant\nbeam of light upon the iron safe, the door of which was ajar.\nBeside this table, on the wooden chair, sat Dr. Grimesby Roylott\nclad in a long grey dressing-gown, his bare ankles protruding\nbeneath, and his feet thrust into red heelless Turkish slippers.\nAcross his lap lay the short stock with the long lash which we\nhad noticed during the day. His chin was cocked upward and his\neyes were fixed in a dreadful, rigid stare at the corner of the\nceiling. Round his brow he had a peculiar yellow band, with\nbrownish speckles, which seemed to be bound tightly round his\nhead. As we entered he made neither sound nor motion.\n\n\"The band! the speckled band!\" whispered Holmes.\n\nI took a step forward. In an instant his strange headgear began\nto move, and there reared itself from among his hair the squat\ndiamond-shaped head and puffed neck of a loathsome serpent.\n\n\"It is a swamp adder!\" cried Holmes; \"the deadliest snake in\nIndia. He has died within ten seconds of being bitten. Violence\ndoes, in truth, recoil upon the violent, and the schemer falls\ninto the pit which he digs for another. Let us thrust this\ncreature back into its den, and we can then remove Miss Stoner to\nsome place of shelter and let the county police know what has\nhappened.\"\n\nAs he spoke he drew the dog-whip swiftly from the dead man's lap,\nand throwing the noose round the reptile's neck he drew it from\nits horrid perch and, carrying it at arm's length, threw it into\nthe iron safe, which he closed upon it.\n\nSuch are the true facts of the death of Dr. Grimesby Roylott, of\nStoke Moran. It is not necessary that I should prolong a\nnarrative which has already run to too great a length by telling\nhow we broke the sad news to the terrified girl, how we conveyed\nher by the morning train to the care of her good aunt at Harrow,\nof how the slow process of official inquiry came to the\nconclusion that the doctor met his fate while indiscreetly\nplaying with a dangerous pet. The little which I had yet to learn\nof the case was told me by Sherlock Holmes as we travelled back\nnext day.\n\n\"I had,\" said he, \"come to an entirely erroneous conclusion which\nshows, my dear Watson, how dangerous it always is to reason from\ninsufficient data. The presence of the gipsies, and the use of\nthe word 'band,' which was used by the poor girl, no doubt, to\nexplain the appearance which she had caught a hurried glimpse of\nby the light of her match, were sufficient to put me upon an\nentirely wrong scent. I can only claim the merit that I instantly\nreconsidered my position when, however, it became clear to me\nthat whatever danger threatened an occupant of the room could not\ncome either from the window or the door. My attention was\nspeedily drawn, as I have already remarked to you, to this\nventilator, and to the bell-rope which hung down to the bed. The\ndiscovery that this was a dummy, and that the bed was clamped to\nthe floor, instantly gave rise to the suspicion that the rope was\nthere as a bridge for something passing through the hole and\ncoming to the bed. The idea of a snake instantly occurred to me,\nand when I coupled it with my knowledge that the doctor was\nfurnished with a supply of creatures from India, I felt that I\nwas probably on the right track. The idea of using a form of\npoison which could not possibly be discovered by any chemical\ntest was just such a one as would occur to a clever and ruthless\nman who had had an Eastern training. The rapidity with which such\na poison would take effect would also, from his point of view, be\nan advantage. It would be a sharp-eyed coroner, indeed, who could\ndistinguish the two little dark punctures which would show where\nthe poison fangs had done their work. Then I thought of the\nwhistle. Of course he must recall the snake before the morning\nlight revealed it to the victim. He had trained it, probably by\nthe use of the milk which we saw, to return to him when summoned.\nHe would put it through this ventilator at the hour that he\nthought best, with the certainty that it would crawl down the\nrope and land on the bed. It might or might not bite the\noccupant, perhaps she might escape every night for a week, but\nsooner or later she must fall a victim.\n\n\"I had come to these conclusions before ever I had entered his\nroom. An inspection of his chair showed me that he had been in\nthe habit of standing on it, which of course would be necessary\nin order that he should reach the ventilator. The sight of the\nsafe, the saucer of milk, and the loop of whipcord were enough to\nfinally dispel any doubts which may have remained. The metallic\nclang heard by Miss Stoner was obviously caused by her stepfather\nhastily closing the door of his safe upon its terrible occupant.\nHaving once made up my mind, you know the steps which I took in\norder to put the matter to the proof. I heard the creature hiss\nas I have no doubt that you did also, and I instantly lit the\nlight and attacked it.\"\n\n\"With the result of driving it through the ventilator.\"\n\n\"And also with the result of causing it to turn upon its master\nat the other side. Some of the blows of my cane came home and\nroused its snakish temper, so that it flew upon the first person\nit saw. In this way I am no doubt indirectly responsible for Dr.\nGrimesby Roylott's death, and I cannot say that it is likely to\nweigh very heavily upon my conscience.\"\n\n\n\nIX. THE ADVENTURE OF THE ENGINEER'S THUMB\n\nOf all the problems which have been submitted to my friend, Mr.\nSherlock Holmes, for solution during the years of our intimacy,\nthere were only two which I was the means of introducing to his\nnotice--that of Mr. Hatherley's thumb, and that of Colonel\nWarburton's madness. Of these the latter may have afforded a\nfiner field for an acute and original observer, but the other was\nso strange in its inception and so dramatic in its details that\nit may be the more worthy of being placed upon record, even if it\ngave my friend fewer openings for those deductive methods of\nreasoning by which he achieved such remarkable results. The story\nhas, I believe, been told more than once in the newspapers, but,\nlike all such narratives, its effect is much less striking when\nset forth en bloc in a single half-column of print than when the\nfacts slowly evolve before your own eyes, and the mystery clears\ngradually away as each new discovery furnishes a step which leads\non to the complete truth. At the time the circumstances made a\ndeep impression upon me, and the lapse of two years has hardly\nserved to weaken the effect.\n\nIt was in the summer of '89, not long after my marriage, that the\nevents occurred which I am now about to summarise. I had returned\nto civil practice and had finally abandoned Holmes in his Baker\nStreet rooms, although I continually visited him and occasionally\neven persuaded him to forgo his Bohemian habits so far as to come\nand visit us. My practice had steadily increased, and as I\nhappened to live at no very great distance from Paddington\nStation, I got a few patients from among the officials. One of\nthese, whom I had cured of a painful and lingering disease, was\nnever weary of advertising my virtues and of endeavouring to send\nme on every sufferer over whom he might have any influence.\n\nOne morning, at a little before seven o'clock, I was awakened by\nthe maid tapping at the door to announce that two men had come\nfrom Paddington and were waiting in the consulting-room. I\ndressed hurriedly, for I knew by experience that railway cases\nwere seldom trivial, and hastened downstairs. As I descended, my\nold ally, the guard, came out of the room and closed the door\ntightly behind him.\n\n\"I've got him here,\" he whispered, jerking his thumb over his\nshoulder; \"he's all right.\"\n\n\"What is it, then?\" I asked, for his manner suggested that it was\nsome strange creature which he had caged up in my room.\n\n\"It's a new patient,\" he whispered. \"I thought I'd bring him\nround myself; then he couldn't slip away. There he is, all safe\nand sound. I must go now, Doctor; I have my dooties, just the\nsame as you.\" And off he went, this trusty tout, without even\ngiving me time to thank him.\n\nI entered my consulting-room and found a gentleman seated by the\ntable. He was quietly dressed in a suit of heather tweed with a\nsoft cloth cap which he had laid down upon my books. Round one of\nhis hands he had a handkerchief wrapped, which was mottled all\nover with bloodstains. He was young, not more than\nfive-and-twenty, I should say, with a strong, masculine face; but\nhe was exceedingly pale and gave me the impression of a man who\nwas suffering from some strong agitation, which it took all his\nstrength of mind to control.\n\n\"I am sorry to knock you up so early, Doctor,\" said he, \"but I\nhave had a very serious accident during the night. I came in by\ntrain this morning, and on inquiring at Paddington as to where I\nmight find a doctor, a worthy fellow very kindly escorted me\nhere. I gave the maid a card, but I see that she has left it upon\nthe side-table.\"\n\nI took it up and glanced at it. \"Mr. Victor Hatherley, hydraulic\nengineer, 16A, Victoria Street (3rd floor).\" That was the name,\nstyle, and abode of my morning visitor. \"I regret that I have\nkept you waiting,\" said I, sitting down in my library-chair. \"You\nare fresh from a night journey, I understand, which is in itself\na monotonous occupation.\"\n\n\"Oh, my night could not be called monotonous,\" said he, and\nlaughed. He laughed very heartily, with a high, ringing note,\nleaning back in his chair and shaking his sides. All my medical\ninstincts rose up against that laugh.\n\n\"Stop it!\" I cried; \"pull yourself together!\" and I poured out\nsome water from a caraffe.\n\nIt was useless, however. He was off in one of those hysterical\noutbursts which come upon a strong nature when some great crisis\nis over and gone. Presently he came to himself once more, very\nweary and pale-looking.\n\n\"I have been making a fool of myself,\" he gasped.\n\n\"Not at all. Drink this.\" I dashed some brandy into the water,\nand the colour began to come back to his bloodless cheeks.\n\n\"That's better!\" said he. \"And now, Doctor, perhaps you would\nkindly attend to my thumb, or rather to the place where my thumb\nused to be.\"\n\nHe unwound the handkerchief and held out his hand. It gave even\nmy hardened nerves a shudder to look at it. There were four\nprotruding fingers and a horrid red, spongy surface where the\nthumb should have been. It had been hacked or torn right out from\nthe roots.\n\n\"Good heavens!\" I cried, \"this is a terrible injury. It must have\nbled considerably.\"\n\n\"Yes, it did. I fainted when it was done, and I think that I must\nhave been senseless for a long time. When I came to I found that\nit was still bleeding, so I tied one end of my handkerchief very\ntightly round the wrist and braced it up with a twig.\"\n\n\"Excellent! You should have been a surgeon.\"\n\n\"It is a question of hydraulics, you see, and came within my own\nprovince.\"\n\n\"This has been done,\" said I, examining the wound, \"by a very\nheavy and sharp instrument.\"\n\n\"A thing like a cleaver,\" said he.\n\n\"An accident, I presume?\"\n\n\"By no means.\"\n\n\"What! a murderous attack?\"\n\n\"Very murderous indeed.\"\n\n\"You horrify me.\"\n\nI sponged the wound, cleaned it, dressed it, and finally covered\nit over with cotton wadding and carbolised bandages. He lay back\nwithout wincing, though he bit his lip from time to time.\n\n\"How is that?\" I asked when I had finished.\n\n\"Capital! Between your brandy and your bandage, I feel a new man.\nI was very weak, but I have had a good deal to go through.\"\n\n\"Perhaps you had better not speak of the matter. It is evidently\ntrying to your nerves.\"\n\n\"Oh, no, not now. I shall have to tell my tale to the police;\nbut, between ourselves, if it were not for the convincing\nevidence of this wound of mine, I should be surprised if they\nbelieved my statement, for it is a very extraordinary one, and I\nhave not much in the way of proof with which to back it up; and,\neven if they believe me, the clues which I can give them are so\nvague that it is a question whether justice will be done.\"\n\n\"Ha!\" cried I, \"if it is anything in the nature of a problem\nwhich you desire to see solved, I should strongly recommend you\nto come to my friend, Mr. Sherlock Holmes, before you go to the\nofficial police.\"\n\n\"Oh, I have heard of that fellow,\" answered my visitor, \"and I\nshould be very glad if he would take the matter up, though of\ncourse I must use the official police as well. Would you give me\nan introduction to him?\"\n\n\"I'll do better. I'll take you round to him myself.\"\n\n\"I should be immensely obliged to you.\"\n\n\"We'll call a cab and go together. We shall just be in time to\nhave a little breakfast with him. Do you feel equal to it?\"\n\n\"Yes; I shall not feel easy until I have told my story.\"\n\n\"Then my servant will call a cab, and I shall be with you in an\ninstant.\" I rushed upstairs, explained the matter shortly to my\nwife, and in five minutes was inside a hansom, driving with my\nnew acquaintance to Baker Street.\n\nSherlock Holmes was, as I expected, lounging about his\nsitting-room in his dressing-gown, reading the agony column of The\nTimes and smoking his before-breakfast pipe, which was composed\nof all the plugs and dottles left from his smokes of the day\nbefore, all carefully dried and collected on the corner of the\nmantelpiece. He received us in his quietly genial fashion,\nordered fresh rashers and eggs, and joined us in a hearty meal.\nWhen it was concluded he settled our new acquaintance upon the\nsofa, placed a pillow beneath his head, and laid a glass of\nbrandy and water within his reach.\n\n\"It is easy to see that your experience has been no common one,\nMr. Hatherley,\" said he. \"Pray, lie down there and make yourself\nabsolutely at home. Tell us what you can, but stop when you are\ntired and keep up your strength with a little stimulant.\"\n\n\"Thank you,\" said my patient, \"but I have felt another man since\nthe doctor bandaged me, and I think that your breakfast has\ncompleted the cure. I shall take up as little of your valuable\ntime as possible, so I shall start at once upon my peculiar\nexperiences.\"\n\nHolmes sat in his big armchair with the weary, heavy-lidded\nexpression which veiled his keen and eager nature, while I sat\nopposite to him, and we listened in silence to the strange story\nwhich our visitor detailed to us.\n\n\"You must know,\" said he, \"that I am an orphan and a bachelor,\nresiding alone in lodgings in London. By profession I am a\nhydraulic engineer, and I have had considerable experience of my\nwork during the seven years that I was apprenticed to Venner &\nMatheson, the well-known firm, of Greenwich. Two years ago,\nhaving served my time, and having also come into a fair sum of\nmoney through my poor father's death, I determined to start in\nbusiness for myself and took professional chambers in Victoria\nStreet.\n\n\"I suppose that everyone finds his first independent start in\nbusiness a dreary experience. To me it has been exceptionally so.\nDuring two years I have had three consultations and one small\njob, and that is absolutely all that my profession has brought\nme. My gross takings amount to 27 pounds 10s. Every day, from\nnine in the morning until four in the afternoon, I waited in my\nlittle den, until at last my heart began to sink, and I came to\nbelieve that I should never have any practice at all.\n\n\"Yesterday, however, just as I was thinking of leaving the\noffice, my clerk entered to say there was a gentleman waiting who\nwished to see me upon business. He brought up a card, too, with\nthe name of 'Colonel Lysander Stark' engraved upon it. Close at\nhis heels came the colonel himself, a man rather over the middle\nsize, but of an exceeding thinness. I do not think that I have\never seen so thin a man. His whole face sharpened away into nose\nand chin, and the skin of his cheeks was drawn quite tense over\nhis outstanding bones. Yet this emaciation seemed to be his\nnatural habit, and due to no disease, for his eye was bright, his\nstep brisk, and his bearing assured. He was plainly but neatly\ndressed, and his age, I should judge, would be nearer forty than\nthirty.\n\n\"'Mr. Hatherley?' said he, with something of a German accent.\n'You have been recommended to me, Mr. Hatherley, as being a man\nwho is not only proficient in his profession but is also discreet\nand capable of preserving a secret.'\n\n\"I bowed, feeling as flattered as any young man would at such an\naddress. 'May I ask who it was who gave me so good a character?'\n\n\"'Well, perhaps it is better that I should not tell you that just\nat this moment. I have it from the same source that you are both\nan orphan and a bachelor and are residing alone in London.'\n\n\"'That is quite correct,' I answered; 'but you will excuse me if\nI say that I cannot see how all this bears upon my professional\nqualifications. I understand that it was on a professional matter\nthat you wished to speak to me?'\n\n\"'Undoubtedly so. But you will find that all I say is really to\nthe point. I have a professional commission for you, but absolute\nsecrecy is quite essential--absolute secrecy, you understand, and\nof course we may expect that more from a man who is alone than\nfrom one who lives in the bosom of his family.'\n\n\"'If I promise to keep a secret,' said I, 'you may absolutely\ndepend upon my doing so.'\n\n\"He looked very hard at me as I spoke, and it seemed to me that I\nhad never seen so suspicious and questioning an eye.\n\n\"'Do you promise, then?' said he at last.\n\n\"'Yes, I promise.'\n\n\"'Absolute and complete silence before, during, and after? No\nreference to the matter at all, either in word or writing?'\n\n\"'I have already given you my word.'\n\n\"'Very good.' He suddenly sprang up, and darting like lightning\nacross the room he flung open the door. The passage outside was\nempty.\n\n\"'That's all right,' said he, coming back. 'I know that clerks are\nsometimes curious as to their master's affairs. Now we can talk\nin safety.' He drew up his chair very close to mine and began to\nstare at me again with the same questioning and thoughtful look.\n\n\"A feeling of repulsion, and of something akin to fear had begun\nto rise within me at the strange antics of this fleshless man.\nEven my dread of losing a client could not restrain me from\nshowing my impatience.\n\n\"'I beg that you will state your business, sir,' said I; 'my time\nis of value.' Heaven forgive me for that last sentence, but the\nwords came to my lips.\n\n\"'How would fifty guineas for a night's work suit you?' he asked.\n\n\"'Most admirably.'\n\n\"'I say a night's work, but an hour's would be nearer the mark. I\nsimply want your opinion about a hydraulic stamping machine which\nhas got out of gear. If you show us what is wrong we shall soon\nset it right ourselves. What do you think of such a commission as\nthat?'\n\n\"'The work appears to be light and the pay munificent.'\n\n\"'Precisely so. We shall want you to come to-night by the last\ntrain.'\n\n\"'Where to?'\n\n\"'To Eyford, in Berkshire. It is a little place near the borders\nof Oxfordshire, and within seven miles of Reading. There is a\ntrain from Paddington which would bring you there at about\n11:15.'\n\n\"'Very good.'\n\n\"'I shall come down in a carriage to meet you.'\n\n\"'There is a drive, then?'\n\n\"'Yes, our little place is quite out in the country. It is a good\nseven miles from Eyford Station.'\n\n\"'Then we can hardly get there before midnight. I suppose there\nwould be no chance of a train back. I should be compelled to stop\nthe night.'\n\n\"'Yes, we could easily give you a shake-down.'\n\n\"'That is very awkward. Could I not come at some more convenient\nhour?'\n\n\"'We have judged it best that you should come late. It is to\nrecompense you for any inconvenience that we are paying to you, a\nyoung and unknown man, a fee which would buy an opinion from the\nvery heads of your profession. Still, of course, if you would\nlike to draw out of the business, there is plenty of time to do\nso.'\n\n\"I thought of the fifty guineas, and of how very useful they\nwould be to me. 'Not at all,' said I, 'I shall be very happy to\naccommodate myself to your wishes. I should like, however, to\nunderstand a little more clearly what it is that you wish me to\ndo.'\n\n\"'Quite so. It is very natural that the pledge of secrecy which\nwe have exacted from you should have aroused your curiosity. I\nhave no wish to commit you to anything without your having it all\nlaid before you. I suppose that we are absolutely safe from\neavesdroppers?'\n\n\"'Entirely.'\n\n\"'Then the matter stands thus. You are probably aware that\nfuller's-earth is a valuable product, and that it is only found\nin one or two places in England?'\n\n\"'I have heard so.'\n\n\"'Some little time ago I bought a small place--a very small\nplace--within ten miles of Reading. I was fortunate enough to\ndiscover that there was a deposit of fuller's-earth in one of my\nfields. On examining it, however, I found that this deposit was a\ncomparatively small one, and that it formed a link between two\nvery much larger ones upon the right and left--both of them,\nhowever, in the grounds of my neighbours. These good people were\nabsolutely ignorant that their land contained that which was\nquite as valuable as a gold-mine. Naturally, it was to my\ninterest to buy their land before they discovered its true value,\nbut unfortunately I had no capital by which I could do this. I\ntook a few of my friends into the secret, however, and they\nsuggested that we should quietly and secretly work our own little\ndeposit and that in this way we should earn the money which would\nenable us to buy the neighbouring fields. This we have now been\ndoing for some time, and in order to help us in our operations we\nerected a hydraulic press. This press, as I have already\nexplained, has got out of order, and we wish your advice upon the\nsubject. We guard our secret very jealously, however, and if it\nonce became known that we had hydraulic engineers coming to our\nlittle house, it would soon rouse inquiry, and then, if the facts\ncame out, it would be good-bye to any chance of getting these\nfields and carrying out our plans. That is why I have made you\npromise me that you will not tell a human being that you are\ngoing to Eyford to-night. I hope that I make it all plain?'\n\n\"'I quite follow you,' said I. 'The only point which I could not\nquite understand was what use you could make of a hydraulic press\nin excavating fuller's-earth, which, as I understand, is dug out\nlike gravel from a pit.'\n\n\"'Ah!' said he carelessly, 'we have our own process. We compress\nthe earth into bricks, so as to remove them without revealing\nwhat they are. But that is a mere detail. I have taken you fully\ninto my confidence now, Mr. Hatherley, and I have shown you how I\ntrust you.' He rose as he spoke. 'I shall expect you, then, at\nEyford at 11:15.'\n\n\"'I shall certainly be there.'\n\n\"'And not a word to a soul.' He looked at me with a last long,\nquestioning gaze, and then, pressing my hand in a cold, dank\ngrasp, he hurried from the room.\n\n\"Well, when I came to think it all over in cool blood I was very\nmuch astonished, as you may both think, at this sudden commission\nwhich had been intrusted to me. On the one hand, of course, I was\nglad, for the fee was at least tenfold what I should have asked\nhad I set a price upon my own services, and it was possible that\nthis order might lead to other ones. On the other hand, the face\nand manner of my patron had made an unpleasant impression upon\nme, and I could not think that his explanation of the\nfuller's-earth was sufficient to explain the necessity for my\ncoming at midnight, and his extreme anxiety lest I should tell\nanyone of my errand. However, I threw all fears to the winds, ate\na hearty supper, drove to Paddington, and started off, having\nobeyed to the letter the injunction as to holding my tongue.\n\n\"At Reading I had to change not only my carriage but my station.\nHowever, I was in time for the last train to Eyford, and I\nreached the little dim-lit station after eleven o'clock. I was the\nonly passenger who got out there, and there was no one upon the\nplatform save a single sleepy porter with a lantern. As I passed\nout through the wicket gate, however, I found my acquaintance of\nthe morning waiting in the shadow upon the other side. Without a\nword he grasped my arm and hurried me into a carriage, the door\nof which was standing open. He drew up the windows on either\nside, tapped on the wood-work, and away we went as fast as the\nhorse could go.\"\n\n\"One horse?\" interjected Holmes.\n\n\"Yes, only one.\"\n\n\"Did you observe the colour?\"\n\n\"Yes, I saw it by the side-lights when I was stepping into the\ncarriage. It was a chestnut.\"\n\n\"Tired-looking or fresh?\"\n\n\"Oh, fresh and glossy.\"\n\n\"Thank you. I am sorry to have interrupted you. Pray continue\nyour most interesting statement.\"\n\n\"Away we went then, and we drove for at least an hour. Colonel\nLysander Stark had said that it was only seven miles, but I\nshould think, from the rate that we seemed to go, and from the\ntime that we took, that it must have been nearer twelve. He sat\nat my side in silence all the time, and I was aware, more than\nonce when I glanced in his direction, that he was looking at me\nwith great intensity. The country roads seem to be not very good\nin that part of the world, for we lurched and jolted terribly. I\ntried to look out of the windows to see something of where we\nwere, but they were made of frosted glass, and I could make out\nnothing save the occasional bright blur of a passing light. Now\nand then I hazarded some remark to break the monotony of the\njourney, but the colonel answered only in monosyllables, and the\nconversation soon flagged. At last, however, the bumping of the\nroad was exchanged for the crisp smoothness of a gravel-drive,\nand the carriage came to a stand. Colonel Lysander Stark sprang\nout, and, as I followed after him, pulled me swiftly into a porch\nwhich gaped in front of us. We stepped, as it were, right out of\nthe carriage and into the hall, so that I failed to catch the\nmost fleeting glance of the front of the house. The instant that\nI had crossed the threshold the door slammed heavily behind us,\nand I heard faintly the rattle of the wheels as the carriage\ndrove away.\n\n\"It was pitch dark inside the house, and the colonel fumbled\nabout looking for matches and muttering under his breath.\nSuddenly a door opened at the other end of the passage, and a\nlong, golden bar of light shot out in our direction. It grew\nbroader, and a woman appeared with a lamp in her hand, which she\nheld above her head, pushing her face forward and peering at us.\nI could see that she was pretty, and from the gloss with which\nthe light shone upon her dark dress I knew that it was a rich\nmaterial. She spoke a few words in a foreign tongue in a tone as\nthough asking a question, and when my companion answered in a\ngruff monosyllable she gave such a start that the lamp nearly\nfell from her hand. Colonel Stark went up to her, whispered\nsomething in her ear, and then, pushing her back into the room\nfrom whence she had come, he walked towards me again with the\nlamp in his hand.\n\n\"'Perhaps you will have the kindness to wait in this room for a\nfew minutes,' said he, throwing open another door. It was a\nquiet, little, plainly furnished room, with a round table in the\ncentre, on which several German books were scattered. Colonel\nStark laid down the lamp on the top of a harmonium beside the\ndoor. 'I shall not keep you waiting an instant,' said he, and\nvanished into the darkness.\n\n\"I glanced at the books upon the table, and in spite of my\nignorance of German I could see that two of them were treatises\non science, the others being volumes of poetry. Then I walked\nacross to the window, hoping that I might catch some glimpse of\nthe country-side, but an oak shutter, heavily barred, was folded\nacross it. It was a wonderfully silent house. There was an old\nclock ticking loudly somewhere in the passage, but otherwise\neverything was deadly still. A vague feeling of uneasiness began\nto steal over me. Who were these German people, and what were\nthey doing living in this strange, out-of-the-way place? And\nwhere was the place? I was ten miles or so from Eyford, that was\nall I knew, but whether north, south, east, or west I had no\nidea. For that matter, Reading, and possibly other large towns,\nwere within that radius, so the place might not be so secluded,\nafter all. Yet it was quite certain, from the absolute stillness,\nthat we were in the country. I paced up and down the room,\nhumming a tune under my breath to keep up my spirits and feeling\nthat I was thoroughly earning my fifty-guinea fee.\n\n\"Suddenly, without any preliminary sound in the midst of the\nutter stillness, the door of my room swung slowly open. The woman\nwas standing in the aperture, the darkness of the hall behind\nher, the yellow light from my lamp beating upon her eager and\nbeautiful face. I could see at a glance that she was sick with\nfear, and the sight sent a chill to my own heart. She held up one\nshaking finger to warn me to be silent, and she shot a few\nwhispered words of broken English at me, her eyes glancing back,\nlike those of a frightened horse, into the gloom behind her.\n\n\"'I would go,' said she, trying hard, as it seemed to me, to\nspeak calmly; 'I would go. I should not stay here. There is no\ngood for you to do.'\n\n\"'But, madam,' said I, 'I have not yet done what I came for. I\ncannot possibly leave until I have seen the machine.'\n\n\"'It is not worth your while to wait,' she went on. 'You can pass\nthrough the door; no one hinders.' And then, seeing that I smiled\nand shook my head, she suddenly threw aside her constraint and\nmade a step forward, with her hands wrung together. 'For the love\nof Heaven!' she whispered, 'get away from here before it is too\nlate!'\n\n\"But I am somewhat headstrong by nature, and the more ready to\nengage in an affair when there is some obstacle in the way. I\nthought of my fifty-guinea fee, of my wearisome journey, and of\nthe unpleasant night which seemed to be before me. Was it all to\ngo for nothing? Why should I slink away without having carried\nout my commission, and without the payment which was my due? This\nwoman might, for all I knew, be a monomaniac. With a stout\nbearing, therefore, though her manner had shaken me more than I\ncared to confess, I still shook my head and declared my intention\nof remaining where I was. She was about to renew her entreaties\nwhen a door slammed overhead, and the sound of several footsteps\nwas heard upon the stairs. She listened for an instant, threw up\nher hands with a despairing gesture, and vanished as suddenly and\nas noiselessly as she had come.\n\n\"The newcomers were Colonel Lysander Stark and a short thick man\nwith a chinchilla beard growing out of the creases of his double\nchin, who was introduced to me as Mr. Ferguson.\n\n\"'This is my secretary and manager,' said the colonel. 'By the\nway, I was under the impression that I left this door shut just\nnow. I fear that you have felt the draught.'\n\n\"'On the contrary,' said I, 'I opened the door myself because I\nfelt the room to be a little close.'\n\n\"He shot one of his suspicious looks at me. 'Perhaps we had\nbetter proceed to business, then,' said he. 'Mr. Ferguson and I\nwill take you up to see the machine.'\n\n\"'I had better put my hat on, I suppose.'\n\n\"'Oh, no, it is in the house.'\n\n\"'What, you dig fuller's-earth in the house?'\n\n\"'No, no. This is only where we compress it. But never mind that.\nAll we wish you to do is to examine the machine and to let us\nknow what is wrong with it.'\n\n\"We went upstairs together, the colonel first with the lamp, the\nfat manager and I behind him. It was a labyrinth of an old house,\nwith corridors, passages, narrow winding staircases, and little\nlow doors, the thresholds of which were hollowed out by the\ngenerations who had crossed them. There were no carpets and no\nsigns of any furniture above the ground floor, while the plaster\nwas peeling off the walls, and the damp was breaking through in\ngreen, unhealthy blotches. I tried to put on as unconcerned an\nair as possible, but I had not forgotten the warnings of the\nlady, even though I disregarded them, and I kept a keen eye upon\nmy two companions. Ferguson appeared to be a morose and silent\nman, but I could see from the little that he said that he was at\nleast a fellow-countryman.\n\n\"Colonel Lysander Stark stopped at last before a low door, which\nhe unlocked. Within was a small, square room, in which the three\nof us could hardly get at one time. Ferguson remained outside,\nand the colonel ushered me in.\n\n\"'We are now,' said he, 'actually within the hydraulic press, and\nit would be a particularly unpleasant thing for us if anyone were\nto turn it on. The ceiling of this small chamber is really the\nend of the descending piston, and it comes down with the force of\nmany tons upon this metal floor. There are small lateral columns\nof water outside which receive the force, and which transmit and\nmultiply it in the manner which is familiar to you. The machine\ngoes readily enough, but there is some stiffness in the working\nof it, and it has lost a little of its force. Perhaps you will\nhave the goodness to look it over and to show us how we can set\nit right.'\n\n\"I took the lamp from him, and I examined the machine very\nthoroughly. It was indeed a gigantic one, and capable of\nexercising enormous pressure. When I passed outside, however, and\npressed down the levers which controlled it, I knew at once by\nthe whishing sound that there was a slight leakage, which allowed\na regurgitation of water through one of the side cylinders. An\nexamination showed that one of the india-rubber bands which was\nround the head of a driving-rod had shrunk so as not quite to\nfill the socket along which it worked. This was clearly the cause\nof the loss of power, and I pointed it out to my companions, who\nfollowed my remarks very carefully and asked several practical\nquestions as to how they should proceed to set it right. When I\nhad made it clear to them, I returned to the main chamber of the\nmachine and took a good look at it to satisfy my own curiosity.\nIt was obvious at a glance that the story of the fuller's-earth\nwas the merest fabrication, for it would be absurd to suppose\nthat so powerful an engine could be designed for so inadequate a\npurpose. The walls were of wood, but the floor consisted of a\nlarge iron trough, and when I came to examine it I could see a\ncrust of metallic deposit all over it. I had stooped and was\nscraping at this to see exactly what it was when I heard a\nmuttered exclamation in German and saw the cadaverous face of the\ncolonel looking down at me.\n\n\"'What are you doing there?' he asked.\n\n\"I felt angry at having been tricked by so elaborate a story as\nthat which he had told me. 'I was admiring your fuller's-earth,'\nsaid I; 'I think that I should be better able to advise you as to\nyour machine if I knew what the exact purpose was for which it\nwas used.'\n\n\"The instant that I uttered the words I regretted the rashness of\nmy speech. His face set hard, and a baleful light sprang up in\nhis grey eyes.\n\n\"'Very well,' said he, 'you shall know all about the machine.' He\ntook a step backward, slammed the little door, and turned the key\nin the lock. I rushed towards it and pulled at the handle, but it\nwas quite secure, and did not give in the least to my kicks and\nshoves. 'Hullo!' I yelled. 'Hullo! Colonel! Let me out!'\n\n\"And then suddenly in the silence I heard a sound which sent my\nheart into my mouth. It was the clank of the levers and the swish\nof the leaking cylinder. He had set the engine at work. The lamp\nstill stood upon the floor where I had placed it when examining\nthe trough. By its light I saw that the black ceiling was coming\ndown upon me, slowly, jerkily, but, as none knew better than\nmyself, with a force which must within a minute grind me to a\nshapeless pulp. I threw myself, screaming, against the door, and\ndragged with my nails at the lock. I implored the colonel to let\nme out, but the remorseless clanking of the levers drowned my\ncries. The ceiling was only a foot or two above my head, and with\nmy hand upraised I could feel its hard, rough surface. Then it\nflashed through my mind that the pain of my death would depend\nvery much upon the position in which I met it. If I lay on my\nface the weight would come upon my spine, and I shuddered to\nthink of that dreadful snap. Easier the other way, perhaps; and\nyet, had I the nerve to lie and look up at that deadly black\nshadow wavering down upon me? Already I was unable to stand\nerect, when my eye caught something which brought a gush of hope\nback to my heart.\n\n\"I have said that though the floor and ceiling were of iron, the\nwalls were of wood. As I gave a last hurried glance around, I saw\na thin line of yellow light between two of the boards, which\nbroadened and broadened as a small panel was pushed backward. For\nan instant I could hardly believe that here was indeed a door\nwhich led away from death. The next instant I threw myself\nthrough, and lay half-fainting upon the other side. The panel had\nclosed again behind me, but the crash of the lamp, and a few\nmoments afterwards the clang of the two slabs of metal, told me\nhow narrow had been my escape.\n\n\"I was recalled to myself by a frantic plucking at my wrist, and\nI found myself lying upon the stone floor of a narrow corridor,\nwhile a woman bent over me and tugged at me with her left hand,\nwhile she held a candle in her right. It was the same good friend\nwhose warning I had so foolishly rejected.\n\n\"'Come! come!' she cried breathlessly. 'They will be here in a\nmoment. They will see that you are not there. Oh, do not waste\nthe so-precious time, but come!'\n\n\"This time, at least, I did not scorn her advice. I staggered to\nmy feet and ran with her along the corridor and down a winding\nstair. The latter led to another broad passage, and just as we\nreached it we heard the sound of running feet and the shouting of\ntwo voices, one answering the other from the floor on which  we\nwere and from the one beneath. My guide stopped and looked about\nher like one  who is at her wit's end. Then she threw open a door\nwhich led into a bedroom, through the window of which the moon\nwas shining brightly.\n\n\"'It is your only chance,' said she. 'It is high, but it may be\nthat you can jump it.'\n\n\"As she spoke a light sprang into view at the further end of the\npassage, and I saw the lean figure of Colonel Lysander Stark\nrushing forward with a lantern in one hand and a weapon like a\nbutcher's cleaver in the other. I rushed across the bedroom,\nflung open the window, and looked out. How quiet and sweet and\nwholesome the garden looked in the moonlight, and it could not be\nmore than thirty feet down. I clambered out upon the sill, but I\nhesitated to jump until I should have heard what passed between\nmy saviour and the ruffian who pursued me. If she were ill-used,\nthen at any risks I was determined to go back to her assistance.\nThe thought had hardly flashed through my mind before he was at\nthe door, pushing his way past her; but she threw her arms round\nhim and tried to hold him back.\n\n\"'Fritz! Fritz!' she cried in English, 'remember your promise\nafter the last time. You said it should not be again. He will be\nsilent! Oh, he will be silent!'\n\n\"'You are mad, Elise!' he shouted, struggling to break away from\nher. 'You will be the ruin of us. He has seen too much. Let me\npass, I say!' He dashed her to one side, and, rushing to the\nwindow, cut at me with his heavy weapon. I had let myself go, and\nwas hanging by the hands to the sill, when his blow fell. I was\nconscious of a dull pain, my grip loosened, and I fell into the\ngarden below.\n\n\"I was shaken but not hurt by the fall; so I picked myself up and\nrushed off among the bushes as hard as I could run, for I\nunderstood that I was far from being out of danger yet. Suddenly,\nhowever, as I ran, a deadly dizziness and sickness came over me.\nI glanced down at my hand, which was throbbing painfully, and\nthen, for the first time, saw that my thumb had been cut off and\nthat the blood was pouring from my wound. I endeavoured to tie my\nhandkerchief round it, but there came a sudden buzzing in my\nears, and next moment I fell in a dead faint among the\nrose-bushes.\n\n\"How long I remained unconscious I cannot tell. It must have been\na very long time, for the moon had sunk, and a bright morning was\nbreaking when I came to myself. My clothes were all sodden with\ndew, and my coat-sleeve was drenched with blood from my wounded\nthumb. The smarting of it recalled in an instant all the\nparticulars of my night's adventure, and I sprang to my feet with\nthe feeling that I might hardly yet be safe from my pursuers. But\nto my astonishment, when I came to look round me, neither house\nnor garden were to be seen. I had been lying in an angle of the\nhedge close by the highroad, and just a little lower down was a\nlong building, which proved, upon my approaching it, to be the\nvery station at which I had arrived upon the previous night. Were\nit not for the ugly wound upon my hand, all that had passed\nduring those dreadful hours might have been an evil dream.\n\n\"Half dazed, I went into the station and asked about the morning\ntrain. There would be one to Reading in less than an hour. The\nsame porter was on duty, I found, as had been there when I\narrived. I inquired of him whether he had ever heard of Colonel\nLysander Stark. The name was strange to him. Had he observed a\ncarriage the night before waiting for me? No, he had not. Was\nthere a police-station anywhere near? There was one about three\nmiles off.\n\n\"It was too far for me to go, weak and ill as I was. I determined\nto wait until I got back to town before telling my story to the\npolice. It was a little past six when I arrived, so I went first\nto have my wound dressed, and then the doctor was kind enough to\nbring me along here. I put the case into your hands and shall do\nexactly what you advise.\"\n\nWe both sat in silence for some little time after listening to\nthis extraordinary narrative. Then Sherlock Holmes pulled down\nfrom the shelf one of the ponderous commonplace books in which he\nplaced his cuttings.\n\n\"Here is an advertisement which will interest you,\" said he. \"It\nappeared in all the papers about a year ago. Listen to this:\n'Lost, on the 9th inst., Mr. Jeremiah Hayling, aged\ntwenty-six, a hydraulic engineer. Left his lodgings at ten\no'clock at night, and has not been heard of since. Was\ndressed in,' etc., etc. Ha! That represents the last time that\nthe colonel needed to have his machine overhauled, I fancy.\"\n\n\"Good heavens!\" cried my patient. \"Then that explains what the\ngirl said.\"\n\n\"Undoubtedly. It is quite clear that the colonel was a cool and\ndesperate man, who was absolutely determined that nothing should\nstand in the way of his little game, like those out-and-out\npirates who will leave no survivor from a captured ship. Well,\nevery moment now is precious, so if you feel equal to it we shall\ngo down to Scotland Yard at once as a preliminary to starting for\nEyford.\"\n\nSome three hours or so afterwards we were all in the train\ntogether, bound from Reading to the little Berkshire village.\nThere were Sherlock Holmes, the hydraulic engineer, Inspector\nBradstreet, of Scotland Yard, a plain-clothes man, and myself.\nBradstreet had spread an ordnance map of the county out upon the\nseat and was busy with his compasses drawing a circle with Eyford\nfor its centre.\n\n\"There you are,\" said he. \"That circle is drawn at a radius of\nten miles from the village. The place we want must be somewhere\nnear that line. You said ten miles, I think, sir.\"\n\n\"It was an hour's good drive.\"\n\n\"And you think that they brought you back all that way when you\nwere unconscious?\"\n\n\"They must have done so. I have a confused memory, too, of having\nbeen lifted and conveyed somewhere.\"\n\n\"What I cannot understand,\" said I, \"is why they should have\nspared you when they found you lying fainting in the garden.\nPerhaps the villain was softened by the woman's entreaties.\"\n\n\"I hardly think that likely. I never saw a more inexorable face\nin my life.\"\n\n\"Oh, we shall soon clear up all that,\" said Bradstreet. \"Well, I\nhave drawn my circle, and I only wish I knew at what point upon\nit the folk that we are in search of are to be found.\"\n\n\"I think I could lay my finger on it,\" said Holmes quietly.\n\n\"Really, now!\" cried the inspector, \"you have formed your\nopinion! Come, now, we shall see who agrees with you. I say it is\nsouth, for the country is more deserted there.\"\n\n\"And I say east,\" said my patient.\n\n\"I am for west,\" remarked the plain-clothes man. \"There are\nseveral quiet little villages up there.\"\n\n\"And I am for north,\" said I, \"because there are no hills there,\nand our friend says that he did not notice the carriage go up\nany.\"\n\n\"Come,\" cried the inspector, laughing; \"it's a very pretty\ndiversity of opinion. We have boxed the compass among us. Who do\nyou give your casting vote to?\"\n\n\"You are all wrong.\"\n\n\"But we can't all be.\"\n\n\"Oh, yes, you can. This is my point.\" He placed his finger in the\ncentre of the circle. \"This is where we shall find them.\"\n\n\"But the twelve-mile drive?\" gasped Hatherley.\n\n\"Six out and six back. Nothing simpler. You say yourself that the\nhorse was fresh and glossy when you got in. How could it be that\nif it had gone twelve miles over heavy roads?\"\n\n\"Indeed, it is a likely ruse enough,\" observed Bradstreet\nthoughtfully. \"Of course there can be no doubt as to the nature\nof this gang.\"\n\n\"None at all,\" said Holmes. \"They are coiners on a large scale,\nand have used the machine to form the amalgam which has taken the\nplace of silver.\"\n\n\"We have known for some time that a clever gang was at work,\"\nsaid the inspector. \"They have been turning out half-crowns by\nthe thousand. We even traced them as far as Reading, but could\nget no farther, for they had covered their traces in a way that\nshowed that they were very old hands. But now, thanks to this\nlucky chance, I think that we have got them right enough.\"\n\nBut the inspector was mistaken, for those criminals were not\ndestined to fall into the hands of justice. As we rolled into\nEyford Station we saw a gigantic column of smoke which streamed\nup from behind a small clump of trees in the neighbourhood and\nhung like an immense ostrich feather over the landscape.\n\n\"A house on fire?\" asked Bradstreet as the train steamed off\nagain on its way.\n\n\"Yes, sir!\" said the station-master.\n\n\"When did it break out?\"\n\n\"I hear that it was during the night, sir, but it has got worse,\nand the whole place is in a blaze.\"\n\n\"Whose house is it?\"\n\n\"Dr. Becher's.\"\n\n\"Tell me,\" broke in the engineer, \"is Dr. Becher a German, very\nthin, with a long, sharp nose?\"\n\nThe station-master laughed heartily. \"No, sir, Dr. Becher is an\nEnglishman, and there isn't a man in the parish who has a\nbetter-lined waistcoat. But he has a gentleman staying with him,\na patient, as I understand, who is a foreigner, and he looks as\nif a little good Berkshire beef would do him no harm.\"\n\nThe station-master had not finished his speech before we were all\nhastening in the direction of the fire. The road topped a low\nhill, and there was a great widespread whitewashed building in\nfront of us, spouting fire at every chink and window, while in\nthe garden in front three fire-engines were vainly striving to\nkeep the flames under.\n\n\"That's it!\" cried Hatherley, in intense excitement. \"There is\nthe gravel-drive, and there are the rose-bushes where I lay. That\nsecond window is the one that I jumped from.\"\n\n\"Well, at least,\" said Holmes, \"you have had your revenge upon\nthem. There can be no question that it was your oil-lamp which,\nwhen it was crushed in the press, set fire to the wooden walls,\nthough no doubt they were too excited in the chase after you to\nobserve it at the time. Now keep your eyes open in this crowd for\nyour friends of last night, though I very much fear that they are\na good hundred miles off by now.\"\n\nAnd Holmes' fears came to be realised, for from that day to this\nno word has ever been heard either of the beautiful woman, the\nsinister German, or the morose Englishman. Early that morning a\npeasant had met a cart containing several people and some very\nbulky boxes driving rapidly in the direction of Reading, but\nthere all traces of the fugitives disappeared, and even Holmes'\ningenuity failed ever to discover the least clue as to their\nwhereabouts.\n\nThe firemen had been much perturbed at the strange arrangements\nwhich they had found within, and still more so by discovering a\nnewly severed human thumb upon a window-sill of the second floor.\nAbout sunset, however, their efforts were at last successful, and\nthey subdued the flames, but not before the roof had fallen in,\nand the whole place been reduced to such absolute ruin that, save\nsome twisted cylinders and iron piping, not a trace remained of\nthe machinery which had cost our unfortunate acquaintance so\ndearly. Large masses of nickel and of tin were discovered stored\nin an out-house, but no coins were to be found, which may have\nexplained the presence of those bulky boxes which have been\nalready referred to.\n\nHow our hydraulic engineer had been conveyed from the garden to\nthe spot where he recovered his senses might have remained\nforever a mystery were it not for the soft mould, which told us a\nvery plain tale. He had evidently been carried down by two\npersons, one of whom had remarkably small feet and the other\nunusually large ones. On the whole, it was most probable that the\nsilent Englishman, being less bold or less murderous than his\ncompanion, had assisted the woman to bear the unconscious man out\nof the way of danger.\n\n\"Well,\" said our engineer ruefully as we took our seats to return\nonce more to London, \"it has been a pretty business for me! I\nhave lost my thumb and I have lost a fifty-guinea fee, and what\nhave I gained?\"\n\n\"Experience,\" said Holmes, laughing. \"Indirectly it may be of\nvalue, you know; you have only to put it into words to gain the\nreputation of being excellent company for the remainder of your\nexistence.\"\n\n\n\nX. THE ADVENTURE OF THE NOBLE BACHELOR\n\nThe Lord St. Simon marriage, and its curious termination, have\nlong ceased to be a subject of interest in those exalted circles\nin which the unfortunate bridegroom moves. Fresh scandals have\neclipsed it, and their more piquant details have drawn the\ngossips away from this four-year-old drama. As I have reason to\nbelieve, however, that the full facts have never been revealed to\nthe general public, and as my friend Sherlock Holmes had a\nconsiderable share in clearing the matter up, I feel that no\nmemoir of him would be complete without some little sketch of\nthis remarkable episode.\n\nIt was a few weeks before my own marriage, during the days when I\nwas still sharing rooms with Holmes in Baker Street, that he came\nhome from an afternoon stroll to find a letter on the table\nwaiting for him. I had remained indoors all day, for the weather\nhad taken a sudden turn to rain, with high autumnal winds, and\nthe Jezail bullet which I had brought back in one of my limbs as\na relic of my Afghan campaign throbbed with dull persistence.\nWith my body in one easy-chair and my legs upon another, I had\nsurrounded myself with a cloud of newspapers until at last,\nsaturated with the news of the day, I tossed them all aside and\nlay listless, watching the huge crest and monogram upon the\nenvelope upon the table and wondering lazily who my friend's\nnoble correspondent could be.\n\n\"Here is a very fashionable epistle,\" I remarked as he entered.\n\"Your morning letters, if I remember right, were from a\nfish-monger and a tide-waiter.\"\n\n\"Yes, my correspondence has certainly the charm of variety,\" he\nanswered, smiling, \"and the humbler are usually the more\ninteresting. This looks like one of those unwelcome social\nsummonses which call upon a man either to be bored or to lie.\"\n\nHe broke the seal and glanced over the contents.\n\n\"Oh, come, it may prove to be something of interest, after all.\"\n\n\"Not social, then?\"\n\n\"No, distinctly professional.\"\n\n\"And from a noble client?\"\n\n\"One of the highest in England.\"\n\n\"My dear fellow, I congratulate you.\"\n\n\"I assure you, Watson, without affectation, that the status of my\nclient is a matter of less moment to me than the interest of his\ncase. It is just possible, however, that that also may not be\nwanting in this new investigation. You have been reading the\npapers diligently of late, have you not?\"\n\n\"It looks like it,\" said I ruefully, pointing to a huge bundle in\nthe corner. \"I have had nothing else to do.\"\n\n\"It is fortunate, for you will perhaps be able to post me up. I\nread nothing except the criminal news and the agony column. The\nlatter is always instructive. But if you have followed recent\nevents so closely you must have read about Lord St. Simon and his\nwedding?\"\n\n\"Oh, yes, with the deepest interest.\"\n\n\"That is well. The letter which I hold in my hand is from Lord\nSt. Simon. I will read it to you, and in return you must turn\nover these papers and let me have whatever bears upon the matter.\nThis is what he says:\n\n\"'MY DEAR MR. SHERLOCK HOLMES:--Lord Backwater tells me that I\nmay place implicit reliance upon your judgment and discretion. I\nhave determined, therefore, to call upon you and to consult you\nin reference to the very painful event which has occurred in\nconnection with my wedding. Mr. Lestrade, of Scotland Yard, is\nacting already in the matter, but he assures me that he sees no\nobjection to your co-operation, and that he even thinks that\nit might be of some assistance. I will call at four o'clock in\nthe afternoon, and, should you have any other engagement at that\ntime, I hope that you will postpone it, as this matter is of\nparamount importance. Yours faithfully, ST. SIMON.'\n\n\"It is dated from Grosvenor Mansions, written with a quill pen,\nand the noble lord has had the misfortune to get a smear of ink\nupon the outer side of his right little finger,\" remarked Holmes\nas he folded up the epistle.\n\n\"He says four o'clock. It is three now. He will be here in an\nhour.\"\n\n\"Then I have just time, with your assistance, to get clear upon\nthe subject. Turn over those papers and arrange the extracts in\ntheir order of time, while I take a glance as to who our client\nis.\" He picked a red-covered volume from a line of books of\nreference beside the mantelpiece. \"Here he is,\" said he, sitting\ndown and flattening it out upon his knee. \"'Lord Robert Walsingham\nde Vere St. Simon, second son of the Duke of Balmoral.' Hum! 'Arms:\nAzure, three caltrops in chief over a fess sable. Born in 1846.'\nHe's forty-one years of age, which is mature for marriage. Was\nUnder-Secretary for the colonies in a late administration. The\nDuke, his father, was at one time Secretary for Foreign Affairs.\nThey inherit Plantagenet blood by direct descent, and Tudor on\nthe distaff side. Ha! Well, there is nothing very instructive in\nall this. I think that I must turn to you Watson, for something\nmore solid.\"\n\n\"I have very little difficulty in finding what I want,\" said I,\n\"for the facts are quite recent, and the matter struck me as\nremarkable. I feared to refer them to you, however, as I knew\nthat you had an inquiry on hand and that you disliked the\nintrusion of other matters.\"\n\n\"Oh, you mean the little problem of the Grosvenor Square\nfurniture van. That is quite cleared up now--though, indeed, it\nwas obvious from the first. Pray give me the results of your\nnewspaper selections.\"\n\n\"Here is the first notice which I can find. It is in the personal\ncolumn of the Morning Post, and dates, as you see, some weeks\nback: 'A marriage has been arranged,' it says, 'and will, if\nrumour is correct, very shortly take place, between Lord Robert\nSt. Simon, second son of the Duke of Balmoral, and Miss Hatty\nDoran, the only daughter of Aloysius Doran. Esq., of San\nFrancisco, Cal., U.S.A.' That is all.\"\n\n\"Terse and to the point,\" remarked Holmes, stretching his long,\nthin legs towards the fire.\n\n\"There was a paragraph amplifying this in one of the society\npapers of the same week. Ah, here it is: 'There will soon be a\ncall for protection in the marriage market, for the present\nfree-trade principle appears to tell heavily against our home\nproduct. One by one the management of the noble houses of Great\nBritain is passing into the hands of our fair cousins from across\nthe Atlantic. An important addition has been made during the last\nweek to the list of the prizes which have been borne away by\nthese charming invaders. Lord St. Simon, who has shown himself\nfor over twenty years proof against the little god's arrows, has\nnow definitely announced his approaching marriage with Miss Hatty\nDoran, the fascinating daughter of a California millionaire. Miss\nDoran, whose graceful figure and striking face attracted much\nattention at the Westbury House festivities, is an only child,\nand it is currently reported that her dowry will run to\nconsiderably over the six figures, with expectancies for the\nfuture. As it is an open secret that the Duke of Balmoral has\nbeen compelled to sell his pictures within the last few years,\nand as Lord St. Simon has no property of his own save the small\nestate of Birchmoor, it is obvious that the Californian heiress\nis not the only gainer by an alliance which will enable her to\nmake the easy and common transition from a Republican lady to a\nBritish peeress.'\"\n\n\"Anything else?\" asked Holmes, yawning.\n\n\"Oh, yes; plenty. Then there is another note in the Morning Post\nto say that the marriage would be an absolutely quiet one, that it\nwould be at St. George's, Hanover Square, that only half a dozen\nintimate friends would be invited, and that the party would\nreturn to the furnished house at Lancaster Gate which has been\ntaken by Mr. Aloysius Doran. Two days later--that is, on\nWednesday last--there is a curt announcement that the wedding had\ntaken place, and that the honeymoon would be passed at Lord\nBackwater's place, near Petersfield. Those are all the notices\nwhich appeared before the disappearance of the bride.\"\n\n\"Before the what?\" asked Holmes with a start.\n\n\"The vanishing of the lady.\"\n\n\"When did she vanish, then?\"\n\n\"At the wedding breakfast.\"\n\n\"Indeed. This is more interesting than it promised to be; quite\ndramatic, in fact.\"\n\n\"Yes; it struck me as being a little out of the common.\"\n\n\"They often vanish before the ceremony, and occasionally during\nthe honeymoon; but I cannot call to mind anything quite so prompt\nas this. Pray let me have the details.\"\n\n\"I warn you that they are very incomplete.\"\n\n\"Perhaps we may make them less so.\"\n\n\"Such as they are, they are set forth in a single article of a\nmorning paper of yesterday, which I will read to you. It is\nheaded, 'Singular Occurrence at a Fashionable Wedding':\n\n\"'The family of Lord Robert St. Simon has been thrown into the\ngreatest consternation by the strange and painful episodes which\nhave taken place in connection with his wedding. The ceremony, as\nshortly announced in the papers of yesterday, occurred on the\nprevious morning; but it is only now that it has been possible to\nconfirm the strange rumours which have been so persistently\nfloating about. In spite of the attempts of the friends to hush\nthe matter up, so much public attention has now been drawn to it\nthat no good purpose can be served by affecting to disregard what\nis a common subject for conversation.\n\n\"'The ceremony, which was performed at St. George's, Hanover\nSquare, was a very quiet one, no one being present save the\nfather of the bride, Mr. Aloysius Doran, the Duchess of Balmoral,\nLord Backwater, Lord Eustace and Lady Clara St. Simon (the\nyounger brother and sister of the bridegroom), and Lady Alicia\nWhittington. The whole party proceeded afterwards to the house of\nMr. Aloysius Doran, at Lancaster Gate, where breakfast had been\nprepared. It appears that some little trouble was caused by a\nwoman, whose name has not been ascertained, who endeavoured to\nforce her way into the house after the bridal party, alleging\nthat she had some claim upon Lord St. Simon. It was only after a\npainful and prolonged scene that she was ejected by the butler\nand the footman. The bride, who had fortunately entered the house\nbefore this unpleasant interruption, had sat down to breakfast\nwith the rest, when she complained of a sudden indisposition and\nretired to her room. Her prolonged absence having caused some\ncomment, her father followed her, but learned from her maid that\nshe had only come up to her chamber for an instant, caught up an\nulster and bonnet, and hurried down to the passage. One of the\nfootmen declared that he had seen a lady leave the house thus\napparelled, but had refused to credit that it was his mistress,\nbelieving her to be with the company. On ascertaining that his\ndaughter had disappeared, Mr. Aloysius Doran, in conjunction with\nthe bridegroom, instantly put themselves in communication with\nthe police, and very energetic inquiries are being made, which\nwill probably result in a speedy clearing up of this very\nsingular business. Up to a late hour last night, however, nothing\nhad transpired as to the whereabouts of the missing lady. There\nare rumours of foul play in the matter, and it is said that the\npolice have caused the arrest of the woman who had caused the\noriginal disturbance, in the belief that, from jealousy or some\nother motive, she may have been concerned in the strange\ndisappearance of the bride.'\"\n\n\"And is that all?\"\n\n\"Only one little item in another of the morning papers, but it is\na suggestive one.\"\n\n\"And it is--\"\n\n\"That Miss Flora Millar, the lady who had caused the disturbance,\nhas actually been arrested. It appears that she was formerly a\ndanseuse at the Allegro, and that she has known the bridegroom\nfor some years. There are no further particulars, and the whole\ncase is in your hands now--so far as it has been set forth in the\npublic press.\"\n\n\"And an exceedingly interesting case it appears to be. I would\nnot have missed it for worlds. But there is a ring at the bell,\nWatson, and as the clock makes it a few minutes after four, I\nhave no doubt that this will prove to be our noble client. Do not\ndream of going, Watson, for I very much prefer having a witness,\nif only as a check to my own memory.\"\n\n\"Lord Robert St. Simon,\" announced our page-boy, throwing open\nthe door. A gentleman entered, with a pleasant, cultured face,\nhigh-nosed and pale, with something perhaps of petulance about\nthe mouth, and with the steady, well-opened eye of a man whose\npleasant lot it had ever been to command and to be obeyed. His\nmanner was brisk, and yet his general appearance gave an undue\nimpression of age, for he had a slight forward stoop and a little\nbend of the knees as he walked. His hair, too, as he swept off\nhis very curly-brimmed hat, was grizzled round the edges and thin\nupon the top. As to his dress, it was careful to the verge of\nfoppishness, with high collar, black frock-coat, white waistcoat,\nyellow gloves, patent-leather shoes, and light-coloured gaiters.\nHe advanced slowly into the room, turning his head from left to\nright, and swinging in his right hand the cord which held his\ngolden eyeglasses.\n\n\"Good-day, Lord St. Simon,\" said Holmes, rising and bowing. \"Pray\ntake the basket-chair. This is my friend and colleague, Dr.\nWatson. Draw up a little to the fire, and we will talk this\nmatter over.\"\n\n\"A most painful matter to me, as you can most readily imagine,\nMr. Holmes. I have been cut to the quick. I understand that you\nhave already managed several delicate cases of this sort, sir,\nthough I presume that they were hardly from the same class of\nsociety.\"\n\n\"No, I am descending.\"\n\n\"I beg pardon.\"\n\n\"My last client of the sort was a king.\"\n\n\"Oh, really! I had no idea. And which king?\"\n\n\"The King of Scandinavia.\"\n\n\"What! Had he lost his wife?\"\n\n\"You can understand,\" said Holmes suavely, \"that I extend to the\naffairs of my other clients the same secrecy which I promise to\nyou in yours.\"\n\n\"Of course! Very right! very right! I'm sure I beg pardon. As to\nmy own case, I am ready to give you any information which may\nassist you in forming an opinion.\"\n\n\"Thank you. I have already learned all that is in the public\nprints, nothing more. I presume that I may take it as correct--this\narticle, for example, as to the disappearance of the bride.\"\n\nLord St. Simon glanced over it. \"Yes, it is correct, as far as it\ngoes.\"\n\n\"But it needs a great deal of supplementing before anyone could\noffer an opinion. I think that I may arrive at my facts most\ndirectly by questioning you.\"\n\n\"Pray do so.\"\n\n\"When did you first meet Miss Hatty Doran?\"\n\n\"In San Francisco, a year ago.\"\n\n\"You were travelling in the States?\"\n\n\"Yes.\"\n\n\"Did you become engaged then?\"\n\n\"No.\"\n\n\"But you were on a friendly footing?\"\n\n\"I was amused by her society, and she could see that I was\namused.\"\n\n\"Her father is very rich?\"\n\n\"He is said to be the richest man on the Pacific slope.\"\n\n\"And how did he make his money?\"\n\n\"In mining. He had nothing a few years ago. Then he struck gold,\ninvested it, and came up by leaps and bounds.\"\n\n\"Now, what is your own impression as to the young lady's--your\nwife's character?\"\n\nThe nobleman swung his glasses a little faster and stared down\ninto the fire. \"You see, Mr. Holmes,\" said he, \"my wife was\ntwenty before her father became a rich man. During that time she\nran free in a mining camp and wandered through woods or\nmountains, so that her education has come from Nature rather than\nfrom the schoolmaster. She is what we call in England a tomboy,\nwith a strong nature, wild and free, unfettered by any sort of\ntraditions. She is impetuous--volcanic, I was about to say. She\nis swift in making up her mind and fearless in carrying out her\nresolutions. On the other hand, I would not have given her the\nname which I have the honour to bear\"--he gave a little stately\ncough--\"had not I thought her to be at bottom a noble woman. I\nbelieve that she is capable of heroic self-sacrifice and that\nanything dishonourable would be repugnant to her.\"\n\n\"Have you her photograph?\"\n\n\"I brought this with me.\" He opened a locket and showed us the\nfull face of a very lovely woman. It was not a photograph but an\nivory miniature, and the artist had brought out the full effect\nof the lustrous black hair, the large dark eyes, and the\nexquisite mouth. Holmes gazed long and earnestly at it. Then he\nclosed the locket and handed it back to Lord St. Simon.\n\n\"The young lady came to London, then, and you renewed your\nacquaintance?\"\n\n\"Yes, her father brought her over for this last London season. I\nmet her several times, became engaged to her, and have now\nmarried her.\"\n\n\"She brought, I understand, a considerable dowry?\"\n\n\"A fair dowry. Not more than is usual in my family.\"\n\n\"And this, of course, remains to you, since the marriage is a\nfait accompli?\"\n\n\"I really have made no inquiries on the subject.\"\n\n\"Very naturally not. Did you see Miss Doran on the day before the\nwedding?\"\n\n\"Yes.\"\n\n\"Was she in good spirits?\"\n\n\"Never better. She kept talking of what we should do in our\nfuture lives.\"\n\n\"Indeed! That is very interesting. And on the morning of the\nwedding?\"\n\n\"She was as bright as possible--at least until after the\nceremony.\"\n\n\"And did you observe any change in her then?\"\n\n\"Well, to tell the truth, I saw then the first signs that I had\never seen that her temper was just a little sharp. The incident\nhowever, was too trivial to relate and can have no possible\nbearing upon the case.\"\n\n\"Pray let us have it, for all that.\"\n\n\"Oh, it is childish. She dropped her bouquet as we went towards\nthe vestry. She was passing the front pew at the time, and it\nfell over into the pew. There was a moment's delay, but the\ngentleman in the pew handed it up to her again, and it did not\nappear to be the worse for the fall. Yet when I spoke to her of\nthe matter, she answered me abruptly; and in the carriage, on our\nway home, she seemed absurdly agitated over this trifling cause.\"\n\n\"Indeed! You say that there was a gentleman in the pew. Some of\nthe general public were present, then?\"\n\n\"Oh, yes. It is impossible to exclude them when the church is\nopen.\"\n\n\"This gentleman was not one of your wife's friends?\"\n\n\"No, no; I call him a gentleman by courtesy, but he was quite a\ncommon-looking person. I hardly noticed his appearance. But\nreally I think that we are wandering rather far from the point.\"\n\n\"Lady St. Simon, then, returned from the wedding in a less\ncheerful frame of mind than she had gone to it. What did she do\non re-entering her father's house?\"\n\n\"I saw her in conversation with her maid.\"\n\n\"And who is her maid?\"\n\n\"Alice is her name. She is an American and came from California\nwith her.\"\n\n\"A confidential servant?\"\n\n\"A little too much so. It seemed to me that her mistress allowed\nher to take great liberties. Still, of course, in America they\nlook upon these things in a different way.\"\n\n\"How long did she speak to this Alice?\"\n\n\"Oh, a few minutes. I had something else to think of.\"\n\n\"You did not overhear what they said?\"\n\n\"Lady St. Simon said something about 'jumping a claim.' She was\naccustomed to use slang of the kind. I have no idea what she\nmeant.\"\n\n\"American slang is very expressive sometimes. And what did your\nwife do when she finished speaking to her maid?\"\n\n\"She walked into the breakfast-room.\"\n\n\"On your arm?\"\n\n\"No, alone. She was very independent in little matters like that.\nThen, after we had sat down for ten minutes or so, she rose\nhurriedly, muttered some words of apology, and left the room. She\nnever came back.\"\n\n\"But this maid, Alice, as I understand, deposes that she went to\nher room, covered her bride's dress with a long ulster, put on a\nbonnet, and went out.\"\n\n\"Quite so. And she was afterwards seen walking into Hyde Park in\ncompany with Flora Millar, a woman who is now in custody, and who\nhad already made a disturbance at Mr. Doran's house that\nmorning.\"\n\n\"Ah, yes. I should like a few particulars as to this young lady,\nand your relations to her.\"\n\nLord St. Simon shrugged his shoulders and raised his eyebrows.\n\"We have been on a friendly footing for some years--I may say on\na very friendly footing. She used to be at the Allegro. I have\nnot treated her ungenerously, and she had no just cause of\ncomplaint against me, but you know what women are, Mr. Holmes.\nFlora was a dear little thing, but exceedingly hot-headed and\ndevotedly attached to me. She wrote me dreadful letters when she\nheard that I was about to be married, and, to tell the truth, the\nreason why I had the marriage celebrated so quietly was that I\nfeared lest there might be a scandal in the church. She came to\nMr. Doran's door just after we returned, and she endeavoured to\npush her way in, uttering very abusive expressions towards my\nwife, and even threatening her, but I had foreseen the\npossibility of something of the sort, and I had two police\nfellows there in private clothes, who soon pushed her out again.\nShe was quiet when she saw that there was no good in making a\nrow.\"\n\n\"Did your wife hear all this?\"\n\n\"No, thank goodness, she did not.\"\n\n\"And she was seen walking with this very woman afterwards?\"\n\n\"Yes. That is what Mr. Lestrade, of Scotland Yard, looks upon as\nso serious. It is thought that Flora decoyed my wife out and laid\nsome terrible trap for her.\"\n\n\"Well, it is a possible supposition.\"\n\n\"You think so, too?\"\n\n\"I did not say a probable one. But you do not yourself look upon\nthis as likely?\"\n\n\"I do not think Flora would hurt a fly.\"\n\n\"Still, jealousy is a strange transformer of characters. Pray\nwhat is your own theory as to what took place?\"\n\n\"Well, really, I came to seek a theory, not to propound one. I\nhave given you all the facts. Since you ask me, however, I may\nsay that it has occurred to me as possible that the excitement of\nthis affair, the consciousness that she had made so immense a\nsocial stride, had the effect of causing some little nervous\ndisturbance in my wife.\"\n\n\"In short, that she had become suddenly deranged?\"\n\n\"Well, really, when I consider that she has turned her back--I\nwill not say upon me, but upon so much that many have aspired to\nwithout success--I can hardly explain it in any other fashion.\"\n\n\"Well, certainly that is also a conceivable hypothesis,\" said\nHolmes, smiling. \"And now, Lord St. Simon, I think that I have\nnearly all my data. May I ask whether you were seated at the\nbreakfast-table so that you could see out of the window?\"\n\n\"We could see the other side of the road and the Park.\"\n\n\"Quite so. Then I do not think that I need to detain you longer.\nI shall communicate with you.\"\n\n\"Should you be fortunate enough to solve this problem,\" said our\nclient, rising.\n\n\"I have solved it.\"\n\n\"Eh? What was that?\"\n\n\"I say that I have solved it.\"\n\n\"Where, then, is my wife?\"\n\n\"That is a detail which I shall speedily supply.\"\n\nLord St. Simon shook his head. \"I am afraid that it will take\nwiser heads than yours or mine,\" he remarked, and bowing in a\nstately, old-fashioned manner he departed.\n\n\"It is very good of Lord St. Simon to honour my head by putting\nit on a level with his own,\" said Sherlock Holmes, laughing. \"I\nthink that I shall have a whisky and soda and a cigar after all\nthis cross-questioning. I had formed my conclusions as to the\ncase before our client came into the room.\"\n\n\"My dear Holmes!\"\n\n\"I have notes of several similar cases, though none, as I\nremarked before, which were quite as prompt. My whole examination\nserved to turn my conjecture into a certainty. Circumstantial\nevidence is occasionally very convincing, as when you find a\ntrout in the milk, to quote Thoreau's example.\"\n\n\"But I have heard all that you have heard.\"\n\n\"Without, however, the knowledge of pre-existing cases which\nserves me so well. There was a parallel instance in Aberdeen some\nyears back, and something on very much the same lines at Munich\nthe year after the Franco-Prussian War. It is one of these\ncases--but, hullo, here is Lestrade! Good-afternoon, Lestrade!\nYou will find an extra tumbler upon the sideboard, and there are\ncigars in the box.\"\n\nThe official detective was attired in a pea-jacket and cravat,\nwhich gave him a decidedly nautical appearance, and he carried a\nblack canvas bag in his hand. With a short greeting he seated\nhimself and lit the cigar which had been offered to him.\n\n\"What's up, then?\" asked Holmes with a twinkle in his eye. \"You\nlook dissatisfied.\"\n\n\"And I feel dissatisfied. It is this infernal St. Simon marriage\ncase. I can make neither head nor tail of the business.\"\n\n\"Really! You surprise me.\"\n\n\"Who ever heard of such a mixed affair? Every clue seems to slip\nthrough my fingers. I have been at work upon it all day.\"\n\n\"And very wet it seems to have made you,\" said Holmes laying his\nhand upon the arm of the pea-jacket.\n\n\"Yes, I have been dragging the Serpentine.\"\n\n\"In heaven's name, what for?\"\n\n\"In search of the body of Lady St. Simon.\"\n\nSherlock Holmes leaned back in his chair and laughed heartily.\n\n\"Have you dragged the basin of Trafalgar Square fountain?\" he\nasked.\n\n\"Why? What do you mean?\"\n\n\"Because you have just as good a chance of finding this lady in\nthe one as in the other.\"\n\nLestrade shot an angry glance at my companion. \"I suppose you\nknow all about it,\" he snarled.\n\n\"Well, I have only just heard the facts, but my mind is made up.\"\n\n\"Oh, indeed! Then you think that the Serpentine plays no part in\nthe matter?\"\n\n\"I think it very unlikely.\"\n\n\"Then perhaps you will kindly explain how it is that we found\nthis in it?\" He opened his bag as he spoke, and tumbled onto the\nfloor a wedding-dress of watered silk, a pair of white satin\nshoes and a bride's wreath and veil, all discoloured and soaked\nin water. \"There,\" said he, putting a new wedding-ring upon the\ntop of the pile. \"There is a little nut for you to crack, Master\nHolmes.\"\n\n\"Oh, indeed!\" said my friend, blowing blue rings into the air.\n\"You dragged them from the Serpentine?\"\n\n\"No. They were found floating near the margin by a park-keeper.\nThey have been identified as her clothes, and it seemed to me\nthat if the clothes were there the body would not be far off.\"\n\n\"By the same brilliant reasoning, every man's body is to be found\nin the neighbourhood of his wardrobe. And pray what did you hope\nto arrive at through this?\"\n\n\"At some evidence implicating Flora Millar in the disappearance.\"\n\n\"I am afraid that you will find it difficult.\"\n\n\"Are you, indeed, now?\" cried Lestrade with some bitterness. \"I\nam afraid, Holmes, that you are not very practical with your\ndeductions and your inferences. You have made two blunders in as\nmany minutes. This dress does implicate Miss Flora Millar.\"\n\n\"And how?\"\n\n\"In the dress is a pocket. In the pocket is a card-case. In the\ncard-case is a note. And here is the very note.\" He slapped it\ndown upon the table in front of him. \"Listen to this: 'You will\nsee me when all is ready. Come at once. F.H.M.' Now my theory all\nalong has been that Lady St. Simon was decoyed away by Flora\nMillar, and that she, with confederates, no doubt, was\nresponsible for her disappearance. Here, signed with her\ninitials, is the very note which was no doubt quietly slipped\ninto her hand at the door and which lured her within their\nreach.\"\n\n\"Very good, Lestrade,\" said Holmes, laughing. \"You really are\nvery fine indeed. Let me see it.\" He took up the paper in a\nlistless way, but his attention instantly became riveted, and he\ngave a little cry of satisfaction. \"This is indeed important,\"\nsaid he.\n\n\"Ha! you find it so?\"\n\n\"Extremely so. I congratulate you warmly.\"\n\nLestrade rose in his triumph and bent his head to look. \"Why,\" he\nshrieked, \"you're looking at the wrong side!\"\n\n\"On the contrary, this is the right side.\"\n\n\"The right side? You're mad! Here is the note written in pencil\nover here.\"\n\n\"And over here is what appears to be the fragment of a hotel\nbill, which interests me deeply.\"\n\n\"There's nothing in it. I looked at it before,\" said Lestrade.\n\"'Oct. 4th, rooms 8s., breakfast 2s. 6d., cocktail 1s., lunch 2s.\n6d., glass sherry, 8d.' I see nothing in that.\"\n\n\"Very likely not. It is most important, all the same. As to the\nnote, it is important also, or at least the initials are, so I\ncongratulate you again.\"\n\n\"I've wasted time enough,\" said Lestrade, rising. \"I believe in\nhard work and not in sitting by the fire spinning fine theories.\nGood-day, Mr. Holmes, and we shall see which gets to the bottom\nof the matter first.\" He gathered up the garments, thrust them\ninto the bag, and made for the door.\n\n\"Just one hint to you, Lestrade,\" drawled Holmes before his rival\nvanished; \"I will tell you the true solution of the matter. Lady\nSt. Simon is a myth. There is not, and there never has been, any\nsuch person.\"\n\nLestrade looked sadly at my companion. Then he turned to me,\ntapped his forehead three times, shook his head solemnly, and\nhurried away.\n\nHe had hardly shut the door behind him when Holmes rose to put on\nhis overcoat. \"There is something in what the fellow says about\noutdoor work,\" he remarked, \"so I think, Watson, that I must\nleave you to your papers for a little.\"\n\nIt was after five o'clock when Sherlock Holmes left me, but I had\nno time to be lonely, for within an hour there arrived a\nconfectioner's man with a very large flat box. This he unpacked\nwith the help of a youth whom he had brought with him, and\npresently, to my very great astonishment, a quite epicurean\nlittle cold supper began to be laid out upon our humble\nlodging-house mahogany. There were a couple of brace of cold\nwoodcock, a pheasant, a pâté de foie gras pie with a group of\nancient and cobwebby bottles. Having laid out all these luxuries,\nmy two visitors vanished away, like the genii of the Arabian\nNights, with no explanation save that the things had been paid\nfor and were ordered to this address.\n\nJust before nine o'clock Sherlock Holmes stepped briskly into the\nroom. His features were gravely set, but there was a light in his\neye which made me think that he had not been disappointed in his\nconclusions.\n\n\"They have laid the supper, then,\" he said, rubbing his hands.\n\n\"You seem to expect company. They have laid for five.\"\n\n\"Yes, I fancy we may have some company dropping in,\" said he. \"I\nam surprised that Lord St. Simon has not already arrived. Ha! I\nfancy that I hear his step now upon the stairs.\"\n\nIt was indeed our visitor of the afternoon who came bustling in,\ndangling his glasses more vigorously than ever, and with a very\nperturbed expression upon his aristocratic features.\n\n\"My messenger reached you, then?\" asked Holmes.\n\n\"Yes, and I confess that the contents startled me beyond measure.\nHave you good authority for what you say?\"\n\n\"The best possible.\"\n\nLord St. Simon sank into a chair and passed his hand over his\nforehead.\n\n\"What will the Duke say,\" he murmured, \"when he hears that one of\nthe family has been subjected to such humiliation?\"\n\n\"It is the purest accident. I cannot allow that there is any\nhumiliation.\"\n\n\"Ah, you look on these things from another standpoint.\"\n\n\"I fail to see that anyone is to blame. I can hardly see how the\nlady could have acted otherwise, though her abrupt method of\ndoing it was undoubtedly to be regretted. Having no mother, she\nhad no one to advise her at such a crisis.\"\n\n\"It was a slight, sir, a public slight,\" said Lord St. Simon,\ntapping his fingers upon the table.\n\n\"You must make allowance for this poor girl, placed in so\nunprecedented a position.\"\n\n\"I will make no allowance. I am very angry indeed, and I have\nbeen shamefully used.\"\n\n\"I think that I heard a ring,\" said Holmes. \"Yes, there are steps\non the landing. If I cannot persuade you to take a lenient view\nof the matter, Lord St. Simon, I have brought an advocate here\nwho may be more successful.\" He opened the door and ushered in a\nlady and gentleman. \"Lord St. Simon,\" said he \"allow me to\nintroduce you to Mr. and Mrs. Francis Hay Moulton. The lady, I\nthink, you have already met.\"\n\nAt the sight of these newcomers our client had sprung from his\nseat and stood very erect, with his eyes cast down and his hand\nthrust into the breast of his frock-coat, a picture of offended\ndignity. The lady had taken a quick step forward and had held out\nher hand to him, but he still refused to raise his eyes. It was\nas well for his resolution, perhaps, for her pleading face was\none which it was hard to resist.\n\n\"You're angry, Robert,\" said she. \"Well, I guess you have every\ncause to be.\"\n\n\"Pray make no apology to me,\" said Lord St. Simon bitterly.\n\n\"Oh, yes, I know that I have treated you real bad and that I\nshould have spoken to you before I went; but I was kind of\nrattled, and from the time when I saw Frank here again I just\ndidn't know what I was doing or saying. I only wonder I didn't\nfall down and do a faint right there before the altar.\"\n\n\"Perhaps, Mrs. Moulton, you would like my friend and me to leave\nthe room while you explain this matter?\"\n\n\"If I may give an opinion,\" remarked the strange gentleman,\n\"we've had just a little too much secrecy over this business\nalready. For my part, I should like all Europe and America to\nhear the rights of it.\" He was a small, wiry, sunburnt man,\nclean-shaven, with a sharp face and alert manner.\n\n\"Then I'll tell our story right away,\" said the lady. \"Frank here\nand I met in '84, in McQuire's camp, near the Rockies, where pa\nwas working a claim. We were engaged to each other, Frank and I;\nbut then one day father struck a rich pocket and made a pile,\nwhile poor Frank here had a claim that petered out and came to\nnothing. The richer pa grew the poorer was Frank; so at last pa\nwouldn't hear of our engagement lasting any longer, and he took\nme away to 'Frisco. Frank wouldn't throw up his hand, though; so\nhe followed me there, and he saw me without pa knowing anything\nabout it. It would only have made him mad to know, so we just\nfixed it all up for ourselves. Frank said that he would go and\nmake his pile, too, and never come back to claim me until he had\nas much as pa. So then I promised to wait for him to the end of\ntime and pledged myself not to marry anyone else while he lived.\n'Why shouldn't we be married right away, then,' said he, 'and\nthen I will feel sure of you; and I won't claim to be your\nhusband until I come back?' Well, we talked it over, and he had\nfixed it all up so nicely, with a clergyman all ready in waiting,\nthat we just did it right there; and then Frank went off to seek\nhis fortune, and I went back to pa.\n\n\"The next I heard of Frank was that he was in Montana, and then\nhe went prospecting in Arizona, and then I heard of him from New\nMexico. After that came a long newspaper story about how a\nminers' camp had been attacked by Apache Indians, and there was\nmy Frank's name among the killed. I fainted dead away, and I was\nvery sick for months after. Pa thought I had a decline and took\nme to half the doctors in 'Frisco. Not a word of news came for a\nyear and more, so that I never doubted that Frank was really\ndead. Then Lord St. Simon came to 'Frisco, and we came to London,\nand a marriage was arranged, and pa was very pleased, but I felt\nall the time that no man on this earth would ever take the place\nin my heart that had been given to my poor Frank.\n\n\"Still, if I had married Lord St. Simon, of course I'd have done\nmy duty by him. We can't command our love, but we can our\nactions. I went to the altar with him with the intention to make\nhim just as good a wife as it was in me to be. But you may\nimagine what I felt when, just as I came to the altar rails, I\nglanced back and saw Frank standing and looking at me out of the\nfirst pew. I thought it was his ghost at first; but when I looked\nagain there he was still, with a kind of question in his eyes, as\nif to ask me whether I were glad or sorry to see him. I wonder I\ndidn't drop. I know that everything was turning round, and the\nwords of the clergyman were just like the buzz of a bee in my\near. I didn't know what to do. Should I stop the service and make\na scene in the church? I glanced at him again, and he seemed to\nknow what I was thinking, for he raised his finger to his lips to\ntell me to be still. Then I saw him scribble on a piece of paper,\nand I knew that he was writing me a note. As I passed his pew on\nthe way out I dropped my bouquet over to him, and he slipped the\nnote into my hand when he returned me the flowers. It was only a\nline asking me to join him when he made the sign to me to do so.\nOf course I never doubted for a moment that my first duty was now\nto him, and I determined to do just whatever he might direct.\n\n\"When I got back I told my maid, who had known him in California,\nand had always been his friend. I ordered her to say nothing, but\nto get a few things packed and my ulster ready. I know I ought to\nhave spoken to Lord St. Simon, but it was dreadful hard before\nhis mother and all those great people. I just made up my mind to\nrun away and explain afterwards. I hadn't been at the table ten\nminutes before I saw Frank out of the window at the other side of\nthe road. He beckoned to me and then began walking into the Park.\nI slipped out, put on my things, and followed him. Some woman\ncame talking something or other about Lord St. Simon to\nme--seemed to me from the little I heard as if he had a little\nsecret of his own before marriage also--but I managed to get away\nfrom her and soon overtook Frank. We got into a cab together, and\naway we drove to some lodgings he had taken in Gordon Square, and\nthat was my true wedding after all those years of waiting. Frank\nhad been a prisoner among the Apaches, had escaped, came on to\n'Frisco, found that I had given him up for dead and had gone to\nEngland, followed me there, and had come upon me at last on the\nvery morning of my second wedding.\"\n\n\"I saw it in a paper,\" explained the American. \"It gave the name\nand the church but not where the lady lived.\"\n\n\"Then we had a talk as to what we should do, and Frank was all\nfor openness, but I was so ashamed of it all that I felt as if I\nshould like to vanish away and never see any of them again--just\nsending a line to pa, perhaps, to show him that I was alive. It\nwas awful to me to think of all those lords and ladies sitting\nround that breakfast-table and waiting for me to come back. So\nFrank took my wedding-clothes and things and made a bundle of\nthem, so that I should not be traced, and dropped them away\nsomewhere where no one could find them. It is likely that we\nshould have gone on to Paris to-morrow, only that this good\ngentleman, Mr. Holmes, came round to us this evening, though how\nhe found us is more than I can think, and he showed us very\nclearly and kindly that I was wrong and that Frank was right, and\nthat we should be putting ourselves in the wrong if we were so\nsecret. Then he offered to give us a chance of talking to Lord\nSt. Simon alone, and so we came right away round to his rooms at\nonce. Now, Robert, you have heard it all, and I am very sorry if\nI have given you pain, and I hope that you do not think very\nmeanly of me.\"\n\nLord St. Simon had by no means relaxed his rigid attitude, but\nhad listened with a frowning brow and a compressed lip to this\nlong narrative.\n\n\"Excuse me,\" he said, \"but it is not my custom to discuss my most\nintimate personal affairs in this public manner.\"\n\n\"Then you won't forgive me? You won't shake hands before I go?\"\n\n\"Oh, certainly, if it would give you any pleasure.\" He put out\nhis hand and coldly grasped that which she extended to him.\n\n\"I had hoped,\" suggested Holmes, \"that you would have joined us\nin a friendly supper.\"\n\n\"I think that there you ask a little too much,\" responded his\nLordship. \"I may be forced to acquiesce in these recent\ndevelopments, but I can hardly be expected to make merry over\nthem. I think that with your permission I will now wish you all a\nvery good-night.\" He included us all in a sweeping bow and\nstalked out of the room.\n\n\"Then I trust that you at least will honour me with your\ncompany,\" said Sherlock Holmes. \"It is always a joy to meet an\nAmerican, Mr. Moulton, for I am one of those who believe that the\nfolly of a monarch and the blundering of a minister in far-gone\nyears will not prevent our children from being some day citizens\nof the same world-wide country under a flag which shall be a\nquartering of the Union Jack with the Stars and Stripes.\"\n\n\"The case has been an interesting one,\" remarked Holmes when our\nvisitors had left us, \"because it serves to show very clearly how\nsimple the explanation may be of an affair which at first sight\nseems to be almost inexplicable. Nothing could be more natural\nthan the sequence of events as narrated by this lady, and nothing\nstranger than the result when viewed, for instance, by Mr.\nLestrade of Scotland Yard.\"\n\n\"You were not yourself at fault at all, then?\"\n\n\"From the first, two facts were very obvious to me, the one that\nthe lady had been quite willing to undergo the wedding ceremony,\nthe other that she had repented of it within a few minutes of\nreturning home. Obviously something had occurred during the\nmorning, then, to cause her to change her mind. What could that\nsomething be? She could not have spoken to anyone when she was\nout, for she had been in the company of the bridegroom. Had she\nseen someone, then? If she had, it must be someone from America\nbecause she had spent so short a time in this country that she\ncould hardly have allowed anyone to acquire so deep an influence\nover her that the mere sight of him would induce her to change\nher plans so completely. You see we have already arrived, by a\nprocess of exclusion, at the idea that she might have seen an\nAmerican. Then who could this American be, and why should he\npossess so much influence over her? It might be a lover; it might\nbe a husband. Her young womanhood had, I knew, been spent in\nrough scenes and under strange conditions. So far I had got\nbefore I ever heard Lord St. Simon's narrative. When he told us\nof a man in a pew, of the change in the bride's manner, of so\ntransparent a device for obtaining a note as the dropping of a\nbouquet, of her resort to her confidential maid, and of her very\nsignificant allusion to claim-jumping--which in miners' parlance\nmeans taking possession of that which another person has a prior\nclaim to--the whole situation became absolutely clear. She had\ngone off with a man, and the man was either a lover or was a\nprevious husband--the chances being in favour of the latter.\"\n\n\"And how in the world did you find them?\"\n\n\"It might have been difficult, but friend Lestrade held\ninformation in his hands the value of which he did not himself\nknow. The initials were, of course, of the highest importance,\nbut more valuable still was it to know that within a week he had\nsettled his bill at one of the most select London hotels.\"\n\n\"How did you deduce the select?\"\n\n\"By the select prices. Eight shillings for a bed and eightpence\nfor a glass of sherry pointed to one of the most expensive\nhotels. There are not many in London which charge at that rate.\nIn the second one which I visited in Northumberland Avenue, I\nlearned by an inspection of the book that Francis H. Moulton, an\nAmerican gentleman, had left only the day before, and on looking\nover the entries against him, I came upon the very items which I\nhad seen in the duplicate bill. His letters were to be forwarded\nto 226 Gordon Square; so thither I travelled, and being fortunate\nenough to find the loving couple at home, I ventured to give them\nsome paternal advice and to point out to them that it would be\nbetter in every way that they should make their position a little\nclearer both to the general public and to Lord St. Simon in\nparticular. I invited them to meet him here, and, as you see, I\nmade him keep the appointment.\"\n\n\"But with no very good result,\" I remarked. \"His conduct was\ncertainly not very gracious.\"\n\n\"Ah, Watson,\" said Holmes, smiling, \"perhaps you would not be\nvery gracious either, if, after all the trouble of wooing and\nwedding, you found yourself deprived in an instant of wife and of\nfortune. I think that we may judge Lord St. Simon very mercifully\nand thank our stars that we are never likely to find ourselves in\nthe same position. Draw your chair up and hand me my violin, for\nthe only problem we have still to solve is how to while away\nthese bleak autumnal evenings.\"\n\n\n\nXI. THE ADVENTURE OF THE BERYL CORONET\n\n\"Holmes,\" said I as I stood one morning in our bow-window looking\ndown the street, \"here is a madman coming along. It seems rather\nsad that his relatives should allow him to come out alone.\"\n\nMy friend rose lazily from his armchair and stood with his hands\nin the pockets of his dressing-gown, looking over my shoulder. It\nwas a bright, crisp February morning, and the snow of the day\nbefore still lay deep upon the ground, shimmering brightly in the\nwintry sun. Down the centre of Baker Street it had been ploughed\ninto a brown crumbly band by the traffic, but at either side and\non the heaped-up edges of the foot-paths it still lay as white as\nwhen it fell. The grey pavement had been cleaned and scraped, but\nwas still dangerously slippery, so that there were fewer\npassengers than usual. Indeed, from the direction of the\nMetropolitan Station no one was coming save the single gentleman\nwhose eccentric conduct had drawn my attention.\n\nHe was a man of about fifty, tall, portly, and imposing, with a\nmassive, strongly marked face and a commanding figure. He was\ndressed in a sombre yet rich style, in black frock-coat, shining\nhat, neat brown gaiters, and well-cut pearl-grey trousers. Yet\nhis actions were in absurd contrast to the dignity of his dress\nand features, for he was running hard, with occasional little\nsprings, such as a weary man gives who is little accustomed to\nset any tax upon his legs. As he ran he jerked his hands up and\ndown, waggled his head, and writhed his face into the most\nextraordinary contortions.\n\n\"What on earth can be the matter with him?\" I asked. \"He is\nlooking up at the numbers of the houses.\"\n\n\"I believe that he is coming here,\" said Holmes, rubbing his\nhands.\n\n\"Here?\"\n\n\"Yes; I rather think he is coming to consult me professionally. I\nthink that I recognise the symptoms. Ha! did I not tell you?\" As\nhe spoke, the man, puffing and blowing, rushed at our door and\npulled at our bell until the whole house resounded with the\nclanging.\n\nA few moments later he was in our room, still puffing, still\ngesticulating, but with so fixed a look of grief and despair in\nhis eyes that our smiles were turned in an instant to horror and\npity. For a while he could not get his words out, but swayed his\nbody and plucked at his hair like one who has been driven to the\nextreme limits of his reason. Then, suddenly springing to his\nfeet, he beat his head against the wall with such force that we\nboth rushed upon him and tore him away to the centre of the room.\nSherlock Holmes pushed him down into the easy-chair and, sitting\nbeside him, patted his hand and chatted with him in the easy,\nsoothing tones which he knew so well how to employ.\n\n\"You have come to me to tell your story, have you not?\" said he.\n\"You are fatigued with your haste. Pray wait until you have\nrecovered yourself, and then I shall be most happy to look into\nany little problem which you may submit to me.\"\n\nThe man sat for a minute or more with a heaving chest, fighting\nagainst his emotion. Then he passed his handkerchief over his\nbrow, set his lips tight, and turned his face towards us.\n\n\"No doubt you think me mad?\" said he.\n\n\"I see that you have had some great trouble,\" responded Holmes.\n\n\"God knows I have!--a trouble which is enough to unseat my\nreason, so sudden and so terrible is it. Public disgrace I might\nhave faced, although I am a man whose character has never yet\nborne a stain. Private affliction also is the lot of every man;\nbut the two coming together, and in so frightful a form, have\nbeen enough to shake my very soul. Besides, it is not I alone.\nThe very noblest in the land may suffer unless some way be found\nout of this horrible affair.\"\n\n\"Pray compose yourself, sir,\" said Holmes, \"and let me have a\nclear account of who you are and what it is that has befallen\nyou.\"\n\n\"My name,\" answered our visitor, \"is probably familiar to your\nears. I am Alexander Holder, of the banking firm of Holder &\nStevenson, of Threadneedle Street.\"\n\nThe name was indeed well known to us as belonging to the senior\npartner in the second largest private banking concern in the City\nof London. What could have happened, then, to bring one of the\nforemost citizens of London to this most pitiable pass? We\nwaited, all curiosity, until with another effort he braced\nhimself to tell his story.\n\n\"I feel that time is of value,\" said he; \"that is why I hastened\nhere when the police inspector suggested that I should secure\nyour co-operation. I came to Baker Street by the Underground and\nhurried from there on foot, for the cabs go slowly through this\nsnow. That is why I was so out of breath, for I am a man who\ntakes very little exercise. I feel better now, and I will put the\nfacts before you as shortly and yet as clearly as I can.\n\n\"It is, of course, well known to you that in a successful banking\nbusiness as much depends upon our being able to find remunerative\ninvestments for our funds as upon our increasing our connection\nand the number of our depositors. One of our most lucrative means\nof laying out money is in the shape of loans, where the security\nis unimpeachable. We have done a good deal in this direction\nduring the last few years, and there are many noble families to\nwhom we have advanced large sums upon the security of their\npictures, libraries, or plate.\n\n\"Yesterday morning I was seated in my office at the bank when a\ncard was brought in to me by one of the clerks. I started when I\nsaw the name, for it was that of none other than--well, perhaps\neven to you I had better say no more than that it was a name\nwhich is a household word all over the earth--one of the highest,\nnoblest, most exalted names in England. I was overwhelmed by the\nhonour and attempted, when he entered, to say so, but he plunged\nat once into business with the air of a man who wishes to hurry\nquickly through a disagreeable task.\n\n\"'Mr. Holder,' said he, 'I have been informed that you are in the\nhabit of advancing money.'\n\n\"'The firm does so when the security is good.' I answered.\n\n\"'It is absolutely essential to me,' said he, 'that I should have\n50,000 pounds at once. I could, of course, borrow so trifling a\nsum ten times over from my friends, but I much prefer to make it\na matter of business and to carry out that business myself. In my\nposition you can readily understand that it is unwise to place\none's self under obligations.'\n\n\"'For how long, may I ask, do you want this sum?' I asked.\n\n\"'Next Monday I have a large sum due to me, and I shall then most\ncertainly repay what you advance, with whatever interest you\nthink it right to charge. But it is very essential to me that the\nmoney should be paid at once.'\n\n\"'I should be happy to advance it without further parley from my\nown private purse,' said I, 'were it not that the strain would be\nrather more than it could bear. If, on the other hand, I am to do\nit in the name of the firm, then in justice to my partner I must\ninsist that, even in your case, every businesslike precaution\nshould be taken.'\n\n\"'I should much prefer to have it so,' said he, raising up a\nsquare, black morocco case which he had laid beside his chair.\n'You have doubtless heard of the Beryl Coronet?'\n\n\"'One of the most precious public possessions of the empire,'\nsaid I.\n\n\"'Precisely.' He opened the case, and there, imbedded in soft,\nflesh-coloured velvet, lay the magnificent piece of jewellery\nwhich he had named. 'There are thirty-nine enormous beryls,' said\nhe, 'and the price of the gold chasing is incalculable. The\nlowest estimate would put the worth of the coronet at double the\nsum which I have asked. I am prepared to leave it with you as my\nsecurity.'\n\n\"I took the precious case into my hands and looked in some\nperplexity from it to my illustrious client.\n\n\"'You doubt its value?' he asked.\n\n\"'Not at all. I only doubt--'\n\n\"'The propriety of my leaving it. You may set your mind at rest\nabout that. I should not dream of doing so were it not absolutely\ncertain that I should be able in four days to reclaim it. It is a\npure matter of form. Is the security sufficient?'\n\n\"'Ample.'\n\n\"'You understand, Mr. Holder, that I am giving you a strong proof\nof the confidence which I have in you, founded upon all that I\nhave heard of you. I rely upon you not only to be discreet and to\nrefrain from all gossip upon the matter but, above all, to\npreserve this coronet with every possible precaution because I\nneed not say that a great public scandal would be caused if any\nharm were to befall it. Any injury to it would be almost as\nserious as its complete loss, for there are no beryls in the\nworld to match these, and it would be impossible to replace them.\nI leave it with you, however, with every confidence, and I shall\ncall for it in person on Monday morning.'\n\n\"Seeing that my client was anxious to leave, I said no more but,\ncalling for my cashier, I ordered him to pay over fifty 1000\npound notes. When I was alone once more, however, with the\nprecious case lying upon the table in front of me, I could not\nbut think with some misgivings of the immense responsibility\nwhich it entailed upon me. There could be no doubt that, as it\nwas a national possession, a horrible scandal would ensue if any\nmisfortune should occur to it. I already regretted having ever\nconsented to take charge of it. However, it was too late to alter\nthe matter now, so I locked it up in my private safe and turned\nonce more to my work.\n\n\"When evening came I felt that it would be an imprudence to leave\nso precious a thing in the office behind me. Bankers' safes had\nbeen forced before now, and why should not mine be? If so, how\nterrible would be the position in which I should find myself! I\ndetermined, therefore, that for the next few days I would always\ncarry the case backward and forward with me, so that it might\nnever be really out of my reach. With this intention, I called a\ncab and drove out to my house at Streatham, carrying the jewel\nwith me. I did not breathe freely until I had taken it upstairs\nand locked it in the bureau of my dressing-room.\n\n\"And now a word as to my household, Mr. Holmes, for I wish you to\nthoroughly understand the situation. My groom and my page sleep\nout of the house, and may be set aside altogether. I have three\nmaid-servants who have been with me a number of years and whose\nabsolute reliability is quite above suspicion. Another, Lucy\nParr, the second waiting-maid, has only been in my service a few\nmonths. She came with an excellent character, however, and has\nalways given me satisfaction. She is a very pretty girl and has\nattracted admirers who have occasionally hung about the place.\nThat is the only drawback which we have found to her, but we\nbelieve her to be a thoroughly good girl in every way.\n\n\"So much for the servants. My family itself is so small that it\nwill not take me long to describe it. I am a widower and have an\nonly son, Arthur. He has been a disappointment to me, Mr.\nHolmes--a grievous disappointment. I have no doubt that I am\nmyself to blame. People tell me that I have spoiled him. Very\nlikely I have. When my dear wife died I felt that he was all I\nhad to love. I could not bear to see the smile fade even for a\nmoment from his face. I have never denied him a wish. Perhaps it\nwould have been better for both of us had I been sterner, but I\nmeant it for the best.\n\n\"It was naturally my intention that he should succeed me in my\nbusiness, but he was not of a business turn. He was wild,\nwayward, and, to speak the truth, I could not trust him in the\nhandling of large sums of money. When he was young he became a\nmember of an aristocratic club, and there, having charming\nmanners, he was soon the intimate of a number of men with long\npurses and expensive habits. He learned to play heavily at cards\nand to squander money on the turf, until he had again and again\nto come to me and implore me to give him an advance upon his\nallowance, that he might settle his debts of honour. He tried\nmore than once to break away from the dangerous company which he\nwas keeping, but each time the influence of his friend, Sir\nGeorge Burnwell, was enough to draw him back again.\n\n\"And, indeed, I could not wonder that such a man as Sir George\nBurnwell should gain an influence over him, for he has frequently\nbrought him to my house, and I have found myself that I could\nhardly resist the fascination of his manner. He is older than\nArthur, a man of the world to his finger-tips, one who had been\neverywhere, seen everything, a brilliant talker, and a man of\ngreat personal beauty. Yet when I think of him in cold blood, far\naway from the glamour of his presence, I am convinced from his\ncynical speech and the look which I have caught in his eyes that\nhe is one who should be deeply distrusted. So I think, and so,\ntoo, thinks my little Mary, who has a woman's quick insight into\ncharacter.\n\n\"And now there is only she to be described. She is my niece; but\nwhen my brother died five years ago and left her alone in the\nworld I adopted her, and have looked upon her ever since as my\ndaughter. She is a sunbeam in my house--sweet, loving, beautiful,\na wonderful manager and housekeeper, yet as tender and quiet and\ngentle as a woman could be. She is my right hand. I do not know\nwhat I could do without her. In only one matter has she ever gone\nagainst my wishes. Twice my boy has asked her to marry him, for\nhe loves her devotedly, but each time she has refused him. I\nthink that if anyone could have drawn him into the right path it\nwould have been she, and that his marriage might have changed his\nwhole life; but now, alas! it is too late--forever too late!\n\n\"Now, Mr. Holmes, you know the people who live under my roof, and\nI shall continue with my miserable story.\n\n\"When we were taking coffee in the drawing-room that night after\ndinner, I told Arthur and Mary my experience, and of the precious\ntreasure which we had under our roof, suppressing only the name\nof my client. Lucy Parr, who had brought in the coffee, had, I am\nsure, left the room; but I cannot swear that the door was closed.\nMary and Arthur were much interested and wished to see the famous\ncoronet, but I thought it better not to disturb it.\n\n\"'Where have you put it?' asked Arthur.\n\n\"'In my own bureau.'\n\n\"'Well, I hope to goodness the house won't be burgled during the\nnight.' said he.\n\n\"'It is locked up,' I answered.\n\n\"'Oh, any old key will fit that bureau. When I was a youngster I\nhave opened it myself with the key of the box-room cupboard.'\n\n\"He often had a wild way of talking, so that I thought little of\nwhat he said. He followed me to my room, however, that night with\na very grave face.\n\n\"'Look here, dad,' said he with his eyes cast down, 'can you let\nme have 200 pounds?'\n\n\"'No, I cannot!' I answered sharply. 'I have been far too\ngenerous with you in money matters.'\n\n\"'You have been very kind,' said he, 'but I must have this money,\nor else I can never show my face inside the club again.'\n\n\"'And a very good thing, too!' I cried.\n\n\"'Yes, but you would not have me leave it a dishonoured man,'\nsaid he. 'I could not bear the disgrace. I must raise the money\nin some way, and if you will not let me have it, then I must try\nother means.'\n\n\"I was very angry, for this was the third demand during the\nmonth. 'You shall not have a farthing from me,' I cried, on which\nhe bowed and left the room without another word.\n\n\"When he was gone I unlocked my bureau, made sure that my\ntreasure was safe, and locked it again. Then I started to go\nround the house to see that all was secure--a duty which I\nusually leave to Mary but which I thought it well to perform\nmyself that night. As I came down the stairs I saw Mary herself\nat the side window of the hall, which she closed and fastened as\nI approached.\n\n\"'Tell me, dad,' said she, looking, I thought, a little\ndisturbed, 'did you give Lucy, the maid, leave to go out\nto-night?'\n\n\"'Certainly not.'\n\n\"'She came in just now by the back door. I have no doubt that she\nhas only been to the side gate to see someone, but I think that\nit is hardly safe and should be stopped.'\n\n\"'You must speak to her in the morning, or I will if you prefer\nit. Are you sure that everything is fastened?'\n\n\"'Quite sure, dad.'\n\n\"'Then, good-night.' I kissed her and went up to my bedroom\nagain, where I was soon asleep.\n\n\"I am endeavouring to tell you everything, Mr. Holmes, which may\nhave any bearing upon the case, but I beg that you will question\nme upon any point which I do not make clear.\"\n\n\"On the contrary, your statement is singularly lucid.\"\n\n\"I come to a part of my story now in which I should wish to be\nparticularly so. I am not a very heavy sleeper, and the anxiety\nin my mind tended, no doubt, to make me even less so than usual.\nAbout two in the morning, then, I was awakened by some sound in\nthe house. It had ceased ere I was wide awake, but it had left an\nimpression behind it as though a window had gently closed\nsomewhere. I lay listening with all my ears. Suddenly, to my\nhorror, there was a distinct sound of footsteps moving softly in\nthe next room. I slipped out of bed, all palpitating with fear,\nand peeped round the corner of my dressing-room door.\n\n\"'Arthur!' I screamed, 'you villain! you thief! How dare you\ntouch that coronet?'\n\n\"The gas was half up, as I had left it, and my unhappy boy,\ndressed only in his shirt and trousers, was standing beside the\nlight, holding the coronet in his hands. He appeared to be\nwrenching at it, or bending it with all his strength. At my cry\nhe dropped it from his grasp and turned as pale as death. I\nsnatched it up and examined it. One of the gold corners, with\nthree of the beryls in it, was missing.\n\n\"'You blackguard!' I shouted, beside myself with rage. 'You have\ndestroyed it! You have dishonoured me forever! Where are the\njewels which you have stolen?'\n\n\"'Stolen!' he cried.\n\n\"'Yes, thief!' I roared, shaking him by the shoulder.\n\n\"'There are none missing. There cannot be any missing,' said he.\n\n\"'There are three missing. And you know where they are. Must I\ncall you a liar as well as a thief? Did I not see you trying to\ntear off another piece?'\n\n\"'You have called me names enough,' said he, 'I will not stand it\nany longer. I shall not say another word about this business,\nsince you have chosen to insult me. I will leave your house in\nthe morning and make my own way in the world.'\n\n\"'You shall leave it in the hands of the police!' I cried\nhalf-mad with grief and rage. 'I shall have this matter probed to\nthe bottom.'\n\n\"'You shall learn nothing from me,' said he with a passion such\nas I should not have thought was in his nature. 'If you choose to\ncall the police, let the police find what they can.'\n\n\"By this time the whole house was astir, for I had raised my\nvoice in my anger. Mary was the first to rush into my room, and,\nat the sight of the coronet and of Arthur's face, she read the\nwhole story and, with a scream, fell down senseless on the\nground. I sent the house-maid for the police and put the\ninvestigation into their hands at once. When the inspector and a\nconstable entered the house, Arthur, who had stood sullenly with\nhis arms folded, asked me whether it was my intention to charge\nhim with theft. I answered that it had ceased to be a private\nmatter, but had become a public one, since the ruined coronet was\nnational property. I was determined that the law should have its\nway in everything.\n\n\"'At least,' said he, 'you will not have me arrested at once. It\nwould be to your advantage as well as mine if I might leave the\nhouse for five minutes.'\n\n\"'That you may get away, or perhaps that you may conceal what you\nhave stolen,' said I. And then, realising the dreadful position\nin which I was placed, I implored him to remember that not only\nmy honour but that of one who was far greater than I was at\nstake; and that he threatened to raise a scandal which would\nconvulse the nation. He might avert it all if he would but tell\nme what he had done with the three missing stones.\n\n\"'You may as well face the matter,' said I; 'you have been caught\nin the act, and no confession could make your guilt more heinous.\nIf you but make such reparation as is in your power, by telling\nus where the beryls are, all shall be forgiven and forgotten.'\n\n\"'Keep your forgiveness for those who ask for it,' he answered,\nturning away from me with a sneer. I saw that he was too hardened\nfor any words of mine to influence him. There was but one way for\nit. I called in the inspector and gave him into custody. A search\nwas made at once not only of his person but of his room and of\nevery portion of the house where he could possibly have concealed\nthe gems; but no trace of them could be found, nor would the\nwretched boy open his mouth for all our persuasions and our\nthreats. This morning he was removed to a cell, and I, after\ngoing through all the police formalities, have hurried round to\nyou to implore you to use your skill in unravelling the matter.\nThe police have openly confessed that they can at present make\nnothing of it. You may go to any expense which you think\nnecessary. I have already offered a reward of 1000 pounds. My\nGod, what shall I do! I have lost my honour, my gems, and my son\nin one night. Oh, what shall I do!\"\n\nHe put a hand on either side of his head and rocked himself to\nand fro, droning to himself like a child whose grief has got\nbeyond words.\n\nSherlock Holmes sat silent for some few minutes, with his brows\nknitted and his eyes fixed upon the fire.\n\n\"Do you receive much company?\" he asked.\n\n\"None save my partner with his family and an occasional friend of\nArthur's. Sir George Burnwell has been several times lately. No\none else, I think.\"\n\n\"Do you go out much in society?\"\n\n\"Arthur does. Mary and I stay at home. We neither of us care for\nit.\"\n\n\"That is unusual in a young girl.\"\n\n\"She is of a quiet nature. Besides, she is not so very young. She\nis four-and-twenty.\"\n\n\"This matter, from what you say, seems to have been a shock to\nher also.\"\n\n\"Terrible! She is even more affected than I.\"\n\n\"You have neither of you any doubt as to your son's guilt?\"\n\n\"How can we have when I saw him with my own eyes with the coronet\nin his hands.\"\n\n\"I hardly consider that a conclusive proof. Was the remainder of\nthe coronet at all injured?\"\n\n\"Yes, it was twisted.\"\n\n\"Do you not think, then, that he might have been trying to\nstraighten it?\"\n\n\"God bless you! You are doing what you can for him and for me.\nBut it is too heavy a task. What was he doing there at all? If\nhis purpose were innocent, why did he not say so?\"\n\n\"Precisely. And if it were guilty, why did he not invent a lie?\nHis silence appears to me to cut both ways. There are several\nsingular points about the case. What did the police think of the\nnoise which awoke you from your sleep?\"\n\n\"They considered that it might be caused by Arthur's closing his\nbedroom door.\"\n\n\"A likely story! As if a man bent on felony would slam his door\nso as to wake a household. What did they say, then, of the\ndisappearance of these gems?\"\n\n\"They are still sounding the planking and probing the furniture\nin the hope of finding them.\"\n\n\"Have they thought of looking outside the house?\"\n\n\"Yes, they have shown extraordinary energy. The whole garden has\nalready been minutely examined.\"\n\n\"Now, my dear sir,\" said Holmes, \"is it not obvious to you now\nthat this matter really strikes very much deeper than either you\nor the police were at first inclined to think? It appeared to you\nto be a simple case; to me it seems exceedingly complex. Consider\nwhat is involved by your theory. You suppose that your son came\ndown from his bed, went, at great risk, to your dressing-room,\nopened your bureau, took out your coronet, broke off by main\nforce a small portion of it, went off to some other place,\nconcealed three gems out of the thirty-nine, with such skill that\nnobody can find them, and then returned with the other thirty-six\ninto the room in which he exposed himself to the greatest danger\nof being discovered. I ask you now, is such a theory tenable?\"\n\n\"But what other is there?\" cried the banker with a gesture of\ndespair. \"If his motives were innocent, why does he not explain\nthem?\"\n\n\"It is our task to find that out,\" replied Holmes; \"so now, if\nyou please, Mr. Holder, we will set off for Streatham together,\nand devote an hour to glancing a little more closely into\ndetails.\"\n\nMy friend insisted upon my accompanying them in their expedition,\nwhich I was eager enough to do, for my curiosity and sympathy\nwere deeply stirred by the story to which we had listened. I\nconfess that the guilt of the banker's son appeared to me to be\nas obvious as it did to his unhappy father, but still I had such\nfaith in Holmes' judgment that I felt that there must be some\ngrounds for hope as long as he was dissatisfied with the accepted\nexplanation. He hardly spoke a word the whole way out to the\nsouthern suburb, but sat with his chin upon his breast and his\nhat drawn over his eyes, sunk in the deepest thought. Our client\nappeared to have taken fresh heart at the little glimpse of hope\nwhich had been presented to him, and he even broke into a\ndesultory chat with me over his business affairs. A short railway\njourney and a shorter walk brought us to Fairbank, the modest\nresidence of the great financier.\n\nFairbank was a good-sized square house of white stone, standing\nback a little from the road. A double carriage-sweep, with a\nsnow-clad lawn, stretched down in front to two large iron gates\nwhich closed the entrance. On the right side was a small wooden\nthicket, which led into a narrow path between two neat hedges\nstretching from the road to the kitchen door, and forming the\ntradesmen's entrance. On the left ran a lane which led to the\nstables, and was not itself within the grounds at all, being a\npublic, though little used, thoroughfare. Holmes left us standing\nat the door and walked slowly all round the house, across the\nfront, down the tradesmen's path, and so round by the garden\nbehind into the stable lane. So long was he that Mr. Holder and I\nwent into the dining-room and waited by the fire until he should\nreturn. We were sitting there in silence when the door opened and\na young lady came in. She was rather above the middle height,\nslim, with dark hair and eyes, which seemed the darker against\nthe absolute pallor of her skin. I do not think that I have ever\nseen such deadly paleness in a woman's face. Her lips, too, were\nbloodless, but her eyes were flushed with crying. As she swept\nsilently into the room she impressed me with a greater sense of\ngrief than the banker had done in the morning, and it was the\nmore striking in her as she was evidently a woman of strong\ncharacter, with immense capacity for self-restraint. Disregarding\nmy presence, she went straight to her uncle and passed her hand\nover his head with a sweet womanly caress.\n\n\"You have given orders that Arthur should be liberated, have you\nnot, dad?\" she asked.\n\n\"No, no, my girl, the matter must be probed to the bottom.\"\n\n\"But I am so sure that he is innocent. You know what woman's\ninstincts are. I know that he has done no harm and that you will\nbe sorry for having acted so harshly.\"\n\n\"Why is he silent, then, if he is innocent?\"\n\n\"Who knows? Perhaps because he was so angry that you should\nsuspect him.\"\n\n\"How could I help suspecting him, when I actually saw him with\nthe coronet in his hand?\"\n\n\"Oh, but he had only picked it up to look at it. Oh, do, do take\nmy word for it that he is innocent. Let the matter drop and say\nno more. It is so dreadful to think of our dear Arthur in\nprison!\"\n\n\"I shall never let it drop until the gems are found--never, Mary!\nYour affection for Arthur blinds you as to the awful consequences\nto me. Far from hushing the thing up, I have brought a gentleman\ndown from London to inquire more deeply into it.\"\n\n\"This gentleman?\" she asked, facing round to me.\n\n\"No, his friend. He wished us to leave him alone. He is round in\nthe stable lane now.\"\n\n\"The stable lane?\" She raised her dark eyebrows. \"What can he\nhope to find there? Ah! this, I suppose, is he. I trust, sir,\nthat you will succeed in proving, what I feel sure is the truth,\nthat my cousin Arthur is innocent of this crime.\"\n\n\"I fully share your opinion, and I trust, with you, that we may\nprove it,\" returned Holmes, going back to the mat to knock the\nsnow from his shoes. \"I believe I have the honour of addressing\nMiss Mary Holder. Might I ask you a question or two?\"\n\n\"Pray do, sir, if it may help to clear this horrible affair up.\"\n\n\"You heard nothing yourself last night?\"\n\n\"Nothing, until my uncle here began to speak loudly. I heard\nthat, and I came down.\"\n\n\"You shut up the windows and doors the night before. Did you\nfasten all the windows?\"\n\n\"Yes.\"\n\n\"Were they all fastened this morning?\"\n\n\"Yes.\"\n\n\"You have a maid who has a sweetheart? I think that you remarked\nto your uncle last night that she had been out to see him?\"\n\n\"Yes, and she was the girl who waited in the drawing-room, and\nwho may have heard uncle's remarks about the coronet.\"\n\n\"I see. You infer that she may have gone out to tell her\nsweetheart, and that the two may have planned the robbery.\"\n\n\"But what is the good of all these vague theories,\" cried the\nbanker impatiently, \"when I have told you that I saw Arthur with\nthe coronet in his hands?\"\n\n\"Wait a little, Mr. Holder. We must come back to that. About this\ngirl, Miss Holder. You saw her return by the kitchen door, I\npresume?\"\n\n\"Yes; when I went to see if the door was fastened for the night I\nmet her slipping in. I saw the man, too, in the gloom.\"\n\n\"Do you know him?\"\n\n\"Oh, yes! he is the green-grocer who brings our vegetables round.\nHis name is Francis Prosper.\"\n\n\"He stood,\" said Holmes, \"to the left of the door--that is to\nsay, farther up the path than is necessary to reach the door?\"\n\n\"Yes, he did.\"\n\n\"And he is a man with a wooden leg?\"\n\nSomething like fear sprang up in the young lady's expressive\nblack eyes. \"Why, you are like a magician,\" said she. \"How do you\nknow that?\" She smiled, but there was no answering smile in\nHolmes' thin, eager face.\n\n\"I should be very glad now to go upstairs,\" said he. \"I shall\nprobably wish to go over the outside of the house again. Perhaps\nI had better take a look at the lower windows before I go up.\"\n\nHe walked swiftly round from one to the other, pausing only at\nthe large one which looked from the hall onto the stable lane.\nThis he opened and made a very careful examination of the sill\nwith his powerful magnifying lens. \"Now we shall go upstairs,\"\nsaid he at last.\n\nThe banker's dressing-room was a plainly furnished little\nchamber, with a grey carpet, a large bureau, and a long mirror.\nHolmes went to the bureau first and looked hard at the lock.\n\n\"Which key was used to open it?\" he asked.\n\n\"That which my son himself indicated--that of the cupboard of the\nlumber-room.\"\n\n\"Have you it here?\"\n\n\"That is it on the dressing-table.\"\n\nSherlock Holmes took it up and opened the bureau.\n\n\"It is a noiseless lock,\" said he. \"It is no wonder that it did\nnot wake you. This case, I presume, contains the coronet. We must\nhave a look at it.\" He opened the case, and taking out the diadem\nhe laid it upon the table. It was a magnificent specimen of the\njeweller's art, and the thirty-six stones were the finest that I\nhave ever seen. At one side of the coronet was a cracked edge,\nwhere a corner holding three gems had been torn away.\n\n\"Now, Mr. Holder,\" said Holmes, \"here is the corner which\ncorresponds to that which has been so unfortunately lost. Might I\nbeg that you will break it off.\"\n\nThe banker recoiled in horror. \"I should not dream of trying,\"\nsaid he.\n\n\"Then I will.\" Holmes suddenly bent his strength upon it, but\nwithout result. \"I feel it give a little,\" said he; \"but, though\nI am exceptionally strong in the fingers, it would take me all my\ntime to break it. An ordinary man could not do it. Now, what do\nyou think would happen if I did break it, Mr. Holder? There would\nbe a noise like a pistol shot. Do you tell me that all this\nhappened within a few yards of your bed and that you heard\nnothing of it?\"\n\n\"I do not know what to think. It is all dark to me.\"\n\n\"But perhaps it may grow lighter as we go. What do you think,\nMiss Holder?\"\n\n\"I confess that I still share my uncle's perplexity.\"\n\n\"Your son had no shoes or slippers on when you saw him?\"\n\n\"He had nothing on save only his trousers and shirt.\"\n\n\"Thank you. We have certainly been favoured with extraordinary\nluck during this inquiry, and it will be entirely our own fault\nif we do not succeed in clearing the matter up. With your\npermission, Mr. Holder, I shall now continue my investigations\noutside.\"\n\nHe went alone, at his own request, for he explained that any\nunnecessary footmarks might make his task more difficult. For an\nhour or more he was at work, returning at last with his feet\nheavy with snow and his features as inscrutable as ever.\n\n\"I think that I have seen now all that there is to see, Mr.\nHolder,\" said he; \"I can serve you best by returning to my\nrooms.\"\n\n\"But the gems, Mr. Holmes. Where are they?\"\n\n\"I cannot tell.\"\n\nThe banker wrung his hands. \"I shall never see them again!\" he\ncried. \"And my son? You give me hopes?\"\n\n\"My opinion is in no way altered.\"\n\n\"Then, for God's sake, what was this dark business which was\nacted in my house last night?\"\n\n\"If you can call upon me at my Baker Street rooms to-morrow\nmorning between nine and ten I shall be happy to do what I can to\nmake it clearer. I understand that you give me carte blanche to\nact for you, provided only that I get back the gems, and that you\nplace no limit on the sum I may draw.\"\n\n\"I would give my fortune to have them back.\"\n\n\"Very good. I shall look into the matter between this and then.\nGood-bye; it is just possible that I may have to come over here\nagain before evening.\"\n\nIt was obvious to me that my companion's mind was now made up\nabout the case, although what his conclusions were was more than\nI could even dimly imagine. Several times during our homeward\njourney I endeavoured to sound him upon the point, but he always\nglided away to some other topic, until at last I gave it over in\ndespair. It was not yet three when we found ourselves in our\nrooms once more. He hurried to his chamber and was down again in\na few minutes dressed as a common loafer. With his collar turned\nup, his shiny, seedy coat, his red cravat, and his worn boots, he\nwas a perfect sample of the class.\n\n\"I think that this should do,\" said he, glancing into the glass\nabove the fireplace. \"I only wish that you could come with me,\nWatson, but I fear that it won't do. I may be on the trail in\nthis matter, or I may be following a will-o'-the-wisp, but I\nshall soon know which it is. I hope that I may be back in a few\nhours.\" He cut a slice of beef from the joint upon the sideboard,\nsandwiched it between two rounds of bread, and thrusting this\nrude meal into his pocket he started off upon his expedition.\n\nI had just finished my tea when he returned, evidently in\nexcellent spirits, swinging an old elastic-sided boot in his\nhand. He chucked it down into a corner and helped himself to a\ncup of tea.\n\n\"I only looked in as I passed,\" said he. \"I am going right on.\"\n\n\"Where to?\"\n\n\"Oh, to the other side of the West End. It may be some time\nbefore I get back. Don't wait up for me in case I should be\nlate.\"\n\n\"How are you getting on?\"\n\n\"Oh, so so. Nothing to complain of. I have been out to Streatham\nsince I saw you last, but I did not call at the house. It is a\nvery sweet little problem, and I would not have missed it for a\ngood deal. However, I must not sit gossiping here, but must get\nthese disreputable clothes off and return to my highly\nrespectable self.\"\n\nI could see by his manner that he had stronger reasons for\nsatisfaction than his words alone would imply. His eyes twinkled,\nand there was even a touch of colour upon his sallow cheeks. He\nhastened upstairs, and a few minutes later I heard the slam of\nthe hall door, which told me that he was off once more upon his\ncongenial hunt.\n\nI waited until midnight, but there was no sign of his return, so\nI retired to my room. It was no uncommon thing for him to be away\nfor days and nights on end when he was hot upon a scent, so that\nhis lateness caused me no surprise. I do not know at what hour he\ncame in, but when I came down to breakfast in the morning there\nhe was with a cup of coffee in one hand and the paper in the\nother, as fresh and trim as possible.\n\n\"You will excuse my beginning without you, Watson,\" said he, \"but\nyou remember that our client has rather an early appointment this\nmorning.\"\n\n\"Why, it is after nine now,\" I answered. \"I should not be\nsurprised if that were he. I thought I heard a ring.\"\n\nIt was, indeed, our friend the financier. I was shocked by the\nchange which had come over him, for his face which was naturally\nof a broad and massive mould, was now pinched and fallen in,\nwhile his hair seemed to me at least a shade whiter. He entered\nwith a weariness and lethargy which was even more painful than\nhis violence of the morning before, and he dropped heavily into\nthe armchair which I pushed forward for him.\n\n\"I do not know what I have done to be so severely tried,\" said\nhe. \"Only two days ago I was a happy and prosperous man, without\na care in the world. Now I am left to a lonely and dishonoured\nage. One sorrow comes close upon the heels of another. My niece,\nMary, has deserted me.\"\n\n\"Deserted you?\"\n\n\"Yes. Her bed this morning had not been slept in, her room was\nempty, and a note for me lay upon the hall table. I had said to\nher last night, in sorrow and not in anger, that if she had\nmarried my boy all might have been well with him. Perhaps it was\nthoughtless of me to say so. It is to that remark that she refers\nin this note:\n\n\"'MY DEAREST UNCLE:--I feel that I have brought trouble upon you,\nand that if I had acted differently this terrible misfortune\nmight never have occurred. I cannot, with this thought in my\nmind, ever again be happy under your roof, and I feel that I must\nleave you forever. Do not worry about my future, for that is\nprovided for; and, above all, do not search for me, for it will\nbe fruitless labour and an ill-service to me. In life or in\ndeath, I am ever your loving,--MARY.'\n\n\"What could she mean by that note, Mr. Holmes? Do you think it\npoints to suicide?\"\n\n\"No, no, nothing of the kind. It is perhaps the best possible\nsolution. I trust, Mr. Holder, that you are nearing the end of\nyour troubles.\"\n\n\"Ha! You say so! You have heard something, Mr. Holmes; you have\nlearned something! Where are the gems?\"\n\n\"You would not think 1000 pounds apiece an excessive sum for\nthem?\"\n\n\"I would pay ten.\"\n\n\"That would be unnecessary. Three thousand will cover the matter.\nAnd there is a little reward, I fancy. Have you your check-book?\nHere is a pen. Better make it out for 4000 pounds.\"\n\nWith a dazed face the banker made out the required check. Holmes\nwalked over to his desk, took out a little triangular piece of\ngold with three gems in it, and threw it down upon the table.\n\nWith a shriek of joy our client clutched it up.\n\n\"You have it!\" he gasped. \"I am saved! I am saved!\"\n\nThe reaction of joy was as passionate as his grief had been, and\nhe hugged his recovered gems to his bosom.\n\n\"There is one other thing you owe, Mr. Holder,\" said Sherlock\nHolmes rather sternly.\n\n\"Owe!\" He caught up a pen. \"Name the sum, and I will pay it.\"\n\n\"No, the debt is not to me. You owe a very humble apology to that\nnoble lad, your son, who has carried himself in this matter as I\nshould be proud to see my own son do, should I ever chance to\nhave one.\"\n\n\"Then it was not Arthur who took them?\"\n\n\"I told you yesterday, and I repeat to-day, that it was not.\"\n\n\"You are sure of it! Then let us hurry to him at once to let him\nknow that the truth is known.\"\n\n\"He knows it already. When I had cleared it all up I had an\ninterview with him, and finding that he would not tell me the\nstory, I told it to him, on which he had to confess that I was\nright and to add the very few details which were not yet quite\nclear to me. Your news of this morning, however, may open his\nlips.\"\n\n\"For heaven's sake, tell me, then, what is this extraordinary\nmystery!\"\n\n\"I will do so, and I will show you the steps by which I reached\nit. And let me say to you, first, that which it is hardest for me\nto say and for you to hear: there has been an understanding\nbetween Sir George Burnwell and your niece Mary. They have now\nfled together.\"\n\n\"My Mary? Impossible!\"\n\n\"It is unfortunately more than possible; it is certain. Neither\nyou nor your son knew the true character of this man when you\nadmitted him into your family circle. He is one of the most\ndangerous men in England--a ruined gambler, an absolutely\ndesperate villain, a man without heart or conscience. Your niece\nknew nothing of such men. When he breathed his vows to her, as he\nhad done to a hundred before her, she flattered herself that she\nalone had touched his heart. The devil knows best what he said,\nbut at least she became his tool and was in the habit of seeing\nhim nearly every evening.\"\n\n\"I cannot, and I will not, believe it!\" cried the banker with an\nashen face.\n\n\"I will tell you, then, what occurred in your house last night.\nYour niece, when you had, as she thought, gone to your room,\nslipped down and talked to her lover through the window which\nleads into the stable lane. His footmarks had pressed right\nthrough the snow, so long had he stood there. She told him of the\ncoronet. His wicked lust for gold kindled at the news, and he\nbent her to his will. I have no doubt that she loved you, but\nthere are women in whom the love of a lover extinguishes all\nother loves, and I think that she must have been one. She had\nhardly listened to his instructions when she saw you coming\ndownstairs, on which she closed the window rapidly and told you\nabout one of the servants' escapade with her wooden-legged lover,\nwhich was all perfectly true.\n\n\"Your boy, Arthur, went to bed after his interview with you but\nhe slept badly on account of his uneasiness about his club debts.\nIn the middle of the night he heard a soft tread pass his door,\nso he rose and, looking out, was surprised to see his cousin\nwalking very stealthily along the passage until she disappeared\ninto your dressing-room. Petrified with astonishment, the lad\nslipped on some clothes and waited there in the dark to see what\nwould come of this strange affair. Presently she emerged from the\nroom again, and in the light of the passage-lamp your son saw\nthat she carried the precious coronet in her hands. She passed\ndown the stairs, and he, thrilling with horror, ran along and\nslipped behind the curtain near your door, whence he could see\nwhat passed in the hall beneath. He saw her stealthily open the\nwindow, hand out the coronet to someone in the gloom, and then\nclosing it once more hurry back to her room, passing quite close\nto where he stood hid behind the curtain.\n\n\"As long as she was on the scene he could not take any action\nwithout a horrible exposure of the woman whom he loved. But the\ninstant that she was gone he realised how crushing a misfortune\nthis would be for you, and how all-important it was to set it\nright. He rushed down, just as he was, in his bare feet, opened\nthe window, sprang out into the snow, and ran down the lane,\nwhere he could see a dark figure in the moonlight. Sir George\nBurnwell tried to get away, but Arthur caught him, and there was\na struggle between them, your lad tugging at one side of the\ncoronet, and his opponent at the other. In the scuffle, your son\nstruck Sir George and cut him over the eye. Then something\nsuddenly snapped, and your son, finding that he had the coronet\nin his hands, rushed back, closed the window, ascended to your\nroom, and had just observed that the coronet had been twisted in\nthe struggle and was endeavouring to straighten it when you\nappeared upon the scene.\"\n\n\"Is it possible?\" gasped the banker.\n\n\"You then roused his anger by calling him names at a moment when\nhe felt that he had deserved your warmest thanks. He could not\nexplain the true state of affairs without betraying one who\ncertainly deserved little enough consideration at his hands. He\ntook the more chivalrous view, however, and preserved her\nsecret.\"\n\n\"And that was why she shrieked and fainted when she saw the\ncoronet,\" cried Mr. Holder. \"Oh, my God! what a blind fool I have\nbeen! And his asking to be allowed to go out for five minutes!\nThe dear fellow wanted to see if the missing piece were at the\nscene of the struggle. How cruelly I have misjudged him!\"\n\n\"When I arrived at the house,\" continued Holmes, \"I at once went\nvery carefully round it to observe if there were any traces in\nthe snow which might help me. I knew that none had fallen since\nthe evening before, and also that there had been a strong frost\nto preserve impressions. I passed along the tradesmen's path, but\nfound it all trampled down and indistinguishable. Just beyond it,\nhowever, at the far side of the kitchen door, a woman had stood\nand talked with a man, whose round impressions on one side showed\nthat he had a wooden leg. I could even tell that they had been\ndisturbed, for the woman had run back swiftly to the door, as was\nshown by the deep toe and light heel marks, while Wooden-leg had\nwaited a little, and then had gone away. I thought at the time\nthat this might be the maid and her sweetheart, of whom you had\nalready spoken to me, and inquiry showed it was so. I passed\nround the garden without seeing anything more than random tracks,\nwhich I took to be the police; but when I got into the stable\nlane a very long and complex story was written in the snow in\nfront of me.\n\n\"There was a double line of tracks of a booted man, and a second\ndouble line which I saw with delight belonged to a man with naked\nfeet. I was at once convinced from what you had told me that the\nlatter was your son. The first had walked both ways, but the\nother had run swiftly, and as his tread was marked in places over\nthe depression of the boot, it was obvious that he had passed\nafter the other. I followed them up and found they led to the\nhall window, where Boots had worn all the snow away while\nwaiting. Then I walked to the other end, which was a hundred\nyards or more down the lane. I saw where Boots had faced round,\nwhere the snow was cut up as though there had been a struggle,\nand, finally, where a few drops of blood had fallen, to show me\nthat I was not mistaken. Boots had then run down the lane, and\nanother little smudge of blood showed that it was he who had been\nhurt. When he came to the highroad at the other end, I found that\nthe pavement had been cleared, so there was an end to that clue.\n\n\"On entering the house, however, I examined, as you remember, the\nsill and framework of the hall window with my lens, and I could\nat once see that someone had passed out. I could distinguish the\noutline of an instep where the wet foot had been placed in coming\nin. I was then beginning to be able to form an opinion as to what\nhad occurred. A man had waited outside the window; someone had\nbrought the gems; the deed had been overseen by your son; he had\npursued the thief; had struggled with him; they had each tugged\nat the coronet, their united strength causing injuries which\nneither alone could have effected. He had returned with the\nprize, but had left a fragment in the grasp of his opponent. So\nfar I was clear. The question now was, who was the man and who\nwas it brought him the coronet?\n\n\"It is an old maxim of mine that when you have excluded the\nimpossible, whatever remains, however improbable, must be the\ntruth. Now, I knew that it was not you who had brought it down,\nso there only remained your niece and the maids. But if it were\nthe maids, why should your son allow himself to be accused in\ntheir place? There could be no possible reason. As he loved his\ncousin, however, there was an excellent explanation why he should\nretain her secret--the more so as the secret was a disgraceful\none. When I remembered that you had seen her at that window, and\nhow she had fainted on seeing the coronet again, my conjecture\nbecame a certainty.\n\n\"And who could it be who was her confederate? A lover evidently,\nfor who else could outweigh the love and gratitude which she must\nfeel to you? I knew that you went out little, and that your\ncircle of friends was a very limited one. But among them was Sir\nGeorge Burnwell. I had heard of him before as being a man of evil\nreputation among women. It must have been he who wore those boots\nand retained the missing gems. Even though he knew that Arthur\nhad discovered him, he might still flatter himself that he was\nsafe, for the lad could not say a word without compromising his\nown family.\n\n\"Well, your own good sense will suggest what measures I took\nnext. I went in the shape of a loafer to Sir George's house,\nmanaged to pick up an acquaintance with his valet, learned that\nhis master had cut his head the night before, and, finally, at\nthe expense of six shillings, made all sure by buying a pair of\nhis cast-off shoes. With these I journeyed down to Streatham and\nsaw that they exactly fitted the tracks.\"\n\n\"I saw an ill-dressed vagabond in the lane yesterday evening,\"\nsaid Mr. Holder.\n\n\"Precisely. It was I. I found that I had my man, so I came home\nand changed my clothes. It was a delicate part which I had to\nplay then, for I saw that a prosecution must be avoided to avert\nscandal, and I knew that so astute a villain would see that our\nhands were tied in the matter. I went and saw him. At first, of\ncourse, he denied everything. But when I gave him every\nparticular that had occurred, he tried to bluster and took down a\nlife-preserver from the wall. I knew my man, however, and I\nclapped a pistol to his head before he could strike. Then he\nbecame a little more reasonable. I told him that we would give\nhim a price for the stones he held--1000 pounds apiece. That\nbrought out the first signs of grief that he had shown. 'Why,\ndash it all!' said he, 'I've let them go at six hundred for the\nthree!' I soon managed to get the address of the receiver who had\nthem, on promising him that there would be no prosecution. Off I\nset to him, and after much chaffering I got our stones at 1000\npounds apiece. Then I looked in upon your son, told him that all\nwas right, and eventually got to my bed about two o'clock, after\nwhat I may call a really hard day's work.\"\n\n\"A day which has saved England from a great public scandal,\" said\nthe banker, rising. \"Sir, I cannot find words to thank you, but\nyou shall not find me ungrateful for what you have done. Your\nskill has indeed exceeded all that I have heard of it. And now I\nmust fly to my dear boy to apologise to him for the wrong which I\nhave done him. As to what you tell me of poor Mary, it goes to my\nvery heart. Not even your skill can inform me where she is now.\"\n\n\"I think that we may safely say,\" returned Holmes, \"that she is\nwherever Sir George Burnwell is. It is equally certain, too, that\nwhatever her sins are, they will soon receive a more than\nsufficient punishment.\"\n\n\n\nXII. THE ADVENTURE OF THE COPPER BEECHES\n\n\"To the man who loves art for its own sake,\" remarked Sherlock\nHolmes, tossing aside the advertisement sheet of the Daily\nTelegraph, \"it is frequently in its least important and lowliest\nmanifestations that the keenest pleasure is to be derived. It is\npleasant to me to observe, Watson, that you have so far grasped\nthis truth that in these little records of our cases which you\nhave been good enough to draw up, and, I am bound to say,\noccasionally to embellish, you have given prominence not so much\nto the many causes célèbres and sensational trials in which I\nhave figured but rather to those incidents which may have been\ntrivial in themselves, but which have given room for those\nfaculties of deduction and of logical synthesis which I have made\nmy special province.\"\n\n\"And yet,\" said I, smiling, \"I cannot quite hold myself absolved\nfrom the charge of sensationalism which has been urged against my\nrecords.\"\n\n\"You have erred, perhaps,\" he observed, taking up a glowing\ncinder with the tongs and lighting with it the long cherry-wood\npipe which was wont to replace his clay when he was in a\ndisputatious rather than a meditative mood--\"you have erred\nperhaps in attempting to put colour and life into each of your\nstatements instead of confining yourself to the task of placing\nupon record that severe reasoning from cause to effect which is\nreally the only notable feature about the thing.\"\n\n\"It seems to me that I have done you full justice in the matter,\"\nI remarked with some coldness, for I was repelled by the egotism\nwhich I had more than once observed to be a strong factor in my\nfriend's singular character.\n\n\"No, it is not selfishness or conceit,\" said he, answering, as\nwas his wont, my thoughts rather than my words. \"If I claim full\njustice for my art, it is because it is an impersonal thing--a\nthing beyond myself. Crime is common. Logic is rare. Therefore it\nis upon the logic rather than upon the crime that you should\ndwell. You have degraded what should have been a course of\nlectures into a series of tales.\"\n\nIt was a cold morning of the early spring, and we sat after\nbreakfast on either side of a cheery fire in the old room at\nBaker Street. A thick fog rolled down between the lines of\ndun-coloured houses, and the opposing windows loomed like dark,\nshapeless blurs through the heavy yellow wreaths. Our gas was lit\nand shone on the white cloth and glimmer of china and metal, for\nthe table had not been cleared yet. Sherlock Holmes had been\nsilent all the morning, dipping continuously into the\nadvertisement columns of a succession of papers until at last,\nhaving apparently given up his search, he had emerged in no very\nsweet temper to lecture me upon my literary shortcomings.\n\n\"At the same time,\" he remarked after a pause, during which he\nhad sat puffing at his long pipe and gazing down into the fire,\n\"you can hardly be open to a charge of sensationalism, for out of\nthese cases which you have been so kind as to interest yourself\nin, a fair proportion do not treat of crime, in its legal sense,\nat all. The small matter in which I endeavoured to help the King\nof Bohemia, the singular experience of Miss Mary Sutherland, the\nproblem connected with the man with the twisted lip, and the\nincident of the noble bachelor, were all matters which are\noutside the pale of the law. But in avoiding the sensational, I\nfear that you may have bordered on the trivial.\"\n\n\"The end may have been so,\" I answered, \"but the methods I hold\nto have been novel and of interest.\"\n\n\"Pshaw, my dear fellow, what do the public, the great unobservant\npublic, who could hardly tell a weaver by his tooth or a\ncompositor by his left thumb, care about the finer shades of\nanalysis and deduction! But, indeed, if you are trivial, I cannot\nblame you, for the days of the great cases are past. Man, or at\nleast criminal man, has lost all enterprise and originality. As\nto my own little practice, it seems to be degenerating into an\nagency for recovering lost lead pencils and giving advice to\nyoung ladies from boarding-schools. I think that I have touched\nbottom at last, however. This note I had this morning marks my\nzero-point, I fancy. Read it!\" He tossed a crumpled letter across\nto me.\n\nIt was dated from Montague Place upon the preceding evening, and\nran thus:\n\n\"DEAR MR. HOLMES:--I am very anxious to consult you as to whether\nI should or should not accept a situation which has been offered\nto me as governess. I shall call at half-past ten to-morrow if I\ndo not inconvenience you. Yours faithfully,\n                                               \"VIOLET HUNTER.\"\n\n\"Do you know the young lady?\" I asked.\n\n\"Not I.\"\n\n\"It is half-past ten now.\"\n\n\"Yes, and I have no doubt that is her ring.\"\n\n\"It may turn out to be of more interest than you think. You\nremember that the affair of the blue carbuncle, which appeared to\nbe a mere whim at first, developed into a serious investigation.\nIt may be so in this case, also.\"\n\n\"Well, let us hope so. But our doubts will very soon be solved,\nfor here, unless I am much mistaken, is the person in question.\"\n\nAs he spoke the door opened and a young lady entered the room.\nShe was plainly but neatly dressed, with a bright, quick face,\nfreckled like a plover's egg, and with the brisk manner of a\nwoman who has had her own way to make in the world.\n\n\"You will excuse my troubling you, I am sure,\" said she, as my\ncompanion rose to greet her, \"but I have had a very strange\nexperience, and as I have no parents or relations of any sort\nfrom whom I could ask advice, I thought that perhaps you would be\nkind enough to tell me what I should do.\"\n\n\"Pray take a seat, Miss Hunter. I shall be happy to do anything\nthat I can to serve you.\"\n\nI could see that Holmes was favourably impressed by the manner\nand speech of his new client. He looked her over in his searching\nfashion, and then composed himself, with his lids drooping and\nhis finger-tips together, to listen to her story.\n\n\"I have been a governess for five years,\" said she, \"in the\nfamily of Colonel Spence Munro, but two months ago the colonel\nreceived an appointment at Halifax, in Nova Scotia, and took his\nchildren over to America with him, so that I found myself without\na situation. I advertised, and I answered advertisements, but\nwithout success. At last the little money which I had saved began\nto run short, and I was at my wit's end as to what I should do.\n\n\"There is a well-known agency for governesses in the West End\ncalled Westaway's, and there I used to call about once a week in\norder to see whether anything had turned up which might suit me.\nWestaway was the name of the founder of the business, but it is\nreally managed by Miss Stoper. She sits in her own little office,\nand the ladies who are seeking employment wait in an anteroom,\nand are then shown in one by one, when she consults her ledgers\nand sees whether she has anything which would suit them.\n\n\"Well, when I called last week I was shown into the little office\nas usual, but I found that Miss Stoper was not alone. A\nprodigiously stout man with a very smiling face and a great heavy\nchin which rolled down in fold upon fold over his throat sat at\nher elbow with a pair of glasses on his nose, looking very\nearnestly at the ladies who entered. As I came in he gave quite a\njump in his chair and turned quickly to Miss Stoper.\n\n\"'That will do,' said he; 'I could not ask for anything better.\nCapital! capital!' He seemed quite enthusiastic and rubbed his\nhands together in the most genial fashion. He was such a\ncomfortable-looking man that it was quite a pleasure to look at\nhim.\n\n\"'You are looking for a situation, miss?' he asked.\n\n\"'Yes, sir.'\n\n\"'As governess?'\n\n\"'Yes, sir.'\n\n\"'And what salary do you ask?'\n\n\"'I had 4 pounds a month in my last place with Colonel Spence\nMunro.'\n\n\"'Oh, tut, tut! sweating--rank sweating!' he cried, throwing his\nfat hands out into the air like a man who is in a boiling\npassion. 'How could anyone offer so pitiful a sum to a lady with\nsuch attractions and accomplishments?'\n\n\"'My accomplishments, sir, may be less than you imagine,' said I.\n'A little French, a little German, music, and drawing--'\n\n\"'Tut, tut!' he cried. 'This is all quite beside the question.\nThe point is, have you or have you not the bearing and deportment\nof a lady? There it is in a nutshell. If you have not, you are\nnot fitted for the rearing of a child who may some day play a\nconsiderable part in the history of the country. But if you have\nwhy, then, how could any gentleman ask you to condescend to\naccept anything under the three figures? Your salary with me,\nmadam, would commence at 100 pounds a year.'\n\n\"You may imagine, Mr. Holmes, that to me, destitute as I was,\nsuch an offer seemed almost too good to be true. The gentleman,\nhowever, seeing perhaps the look of incredulity upon my face,\nopened a pocket-book and took out a note.\n\n\"'It is also my custom,' said he, smiling in the most pleasant\nfashion until his eyes were just two little shining slits amid\nthe white creases of his face, 'to advance to my young ladies\nhalf their salary beforehand, so that they may meet any little\nexpenses of their journey and their wardrobe.'\n\n\"It seemed to me that I had never met so fascinating and so\nthoughtful a man. As I was already in debt to my tradesmen, the\nadvance was a great convenience, and yet there was something\nunnatural about the whole transaction which made me wish to know\na little more before I quite committed myself.\n\n\"'May I ask where you live, sir?' said I.\n\n\"'Hampshire. Charming rural place. The Copper Beeches, five miles\non the far side of Winchester. It is the most lovely country, my\ndear young lady, and the dearest old country-house.'\n\n\"'And my duties, sir? I should be glad to know what they would\nbe.'\n\n\"'One child--one dear little romper just six years old. Oh, if\nyou could see him killing cockroaches with a slipper! Smack!\nsmack! smack! Three gone before you could wink!' He leaned back\nin his chair and laughed his eyes into his head again.\n\n\"I was a little startled at the nature of the child's amusement,\nbut the father's laughter made me think that perhaps he was\njoking.\n\n\"'My sole duties, then,' I asked, 'are to take charge of a single\nchild?'\n\n\"'No, no, not the sole, not the sole, my dear young lady,' he\ncried. 'Your duty would be, as I am sure your good sense would\nsuggest, to obey any little commands my wife might give, provided\nalways that they were such commands as a lady might with\npropriety obey. You see no difficulty, heh?'\n\n\"'I should be happy to make myself useful.'\n\n\"'Quite so. In dress now, for example. We are faddy people, you\nknow--faddy but kind-hearted. If you were asked to wear any dress\nwhich we might give you, you would not object to our little whim.\nHeh?'\n\n\"'No,' said I, considerably astonished at his words.\n\n\"'Or to sit here, or sit there, that would not be offensive to\nyou?'\n\n\"'Oh, no.'\n\n\"'Or to cut your hair quite short before you come to us?'\n\n\"I could hardly believe my ears. As you may observe, Mr. Holmes,\nmy hair is somewhat luxuriant, and of a rather peculiar tint of\nchestnut. It has been considered artistic. I could not dream of\nsacrificing it in this offhand fashion.\n\n\"'I am afraid that that is quite impossible,' said I. He had been\nwatching me eagerly out of his small eyes, and I could see a\nshadow pass over his face as I spoke.\n\n\"'I am afraid that it is quite essential,' said he. 'It is a\nlittle fancy of my wife's, and ladies' fancies, you know, madam,\nladies' fancies must be consulted. And so you won't cut your\nhair?'\n\n\"'No, sir, I really could not,' I answered firmly.\n\n\"'Ah, very well; then that quite settles the matter. It is a\npity, because in other respects you would really have done very\nnicely. In that case, Miss Stoper, I had best inspect a few more\nof your young ladies.'\n\n\"The manageress had sat all this while busy with her papers\nwithout a word to either of us, but she glanced at me now with so\nmuch annoyance upon her face that I could not help suspecting\nthat she had lost a handsome commission through my refusal.\n\n\"'Do you desire your name to be kept upon the books?' she asked.\n\n\"'If you please, Miss Stoper.'\n\n\"'Well, really, it seems rather useless, since you refuse the\nmost excellent offers in this fashion,' said she sharply. 'You\ncan hardly expect us to exert ourselves to find another such\nopening for you. Good-day to you, Miss Hunter.' She struck a gong\nupon the table, and I was shown out by the page.\n\n\"Well, Mr. Holmes, when I got back to my lodgings and found\nlittle enough in the cupboard, and two or three bills upon the\ntable, I began to ask myself whether I had not done a very\nfoolish thing. After all, if these people had strange fads and\nexpected obedience on the most extraordinary matters, they were\nat least ready to pay for their eccentricity. Very few\ngovernesses in England are getting 100 pounds a year. Besides,\nwhat use was my hair to me? Many people are improved by wearing\nit short and perhaps I should be among the number. Next day I was\ninclined to think that I had made a mistake, and by the day after\nI was sure of it. I had almost overcome my pride so far as to go\nback to the agency and inquire whether the place was still open\nwhen I received this letter from the gentleman himself. I have it\nhere and I will read it to you:\n\n                       \"'The Copper Beeches, near Winchester.\n\"'DEAR MISS HUNTER:--Miss Stoper has very kindly given me your\naddress, and I write from here to ask you whether you have\nreconsidered your decision. My wife is very anxious that you\nshould come, for she has been much attracted by my description of\nyou. We are willing to give 30 pounds a quarter, or 120 pounds a\nyear, so as to recompense you for any little inconvenience which\nour fads may cause you. They are not very exacting, after all. My\nwife is fond of a particular shade of electric blue and would\nlike you to wear such a dress indoors in the morning. You need\nnot, however, go to the expense of purchasing one, as we have one\nbelonging to my dear daughter Alice (now in Philadelphia), which\nwould, I should think, fit you very well. Then, as to sitting\nhere or there, or amusing yourself in any manner indicated, that\nneed cause you no inconvenience. As regards your hair, it is no\ndoubt a pity, especially as I could not help remarking its beauty\nduring our short interview, but I am afraid that I must remain\nfirm upon this point, and I only hope that the increased salary\nmay recompense you for the loss. Your duties, as far as the child\nis concerned, are very light. Now do try to come, and I shall\nmeet you with the dog-cart at Winchester. Let me know your train.\nYours faithfully, JEPHRO RUCASTLE.'\n\n\"That is the letter which I have just received, Mr. Holmes, and\nmy mind is made up that I will accept it. I thought, however,\nthat before taking the final step I should like to submit the\nwhole matter to your consideration.\"\n\n\"Well, Miss Hunter, if your mind is made up, that settles the\nquestion,\" said Holmes, smiling.\n\n\"But you would not advise me to refuse?\"\n\n\"I confess that it is not the situation which I should like to\nsee a sister of mine apply for.\"\n\n\"What is the meaning of it all, Mr. Holmes?\"\n\n\"Ah, I have no data. I cannot tell. Perhaps you have yourself\nformed some opinion?\"\n\n\"Well, there seems to me to be only one possible solution. Mr.\nRucastle seemed to be a very kind, good-natured man. Is it not\npossible that his wife is a lunatic, that he desires to keep the\nmatter quiet for fear she should be taken to an asylum, and that\nhe humours her fancies in every way in order to prevent an\noutbreak?\"\n\n\"That is a possible solution--in fact, as matters stand, it is\nthe most probable one. But in any case it does not seem to be a\nnice household for a young lady.\"\n\n\"But the money, Mr. Holmes, the money!\"\n\n\"Well, yes, of course the pay is good--too good. That is what\nmakes me uneasy. Why should they give you 120 pounds a year, when\nthey could have their pick for 40 pounds? There must be some\nstrong reason behind.\"\n\n\"I thought that if I told you the circumstances you would\nunderstand afterwards if I wanted your help. I should feel so\nmuch stronger if I felt that you were at the back of me.\"\n\n\"Oh, you may carry that feeling away with you. I assure you that\nyour little problem promises to be the most interesting which has\ncome my way for some months. There is something distinctly novel\nabout some of the features. If you should find yourself in doubt\nor in danger--\"\n\n\"Danger! What danger do you foresee?\"\n\nHolmes shook his head gravely. \"It would cease to be a danger if\nwe could define it,\" said he. \"But at any time, day or night, a\ntelegram would bring me down to your help.\"\n\n\"That is enough.\" She rose briskly from her chair with the\nanxiety all swept from her face. \"I shall go down to Hampshire\nquite easy in my mind now. I shall write to Mr. Rucastle at once,\nsacrifice my poor hair to-night, and start for Winchester\nto-morrow.\" With a few grateful words to Holmes she bade us both\ngood-night and bustled off upon her way.\n\n\"At least,\" said I as we heard her quick, firm steps descending\nthe stairs, \"she seems to be a young lady who is very well able\nto take care of herself.\"\n\n\"And she would need to be,\" said Holmes gravely. \"I am much\nmistaken if we do not hear from her before many days are past.\"\n\nIt was not very long before my friend's prediction was fulfilled.\nA fortnight went by, during which I frequently found my thoughts\nturning in her direction and wondering what strange side-alley of\nhuman experience this lonely woman had strayed into. The unusual\nsalary, the curious conditions, the light duties, all pointed to\nsomething abnormal, though whether a fad or a plot, or whether\nthe man were a philanthropist or a villain, it was quite beyond\nmy powers to determine. As to Holmes, I observed that he sat\nfrequently for half an hour on end, with knitted brows and an\nabstracted air, but he swept the matter away with a wave of his\nhand when I mentioned it. \"Data! data! data!\" he cried\nimpatiently. \"I can't make bricks without clay.\" And yet he would\nalways wind up by muttering that no sister of his should ever\nhave accepted such a situation.\n\nThe telegram which we eventually received came late one night\njust as I was thinking of turning in and Holmes was settling down\nto one of those all-night chemical researches which he frequently\nindulged in, when I would leave him stooping over a retort and a\ntest-tube at night and find him in the same position when I came\ndown to breakfast in the morning. He opened the yellow envelope,\nand then, glancing at the message, threw it across to me.\n\n\"Just look up the trains in Bradshaw,\" said he, and turned back\nto his chemical studies.\n\nThe summons was a brief and urgent one.\n\n\"Please be at the Black Swan Hotel at Winchester at midday\nto-morrow,\" it said. \"Do come! I am at my wit's end.  HUNTER.\"\n\n\"Will you come with me?\" asked Holmes, glancing up.\n\n\"I should wish to.\"\n\n\"Just look it up, then.\"\n\n\"There is a train at half-past nine,\" said I, glancing over my\nBradshaw. \"It is due at Winchester at 11:30.\"\n\n\"That will do very nicely. Then perhaps I had better postpone my\nanalysis of the acetones, as we may need to be at our best in the\nmorning.\"\n\nBy eleven o'clock the next day we were well upon our way to the\nold English capital. Holmes had been buried in the morning papers\nall the way down, but after we had passed the Hampshire border he\nthrew them down and began to admire the scenery. It was an ideal\nspring day, a light blue sky, flecked with little fleecy white\nclouds drifting across from west to east. The sun was shining\nvery brightly, and yet there was an exhilarating nip in the air,\nwhich set an edge to a man's energy. All over the countryside,\naway to the rolling hills around Aldershot, the little red and\ngrey roofs of the farm-steadings peeped out from amid the light\ngreen of the new foliage.\n\n\"Are they not fresh and beautiful?\" I cried with all the\nenthusiasm of a man fresh from the fogs of Baker Street.\n\nBut Holmes shook his head gravely.\n\n\"Do you know, Watson,\" said he, \"that it is one of the curses of\na mind with a turn like mine that I must look at everything with\nreference to my own special subject. You look at these scattered\nhouses, and you are impressed by their beauty. I look at them,\nand the only thought which comes to me is a feeling of their\nisolation and of the impunity with which crime may be committed\nthere.\"\n\n\"Good heavens!\" I cried. \"Who would associate crime with these\ndear old homesteads?\"\n\n\"They always fill me with a certain horror. It is my belief,\nWatson, founded upon my experience, that the lowest and vilest\nalleys in London do not present a more dreadful record of sin\nthan does the smiling and beautiful countryside.\"\n\n\"You horrify me!\"\n\n\"But the reason is very obvious. The pressure of public opinion\ncan do in the town what the law cannot accomplish. There is no\nlane so vile that the scream of a tortured child, or the thud of\na drunkard's blow, does not beget sympathy and indignation among\nthe neighbours, and then the whole machinery of justice is ever\nso close that a word of complaint can set it going, and there is\nbut a step between the crime and the dock. But look at these\nlonely houses, each in its own fields, filled for the most part\nwith poor ignorant folk who know little of the law. Think of the\ndeeds of hellish cruelty, the hidden wickedness which may go on,\nyear in, year out, in such places, and none the wiser. Had this\nlady who appeals to us for help gone to live in Winchester, I\nshould never have had a fear for her. It is the five miles of\ncountry which makes the danger. Still, it is clear that she is\nnot personally threatened.\"\n\n\"No. If she can come to Winchester to meet us she can get away.\"\n\n\"Quite so. She has her freedom.\"\n\n\"What CAN be the matter, then? Can you suggest no explanation?\"\n\n\"I have devised seven separate explanations, each of which would\ncover the facts as far as we know them. But which of these is\ncorrect can only be determined by the fresh information which we\nshall no doubt find waiting for us. Well, there is the tower of\nthe cathedral, and we shall soon learn all that Miss Hunter has\nto tell.\"\n\nThe Black Swan is an inn of repute in the High Street, at no\ndistance from the station, and there we found the young lady\nwaiting for us. She had engaged a sitting-room, and our lunch\nawaited us upon the table.\n\n\"I am so delighted that you have come,\" she said earnestly. \"It\nis so very kind of you both; but indeed I do not know what I\nshould do. Your advice will be altogether invaluable to me.\"\n\n\"Pray tell us what has happened to you.\"\n\n\"I will do so, and I must be quick, for I have promised Mr.\nRucastle to be back before three. I got his leave to come into\ntown this morning, though he little knew for what purpose.\"\n\n\"Let us have everything in its due order.\" Holmes thrust his long\nthin legs out towards the fire and composed himself to listen.\n\n\"In the first place, I may say that I have met, on the whole,\nwith no actual ill-treatment from Mr. and Mrs. Rucastle. It is\nonly fair to them to say that. But I cannot understand them, and\nI am not easy in my mind about them.\"\n\n\"What can you not understand?\"\n\n\"Their reasons for their conduct. But you shall have it all just\nas it occurred. When I came down, Mr. Rucastle met me here and\ndrove me in his dog-cart to the Copper Beeches. It is, as he\nsaid, beautifully situated, but it is not beautiful in itself,\nfor it is a large square block of a house, whitewashed, but all\nstained and streaked with damp and bad weather. There are grounds\nround it, woods on three sides, and on the fourth a field which\nslopes down to the Southampton highroad, which curves past about\na hundred yards from the front door. This ground in front belongs\nto the house, but the woods all round are part of Lord\nSoutherton's preserves. A clump of copper beeches immediately in\nfront of the hall door has given its name to the place.\n\n\"I was driven over by my employer, who was as amiable as ever,\nand was introduced by him that evening to his wife and the child.\nThere was no truth, Mr. Holmes, in the conjecture which seemed to\nus to be probable in your rooms at Baker Street. Mrs. Rucastle is\nnot mad. I found her to be a silent, pale-faced woman, much\nyounger than her husband, not more than thirty, I should think,\nwhile he can hardly be less than forty-five. From their\nconversation I have gathered that they have been married about\nseven years, that he was a widower, and that his only child by\nthe first wife was the daughter who has gone to Philadelphia. Mr.\nRucastle told me in private that the reason why she had left them\nwas that she had an unreasoning aversion to her stepmother. As\nthe daughter could not have been less than twenty, I can quite\nimagine that her position must have been uncomfortable with her\nfather's young wife.\n\n\"Mrs. Rucastle seemed to me to be colourless in mind as well as\nin feature. She impressed me neither favourably nor the reverse.\nShe was a nonentity. It was easy to see that she was passionately\ndevoted both to her husband and to her little son. Her light grey\neyes wandered continually from one to the other, noting every\nlittle want and forestalling it if possible. He was kind to her\nalso in his bluff, boisterous fashion, and on the whole they\nseemed to be a happy couple. And yet she had some secret sorrow,\nthis woman. She would often be lost in deep thought, with the\nsaddest look upon her face. More than once I have surprised her\nin tears. I have thought sometimes that it was the disposition of\nher child which weighed upon her mind, for I have never met so\nutterly spoiled and so ill-natured a little creature. He is small\nfor his age, with a head which is quite disproportionately large.\nHis whole life appears to be spent in an alternation between\nsavage fits of passion and gloomy intervals of sulking. Giving\npain to any creature weaker than himself seems to be his one idea\nof amusement, and he shows quite remarkable talent in planning\nthe capture of mice, little birds, and insects. But I would\nrather not talk about the creature, Mr. Holmes, and, indeed, he\nhas little to do with my story.\"\n\n\"I am glad of all details,\" remarked my friend, \"whether they\nseem to you to be relevant or not.\"\n\n\"I shall try not to miss anything of importance. The one\nunpleasant thing about the house, which struck me at once, was\nthe appearance and conduct of the servants. There are only two, a\nman and his wife. Toller, for that is his name, is a rough,\nuncouth man, with grizzled hair and whiskers, and a perpetual\nsmell of drink. Twice since I have been with them he has been\nquite drunk, and yet Mr. Rucastle seemed to take no notice of it.\nHis wife is a very tall and strong woman with a sour face, as\nsilent as Mrs. Rucastle and much less amiable. They are a most\nunpleasant couple, but fortunately I spend most of my time in the\nnursery and my own room, which are next to each other in one\ncorner of the building.\n\n\"For two days after my arrival at the Copper Beeches my life was\nvery quiet; on the third, Mrs. Rucastle came down just after\nbreakfast and whispered something to her husband.\n\n\"'Oh, yes,' said he, turning to me, 'we are very much obliged to\nyou, Miss Hunter, for falling in with our whims so far as to cut\nyour hair. I assure you that it has not detracted in the tiniest\niota from your appearance. We shall now see how the electric-blue\ndress will become you. You will find it laid out upon the bed in\nyour room, and if you would be so good as to put it on we should\nboth be extremely obliged.'\n\n\"The dress which I found waiting for me was of a peculiar shade\nof blue. It was of excellent material, a sort of beige, but it\nbore unmistakable signs of having been worn before. It could not\nhave been a better fit if I had been measured for it. Both Mr.\nand Mrs. Rucastle expressed a delight at the look of it, which\nseemed quite exaggerated in its vehemence. They were waiting for\nme in the drawing-room, which is a very large room, stretching\nalong the entire front of the house, with three long windows\nreaching down to the floor. A chair had been placed close to the\ncentral window, with its back turned towards it. In this I was\nasked to sit, and then Mr. Rucastle, walking up and down on the\nother side of the room, began to tell me a series of the funniest\nstories that I have ever listened to. You cannot imagine how\ncomical he was, and I laughed until I was quite weary. Mrs.\nRucastle, however, who has evidently no sense of humour, never so\nmuch as smiled, but sat with her hands in her lap, and a sad,\nanxious look upon her face. After an hour or so, Mr. Rucastle\nsuddenly remarked that it was time to commence the duties of the\nday, and that I might change my dress and go to little Edward in\nthe nursery.\n\n\"Two days later this same performance was gone through under\nexactly similar circumstances. Again I changed my dress, again I\nsat in the window, and again I laughed very heartily at the funny\nstories of which my employer had an immense répertoire, and which\nhe told inimitably. Then he handed me a yellow-backed novel, and\nmoving my chair a little sideways, that my own shadow might not\nfall upon the page, he begged me to read aloud to him. I read for\nabout ten minutes, beginning in the heart of a chapter, and then\nsuddenly, in the middle of a sentence, he ordered me to cease and\nto change my dress.\n\n\"You can easily imagine, Mr. Holmes, how curious I became as to\nwhat the meaning of this extraordinary performance could possibly\nbe. They were always very careful, I observed, to turn my face\naway from the window, so that I became consumed with the desire\nto see what was going on behind my back. At first it seemed to be\nimpossible, but I soon devised a means. My hand-mirror had been\nbroken, so a happy thought seized me, and I concealed a piece of\nthe glass in my handkerchief. On the next occasion, in the midst\nof my laughter, I put my handkerchief up to my eyes, and was able\nwith a little management to see all that there was behind me. I\nconfess that I was disappointed. There was nothing. At least that\nwas my first impression. At the second glance, however, I\nperceived that there was a man standing in the Southampton Road,\na small bearded man in a grey suit, who seemed to be looking in\nmy direction. The road is an important highway, and there are\nusually people there. This man, however, was leaning against the\nrailings which bordered our field and was looking earnestly up. I\nlowered my handkerchief and glanced at Mrs. Rucastle to find her\neyes fixed upon me with a most searching gaze. She said nothing,\nbut I am convinced that she had divined that I had a mirror in my\nhand and had seen what was behind me. She rose at once.\n\n\"'Jephro,' said she, 'there is an impertinent fellow upon the\nroad there who stares up at Miss Hunter.'\n\n\"'No friend of yours, Miss Hunter?' he asked.\n\n\"'No, I know no one in these parts.'\n\n\"'Dear me! How very impertinent! Kindly turn round and motion to\nhim to go away.'\n\n\"'Surely it would be better to take no notice.'\n\n\"'No, no, we should have him loitering here always. Kindly turn\nround and wave him away like that.'\n\n\"I did as I was told, and at the same instant Mrs. Rucastle drew\ndown the blind. That was a week ago, and from that time I have\nnot sat again in the window, nor have I worn the blue dress, nor\nseen the man in the road.\"\n\n\"Pray continue,\" said Holmes. \"Your narrative promises to be a\nmost interesting one.\"\n\n\"You will find it rather disconnected, I fear, and there may\nprove to be little relation between the different incidents of\nwhich I speak. On the very first day that I was at the Copper\nBeeches, Mr. Rucastle took me to a small outhouse which stands\nnear the kitchen door. As we approached it I heard the sharp\nrattling of a chain, and the sound as of a large animal moving\nabout.\n\n\"'Look in here!' said Mr. Rucastle, showing me a slit between two\nplanks. 'Is he not a beauty?'\n\n\"I looked through and was conscious of two glowing eyes, and of a\nvague figure huddled up in the darkness.\n\n\"'Don't be frightened,' said my employer, laughing at the start\nwhich I had given. 'It's only Carlo, my mastiff. I call him mine,\nbut really old Toller, my groom, is the only man who can do\nanything with him. We feed him once a day, and not too much then,\nso that he is always as keen as mustard. Toller lets him loose\nevery night, and God help the trespasser whom he lays his fangs\nupon. For goodness' sake don't you ever on any pretext set your\nfoot over the threshold at night, for it's as much as your life\nis worth.'\n\n\"The warning was no idle one, for two nights later I happened to\nlook out of my bedroom window about two o'clock in the morning.\nIt was a beautiful moonlight night, and the lawn in front of the\nhouse was silvered over and almost as bright as day. I was\nstanding, rapt in the peaceful beauty of the scene, when I was\naware that something was moving under the shadow of the copper\nbeeches. As it emerged into the moonshine I saw what it was. It\nwas a giant dog, as large as a calf, tawny tinted, with hanging\njowl, black muzzle, and huge projecting bones. It walked slowly\nacross the lawn and vanished into the shadow upon the other side.\nThat dreadful sentinel sent a chill to my heart which I do not\nthink that any burglar could have done.\n\n\"And now I have a very strange experience to tell you. I had, as\nyou know, cut off my hair in London, and I had placed it in a\ngreat coil at the bottom of my trunk. One evening, after the\nchild was in bed, I began to amuse myself by examining the\nfurniture of my room and by rearranging my own little things.\nThere was an old chest of drawers in the room, the two upper ones\nempty and open, the lower one locked. I had filled the first two\nwith my linen, and as I had still much to pack away I was\nnaturally annoyed at not having the use of the third drawer. It\nstruck me that it might have been fastened by a mere oversight,\nso I took out my bunch of keys and tried to open it. The very\nfirst key fitted to perfection, and I drew the drawer open. There\nwas only one thing in it, but I am sure that you would never\nguess what it was. It was my coil of hair.\n\n\"I took it up and examined it. It was of the same peculiar tint,\nand the same thickness. But then the impossibility of the thing\nobtruded itself upon me. How could my hair have been locked in\nthe drawer? With trembling hands I undid my trunk, turned out the\ncontents, and drew from the bottom my own hair. I laid the two\ntresses together, and I assure you that they were identical. Was\nit not extraordinary? Puzzle as I would, I could make nothing at\nall of what it meant. I returned the strange hair to the drawer,\nand I said nothing of the matter to the Rucastles as I felt that\nI had put myself in the wrong by opening a drawer which they had\nlocked.\n\n\"I am naturally observant, as you may have remarked, Mr. Holmes,\nand I soon had a pretty good plan of the whole house in my head.\nThere was one wing, however, which appeared not to be inhabited\nat all. A door which faced that which led into the quarters of\nthe Tollers opened into this suite, but it was invariably locked.\nOne day, however, as I ascended the stair, I met Mr. Rucastle\ncoming out through this door, his keys in his hand, and a look on\nhis face which made him a very different person to the round,\njovial man to whom I was accustomed. His cheeks were red, his\nbrow was all crinkled with anger, and the veins stood out at his\ntemples with passion. He locked the door and hurried past me\nwithout a word or a look.\n\n\"This aroused my curiosity, so when I went out for a walk in the\ngrounds with my charge, I strolled round to the side from which I\ncould see the windows of this part of the house. There were four\nof them in a row, three of which were simply dirty, while the\nfourth was shuttered up. They were evidently all deserted. As I\nstrolled up and down, glancing at them occasionally, Mr. Rucastle\ncame out to me, looking as merry and jovial as ever.\n\n\"'Ah!' said he, 'you must not think me rude if I passed you\nwithout a word, my dear young lady. I was preoccupied with\nbusiness matters.'\n\n\"I assured him that I was not offended. 'By the way,' said I,\n'you seem to have quite a suite of spare rooms up there, and one\nof them has the shutters up.'\n\n\"He looked surprised and, as it seemed to me, a little startled\nat my remark.\n\n\"'Photography is one of my hobbies,' said he. 'I have made my\ndark room up there. But, dear me! what an observant young lady we\nhave come upon. Who would have believed it? Who would have ever\nbelieved it?' He spoke in a jesting tone, but there was no jest\nin his eyes as he looked at me. I read suspicion there and\nannoyance, but no jest.\n\n\"Well, Mr. Holmes, from the moment that I understood that there\nwas something about that suite of rooms which I was not to know,\nI was all on fire to go over them. It was not mere curiosity,\nthough I have my share of that. It was more a feeling of duty--a\nfeeling that some good might come from my penetrating to this\nplace. They talk of woman's instinct; perhaps it was woman's\ninstinct which gave me that feeling. At any rate, it was there,\nand I was keenly on the lookout for any chance to pass the\nforbidden door.\n\n\"It was only yesterday that the chance came. I may tell you that,\nbesides Mr. Rucastle, both Toller and his wife find something to\ndo in these deserted rooms, and I once saw him carrying a large\nblack linen bag with him through the door. Recently he has been\ndrinking hard, and yesterday evening he was very drunk; and when\nI came upstairs there was the key in the door. I have no doubt at\nall that he had left it there. Mr. and Mrs. Rucastle were both\ndownstairs, and the child was with them, so that I had an\nadmirable opportunity. I turned the key gently in the lock,\nopened the door, and slipped through.\n\n\"There was a little passage in front of me, unpapered and\nuncarpeted, which turned at a right angle at the farther end.\nRound this corner were three doors in a line, the first and third\nof which were open. They each led into an empty room, dusty and\ncheerless, with two windows in the one and one in the other, so\nthick with dirt that the evening light glimmered dimly through\nthem. The centre door was closed, and across the outside of it\nhad been fastened one of the broad bars of an iron bed, padlocked\nat one end to a ring in the wall, and fastened at the other with\nstout cord. The door itself was locked as well, and the key was\nnot there. This barricaded door corresponded clearly with the\nshuttered window outside, and yet I could see by the glimmer from\nbeneath it that the room was not in darkness. Evidently there was\na skylight which let in light from above. As I stood in the\npassage gazing at the sinister door and wondering what secret it\nmight veil, I suddenly heard the sound of steps within the room\nand saw a shadow pass backward and forward against the little\nslit of dim light which shone out from under the door. A mad,\nunreasoning terror rose up in me at the sight, Mr. Holmes. My\noverstrung nerves failed me suddenly, and I turned and ran--ran\nas though some dreadful hand were behind me clutching at the\nskirt of my dress. I rushed down the passage, through the door,\nand straight into the arms of Mr. Rucastle, who was waiting\noutside.\n\n\"'So,' said he, smiling, 'it was you, then. I thought that it\nmust be when I saw the door open.'\n\n\"'Oh, I am so frightened!' I panted.\n\n\"'My dear young lady! my dear young lady!'--you cannot think how\ncaressing and soothing his manner was--'and what has frightened\nyou, my dear young lady?'\n\n\"But his voice was just a little too coaxing. He overdid it. I\nwas keenly on my guard against him.\n\n\"'I was foolish enough to go into the empty wing,' I answered.\n'But it is so lonely and eerie in this dim light that I was\nfrightened and ran out again. Oh, it is so dreadfully still in\nthere!'\n\n\"'Only that?' said he, looking at me keenly.\n\n\"'Why, what did you think?' I asked.\n\n\"'Why do you think that I lock this door?'\n\n\"'I am sure that I do not know.'\n\n\"'It is to keep people out who have no business there. Do you\nsee?' He was still smiling in the most amiable manner.\n\n\"'I am sure if I had known--'\n\n\"'Well, then, you know now. And if you ever put your foot over\nthat threshold again'--here in an instant the smile hardened into\na grin of rage, and he glared down at me with the face of a\ndemon--'I'll throw you to the mastiff.'\n\n\"I was so terrified that I do not know what I did. I suppose that\nI must have rushed past him into my room. I remember nothing\nuntil I found myself lying on my bed trembling all over. Then I\nthought of you, Mr. Holmes. I could not live there longer without\nsome advice. I was frightened of the house, of the man, of the\nwoman, of the servants, even of the child. They were all horrible\nto me. If I could only bring you down all would be well. Of\ncourse I might have fled from the house, but my curiosity was\nalmost as strong as my fears. My mind was soon made up. I would\nsend you a wire. I put on my hat and cloak, went down to the\noffice, which is about half a mile from the house, and then\nreturned, feeling very much easier. A horrible doubt came into my\nmind as I approached the door lest the dog might be loose, but I\nremembered that Toller had drunk himself into a state of\ninsensibility that evening, and I knew that he was the only one\nin the household who had any influence with the savage creature,\nor who would venture to set him free. I slipped in in safety and\nlay awake half the night in my joy at the thought of seeing you.\nI had no difficulty in getting leave to come into Winchester this\nmorning, but I must be back before three o'clock, for Mr. and\nMrs. Rucastle are going on a visit, and will be away all the\nevening, so that I must look after the child. Now I have told you\nall my adventures, Mr. Holmes, and I should be very glad if you\ncould tell me what it all means, and, above all, what I should\ndo.\"\n\nHolmes and I had listened spellbound to this extraordinary story.\nMy friend rose now and paced up and down the room, his hands in\nhis pockets, and an expression of the most profound gravity upon\nhis face.\n\n\"Is Toller still drunk?\" he asked.\n\n\"Yes. I heard his wife tell Mrs. Rucastle that she could do\nnothing with him.\"\n\n\"That is well. And the Rucastles go out to-night?\"\n\n\"Yes.\"\n\n\"Is there a cellar with a good strong lock?\"\n\n\"Yes, the wine-cellar.\"\n\n\"You seem to me to have acted all through this matter like a very\nbrave and sensible girl, Miss Hunter. Do you think that you could\nperform one more feat? I should not ask it of you if I did not\nthink you a quite exceptional woman.\"\n\n\"I will try. What is it?\"\n\n\"We shall be at the Copper Beeches by seven o'clock, my friend\nand I. The Rucastles will be gone by that time, and Toller will,\nwe hope, be incapable. There only remains Mrs. Toller, who might\ngive the alarm. If you could send her into the cellar on some\nerrand, and then turn the key upon her, you would facilitate\nmatters immensely.\"\n\n\"I will do it.\"\n\n\"Excellent! We shall then look thoroughly into the affair. Of\ncourse there is only one feasible explanation. You have been\nbrought there to personate someone, and the real person is\nimprisoned in this chamber. That is obvious. As to who this\nprisoner is, I have no doubt that it is the daughter, Miss Alice\nRucastle, if I remember right, who was said to have gone to\nAmerica. You were chosen, doubtless, as resembling her in height,\nfigure, and the colour of your hair. Hers had been cut off, very\npossibly in some illness through which she has passed, and so, of\ncourse, yours had to be sacrificed also. By a curious chance you\ncame upon her tresses. The man in the road was undoubtedly some\nfriend of hers--possibly her fiancé--and no doubt, as you wore\nthe girl's dress and were so like her, he was convinced from your\nlaughter, whenever he saw you, and afterwards from your gesture,\nthat Miss Rucastle was perfectly happy, and that she no longer\ndesired his attentions. The dog is let loose at night to prevent\nhim from endeavouring to communicate with her. So much is fairly\nclear. The most serious point in the case is the disposition of\nthe child.\"\n\n\"What on earth has that to do with it?\" I ejaculated.\n\n\"My dear Watson, you as a medical man are continually gaining\nlight as to the tendencies of a child by the study of the\nparents. Don't you see that the converse is equally valid. I have\nfrequently gained my first real insight into the character of\nparents by studying their children. This child's disposition is\nabnormally cruel, merely for cruelty's sake, and whether he\nderives this from his smiling father, as I should suspect, or\nfrom his mother, it bodes evil for the poor girl who is in their\npower.\"\n\n\"I am sure that you are right, Mr. Holmes,\" cried our client. \"A\nthousand things come back to me which make me certain that you\nhave hit it. Oh, let us lose not an instant in bringing help to\nthis poor creature.\"\n\n\"We must be circumspect, for we are dealing with a very cunning\nman. We can do nothing until seven o'clock. At that hour we shall\nbe with you, and it will not be long before we solve the\nmystery.\"\n\nWe were as good as our word, for it was just seven when we\nreached the Copper Beeches, having put up our trap at a wayside\npublic-house. The group of trees, with their dark leaves shining\nlike burnished metal in the light of the setting sun, were\nsufficient to mark the house even had Miss Hunter not been\nstanding smiling on the door-step.\n\n\"Have you managed it?\" asked Holmes.\n\nA loud thudding noise came from somewhere downstairs. \"That is\nMrs. Toller in the cellar,\" said she. \"Her husband lies snoring\non the kitchen rug. Here are his keys, which are the duplicates\nof Mr. Rucastle's.\"\n\n\"You have done well indeed!\" cried Holmes with enthusiasm. \"Now\nlead the way, and we shall soon see the end of this black\nbusiness.\"\n\nWe passed up the stair, unlocked the door, followed on down a\npassage, and found ourselves in front of the barricade which Miss\nHunter had described. Holmes cut the cord and removed the\ntransverse bar. Then he tried the various keys in the lock, but\nwithout success. No sound came from within, and at the silence\nHolmes' face clouded over.\n\n\"I trust that we are not too late,\" said he. \"I think, Miss\nHunter, that we had better go in without you. Now, Watson, put\nyour shoulder to it, and we shall see whether we cannot make our\nway in.\"\n\nIt was an old rickety door and gave at once before our united\nstrength. Together we rushed into the room. It was empty. There\nwas no furniture save a little pallet bed, a small table, and a\nbasketful of linen. The skylight above was open, and the prisoner\ngone.\n\n\"There has been some villainy here,\" said Holmes; \"this beauty\nhas guessed Miss Hunter's intentions and has carried his victim\noff.\"\n\n\"But how?\"\n\n\"Through the skylight. We shall soon see how he managed it.\" He\nswung himself up onto the roof. \"Ah, yes,\" he cried, \"here's the\nend of a long light ladder against the eaves. That is how he did\nit.\"\n\n\"But it is impossible,\" said Miss Hunter; \"the ladder was not\nthere when the Rucastles went away.\"\n\n\"He has come back and done it. I tell you that he is a clever and\ndangerous man. I should not be very much surprised if this were\nhe whose step I hear now upon the stair. I think, Watson, that it\nwould be as well for you to have your pistol ready.\"\n\nThe words were hardly out of his mouth before a man appeared at\nthe door of the room, a very fat and burly man, with a heavy\nstick in his hand. Miss Hunter screamed and shrunk against the\nwall at the sight of him, but Sherlock Holmes sprang forward and\nconfronted him.\n\n\"You villain!\" said he, \"where's your daughter?\"\n\nThe fat man cast his eyes round, and then up at the open\nskylight.\n\n\"It is for me to ask you that,\" he shrieked, \"you thieves! Spies\nand thieves! I have caught you, have I? You are in my power. I'll\nserve you!\" He turned and clattered down the stairs as hard as he\ncould go.\n\n\"He's gone for the dog!\" cried Miss Hunter.\n\n\"I have my revolver,\" said I.\n\n\"Better close the front door,\" cried Holmes, and we all rushed\ndown the stairs together. We had hardly reached the hall when we\nheard the baying of a hound, and then a scream of agony, with a\nhorrible worrying sound which it was dreadful to listen to. An\nelderly man with a red face and shaking limbs came staggering out\nat a side door.\n\n\"My God!\" he cried. \"Someone has loosed the dog. It's not been\nfed for two days. Quick, quick, or it'll be too late!\"\n\nHolmes and I rushed out and round the angle of the house, with\nToller hurrying behind us. There was the huge famished brute, its\nblack muzzle buried in Rucastle's throat, while he writhed and\nscreamed upon the ground. Running up, I blew its brains out, and\nit fell over with its keen white teeth still meeting in the great\ncreases of his neck. With much labour we separated them and\ncarried him, living but horribly mangled, into the house. We laid\nhim upon the drawing-room sofa, and having dispatched the sobered\nToller to bear the news to his wife, I did what I could to\nrelieve his pain. We were all assembled round him when the door\nopened, and a tall, gaunt woman entered the room.\n\n\"Mrs. Toller!\" cried Miss Hunter.\n\n\"Yes, miss. Mr. Rucastle let me out when he came back before he\nwent up to you. Ah, miss, it is a pity you didn't let me know\nwhat you were planning, for I would have told you that your pains\nwere wasted.\"\n\n\"Ha!\" said Holmes, looking keenly at her. \"It is clear that Mrs.\nToller knows more about this matter than anyone else.\"\n\n\"Yes, sir, I do, and I am ready enough to tell what I know.\"\n\n\"Then, pray, sit down, and let us hear it for there are several\npoints on which I must confess that I am still in the dark.\"\n\n\"I will soon make it clear to you,\" said she; \"and I'd have done\nso before now if I could ha' got out from the cellar. If there's\npolice-court business over this, you'll remember that I was the\none that stood your friend, and that I was Miss Alice's friend\ntoo.\n\n\"She was never happy at home, Miss Alice wasn't, from the time\nthat her father married again. She was slighted like and had no\nsay in anything, but it never really became bad for her until\nafter she met Mr. Fowler at a friend's house. As well as I could\nlearn, Miss Alice had rights of her own by will, but she was so\nquiet and patient, she was, that she never said a word about them\nbut just left everything in Mr. Rucastle's hands. He knew he was\nsafe with her; but when there was a chance of a husband coming\nforward, who would ask for all that the law would give him, then\nher father thought it time to put a stop on it. He wanted her to\nsign a paper, so that whether she married or not, he could use\nher money. When she wouldn't do it, he kept on worrying her until\nshe got brain-fever, and for six weeks was at death's door. Then\nshe got better at last, all worn to a shadow, and with her\nbeautiful hair cut off; but that didn't make no change in her\nyoung man, and he stuck to her as true as man could be.\"\n\n\"Ah,\" said Holmes, \"I think that what you have been good enough\nto tell us makes the matter fairly clear, and that I can deduce\nall that remains. Mr. Rucastle then, I presume, took to this\nsystem of imprisonment?\"\n\n\"Yes, sir.\"\n\n\"And brought Miss Hunter down from London in order to get rid of\nthe disagreeable persistence of Mr. Fowler.\"\n\n\"That was it, sir.\"\n\n\"But Mr. Fowler being a persevering man, as a good seaman should\nbe, blockaded the house, and having met you succeeded by certain\narguments, metallic or otherwise, in convincing you that your\ninterests were the same as his.\"\n\n\"Mr. Fowler was a very kind-spoken, free-handed gentleman,\" said\nMrs. Toller serenely.\n\n\"And in this way he managed that your good man should have no\nwant of drink, and that a ladder should be ready at the moment\nwhen your master had gone out.\"\n\n\"You have it, sir, just as it happened.\"\n\n\"I am sure we owe you an apology, Mrs. Toller,\" said Holmes, \"for\nyou have certainly cleared up everything which puzzled us. And\nhere comes the country surgeon and Mrs. Rucastle, so I think,\nWatson, that we had best escort Miss Hunter back to Winchester,\nas it seems to me that our locus standi now is rather a\nquestionable one.\"\n\nAnd thus was solved the mystery of the sinister house with the\ncopper beeches in front of the door. Mr. Rucastle survived, but\nwas always a broken man, kept alive solely through the care of\nhis devoted wife. They still live with their old servants, who\nprobably know so much of Rucastle's past life that he finds it\ndifficult to part from them. Mr. Fowler and Miss Rucastle were\nmarried, by special license, in Southampton the day after their\nflight, and he is now the holder of a government appointment in\nthe island of Mauritius. As to Miss Violet Hunter, my friend\nHolmes, rather to my disappointment, manifested no further\ninterest in her when once she had ceased to be the centre of one\nof his problems, and she is now the head of a private school at\nWalsall, where I believe that she has met with considerable success.\n\n\n\n\n\n\n\n\n\nEnd of the Project Gutenberg EBook of The Adventures of Sherlock Holmes, by \nArthur Conan Doyle\n\n*** END OF THIS PROJECT GUTENBERG EBOOK THE ADVENTURES OF SHERLOCK HOLMES ***\n\n***** This file should be named 1661-8.txt or 1661-8.zip *****\nThis and all associated files of various formats will be found in:\n        http://www.gutenberg.org/1/6/6/1661/\n\nProduced by an anonymous Project Gutenberg volunteer and Jose Menendez\n\nUpdated editions will replace the previous one--the old editions\nwill be renamed.\n\nCreating the works from public domain print editions means that no\none owns a United States copyright in these works, so the Foundation\n(and you!) can copy and distribute it in the United States without\npermission and without paying copyright royalties.  Special rules,\nset forth in the General Terms of Use part of this license, apply to\ncopying and distributing Project Gutenberg-tm electronic works to\nprotect the PROJECT GUTENBERG-tm concept and trademark.  Project\nGutenberg is a registered trademark, and may not be used if you\ncharge for the eBooks, unless you receive specific permission.  If you\ndo not charge anything for copies of this eBook, complying with the\nrules is very easy.  You may use this eBook for nearly any purpose\nsuch as creation of derivative works, reports, performances and\nresearch.  They may be modified and printed and given away--you may do\npractically ANYTHING with public domain eBooks.  Redistribution is\nsubject to the trademark license, especially commercial\nredistribution.\n\n\n\n*** START: FULL LICENSE ***\n\nTHE FULL PROJECT GUTENBERG LICENSE\nPLEASE READ THIS BEFORE YOU DISTRIBUTE OR USE THIS WORK\n\nTo protect the Project Gutenberg-tm mission of promoting the free\ndistribution of electronic works, by using or distributing this work\n(or any other work associated in any way with the phrase \"Project\nGutenberg\"), you agree to comply with all the terms of the Full Project\nGutenberg-tm License (available with this file or online at\nhttp://gutenberg.net/license).\n\n\nSection 1.  General Terms of Use and Redistributing Project Gutenberg-tm\nelectronic works\n\n1.A.  By reading or using any part of this Project Gutenberg-tm\nelectronic work, you indicate that you have read, understand, agree to\nand accept all the terms of this license and intellectual property\n(trademark/copyright) agreement.  If you do not agree to abide by all\nthe terms of this agreement, you must cease using and return or destroy\nall copies of Project Gutenberg-tm electronic works in your possession.\nIf you paid a fee for obtaining a copy of or access to a Project\nGutenberg-tm electronic work and you do not agree to be bound by the\nterms of this agreement, you may obtain a refund from the person or\nentity to whom you paid the fee as set forth in paragraph 1.E.8.\n\n1.B.  \"Project Gutenberg\" is a registered trademark.  It may only be\nused on or associated in any way with an electronic work by people who\nagree to be bound by the terms of this agreement.  There are a few\nthings that you can do with most Project Gutenberg-tm electronic works\neven without complying with the full terms of this agreement.  See\nparagraph 1.C below.  There are a lot of things you can do with Project\nGutenberg-tm electronic works if you follow the terms of this agreement\nand help preserve free future access to Project Gutenberg-tm electronic\nworks.  See paragraph 1.E below.\n\n1.C.  The Project Gutenberg Literary Archive Foundation (\"the Foundation\"\nor PGLAF), owns a compilation copyright in the collection of Project\nGutenberg-tm electronic works.  Nearly all the individual works in the\ncollection are in the public domain in the United States.  If an\nindividual work is in the public domain in the United States and you are\nlocated in the United States, we do not claim a right to prevent you from\ncopying, distributing, performing, displaying or creating derivative\nworks based on the work as long as all references to Project Gutenberg\nare removed.  Of course, we hope that you will support the Project\nGutenberg-tm mission of promoting free access to electronic works by\nfreely sharing Project Gutenberg-tm works in compliance with the terms of\nthis agreement for keeping the Project Gutenberg-tm name associated with\nthe work.  You can easily comply with the terms of this agreement by\nkeeping this work in the same format with its attached full Project\nGutenberg-tm License when you share it without charge with others.\n\n1.D.  The copyright laws of the place where you are located also govern\nwhat you can do with this work.  Copyright laws in most countries are in\na constant state of change.  If you are outside the United States, check\nthe laws of your country in addition to the terms of this agreement\nbefore downloading, copying, displaying, performing, distributing or\ncreating derivative works based on this work or any other Project\nGutenberg-tm work.  The Foundation makes no representations concerning\nthe copyright status of any work in any country outside the United\nStates.\n\n1.E.  Unless you have removed all references to Project Gutenberg:\n\n1.E.1.  The following sentence, with active links to, or other immediate\naccess to, the full Project Gutenberg-tm License must appear prominently\nwhenever any copy of a Project Gutenberg-tm work (any work on which the\nphrase \"Project Gutenberg\" appears, or with which the phrase \"Project\nGutenberg\" is associated) is accessed, displayed, performed, viewed,\ncopied or distributed:\n\nThis eBook is for the use of anyone anywhere at no cost and with\nalmost no restrictions whatsoever.  You may copy it, give it away or\nre-use it under the terms of the Project Gutenberg License included\nwith this eBook or online at www.gutenberg.net\n\n1.E.2.  If an individual Project Gutenberg-tm electronic work is derived\nfrom the public domain (does not contain a notice indicating that it is\nposted with permission of the copyright holder), the work can be copied\nand distributed to anyone in the United States without paying any fees\nor charges.  If you are redistributing or providing access to a work\nwith the phrase \"Project Gutenberg\" associated with or appearing on the\nwork, you must comply either with the requirements of paragraphs 1.E.1\nthrough 1.E.7 or obtain permission for the use of the work and the\nProject Gutenberg-tm trademark as set forth in paragraphs 1.E.8 or\n1.E.9.\n\n1.E.3.  If an individual Project Gutenberg-tm electronic work is posted\nwith the permission of the copyright holder, your use and distribution\nmust comply with both paragraphs 1.E.1 through 1.E.7 and any additional\nterms imposed by the copyright holder.  Additional terms will be linked\nto the Project Gutenberg-tm License for all works posted with the\npermission of the copyright holder found at the beginning of this work.\n\n1.E.4.  Do not unlink or detach or remove the full Project Gutenberg-tm\nLicense terms from this work, or any files containing a part of this\nwork or any other work associated with Project Gutenberg-tm.\n\n1.E.5.  Do not copy, display, perform, distribute or redistribute this\nelectronic work, or any part of this electronic work, without\nprominently displaying the sentence set forth in paragraph 1.E.1 with\nactive links or immediate access to the full terms of the Project\nGutenberg-tm License.\n\n1.E.6.  You may convert to and distribute this work in any binary,\ncompressed, marked up, nonproprietary or proprietary form, including any\nword processing or hypertext form.  However, if you provide access to or\ndistribute copies of a Project Gutenberg-tm work in a format other than\n\"Plain Vanilla ASCII\" or other format used in the official version\nposted on the official Project Gutenberg-tm web site (www.gutenberg.net),\nyou must, at no additional cost, fee or expense to the user, provide a\ncopy, a means of exporting a copy, or a means of obtaining a copy upon\nrequest, of the work in its original \"Plain Vanilla ASCII\" or other\nform.  Any alternate format must include the full Project Gutenberg-tm\nLicense as specified in paragraph 1.E.1.\n\n1.E.7.  Do not charge a fee for access to, viewing, displaying,\nperforming, copying or distributing any Project Gutenberg-tm works\nunless you comply with paragraph 1.E.8 or 1.E.9.\n\n1.E.8.  You may charge a reasonable fee for copies of or providing\naccess to or distributing Project Gutenberg-tm electronic works provided\nthat\n\n- You pay a royalty fee of 20% of the gross profits you derive from\n     the use of Project Gutenberg-tm works calculated using the method\n     you already use to calculate your applicable taxes.  The fee is\n     owed to the owner of the Project Gutenberg-tm trademark, but he\n     has agreed to donate royalties under this paragraph to the\n     Project Gutenberg Literary Archive Foundation.  Royalty payments\n     must be paid within 60 days following each date on which you\n     prepare (or are legally required to prepare) your periodic tax\n     returns.  Royalty payments should be clearly marked as such and\n     sent to the Project Gutenberg Literary Archive Foundation at the\n     address specified in Section 4, \"Information about donations to\n     the Project Gutenberg Literary Archive Foundation.\"\n\n- You provide a full refund of any money paid by a user who notifies\n     you in writing (or by e-mail) within 30 days of receipt that s/he\n     does not agree to the terms of the full Project Gutenberg-tm\n     License.  You must require such a user to return or\n     destroy all copies of the works possessed in a physical medium\n     and discontinue all use of and all access to other copies of\n     Project Gutenberg-tm works.\n\n- You provide, in accordance with paragraph 1.F.3, a full refund of any\n     money paid for a work or a replacement copy, if a defect in the\n     electronic work is discovered and reported to you within 90 days\n     of receipt of the work.\n\n- You comply with all other terms of this agreement for free\n     distribution of Project Gutenberg-tm works.\n\n1.E.9.  If you wish to charge a fee or distribute a Project Gutenberg-tm\nelectronic work or group of works on different terms than are set\nforth in this agreement, you must obtain permission in writing from\nboth the Project Gutenberg Literary Archive Foundation and Michael\nHart, the owner of the Project Gutenberg-tm trademark.  Contact the\nFoundation as set forth in Section 3 below.\n\n1.F.\n\n1.F.1.  Project Gutenberg volunteers and employees expend considerable\neffort to identify, do copyright research on, transcribe and proofread\npublic domain works in creating the Project Gutenberg-tm\ncollection.  Despite these efforts, Project Gutenberg-tm electronic\nworks, and the medium on which they may be stored, may contain\n\"Defects,\" such as, but not limited to, incomplete, inaccurate or\ncorrupt data, transcription errors, a copyright or other intellectual\nproperty infringement, a defective or damaged disk or other medium, a\ncomputer virus, or computer codes that damage or cannot be read by\nyour equipment.\n\n1.F.2.  LIMITED WARRANTY, DISCLAIMER OF DAMAGES - Except for the \"Right\nof Replacement or Refund\" described in paragraph 1.F.3, the Project\nGutenberg Literary Archive Foundation, the owner of the Project\nGutenberg-tm trademark, and any other party distributing a Project\nGutenberg-tm electronic work under this agreement, disclaim all\nliability to you for damages, costs and expenses, including legal\nfees.  YOU AGREE THAT YOU HAVE NO REMEDIES FOR NEGLIGENCE, STRICT\nLIABILITY, BREACH OF WARRANTY OR BREACH OF CONTRACT EXCEPT THOSE\nPROVIDED IN PARAGRAPH 1.F.3.  YOU AGREE THAT THE FOUNDATION, THE\nTRADEMARK OWNER, AND ANY DISTRIBUTOR UNDER THIS AGREEMENT WILL NOT BE\nLIABLE TO YOU FOR ACTUAL, DIRECT, INDIRECT, CONSEQUENTIAL, PUNITIVE OR\nINCIDENTAL DAMAGES EVEN IF YOU GIVE NOTICE OF THE POSSIBILITY OF SUCH\nDAMAGE.\n\n1.F.3.  LIMITED RIGHT OF REPLACEMENT OR REFUND - If you discover a\ndefect in this electronic work within 90 days of receiving it, you can\nreceive a refund of the money (if any) you paid for it by sending a\nwritten explanation to the person you received the work from.  If you\nreceived the work on a physical medium, you must return the medium with\nyour written explanation.  The person or entity that provided you with\nthe defective work may elect to provide a replacement copy in lieu of a\nrefund.  If you received the work electronically, the person or entity\nproviding it to you may choose to give you a second opportunity to\nreceive the work electronically in lieu of a refund.  If the second copy\nis also defective, you may demand a refund in writing without further\nopportunities to fix the problem.\n\n1.F.4.  Except for the limited right of replacement or refund set forth\nin paragraph 1.F.3, this work is provided to you 'AS-IS' WITH NO OTHER\nWARRANTIES OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\nWARRANTIES OF MERCHANTIBILITY OR FITNESS FOR ANY PURPOSE.\n\n1.F.5.  Some states do not allow disclaimers of certain implied\nwarranties or the exclusion or limitation of certain types of damages.\nIf any disclaimer or limitation set forth in this agreement violates the\nlaw of the state applicable to this agreement, the agreement shall be\ninterpreted to make the maximum disclaimer or limitation permitted by\nthe applicable state law.  The invalidity or unenforceability of any\nprovision of this agreement shall not void the remaining provisions.\n\n1.F.6.  INDEMNITY - You agree to indemnify and hold the Foundation, the\ntrademark owner, any agent or employee of the Foundation, anyone\nproviding copies of Project Gutenberg-tm electronic works in accordance\nwith this agreement, and any volunteers associated with the production,\npromotion and distribution of Project Gutenberg-tm electronic works,\nharmless from all liability, costs and expenses, including legal fees,\nthat arise directly or indirectly from any of the following which you do\nor cause to occur: (a) distribution of this or any Project Gutenberg-tm\nwork, (b) alteration, modification, or additions or deletions to any\nProject Gutenberg-tm work, and (c) any Defect you cause.\n\n\nSection  2.  Information about the Mission of Project Gutenberg-tm\n\nProject Gutenberg-tm is synonymous with the free distribution of\nelectronic works in formats readable by the widest variety of computers\nincluding obsolete, old, middle-aged and new computers.  It exists\nbecause of the efforts of hundreds of volunteers and donations from\npeople in all walks of life.\n\nVolunteers and financial support to provide volunteers with the\nassistance they need are critical to reaching Project Gutenberg-tm's\ngoals and ensuring that the Project Gutenberg-tm collection will\nremain freely available for generations to come.  In 2001, the Project\nGutenberg Literary Archive Foundation was created to provide a secure\nand permanent future for Project Gutenberg-tm and future generations.\nTo learn more about the Project Gutenberg Literary Archive Foundation\nand how your efforts and donations can help, see Sections 3 and 4\nand the Foundation web page at http://www.pglaf.org.\n\n\nSection 3.  Information about the Project Gutenberg Literary Archive\nFoundation\n\nThe Project Gutenberg Literary Archive Foundation is a non profit\n501(c)(3) educational corporation organized under the laws of the\nstate of Mississippi and granted tax exempt status by the Internal\nRevenue Service.  The Foundation's EIN or federal tax identification\nnumber is 64-6221541.  Its 501(c)(3) letter is posted at\nhttp://pglaf.org/fundraising.  Contributions to the Project Gutenberg\nLiterary Archive Foundation are tax deductible to the full extent\npermitted by U.S. federal laws and your state's laws.\n\nThe Foundation's principal office is located at 4557 Melan Dr. S.\nFairbanks, AK, 99712., but its volunteers and employees are scattered\nthroughout numerous locations.  Its business office is located at\n809 North 1500 West, Salt Lake City, UT 84116, (801) 596-1887, email\nbusiness@pglaf.org.  Email contact links and up to date contact\ninformation can be found at the Foundation's web site and official\npage at http://pglaf.org\n\nFor additional contact information:\n     Dr. Gregory B. Newby\n     Chief Executive and Director\n     gbnewby@pglaf.org\n\n\nSection 4.  Information about Donations to the Project Gutenberg\nLiterary Archive Foundation\n\nProject Gutenberg-tm depends upon and cannot survive without wide\nspread public support and donations to carry out its mission of\nincreasing the number of public domain and licensed works that can be\nfreely distributed in machine readable form accessible by the widest\narray of equipment including outdated equipment.  Many small donations\n($1 to $5,000) are particularly important to maintaining tax exempt\nstatus with the IRS.\n\nThe Foundation is committed to complying with the laws regulating\ncharities and charitable donations in all 50 states of the United\nStates.  Compliance requirements are not uniform and it takes a\nconsiderable effort, much paperwork and many fees to meet and keep up\nwith these requirements.  We do not solicit donations in locations\nwhere we have not received written confirmation of compliance.  To\nSEND DONATIONS or determine the status of compliance for any\nparticular state visit http://pglaf.org\n\nWhile we cannot and do not solicit contributions from states where we\nhave not met the solicitation requirements, we know of no prohibition\nagainst accepting unsolicited donations from donors in such states who\napproach us with offers to donate.\n\nInternational donations are gratefully accepted, but we cannot make\nany statements concerning tax treatment of donations received from\noutside the United States.  U.S. laws alone swamp our small staff.\n\nPlease check the Project Gutenberg Web pages for current donation\nmethods and addresses.  Donations are accepted in a number of other\nways including including checks, online payments and credit card\ndonations.  To donate, please visit: http://pglaf.org/donate\n\n\nSection 5.  General Information About Project Gutenberg-tm electronic\nworks.\n\nProfessor Michael S. Hart is the originator of the Project Gutenberg-tm\nconcept of a library of electronic works that could be freely shared\nwith anyone.  For thirty years, he produced and distributed Project\nGutenberg-tm eBooks with only a loose network of volunteer support.\n\n\nProject Gutenberg-tm eBooks are often created from several printed\neditions, all of which are confirmed as Public Domain in the U.S.\nunless a copyright notice is included.  Thus, we do not necessarily\nkeep eBooks in compliance with any particular paper edition.\n\n\nMost people start at our Web site which has the main PG search facility:\n\n     http://www.gutenberg.net\n\nThis Web site includes information about Project Gutenberg-tm,\nincluding how to make donations to the Project Gutenberg Literary\nArchive Foundation, how to help produce our new eBooks, and how to\nsubscribe to our email newsletter to hear about new eBooks.\n"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/hidden-files-folder/ipfs-add.js",
    "content": "#!/usr/bin/env node\n\nconst ipfs = require('../src')('localhost', 5001)\nconst files = process.argv.slice(2)\n\nipfs.add(files, { recursive: true }, function (err, res) {\n  if (err || !res) return console.log(err)\n\n  for (let i = 0; i < res.length; i++) {\n    console.log('added', res[i].Hash, res[i].Name)\n  }\n})\n"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/hidden-files-folder/jungle.txt",
    "content": "Mowgli's Brothers\n\n     Now Rann the Kite brings home the night\n        That Mang the Bat sets free--\n     The herds are shut in byre and hut\n        For loosed till dawn are we.\n     This is the hour of pride and power,\n        Talon and tush and claw.\n     Oh, hear the call!--Good hunting all\n        That keep the Jungle Law!\n     Night-Song in the Jungle\n\nIt was seven o'clock of a very warm evening in the Seeonee hills when\nFather Wolf woke up from his day's rest, scratched himself, yawned, and\nspread out his paws one after the other to get rid of the sleepy feeling\nin their tips. Mother Wolf lay with her big gray nose dropped across her\nfour tumbling, squealing cubs, and the moon shone into the mouth of the\ncave where they all lived. \"Augrh!\" said Father Wolf. \"It is time to\nhunt again.\" He was going to spring down hill when a little shadow with\na bushy tail crossed the threshold and whined: \"Good luck go with you, O\nChief of the Wolves. And good luck and strong white teeth go with noble\nchildren that they may never forget the hungry in this world.\"\n\nIt was the jackal--Tabaqui, the Dish-licker--and the wolves of India\ndespise Tabaqui because he runs about making mischief, and telling\ntales, and eating rags and pieces of leather from the village\nrubbish-heaps. But they are afraid of him too, because Tabaqui, more\nthan anyone else in the jungle, is apt to go mad, and then he forgets\nthat he was ever afraid of anyone, and runs through the forest biting\neverything in his way. Even the tiger runs and hides when little Tabaqui\ngoes mad, for madness is the most disgraceful thing that can overtake\na wild creature. We call it hydrophobia, but they call it dewanee--the\nmadness--and run.\n\n\"Enter, then, and look,\" said Father Wolf stiffly, \"but there is no food\nhere.\"\n\n\"For a wolf, no,\" said Tabaqui, \"but for so mean a person as myself a\ndry bone is a good feast. Who are we, the Gidur-log [the jackal people],\nto pick and choose?\" He scuttled to the back of the cave, where he\nfound the bone of a buck with some meat on it, and sat cracking the end\nmerrily.\n\n\"All thanks for this good meal,\" he said, licking his lips. \"How\nbeautiful are the noble children! How large are their eyes! And so young\ntoo! Indeed, indeed, I might have remembered that the children of kings\n"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/hidden-files-folder/pp.txt",
    "content": "PRIDE AND PREJUDICE\n\nBy Jane Austen\n\n\n\nChapter 1\n\n\nIt is a truth universally acknowledged, that a single man in possession\nof a good fortune, must be in want of a wife.\n\nHowever little known the feelings or views of such a man may be on his\nfirst entering a neighbourhood, this truth is so well fixed in the minds\nof the surrounding families, that he is considered the rightful property\nof some one or other of their daughters.\n\n\"My dear Mr. Bennet,\" said his lady to him one day, \"have you heard that\nNetherfield Park is let at last?\"\n\nMr. Bennet replied that he had not.\n\n\"But it is,\" returned she; \"for Mrs. Long has just been here, and she\ntold me all about it.\"\n\nMr. Bennet made no answer.\n\n\"Do you not want to know who has taken it?\" cried his wife impatiently.\n\n\"_You_ want to tell me, and I have no objection to hearing it.\"\n\nThis was invitation enough.\n\n\"Why, my dear, you must know, Mrs. Long says that Netherfield is taken\nby a young man of large fortune from the north of England; that he came\ndown on Monday in a chaise and four to see the place, and was so much\ndelighted with it, that he agreed with Mr. Morris immediately; that he\nis to take possession before Michaelmas, and some of his servants are to\nbe in the house by the end of next week.\"\n\n\"What is his name?\"\n\n\"Bingley.\"\n\n\"Is he married or single?\"\n\n\"Oh! Single, my dear, to be sure! A single man of large fortune; four or\nfive thousand a year. What a fine thing for our girls!\"\n\n\"How so? How can it affect them?\"\n\n\"My dear Mr. Bennet,\" replied his wife, \"how can you be so tiresome! You\nmust know that I am thinking of his marrying one of them.\"\n\n\"Is that his design in settling here?\"\n\n\"Design! Nonsense, how can you talk so! But it is very likely that he\n_may_ fall in love with one of them, and therefore you must visit him as\nsoon as he comes.\"\n\n\"I see no occasion for that. You and the girls may go, or you may send\nthem by themselves, which perhaps will be still better, for as you are\nas handsome as any of them, Mr. Bingley may like you the best of the\nparty.\"\n\n\"My dear, you flatter me. I certainly _have_ had my share of beauty, but\nI do not pretend to be anything extraordinary now. When a woman has five\ngrown-up daughters, she ought to give over thinking of her own beauty.\"\n\n\"In such cases, a woman has not often much beauty to think of.\"\n\n\"But, my dear, you must indeed go and see Mr. Bingley when he comes into\nthe neighbourhood.\"\n\n\"It is more than I engage for, I assure you.\"\n\n\"But consider your daughters. Only think what an establishment it would\nbe for one of them. Sir William and Lady Lucas are determined to\ngo, merely on that account, for in general, you know, they visit no\nnewcomers. Indeed you must go, for it will be impossible for _us_ to\nvisit him if you do not.\"\n\n\"You are over-scrupulous, surely. I dare say Mr. Bingley will be very\nglad to see you; and I will send a few lines by you to assure him of my\nhearty consent to his marrying whichever he chooses of the girls; though\nI must throw in a good word for my little Lizzy.\"\n\n\"I desire you will do no such thing. Lizzy is not a bit better than the\nothers; and I am sure she is not half so handsome as Jane, nor half so\ngood-humoured as Lydia. But you are always giving _her_ the preference.\"\n\n\"They have none of them much to recommend them,\" replied he; \"they are\nall silly and ignorant like other girls; but Lizzy has something more of\nquickness than her sisters.\"\n\n\"Mr. Bennet, how _can_ you abuse your own children in such a way? You\ntake delight in vexing me. You have no compassion for my poor nerves.\"\n\n\"You mistake me, my dear. I have a high respect for your nerves. They\nare my old friends. I have heard you mention them with consideration\nthese last twenty years at least.\"\n\n\"Ah, you do not know what I suffer.\"\n\n\"But I hope you will get over it, and live to see many young men of four\nthousand a year come into the neighbourhood.\"\n\n\"It will be no use to us, if twenty such should come, since you will not\nvisit them.\"\n\n\"Depend upon it, my dear, that when there are twenty, I will visit them\nall.\"\n\nMr. Bennet was so odd a mixture of quick parts, sarcastic humour,\nreserve, and caprice, that the experience of three-and-twenty years had\nbeen insufficient to make his wife understand his character. _Her_ mind\nwas less difficult to develop. She was a woman of mean understanding,\nlittle information, and uncertain temper. When she was discontented,\nshe fancied herself nervous. The business of her life was to get her\ndaughters married; its solace was visiting and news.\n"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/refs-test/animals/land/african.txt",
    "content": "elephant\nrhinocerous"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/refs-test/animals/land/americas.txt",
    "content": "ñandu\ntapir"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/refs-test/animals/land/australian.txt",
    "content": "emu\nkangaroo"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/refs-test/animals/sea/atlantic.txt",
    "content": "dolphin\nwhale"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/refs-test/animals/sea/indian.txt",
    "content": "cuttlefish\noctopus"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/refs-test/atlantic-animals",
    "content": "dolphin\nwhale"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/refs-test/fruits/tropical.txt",
    "content": "banana\npineapple"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/refs-test/mushroom.txt",
    "content": "mushroom"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/ssl/cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIC8jCCAdqgAwIBAgIUA7b/br1Ovf/mNDhm3P26ewfHbpIwDQYJKoZIhvcNAQEL\nBQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MCAXDTE5MDEyNDE4MjE0OVoYDzIxMTkw\nMTI0MTgyMTQ5WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB\nAQUAA4IBDwAwggEKAoIBAQCuskZ+qCJz72ihFgrF6yvjy7pXpETtHXJK+elXoU6M\noWNukWOgk9EZQow43wM6pQR/rTxDWE9W/qSpLb08cvW03+RlbyQn0lkO327rN9Nd\nVjP1Nu6WwAdk6U0CaGdNe4dwxc69eB3ZS4B32d/GIpIti23F3bRxAKE15km+Ufhj\nX1NGuFqbJOYHfbqOMkgMlkO54y4gAJa5tQnb3n0pNzpIklSzBO65T/u1HAsVDVZW\nBgVW/pqourvh6TtjCA3LJp33T9IcItTAlYbiFM0hKytlePzaHG6OKRazO6Z46l/V\ngTvRY90B7LrQWnSY3saLYuv9Gs5qQvEvthP/U6LroXjNAgMBAAGjOjA4MBQGA1Ud\nEQQNMAuCCWxvY2FsaG9zdDALBgNVHQ8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUH\nAwEwDQYJKoZIhvcNAQELBQADggEBAJHi6CJk5aZViLK+dm/ruV2vBiqGuRgfuviJ\nMb+iApO39Q/PjxE2IQoVVcf7Rpml2SSARyN7K9cxLdSFFZn3Wgq3yHXB6vhsyGO+\nr17awBEI08PUlCuYVlE/mEzHGUGYbR0whIQSWK+gLMSQ2NG11DJyIPnErZYM1XSS\np9ERjyR4KXC33RxEc0AtGZsGgCThGkmwas3v702pzGfDd3qpbXztb+jdbfUVMUj4\nWrzhps9JZ6HJ8RZBjnSMMqmWDbvJI+2aG0Ky6BYChrARLn9H7rCMgfe0l0QIL5br\nT1BqL+HHCVNiyt82+byg5mjpcsKvojCrQVVcQBs1xUkk6F45LeU=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/ssl/privkey.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCuskZ+qCJz72ih\nFgrF6yvjy7pXpETtHXJK+elXoU6MoWNukWOgk9EZQow43wM6pQR/rTxDWE9W/qSp\nLb08cvW03+RlbyQn0lkO327rN9NdVjP1Nu6WwAdk6U0CaGdNe4dwxc69eB3ZS4B3\n2d/GIpIti23F3bRxAKE15km+UfhjX1NGuFqbJOYHfbqOMkgMlkO54y4gAJa5tQnb\n3n0pNzpIklSzBO65T/u1HAsVDVZWBgVW/pqourvh6TtjCA3LJp33T9IcItTAlYbi\nFM0hKytlePzaHG6OKRazO6Z46l/VgTvRY90B7LrQWnSY3saLYuv9Gs5qQvEvthP/\nU6LroXjNAgMBAAECggEAYukJRNkJeL7KbLpAK0M1vGoy/UBCzkXn2k+ZMEZiZPlT\nhNzInbhToYuuPNz3xRJ9c5SwFClB8q2GqUr+Y+Vq/JfvhwbgX7OXPPaApKkdATG3\nhVUuzSe4iAgX1A8svg/85Xr5zQjfTZKUEEfJjTMxtJvG8UrPyVNj81KJ2joq+oek\nCspuQrhP9mpUaPBGmBykP0qbPqRO/E/eqMMEyfURDNXQjH1i4TLzUsIjd5IdkA9h\nDoC4fzK6R6LTjRdIEXK2xJGSKgIDnNu7sC72tTLRrbd4NeMjWg/yWEd7W/qQIy/6\nJG1Zj8zgjK1UnkPxZIwGuSOgCcNSCjA0WiSkF6sGeQKBgQDmvLIqCDaNDt+EOLoM\nHqXyoedriaJwzyTVUPf+EFHwwrOo/HoKesfca5coNIEJHMp5DjS+syPVTwKOfK82\nipPT9UuW+F9N+dWGKjCiZ0vwc6ijrq2pEeXuxrJWsIJ3yoKTXZWTRbRnu6kA+dVJ\nXGwqwnHni8cikAhdMzGyGi0vXwKBgQDB0tIfGOwmHo+0W94KjnIoB8dA/Cy9K0V+\nl/9IBX8I5/hFV2BF4IZoWepJ15UAJR855Tw5+0ea+/QpdSrl9MmmUQ0Hftvlmypp\nXYIDbWewgKHZOO6TnCs6FLtm6Zrq/1yrppnTslLXq/4QkOPSbQ9uY83N3upxfRIb\nV9//OLMDUwKBgHk7d9kBy7e9ss8EByzLBaJAUxl7jW/8RnwWONayuHrpsf/9+Bl9\nfXlgxmEHhSzGhdOpFSmFcjRneQ5okJ71nMpnPboq8dhEhl4h2L/byliiTF8ELpaA\novEcUSOfRk2uh4DqUOa6XxmJzjiHC/uppeOpmrNwC8crKlndxiSwAEG9AoGAFNLS\nkla6IEpORCFOlLHDH/vd82RkZhp9B+HKonE8ubc6XDDL/hXmOtXWLwLDVlWmqjCv\nrMcLZWJGVCHrbvNCquSwUqrVczCdeN579mRNrI/VU6IjN6aimkXZ8G+Onkq7KRHo\nGu9gqR0oWZ1HbLcc3k5IsSKO64x1YoypWyE7UlMCgYBaMIyJb8hd+lcgTy+WNJwZ\novcp8KoxUFONA/fd3iyWKYPq+5OTD5iDOy9LKXUrlYduFEQog6vUamlQyoyTplLs\nd3sIeeu9RRvuCidKzHtmQNAOw8adIa4GAnBvJ2G2oZ3lWnDgIbKgHido+YSyyI2E\njZOVuk3817sAOTaWSlQEZQ==\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/test-folder/alice.txt",
    "content": "CHAPTER XII. Alice's Evidence\n\n\n'Here!' cried Alice, quite forgetting in the flurry of the moment how\nlarge she had grown in the last few minutes, and she jumped up in such\na hurry that she tipped over the jury-box with the edge of her skirt,\nupsetting all the jurymen on to the heads of the crowd below, and there\nthey lay sprawling about, reminding her very much of a globe of goldfish\nshe had accidentally upset the week before.\n\n'Oh, I BEG your pardon!' she exclaimed in a tone of great dismay, and\nbegan picking them up again as quickly as she could, for the accident of\nthe goldfish kept running in her head, and she had a vague sort of idea\nthat they must be collected at once and put back into the jury-box, or\nthey would die.\n\n'The trial cannot proceed,' said the King in a very grave voice, 'until\nall the jurymen are back in their proper places--ALL,' he repeated with\ngreat emphasis, looking hard at Alice as he said do.\n\nAlice looked at the jury-box, and saw that, in her haste, she had put\nthe Lizard in head downwards, and the poor little thing was waving its\ntail about in a melancholy way, being quite unable to move. She soon got\nit out again, and put it right; 'not that it signifies much,' she said\nto herself; 'I should think it would be QUITE as much use in the trial\none way up as the other.'\n\nAs soon as the jury had a little recovered from the shock of being\nupset, and their slates and pencils had been found and handed back to\nthem, they set to work very diligently to write out a history of the\naccident, all except the Lizard, who seemed too much overcome to do\nanything but sit with its mouth open, gazing up into the roof of the\ncourt.\n\n'What do you know about this business?' the King said to Alice.\n\n'Nothing,' said Alice.\n\n'Nothing WHATEVER?' persisted the King.\n\n'Nothing whatever,' said Alice.\n\n'That's very important,' the King said, turning to the jury. They were\njust beginning to write this down on their slates, when the White Rabbit\ninterrupted: 'UNimportant, your Majesty means, of course,' he said in a\nvery respectful tone, but frowning and making faces at him as he spoke.\n\n'UNimportant, of course, I meant,' the King hastily said, and went on\nto himself in an undertone,\n\n'important--unimportant--unimportant--important--' as if he were trying\nwhich word sounded best.\n\nSome of the jury wrote it down 'important,' and some 'unimportant.'\nAlice could see this, as she was near enough to look over their slates;\n'but it doesn't matter a bit,' she thought to herself.\n\nAt this moment the King, who had been for some time busily writing in\nhis note-book, cackled out 'Silence!' and read out from his book, 'Rule\nForty-two. ALL PERSONS MORE THAN A MILE HIGH TO LEAVE THE COURT.'\n\nEverybody looked at Alice.\n\n'I'M not a mile high,' said Alice.\n\n'You are,' said the King.\n\n'Nearly two miles high,' added the Queen.\n\n'Well, I shan't go, at any rate,' said Alice: 'besides, that's not a\nregular rule: you invented it just now.'\n\n'It's the oldest rule in the book,' said the King.\n\n'Then it ought to be Number One,' said Alice.\n\nThe King turned pale, and shut his note-book hastily. 'Consider your\nverdict,' he said to the jury, in a low, trembling voice.\n\n'There's more evidence to come yet, please your Majesty,' said the White\nRabbit, jumping up in a great hurry; 'this paper has just been picked\nup.'\n\n'What's in it?' said the Queen.\n\n'I haven't opened it yet,' said the White Rabbit, 'but it seems to be a\nletter, written by the prisoner to--to somebody.'\n\n'It must have been that,' said the King, 'unless it was written to\nnobody, which isn't usual, you know.'\n\n'Who is it directed to?' said one of the jurymen.\n\n'It isn't directed at all,' said the White Rabbit; 'in fact, there's\nnothing written on the OUTSIDE.' He unfolded the paper as he spoke, and\nadded 'It isn't a letter, after all: it's a set of verses.'\n\n'Are they in the prisoner's handwriting?' asked another of the jurymen.\n\n'No, they're not,' said the White Rabbit, 'and that's the queerest thing\nabout it.' (The jury all looked puzzled.)\n\n'He must have imitated somebody else's hand,' said the King. (The jury\nall brightened up again.)\n\n'Please your Majesty,' said the Knave, 'I didn't write it, and they\ncan't prove I did: there's no name signed at the end.'\n\n'If you didn't sign it,' said the King, 'that only makes the matter\nworse. You MUST have meant some mischief, or else you'd have signed your\nname like an honest man.'\n\nThere was a general clapping of hands at this: it was the first really\nclever thing the King had said that day.\n\n'That PROVES his guilt,' said the Queen.\n\n'It proves nothing of the sort!' said Alice. 'Why, you don't even know\nwhat they're about!'\n\n'Read them,' said the King.\n\nThe White Rabbit put on his spectacles. 'Where shall I begin, please\nyour Majesty?' he asked.\n\n'Begin at the beginning,' the King said gravely, 'and go on till you\ncome to the end: then stop.'\n\nThese were the verses the White Rabbit read:--\n\n   'They told me you had been to her,\n    And mentioned me to him:\n   She gave me a good character,\n    But said I could not swim.\n\n   He sent them word I had not gone\n    (We know it to be true):\n   If she should push the matter on,\n    What would become of you?\n\n   I gave her one, they gave him two,\n    You gave us three or more;\n   They all returned from him to you,\n    Though they were mine before.\n\n   If I or she should chance to be\n    Involved in this affair,\n   He trusts to you to set them free,\n    Exactly as we were.\n\n   My notion was that you had been\n    (Before she had this fit)\n   An obstacle that came between\n    Him, and ourselves, and it.\n\n   Don't let him know she liked them best,\n    For this must ever be\n   A secret, kept from all the rest,\n    Between yourself and me.'\n\n'That's the most important piece of evidence we've heard yet,' said the\nKing, rubbing his hands; 'so now let the jury--'\n\n'If any one of them can explain it,' said Alice, (she had grown so large\nin the last few minutes that she wasn't a bit afraid of interrupting\nhim,) 'I'll give him sixpence. _I_ don't believe there's an atom of\nmeaning in it.'\n\nThe jury all wrote down on their slates, 'SHE doesn't believe there's an\natom of meaning in it,' but none of them attempted to explain the paper.\n\n'If there's no meaning in it,' said the King, 'that saves a world of\ntrouble, you know, as we needn't try to find any. And yet I don't know,'\nhe went on, spreading out the verses on his knee, and looking at them\nwith one eye; 'I seem to see some meaning in them, after all. \"--SAID\nI COULD NOT SWIM--\" you can't swim, can you?' he added, turning to the\nKnave.\n\nThe Knave shook his head sadly. 'Do I look like it?' he said. (Which he\ncertainly did NOT, being made entirely of cardboard.)\n\n'All right, so far,' said the King, and he went on muttering over\nthe verses to himself: '\"WE KNOW IT TO BE TRUE--\" that's the jury, of\ncourse--\"I GAVE HER ONE, THEY GAVE HIM TWO--\" why, that must be what he\ndid with the tarts, you know--'\n\n'But, it goes on \"THEY ALL RETURNED FROM HIM TO YOU,\"' said Alice.\n\n'Why, there they are!' said the King triumphantly, pointing to the tarts\non the table. 'Nothing can be clearer than THAT. Then again--\"BEFORE SHE\nHAD THIS FIT--\" you never had fits, my dear, I think?' he said to the\nQueen.\n\n'Never!' said the Queen furiously, throwing an inkstand at the Lizard\nas she spoke. (The unfortunate little Bill had left off writing on his\nslate with one finger, as he found it made no mark; but he now hastily\nbegan again, using the ink, that was trickling down his face, as long as\nit lasted.)\n\n'Then the words don't FIT you,' said the King, looking round the court\nwith a smile. There was a dead silence.\n\n'It's a pun!' the King added in an offended tone, and everybody laughed,\n'Let the jury consider their verdict,' the King said, for about the\ntwentieth time that day.\n\n'No, no!' said the Queen. 'Sentence first--verdict afterwards.'\n\n'Stuff and nonsense!' said Alice loudly. 'The idea of having the\nsentence first!'\n\n'Hold your tongue!' said the Queen, turning purple.\n\n'I won't!' said Alice.\n\n'Off with her head!' the Queen shouted at the top of her voice. Nobody\nmoved.\n\n'Who cares for you?' said Alice, (she had grown to her full size by this\ntime.) 'You're nothing but a pack of cards!'\n\nAt this the whole pack rose up into the air, and came flying down upon\nher: she gave a little scream, half of fright and half of anger, and\ntried to beat them off, and found herself lying on the bank, with her\nhead in the lap of her sister, who was gently brushing away some dead\nleaves that had fluttered down from the trees upon her face.\n\n'Wake up, Alice dear!' said her sister; 'Why, what a long sleep you've\nhad!'\n\n'Oh, I've had such a curious dream!' said Alice, and she told her\nsister, as well as she could remember them, all these strange Adventures\nof hers that you have just been reading about; and when she had\nfinished, her sister kissed her, and said, 'It WAS a curious dream,\ndear, certainly: but now run in to your tea; it's getting late.' So\nAlice got up and ran off, thinking while she ran, as well she might,\nwhat a wonderful dream it had been.\n\nBut her sister sat still just as she left her, leaning her head on her\nhand, watching the setting sun, and thinking of little Alice and all her\nwonderful Adventures, till she too began dreaming after a fashion, and\nthis was her dream:--\n\nFirst, she dreamed of little Alice herself, and once again the tiny\nhands were clasped upon her knee, and the bright eager eyes were looking\nup into hers--she could hear the very tones of her voice, and see that\nqueer little toss of her head to keep back the wandering hair that\nWOULD always get into her eyes--and still as she listened, or seemed to\nlisten, the whole place around her became alive with the strange creatures\nof her little sister's dream.\n\nThe long grass rustled at her feet as the White Rabbit hurried by--the\nfrightened Mouse splashed his way through the neighbouring pool--she\ncould hear the rattle of the teacups as the March Hare and his friends\nshared their never-ending meal, and the shrill voice of the Queen\nordering off her unfortunate guests to execution--once more the pig-baby\nwas sneezing on the Duchess's knee, while plates and dishes crashed\naround it--once more the shriek of the Gryphon, the squeaking of the\nLizard's slate-pencil, and the choking of the suppressed guinea-pigs,\nfilled the air, mixed up with the distant sobs of the miserable Mock\nTurtle.\n\nSo she sat on, with closed eyes, and half believed herself in\nWonderland, though she knew she had but to open them again, and all\nwould change to dull reality--the grass would be only rustling in the\nwind, and the pool rippling to the waving of the reeds--the rattling\nteacups would change to tinkling sheep-bells, and the Queen's shrill\ncries to the voice of the shepherd boy--and the sneeze of the baby, the\nshriek of the Gryphon, and all the other queer noises, would change (she\nknew) to the confused clamour of the busy farm-yard--while the lowing\nof the cattle in the distance would take the place of the Mock Turtle's\nheavy sobs.\n\nLastly, she pictured to herself how this same little sister of hers\nwould, in the after-time, be herself a grown woman; and how she would\nkeep, through all her riper years, the simple and loving heart of her\nchildhood: and how she would gather about her other little children, and\nmake THEIR eyes bright and eager with many a strange tale, perhaps even\nwith the dream of Wonderland of long ago: and how she would feel with\nall their simple sorrows, and find a pleasure in all their simple joys,\nremembering her own child-life, and the happy summer days.\n\n              THE END\n"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/test-folder/files/hello.txt",
    "content": "Hello\n"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/test-folder/files/ipfs.txt",
    "content": "IPFS\n"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/test-folder/holmes.txt",
    "content": "Project Gutenberg's The Adventures of Sherlock Holmes, by Arthur Conan Doyle\n\nThis eBook is for the use of anyone anywhere at no cost and with\nalmost no restrictions whatsoever.  You may copy it, give it away or\nre-use it under the terms of the Project Gutenberg License included\nwith this eBook or online at www.gutenberg.net\n\n\nTitle: The Adventures of Sherlock Holmes\n\nAuthor: Arthur Conan Doyle\n\nPosting Date: April 18, 2011 [EBook #1661]\nFirst Posted: November 29, 2002\n\nLanguage: English\n\n\n*** START OF THIS PROJECT GUTENBERG EBOOK THE ADVENTURES OF SHERLOCK HOLMES ***\n\n\n\n\nProduced by an anonymous Project Gutenberg volunteer and Jose Menendez\n\n\n\n\n\n\n\n\n\nTHE ADVENTURES OF SHERLOCK HOLMES\n\nby\n\nSIR ARTHUR CONAN DOYLE\n\n\n\n   I. A Scandal in Bohemia\n  II. The Red-headed League\n III. A Case of Identity\n  IV. The Boscombe Valley Mystery\n   V. The Five Orange Pips\n  VI. The Man with the Twisted Lip\n VII. The Adventure of the Blue Carbuncle\nVIII. The Adventure of the Speckled Band\n  IX. The Adventure of the Engineer's Thumb\n   X. The Adventure of the Noble Bachelor\n  XI. The Adventure of the Beryl Coronet\n XII. The Adventure of the Copper Beeches\n\n\n\n\nADVENTURE I. A SCANDAL IN BOHEMIA\n\nI.\n\nTo Sherlock Holmes she is always THE woman. I have seldom heard\nhim mention her under any other name. In his eyes she eclipses\nand predominates the whole of her sex. It was not that he felt\nany emotion akin to love for Irene Adler. All emotions, and that\none particularly, were abhorrent to his cold, precise but\nadmirably balanced mind. He was, I take it, the most perfect\nreasoning and observing machine that the world has seen, but as a\nlover he would have placed himself in a false position. He never\nspoke of the softer passions, save with a gibe and a sneer. They\nwere admirable things for the observer--excellent for drawing the\nveil from men's motives and actions. But for the trained reasoner\nto admit such intrusions into his own delicate and finely\nadjusted temperament was to introduce a distracting factor which\nmight throw a doubt upon all his mental results. Grit in a\nsensitive instrument, or a crack in one of his own high-power\nlenses, would not be more disturbing than a strong emotion in a\nnature such as his. And yet there was but one woman to him, and\nthat woman was the late Irene Adler, of dubious and questionable\nmemory.\n\nI had seen little of Holmes lately. My marriage had drifted us\naway from each other. My own complete happiness, and the\nhome-centred interests which rise up around the man who first\nfinds himself master of his own establishment, were sufficient to\nabsorb all my attention, while Holmes, who loathed every form of\nsociety with his whole Bohemian soul, remained in our lodgings in\nBaker Street, buried among his old books, and alternating from\nweek to week between cocaine and ambition, the drowsiness of the\ndrug, and the fierce energy of his own keen nature. He was still,\nas ever, deeply attracted by the study of crime, and occupied his\nimmense faculties and extraordinary powers of observation in\nfollowing out those clues, and clearing up those mysteries which\nhad been abandoned as hopeless by the official police. From time\nto time I heard some vague account of his doings: of his summons\nto Odessa in the case of the Trepoff murder, of his clearing up\nof the singular tragedy of the Atkinson brothers at Trincomalee,\nand finally of the mission which he had accomplished so\ndelicately and successfully for the reigning family of Holland.\nBeyond these signs of his activity, however, which I merely\nshared with all the readers of the daily press, I knew little of\nmy former friend and companion.\n\nOne night--it was on the twentieth of March, 1888--I was\nreturning from a journey to a patient (for I had now returned to\ncivil practice), when my way led me through Baker Street. As I\npassed the well-remembered door, which must always be associated\nin my mind with my wooing, and with the dark incidents of the\nStudy in Scarlet, I was seized with a keen desire to see Holmes\nagain, and to know how he was employing his extraordinary powers.\nHis rooms were brilliantly lit, and, even as I looked up, I saw\nhis tall, spare figure pass twice in a dark silhouette against\nthe blind. He was pacing the room swiftly, eagerly, with his head\nsunk upon his chest and his hands clasped behind him. To me, who\nknew his every mood and habit, his attitude and manner told their\nown story. He was at work again. He had risen out of his\ndrug-created dreams and was hot upon the scent of some new\nproblem. I rang the bell and was shown up to the chamber which\nhad formerly been in part my own.\n\nHis manner was not effusive. It seldom was; but he was glad, I\nthink, to see me. With hardly a word spoken, but with a kindly\neye, he waved me to an armchair, threw across his case of cigars,\nand indicated a spirit case and a gasogene in the corner. Then he\nstood before the fire and looked me over in his singular\nintrospective fashion.\n\n\"Wedlock suits you,\" he remarked. \"I think, Watson, that you have\nput on seven and a half pounds since I saw you.\"\n\n\"Seven!\" I answered.\n\n\"Indeed, I should have thought a little more. Just a trifle more,\nI fancy, Watson. And in practice again, I observe. You did not\ntell me that you intended to go into harness.\"\n\n\"Then, how do you know?\"\n\n\"I see it, I deduce it. How do I know that you have been getting\nyourself very wet lately, and that you have a most clumsy and\ncareless servant girl?\"\n\n\"My dear Holmes,\" said I, \"this is too much. You would certainly\nhave been burned, had you lived a few centuries ago. It is true\nthat I had a country walk on Thursday and came home in a dreadful\nmess, but as I have changed my clothes I can't imagine how you\ndeduce it. As to Mary Jane, she is incorrigible, and my wife has\ngiven her notice, but there, again, I fail to see how you work it\nout.\"\n\nHe chuckled to himself and rubbed his long, nervous hands\ntogether.\n\n\"It is simplicity itself,\" said he; \"my eyes tell me that on the\ninside of your left shoe, just where the firelight strikes it,\nthe leather is scored by six almost parallel cuts. Obviously they\nhave been caused by someone who has very carelessly scraped round\nthe edges of the sole in order to remove crusted mud from it.\nHence, you see, my double deduction that you had been out in vile\nweather, and that you had a particularly malignant boot-slitting\nspecimen of the London slavey. As to your practice, if a\ngentleman walks into my rooms smelling of iodoform, with a black\nmark of nitrate of silver upon his right forefinger, and a bulge\non the right side of his top-hat to show where he has secreted\nhis stethoscope, I must be dull, indeed, if I do not pronounce\nhim to be an active member of the medical profession.\"\n\nI could not help laughing at the ease with which he explained his\nprocess of deduction. \"When I hear you give your reasons,\" I\nremarked, \"the thing always appears to me to be so ridiculously\nsimple that I could easily do it myself, though at each\nsuccessive instance of your reasoning I am baffled until you\nexplain your process. And yet I believe that my eyes are as good\nas yours.\"\n\n\"Quite so,\" he answered, lighting a cigarette, and throwing\nhimself down into an armchair. \"You see, but you do not observe.\nThe distinction is clear. For example, you have frequently seen\nthe steps which lead up from the hall to this room.\"\n\n\"Frequently.\"\n\n\"How often?\"\n\n\"Well, some hundreds of times.\"\n\n\"Then how many are there?\"\n\n\"How many? I don't know.\"\n\n\"Quite so! You have not observed. And yet you have seen. That is\njust my point. Now, I know that there are seventeen steps,\nbecause I have both seen and observed. By-the-way, since you are\ninterested in these little problems, and since you are good\nenough to chronicle one or two of my trifling experiences, you\nmay be interested in this.\" He threw over a sheet of thick,\npink-tinted note-paper which had been lying open upon the table.\n\"It came by the last post,\" said he. \"Read it aloud.\"\n\nThe note was undated, and without either signature or address.\n\n\"There will call upon you to-night, at a quarter to eight\no'clock,\" it said, \"a gentleman who desires to consult you upon a\nmatter of the very deepest moment. Your recent services to one of\nthe royal houses of Europe have shown that you are one who may\nsafely be trusted with matters which are of an importance which\ncan hardly be exaggerated. This account of you we have from all\nquarters received. Be in your chamber then at that hour, and do\nnot take it amiss if your visitor wear a mask.\"\n\n\"This is indeed a mystery,\" I remarked. \"What do you imagine that\nit means?\"\n\n\"I have no data yet. It is a capital mistake to theorize before\none has data. Insensibly one begins to twist facts to suit\ntheories, instead of theories to suit facts. But the note itself.\nWhat do you deduce from it?\"\n\nI carefully examined the writing, and the paper upon which it was\nwritten.\n\n\"The man who wrote it was presumably well to do,\" I remarked,\nendeavouring to imitate my companion's processes. \"Such paper\ncould not be bought under half a crown a packet. It is peculiarly\nstrong and stiff.\"\n\n\"Peculiar--that is the very word,\" said Holmes. \"It is not an\nEnglish paper at all. Hold it up to the light.\"\n\nI did so, and saw a large \"E\" with a small \"g,\" a \"P,\" and a\nlarge \"G\" with a small \"t\" woven into the texture of the paper.\n\n\"What do you make of that?\" asked Holmes.\n\n\"The name of the maker, no doubt; or his monogram, rather.\"\n\n\"Not at all. The 'G' with the small 't' stands for\n'Gesellschaft,' which is the German for 'Company.' It is a\ncustomary contraction like our 'Co.' 'P,' of course, stands for\n'Papier.' Now for the 'Eg.' Let us glance at our Continental\nGazetteer.\" He took down a heavy brown volume from his shelves.\n\"Eglow, Eglonitz--here we are, Egria. It is in a German-speaking\ncountry--in Bohemia, not far from Carlsbad. 'Remarkable as being\nthe scene of the death of Wallenstein, and for its numerous\nglass-factories and paper-mills.' Ha, ha, my boy, what do you\nmake of that?\" His eyes sparkled, and he sent up a great blue\ntriumphant cloud from his cigarette.\n\n\"The paper was made in Bohemia,\" I said.\n\n\"Precisely. And the man who wrote the note is a German. Do you\nnote the peculiar construction of the sentence--'This account of\nyou we have from all quarters received.' A Frenchman or Russian\ncould not have written that. It is the German who is so\nuncourteous to his verbs. It only remains, therefore, to discover\nwhat is wanted by this German who writes upon Bohemian paper and\nprefers wearing a mask to showing his face. And here he comes, if\nI am not mistaken, to resolve all our doubts.\"\n\nAs he spoke there was the sharp sound of horses' hoofs and\ngrating wheels against the curb, followed by a sharp pull at the\nbell. Holmes whistled.\n\n\"A pair, by the sound,\" said he. \"Yes,\" he continued, glancing\nout of the window. \"A nice little brougham and a pair of\nbeauties. A hundred and fifty guineas apiece. There's money in\nthis case, Watson, if there is nothing else.\"\n\n\"I think that I had better go, Holmes.\"\n\n\"Not a bit, Doctor. Stay where you are. I am lost without my\nBoswell. And this promises to be interesting. It would be a pity\nto miss it.\"\n\n\"But your client--\"\n\n\"Never mind him. I may want your help, and so may he. Here he\ncomes. Sit down in that armchair, Doctor, and give us your best\nattention.\"\n\nA slow and heavy step, which had been heard upon the stairs and\nin the passage, paused immediately outside the door. Then there\nwas a loud and authoritative tap.\n\n\"Come in!\" said Holmes.\n\nA man entered who could hardly have been less than six feet six\ninches in height, with the chest and limbs of a Hercules. His\ndress was rich with a richness which would, in England, be looked\nupon as akin to bad taste. Heavy bands of astrakhan were slashed\nacross the sleeves and fronts of his double-breasted coat, while\nthe deep blue cloak which was thrown over his shoulders was lined\nwith flame-coloured silk and secured at the neck with a brooch\nwhich consisted of a single flaming beryl. Boots which extended\nhalfway up his calves, and which were trimmed at the tops with\nrich brown fur, completed the impression of barbaric opulence\nwhich was suggested by his whole appearance. He carried a\nbroad-brimmed hat in his hand, while he wore across the upper\npart of his face, extending down past the cheekbones, a black\nvizard mask, which he had apparently adjusted that very moment,\nfor his hand was still raised to it as he entered. From the lower\npart of the face he appeared to be a man of strong character,\nwith a thick, hanging lip, and a long, straight chin suggestive\nof resolution pushed to the length of obstinacy.\n\n\"You had my note?\" he asked with a deep harsh voice and a\nstrongly marked German accent. \"I told you that I would call.\" He\nlooked from one to the other of us, as if uncertain which to\naddress.\n\n\"Pray take a seat,\" said Holmes. \"This is my friend and\ncolleague, Dr. Watson, who is occasionally good enough to help me\nin my cases. Whom have I the honour to address?\"\n\n\"You may address me as the Count Von Kramm, a Bohemian nobleman.\nI understand that this gentleman, your friend, is a man of honour\nand discretion, whom I may trust with a matter of the most\nextreme importance. If not, I should much prefer to communicate\nwith you alone.\"\n\nI rose to go, but Holmes caught me by the wrist and pushed me\nback into my chair. \"It is both, or none,\" said he. \"You may say\nbefore this gentleman anything which you may say to me.\"\n\nThe Count shrugged his broad shoulders. \"Then I must begin,\" said\nhe, \"by binding you both to absolute secrecy for two years; at\nthe end of that time the matter will be of no importance. At\npresent it is not too much to say that it is of such weight it\nmay have an influence upon European history.\"\n\n\"I promise,\" said Holmes.\n\n\"And I.\"\n\n\"You will excuse this mask,\" continued our strange visitor. \"The\naugust person who employs me wishes his agent to be unknown to\nyou, and I may confess at once that the title by which I have\njust called myself is not exactly my own.\"\n\n\"I was aware of it,\" said Holmes dryly.\n\n\"The circumstances are of great delicacy, and every precaution\nhas to be taken to quench what might grow to be an immense\nscandal and seriously compromise one of the reigning families of\nEurope. To speak plainly, the matter implicates the great House\nof Ormstein, hereditary kings of Bohemia.\"\n\n\"I was also aware of that,\" murmured Holmes, settling himself\ndown in his armchair and closing his eyes.\n\nOur visitor glanced with some apparent surprise at the languid,\nlounging figure of the man who had been no doubt depicted to him\nas the most incisive reasoner and most energetic agent in Europe.\nHolmes slowly reopened his eyes and looked impatiently at his\ngigantic client.\n\n\"If your Majesty would condescend to state your case,\" he\nremarked, \"I should be better able to advise you.\"\n\nThe man sprang from his chair and paced up and down the room in\nuncontrollable agitation. Then, with a gesture of desperation, he\ntore the mask from his face and hurled it upon the ground. \"You\nare right,\" he cried; \"I am the King. Why should I attempt to\nconceal it?\"\n\n\"Why, indeed?\" murmured Holmes. \"Your Majesty had not spoken\nbefore I was aware that I was addressing Wilhelm Gottsreich\nSigismond von Ormstein, Grand Duke of Cassel-Felstein, and\nhereditary King of Bohemia.\"\n\n\"But you can understand,\" said our strange visitor, sitting down\nonce more and passing his hand over his high white forehead, \"you\ncan understand that I am not accustomed to doing such business in\nmy own person. Yet the matter was so delicate that I could not\nconfide it to an agent without putting myself in his power. I\nhave come incognito from Prague for the purpose of consulting\nyou.\"\n\n\"Then, pray consult,\" said Holmes, shutting his eyes once more.\n\n\"The facts are briefly these: Some five years ago, during a\nlengthy visit to Warsaw, I made the acquaintance of the well-known\nadventuress, Irene Adler. The name is no doubt familiar to you.\"\n\n\"Kindly look her up in my index, Doctor,\" murmured Holmes without\nopening his eyes. For many years he had adopted a system of\ndocketing all paragraphs concerning men and things, so that it\nwas difficult to name a subject or a person on which he could not\nat once furnish information. In this case I found her biography\nsandwiched in between that of a Hebrew rabbi and that of a\nstaff-commander who had written a monograph upon the deep-sea\nfishes.\n\n\"Let me see!\" said Holmes. \"Hum! Born in New Jersey in the year\n1858. Contralto--hum! La Scala, hum! Prima donna Imperial Opera\nof Warsaw--yes! Retired from operatic stage--ha! Living in\nLondon--quite so! Your Majesty, as I understand, became entangled\nwith this young person, wrote her some compromising letters, and\nis now desirous of getting those letters back.\"\n\n\"Precisely so. But how--\"\n\n\"Was there a secret marriage?\"\n\n\"None.\"\n\n\"No legal papers or certificates?\"\n\n\"None.\"\n\n\"Then I fail to follow your Majesty. If this young person should\nproduce her letters for blackmailing or other purposes, how is\nshe to prove their authenticity?\"\n\n\"There is the writing.\"\n\n\"Pooh, pooh! Forgery.\"\n\n\"My private note-paper.\"\n\n\"Stolen.\"\n\n\"My own seal.\"\n\n\"Imitated.\"\n\n\"My photograph.\"\n\n\"Bought.\"\n\n\"We were both in the photograph.\"\n\n\"Oh, dear! That is very bad! Your Majesty has indeed committed an\nindiscretion.\"\n\n\"I was mad--insane.\"\n\n\"You have compromised yourself seriously.\"\n\n\"I was only Crown Prince then. I was young. I am but thirty now.\"\n\n\"It must be recovered.\"\n\n\"We have tried and failed.\"\n\n\"Your Majesty must pay. It must be bought.\"\n\n\"She will not sell.\"\n\n\"Stolen, then.\"\n\n\"Five attempts have been made. Twice burglars in my pay ransacked\nher house. Once we diverted her luggage when she travelled. Twice\nshe has been waylaid. There has been no result.\"\n\n\"No sign of it?\"\n\n\"Absolutely none.\"\n\nHolmes laughed. \"It is quite a pretty little problem,\" said he.\n\n\"But a very serious one to me,\" returned the King reproachfully.\n\n\"Very, indeed. And what does she propose to do with the\nphotograph?\"\n\n\"To ruin me.\"\n\n\"But how?\"\n\n\"I am about to be married.\"\n\n\"So I have heard.\"\n\n\"To Clotilde Lothman von Saxe-Meningen, second daughter of the\nKing of Scandinavia. You may know the strict principles of her\nfamily. She is herself the very soul of delicacy. A shadow of a\ndoubt as to my conduct would bring the matter to an end.\"\n\n\"And Irene Adler?\"\n\n\"Threatens to send them the photograph. And she will do it. I\nknow that she will do it. You do not know her, but she has a soul\nof steel. She has the face of the most beautiful of women, and\nthe mind of the most resolute of men. Rather than I should marry\nanother woman, there are no lengths to which she would not\ngo--none.\"\n\n\"You are sure that she has not sent it yet?\"\n\n\"I am sure.\"\n\n\"And why?\"\n\n\"Because she has said that she would send it on the day when the\nbetrothal was publicly proclaimed. That will be next Monday.\"\n\n\"Oh, then we have three days yet,\" said Holmes with a yawn. \"That\nis very fortunate, as I have one or two matters of importance to\nlook into just at present. Your Majesty will, of course, stay in\nLondon for the present?\"\n\n\"Certainly. You will find me at the Langham under the name of the\nCount Von Kramm.\"\n\n\"Then I shall drop you a line to let you know how we progress.\"\n\n\"Pray do so. I shall be all anxiety.\"\n\n\"Then, as to money?\"\n\n\"You have carte blanche.\"\n\n\"Absolutely?\"\n\n\"I tell you that I would give one of the provinces of my kingdom\nto have that photograph.\"\n\n\"And for present expenses?\"\n\nThe King took a heavy chamois leather bag from under his cloak\nand laid it on the table.\n\n\"There are three hundred pounds in gold and seven hundred in\nnotes,\" he said.\n\nHolmes scribbled a receipt upon a sheet of his note-book and\nhanded it to him.\n\n\"And Mademoiselle's address?\" he asked.\n\n\"Is Briony Lodge, Serpentine Avenue, St. John's Wood.\"\n\nHolmes took a note of it. \"One other question,\" said he. \"Was the\nphotograph a cabinet?\"\n\n\"It was.\"\n\n\"Then, good-night, your Majesty, and I trust that we shall soon\nhave some good news for you. And good-night, Watson,\" he added,\nas the wheels of the royal brougham rolled down the street. \"If\nyou will be good enough to call to-morrow afternoon at three\no'clock I should like to chat this little matter over with you.\"\n\n\nII.\n\nAt three o'clock precisely I was at Baker Street, but Holmes had\nnot yet returned. The landlady informed me that he had left the\nhouse shortly after eight o'clock in the morning. I sat down\nbeside the fire, however, with the intention of awaiting him,\nhowever long he might be. I was already deeply interested in his\ninquiry, for, though it was surrounded by none of the grim and\nstrange features which were associated with the two crimes which\nI have already recorded, still, the nature of the case and the\nexalted station of his client gave it a character of its own.\nIndeed, apart from the nature of the investigation which my\nfriend had on hand, there was something in his masterly grasp of\na situation, and his keen, incisive reasoning, which made it a\npleasure to me to study his system of work, and to follow the\nquick, subtle methods by which he disentangled the most\ninextricable mysteries. So accustomed was I to his invariable\nsuccess that the very possibility of his failing had ceased to\nenter into my head.\n\nIt was close upon four before the door opened, and a\ndrunken-looking groom, ill-kempt and side-whiskered, with an\ninflamed face and disreputable clothes, walked into the room.\nAccustomed as I was to my friend's amazing powers in the use of\ndisguises, I had to look three times before I was certain that it\nwas indeed he. With a nod he vanished into the bedroom, whence he\nemerged in five minutes tweed-suited and respectable, as of old.\nPutting his hands into his pockets, he stretched out his legs in\nfront of the fire and laughed heartily for some minutes.\n\n\"Well, really!\" he cried, and then he choked and laughed again\nuntil he was obliged to lie back, limp and helpless, in the\nchair.\n\n\"What is it?\"\n\n\"It's quite too funny. I am sure you could never guess how I\nemployed my morning, or what I ended by doing.\"\n\n\"I can't imagine. I suppose that you have been watching the\nhabits, and perhaps the house, of Miss Irene Adler.\"\n\n\"Quite so; but the sequel was rather unusual. I will tell you,\nhowever. I left the house a little after eight o'clock this\nmorning in the character of a groom out of work. There is a\nwonderful sympathy and freemasonry among horsey men. Be one of\nthem, and you will know all that there is to know. I soon found\nBriony Lodge. It is a bijou villa, with a garden at the back, but\nbuilt out in front right up to the road, two stories. Chubb lock\nto the door. Large sitting-room on the right side, well\nfurnished, with long windows almost to the floor, and those\npreposterous English window fasteners which a child could open.\nBehind there was nothing remarkable, save that the passage window\ncould be reached from the top of the coach-house. I walked round\nit and examined it closely from every point of view, but without\nnoting anything else of interest.\n\n\"I then lounged down the street and found, as I expected, that\nthere was a mews in a lane which runs down by one wall of the\ngarden. I lent the ostlers a hand in rubbing down their horses,\nand received in exchange twopence, a glass of half and half, two\nfills of shag tobacco, and as much information as I could desire\nabout Miss Adler, to say nothing of half a dozen other people in\nthe neighbourhood in whom I was not in the least interested, but\nwhose biographies I was compelled to listen to.\"\n\n\"And what of Irene Adler?\" I asked.\n\n\"Oh, she has turned all the men's heads down in that part. She is\nthe daintiest thing under a bonnet on this planet. So say the\nSerpentine-mews, to a man. She lives quietly, sings at concerts,\ndrives out at five every day, and returns at seven sharp for\ndinner. Seldom goes out at other times, except when she sings.\nHas only one male visitor, but a good deal of him. He is dark,\nhandsome, and dashing, never calls less than once a day, and\noften twice. He is a Mr. Godfrey Norton, of the Inner Temple. See\nthe advantages of a cabman as a confidant. They had driven him\nhome a dozen times from Serpentine-mews, and knew all about him.\nWhen I had listened to all they had to tell, I began to walk up\nand down near Briony Lodge once more, and to think over my plan\nof campaign.\n\n\"This Godfrey Norton was evidently an important factor in the\nmatter. He was a lawyer. That sounded ominous. What was the\nrelation between them, and what the object of his repeated\nvisits? Was she his client, his friend, or his mistress? If the\nformer, she had probably transferred the photograph to his\nkeeping. If the latter, it was less likely. On the issue of this\nquestion depended whether I should continue my work at Briony\nLodge, or turn my attention to the gentleman's chambers in the\nTemple. It was a delicate point, and it widened the field of my\ninquiry. I fear that I bore you with these details, but I have to\nlet you see my little difficulties, if you are to understand the\nsituation.\"\n\n\"I am following you closely,\" I answered.\n\n\"I was still balancing the matter in my mind when a hansom cab\ndrove up to Briony Lodge, and a gentleman sprang out. He was a\nremarkably handsome man, dark, aquiline, and moustached--evidently\nthe man of whom I had heard. He appeared to be in a\ngreat hurry, shouted to the cabman to wait, and brushed past the\nmaid who opened the door with the air of a man who was thoroughly\nat home.\n\n\"He was in the house about half an hour, and I could catch\nglimpses of him in the windows of the sitting-room, pacing up and\ndown, talking excitedly, and waving his arms. Of her I could see\nnothing. Presently he emerged, looking even more flurried than\nbefore. As he stepped up to the cab, he pulled a gold watch from\nhis pocket and looked at it earnestly, 'Drive like the devil,' he\nshouted, 'first to Gross & Hankey's in Regent Street, and then to\nthe Church of St. Monica in the Edgeware Road. Half a guinea if\nyou do it in twenty minutes!'\n\n\"Away they went, and I was just wondering whether I should not do\nwell to follow them when up the lane came a neat little landau,\nthe coachman with his coat only half-buttoned, and his tie under\nhis ear, while all the tags of his harness were sticking out of\nthe buckles. It hadn't pulled up before she shot out of the hall\ndoor and into it. I only caught a glimpse of her at the moment,\nbut she was a lovely woman, with a face that a man might die for.\n\n\"'The Church of St. Monica, John,' she cried, 'and half a\nsovereign if you reach it in twenty minutes.'\n\n\"This was quite too good to lose, Watson. I was just balancing\nwhether I should run for it, or whether I should perch behind her\nlandau when a cab came through the street. The driver looked\ntwice at such a shabby fare, but I jumped in before he could\nobject. 'The Church of St. Monica,' said I, 'and half a sovereign\nif you reach it in twenty minutes.' It was twenty-five minutes to\ntwelve, and of course it was clear enough what was in the wind.\n\n\"My cabby drove fast. I don't think I ever drove faster, but the\nothers were there before us. The cab and the landau with their\nsteaming horses were in front of the door when I arrived. I paid\nthe man and hurried into the church. There was not a soul there\nsave the two whom I had followed and a surpliced clergyman, who\nseemed to be expostulating with them. They were all three\nstanding in a knot in front of the altar. I lounged up the side\naisle like any other idler who has dropped into a church.\nSuddenly, to my surprise, the three at the altar faced round to\nme, and Godfrey Norton came running as hard as he could towards\nme.\n\n\"'Thank God,' he cried. 'You'll do. Come! Come!'\n\n\"'What then?' I asked.\n\n\"'Come, man, come, only three minutes, or it won't be legal.'\n\n\"I was half-dragged up to the altar, and before I knew where I was\nI found myself mumbling responses which were whispered in my ear,\nand vouching for things of which I knew nothing, and generally\nassisting in the secure tying up of Irene Adler, spinster, to\nGodfrey Norton, bachelor. It was all done in an instant, and\nthere was the gentleman thanking me on the one side and the lady\non the other, while the clergyman beamed on me in front. It was\nthe most preposterous position in which I ever found myself in my\nlife, and it was the thought of it that started me laughing just\nnow. It seems that there had been some informality about their\nlicense, that the clergyman absolutely refused to marry them\nwithout a witness of some sort, and that my lucky appearance\nsaved the bridegroom from having to sally out into the streets in\nsearch of a best man. The bride gave me a sovereign, and I mean\nto wear it on my watch-chain in memory of the occasion.\"\n\n\"This is a very unexpected turn of affairs,\" said I; \"and what\nthen?\"\n\n\"Well, I found my plans very seriously menaced. It looked as if\nthe pair might take an immediate departure, and so necessitate\nvery prompt and energetic measures on my part. At the church\ndoor, however, they separated, he driving back to the Temple, and\nshe to her own house. 'I shall drive out in the park at five as\nusual,' she said as she left him. I heard no more. They drove\naway in different directions, and I went off to make my own\narrangements.\"\n\n\"Which are?\"\n\n\"Some cold beef and a glass of beer,\" he answered, ringing the\nbell. \"I have been too busy to think of food, and I am likely to\nbe busier still this evening. By the way, Doctor, I shall want\nyour co-operation.\"\n\n\"I shall be delighted.\"\n\n\"You don't mind breaking the law?\"\n\n\"Not in the least.\"\n\n\"Nor running a chance of arrest?\"\n\n\"Not in a good cause.\"\n\n\"Oh, the cause is excellent!\"\n\n\"Then I am your man.\"\n\n\"I was sure that I might rely on you.\"\n\n\"But what is it you wish?\"\n\n\"When Mrs. Turner has brought in the tray I will make it clear to\nyou. Now,\" he said as he turned hungrily on the simple fare that\nour landlady had provided, \"I must discuss it while I eat, for I\nhave not much time. It is nearly five now. In two hours we must\nbe on the scene of action. Miss Irene, or Madame, rather, returns\nfrom her drive at seven. We must be at Briony Lodge to meet her.\"\n\n\"And what then?\"\n\n\"You must leave that to me. I have already arranged what is to\noccur. There is only one point on which I must insist. You must\nnot interfere, come what may. You understand?\"\n\n\"I am to be neutral?\"\n\n\"To do nothing whatever. There will probably be some small\nunpleasantness. Do not join in it. It will end in my being\nconveyed into the house. Four or five minutes afterwards the\nsitting-room window will open. You are to station yourself close\nto that open window.\"\n\n\"Yes.\"\n\n\"You are to watch me, for I will be visible to you.\"\n\n\"Yes.\"\n\n\"And when I raise my hand--so--you will throw into the room what\nI give you to throw, and will, at the same time, raise the cry of\nfire. You quite follow me?\"\n\n\"Entirely.\"\n\n\"It is nothing very formidable,\" he said, taking a long cigar-shaped\nroll from his pocket. \"It is an ordinary plumber's smoke-rocket,\nfitted with a cap at either end to make it self-lighting.\nYour task is confined to that. When you raise your cry of fire,\nit will be taken up by quite a number of people. You may then\nwalk to the end of the street, and I will rejoin you in ten\nminutes. I hope that I have made myself clear?\"\n\n\"I am to remain neutral, to get near the window, to watch you,\nand at the signal to throw in this object, then to raise the cry\nof fire, and to wait you at the corner of the street.\"\n\n\"Precisely.\"\n\n\"Then you may entirely rely on me.\"\n\n\"That is excellent. I think, perhaps, it is almost time that I\nprepare for the new role I have to play.\"\n\nHe disappeared into his bedroom and returned in a few minutes in\nthe character of an amiable and simple-minded Nonconformist\nclergyman. His broad black hat, his baggy trousers, his white\ntie, his sympathetic smile, and general look of peering and\nbenevolent curiosity were such as Mr. John Hare alone could have\nequalled. It was not merely that Holmes changed his costume. His\nexpression, his manner, his very soul seemed to vary with every\nfresh part that he assumed. The stage lost a fine actor, even as\nscience lost an acute reasoner, when he became a specialist in\ncrime.\n\nIt was a quarter past six when we left Baker Street, and it still\nwanted ten minutes to the hour when we found ourselves in\nSerpentine Avenue. It was already dusk, and the lamps were just\nbeing lighted as we paced up and down in front of Briony Lodge,\nwaiting for the coming of its occupant. The house was just such\nas I had pictured it from Sherlock Holmes' succinct description,\nbut the locality appeared to be less private than I expected. On\nthe contrary, for a small street in a quiet neighbourhood, it was\nremarkably animated. There was a group of shabbily dressed men\nsmoking and laughing in a corner, a scissors-grinder with his\nwheel, two guardsmen who were flirting with a nurse-girl, and\nseveral well-dressed young men who were lounging up and down with\ncigars in their mouths.\n\n\"You see,\" remarked Holmes, as we paced to and fro in front of\nthe house, \"this marriage rather simplifies matters. The\nphotograph becomes a double-edged weapon now. The chances are\nthat she would be as averse to its being seen by Mr. Godfrey\nNorton, as our client is to its coming to the eyes of his\nprincess. Now the question is, Where are we to find the\nphotograph?\"\n\n\"Where, indeed?\"\n\n\"It is most unlikely that she carries it about with her. It is\ncabinet size. Too large for easy concealment about a woman's\ndress. She knows that the King is capable of having her waylaid\nand searched. Two attempts of the sort have already been made. We\nmay take it, then, that she does not carry it about with her.\"\n\n\"Where, then?\"\n\n\"Her banker or her lawyer. There is that double possibility. But\nI am inclined to think neither. Women are naturally secretive,\nand they like to do their own secreting. Why should she hand it\nover to anyone else? She could trust her own guardianship, but\nshe could not tell what indirect or political influence might be\nbrought to bear upon a business man. Besides, remember that she\nhad resolved to use it within a few days. It must be where she\ncan lay her hands upon it. It must be in her own house.\"\n\n\"But it has twice been burgled.\"\n\n\"Pshaw! They did not know how to look.\"\n\n\"But how will you look?\"\n\n\"I will not look.\"\n\n\"What then?\"\n\n\"I will get her to show me.\"\n\n\"But she will refuse.\"\n\n\"She will not be able to. But I hear the rumble of wheels. It is\nher carriage. Now carry out my orders to the letter.\"\n\nAs he spoke the gleam of the side-lights of a carriage came round\nthe curve of the avenue. It was a smart little landau which\nrattled up to the door of Briony Lodge. As it pulled up, one of\nthe loafing men at the corner dashed forward to open the door in\nthe hope of earning a copper, but was elbowed away by another\nloafer, who had rushed up with the same intention. A fierce\nquarrel broke out, which was increased by the two guardsmen, who\ntook sides with one of the loungers, and by the scissors-grinder,\nwho was equally hot upon the other side. A blow was struck, and\nin an instant the lady, who had stepped from her carriage, was\nthe centre of a little knot of flushed and struggling men, who\nstruck savagely at each other with their fists and sticks. Holmes\ndashed into the crowd to protect the lady; but just as he reached\nher he gave a cry and dropped to the ground, with the blood\nrunning freely down his face. At his fall the guardsmen took to\ntheir heels in one direction and the loungers in the other, while\na number of better-dressed people, who had watched the scuffle\nwithout taking part in it, crowded in to help the lady and to\nattend to the injured man. Irene Adler, as I will still call her,\nhad hurried up the steps; but she stood at the top with her\nsuperb figure outlined against the lights of the hall, looking\nback into the street.\n\n\"Is the poor gentleman much hurt?\" she asked.\n\n\"He is dead,\" cried several voices.\n\n\"No, no, there's life in him!\" shouted another. \"But he'll be\ngone before you can get him to hospital.\"\n\n\"He's a brave fellow,\" said a woman. \"They would have had the\nlady's purse and watch if it hadn't been for him. They were a\ngang, and a rough one, too. Ah, he's breathing now.\"\n\n\"He can't lie in the street. May we bring him in, marm?\"\n\n\"Surely. Bring him into the sitting-room. There is a comfortable\nsofa. This way, please!\"\n\nSlowly and solemnly he was borne into Briony Lodge and laid out\nin the principal room, while I still observed the proceedings\nfrom my post by the window. The lamps had been lit, but the\nblinds had not been drawn, so that I could see Holmes as he lay\nupon the couch. I do not know whether he was seized with\ncompunction at that moment for the part he was playing, but I\nknow that I never felt more heartily ashamed of myself in my life\nthan when I saw the beautiful creature against whom I was\nconspiring, or the grace and kindliness with which she waited\nupon the injured man. And yet it would be the blackest treachery\nto Holmes to draw back now from the part which he had intrusted\nto me. I hardened my heart, and took the smoke-rocket from under\nmy ulster. After all, I thought, we are not injuring her. We are\nbut preventing her from injuring another.\n\nHolmes had sat up upon the couch, and I saw him motion like a man\nwho is in need of air. A maid rushed across and threw open the\nwindow. At the same instant I saw him raise his hand and at the\nsignal I tossed my rocket into the room with a cry of \"Fire!\" The\nword was no sooner out of my mouth than the whole crowd of\nspectators, well dressed and ill--gentlemen, ostlers, and\nservant-maids--joined in a general shriek of \"Fire!\" Thick clouds\nof smoke curled through the room and out at the open window. I\ncaught a glimpse of rushing figures, and a moment later the voice\nof Holmes from within assuring them that it was a false alarm.\nSlipping through the shouting crowd I made my way to the corner\nof the street, and in ten minutes was rejoiced to find my\nfriend's arm in mine, and to get away from the scene of uproar.\nHe walked swiftly and in silence for some few minutes until we\nhad turned down one of the quiet streets which lead towards the\nEdgeware Road.\n\n\"You did it very nicely, Doctor,\" he remarked. \"Nothing could\nhave been better. It is all right.\"\n\n\"You have the photograph?\"\n\n\"I know where it is.\"\n\n\"And how did you find out?\"\n\n\"She showed me, as I told you she would.\"\n\n\"I am still in the dark.\"\n\n\"I do not wish to make a mystery,\" said he, laughing. \"The matter\nwas perfectly simple. You, of course, saw that everyone in the\nstreet was an accomplice. They were all engaged for the evening.\"\n\n\"I guessed as much.\"\n\n\"Then, when the row broke out, I had a little moist red paint in\nthe palm of my hand. I rushed forward, fell down, clapped my hand\nto my face, and became a piteous spectacle. It is an old trick.\"\n\n\"That also I could fathom.\"\n\n\"Then they carried me in. She was bound to have me in. What else\ncould she do? And into her sitting-room, which was the very room\nwhich I suspected. It lay between that and her bedroom, and I was\ndetermined to see which. They laid me on a couch, I motioned for\nair, they were compelled to open the window, and you had your\nchance.\"\n\n\"How did that help you?\"\n\n\"It was all-important. When a woman thinks that her house is on\nfire, her instinct is at once to rush to the thing which she\nvalues most. It is a perfectly overpowering impulse, and I have\nmore than once taken advantage of it. In the case of the\nDarlington substitution scandal it was of use to me, and also in\nthe Arnsworth Castle business. A married woman grabs at her baby;\nan unmarried one reaches for her jewel-box. Now it was clear to\nme that our lady of to-day had nothing in the house more precious\nto her than what we are in quest of. She would rush to secure it.\nThe alarm of fire was admirably done. The smoke and shouting were\nenough to shake nerves of steel. She responded beautifully. The\nphotograph is in a recess behind a sliding panel just above the\nright bell-pull. She was there in an instant, and I caught a\nglimpse of it as she half-drew it out. When I cried out that it\nwas a false alarm, she replaced it, glanced at the rocket, rushed\nfrom the room, and I have not seen her since. I rose, and, making\nmy excuses, escaped from the house. I hesitated whether to\nattempt to secure the photograph at once; but the coachman had\ncome in, and as he was watching me narrowly it seemed safer to\nwait. A little over-precipitance may ruin all.\"\n\n\"And now?\" I asked.\n\n\"Our quest is practically finished. I shall call with the King\nto-morrow, and with you, if you care to come with us. We will be\nshown into the sitting-room to wait for the lady, but it is\nprobable that when she comes she may find neither us nor the\nphotograph. It might be a satisfaction to his Majesty to regain\nit with his own hands.\"\n\n\"And when will you call?\"\n\n\"At eight in the morning. She will not be up, so that we shall\nhave a clear field. Besides, we must be prompt, for this marriage\nmay mean a complete change in her life and habits. I must wire to\nthe King without delay.\"\n\nWe had reached Baker Street and had stopped at the door. He was\nsearching his pockets for the key when someone passing said:\n\n\"Good-night, Mister Sherlock Holmes.\"\n\nThere were several people on the pavement at the time, but the\ngreeting appeared to come from a slim youth in an ulster who had\nhurried by.\n\n\"I've heard that voice before,\" said Holmes, staring down the\ndimly lit street. \"Now, I wonder who the deuce that could have\nbeen.\"\n\n\nIII.\n\nI slept at Baker Street that night, and we were engaged upon our\ntoast and coffee in the morning when the King of Bohemia rushed\ninto the room.\n\n\"You have really got it!\" he cried, grasping Sherlock Holmes by\neither shoulder and looking eagerly into his face.\n\n\"Not yet.\"\n\n\"But you have hopes?\"\n\n\"I have hopes.\"\n\n\"Then, come. I am all impatience to be gone.\"\n\n\"We must have a cab.\"\n\n\"No, my brougham is waiting.\"\n\n\"Then that will simplify matters.\" We descended and started off\nonce more for Briony Lodge.\n\n\"Irene Adler is married,\" remarked Holmes.\n\n\"Married! When?\"\n\n\"Yesterday.\"\n\n\"But to whom?\"\n\n\"To an English lawyer named Norton.\"\n\n\"But she could not love him.\"\n\n\"I am in hopes that she does.\"\n\n\"And why in hopes?\"\n\n\"Because it would spare your Majesty all fear of future\nannoyance. If the lady loves her husband, she does not love your\nMajesty. If she does not love your Majesty, there is no reason\nwhy she should interfere with your Majesty's plan.\"\n\n\"It is true. And yet--Well! I wish she had been of my own\nstation! What a queen she would have made!\" He relapsed into a\nmoody silence, which was not broken until we drew up in\nSerpentine Avenue.\n\nThe door of Briony Lodge was open, and an elderly woman stood\nupon the steps. She watched us with a sardonic eye as we stepped\nfrom the brougham.\n\n\"Mr. Sherlock Holmes, I believe?\" said she.\n\n\"I am Mr. Holmes,\" answered my companion, looking at her with a\nquestioning and rather startled gaze.\n\n\"Indeed! My mistress told me that you were likely to call. She\nleft this morning with her husband by the 5:15 train from Charing\nCross for the Continent.\"\n\n\"What!\" Sherlock Holmes staggered back, white with chagrin and\nsurprise. \"Do you mean that she has left England?\"\n\n\"Never to return.\"\n\n\"And the papers?\" asked the King hoarsely. \"All is lost.\"\n\n\"We shall see.\" He pushed past the servant and rushed into the\ndrawing-room, followed by the King and myself. The furniture was\nscattered about in every direction, with dismantled shelves and\nopen drawers, as if the lady had hurriedly ransacked them before\nher flight. Holmes rushed at the bell-pull, tore back a small\nsliding shutter, and, plunging in his hand, pulled out a\nphotograph and a letter. The photograph was of Irene Adler\nherself in evening dress, the letter was superscribed to\n\"Sherlock Holmes, Esq. To be left till called for.\" My friend\ntore it open and we all three read it together. It was dated at\nmidnight of the preceding night and ran in this way:\n\n\"MY DEAR MR. SHERLOCK HOLMES,--You really did it very well. You\ntook me in completely. Until after the alarm of fire, I had not a\nsuspicion. But then, when I found how I had betrayed myself, I\nbegan to think. I had been warned against you months ago. I had\nbeen told that if the King employed an agent it would certainly\nbe you. And your address had been given me. Yet, with all this,\nyou made me reveal what you wanted to know. Even after I became\nsuspicious, I found it hard to think evil of such a dear, kind\nold clergyman. But, you know, I have been trained as an actress\nmyself. Male costume is nothing new to me. I often take advantage\nof the freedom which it gives. I sent John, the coachman, to\nwatch you, ran up stairs, got into my walking-clothes, as I call\nthem, and came down just as you departed.\n\n\"Well, I followed you to your door, and so made sure that I was\nreally an object of interest to the celebrated Mr. Sherlock\nHolmes. Then I, rather imprudently, wished you good-night, and\nstarted for the Temple to see my husband.\n\n\"We both thought the best resource was flight, when pursued by\nso formidable an antagonist; so you will find the nest empty when\nyou call to-morrow. As to the photograph, your client may rest in\npeace. I love and am loved by a better man than he. The King may\ndo what he will without hindrance from one whom he has cruelly\nwronged. I keep it only to safeguard myself, and to preserve a\nweapon which will always secure me from any steps which he might\ntake in the future. I leave a photograph which he might care to\npossess; and I remain, dear Mr. Sherlock Holmes,\n\n                                      \"Very truly yours,\n                                   \"IRENE NORTON, née ADLER.\"\n\n\"What a woman--oh, what a woman!\" cried the King of Bohemia, when\nwe had all three read this epistle. \"Did I not tell you how quick\nand resolute she was? Would she not have made an admirable queen?\nIs it not a pity that she was not on my level?\"\n\n\"From what I have seen of the lady she seems indeed to be on a\nvery different level to your Majesty,\" said Holmes coldly. \"I am\nsorry that I have not been able to bring your Majesty's business\nto a more successful conclusion.\"\n\n\"On the contrary, my dear sir,\" cried the King; \"nothing could be\nmore successful. I know that her word is inviolate. The\nphotograph is now as safe as if it were in the fire.\"\n\n\"I am glad to hear your Majesty say so.\"\n\n\"I am immensely indebted to you. Pray tell me in what way I can\nreward you. This ring--\" He slipped an emerald snake ring from\nhis finger and held it out upon the palm of his hand.\n\n\"Your Majesty has something which I should value even more\nhighly,\" said Holmes.\n\n\"You have but to name it.\"\n\n\"This photograph!\"\n\nThe King stared at him in amazement.\n\n\"Irene's photograph!\" he cried. \"Certainly, if you wish it.\"\n\n\"I thank your Majesty. Then there is no more to be done in the\nmatter. I have the honour to wish you a very good-morning.\" He\nbowed, and, turning away without observing the hand which the\nKing had stretched out to him, he set off in my company for his\nchambers.\n\nAnd that was how a great scandal threatened to affect the kingdom\nof Bohemia, and how the best plans of Mr. Sherlock Holmes were\nbeaten by a woman's wit. He used to make merry over the\ncleverness of women, but I have not heard him do it of late. And\nwhen he speaks of Irene Adler, or when he refers to her\nphotograph, it is always under the honourable title of the woman.\n\n\n\nADVENTURE II. THE RED-HEADED LEAGUE\n\nI had called upon my friend, Mr. Sherlock Holmes, one day in the\nautumn of last year and found him in deep conversation with a\nvery stout, florid-faced, elderly gentleman with fiery red hair.\nWith an apology for my intrusion, I was about to withdraw when\nHolmes pulled me abruptly into the room and closed the door\nbehind me.\n\n\"You could not possibly have come at a better time, my dear\nWatson,\" he said cordially.\n\n\"I was afraid that you were engaged.\"\n\n\"So I am. Very much so.\"\n\n\"Then I can wait in the next room.\"\n\n\"Not at all. This gentleman, Mr. Wilson, has been my partner and\nhelper in many of my most successful cases, and I have no\ndoubt that he will be of the utmost use to me in yours also.\"\n\nThe stout gentleman half rose from his chair and gave a bob of\ngreeting, with a quick little questioning glance from his small\nfat-encircled eyes.\n\n\"Try the settee,\" said Holmes, relapsing into his armchair and\nputting his fingertips together, as was his custom when in\njudicial moods. \"I know, my dear Watson, that you share my love\nof all that is bizarre and outside the conventions and humdrum\nroutine of everyday life. You have shown your relish for it by\nthe enthusiasm which has prompted you to chronicle, and, if you\nwill excuse my saying so, somewhat to embellish so many of my own\nlittle adventures.\"\n\n\"Your cases have indeed been of the greatest interest to me,\" I\nobserved.\n\n\"You will remember that I remarked the other day, just before we\nwent into the very simple problem presented by Miss Mary\nSutherland, that for strange effects and extraordinary\ncombinations we must go to life itself, which is always far more\ndaring than any effort of the imagination.\"\n\n\"A proposition which I took the liberty of doubting.\"\n\n\"You did, Doctor, but none the less you must come round to my\nview, for otherwise I shall keep on piling fact upon fact on you\nuntil your reason breaks down under them and acknowledges me to\nbe right. Now, Mr. Jabez Wilson here has been good enough to call\nupon me this morning, and to begin a narrative which promises to\nbe one of the most singular which I have listened to for some\ntime. You have heard me remark that the strangest and most unique\nthings are very often connected not with the larger but with the\nsmaller crimes, and occasionally, indeed, where there is room for\ndoubt whether any positive crime has been committed. As far as I\nhave heard it is impossible for me to say whether the present\ncase is an instance of crime or not, but the course of events is\ncertainly among the most singular that I have ever listened to.\nPerhaps, Mr. Wilson, you would have the great kindness to\nrecommence your narrative. I ask you not merely because my friend\nDr. Watson has not heard the opening part but also because the\npeculiar nature of the story makes me anxious to have every\npossible detail from your lips. As a rule, when I have heard some\nslight indication of the course of events, I am able to guide\nmyself by the thousands of other similar cases which occur to my\nmemory. In the present instance I am forced to admit that the\nfacts are, to the best of my belief, unique.\"\n\nThe portly client puffed out his chest with an appearance of some\nlittle pride and pulled a dirty and wrinkled newspaper from the\ninside pocket of his greatcoat. As he glanced down the\nadvertisement column, with his head thrust forward and the paper\nflattened out upon his knee, I took a good look at the man and\nendeavoured, after the fashion of my companion, to read the\nindications which might be presented by his dress or appearance.\n\nI did not gain very much, however, by my inspection. Our visitor\nbore every mark of being an average commonplace British\ntradesman, obese, pompous, and slow. He wore rather baggy grey\nshepherd's check trousers, a not over-clean black frock-coat,\nunbuttoned in the front, and a drab waistcoat with a heavy brassy\nAlbert chain, and a square pierced bit of metal dangling down as\nan ornament. A frayed top-hat and a faded brown overcoat with a\nwrinkled velvet collar lay upon a chair beside him. Altogether,\nlook as I would, there was nothing remarkable about the man save\nhis blazing red head, and the expression of extreme chagrin and\ndiscontent upon his features.\n\nSherlock Holmes' quick eye took in my occupation, and he shook\nhis head with a smile as he noticed my questioning glances.\n\"Beyond the obvious facts that he has at some time done manual\nlabour, that he takes snuff, that he is a Freemason, that he has\nbeen in China, and that he has done a considerable amount of\nwriting lately, I can deduce nothing else.\"\n\nMr. Jabez Wilson started up in his chair, with his forefinger\nupon the paper, but his eyes upon my companion.\n\n\"How, in the name of good-fortune, did you know all that, Mr.\nHolmes?\" he asked. \"How did you know, for example, that I did\nmanual labour. It's as true as gospel, for I began as a ship's\ncarpenter.\"\n\n\"Your hands, my dear sir. Your right hand is quite a size larger\nthan your left. You have worked with it, and the muscles are more\ndeveloped.\"\n\n\"Well, the snuff, then, and the Freemasonry?\"\n\n\"I won't insult your intelligence by telling you how I read that,\nespecially as, rather against the strict rules of your order, you\nuse an arc-and-compass breastpin.\"\n\n\"Ah, of course, I forgot that. But the writing?\"\n\n\"What else can be indicated by that right cuff so very shiny for\nfive inches, and the left one with the smooth patch near the\nelbow where you rest it upon the desk?\"\n\n\"Well, but China?\"\n\n\"The fish that you have tattooed immediately above your right\nwrist could only have been done in China. I have made a small\nstudy of tattoo marks and have even contributed to the literature\nof the subject. That trick of staining the fishes' scales of a\ndelicate pink is quite peculiar to China. When, in addition, I\nsee a Chinese coin hanging from your watch-chain, the matter\nbecomes even more simple.\"\n\nMr. Jabez Wilson laughed heavily. \"Well, I never!\" said he. \"I\nthought at first that you had done something clever, but I see\nthat there was nothing in it, after all.\"\n\n\"I begin to think, Watson,\" said Holmes, \"that I make a mistake\nin explaining. 'Omne ignotum pro magnifico,' you know, and my\npoor little reputation, such as it is, will suffer shipwreck if I\nam so candid. Can you not find the advertisement, Mr. Wilson?\"\n\n\"Yes, I have got it now,\" he answered with his thick red finger\nplanted halfway down the column. \"Here it is. This is what began\nit all. You just read it for yourself, sir.\"\n\nI took the paper from him and read as follows:\n\n\"TO THE RED-HEADED LEAGUE: On account of the bequest of the late\nEzekiah Hopkins, of Lebanon, Pennsylvania, U. S. A., there is now\nanother vacancy open which entitles a member of the League to a\nsalary of 4 pounds a week for purely nominal services. All\nred-headed men who are sound in body and mind and above the age\nof twenty-one years, are eligible. Apply in person on Monday, at\neleven o'clock, to Duncan Ross, at the offices of the League, 7\nPope's Court, Fleet Street.\"\n\n\"What on earth does this mean?\" I ejaculated after I had twice\nread over the extraordinary announcement.\n\nHolmes chuckled and wriggled in his chair, as was his habit when\nin high spirits. \"It is a little off the beaten track, isn't it?\"\nsaid he. \"And now, Mr. Wilson, off you go at scratch and tell us\nall about yourself, your household, and the effect which this\nadvertisement had upon your fortunes. You will first make a note,\nDoctor, of the paper and the date.\"\n\n\"It is The Morning Chronicle of April 27, 1890. Just two months\nago.\"\n\n\"Very good. Now, Mr. Wilson?\"\n\n\"Well, it is just as I have been telling you, Mr. Sherlock\nHolmes,\" said Jabez Wilson, mopping his forehead; \"I have a small\npawnbroker's business at Coburg Square, near the City. It's not a\nvery large affair, and of late years it has not done more than\njust give me a living. I used to be able to keep two assistants,\nbut now I only keep one; and I would have a job to pay him but\nthat he is willing to come for half wages so as to learn the\nbusiness.\"\n\n\"What is the name of this obliging youth?\" asked Sherlock Holmes.\n\n\"His name is Vincent Spaulding, and he's not such a youth,\neither. It's hard to say his age. I should not wish a smarter\nassistant, Mr. Holmes; and I know very well that he could better\nhimself and earn twice what I am able to give him. But, after\nall, if he is satisfied, why should I put ideas in his head?\"\n\n\"Why, indeed? You seem most fortunate in having an employé who\ncomes under the full market price. It is not a common experience\namong employers in this age. I don't know that your assistant is\nnot as remarkable as your advertisement.\"\n\n\"Oh, he has his faults, too,\" said Mr. Wilson. \"Never was such a\nfellow for photography. Snapping away with a camera when he ought\nto be improving his mind, and then diving down into the cellar\nlike a rabbit into its hole to develop his pictures. That is his\nmain fault, but on the whole he's a good worker. There's no vice\nin him.\"\n\n\"He is still with you, I presume?\"\n\n\"Yes, sir. He and a girl of fourteen, who does a bit of simple\ncooking and keeps the place clean--that's all I have in the\nhouse, for I am a widower and never had any family. We live very\nquietly, sir, the three of us; and we keep a roof over our heads\nand pay our debts, if we do nothing more.\n\n\"The first thing that put us out was that advertisement.\nSpaulding, he came down into the office just this day eight\nweeks, with this very paper in his hand, and he says:\n\n\"'I wish to the Lord, Mr. Wilson, that I was a red-headed man.'\n\n\"'Why that?' I asks.\n\n\"'Why,' says he, 'here's another vacancy on the League of the\nRed-headed Men. It's worth quite a little fortune to any man who\ngets it, and I understand that there are more vacancies than\nthere are men, so that the trustees are at their wits' end what\nto do with the money. If my hair would only change colour, here's\na nice little crib all ready for me to step into.'\n\n\"'Why, what is it, then?' I asked. You see, Mr. Holmes, I am a\nvery stay-at-home man, and as my business came to me instead of\nmy having to go to it, I was often weeks on end without putting\nmy foot over the door-mat. In that way I didn't know much of what\nwas going on outside, and I was always glad of a bit of news.\n\n\"'Have you never heard of the League of the Red-headed Men?' he\nasked with his eyes open.\n\n\"'Never.'\n\n\"'Why, I wonder at that, for you are eligible yourself for one\nof the vacancies.'\n\n\"'And what are they worth?' I asked.\n\n\"'Oh, merely a couple of hundred a year, but the work is slight,\nand it need not interfere very much with one's other\noccupations.'\n\n\"Well, you can easily think that that made me prick up my ears,\nfor the business has not been over-good for some years, and an\nextra couple of hundred would have been very handy.\n\n\"'Tell me all about it,' said I.\n\n\"'Well,' said he, showing me the advertisement, 'you can see for\nyourself that the League has a vacancy, and there is the address\nwhere you should apply for particulars. As far as I can make out,\nthe League was founded by an American millionaire, Ezekiah\nHopkins, who was very peculiar in his ways. He was himself\nred-headed, and he had a great sympathy for all red-headed men;\nso when he died it was found that he had left his enormous\nfortune in the hands of trustees, with instructions to apply the\ninterest to the providing of easy berths to men whose hair is of\nthat colour. From all I hear it is splendid pay and very little to\ndo.'\n\n\"'But,' said I, 'there would be millions of red-headed men who\nwould apply.'\n\n\"'Not so many as you might think,' he answered. 'You see it is\nreally confined to Londoners, and to grown men. This American had\nstarted from London when he was young, and he wanted to do the\nold town a good turn. Then, again, I have heard it is no use your\napplying if your hair is light red, or dark red, or anything but\nreal bright, blazing, fiery red. Now, if you cared to apply, Mr.\nWilson, you would just walk in; but perhaps it would hardly be\nworth your while to put yourself out of the way for the sake of a\nfew hundred pounds.'\n\n\"Now, it is a fact, gentlemen, as you may see for yourselves,\nthat my hair is of a very full and rich tint, so that it seemed\nto me that if there was to be any competition in the matter I\nstood as good a chance as any man that I had ever met. Vincent\nSpaulding seemed to know so much about it that I thought he might\nprove useful, so I just ordered him to put up the shutters for\nthe day and to come right away with me. He was very willing to\nhave a holiday, so we shut the business up and started off for\nthe address that was given us in the advertisement.\n\n\"I never hope to see such a sight as that again, Mr. Holmes. From\nnorth, south, east, and west every man who had a shade of red in\nhis hair had tramped into the city to answer the advertisement.\nFleet Street was choked with red-headed folk, and Pope's Court\nlooked like a coster's orange barrow. I should not have thought\nthere were so many in the whole country as were brought together\nby that single advertisement. Every shade of colour they\nwere--straw, lemon, orange, brick, Irish-setter, liver, clay;\nbut, as Spaulding said, there were not many who had the real\nvivid flame-coloured tint. When I saw how many were waiting, I\nwould have given it up in despair; but Spaulding would not hear\nof it. How he did it I could not imagine, but he pushed and\npulled and butted until he got me through the crowd, and right up\nto the steps which led to the office. There was a double stream\nupon the stair, some going up in hope, and some coming back\ndejected; but we wedged in as well as we could and soon found\nourselves in the office.\"\n\n\"Your experience has been a most entertaining one,\" remarked\nHolmes as his client paused and refreshed his memory with a huge\npinch of snuff. \"Pray continue your very interesting statement.\"\n\n\"There was nothing in the office but a couple of wooden chairs\nand a deal table, behind which sat a small man with a head that\nwas even redder than mine. He said a few words to each candidate\nas he came up, and then he always managed to find some fault in\nthem which would disqualify them. Getting a vacancy did not seem\nto be such a very easy matter, after all. However, when our turn\ncame the little man was much more favourable to me than to any of\nthe others, and he closed the door as we entered, so that he\nmight have a private word with us.\n\n\"'This is Mr. Jabez Wilson,' said my assistant, 'and he is\nwilling to fill a vacancy in the League.'\n\n\"'And he is admirably suited for it,' the other answered. 'He has\nevery requirement. I cannot recall when I have seen anything so\nfine.' He took a step backward, cocked his head on one side, and\ngazed at my hair until I felt quite bashful. Then suddenly he\nplunged forward, wrung my hand, and congratulated me warmly on my\nsuccess.\n\n\"'It would be injustice to hesitate,' said he. 'You will,\nhowever, I am sure, excuse me for taking an obvious precaution.'\nWith that he seized my hair in both his hands, and tugged until I\nyelled with the pain. 'There is water in your eyes,' said he as\nhe released me. 'I perceive that all is as it should be. But we\nhave to be careful, for we have twice been deceived by wigs and\nonce by paint. I could tell you tales of cobbler's wax which\nwould disgust you with human nature.' He stepped over to the\nwindow and shouted through it at the top of his voice that the\nvacancy was filled. A groan of disappointment came up from below,\nand the folk all trooped away in different directions until there\nwas not a red-head to be seen except my own and that of the\nmanager.\n\n\"'My name,' said he, 'is Mr. Duncan Ross, and I am myself one of\nthe pensioners upon the fund left by our noble benefactor. Are\nyou a married man, Mr. Wilson? Have you a family?'\n\n\"I answered that I had not.\n\n\"His face fell immediately.\n\n\"'Dear me!' he said gravely, 'that is very serious indeed! I am\nsorry to hear you say that. The fund was, of course, for the\npropagation and spread of the red-heads as well as for their\nmaintenance. It is exceedingly unfortunate that you should be a\nbachelor.'\n\n\"My face lengthened at this, Mr. Holmes, for I thought that I was\nnot to have the vacancy after all; but after thinking it over for\na few minutes he said that it would be all right.\n\n\"'In the case of another,' said he, 'the objection might be\nfatal, but we must stretch a point in favour of a man with such a\nhead of hair as yours. When shall you be able to enter upon your\nnew duties?'\n\n\"'Well, it is a little awkward, for I have a business already,'\nsaid I.\n\n\"'Oh, never mind about that, Mr. Wilson!' said Vincent Spaulding.\n'I should be able to look after that for you.'\n\n\"'What would be the hours?' I asked.\n\n\"'Ten to two.'\n\n\"Now a pawnbroker's business is mostly done of an evening, Mr.\nHolmes, especially Thursday and Friday evening, which is just\nbefore pay-day; so it would suit me very well to earn a little in\nthe mornings. Besides, I knew that my assistant was a good man,\nand that he would see to anything that turned up.\n\n\"'That would suit me very well,' said I. 'And the pay?'\n\n\"'Is 4 pounds a week.'\n\n\"'And the work?'\n\n\"'Is purely nominal.'\n\n\"'What do you call purely nominal?'\n\n\"'Well, you have to be in the office, or at least in the\nbuilding, the whole time. If you leave, you forfeit your whole\nposition forever. The will is very clear upon that point. You\ndon't comply with the conditions if you budge from the office\nduring that time.'\n\n\"'It's only four hours a day, and I should not think of leaving,'\nsaid I.\n\n\"'No excuse will avail,' said Mr. Duncan Ross; 'neither sickness\nnor business nor anything else. There you must stay, or you lose\nyour billet.'\n\n\"'And the work?'\n\n\"'Is to copy out the \"Encyclopaedia Britannica.\" There is the first\nvolume of it in that press. You must find your own ink, pens, and\nblotting-paper, but we provide this table and chair. Will you be\nready to-morrow?'\n\n\"'Certainly,' I answered.\n\n\"'Then, good-bye, Mr. Jabez Wilson, and let me congratulate you\nonce more on the important position which you have been fortunate\nenough to gain.' He bowed me out of the room and I went home with\nmy assistant, hardly knowing what to say or do, I was so pleased\nat my own good fortune.\n\n\"Well, I thought over the matter all day, and by evening I was in\nlow spirits again; for I had quite persuaded myself that the\nwhole affair must be some great hoax or fraud, though what its\nobject might be I could not imagine. It seemed altogether past\nbelief that anyone could make such a will, or that they would pay\nsuch a sum for doing anything so simple as copying out the\n'Encyclopaedia Britannica.' Vincent Spaulding did what he could to\ncheer me up, but by bedtime I had reasoned myself out of the\nwhole thing. However, in the morning I determined to have a look\nat it anyhow, so I bought a penny bottle of ink, and with a\nquill-pen, and seven sheets of foolscap paper, I started off for\nPope's Court.\n\n\"Well, to my surprise and delight, everything was as right as\npossible. The table was set out ready for me, and Mr. Duncan Ross\nwas there to see that I got fairly to work. He started me off\nupon the letter A, and then he left me; but he would drop in from\ntime to time to see that all was right with me. At two o'clock he\nbade me good-day, complimented me upon the amount that I had\nwritten, and locked the door of the office after me.\n\n\"This went on day after day, Mr. Holmes, and on Saturday the\nmanager came in and planked down four golden sovereigns for my\nweek's work. It was the same next week, and the same the week\nafter. Every morning I was there at ten, and every afternoon I\nleft at two. By degrees Mr. Duncan Ross took to coming in only\nonce of a morning, and then, after a time, he did not come in at\nall. Still, of course, I never dared to leave the room for an\ninstant, for I was not sure when he might come, and the billet\nwas such a good one, and suited me so well, that I would not risk\nthe loss of it.\n\n\"Eight weeks passed away like this, and I had written about\nAbbots and Archery and Armour and Architecture and Attica, and\nhoped with diligence that I might get on to the B's before very\nlong. It cost me something in foolscap, and I had pretty nearly\nfilled a shelf with my writings. And then suddenly the whole\nbusiness came to an end.\"\n\n\"To an end?\"\n\n\"Yes, sir. And no later than this morning. I went to my work as\nusual at ten o'clock, but the door was shut and locked, with a\nlittle square of cardboard hammered on to the middle of the\npanel with a tack. Here it is, and you can read for yourself.\"\n\nHe held up a piece of white cardboard about the size of a sheet\nof note-paper. It read in this fashion:\n\n                  THE RED-HEADED LEAGUE\n\n                           IS\n\n                        DISSOLVED.\n\n                     October 9, 1890.\n\nSherlock Holmes and I surveyed this curt announcement and the\nrueful face behind it, until the comical side of the affair so\ncompletely overtopped every other consideration that we both\nburst out into a roar of laughter.\n\n\"I cannot see that there is anything very funny,\" cried our\nclient, flushing up to the roots of his flaming head. \"If you can\ndo nothing better than laugh at me, I can go elsewhere.\"\n\n\"No, no,\" cried Holmes, shoving him back into the chair from\nwhich he had half risen. \"I really wouldn't miss your case for\nthe world. It is most refreshingly unusual. But there is, if you\nwill excuse my saying so, something just a little funny about it.\nPray what steps did you take when you found the card upon the\ndoor?\"\n\n\"I was staggered, sir. I did not know what to do. Then I called\nat the offices round, but none of them seemed to know anything\nabout it. Finally, I went to the landlord, who is an accountant\nliving on the ground-floor, and I asked him if he could tell me\nwhat had become of the Red-headed League. He said that he had\nnever heard of any such body. Then I asked him who Mr. Duncan\nRoss was. He answered that the name was new to him.\n\n\"'Well,' said I, 'the gentleman at No. 4.'\n\n\"'What, the red-headed man?'\n\n\"'Yes.'\n\n\"'Oh,' said he, 'his name was William Morris. He was a solicitor\nand was using my room as a temporary convenience until his new\npremises were ready. He moved out yesterday.'\n\n\"'Where could I find him?'\n\n\"'Oh, at his new offices. He did tell me the address. Yes, 17\nKing Edward Street, near St. Paul's.'\n\n\"I started off, Mr. Holmes, but when I got to that address it was\na manufactory of artificial knee-caps, and no one in it had ever\nheard of either Mr. William Morris or Mr. Duncan Ross.\"\n\n\"And what did you do then?\" asked Holmes.\n\n\"I went home to Saxe-Coburg Square, and I took the advice of my\nassistant. But he could not help me in any way. He could only say\nthat if I waited I should hear by post. But that was not quite\ngood enough, Mr. Holmes. I did not wish to lose such a place\nwithout a struggle, so, as I had heard that you were good enough\nto give advice to poor folk who were in need of it, I came right\naway to you.\"\n\n\"And you did very wisely,\" said Holmes. \"Your case is an\nexceedingly remarkable one, and I shall be happy to look into it.\nFrom what you have told me I think that it is possible that\ngraver issues hang from it than might at first sight appear.\"\n\n\"Grave enough!\" said Mr. Jabez Wilson. \"Why, I have lost four\npound a week.\"\n\n\"As far as you are personally concerned,\" remarked Holmes, \"I do\nnot see that you have any grievance against this extraordinary\nleague. On the contrary, you are, as I understand, richer by some\n30 pounds, to say nothing of the minute knowledge which you have\ngained on every subject which comes under the letter A. You have\nlost nothing by them.\"\n\n\"No, sir. But I want to find out about them, and who they are,\nand what their object was in playing this prank--if it was a\nprank--upon me. It was a pretty expensive joke for them, for it\ncost them two and thirty pounds.\"\n\n\"We shall endeavour to clear up these points for you. And, first,\none or two questions, Mr. Wilson. This assistant of yours who\nfirst called your attention to the advertisement--how long had he\nbeen with you?\"\n\n\"About a month then.\"\n\n\"How did he come?\"\n\n\"In answer to an advertisement.\"\n\n\"Was he the only applicant?\"\n\n\"No, I had a dozen.\"\n\n\"Why did you pick him?\"\n\n\"Because he was handy and would come cheap.\"\n\n\"At half-wages, in fact.\"\n\n\"Yes.\"\n\n\"What is he like, this Vincent Spaulding?\"\n\n\"Small, stout-built, very quick in his ways, no hair on his face,\nthough he's not short of thirty. Has a white splash of acid upon\nhis forehead.\"\n\nHolmes sat up in his chair in considerable excitement. \"I thought\nas much,\" said he. \"Have you ever observed that his ears are\npierced for earrings?\"\n\n\"Yes, sir. He told me that a gipsy had done it for him when he\nwas a lad.\"\n\n\"Hum!\" said Holmes, sinking back in deep thought. \"He is still\nwith you?\"\n\n\"Oh, yes, sir; I have only just left him.\"\n\n\"And has your business been attended to in your absence?\"\n\n\"Nothing to complain of, sir. There's never very much to do of a\nmorning.\"\n\n\"That will do, Mr. Wilson. I shall be happy to give you an\nopinion upon the subject in the course of a day or two. To-day is\nSaturday, and I hope that by Monday we may come to a conclusion.\"\n\n\"Well, Watson,\" said Holmes when our visitor had left us, \"what\ndo you make of it all?\"\n\n\"I make nothing of it,\" I answered frankly. \"It is a most\nmysterious business.\"\n\n\"As a rule,\" said Holmes, \"the more bizarre a thing is the less\nmysterious it proves to be. It is your commonplace, featureless\ncrimes which are really puzzling, just as a commonplace face is\nthe most difficult to identify. But I must be prompt over this\nmatter.\"\n\n\"What are you going to do, then?\" I asked.\n\n\"To smoke,\" he answered. \"It is quite a three pipe problem, and I\nbeg that you won't speak to me for fifty minutes.\" He curled\nhimself up in his chair, with his thin knees drawn up to his\nhawk-like nose, and there he sat with his eyes closed and his\nblack clay pipe thrusting out like the bill of some strange bird.\nI had come to the conclusion that he had dropped asleep, and\nindeed was nodding myself, when he suddenly sprang out of his\nchair with the gesture of a man who has made up his mind and put\nhis pipe down upon the mantelpiece.\n\n\"Sarasate plays at the St. James's Hall this afternoon,\" he\nremarked. \"What do you think, Watson? Could your patients spare\nyou for a few hours?\"\n\n\"I have nothing to do to-day. My practice is never very\nabsorbing.\"\n\n\"Then put on your hat and come. I am going through the City\nfirst, and we can have some lunch on the way. I observe that\nthere is a good deal of German music on the programme, which is\nrather more to my taste than Italian or French. It is\nintrospective, and I want to introspect. Come along!\"\n\nWe travelled by the Underground as far as Aldersgate; and a short\nwalk took us to Saxe-Coburg Square, the scene of the singular\nstory which we had listened to in the morning. It was a poky,\nlittle, shabby-genteel place, where four lines of dingy\ntwo-storied brick houses looked out into a small railed-in\nenclosure, where a lawn of weedy grass and a few clumps of faded\nlaurel-bushes made a hard fight against a smoke-laden and\nuncongenial atmosphere. Three gilt balls and a brown board with\n\"JABEZ WILSON\" in white letters, upon a corner house, announced\nthe place where our red-headed client carried on his business.\nSherlock Holmes stopped in front of it with his head on one side\nand looked it all over, with his eyes shining brightly between\npuckered lids. Then he walked slowly up the street, and then down\nagain to the corner, still looking keenly at the houses. Finally\nhe returned to the pawnbroker's, and, having thumped vigorously\nupon the pavement with his stick two or three times, he went up\nto the door and knocked. It was instantly opened by a\nbright-looking, clean-shaven young fellow, who asked him to step\nin.\n\n\"Thank you,\" said Holmes, \"I only wished to ask you how you would\ngo from here to the Strand.\"\n\n\"Third right, fourth left,\" answered the assistant promptly,\nclosing the door.\n\n\"Smart fellow, that,\" observed Holmes as we walked away. \"He is,\nin my judgment, the fourth smartest man in London, and for daring\nI am not sure that he has not a claim to be third. I have known\nsomething of him before.\"\n\n\"Evidently,\" said I, \"Mr. Wilson's assistant counts for a good\ndeal in this mystery of the Red-headed League. I am sure that you\ninquired your way merely in order that you might see him.\"\n\n\"Not him.\"\n\n\"What then?\"\n\n\"The knees of his trousers.\"\n\n\"And what did you see?\"\n\n\"What I expected to see.\"\n\n\"Why did you beat the pavement?\"\n\n\"My dear doctor, this is a time for observation, not for talk. We\nare spies in an enemy's country. We know something of Saxe-Coburg\nSquare. Let us now explore the parts which lie behind it.\"\n\nThe road in which we found ourselves as we turned round the\ncorner from the retired Saxe-Coburg Square presented as great a\ncontrast to it as the front of a picture does to the back. It was\none of the main arteries which conveyed the traffic of the City\nto the north and west. The roadway was blocked with the immense\nstream of commerce flowing in a double tide inward and outward,\nwhile the footpaths were black with the hurrying swarm of\npedestrians. It was difficult to realise as we looked at the line\nof fine shops and stately business premises that they really\nabutted on the other side upon the faded and stagnant square\nwhich we had just quitted.\n\n\"Let me see,\" said Holmes, standing at the corner and glancing\nalong the line, \"I should like just to remember the order of the\nhouses here. It is a hobby of mine to have an exact knowledge of\nLondon. There is Mortimer's, the tobacconist, the little\nnewspaper shop, the Coburg branch of the City and Suburban Bank,\nthe Vegetarian Restaurant, and McFarlane's carriage-building\ndepot. That carries us right on to the other block. And now,\nDoctor, we've done our work, so it's time we had some play. A\nsandwich and a cup of coffee, and then off to violin-land, where\nall is sweetness and delicacy and harmony, and there are no\nred-headed clients to vex us with their conundrums.\"\n\nMy friend was an enthusiastic musician, being himself not only a\nvery capable performer but a composer of no ordinary merit. All\nthe afternoon he sat in the stalls wrapped in the most perfect\nhappiness, gently waving his long, thin fingers in time to the\nmusic, while his gently smiling face and his languid, dreamy eyes\nwere as unlike those of Holmes the sleuth-hound, Holmes the\nrelentless, keen-witted, ready-handed criminal agent, as it was\npossible to conceive. In his singular character the dual nature\nalternately asserted itself, and his extreme exactness and\nastuteness represented, as I have often thought, the reaction\nagainst the poetic and contemplative mood which occasionally\npredominated in him. The swing of his nature took him from\nextreme languor to devouring energy; and, as I knew well, he was\nnever so truly formidable as when, for days on end, he had been\nlounging in his armchair amid his improvisations and his\nblack-letter editions. Then it was that the lust of the chase\nwould suddenly come upon him, and that his brilliant reasoning\npower would rise to the level of intuition, until those who were\nunacquainted with his methods would look askance at him as on a\nman whose knowledge was not that of other mortals. When I saw him\nthat afternoon so enwrapped in the music at St. James's Hall I\nfelt that an evil time might be coming upon those whom he had set\nhimself to hunt down.\n\n\"You want to go home, no doubt, Doctor,\" he remarked as we\nemerged.\n\n\"Yes, it would be as well.\"\n\n\"And I have some business to do which will take some hours. This\nbusiness at Coburg Square is serious.\"\n\n\"Why serious?\"\n\n\"A considerable crime is in contemplation. I have every reason to\nbelieve that we shall be in time to stop it. But to-day being\nSaturday rather complicates matters. I shall want your help\nto-night.\"\n\n\"At what time?\"\n\n\"Ten will be early enough.\"\n\n\"I shall be at Baker Street at ten.\"\n\n\"Very well. And, I say, Doctor, there may be some little danger,\nso kindly put your army revolver in your pocket.\" He waved his\nhand, turned on his heel, and disappeared in an instant among the\ncrowd.\n\nI trust that I am not more dense than my neighbours, but I was\nalways oppressed with a sense of my own stupidity in my dealings\nwith Sherlock Holmes. Here I had heard what he had heard, I had\nseen what he had seen, and yet from his words it was evident that\nhe saw clearly not only what had happened but what was about to\nhappen, while to me the whole business was still confused and\ngrotesque. As I drove home to my house in Kensington I thought\nover it all, from the extraordinary story of the red-headed\ncopier of the \"Encyclopaedia\" down to the visit to Saxe-Coburg\nSquare, and the ominous words with which he had parted from me.\nWhat was this nocturnal expedition, and why should I go armed?\nWhere were we going, and what were we to do? I had the hint from\nHolmes that this smooth-faced pawnbroker's assistant was a\nformidable man--a man who might play a deep game. I tried to\npuzzle it out, but gave it up in despair and set the matter aside\nuntil night should bring an explanation.\n\nIt was a quarter-past nine when I started from home and made my\nway across the Park, and so through Oxford Street to Baker\nStreet. Two hansoms were standing at the door, and as I entered\nthe passage I heard the sound of voices from above. On entering\nhis room I found Holmes in animated conversation with two men,\none of whom I recognised as Peter Jones, the official police\nagent, while the other was a long, thin, sad-faced man, with a\nvery shiny hat and oppressively respectable frock-coat.\n\n\"Ha! Our party is complete,\" said Holmes, buttoning up his\npea-jacket and taking his heavy hunting crop from the rack.\n\"Watson, I think you know Mr. Jones, of Scotland Yard? Let me\nintroduce you to Mr. Merryweather, who is to be our companion in\nto-night's adventure.\"\n\n\"We're hunting in couples again, Doctor, you see,\" said Jones in\nhis consequential way. \"Our friend here is a wonderful man for\nstarting a chase. All he wants is an old dog to help him to do\nthe running down.\"\n\n\"I hope a wild goose may not prove to be the end of our chase,\"\nobserved Mr. Merryweather gloomily.\n\n\"You may place considerable confidence in Mr. Holmes, sir,\" said\nthe police agent loftily. \"He has his own little methods, which\nare, if he won't mind my saying so, just a little too theoretical\nand fantastic, but he has the makings of a detective in him. It\nis not too much to say that once or twice, as in that business of\nthe Sholto murder and the Agra treasure, he has been more nearly\ncorrect than the official force.\"\n\n\"Oh, if you say so, Mr. Jones, it is all right,\" said the\nstranger with deference. \"Still, I confess that I miss my rubber.\nIt is the first Saturday night for seven-and-twenty years that I\nhave not had my rubber.\"\n\n\"I think you will find,\" said Sherlock Holmes, \"that you will\nplay for a higher stake to-night than you have ever done yet, and\nthat the play will be more exciting. For you, Mr. Merryweather,\nthe stake will be some 30,000 pounds; and for you, Jones, it will\nbe the man upon whom you wish to lay your hands.\"\n\n\"John Clay, the murderer, thief, smasher, and forger. He's a\nyoung man, Mr. Merryweather, but he is at the head of his\nprofession, and I would rather have my bracelets on him than on\nany criminal in London. He's a remarkable man, is young John\nClay. His grandfather was a royal duke, and he himself has been\nto Eton and Oxford. His brain is as cunning as his fingers, and\nthough we meet signs of him at every turn, we never know where to\nfind the man himself. He'll crack a crib in Scotland one week,\nand be raising money to build an orphanage in Cornwall the next.\nI've been on his track for years and have never set eyes on him\nyet.\"\n\n\"I hope that I may have the pleasure of introducing you to-night.\nI've had one or two little turns also with Mr. John Clay, and I\nagree with you that he is at the head of his profession. It is\npast ten, however, and quite time that we started. If you two\nwill take the first hansom, Watson and I will follow in the\nsecond.\"\n\nSherlock Holmes was not very communicative during the long drive\nand lay back in the cab humming the tunes which he had heard in\nthe afternoon. We rattled through an endless labyrinth of gas-lit\nstreets until we emerged into Farrington Street.\n\n\"We are close there now,\" my friend remarked. \"This fellow\nMerryweather is a bank director, and personally interested in the\nmatter. I thought it as well to have Jones with us also. He is\nnot a bad fellow, though an absolute imbecile in his profession.\nHe has one positive virtue. He is as brave as a bulldog and as\ntenacious as a lobster if he gets his claws upon anyone. Here we\nare, and they are waiting for us.\"\n\nWe had reached the same crowded thoroughfare in which we had\nfound ourselves in the morning. Our cabs were dismissed, and,\nfollowing the guidance of Mr. Merryweather, we passed down a\nnarrow passage and through a side door, which he opened for us.\nWithin there was a small corridor, which ended in a very massive\niron gate. This also was opened, and led down a flight of winding\nstone steps, which terminated at another formidable gate. Mr.\nMerryweather stopped to light a lantern, and then conducted us\ndown a dark, earth-smelling passage, and so, after opening a\nthird door, into a huge vault or cellar, which was piled all\nround with crates and massive boxes.\n\n\"You are not very vulnerable from above,\" Holmes remarked as he\nheld up the lantern and gazed about him.\n\n\"Nor from below,\" said Mr. Merryweather, striking his stick upon\nthe flags which lined the floor. \"Why, dear me, it sounds quite\nhollow!\" he remarked, looking up in surprise.\n\n\"I must really ask you to be a little more quiet!\" said Holmes\nseverely. \"You have already imperilled the whole success of our\nexpedition. Might I beg that you would have the goodness to sit\ndown upon one of those boxes, and not to interfere?\"\n\nThe solemn Mr. Merryweather perched himself upon a crate, with a\nvery injured expression upon his face, while Holmes fell upon his\nknees upon the floor and, with the lantern and a magnifying lens,\nbegan to examine minutely the cracks between the stones. A few\nseconds sufficed to satisfy him, for he sprang to his feet again\nand put his glass in his pocket.\n\n\"We have at least an hour before us,\" he remarked, \"for they can\nhardly take any steps until the good pawnbroker is safely in bed.\nThen they will not lose a minute, for the sooner they do their\nwork the longer time they will have for their escape. We are at\npresent, Doctor--as no doubt you have divined--in the cellar of\nthe City branch of one of the principal London banks. Mr.\nMerryweather is the chairman of directors, and he will explain to\nyou that there are reasons why the more daring criminals of\nLondon should take a considerable interest in this cellar at\npresent.\"\n\n\"It is our French gold,\" whispered the director. \"We have had\nseveral warnings that an attempt might be made upon it.\"\n\n\"Your French gold?\"\n\n\"Yes. We had occasion some months ago to strengthen our resources\nand borrowed for that purpose 30,000 napoleons from the Bank of\nFrance. It has become known that we have never had occasion to\nunpack the money, and that it is still lying in our cellar. The\ncrate upon which I sit contains 2,000 napoleons packed between\nlayers of lead foil. Our reserve of bullion is much larger at\npresent than is usually kept in a single branch office, and the\ndirectors have had misgivings upon the subject.\"\n\n\"Which were very well justified,\" observed Holmes. \"And now it is\ntime that we arranged our little plans. I expect that within an\nhour matters will come to a head. In the meantime Mr.\nMerryweather, we must put the screen over that dark lantern.\"\n\n\"And sit in the dark?\"\n\n\"I am afraid so. I had brought a pack of cards in my pocket, and\nI thought that, as we were a partie carrée, you might have your\nrubber after all. But I see that the enemy's preparations have\ngone so far that we cannot risk the presence of a light. And,\nfirst of all, we must choose our positions. These are daring men,\nand though we shall take them at a disadvantage, they may do us\nsome harm unless we are careful. I shall stand behind this crate,\nand do you conceal yourselves behind those. Then, when I flash a\nlight upon them, close in swiftly. If they fire, Watson, have no\ncompunction about shooting them down.\"\n\nI placed my revolver, cocked, upon the top of the wooden case\nbehind which I crouched. Holmes shot the slide across the front\nof his lantern and left us in pitch darkness--such an absolute\ndarkness as I have never before experienced. The smell of hot\nmetal remained to assure us that the light was still there, ready\nto flash out at a moment's notice. To me, with my nerves worked\nup to a pitch of expectancy, there was something depressing and\nsubduing in the sudden gloom, and in the cold dank air of the\nvault.\n\n\"They have but one retreat,\" whispered Holmes. \"That is back\nthrough the house into Saxe-Coburg Square. I hope that you have\ndone what I asked you, Jones?\"\n\n\"I have an inspector and two officers waiting at the front door.\"\n\n\"Then we have stopped all the holes. And now we must be silent\nand wait.\"\n\nWhat a time it seemed! From comparing notes afterwards it was but\nan hour and a quarter, yet it appeared to me that the night must\nhave almost gone and the dawn be breaking above us. My limbs\nwere weary and stiff, for I feared to change my position; yet my\nnerves were worked up to the highest pitch of tension, and my\nhearing was so acute that I could not only hear the gentle\nbreathing of my companions, but I could distinguish the deeper,\nheavier in-breath of the bulky Jones from the thin, sighing note\nof the bank director. From my position I could look over the case\nin the direction of the floor. Suddenly my eyes caught the glint\nof a light.\n\nAt first it was but a lurid spark upon the stone pavement. Then\nit lengthened out until it became a yellow line, and then,\nwithout any warning or sound, a gash seemed to open and a hand\nappeared, a white, almost womanly hand, which felt about in the\ncentre of the little area of light. For a minute or more the\nhand, with its writhing fingers, protruded out of the floor. Then\nit was withdrawn as suddenly as it appeared, and all was dark\nagain save the single lurid spark which marked a chink between\nthe stones.\n\nIts disappearance, however, was but momentary. With a rending,\ntearing sound, one of the broad, white stones turned over upon\nits side and left a square, gaping hole, through which streamed\nthe light of a lantern. Over the edge there peeped a clean-cut,\nboyish face, which looked keenly about it, and then, with a hand\non either side of the aperture, drew itself shoulder-high and\nwaist-high, until one knee rested upon the edge. In another\ninstant he stood at the side of the hole and was hauling after\nhim a companion, lithe and small like himself, with a pale face\nand a shock of very red hair.\n\n\"It's all clear,\" he whispered. \"Have you the chisel and the\nbags? Great Scott! Jump, Archie, jump, and I'll swing for it!\"\n\nSherlock Holmes had sprung out and seized the intruder by the\ncollar. The other dived down the hole, and I heard the sound of\nrending cloth as Jones clutched at his skirts. The light flashed\nupon the barrel of a revolver, but Holmes' hunting crop came\ndown on the man's wrist, and the pistol clinked upon the stone\nfloor.\n\n\"It's no use, John Clay,\" said Holmes blandly. \"You have no\nchance at all.\"\n\n\"So I see,\" the other answered with the utmost coolness. \"I fancy\nthat my pal is all right, though I see you have got his\ncoat-tails.\"\n\n\"There are three men waiting for him at the door,\" said Holmes.\n\n\"Oh, indeed! You seem to have done the thing very completely. I\nmust compliment you.\"\n\n\"And I you,\" Holmes answered. \"Your red-headed idea was very new\nand effective.\"\n\n\"You'll see your pal again presently,\" said Jones. \"He's quicker\nat climbing down holes than I am. Just hold out while I fix the\nderbies.\"\n\n\"I beg that you will not touch me with your filthy hands,\"\nremarked our prisoner as the handcuffs clattered upon his wrists.\n\"You may not be aware that I have royal blood in my veins. Have\nthe goodness, also, when you address me always to say 'sir' and\n'please.'\"\n\n\"All right,\" said Jones with a stare and a snigger. \"Well, would\nyou please, sir, march upstairs, where we can get a cab to carry\nyour Highness to the police-station?\"\n\n\"That is better,\" said John Clay serenely. He made a sweeping bow\nto the three of us and walked quietly off in the custody of the\ndetective.\n\n\"Really, Mr. Holmes,\" said Mr. Merryweather as we followed them\nfrom the cellar, \"I do not know how the bank can thank you or\nrepay you. There is no doubt that you have detected and defeated\nin the most complete manner one of the most determined attempts\nat bank robbery that have ever come within my experience.\"\n\n\"I have had one or two little scores of my own to settle with Mr.\nJohn Clay,\" said Holmes. \"I have been at some small expense over\nthis matter, which I shall expect the bank to refund, but beyond\nthat I am amply repaid by having had an experience which is in\nmany ways unique, and by hearing the very remarkable narrative of\nthe Red-headed League.\"\n\n\n\"You see, Watson,\" he explained in the early hours of the morning\nas we sat over a glass of whisky and soda in Baker Street, \"it\nwas perfectly obvious from the first that the only possible\nobject of this rather fantastic business of the advertisement of\nthe League, and the copying of the 'Encyclopaedia,' must be to get\nthis not over-bright pawnbroker out of the way for a number of\nhours every day. It was a curious way of managing it, but,\nreally, it would be difficult to suggest a better. The method was\nno doubt suggested to Clay's ingenious mind by the colour of his\naccomplice's hair. The 4 pounds a week was a lure which must draw\nhim, and what was it to them, who were playing for thousands?\nThey put in the advertisement, one rogue has the temporary\noffice, the other rogue incites the man to apply for it, and\ntogether they manage to secure his absence every morning in the\nweek. From the time that I heard of the assistant having come for\nhalf wages, it was obvious to me that he had some strong motive\nfor securing the situation.\"\n\n\"But how could you guess what the motive was?\"\n\n\"Had there been women in the house, I should have suspected a\nmere vulgar intrigue. That, however, was out of the question. The\nman's business was a small one, and there was nothing in his\nhouse which could account for such elaborate preparations, and\nsuch an expenditure as they were at. It must, then, be something\nout of the house. What could it be? I thought of the assistant's\nfondness for photography, and his trick of vanishing into the\ncellar. The cellar! There was the end of this tangled clue. Then\nI made inquiries as to this mysterious assistant and found that I\nhad to deal with one of the coolest and most daring criminals in\nLondon. He was doing something in the cellar--something which\ntook many hours a day for months on end. What could it be, once\nmore? I could think of nothing save that he was running a tunnel\nto some other building.\n\n\"So far I had got when we went to visit the scene of action. I\nsurprised you by beating upon the pavement with my stick. I was\nascertaining whether the cellar stretched out in front or behind.\nIt was not in front. Then I rang the bell, and, as I hoped, the\nassistant answered it. We have had some skirmishes, but we had\nnever set eyes upon each other before. I hardly looked at his\nface. His knees were what I wished to see. You must yourself have\nremarked how worn, wrinkled, and stained they were. They spoke of\nthose hours of burrowing. The only remaining point was what they\nwere burrowing for. I walked round the corner, saw the City and\nSuburban Bank abutted on our friend's premises, and felt that I\nhad solved my problem. When you drove home after the concert I\ncalled upon Scotland Yard and upon the chairman of the bank\ndirectors, with the result that you have seen.\"\n\n\"And how could you tell that they would make their attempt\nto-night?\" I asked.\n\n\"Well, when they closed their League offices that was a sign that\nthey cared no longer about Mr. Jabez Wilson's presence--in other\nwords, that they had completed their tunnel. But it was essential\nthat they should use it soon, as it might be discovered, or the\nbullion might be removed. Saturday would suit them better than\nany other day, as it would give them two days for their escape.\nFor all these reasons I expected them to come to-night.\"\n\n\"You reasoned it out beautifully,\" I exclaimed in unfeigned\nadmiration. \"It is so long a chain, and yet every link rings\ntrue.\"\n\n\"It saved me from ennui,\" he answered, yawning. \"Alas! I already\nfeel it closing in upon me. My life is spent in one long effort\nto escape from the commonplaces of existence. These little\nproblems help me to do so.\"\n\n\"And you are a benefactor of the race,\" said I.\n\nHe shrugged his shoulders. \"Well, perhaps, after all, it is of\nsome little use,\" he remarked. \"'L'homme c'est rien--l'oeuvre\nc'est tout,' as Gustave Flaubert wrote to George Sand.\"\n\n\n\nADVENTURE III. A CASE OF IDENTITY\n\n\"My dear fellow,\" said Sherlock Holmes as we sat on either side\nof the fire in his lodgings at Baker Street, \"life is infinitely\nstranger than anything which the mind of man could invent. We\nwould not dare to conceive the things which are really mere\ncommonplaces of existence. If we could fly out of that window\nhand in hand, hover over this great city, gently remove the\nroofs, and peep in at the queer things which are going on, the\nstrange coincidences, the plannings, the cross-purposes, the\nwonderful chains of events, working through generations, and\nleading to the most outré results, it would make all fiction with\nits conventionalities and foreseen conclusions most stale and\nunprofitable.\"\n\n\"And yet I am not convinced of it,\" I answered. \"The cases which\ncome to light in the papers are, as a rule, bald enough, and\nvulgar enough. We have in our police reports realism pushed to\nits extreme limits, and yet the result is, it must be confessed,\nneither fascinating nor artistic.\"\n\n\"A certain selection and discretion must be used in producing a\nrealistic effect,\" remarked Holmes. \"This is wanting in the\npolice report, where more stress is laid, perhaps, upon the\nplatitudes of the magistrate than upon the details, which to an\nobserver contain the vital essence of the whole matter. Depend\nupon it, there is nothing so unnatural as the commonplace.\"\n\nI smiled and shook my head. \"I can quite understand your thinking\nso,\" I said. \"Of course, in your position of unofficial adviser\nand helper to everybody who is absolutely puzzled, throughout\nthree continents, you are brought in contact with all that is\nstrange and bizarre. But here\"--I picked up the morning paper\nfrom the ground--\"let us put it to a practical test. Here is the\nfirst heading upon which I come. 'A husband's cruelty to his\nwife.' There is half a column of print, but I know without\nreading it that it is all perfectly familiar to me. There is, of\ncourse, the other woman, the drink, the push, the blow, the\nbruise, the sympathetic sister or landlady. The crudest of\nwriters could invent nothing more crude.\"\n\n\"Indeed, your example is an unfortunate one for your argument,\"\nsaid Holmes, taking the paper and glancing his eye down it. \"This\nis the Dundas separation case, and, as it happens, I was engaged\nin clearing up some small points in connection with it. The\nhusband was a teetotaler, there was no other woman, and the\nconduct complained of was that he had drifted into the habit of\nwinding up every meal by taking out his false teeth and hurling\nthem at his wife, which, you will allow, is not an action likely\nto occur to the imagination of the average story-teller. Take a\npinch of snuff, Doctor, and acknowledge that I have scored over\nyou in your example.\"\n\nHe held out his snuffbox of old gold, with a great amethyst in\nthe centre of the lid. Its splendour was in such contrast to his\nhomely ways and simple life that I could not help commenting upon\nit.\n\n\"Ah,\" said he, \"I forgot that I had not seen you for some weeks.\nIt is a little souvenir from the King of Bohemia in return for my\nassistance in the case of the Irene Adler papers.\"\n\n\"And the ring?\" I asked, glancing at a remarkable brilliant which\nsparkled upon his finger.\n\n\"It was from the reigning family of Holland, though the matter in\nwhich I served them was of such delicacy that I cannot confide it\neven to you, who have been good enough to chronicle one or two of\nmy little problems.\"\n\n\"And have you any on hand just now?\" I asked with interest.\n\n\"Some ten or twelve, but none which present any feature of\ninterest. They are important, you understand, without being\ninteresting. Indeed, I have found that it is usually in\nunimportant matters that there is a field for the observation,\nand for the quick analysis of cause and effect which gives the\ncharm to an investigation. The larger crimes are apt to be the\nsimpler, for the bigger the crime the more obvious, as a rule, is\nthe motive. In these cases, save for one rather intricate matter\nwhich has been referred to me from Marseilles, there is nothing\nwhich presents any features of interest. It is possible, however,\nthat I may have something better before very many minutes are\nover, for this is one of my clients, or I am much mistaken.\"\n\nHe had risen from his chair and was standing between the parted\nblinds gazing down into the dull neutral-tinted London street.\nLooking over his shoulder, I saw that on the pavement opposite\nthere stood a large woman with a heavy fur boa round her neck,\nand a large curling red feather in a broad-brimmed hat which was\ntilted in a coquettish Duchess of Devonshire fashion over her\near. From under this great panoply she peeped up in a nervous,\nhesitating fashion at our windows, while her body oscillated\nbackward and forward, and her fingers fidgeted with her glove\nbuttons. Suddenly, with a plunge, as of the swimmer who leaves\nthe bank, she hurried across the road, and we heard the sharp\nclang of the bell.\n\n\"I have seen those symptoms before,\" said Holmes, throwing his\ncigarette into the fire. \"Oscillation upon the pavement always\nmeans an affaire de coeur. She would like advice, but is not sure\nthat the matter is not too delicate for communication. And yet\neven here we may discriminate. When a woman has been seriously\nwronged by a man she no longer oscillates, and the usual symptom\nis a broken bell wire. Here we may take it that there is a love\nmatter, but that the maiden is not so much angry as perplexed, or\ngrieved. But here she comes in person to resolve our doubts.\"\n\nAs he spoke there was a tap at the door, and the boy in buttons\nentered to announce Miss Mary Sutherland, while the lady herself\nloomed behind his small black figure like a full-sailed\nmerchant-man behind a tiny pilot boat. Sherlock Holmes welcomed\nher with the easy courtesy for which he was remarkable, and,\nhaving closed the door and bowed her into an armchair, he looked\nher over in the minute and yet abstracted fashion which was\npeculiar to him.\n\n\"Do you not find,\" he said, \"that with your short sight it is a\nlittle trying to do so much typewriting?\"\n\n\"I did at first,\" she answered, \"but now I know where the letters\nare without looking.\" Then, suddenly realising the full purport\nof his words, she gave a violent start and looked up, with fear\nand astonishment upon her broad, good-humoured face. \"You've\nheard about me, Mr. Holmes,\" she cried, \"else how could you know\nall that?\"\n\n\"Never mind,\" said Holmes, laughing; \"it is my business to know\nthings. Perhaps I have trained myself to see what others\noverlook. If not, why should you come to consult me?\"\n\n\"I came to you, sir, because I heard of you from Mrs. Etherege,\nwhose husband you found so easy when the police and everyone had\ngiven him up for dead. Oh, Mr. Holmes, I wish you would do as\nmuch for me. I'm not rich, but still I have a hundred a year in\nmy own right, besides the little that I make by the machine, and\nI would give it all to know what has become of Mr. Hosmer Angel.\"\n\n\"Why did you come away to consult me in such a hurry?\" asked\nSherlock Holmes, with his finger-tips together and his eyes to\nthe ceiling.\n\nAgain a startled look came over the somewhat vacuous face of Miss\nMary Sutherland. \"Yes, I did bang out of the house,\" she said,\n\"for it made me angry to see the easy way in which Mr.\nWindibank--that is, my father--took it all. He would not go to\nthe police, and he would not go to you, and so at last, as he\nwould do nothing and kept on saying that there was no harm done,\nit made me mad, and I just on with my things and came right away\nto you.\"\n\n\"Your father,\" said Holmes, \"your stepfather, surely, since the\nname is different.\"\n\n\"Yes, my stepfather. I call him father, though it sounds funny,\ntoo, for he is only five years and two months older than myself.\"\n\n\"And your mother is alive?\"\n\n\"Oh, yes, mother is alive and well. I wasn't best pleased, Mr.\nHolmes, when she married again so soon after father's death, and\na man who was nearly fifteen years younger than herself. Father\nwas a plumber in the Tottenham Court Road, and he left a tidy\nbusiness behind him, which mother carried on with Mr. Hardy, the\nforeman; but when Mr. Windibank came he made her sell the\nbusiness, for he was very superior, being a traveller in wines.\nThey got 4700 pounds for the goodwill and interest, which wasn't\nnear as much as father could have got if he had been alive.\"\n\nI had expected to see Sherlock Holmes impatient under this\nrambling and inconsequential narrative, but, on the contrary, he\nhad listened with the greatest concentration of attention.\n\n\"Your own little income,\" he asked, \"does it come out of the\nbusiness?\"\n\n\"Oh, no, sir. It is quite separate and was left me by my uncle\nNed in Auckland. It is in New Zealand stock, paying 4 1/2 per\ncent. Two thousand five hundred pounds was the amount, but I can\nonly touch the interest.\"\n\n\"You interest me extremely,\" said Holmes. \"And since you draw so\nlarge a sum as a hundred a year, with what you earn into the\nbargain, you no doubt travel a little and indulge yourself in\nevery way. I believe that a single lady can get on very nicely\nupon an income of about 60 pounds.\"\n\n\"I could do with much less than that, Mr. Holmes, but you\nunderstand that as long as I live at home I don't wish to be a\nburden to them, and so they have the use of the money just while\nI am staying with them. Of course, that is only just for the\ntime. Mr. Windibank draws my interest every quarter and pays it\nover to mother, and I find that I can do pretty well with what I\nearn at typewriting. It brings me twopence a sheet, and I can\noften do from fifteen to twenty sheets in a day.\"\n\n\"You have made your position very clear to me,\" said Holmes.\n\"This is my friend, Dr. Watson, before whom you can speak as\nfreely as before myself. Kindly tell us now all about your\nconnection with Mr. Hosmer Angel.\"\n\nA flush stole over Miss Sutherland's face, and she picked\nnervously at the fringe of her jacket. \"I met him first at the\ngasfitters' ball,\" she said. \"They used to send father tickets\nwhen he was alive, and then afterwards they remembered us, and\nsent them to mother. Mr. Windibank did not wish us to go. He\nnever did wish us to go anywhere. He would get quite mad if I\nwanted so much as to join a Sunday-school treat. But this time I\nwas set on going, and I would go; for what right had he to\nprevent? He said the folk were not fit for us to know, when all\nfather's friends were to be there. And he said that I had nothing\nfit to wear, when I had my purple plush that I had never so much\nas taken out of the drawer. At last, when nothing else would do,\nhe went off to France upon the business of the firm, but we went,\nmother and I, with Mr. Hardy, who used to be our foreman, and it\nwas there I met Mr. Hosmer Angel.\"\n\n\"I suppose,\" said Holmes, \"that when Mr. Windibank came back from\nFrance he was very annoyed at your having gone to the ball.\"\n\n\"Oh, well, he was very good about it. He laughed, I remember, and\nshrugged his shoulders, and said there was no use denying\nanything to a woman, for she would have her way.\"\n\n\"I see. Then at the gasfitters' ball you met, as I understand, a\ngentleman called Mr. Hosmer Angel.\"\n\n\"Yes, sir. I met him that night, and he called next day to ask if\nwe had got home all safe, and after that we met him--that is to\nsay, Mr. Holmes, I met him twice for walks, but after that father\ncame back again, and Mr. Hosmer Angel could not come to the house\nany more.\"\n\n\"No?\"\n\n\"Well, you know father didn't like anything of the sort. He\nwouldn't have any visitors if he could help it, and he used to\nsay that a woman should be happy in her own family circle. But\nthen, as I used to say to mother, a woman wants her own circle to\nbegin with, and I had not got mine yet.\"\n\n\"But how about Mr. Hosmer Angel? Did he make no attempt to see\nyou?\"\n\n\"Well, father was going off to France again in a week, and Hosmer\nwrote and said that it would be safer and better not to see each\nother until he had gone. We could write in the meantime, and he\nused to write every day. I took the letters in in the morning, so\nthere was no need for father to know.\"\n\n\"Were you engaged to the gentleman at this time?\"\n\n\"Oh, yes, Mr. Holmes. We were engaged after the first walk that\nwe took. Hosmer--Mr. Angel--was a cashier in an office in\nLeadenhall Street--and--\"\n\n\"What office?\"\n\n\"That's the worst of it, Mr. Holmes, I don't know.\"\n\n\"Where did he live, then?\"\n\n\"He slept on the premises.\"\n\n\"And you don't know his address?\"\n\n\"No--except that it was Leadenhall Street.\"\n\n\"Where did you address your letters, then?\"\n\n\"To the Leadenhall Street Post Office, to be left till called\nfor. He said that if they were sent to the office he would be\nchaffed by all the other clerks about having letters from a lady,\nso I offered to typewrite them, like he did his, but he wouldn't\nhave that, for he said that when I wrote them they seemed to come\nfrom me, but when they were typewritten he always felt that the\nmachine had come between us. That will just show you how fond he\nwas of me, Mr. Holmes, and the little things that he would think\nof.\"\n\n\"It was most suggestive,\" said Holmes. \"It has long been an axiom\nof mine that the little things are infinitely the most important.\nCan you remember any other little things about Mr. Hosmer Angel?\"\n\n\"He was a very shy man, Mr. Holmes. He would rather walk with me\nin the evening than in the daylight, for he said that he hated to\nbe conspicuous. Very retiring and gentlemanly he was. Even his\nvoice was gentle. He'd had the quinsy and swollen glands when he\nwas young, he told me, and it had left him with a weak throat,\nand a hesitating, whispering fashion of speech. He was always\nwell dressed, very neat and plain, but his eyes were weak, just\nas mine are, and he wore tinted glasses against the glare.\"\n\n\"Well, and what happened when Mr. Windibank, your stepfather,\nreturned to France?\"\n\n\"Mr. Hosmer Angel came to the house again and proposed that we\nshould marry before father came back. He was in dreadful earnest\nand made me swear, with my hands on the Testament, that whatever\nhappened I would always be true to him. Mother said he was quite\nright to make me swear, and that it was a sign of his passion.\nMother was all in his favour from the first and was even fonder\nof him than I was. Then, when they talked of marrying within the\nweek, I began to ask about father; but they both said never to\nmind about father, but just to tell him afterwards, and mother\nsaid she would make it all right with him. I didn't quite like\nthat, Mr. Holmes. It seemed funny that I should ask his leave, as\nhe was only a few years older than me; but I didn't want to do\nanything on the sly, so I wrote to father at Bordeaux, where the\ncompany has its French offices, but the letter came back to me on\nthe very morning of the wedding.\"\n\n\"It missed him, then?\"\n\n\"Yes, sir; for he had started to England just before it arrived.\"\n\n\"Ha! that was unfortunate. Your wedding was arranged, then, for\nthe Friday. Was it to be in church?\"\n\n\"Yes, sir, but very quietly. It was to be at St. Saviour's, near\nKing's Cross, and we were to have breakfast afterwards at the St.\nPancras Hotel. Hosmer came for us in a hansom, but as there were\ntwo of us he put us both into it and stepped himself into a\nfour-wheeler, which happened to be the only other cab in the\nstreet. We got to the church first, and when the four-wheeler\ndrove up we waited for him to step out, but he never did, and\nwhen the cabman got down from the box and looked there was no one\nthere! The cabman said that he could not imagine what had become\nof him, for he had seen him get in with his own eyes. That was\nlast Friday, Mr. Holmes, and I have never seen or heard anything\nsince then to throw any light upon what became of him.\"\n\n\"It seems to me that you have been very shamefully treated,\" said\nHolmes.\n\n\"Oh, no, sir! He was too good and kind to leave me so. Why, all\nthe morning he was saying to me that, whatever happened, I was to\nbe true; and that even if something quite unforeseen occurred to\nseparate us, I was always to remember that I was pledged to him,\nand that he would claim his pledge sooner or later. It seemed\nstrange talk for a wedding-morning, but what has happened since\ngives a meaning to it.\"\n\n\"Most certainly it does. Your own opinion is, then, that some\nunforeseen catastrophe has occurred to him?\"\n\n\"Yes, sir. I believe that he foresaw some danger, or else he\nwould not have talked so. And then I think that what he foresaw\nhappened.\"\n\n\"But you have no notion as to what it could have been?\"\n\n\"None.\"\n\n\"One more question. How did your mother take the matter?\"\n\n\"She was angry, and said that I was never to speak of the matter\nagain.\"\n\n\"And your father? Did you tell him?\"\n\n\"Yes; and he seemed to think, with me, that something had\nhappened, and that I should hear of Hosmer again. As he said,\nwhat interest could anyone have in bringing me to the doors of\nthe church, and then leaving me? Now, if he had borrowed my\nmoney, or if he had married me and got my money settled on him,\nthere might be some reason, but Hosmer was very independent about\nmoney and never would look at a shilling of mine. And yet, what\ncould have happened? And why could he not write? Oh, it drives me\nhalf-mad to think of it, and I can't sleep a wink at night.\" She\npulled a little handkerchief out of her muff and began to sob\nheavily into it.\n\n\"I shall glance into the case for you,\" said Holmes, rising, \"and\nI have no doubt that we shall reach some definite result. Let the\nweight of the matter rest upon me now, and do not let your mind\ndwell upon it further. Above all, try to let Mr. Hosmer Angel\nvanish from your memory, as he has done from your life.\"\n\n\"Then you don't think I'll see him again?\"\n\n\"I fear not.\"\n\n\"Then what has happened to him?\"\n\n\"You will leave that question in my hands. I should like an\naccurate description of him and any letters of his which you can\nspare.\"\n\n\"I advertised for him in last Saturday's Chronicle,\" said she.\n\"Here is the slip and here are four letters from him.\"\n\n\"Thank you. And your address?\"\n\n\"No. 31 Lyon Place, Camberwell.\"\n\n\"Mr. Angel's address you never had, I understand. Where is your\nfather's place of business?\"\n\n\"He travels for Westhouse & Marbank, the great claret importers\nof Fenchurch Street.\"\n\n\"Thank you. You have made your statement very clearly. You will\nleave the papers here, and remember the advice which I have given\nyou. Let the whole incident be a sealed book, and do not allow it\nto affect your life.\"\n\n\"You are very kind, Mr. Holmes, but I cannot do that. I shall be\ntrue to Hosmer. He shall find me ready when he comes back.\"\n\nFor all the preposterous hat and the vacuous face, there was\nsomething noble in the simple faith of our visitor which\ncompelled our respect. She laid her little bundle of papers upon\nthe table and went her way, with a promise to come again whenever\nshe might be summoned.\n\nSherlock Holmes sat silent for a few minutes with his fingertips\nstill pressed together, his legs stretched out in front of him,\nand his gaze directed upward to the ceiling. Then he took down\nfrom the rack the old and oily clay pipe, which was to him as a\ncounsellor, and, having lit it, he leaned back in his chair, with\nthe thick blue cloud-wreaths spinning up from him, and a look of\ninfinite languor in his face.\n\n\"Quite an interesting study, that maiden,\" he observed. \"I found\nher more interesting than her little problem, which, by the way,\nis rather a trite one. You will find parallel cases, if you\nconsult my index, in Andover in '77, and there was something of\nthe sort at The Hague last year. Old as is the idea, however,\nthere were one or two details which were new to me. But the\nmaiden herself was most instructive.\"\n\n\"You appeared to read a good deal upon her which was quite\ninvisible to me,\" I remarked.\n\n\"Not invisible but unnoticed, Watson. You did not know where to\nlook, and so you missed all that was important. I can never bring\nyou to realise the importance of sleeves, the suggestiveness of\nthumb-nails, or the great issues that may hang from a boot-lace.\nNow, what did you gather from that woman's appearance? Describe\nit.\"\n\n\"Well, she had a slate-coloured, broad-brimmed straw hat, with a\nfeather of a brickish red. Her jacket was black, with black beads\nsewn upon it, and a fringe of little black jet ornaments. Her\ndress was brown, rather darker than coffee colour, with a little\npurple plush at the neck and sleeves. Her gloves were greyish and\nwere worn through at the right forefinger. Her boots I didn't\nobserve. She had small round, hanging gold earrings, and a\ngeneral air of being fairly well-to-do in a vulgar, comfortable,\neasy-going way.\"\n\nSherlock Holmes clapped his hands softly together and chuckled.\n\n\"'Pon my word, Watson, you are coming along wonderfully. You have\nreally done very well indeed. It is true that you have missed\neverything of importance, but you have hit upon the method, and\nyou have a quick eye for colour. Never trust to general\nimpressions, my boy, but concentrate yourself upon details. My\nfirst glance is always at a woman's sleeve. In a man it is\nperhaps better first to take the knee of the trouser. As you\nobserve, this woman had plush upon her sleeves, which is a most\nuseful material for showing traces. The double line a little\nabove the wrist, where the typewritist presses against the table,\nwas beautifully defined. The sewing-machine, of the hand type,\nleaves a similar mark, but only on the left arm, and on the side\nof it farthest from the thumb, instead of being right across the\nbroadest part, as this was. I then glanced at her face, and,\nobserving the dint of a pince-nez at either side of her nose, I\nventured a remark upon short sight and typewriting, which seemed\nto surprise her.\"\n\n\"It surprised me.\"\n\n\"But, surely, it was obvious. I was then much surprised and\ninterested on glancing down to observe that, though the boots\nwhich she was wearing were not unlike each other, they were\nreally odd ones; the one having a slightly decorated toe-cap, and\nthe other a plain one. One was buttoned only in the two lower\nbuttons out of five, and the other at the first, third, and\nfifth. Now, when you see that a young lady, otherwise neatly\ndressed, has come away from home with odd boots, half-buttoned,\nit is no great deduction to say that she came away in a hurry.\"\n\n\"And what else?\" I asked, keenly interested, as I always was, by\nmy friend's incisive reasoning.\n\n\"I noted, in passing, that she had written a note before leaving\nhome but after being fully dressed. You observed that her right\nglove was torn at the forefinger, but you did not apparently see\nthat both glove and finger were stained with violet ink. She had\nwritten in a hurry and dipped her pen too deep. It must have been\nthis morning, or the mark would not remain clear upon the finger.\nAll this is amusing, though rather elementary, but I must go back\nto business, Watson. Would you mind reading me the advertised\ndescription of Mr. Hosmer Angel?\"\n\nI held the little printed slip to the light.\n\n\"Missing,\" it said, \"on the morning of the fourteenth, a gentleman\nnamed Hosmer Angel. About five ft. seven in. in height;\nstrongly built, sallow complexion, black hair, a little bald in\nthe centre, bushy, black side-whiskers and moustache; tinted\nglasses, slight infirmity of speech. Was dressed, when last seen,\nin black frock-coat faced with silk, black waistcoat, gold Albert\nchain, and grey Harris tweed trousers, with brown gaiters over\nelastic-sided boots. Known to have been employed in an office in\nLeadenhall Street. Anybody bringing--\"\n\n\"That will do,\" said Holmes. \"As to the letters,\" he continued,\nglancing over them, \"they are very commonplace. Absolutely no\nclue in them to Mr. Angel, save that he quotes Balzac once. There\nis one remarkable point, however, which will no doubt strike\nyou.\"\n\n\"They are typewritten,\" I remarked.\n\n\"Not only that, but the signature is typewritten. Look at the\nneat little 'Hosmer Angel' at the bottom. There is a date, you\nsee, but no superscription except Leadenhall Street, which is\nrather vague. The point about the signature is very suggestive--in\nfact, we may call it conclusive.\"\n\n\"Of what?\"\n\n\"My dear fellow, is it possible you do not see how strongly it\nbears upon the case?\"\n\n\"I cannot say that I do unless it were that he wished to be able\nto deny his signature if an action for breach of promise were\ninstituted.\"\n\n\"No, that was not the point. However, I shall write two letters,\nwhich should settle the matter. One is to a firm in the City, the\nother is to the young lady's stepfather, Mr. Windibank, asking\nhim whether he could meet us here at six o'clock tomorrow\nevening. It is just as well that we should do business with the\nmale relatives. And now, Doctor, we can do nothing until the\nanswers to those letters come, so we may put our little problem\nupon the shelf for the interim.\"\n\nI had had so many reasons to believe in my friend's subtle powers\nof reasoning and extraordinary energy in action that I felt that\nhe must have some solid grounds for the assured and easy\ndemeanour with which he treated the singular mystery which he had\nbeen called upon to fathom. Once only had I known him to fail, in\nthe case of the King of Bohemia and of the Irene Adler\nphotograph; but when I looked back to the weird business of the\nSign of Four, and the extraordinary circumstances connected with\nthe Study in Scarlet, I felt that it would be a strange tangle\nindeed which he could not unravel.\n\nI left him then, still puffing at his black clay pipe, with the\nconviction that when I came again on the next evening I would\nfind that he held in his hands all the clues which would lead up\nto the identity of the disappearing bridegroom of Miss Mary\nSutherland.\n\nA professional case of great gravity was engaging my own\nattention at the time, and the whole of next day I was busy at\nthe bedside of the sufferer. It was not until close upon six\no'clock that I found myself free and was able to spring into a\nhansom and drive to Baker Street, half afraid that I might be too\nlate to assist at the dénouement of the little mystery. I found\nSherlock Holmes alone, however, half asleep, with his long, thin\nform curled up in the recesses of his armchair. A formidable\narray of bottles and test-tubes, with the pungent cleanly smell\nof hydrochloric acid, told me that he had spent his day in the\nchemical work which was so dear to him.\n\n\"Well, have you solved it?\" I asked as I entered.\n\n\"Yes. It was the bisulphate of baryta.\"\n\n\"No, no, the mystery!\" I cried.\n\n\"Oh, that! I thought of the salt that I have been working upon.\nThere was never any mystery in the matter, though, as I said\nyesterday, some of the details are of interest. The only drawback\nis that there is no law, I fear, that can touch the scoundrel.\"\n\n\"Who was he, then, and what was his object in deserting Miss\nSutherland?\"\n\nThe question was hardly out of my mouth, and Holmes had not yet\nopened his lips to reply, when we heard a heavy footfall in the\npassage and a tap at the door.\n\n\"This is the girl's stepfather, Mr. James Windibank,\" said\nHolmes. \"He has written to me to say that he would be here at\nsix. Come in!\"\n\nThe man who entered was a sturdy, middle-sized fellow, some\nthirty years of age, clean-shaven, and sallow-skinned, with a\nbland, insinuating manner, and a pair of wonderfully sharp and\npenetrating grey eyes. He shot a questioning glance at each of\nus, placed his shiny top-hat upon the sideboard, and with a\nslight bow sidled down into the nearest chair.\n\n\"Good-evening, Mr. James Windibank,\" said Holmes. \"I think that\nthis typewritten letter is from you, in which you made an\nappointment with me for six o'clock?\"\n\n\"Yes, sir. I am afraid that I am a little late, but I am not\nquite my own master, you know. I am sorry that Miss Sutherland\nhas troubled you about this little matter, for I think it is far\nbetter not to wash linen of the sort in public. It was quite\nagainst my wishes that she came, but she is a very excitable,\nimpulsive girl, as you may have noticed, and she is not easily\ncontrolled when she has made up her mind on a point. Of course, I\ndid not mind you so much, as you are not connected with the\nofficial police, but it is not pleasant to have a family\nmisfortune like this noised abroad. Besides, it is a useless\nexpense, for how could you possibly find this Hosmer Angel?\"\n\n\"On the contrary,\" said Holmes quietly; \"I have every reason to\nbelieve that I will succeed in discovering Mr. Hosmer Angel.\"\n\nMr. Windibank gave a violent start and dropped his gloves. \"I am\ndelighted to hear it,\" he said.\n\n\"It is a curious thing,\" remarked Holmes, \"that a typewriter has\nreally quite as much individuality as a man's handwriting. Unless\nthey are quite new, no two of them write exactly alike. Some\nletters get more worn than others, and some wear only on one\nside. Now, you remark in this note of yours, Mr. Windibank, that\nin every case there is some little slurring over of the 'e,' and\na slight defect in the tail of the 'r.' There are fourteen other\ncharacteristics, but those are the more obvious.\"\n\n\"We do all our correspondence with this machine at the office,\nand no doubt it is a little worn,\" our visitor answered, glancing\nkeenly at Holmes with his bright little eyes.\n\n\"And now I will show you what is really a very interesting study,\nMr. Windibank,\" Holmes continued. \"I think of writing another\nlittle monograph some of these days on the typewriter and its\nrelation to crime. It is a subject to which I have devoted some\nlittle attention. I have here four letters which purport to come\nfrom the missing man. They are all typewritten. In each case, not\nonly are the 'e's' slurred and the 'r's' tailless, but you will\nobserve, if you care to use my magnifying lens, that the fourteen\nother characteristics to which I have alluded are there as well.\"\n\nMr. Windibank sprang out of his chair and picked up his hat. \"I\ncannot waste time over this sort of fantastic talk, Mr. Holmes,\"\nhe said. \"If you can catch the man, catch him, and let me know\nwhen you have done it.\"\n\n\"Certainly,\" said Holmes, stepping over and turning the key in\nthe door. \"I let you know, then, that I have caught him!\"\n\n\"What! where?\" shouted Mr. Windibank, turning white to his lips\nand glancing about him like a rat in a trap.\n\n\"Oh, it won't do--really it won't,\" said Holmes suavely. \"There\nis no possible getting out of it, Mr. Windibank. It is quite too\ntransparent, and it was a very bad compliment when you said that\nit was impossible for me to solve so simple a question. That's\nright! Sit down and let us talk it over.\"\n\nOur visitor collapsed into a chair, with a ghastly face and a\nglitter of moisture on his brow. \"It--it's not actionable,\" he\nstammered.\n\n\"I am very much afraid that it is not. But between ourselves,\nWindibank, it was as cruel and selfish and heartless a trick in a\npetty way as ever came before me. Now, let me just run over the\ncourse of events, and you will contradict me if I go wrong.\"\n\nThe man sat huddled up in his chair, with his head sunk upon his\nbreast, like one who is utterly crushed. Holmes stuck his feet up\non the corner of the mantelpiece and, leaning back with his hands\nin his pockets, began talking, rather to himself, as it seemed,\nthan to us.\n\n\"The man married a woman very much older than himself for her\nmoney,\" said he, \"and he enjoyed the use of the money of the\ndaughter as long as she lived with them. It was a considerable\nsum, for people in their position, and the loss of it would have\nmade a serious difference. It was worth an effort to preserve it.\nThe daughter was of a good, amiable disposition, but affectionate\nand warm-hearted in her ways, so that it was evident that with\nher fair personal advantages, and her little income, she would\nnot be allowed to remain single long. Now her marriage would\nmean, of course, the loss of a hundred a year, so what does her\nstepfather do to prevent it? He takes the obvious course of\nkeeping her at home and forbidding her to seek the company of\npeople of her own age. But soon he found that that would not\nanswer forever. She became restive, insisted upon her rights, and\nfinally announced her positive intention of going to a certain\nball. What does her clever stepfather do then? He conceives an\nidea more creditable to his head than to his heart. With the\nconnivance and assistance of his wife he disguised himself,\ncovered those keen eyes with tinted glasses, masked the face with\na moustache and a pair of bushy whiskers, sunk that clear voice\ninto an insinuating whisper, and doubly secure on account of the\ngirl's short sight, he appears as Mr. Hosmer Angel, and keeps off\nother lovers by making love himself.\"\n\n\"It was only a joke at first,\" groaned our visitor. \"We never\nthought that she would have been so carried away.\"\n\n\"Very likely not. However that may be, the young lady was very\ndecidedly carried away, and, having quite made up her mind that\nher stepfather was in France, the suspicion of treachery never\nfor an instant entered her mind. She was flattered by the\ngentleman's attentions, and the effect was increased by the\nloudly expressed admiration of her mother. Then Mr. Angel began\nto call, for it was obvious that the matter should be pushed as\nfar as it would go if a real effect were to be produced. There\nwere meetings, and an engagement, which would finally secure the\ngirl's affections from turning towards anyone else. But the\ndeception could not be kept up forever. These pretended journeys\nto France were rather cumbrous. The thing to do was clearly to\nbring the business to an end in such a dramatic manner that it\nwould leave a permanent impression upon the young lady's mind and\nprevent her from looking upon any other suitor for some time to\ncome. Hence those vows of fidelity exacted upon a Testament, and\nhence also the allusions to a possibility of something happening\non the very morning of the wedding. James Windibank wished Miss\nSutherland to be so bound to Hosmer Angel, and so uncertain as to\nhis fate, that for ten years to come, at any rate, she would not\nlisten to another man. As far as the church door he brought her,\nand then, as he could go no farther, he conveniently vanished\naway by the old trick of stepping in at one door of a\nfour-wheeler and out at the other. I think that was the chain of\nevents, Mr. Windibank!\"\n\nOur visitor had recovered something of his assurance while Holmes\nhad been talking, and he rose from his chair now with a cold\nsneer upon his pale face.\n\n\"It may be so, or it may not, Mr. Holmes,\" said he, \"but if you\nare so very sharp you ought to be sharp enough to know that it is\nyou who are breaking the law now, and not me. I have done nothing\nactionable from the first, but as long as you keep that door\nlocked you lay yourself open to an action for assault and illegal\nconstraint.\"\n\n\"The law cannot, as you say, touch you,\" said Holmes, unlocking\nand throwing open the door, \"yet there never was a man who\ndeserved punishment more. If the young lady has a brother or a\nfriend, he ought to lay a whip across your shoulders. By Jove!\"\nhe continued, flushing up at the sight of the bitter sneer upon\nthe man's face, \"it is not part of my duties to my client, but\nhere's a hunting crop handy, and I think I shall just treat\nmyself to--\" He took two swift steps to the whip, but before he\ncould grasp it there was a wild clatter of steps upon the stairs,\nthe heavy hall door banged, and from the window we could see Mr.\nJames Windibank running at the top of his speed down the road.\n\n\"There's a cold-blooded scoundrel!\" said Holmes, laughing, as he\nthrew himself down into his chair once more. \"That fellow will\nrise from crime to crime until he does something very bad, and\nends on a gallows. The case has, in some respects, been not\nentirely devoid of interest.\"\n\n\"I cannot now entirely see all the steps of your reasoning,\" I\nremarked.\n\n\"Well, of course it was obvious from the first that this Mr.\nHosmer Angel must have some strong object for his curious\nconduct, and it was equally clear that the only man who really\nprofited by the incident, as far as we could see, was the\nstepfather. Then the fact that the two men were never together,\nbut that the one always appeared when the other was away, was\nsuggestive. So were the tinted spectacles and the curious voice,\nwhich both hinted at a disguise, as did the bushy whiskers. My\nsuspicions were all confirmed by his peculiar action in\ntypewriting his signature, which, of course, inferred that his\nhandwriting was so familiar to her that she would recognise even\nthe smallest sample of it. You see all these isolated facts,\ntogether with many minor ones, all pointed in the same\ndirection.\"\n\n\"And how did you verify them?\"\n\n\"Having once spotted my man, it was easy to get corroboration. I\nknew the firm for which this man worked. Having taken the printed\ndescription. I eliminated everything from it which could be the\nresult of a disguise--the whiskers, the glasses, the voice, and I\nsent it to the firm, with a request that they would inform me\nwhether it answered to the description of any of their\ntravellers. I had already noticed the peculiarities of the\ntypewriter, and I wrote to the man himself at his business\naddress asking him if he would come here. As I expected, his\nreply was typewritten and revealed the same trivial but\ncharacteristic defects. The same post brought me a letter from\nWesthouse & Marbank, of Fenchurch Street, to say that the\ndescription tallied in every respect with that of their employé,\nJames Windibank. Voilà tout!\"\n\n\"And Miss Sutherland?\"\n\n\"If I tell her she will not believe me. You may remember the old\nPersian saying, 'There is danger for him who taketh the tiger\ncub, and danger also for whoso snatches a delusion from a woman.'\nThere is as much sense in Hafiz as in Horace, and as much\nknowledge of the world.\"\n\n\n\nADVENTURE IV. THE BOSCOMBE VALLEY MYSTERY\n\nWe were seated at breakfast one morning, my wife and I, when the\nmaid brought in a telegram. It was from Sherlock Holmes and ran\nin this way:\n\n\"Have you a couple of days to spare? Have just been wired for from\nthe west of England in connection with Boscombe Valley tragedy.\nShall be glad if you will come with me. Air and scenery perfect.\nLeave Paddington by the 11:15.\"\n\n\"What do you say, dear?\" said my wife, looking across at me.\n\"Will you go?\"\n\n\"I really don't know what to say. I have a fairly long list at\npresent.\"\n\n\"Oh, Anstruther would do your work for you. You have been looking\na little pale lately. I think that the change would do you good,\nand you are always so interested in Mr. Sherlock Holmes' cases.\"\n\n\"I should be ungrateful if I were not, seeing what I gained\nthrough one of them,\" I answered. \"But if I am to go, I must pack\nat once, for I have only half an hour.\"\n\nMy experience of camp life in Afghanistan had at least had the\neffect of making me a prompt and ready traveller. My wants were\nfew and simple, so that in less than the time stated I was in a\ncab with my valise, rattling away to Paddington Station. Sherlock\nHolmes was pacing up and down the platform, his tall, gaunt\nfigure made even gaunter and taller by his long grey\ntravelling-cloak and close-fitting cloth cap.\n\n\"It is really very good of you to come, Watson,\" said he. \"It\nmakes a considerable difference to me, having someone with me on\nwhom I can thoroughly rely. Local aid is always either worthless\nor else biassed. If you will keep the two corner seats I shall\nget the tickets.\"\n\nWe had the carriage to ourselves save for an immense litter of\npapers which Holmes had brought with him. Among these he rummaged\nand read, with intervals of note-taking and of meditation, until\nwe were past Reading. Then he suddenly rolled them all into a\ngigantic ball and tossed them up onto the rack.\n\n\"Have you heard anything of the case?\" he asked.\n\n\"Not a word. I have not seen a paper for some days.\"\n\n\"The London press has not had very full accounts. I have just\nbeen looking through all the recent papers in order to master the\nparticulars. It seems, from what I gather, to be one of those\nsimple cases which are so extremely difficult.\"\n\n\"That sounds a little paradoxical.\"\n\n\"But it is profoundly true. Singularity is almost invariably a\nclue. The more featureless and commonplace a crime is, the more\ndifficult it is to bring it home. In this case, however, they\nhave established a very serious case against the son of the\nmurdered man.\"\n\n\"It is a murder, then?\"\n\n\"Well, it is conjectured to be so. I shall take nothing for\ngranted until I have the opportunity of looking personally into\nit. I will explain the state of things to you, as far as I have\nbeen able to understand it, in a very few words.\n\n\"Boscombe Valley is a country district not very far from Ross, in\nHerefordshire. The largest landed proprietor in that part is a\nMr. John Turner, who made his money in Australia and returned\nsome years ago to the old country. One of the farms which he\nheld, that of Hatherley, was let to Mr. Charles McCarthy, who was\nalso an ex-Australian. The men had known each other in the\ncolonies, so that it was not unnatural that when they came to\nsettle down they should do so as near each other as possible.\nTurner was apparently the richer man, so McCarthy became his\ntenant but still remained, it seems, upon terms of perfect\nequality, as they were frequently together. McCarthy had one son,\na lad of eighteen, and Turner had an only daughter of the same\nage, but neither of them had wives living. They appear to have\navoided the society of the neighbouring English families and to\nhave led retired lives, though both the McCarthys were fond of\nsport and were frequently seen at the race-meetings of the\nneighbourhood. McCarthy kept two servants--a man and a girl.\nTurner had a considerable household, some half-dozen at the\nleast. That is as much as I have been able to gather about the\nfamilies. Now for the facts.\n\n\"On June 3rd, that is, on Monday last, McCarthy left his house at\nHatherley about three in the afternoon and walked down to the\nBoscombe Pool, which is a small lake formed by the spreading out\nof the stream which runs down the Boscombe Valley. He had been\nout with his serving-man in the morning at Ross, and he had told\nthe man that he must hurry, as he had an appointment of\nimportance to keep at three. From that appointment he never came\nback alive.\n\n\"From Hatherley Farm-house to the Boscombe Pool is a quarter of a\nmile, and two people saw him as he passed over this ground. One\nwas an old woman, whose name is not mentioned, and the other was\nWilliam Crowder, a game-keeper in the employ of Mr. Turner. Both\nthese witnesses depose that Mr. McCarthy was walking alone. The\ngame-keeper adds that within a few minutes of his seeing Mr.\nMcCarthy pass he had seen his son, Mr. James McCarthy, going the\nsame way with a gun under his arm. To the best of his belief, the\nfather was actually in sight at the time, and the son was\nfollowing him. He thought no more of the matter until he heard in\nthe evening of the tragedy that had occurred.\n\n\"The two McCarthys were seen after the time when William Crowder,\nthe game-keeper, lost sight of them. The Boscombe Pool is thickly\nwooded round, with just a fringe of grass and of reeds round the\nedge. A girl of fourteen, Patience Moran, who is the daughter of\nthe lodge-keeper of the Boscombe Valley estate, was in one of the\nwoods picking flowers. She states that while she was there she\nsaw, at the border of the wood and close by the lake, Mr.\nMcCarthy and his son, and that they appeared to be having a\nviolent quarrel. She heard Mr. McCarthy the elder using very\nstrong language to his son, and she saw the latter raise up his\nhand as if to strike his father. She was so frightened by their\nviolence that she ran away and told her mother when she reached\nhome that she had left the two McCarthys quarrelling near\nBoscombe Pool, and that she was afraid that they were going to\nfight. She had hardly said the words when young Mr. McCarthy came\nrunning up to the lodge to say that he had found his father dead\nin the wood, and to ask for the help of the lodge-keeper. He was\nmuch excited, without either his gun or his hat, and his right\nhand and sleeve were observed to be stained with fresh blood. On\nfollowing him they found the dead body stretched out upon the\ngrass beside the pool. The head had been beaten in by repeated\nblows of some heavy and blunt weapon. The injuries were such as\nmight very well have been inflicted by the butt-end of his son's\ngun, which was found lying on the grass within a few paces of the\nbody. Under these circumstances the young man was instantly\narrested, and a verdict of 'wilful murder' having been returned\nat the inquest on Tuesday, he was on Wednesday brought before the\nmagistrates at Ross, who have referred the case to the next\nAssizes. Those are the main facts of the case as they came out\nbefore the coroner and the police-court.\"\n\n\"I could hardly imagine a more damning case,\" I remarked. \"If\never circumstantial evidence pointed to a criminal it does so\nhere.\"\n\n\"Circumstantial evidence is a very tricky thing,\" answered Holmes\nthoughtfully. \"It may seem to point very straight to one thing,\nbut if you shift your own point of view a little, you may find it\npointing in an equally uncompromising manner to something\nentirely different. It must be confessed, however, that the case\nlooks exceedingly grave against the young man, and it is very\npossible that he is indeed the culprit. There are several people\nin the neighbourhood, however, and among them Miss Turner, the\ndaughter of the neighbouring landowner, who believe in his\ninnocence, and who have retained Lestrade, whom you may recollect\nin connection with the Study in Scarlet, to work out the case in\nhis interest. Lestrade, being rather puzzled, has referred the\ncase to me, and hence it is that two middle-aged gentlemen are\nflying westward at fifty miles an hour instead of quietly\ndigesting their breakfasts at home.\"\n\n\"I am afraid,\" said I, \"that the facts are so obvious that you\nwill find little credit to be gained out of this case.\"\n\n\"There is nothing more deceptive than an obvious fact,\" he\nanswered, laughing. \"Besides, we may chance to hit upon some\nother obvious facts which may have been by no means obvious to\nMr. Lestrade. You know me too well to think that I am boasting\nwhen I say that I shall either confirm or destroy his theory by\nmeans which he is quite incapable of employing, or even of\nunderstanding. To take the first example to hand, I very clearly\nperceive that in your bedroom the window is upon the right-hand\nside, and yet I question whether Mr. Lestrade would have noted\neven so self-evident a thing as that.\"\n\n\"How on earth--\"\n\n\"My dear fellow, I know you well. I know the military neatness\nwhich characterises you. You shave every morning, and in this\nseason you shave by the sunlight; but since your shaving is less\nand less complete as we get farther back on the left side, until\nit becomes positively slovenly as we get round the angle of the\njaw, it is surely very clear that that side is less illuminated\nthan the other. I could not imagine a man of your habits looking\nat himself in an equal light and being satisfied with such a\nresult. I only quote this as a trivial example of observation and\ninference. Therein lies my métier, and it is just possible that\nit may be of some service in the investigation which lies before\nus. There are one or two minor points which were brought out in\nthe inquest, and which are worth considering.\"\n\n\"What are they?\"\n\n\"It appears that his arrest did not take place at once, but after\nthe return to Hatherley Farm. On the inspector of constabulary\ninforming him that he was a prisoner, he remarked that he was not\nsurprised to hear it, and that it was no more than his deserts.\nThis observation of his had the natural effect of removing any\ntraces of doubt which might have remained in the minds of the\ncoroner's jury.\"\n\n\"It was a confession,\" I ejaculated.\n\n\"No, for it was followed by a protestation of innocence.\"\n\n\"Coming on the top of such a damning series of events, it was at\nleast a most suspicious remark.\"\n\n\"On the contrary,\" said Holmes, \"it is the brightest rift which I\ncan at present see in the clouds. However innocent he might be,\nhe could not be such an absolute imbecile as not to see that the\ncircumstances were very black against him. Had he appeared\nsurprised at his own arrest, or feigned indignation at it, I\nshould have looked upon it as highly suspicious, because such\nsurprise or anger would not be natural under the circumstances,\nand yet might appear to be the best policy to a scheming man. His\nfrank acceptance of the situation marks him as either an innocent\nman, or else as a man of considerable self-restraint and\nfirmness. As to his remark about his deserts, it was also not\nunnatural if you consider that he stood beside the dead body of\nhis father, and that there is no doubt that he had that very day\nso far forgotten his filial duty as to bandy words with him, and\neven, according to the little girl whose evidence is so\nimportant, to raise his hand as if to strike him. The\nself-reproach and contrition which are displayed in his remark\nappear to me to be the signs of a healthy mind rather than of a\nguilty one.\"\n\nI shook my head. \"Many men have been hanged on far slighter\nevidence,\" I remarked.\n\n\"So they have. And many men have been wrongfully hanged.\"\n\n\"What is the young man's own account of the matter?\"\n\n\"It is, I am afraid, not very encouraging to his supporters,\nthough there are one or two points in it which are suggestive.\nYou will find it here, and may read it for yourself.\"\n\nHe picked out from his bundle a copy of the local Herefordshire\npaper, and having turned down the sheet he pointed out the\nparagraph in which the unfortunate young man had given his own\nstatement of what had occurred. I settled myself down in the\ncorner of the carriage and read it very carefully. It ran in this\nway:\n\n\"Mr. James McCarthy, the only son of the deceased, was then called\nand gave evidence as follows: 'I had been away from home for\nthree days at Bristol, and had only just returned upon the\nmorning of last Monday, the 3rd. My father was absent from home at\nthe time of my arrival, and I was informed by the maid that he\nhad driven over to Ross with John Cobb, the groom. Shortly after\nmy return I heard the wheels of his trap in the yard, and,\nlooking out of my window, I saw him get out and walk rapidly out\nof the yard, though I was not aware in which direction he was\ngoing. I then took my gun and strolled out in the direction of\nthe Boscombe Pool, with the intention of visiting the rabbit\nwarren which is upon the other side. On my way I saw William\nCrowder, the game-keeper, as he had stated in his evidence; but\nhe is mistaken in thinking that I was following my father. I had\nno idea that he was in front of me. When about a hundred yards\nfrom the pool I heard a cry of \"Cooee!\" which was a usual signal\nbetween my father and myself. I then hurried forward, and found\nhim standing by the pool. He appeared to be much surprised at\nseeing me and asked me rather roughly what I was doing there. A\nconversation ensued which led to high words and almost to blows,\nfor my father was a man of a very violent temper. Seeing that his\npassion was becoming ungovernable, I left him and returned\ntowards Hatherley Farm. I had not gone more than 150 yards,\nhowever, when I heard a hideous outcry behind me, which caused me\nto run back again. I found my father expiring upon the ground,\nwith his head terribly injured. I dropped my gun and held him in\nmy arms, but he almost instantly expired. I knelt beside him for\nsome minutes, and then made my way to Mr. Turner's lodge-keeper,\nhis house being the nearest, to ask for assistance. I saw no one\nnear my father when I returned, and I have no idea how he came by\nhis injuries. He was not a popular man, being somewhat cold and\nforbidding in his manners, but he had, as far as I know, no\nactive enemies. I know nothing further of the matter.'\n\n\"The Coroner: Did your father make any statement to you before\nhe died?\n\n\"Witness: He mumbled a few words, but I could only catch some\nallusion to a rat.\n\n\"The Coroner: What did you understand by that?\n\n\"Witness: It conveyed no meaning to me. I thought that he was\ndelirious.\n\n\"The Coroner: What was the point upon which you and your father\nhad this final quarrel?\n\n\"Witness: I should prefer not to answer.\n\n\"The Coroner: I am afraid that I must press it.\n\n\"Witness: It is really impossible for me to tell you. I can\nassure you that it has nothing to do with the sad tragedy which\nfollowed.\n\n\"The Coroner: That is for the court to decide. I need not point\nout to you that your refusal to answer will prejudice your case\nconsiderably in any future proceedings which may arise.\n\n\"Witness: I must still refuse.\n\n\"The Coroner: I understand that the cry of 'Cooee' was a common\nsignal between you and your father?\n\n\"Witness: It was.\n\n\"The Coroner: How was it, then, that he uttered it before he saw\nyou, and before he even knew that you had returned from Bristol?\n\n\"Witness (with considerable confusion): I do not know.\n\n\"A Juryman: Did you see nothing which aroused your suspicions\nwhen you returned on hearing the cry and found your father\nfatally injured?\n\n\"Witness: Nothing definite.\n\n\"The Coroner: What do you mean?\n\n\"Witness: I was so disturbed and excited as I rushed out into\nthe open, that I could think of nothing except of my father. Yet\nI have a vague impression that as I ran forward something lay\nupon the ground to the left of me. It seemed to me to be\nsomething grey in colour, a coat of some sort, or a plaid perhaps.\nWhen I rose from my father I looked round for it, but it was\ngone.\n\n\"'Do you mean that it disappeared before you went for help?'\n\n\"'Yes, it was gone.'\n\n\"'You cannot say what it was?'\n\n\"'No, I had a feeling something was there.'\n\n\"'How far from the body?'\n\n\"'A dozen yards or so.'\n\n\"'And how far from the edge of the wood?'\n\n\"'About the same.'\n\n\"'Then if it was removed it was while you were within a dozen\nyards of it?'\n\n\"'Yes, but with my back towards it.'\n\n\"This concluded the examination of the witness.\"\n\n\"I see,\" said I as I glanced down the column, \"that the coroner\nin his concluding remarks was rather severe upon young McCarthy.\nHe calls attention, and with reason, to the discrepancy about his\nfather having signalled to him before seeing him, also to his\nrefusal to give details of his conversation with his father, and\nhis singular account of his father's dying words. They are all,\nas he remarks, very much against the son.\"\n\nHolmes laughed softly to himself and stretched himself out upon\nthe cushioned seat. \"Both you and the coroner have been at some\npains,\" said he, \"to single out the very strongest points in the\nyoung man's favour. Don't you see that you alternately give him\ncredit for having too much imagination and too little? Too\nlittle, if he could not invent a cause of quarrel which would\ngive him the sympathy of the jury; too much, if he evolved from\nhis own inner consciousness anything so outré as a dying\nreference to a rat, and the incident of the vanishing cloth. No,\nsir, I shall approach this case from the point of view that what\nthis young man says is true, and we shall see whither that\nhypothesis will lead us. And now here is my pocket Petrarch, and\nnot another word shall I say of this case until we are on the\nscene of action. We lunch at Swindon, and I see that we shall be\nthere in twenty minutes.\"\n\nIt was nearly four o'clock when we at last, after passing through\nthe beautiful Stroud Valley, and over the broad gleaming Severn,\nfound ourselves at the pretty little country-town of Ross. A\nlean, ferret-like man, furtive and sly-looking, was waiting for\nus upon the platform. In spite of the light brown dustcoat and\nleather-leggings which he wore in deference to his rustic\nsurroundings, I had no difficulty in recognising Lestrade, of\nScotland Yard. With him we drove to the Hereford Arms where a\nroom had already been engaged for us.\n\n\"I have ordered a carriage,\" said Lestrade as we sat over a cup\nof tea. \"I knew your energetic nature, and that you would not be\nhappy until you had been on the scene of the crime.\"\n\n\"It was very nice and complimentary of you,\" Holmes answered. \"It\nis entirely a question of barometric pressure.\"\n\nLestrade looked startled. \"I do not quite follow,\" he said.\n\n\"How is the glass? Twenty-nine, I see. No wind, and not a cloud\nin the sky. I have a caseful of cigarettes here which need\nsmoking, and the sofa is very much superior to the usual country\nhotel abomination. I do not think that it is probable that I\nshall use the carriage to-night.\"\n\nLestrade laughed indulgently. \"You have, no doubt, already formed\nyour conclusions from the newspapers,\" he said. \"The case is as\nplain as a pikestaff, and the more one goes into it the plainer\nit becomes. Still, of course, one can't refuse a lady, and such a\nvery positive one, too. She has heard of you, and would have your\nopinion, though I repeatedly told her that there was nothing\nwhich you could do which I had not already done. Why, bless my\nsoul! here is her carriage at the door.\"\n\nHe had hardly spoken before there rushed into the room one of the\nmost lovely young women that I have ever seen in my life. Her\nviolet eyes shining, her lips parted, a pink flush upon her\ncheeks, all thought of her natural reserve lost in her\noverpowering excitement and concern.\n\n\"Oh, Mr. Sherlock Holmes!\" she cried, glancing from one to the\nother of us, and finally, with a woman's quick intuition,\nfastening upon my companion, \"I am so glad that you have come. I\nhave driven down to tell you so. I know that James didn't do it.\nI know it, and I want you to start upon your work knowing it,\ntoo. Never let yourself doubt upon that point. We have known each\nother since we were little children, and I know his faults as no\none else does; but he is too tender-hearted to hurt a fly. Such a\ncharge is absurd to anyone who really knows him.\"\n\n\"I hope we may clear him, Miss Turner,\" said Sherlock Holmes.\n\"You may rely upon my doing all that I can.\"\n\n\"But you have read the evidence. You have formed some conclusion?\nDo you not see some loophole, some flaw? Do you not yourself\nthink that he is innocent?\"\n\n\"I think that it is very probable.\"\n\n\"There, now!\" she cried, throwing back her head and looking\ndefiantly at Lestrade. \"You hear! He gives me hopes.\"\n\nLestrade shrugged his shoulders. \"I am afraid that my colleague\nhas been a little quick in forming his conclusions,\" he said.\n\n\"But he is right. Oh! I know that he is right. James never did\nit. And about his quarrel with his father, I am sure that the\nreason why he would not speak about it to the coroner was because\nI was concerned in it.\"\n\n\"In what way?\" asked Holmes.\n\n\"It is no time for me to hide anything. James and his father had\nmany disagreements about me. Mr. McCarthy was very anxious that\nthere should be a marriage between us. James and I have always\nloved each other as brother and sister; but of course he is young\nand has seen very little of life yet, and--and--well, he\nnaturally did not wish to do anything like that yet. So there\nwere quarrels, and this, I am sure, was one of them.\"\n\n\"And your father?\" asked Holmes. \"Was he in favour of such a\nunion?\"\n\n\"No, he was averse to it also. No one but Mr. McCarthy was in\nfavour of it.\" A quick blush passed over her fresh young face as\nHolmes shot one of his keen, questioning glances at her.\n\n\"Thank you for this information,\" said he. \"May I see your father\nif I call to-morrow?\"\n\n\"I am afraid the doctor won't allow it.\"\n\n\"The doctor?\"\n\n\"Yes, have you not heard? Poor father has never been strong for\nyears back, but this has broken him down completely. He has taken\nto his bed, and Dr. Willows says that he is a wreck and that his\nnervous system is shattered. Mr. McCarthy was the only man alive\nwho had known dad in the old days in Victoria.\"\n\n\"Ha! In Victoria! That is important.\"\n\n\"Yes, at the mines.\"\n\n\"Quite so; at the gold-mines, where, as I understand, Mr. Turner\nmade his money.\"\n\n\"Yes, certainly.\"\n\n\"Thank you, Miss Turner. You have been of material assistance to\nme.\"\n\n\"You will tell me if you have any news to-morrow. No doubt you\nwill go to the prison to see James. Oh, if you do, Mr. Holmes, do\ntell him that I know him to be innocent.\"\n\n\"I will, Miss Turner.\"\n\n\"I must go home now, for dad is very ill, and he misses me so if\nI leave him. Good-bye, and God help you in your undertaking.\" She\nhurried from the room as impulsively as she had entered, and we\nheard the wheels of her carriage rattle off down the street.\n\n\"I am ashamed of you, Holmes,\" said Lestrade with dignity after a\nfew minutes' silence. \"Why should you raise up hopes which you\nare bound to disappoint? I am not over-tender of heart, but I\ncall it cruel.\"\n\n\"I think that I see my way to clearing James McCarthy,\" said\nHolmes. \"Have you an order to see him in prison?\"\n\n\"Yes, but only for you and me.\"\n\n\"Then I shall reconsider my resolution about going out. We have\nstill time to take a train to Hereford and see him to-night?\"\n\n\"Ample.\"\n\n\"Then let us do so. Watson, I fear that you will find it very\nslow, but I shall only be away a couple of hours.\"\n\nI walked down to the station with them, and then wandered through\nthe streets of the little town, finally returning to the hotel,\nwhere I lay upon the sofa and tried to interest myself in a\nyellow-backed novel. The puny plot of the story was so thin,\nhowever, when compared to the deep mystery through which we were\ngroping, and I found my attention wander so continually from the\naction to the fact, that I at last flung it across the room and\ngave myself up entirely to a consideration of the events of the\nday. Supposing that this unhappy young man's story were\nabsolutely true, then what hellish thing, what absolutely\nunforeseen and extraordinary calamity could have occurred between\nthe time when he parted from his father, and the moment when,\ndrawn back by his screams, he rushed into the glade? It was\nsomething terrible and deadly. What could it be? Might not the\nnature of the injuries reveal something to my medical instincts?\nI rang the bell and called for the weekly county paper, which\ncontained a verbatim account of the inquest. In the surgeon's\ndeposition it was stated that the posterior third of the left\nparietal bone and the left half of the occipital bone had been\nshattered by a heavy blow from a blunt weapon. I marked the spot\nupon my own head. Clearly such a blow must have been struck from\nbehind. That was to some extent in favour of the accused, as when\nseen quarrelling he was face to face with his father. Still, it\ndid not go for very much, for the older man might have turned his\nback before the blow fell. Still, it might be worth while to call\nHolmes' attention to it. Then there was the peculiar dying\nreference to a rat. What could that mean? It could not be\ndelirium. A man dying from a sudden blow does not commonly become\ndelirious. No, it was more likely to be an attempt to explain how\nhe met his fate. But what could it indicate? I cudgelled my\nbrains to find some possible explanation. And then the incident\nof the grey cloth seen by young McCarthy. If that were true the\nmurderer must have dropped some part of his dress, presumably his\novercoat, in his flight, and must have had the hardihood to\nreturn and to carry it away at the instant when the son was\nkneeling with his back turned not a dozen paces off. What a\ntissue of mysteries and improbabilities the whole thing was! I\ndid not wonder at Lestrade's opinion, and yet I had so much faith\nin Sherlock Holmes' insight that I could not lose hope as long\nas every fresh fact seemed to strengthen his conviction of young\nMcCarthy's innocence.\n\nIt was late before Sherlock Holmes returned. He came back alone,\nfor Lestrade was staying in lodgings in the town.\n\n\"The glass still keeps very high,\" he remarked as he sat down.\n\"It is of importance that it should not rain before we are able\nto go over the ground. On the other hand, a man should be at his\nvery best and keenest for such nice work as that, and I did not\nwish to do it when fagged by a long journey. I have seen young\nMcCarthy.\"\n\n\"And what did you learn from him?\"\n\n\"Nothing.\"\n\n\"Could he throw no light?\"\n\n\"None at all. I was inclined to think at one time that he knew\nwho had done it and was screening him or her, but I am convinced\nnow that he is as puzzled as everyone else. He is not a very\nquick-witted youth, though comely to look at and, I should think,\nsound at heart.\"\n\n\"I cannot admire his taste,\" I remarked, \"if it is indeed a fact\nthat he was averse to a marriage with so charming a young lady as\nthis Miss Turner.\"\n\n\"Ah, thereby hangs a rather painful tale. This fellow is madly,\ninsanely, in love with her, but some two years ago, when he was\nonly a lad, and before he really knew her, for she had been away\nfive years at a boarding-school, what does the idiot do but get\ninto the clutches of a barmaid in Bristol and marry her at a\nregistry office? No one knows a word of the matter, but you can\nimagine how maddening it must be to him to be upbraided for not\ndoing what he would give his very eyes to do, but what he knows\nto be absolutely impossible. It was sheer frenzy of this sort\nwhich made him throw his hands up into the air when his father,\nat their last interview, was goading him on to propose to Miss\nTurner. On the other hand, he had no means of supporting himself,\nand his father, who was by all accounts a very hard man, would\nhave thrown him over utterly had he known the truth. It was with\nhis barmaid wife that he had spent the last three days in\nBristol, and his father did not know where he was. Mark that\npoint. It is of importance. Good has come out of evil, however,\nfor the barmaid, finding from the papers that he is in serious\ntrouble and likely to be hanged, has thrown him over utterly and\nhas written to him to say that she has a husband already in the\nBermuda Dockyard, so that there is really no tie between them. I\nthink that that bit of news has consoled young McCarthy for all\nthat he has suffered.\"\n\n\"But if he is innocent, who has done it?\"\n\n\"Ah! who? I would call your attention very particularly to two\npoints. One is that the murdered man had an appointment with\nsomeone at the pool, and that the someone could not have been his\nson, for his son was away, and he did not know when he would\nreturn. The second is that the murdered man was heard to cry\n'Cooee!' before he knew that his son had returned. Those are the\ncrucial points upon which the case depends. And now let us talk\nabout George Meredith, if you please, and we shall leave all\nminor matters until to-morrow.\"\n\nThere was no rain, as Holmes had foretold, and the morning broke\nbright and cloudless. At nine o'clock Lestrade called for us with\nthe carriage, and we set off for Hatherley Farm and the Boscombe\nPool.\n\n\"There is serious news this morning,\" Lestrade observed. \"It is\nsaid that Mr. Turner, of the Hall, is so ill that his life is\ndespaired of.\"\n\n\"An elderly man, I presume?\" said Holmes.\n\n\"About sixty; but his constitution has been shattered by his life\nabroad, and he has been in failing health for some time. This\nbusiness has had a very bad effect upon him. He was an old friend\nof McCarthy's, and, I may add, a great benefactor to him, for I\nhave learned that he gave him Hatherley Farm rent free.\"\n\n\"Indeed! That is interesting,\" said Holmes.\n\n\"Oh, yes! In a hundred other ways he has helped him. Everybody\nabout here speaks of his kindness to him.\"\n\n\"Really! Does it not strike you as a little singular that this\nMcCarthy, who appears to have had little of his own, and to have\nbeen under such obligations to Turner, should still talk of\nmarrying his son to Turner's daughter, who is, presumably,\nheiress to the estate, and that in such a very cocksure manner,\nas if it were merely a case of a proposal and all else would\nfollow? It is the more strange, since we know that Turner himself\nwas averse to the idea. The daughter told us as much. Do you not\ndeduce something from that?\"\n\n\"We have got to the deductions and the inferences,\" said\nLestrade, winking at me. \"I find it hard enough to tackle facts,\nHolmes, without flying away after theories and fancies.\"\n\n\"You are right,\" said Holmes demurely; \"you do find it very hard\nto tackle the facts.\"\n\n\"Anyhow, I have grasped one fact which you seem to find it\ndifficult to get hold of,\" replied Lestrade with some warmth.\n\n\"And that is--\"\n\n\"That McCarthy senior met his death from McCarthy junior and that\nall theories to the contrary are the merest moonshine.\"\n\n\"Well, moonshine is a brighter thing than fog,\" said Holmes,\nlaughing. \"But I am very much mistaken if this is not Hatherley\nFarm upon the left.\"\n\n\"Yes, that is it.\" It was a widespread, comfortable-looking\nbuilding, two-storied, slate-roofed, with great yellow blotches\nof lichen upon the grey walls. The drawn blinds and the smokeless\nchimneys, however, gave it a stricken look, as though the weight\nof this horror still lay heavy upon it. We called at the door,\nwhen the maid, at Holmes' request, showed us the boots which her\nmaster wore at the time of his death, and also a pair of the\nson's, though not the pair which he had then had. Having measured\nthese very carefully from seven or eight different points, Holmes\ndesired to be led to the court-yard, from which we all followed\nthe winding track which led to Boscombe Pool.\n\nSherlock Holmes was transformed when he was hot upon such a scent\nas this. Men who had only known the quiet thinker and logician of\nBaker Street would have failed to recognise him. His face flushed\nand darkened. His brows were drawn into two hard black lines,\nwhile his eyes shone out from beneath them with a steely glitter.\nHis face was bent downward, his shoulders bowed, his lips\ncompressed, and the veins stood out like whipcord in his long,\nsinewy neck. His nostrils seemed to dilate with a purely animal\nlust for the chase, and his mind was so absolutely concentrated\nupon the matter before him that a question or remark fell\nunheeded upon his ears, or, at the most, only provoked a quick,\nimpatient snarl in reply. Swiftly and silently he made his way\nalong the track which ran through the meadows, and so by way of\nthe woods to the Boscombe Pool. It was damp, marshy ground, as is\nall that district, and there were marks of many feet, both upon\nthe path and amid the short grass which bounded it on either\nside. Sometimes Holmes would hurry on, sometimes stop dead, and\nonce he made quite a little detour into the meadow. Lestrade and\nI walked behind him, the detective indifferent and contemptuous,\nwhile I watched my friend with the interest which sprang from the\nconviction that every one of his actions was directed towards a\ndefinite end.\n\nThe Boscombe Pool, which is a little reed-girt sheet of water\nsome fifty yards across, is situated at the boundary between the\nHatherley Farm and the private park of the wealthy Mr. Turner.\nAbove the woods which lined it upon the farther side we could see\nthe red, jutting pinnacles which marked the site of the rich\nlandowner's dwelling. On the Hatherley side of the pool the woods\ngrew very thick, and there was a narrow belt of sodden grass\ntwenty paces across between the edge of the trees and the reeds\nwhich lined the lake. Lestrade showed us the exact spot at which\nthe body had been found, and, indeed, so moist was the ground,\nthat I could plainly see the traces which had been left by the\nfall of the stricken man. To Holmes, as I could see by his eager\nface and peering eyes, very many other things were to be read\nupon the trampled grass. He ran round, like a dog who is picking\nup a scent, and then turned upon my companion.\n\n\"What did you go into the pool for?\" he asked.\n\n\"I fished about with a rake. I thought there might be some weapon\nor other trace. But how on earth--\"\n\n\"Oh, tut, tut! I have no time! That left foot of yours with its\ninward twist is all over the place. A mole could trace it, and\nthere it vanishes among the reeds. Oh, how simple it would all\nhave been had I been here before they came like a herd of buffalo\nand wallowed all over it. Here is where the party with the\nlodge-keeper came, and they have covered all tracks for six or\neight feet round the body. But here are three separate tracks of\nthe same feet.\" He drew out a lens and lay down upon his\nwaterproof to have a better view, talking all the time rather to\nhimself than to us. \"These are young McCarthy's feet. Twice he\nwas walking, and once he ran swiftly, so that the soles are\ndeeply marked and the heels hardly visible. That bears out his\nstory. He ran when he saw his father on the ground. Then here are\nthe father's feet as he paced up and down. What is this, then? It\nis the butt-end of the gun as the son stood listening. And this?\nHa, ha! What have we here? Tiptoes! tiptoes! Square, too, quite\nunusual boots! They come, they go, they come again--of course\nthat was for the cloak. Now where did they come from?\" He ran up\nand down, sometimes losing, sometimes finding the track until we\nwere well within the edge of the wood and under the shadow of a\ngreat beech, the largest tree in the neighbourhood. Holmes traced\nhis way to the farther side of this and lay down once more upon\nhis face with a little cry of satisfaction. For a long time he\nremained there, turning over the leaves and dried sticks,\ngathering up what seemed to me to be dust into an envelope and\nexamining with his lens not only the ground but even the bark of\nthe tree as far as he could reach. A jagged stone was lying among\nthe moss, and this also he carefully examined and retained. Then\nhe followed a pathway through the wood until he came to the\nhighroad, where all traces were lost.\n\n\"It has been a case of considerable interest,\" he remarked,\nreturning to his natural manner. \"I fancy that this grey house on\nthe right must be the lodge. I think that I will go in and have a\nword with Moran, and perhaps write a little note. Having done\nthat, we may drive back to our luncheon. You may walk to the cab,\nand I shall be with you presently.\"\n\nIt was about ten minutes before we regained our cab and drove\nback into Ross, Holmes still carrying with him the stone which he\nhad picked up in the wood.\n\n\"This may interest you, Lestrade,\" he remarked, holding it out.\n\"The murder was done with it.\"\n\n\"I see no marks.\"\n\n\"There are none.\"\n\n\"How do you know, then?\"\n\n\"The grass was growing under it. It had only lain there a few\ndays. There was no sign of a place whence it had been taken. It\ncorresponds with the injuries. There is no sign of any other\nweapon.\"\n\n\"And the murderer?\"\n\n\"Is a tall man, left-handed, limps with the right leg, wears\nthick-soled shooting-boots and a grey cloak, smokes Indian\ncigars, uses a cigar-holder, and carries a blunt pen-knife in his\npocket. There are several other indications, but these may be\nenough to aid us in our search.\"\n\nLestrade laughed. \"I am afraid that I am still a sceptic,\" he\nsaid. \"Theories are all very well, but we have to deal with a\nhard-headed British jury.\"\n\n\"Nous verrons,\" answered Holmes calmly. \"You work your own\nmethod, and I shall work mine. I shall be busy this afternoon,\nand shall probably return to London by the evening train.\"\n\n\"And leave your case unfinished?\"\n\n\"No, finished.\"\n\n\"But the mystery?\"\n\n\"It is solved.\"\n\n\"Who was the criminal, then?\"\n\n\"The gentleman I describe.\"\n\n\"But who is he?\"\n\n\"Surely it would not be difficult to find out. This is not such a\npopulous neighbourhood.\"\n\nLestrade shrugged his shoulders. \"I am a practical man,\" he said,\n\"and I really cannot undertake to go about the country looking\nfor a left-handed gentleman with a game leg. I should become the\nlaughing-stock of Scotland Yard.\"\n\n\"All right,\" said Holmes quietly. \"I have given you the chance.\nHere are your lodgings. Good-bye. I shall drop you a line before\nI leave.\"\n\nHaving left Lestrade at his rooms, we drove to our hotel, where\nwe found lunch upon the table. Holmes was silent and buried in\nthought with a pained expression upon his face, as one who finds\nhimself in a perplexing position.\n\n\"Look here, Watson,\" he said when the cloth was cleared \"just sit\ndown in this chair and let me preach to you for a little. I don't\nknow quite what to do, and I should value your advice. Light a\ncigar and let me expound.\"\n\n \"Pray do so.\"\n\n\"Well, now, in considering this case there are two points about\nyoung McCarthy's narrative which struck us both instantly,\nalthough they impressed me in his favour and you against him. One\nwas the fact that his father should, according to his account,\ncry 'Cooee!' before seeing him. The other was his singular dying\nreference to a rat. He mumbled several words, you understand, but\nthat was all that caught the son's ear. Now from this double\npoint our research must commence, and we will begin it by\npresuming that what the lad says is absolutely true.\"\n\n\"What of this 'Cooee!' then?\"\n\n\"Well, obviously it could not have been meant for the son. The\nson, as far as he knew, was in Bristol. It was mere chance that\nhe was within earshot. The 'Cooee!' was meant to attract the\nattention of whoever it was that he had the appointment with. But\n'Cooee' is a distinctly Australian cry, and one which is used\nbetween Australians. There is a strong presumption that the\nperson whom McCarthy expected to meet him at Boscombe Pool was\nsomeone who had been in Australia.\"\n\n\"What of the rat, then?\"\n\nSherlock Holmes took a folded paper from his pocket and flattened\nit out on the table. \"This is a map of the Colony of Victoria,\"\nhe said. \"I wired to Bristol for it last night.\" He put his hand\nover part of the map. \"What do you read?\"\n\n\"ARAT,\" I read.\n\n\"And now?\" He raised his hand.\n\n\"BALLARAT.\"\n\n\"Quite so. That was the word the man uttered, and of which his\nson only caught the last two syllables. He was trying to utter\nthe name of his murderer. So and so, of Ballarat.\"\n\n\"It is wonderful!\" I exclaimed.\n\n\"It is obvious. And now, you see, I had narrowed the field down\nconsiderably. The possession of a grey garment was a third point\nwhich, granting the son's statement to be correct, was a\ncertainty. We have come now out of mere vagueness to the definite\nconception of an Australian from Ballarat with a grey cloak.\"\n\n\"Certainly.\"\n\n\"And one who was at home in the district, for the pool can only\nbe approached by the farm or by the estate, where strangers could\nhardly wander.\"\n\n\"Quite so.\"\n\n\"Then comes our expedition of to-day. By an examination of the\nground I gained the trifling details which I gave to that\nimbecile Lestrade, as to the personality of the criminal.\"\n\n\"But how did you gain them?\"\n\n\"You know my method. It is founded upon the observation of\ntrifles.\"\n\n\"His height I know that you might roughly judge from the length\nof his stride. His boots, too, might be told from their traces.\"\n\n\"Yes, they were peculiar boots.\"\n\n\"But his lameness?\"\n\n\"The impression of his right foot was always less distinct than\nhis left. He put less weight upon it. Why? Because he limped--he\nwas lame.\"\n\n\"But his left-handedness.\"\n\n\"You were yourself struck by the nature of the injury as recorded\nby the surgeon at the inquest. The blow was struck from\nimmediately behind, and yet was upon the left side. Now, how can\nthat be unless it were by a left-handed man? He had stood behind\nthat tree during the interview between the father and son. He had\neven smoked there. I found the ash of a cigar, which my special\nknowledge of tobacco ashes enables me to pronounce as an Indian\ncigar. I have, as you know, devoted some attention to this, and\nwritten a little monograph on the ashes of 140 different\nvarieties of pipe, cigar, and cigarette tobacco. Having found the\nash, I then looked round and discovered the stump among the moss\nwhere he had tossed it. It was an Indian cigar, of the variety\nwhich are rolled in Rotterdam.\"\n\n\"And the cigar-holder?\"\n\n\"I could see that the end had not been in his mouth. Therefore he\nused a holder. The tip had been cut off, not bitten off, but the\ncut was not a clean one, so I deduced a blunt pen-knife.\"\n\n\"Holmes,\" I said, \"you have drawn a net round this man from which\nhe cannot escape, and you have saved an innocent human life as\ntruly as if you had cut the cord which was hanging him. I see the\ndirection in which all this points. The culprit is--\"\n\n\"Mr. John Turner,\" cried the hotel waiter, opening the door of\nour sitting-room, and ushering in a visitor.\n\nThe man who entered was a strange and impressive figure. His\nslow, limping step and bowed shoulders gave the appearance of\ndecrepitude, and yet his hard, deep-lined, craggy features, and\nhis enormous limbs showed that he was possessed of unusual\nstrength of body and of character. His tangled beard, grizzled\nhair, and outstanding, drooping eyebrows combined to give an air\nof dignity and power to his appearance, but his face was of an\nashen white, while his lips and the corners of his nostrils were\ntinged with a shade of blue. It was clear to me at a glance that\nhe was in the grip of some deadly and chronic disease.\n\n\"Pray sit down on the sofa,\" said Holmes gently. \"You had my\nnote?\"\n\n\"Yes, the lodge-keeper brought it up. You said that you wished to\nsee me here to avoid scandal.\"\n\n\"I thought people would talk if I went to the Hall.\"\n\n\"And why did you wish to see me?\" He looked across at my\ncompanion with despair in his weary eyes, as though his question\nwas already answered.\n\n\"Yes,\" said Holmes, answering the look rather than the words. \"It\nis so. I know all about McCarthy.\"\n\nThe old man sank his face in his hands. \"God help me!\" he cried.\n\"But I would not have let the young man come to harm. I give you\nmy word that I would have spoken out if it went against him at\nthe Assizes.\"\n\n\"I am glad to hear you say so,\" said Holmes gravely.\n\n\"I would have spoken now had it not been for my dear girl. It\nwould break her heart--it will break her heart when she hears\nthat I am arrested.\"\n\n\"It may not come to that,\" said Holmes.\n\n\"What?\"\n\n\"I am no official agent. I understand that it was your daughter\nwho required my presence here, and I am acting in her interests.\nYoung McCarthy must be got off, however.\"\n\n\"I am a dying man,\" said old Turner. \"I have had diabetes for\nyears. My doctor says it is a question whether I shall live a\nmonth. Yet I would rather die under my own roof than in a gaol.\"\n\nHolmes rose and sat down at the table with his pen in his hand\nand a bundle of paper before him. \"Just tell us the truth,\" he\nsaid. \"I shall jot down the facts. You will sign it, and Watson\nhere can witness it. Then I could produce your confession at the\nlast extremity to save young McCarthy. I promise you that I shall\nnot use it unless it is absolutely needed.\"\n\n\"It's as well,\" said the old man; \"it's a question whether I\nshall live to the Assizes, so it matters little to me, but I\nshould wish to spare Alice the shock. And now I will make the\nthing clear to you; it has been a long time in the acting, but\nwill not take me long to tell.\n\n\"You didn't know this dead man, McCarthy. He was a devil\nincarnate. I tell you that. God keep you out of the clutches of\nsuch a man as he. His grip has been upon me these twenty years,\nand he has blasted my life. I'll tell you first how I came to be\nin his power.\n\n\"It was in the early '60's at the diggings. I was a young chap\nthen, hot-blooded and reckless, ready to turn my hand at\nanything; I got among bad companions, took to drink, had no luck\nwith my claim, took to the bush, and in a word became what you\nwould call over here a highway robber. There were six of us, and\nwe had a wild, free life of it, sticking up a station from time\nto time, or stopping the wagons on the road to the diggings.\nBlack Jack of Ballarat was the name I went under, and our party\nis still remembered in the colony as the Ballarat Gang.\n\n\"One day a gold convoy came down from Ballarat to Melbourne, and\nwe lay in wait for it and attacked it. There were six troopers\nand six of us, so it was a close thing, but we emptied four of\ntheir saddles at the first volley. Three of our boys were killed,\nhowever, before we got the swag. I put my pistol to the head of\nthe wagon-driver, who was this very man McCarthy. I wish to the\nLord that I had shot him then, but I spared him, though I saw his\nwicked little eyes fixed on my face, as though to remember every\nfeature. We got away with the gold, became wealthy men, and made\nour way over to England without being suspected. There I parted\nfrom my old pals and determined to settle down to a quiet and\nrespectable life. I bought this estate, which chanced to be in\nthe market, and I set myself to do a little good with my money,\nto make up for the way in which I had earned it. I married, too,\nand though my wife died young she left me my dear little Alice.\nEven when she was just a baby her wee hand seemed to lead me down\nthe right path as nothing else had ever done. In a word, I turned\nover a new leaf and did my best to make up for the past. All was\ngoing well when McCarthy laid his grip upon me.\n\n\"I had gone up to town about an investment, and I met him in\nRegent Street with hardly a coat to his back or a boot to his\nfoot.\n\n\"'Here we are, Jack,' says he, touching me on the arm; 'we'll be\nas good as a family to you. There's two of us, me and my son, and\nyou can have the keeping of us. If you don't--it's a fine,\nlaw-abiding country is England, and there's always a policeman\nwithin hail.'\n\n\"Well, down they came to the west country, there was no shaking\nthem off, and there they have lived rent free on my best land\never since. There was no rest for me, no peace, no forgetfulness;\nturn where I would, there was his cunning, grinning face at my\nelbow. It grew worse as Alice grew up, for he soon saw I was more\nafraid of her knowing my past than of the police. Whatever he\nwanted he must have, and whatever it was I gave him without\nquestion, land, money, houses, until at last he asked a thing\nwhich I could not give. He asked for Alice.\n\n\"His son, you see, had grown up, and so had my girl, and as I was\nknown to be in weak health, it seemed a fine stroke to him that\nhis lad should step into the whole property. But there I was\nfirm. I would not have his cursed stock mixed with mine; not that\nI had any dislike to the lad, but his blood was in him, and that\nwas enough. I stood firm. McCarthy threatened. I braved him to do\nhis worst. We were to meet at the pool midway between our houses\nto talk it over.\n\n\"When I went down there I found him talking with his son, so I\nsmoked a cigar and waited behind a tree until he should be alone.\nBut as I listened to his talk all that was black and bitter in\nme seemed to come uppermost. He was urging his son to marry my\ndaughter with as little regard for what she might think as if she\nwere a slut from off the streets. It drove me mad to think that I\nand all that I held most dear should be in the power of such a\nman as this. Could I not snap the bond? I was already a dying and\na desperate man. Though clear of mind and fairly strong of limb,\nI knew that my own fate was sealed. But my memory and my girl!\nBoth could be saved if I could but silence that foul tongue. I\ndid it, Mr. Holmes. I would do it again. Deeply as I have sinned,\nI have led a life of martyrdom to atone for it. But that my girl\nshould be entangled in the same meshes which held me was more\nthan I could suffer. I struck him down with no more compunction\nthan if he had been some foul and venomous beast. His cry brought\nback his son; but I had gained the cover of the wood, though I\nwas forced to go back to fetch the cloak which I had dropped in\nmy flight. That is the true story, gentlemen, of all that\noccurred.\"\n\n\"Well, it is not for me to judge you,\" said Holmes as the old man\nsigned the statement which had been drawn out. \"I pray that we\nmay never be exposed to such a temptation.\"\n\n\"I pray not, sir. And what do you intend to do?\"\n\n\"In view of your health, nothing. You are yourself aware that you\nwill soon have to answer for your deed at a higher court than the\nAssizes. I will keep your confession, and if McCarthy is\ncondemned I shall be forced to use it. If not, it shall never be\nseen by mortal eye; and your secret, whether you be alive or\ndead, shall be safe with us.\"\n\n\"Farewell, then,\" said the old man solemnly. \"Your own deathbeds,\nwhen they come, will be the easier for the thought of the peace\nwhich you have given to mine.\" Tottering and shaking in all his\ngiant frame, he stumbled slowly from the room.\n\n\"God help us!\" said Holmes after a long silence. \"Why does fate\nplay such tricks with poor, helpless worms? I never hear of such\na case as this that I do not think of Baxter's words, and say,\n'There, but for the grace of God, goes Sherlock Holmes.'\"\n\nJames McCarthy was acquitted at the Assizes on the strength of a\nnumber of objections which had been drawn out by Holmes and\nsubmitted to the defending counsel. Old Turner lived for seven\nmonths after our interview, but he is now dead; and there is\nevery prospect that the son and daughter may come to live happily\ntogether in ignorance of the black cloud which rests upon their\npast.\n\n\n\nADVENTURE V. THE FIVE ORANGE PIPS\n\nWhen I glance over my notes and records of the Sherlock Holmes\ncases between the years '82 and '90, I am faced by so many which\npresent strange and interesting features that it is no easy\nmatter to know which to choose and which to leave. Some, however,\nhave already gained publicity through the papers, and others have\nnot offered a field for those peculiar qualities which my friend\npossessed in so high a degree, and which it is the object of\nthese papers to illustrate. Some, too, have baffled his\nanalytical skill, and would be, as narratives, beginnings without\nan ending, while others have been but partially cleared up, and\nhave their explanations founded rather upon conjecture and\nsurmise than on that absolute logical proof which was so dear to\nhim. There is, however, one of these last which was so remarkable\nin its details and so startling in its results that I am tempted\nto give some account of it in spite of the fact that there are\npoints in connection with it which never have been, and probably\nnever will be, entirely cleared up.\n\nThe year '87 furnished us with a long series of cases of greater\nor less interest, of which I retain the records. Among my\nheadings under this one twelve months I find an account of the\nadventure of the Paradol Chamber, of the Amateur Mendicant\nSociety, who held a luxurious club in the lower vault of a\nfurniture warehouse, of the facts connected with the loss of the\nBritish barque \"Sophy Anderson\", of the singular adventures of the\nGrice Patersons in the island of Uffa, and finally of the\nCamberwell poisoning case. In the latter, as may be remembered,\nSherlock Holmes was able, by winding up the dead man's watch, to\nprove that it had been wound up two hours before, and that\ntherefore the deceased had gone to bed within that time--a\ndeduction which was of the greatest importance in clearing up the\ncase. All these I may sketch out at some future date, but none of\nthem present such singular features as the strange train of\ncircumstances which I have now taken up my pen to describe.\n\nIt was in the latter days of September, and the equinoctial gales\nhad set in with exceptional violence. All day the wind had\nscreamed and the rain had beaten against the windows, so that\neven here in the heart of great, hand-made London we were forced\nto raise our minds for the instant from the routine of life and\nto recognise the presence of those great elemental forces which\nshriek at mankind through the bars of his civilisation, like\nuntamed beasts in a cage. As evening drew in, the storm grew\nhigher and louder, and the wind cried and sobbed like a child in\nthe chimney. Sherlock Holmes sat moodily at one side of the\nfireplace cross-indexing his records of crime, while I at the\nother was deep in one of Clark Russell's fine sea-stories until\nthe howl of the gale from without seemed to blend with the text,\nand the splash of the rain to lengthen out into the long swash of\nthe sea waves. My wife was on a visit to her mother's, and for a\nfew days I was a dweller once more in my old quarters at Baker\nStreet.\n\n\"Why,\" said I, glancing up at my companion, \"that was surely the\nbell. Who could come to-night? Some friend of yours, perhaps?\"\n\n\"Except yourself I have none,\" he answered. \"I do not encourage\nvisitors.\"\n\n\"A client, then?\"\n\n\"If so, it is a serious case. Nothing less would bring a man out\non such a day and at such an hour. But I take it that it is more\nlikely to be some crony of the landlady's.\"\n\nSherlock Holmes was wrong in his conjecture, however, for there\ncame a step in the passage and a tapping at the door. He\nstretched out his long arm to turn the lamp away from himself and\ntowards the vacant chair upon which a newcomer must sit.\n\n\"Come in!\" said he.\n\nThe man who entered was young, some two-and-twenty at the\noutside, well-groomed and trimly clad, with something of\nrefinement and delicacy in his bearing. The streaming umbrella\nwhich he held in his hand, and his long shining waterproof told\nof the fierce weather through which he had come. He looked about\nhim anxiously in the glare of the lamp, and I could see that his\nface was pale and his eyes heavy, like those of a man who is\nweighed down with some great anxiety.\n\n\"I owe you an apology,\" he said, raising his golden pince-nez to\nhis eyes. \"I trust that I am not intruding. I fear that I have\nbrought some traces of the storm and rain into your snug\nchamber.\"\n\n\"Give me your coat and umbrella,\" said Holmes. \"They may rest\nhere on the hook and will be dry presently. You have come up from\nthe south-west, I see.\"\n\n\"Yes, from Horsham.\"\n\n\"That clay and chalk mixture which I see upon your toe caps is\nquite distinctive.\"\n\n\"I have come for advice.\"\n\n\"That is easily got.\"\n\n\"And help.\"\n\n\"That is not always so easy.\"\n\n\"I have heard of you, Mr. Holmes. I heard from Major Prendergast\nhow you saved him in the Tankerville Club scandal.\"\n\n\"Ah, of course. He was wrongfully accused of cheating at cards.\"\n\n\"He said that you could solve anything.\"\n\n\"He said too much.\"\n\n\"That you are never beaten.\"\n\n\"I have been beaten four times--three times by men, and once by a\nwoman.\"\n\n\"But what is that compared with the number of your successes?\"\n\n\"It is true that I have been generally successful.\"\n\n\"Then you may be so with me.\"\n\n\"I beg that you will draw your chair up to the fire and favour me\nwith some details as to your case.\"\n\n\"It is no ordinary one.\"\n\n\"None of those which come to me are. I am the last court of\nappeal.\"\n\n\"And yet I question, sir, whether, in all your experience, you\nhave ever listened to a more mysterious and inexplicable chain of\nevents than those which have happened in my own family.\"\n\n\"You fill me with interest,\" said Holmes. \"Pray give us the\nessential facts from the commencement, and I can afterwards\nquestion you as to those details which seem to me to be most\nimportant.\"\n\nThe young man pulled his chair up and pushed his wet feet out\ntowards the blaze.\n\n\"My name,\" said he, \"is John Openshaw, but my own affairs have,\nas far as I can understand, little to do with this awful\nbusiness. It is a hereditary matter; so in order to give you an\nidea of the facts, I must go back to the commencement of the\naffair.\n\n\"You must know that my grandfather had two sons--my uncle Elias\nand my father Joseph. My father had a small factory at Coventry,\nwhich he enlarged at the time of the invention of bicycling. He\nwas a patentee of the Openshaw unbreakable tire, and his business\nmet with such success that he was able to sell it and to retire\nupon a handsome competence.\n\n\"My uncle Elias emigrated to America when he was a young man and\nbecame a planter in Florida, where he was reported to have done\nvery well. At the time of the war he fought in Jackson's army,\nand afterwards under Hood, where he rose to be a colonel. When\nLee laid down his arms my uncle returned to his plantation, where\nhe remained for three or four years. About 1869 or 1870 he came\nback to Europe and took a small estate in Sussex, near Horsham.\nHe had made a very considerable fortune in the States, and his\nreason for leaving them was his aversion to the negroes, and his\ndislike of the Republican policy in extending the franchise to\nthem. He was a singular man, fierce and quick-tempered, very\nfoul-mouthed when he was angry, and of a most retiring\ndisposition. During all the years that he lived at Horsham, I\ndoubt if ever he set foot in the town. He had a garden and two or\nthree fields round his house, and there he would take his\nexercise, though very often for weeks on end he would never leave\nhis room. He drank a great deal of brandy and smoked very\nheavily, but he would see no society and did not want any\nfriends, not even his own brother.\n\n\"He didn't mind me; in fact, he took a fancy to me, for at the\ntime when he saw me first I was a youngster of twelve or so. This\nwould be in the year 1878, after he had been eight or nine years\nin England. He begged my father to let me live with him and he\nwas very kind to me in his way. When he was sober he used to be\nfond of playing backgammon and draughts with me, and he would\nmake me his representative both with the servants and with the\ntradespeople, so that by the time that I was sixteen I was quite\nmaster of the house. I kept all the keys and could go where I\nliked and do what I liked, so long as I did not disturb him in\nhis privacy. There was one singular exception, however, for he\nhad a single room, a lumber-room up among the attics, which was\ninvariably locked, and which he would never permit either me or\nanyone else to enter. With a boy's curiosity I have peeped\nthrough the keyhole, but I was never able to see more than such a\ncollection of old trunks and bundles as would be expected in such\na room.\n\n\"One day--it was in March, 1883--a letter with a foreign stamp\nlay upon the table in front of the colonel's plate. It was not a\ncommon thing for him to receive letters, for his bills were all\npaid in ready money, and he had no friends of any sort. 'From\nIndia!' said he as he took it up, 'Pondicherry postmark! What can\nthis be?' Opening it hurriedly, out there jumped five little\ndried orange pips, which pattered down upon his plate. I began to\nlaugh at this, but the laugh was struck from my lips at the sight\nof his face. His lip had fallen, his eyes were protruding, his\nskin the colour of putty, and he glared at the envelope which he\nstill held in his trembling hand, 'K. K. K.!' he shrieked, and\nthen, 'My God, my God, my sins have overtaken me!'\n\n\"'What is it, uncle?' I cried.\n\n\"'Death,' said he, and rising from the table he retired to his\nroom, leaving me palpitating with horror. I took up the envelope\nand saw scrawled in red ink upon the inner flap, just above the\ngum, the letter K three times repeated. There was nothing else\nsave the five dried pips. What could be the reason of his\noverpowering terror? I left the breakfast-table, and as I\nascended the stair I met him coming down with an old rusty key,\nwhich must have belonged to the attic, in one hand, and a small\nbrass box, like a cashbox, in the other.\n\n\"'They may do what they like, but I'll checkmate them still,'\nsaid he with an oath. 'Tell Mary that I shall want a fire in my\nroom to-day, and send down to Fordham, the Horsham lawyer.'\n\n\"I did as he ordered, and when the lawyer arrived I was asked to\nstep up to the room. The fire was burning brightly, and in the\ngrate there was a mass of black, fluffy ashes, as of burned\npaper, while the brass box stood open and empty beside it. As I\nglanced at the box I noticed, with a start, that upon the lid was\nprinted the treble K which I had read in the morning upon the\nenvelope.\n\n\"'I wish you, John,' said my uncle, 'to witness my will. I leave\nmy estate, with all its advantages and all its disadvantages, to\nmy brother, your father, whence it will, no doubt, descend to\nyou. If you can enjoy it in peace, well and good! If you find you\ncannot, take my advice, my boy, and leave it to your deadliest\nenemy. I am sorry to give you such a two-edged thing, but I can't\nsay what turn things are going to take. Kindly sign the paper\nwhere Mr. Fordham shows you.'\n\n\"I signed the paper as directed, and the lawyer took it away with\nhim. The singular incident made, as you may think, the deepest\nimpression upon me, and I pondered over it and turned it every\nway in my mind without being able to make anything of it. Yet I\ncould not shake off the vague feeling of dread which it left\nbehind, though the sensation grew less keen as the weeks passed\nand nothing happened to disturb the usual routine of our lives. I\ncould see a change in my uncle, however. He drank more than ever,\nand he was less inclined for any sort of society. Most of his\ntime he would spend in his room, with the door locked upon the\ninside, but sometimes he would emerge in a sort of drunken frenzy\nand would burst out of the house and tear about the garden with a\nrevolver in his hand, screaming out that he was afraid of no man,\nand that he was not to be cooped up, like a sheep in a pen, by\nman or devil. When these hot fits were over, however, he would\nrush tumultuously in at the door and lock and bar it behind him,\nlike a man who can brazen it out no longer against the terror\nwhich lies at the roots of his soul. At such times I have seen\nhis face, even on a cold day, glisten with moisture, as though it\nwere new raised from a basin.\n\n\"Well, to come to an end of the matter, Mr. Holmes, and not to\nabuse your patience, there came a night when he made one of those\ndrunken sallies from which he never came back. We found him, when\nwe went to search for him, face downward in a little\ngreen-scummed pool, which lay at the foot of the garden. There\nwas no sign of any violence, and the water was but two feet deep,\nso that the jury, having regard to his known eccentricity,\nbrought in a verdict of 'suicide.' But I, who knew how he winced\nfrom the very thought of death, had much ado to persuade myself\nthat he had gone out of his way to meet it. The matter passed,\nhowever, and my father entered into possession of the estate, and\nof some 14,000 pounds, which lay to his credit at the bank.\"\n\n\"One moment,\" Holmes interposed, \"your statement is, I foresee,\none of the most remarkable to which I have ever listened. Let me\nhave the date of the reception by your uncle of the letter, and\nthe date of his supposed suicide.\"\n\n\"The letter arrived on March 10, 1883. His death was seven weeks\nlater, upon the night of May 2nd.\"\n\n\"Thank you. Pray proceed.\"\n\n\"When my father took over the Horsham property, he, at my\nrequest, made a careful examination of the attic, which had been\nalways locked up. We found the brass box there, although its\ncontents had been destroyed. On the inside of the cover was a\npaper label, with the initials of K. K. K. repeated upon it, and\n'Letters, memoranda, receipts, and a register' written beneath.\nThese, we presume, indicated the nature of the papers which had\nbeen destroyed by Colonel Openshaw. For the rest, there was\nnothing of much importance in the attic save a great many\nscattered papers and note-books bearing upon my uncle's life in\nAmerica. Some of them were of the war time and showed that he had\ndone his duty well and had borne the repute of a brave soldier.\nOthers were of a date during the reconstruction of the Southern\nstates, and were mostly concerned with politics, for he had\nevidently taken a strong part in opposing the carpet-bag\npoliticians who had been sent down from the North.\n\n\"Well, it was the beginning of '84 when my father came to live at\nHorsham, and all went as well as possible with us until the\nJanuary of '85. On the fourth day after the new year I heard my\nfather give a sharp cry of surprise as we sat together at the\nbreakfast-table. There he was, sitting with a newly opened\nenvelope in one hand and five dried orange pips in the\noutstretched palm of the other one. He had always laughed at what\nhe called my cock-and-bull story about the colonel, but he looked\nvery scared and puzzled now that the same thing had come upon\nhimself.\n\n\"'Why, what on earth does this mean, John?' he stammered.\n\n\"My heart had turned to lead. 'It is K. K. K.,' said I.\n\n\"He looked inside the envelope. 'So it is,' he cried. 'Here are\nthe very letters. But what is this written above them?'\n\n\"'Put the papers on the sundial,' I read, peeping over his\nshoulder.\n\n\"'What papers? What sundial?' he asked.\n\n\"'The sundial in the garden. There is no other,' said I; 'but the\npapers must be those that are destroyed.'\n\n\"'Pooh!' said he, gripping hard at his courage. 'We are in a\ncivilised land here, and we can't have tomfoolery of this kind.\nWhere does the thing come from?'\n\n\"'From Dundee,' I answered, glancing at the postmark.\n\n\"'Some preposterous practical joke,' said he. 'What have I to do\nwith sundials and papers? I shall take no notice of such\nnonsense.'\n\n\"'I should certainly speak to the police,' I said.\n\n\"'And be laughed at for my pains. Nothing of the sort.'\n\n\"'Then let me do so?'\n\n\"'No, I forbid you. I won't have a fuss made about such\nnonsense.'\n\n\"It was in vain to argue with him, for he was a very obstinate\nman. I went about, however, with a heart which was full of\nforebodings.\n\n\"On the third day after the coming of the letter my father went\nfrom home to visit an old friend of his, Major Freebody, who is\nin command of one of the forts upon Portsdown Hill. I was glad\nthat he should go, for it seemed to me that he was farther from\ndanger when he was away from home. In that, however, I was in\nerror. Upon the second day of his absence I received a telegram\nfrom the major, imploring me to come at once. My father had\nfallen over one of the deep chalk-pits which abound in the\nneighbourhood, and was lying senseless, with a shattered skull. I\nhurried to him, but he passed away without having ever recovered\nhis consciousness. He had, as it appears, been returning from\nFareham in the twilight, and as the country was unknown to him,\nand the chalk-pit unfenced, the jury had no hesitation in\nbringing in a verdict of 'death from accidental causes.'\nCarefully as I examined every fact connected with his death, I\nwas unable to find anything which could suggest the idea of\nmurder. There were no signs of violence, no footmarks, no\nrobbery, no record of strangers having been seen upon the roads.\nAnd yet I need not tell you that my mind was far from at ease,\nand that I was well-nigh certain that some foul plot had been\nwoven round him.\n\n\"In this sinister way I came into my inheritance. You will ask me\nwhy I did not dispose of it? I answer, because I was well\nconvinced that our troubles were in some way dependent upon an\nincident in my uncle's life, and that the danger would be as\npressing in one house as in another.\n\n\"It was in January, '85, that my poor father met his end, and two\nyears and eight months have elapsed since then. During that time\nI have lived happily at Horsham, and I had begun to hope that\nthis curse had passed away from the family, and that it had ended\nwith the last generation. I had begun to take comfort too soon,\nhowever; yesterday morning the blow fell in the very shape in\nwhich it had come upon my father.\"\n\nThe young man took from his waistcoat a crumpled envelope, and\nturning to the table he shook out upon it five little dried\norange pips.\n\n\"This is the envelope,\" he continued. \"The postmark is\nLondon--eastern division. Within are the very words which were\nupon my father's last message: 'K. K. K.'; and then 'Put the\npapers on the sundial.'\"\n\n\"What have you done?\" asked Holmes.\n\n\"Nothing.\"\n\n\"Nothing?\"\n\n\"To tell the truth\"--he sank his face into his thin, white\nhands--\"I have felt helpless. I have felt like one of those poor\nrabbits when the snake is writhing towards it. I seem to be in\nthe grasp of some resistless, inexorable evil, which no foresight\nand no precautions can guard against.\"\n\n\"Tut! tut!\" cried Sherlock Holmes. \"You must act, man, or you are\nlost. Nothing but energy can save you. This is no time for\ndespair.\"\n\n\"I have seen the police.\"\n\n\"Ah!\"\n\n\"But they listened to my story with a smile. I am convinced that\nthe inspector has formed the opinion that the letters are all\npractical jokes, and that the deaths of my relations were really\naccidents, as the jury stated, and were not to be connected with\nthe warnings.\"\n\nHolmes shook his clenched hands in the air. \"Incredible\nimbecility!\" he cried.\n\n\"They have, however, allowed me a policeman, who may remain in\nthe house with me.\"\n\n\"Has he come with you to-night?\"\n\n\"No. His orders were to stay in the house.\"\n\nAgain Holmes raved in the air.\n\n\"Why did you come to me,\" he cried, \"and, above all, why did you\nnot come at once?\"\n\n\"I did not know. It was only to-day that I spoke to Major\nPrendergast about my troubles and was advised by him to come to\nyou.\"\n\n\"It is really two days since you had the letter. We should have\nacted before this. You have no further evidence, I suppose, than\nthat which you have placed before us--no suggestive detail which\nmight help us?\"\n\n\"There is one thing,\" said John Openshaw. He rummaged in his coat\npocket, and, drawing out a piece of discoloured, blue-tinted\npaper, he laid it out upon the table. \"I have some remembrance,\"\nsaid he, \"that on the day when my uncle burned the papers I\nobserved that the small, unburned margins which lay amid the\nashes were of this particular colour. I found this single sheet\nupon the floor of his room, and I am inclined to think that it\nmay be one of the papers which has, perhaps, fluttered out from\namong the others, and in that way has escaped destruction. Beyond\nthe mention of pips, I do not see that it helps us much. I think\nmyself that it is a page from some private diary. The writing is\nundoubtedly my uncle's.\"\n\nHolmes moved the lamp, and we both bent over the sheet of paper,\nwhich showed by its ragged edge that it had indeed been torn from\na book. It was headed, \"March, 1869,\" and beneath were the\nfollowing enigmatical notices:\n\n\"4th. Hudson came. Same old platform.\n\n\"7th. Set the pips on McCauley, Paramore, and\n      John Swain, of St. Augustine.\n\n\"9th. McCauley cleared.\n\n\"10th. John Swain cleared.\n\n\"12th. Visited Paramore. All well.\"\n\n\"Thank you!\" said Holmes, folding up the paper and returning it\nto our visitor. \"And now you must on no account lose another\ninstant. We cannot spare time even to discuss what you have told\nme. You must get home instantly and act.\"\n\n\"What shall I do?\"\n\n\"There is but one thing to do. It must be done at once. You must\nput this piece of paper which you have shown us into the brass\nbox which you have described. You must also put in a note to say\nthat all the other papers were burned by your uncle, and that\nthis is the only one which remains. You must assert that in such\nwords as will carry conviction with them. Having done this, you\nmust at once put the box out upon the sundial, as directed. Do\nyou understand?\"\n\n\"Entirely.\"\n\n\"Do not think of revenge, or anything of the sort, at present. I\nthink that we may gain that by means of the law; but we have our\nweb to weave, while theirs is already woven. The first\nconsideration is to remove the pressing danger which threatens\nyou. The second is to clear up the mystery and to punish the\nguilty parties.\"\n\n\"I thank you,\" said the young man, rising and pulling on his\novercoat. \"You have given me fresh life and hope. I shall\ncertainly do as you advise.\"\n\n\"Do not lose an instant. And, above all, take care of yourself in\nthe meanwhile, for I do not think that there can be a doubt that\nyou are threatened by a very real and imminent danger. How do you\ngo back?\"\n\n\"By train from Waterloo.\"\n\n\"It is not yet nine. The streets will be crowded, so I trust that\nyou may be in safety. And yet you cannot guard yourself too\nclosely.\"\n\n\"I am armed.\"\n\n\"That is well. To-morrow I shall set to work upon your case.\"\n\n\"I shall see you at Horsham, then?\"\n\n\"No, your secret lies in London. It is there that I shall seek\nit.\"\n\n\"Then I shall call upon you in a day, or in two days, with news\nas to the box and the papers. I shall take your advice in every\nparticular.\" He shook hands with us and took his leave. Outside\nthe wind still screamed and the rain splashed and pattered\nagainst the windows. This strange, wild story seemed to have come\nto us from amid the mad elements--blown in upon us like a sheet\nof sea-weed in a gale--and now to have been reabsorbed by them\nonce more.\n\nSherlock Holmes sat for some time in silence, with his head sunk\nforward and his eyes bent upon the red glow of the fire. Then he\nlit his pipe, and leaning back in his chair he watched the blue\nsmoke-rings as they chased each other up to the ceiling.\n\n\"I think, Watson,\" he remarked at last, \"that of all our cases we\nhave had none more fantastic than this.\"\n\n\"Save, perhaps, the Sign of Four.\"\n\n\"Well, yes. Save, perhaps, that. And yet this John Openshaw seems\nto me to be walking amid even greater perils than did the\nSholtos.\"\n\n\"But have you,\" I asked, \"formed any definite conception as to\nwhat these perils are?\"\n\n\"There can be no question as to their nature,\" he answered.\n\n\"Then what are they? Who is this K. K. K., and why does he pursue\nthis unhappy family?\"\n\nSherlock Holmes closed his eyes and placed his elbows upon the\narms of his chair, with his finger-tips together. \"The ideal\nreasoner,\" he remarked, \"would, when he had once been shown a\nsingle fact in all its bearings, deduce from it not only all the\nchain of events which led up to it but also all the results which\nwould follow from it. As Cuvier could correctly describe a whole\nanimal by the contemplation of a single bone, so the observer who\nhas thoroughly understood one link in a series of incidents\nshould be able to accurately state all the other ones, both\nbefore and after. We have not yet grasped the results which the\nreason alone can attain to. Problems may be solved in the study\nwhich have baffled all those who have sought a solution by the\naid of their senses. To carry the art, however, to its highest\npitch, it is necessary that the reasoner should be able to\nutilise all the facts which have come to his knowledge; and this\nin itself implies, as you will readily see, a possession of all\nknowledge, which, even in these days of free education and\nencyclopaedias, is a somewhat rare accomplishment. It is not so\nimpossible, however, that a man should possess all knowledge\nwhich is likely to be useful to him in his work, and this I have\nendeavoured in my case to do. If I remember rightly, you on one\noccasion, in the early days of our friendship, defined my limits\nin a very precise fashion.\"\n\n\"Yes,\" I answered, laughing. \"It was a singular document.\nPhilosophy, astronomy, and politics were marked at zero, I\nremember. Botany variable, geology profound as regards the\nmud-stains from any region within fifty miles of town, chemistry\neccentric, anatomy unsystematic, sensational literature and crime\nrecords unique, violin-player, boxer, swordsman, lawyer, and\nself-poisoner by cocaine and tobacco. Those, I think, were the\nmain points of my analysis.\"\n\nHolmes grinned at the last item. \"Well,\" he said, \"I say now, as\nI said then, that a man should keep his little brain-attic\nstocked with all the furniture that he is likely to use, and the\nrest he can put away in the lumber-room of his library, where he\ncan get it if he wants it. Now, for such a case as the one which\nhas been submitted to us to-night, we need certainly to muster\nall our resources. Kindly hand me down the letter K of the\n'American Encyclopaedia' which stands upon the shelf beside you.\nThank you. Now let us consider the situation and see what may be\ndeduced from it. In the first place, we may start with a strong\npresumption that Colonel Openshaw had some very strong reason for\nleaving America. Men at his time of life do not change all their\nhabits and exchange willingly the charming climate of Florida for\nthe lonely life of an English provincial town. His extreme love\nof solitude in England suggests the idea that he was in fear of\nsomeone or something, so we may assume as a working hypothesis\nthat it was fear of someone or something which drove him from\nAmerica. As to what it was he feared, we can only deduce that by\nconsidering the formidable letters which were received by himself\nand his successors. Did you remark the postmarks of those\nletters?\"\n\n\"The first was from Pondicherry, the second from Dundee, and the\nthird from London.\"\n\n\"From East London. What do you deduce from that?\"\n\n\"They are all seaports. That the writer was on board of a ship.\"\n\n\"Excellent. We have already a clue. There can be no doubt that\nthe probability--the strong probability--is that the writer was\non board of a ship. And now let us consider another point. In the\ncase of Pondicherry, seven weeks elapsed between the threat and\nits fulfilment, in Dundee it was only some three or four days.\nDoes that suggest anything?\"\n\n\"A greater distance to travel.\"\n\n\"But the letter had also a greater distance to come.\"\n\n\"Then I do not see the point.\"\n\n\"There is at least a presumption that the vessel in which the man\nor men are is a sailing-ship. It looks as if they always send\ntheir singular warning or token before them when starting upon\ntheir mission. You see how quickly the deed followed the sign\nwhen it came from Dundee. If they had come from Pondicherry in a\nsteamer they would have arrived almost as soon as their letter.\nBut, as a matter of fact, seven weeks elapsed. I think that those\nseven weeks represented the difference between the mail-boat which\nbrought the letter and the sailing vessel which brought the\nwriter.\"\n\n\"It is possible.\"\n\n\"More than that. It is probable. And now you see the deadly\nurgency of this new case, and why I urged young Openshaw to\ncaution. The blow has always fallen at the end of the time which\nit would take the senders to travel the distance. But this one\ncomes from London, and therefore we cannot count upon delay.\"\n\n\"Good God!\" I cried. \"What can it mean, this relentless\npersecution?\"\n\n\"The papers which Openshaw carried are obviously of vital\nimportance to the person or persons in the sailing-ship. I think\nthat it is quite clear that there must be more than one of them.\nA single man could not have carried out two deaths in such a way\nas to deceive a coroner's jury. There must have been several in\nit, and they must have been men of resource and determination.\nTheir papers they mean to have, be the holder of them who it may.\nIn this way you see K. K. K. ceases to be the initials of an\nindividual and becomes the badge of a society.\"\n\n\"But of what society?\"\n\n\"Have you never--\" said Sherlock Holmes, bending forward and\nsinking his voice--\"have you never heard of the Ku Klux Klan?\"\n\n\"I never have.\"\n\nHolmes turned over the leaves of the book upon his knee. \"Here it\nis,\" said he presently:\n\n\"'Ku Klux Klan. A name derived from the fanciful resemblance to\nthe sound produced by cocking a rifle. This terrible secret\nsociety was formed by some ex-Confederate soldiers in the\nSouthern states after the Civil War, and it rapidly formed local\nbranches in different parts of the country, notably in Tennessee,\nLouisiana, the Carolinas, Georgia, and Florida. Its power was\nused for political purposes, principally for the terrorising of\nthe negro voters and the murdering and driving from the country\nof those who were opposed to its views. Its outrages were usually\npreceded by a warning sent to the marked man in some fantastic\nbut generally recognised shape--a sprig of oak-leaves in some\nparts, melon seeds or orange pips in others. On receiving this\nthe victim might either openly abjure his former ways, or might\nfly from the country. If he braved the matter out, death would\nunfailingly come upon him, and usually in some strange and\nunforeseen manner. So perfect was the organisation of the\nsociety, and so systematic its methods, that there is hardly a\ncase upon record where any man succeeded in braving it with\nimpunity, or in which any of its outrages were traced home to the\nperpetrators. For some years the organisation flourished in spite\nof the efforts of the United States government and of the better\nclasses of the community in the South. Eventually, in the year\n1869, the movement rather suddenly collapsed, although there have\nbeen sporadic outbreaks of the same sort since that date.'\n\n\"You will observe,\" said Holmes, laying down the volume, \"that\nthe sudden breaking up of the society was coincident with the\ndisappearance of Openshaw from America with their papers. It may\nwell have been cause and effect. It is no wonder that he and his\nfamily have some of the more implacable spirits upon their track.\nYou can understand that this register and diary may implicate\nsome of the first men in the South, and that there may be many\nwho will not sleep easy at night until it is recovered.\"\n\n\"Then the page we have seen--\"\n\n\"Is such as we might expect. It ran, if I remember right, 'sent\nthe pips to A, B, and C'--that is, sent the society's warning to\nthem. Then there are successive entries that A and B cleared, or\nleft the country, and finally that C was visited, with, I fear, a\nsinister result for C. Well, I think, Doctor, that we may let\nsome light into this dark place, and I believe that the only\nchance young Openshaw has in the meantime is to do what I have\ntold him. There is nothing more to be said or to be done\nto-night, so hand me over my violin and let us try to forget for\nhalf an hour the miserable weather and the still more miserable\nways of our fellow-men.\"\n\n\nIt had cleared in the morning, and the sun was shining with a\nsubdued brightness through the dim veil which hangs over the\ngreat city. Sherlock Holmes was already at breakfast when I came\ndown.\n\n\"You will excuse me for not waiting for you,\" said he; \"I have, I\nforesee, a very busy day before me in looking into this case of\nyoung Openshaw's.\"\n\n\"What steps will you take?\" I asked.\n\n\"It will very much depend upon the results of my first inquiries.\nI may have to go down to Horsham, after all.\"\n\n\"You will not go there first?\"\n\n\"No, I shall commence with the City. Just ring the bell and the\nmaid will bring up your coffee.\"\n\nAs I waited, I lifted the unopened newspaper from the table and\nglanced my eye over it. It rested upon a heading which sent a\nchill to my heart.\n\n\"Holmes,\" I cried, \"you are too late.\"\n\n\"Ah!\" said he, laying down his cup, \"I feared as much. How was it\ndone?\" He spoke calmly, but I could see that he was deeply moved.\n\n\"My eye caught the name of Openshaw, and the heading 'Tragedy\nNear Waterloo Bridge.' Here is the account:\n\n\"Between nine and ten last night Police-Constable Cook, of the H\nDivision, on duty near Waterloo Bridge, heard a cry for help and\na splash in the water. The night, however, was extremely dark and\nstormy, so that, in spite of the help of several passers-by, it\nwas quite impossible to effect a rescue. The alarm, however, was\ngiven, and, by the aid of the water-police, the body was\neventually recovered. It proved to be that of a young gentleman\nwhose name, as it appears from an envelope which was found in his\npocket, was John Openshaw, and whose residence is near Horsham.\nIt is conjectured that he may have been hurrying down to catch\nthe last train from Waterloo Station, and that in his haste and\nthe extreme darkness he missed his path and walked over the edge\nof one of the small landing-places for river steamboats. The body\nexhibited no traces of violence, and there can be no doubt that\nthe deceased had been the victim of an unfortunate accident,\nwhich should have the effect of calling the attention of the\nauthorities to the condition of the riverside landing-stages.\"\n\nWe sat in silence for some minutes, Holmes more depressed and\nshaken than I had ever seen him.\n\n\"That hurts my pride, Watson,\" he said at last. \"It is a petty\nfeeling, no doubt, but it hurts my pride. It becomes a personal\nmatter with me now, and, if God sends me health, I shall set my\nhand upon this gang. That he should come to me for help, and that\nI should send him away to his death--!\" He sprang from his chair\nand paced about the room in uncontrollable agitation, with a\nflush upon his sallow cheeks and a nervous clasping and\nunclasping of his long thin hands.\n\n\"They must be cunning devils,\" he exclaimed at last. \"How could\nthey have decoyed him down there? The Embankment is not on the\ndirect line to the station. The bridge, no doubt, was too\ncrowded, even on such a night, for their purpose. Well, Watson,\nwe shall see who will win in the long run. I am going out now!\"\n\n\"To the police?\"\n\n\"No; I shall be my own police. When I have spun the web they may\ntake the flies, but not before.\"\n\nAll day I was engaged in my professional work, and it was late in\nthe evening before I returned to Baker Street. Sherlock Holmes\nhad not come back yet. It was nearly ten o'clock before he\nentered, looking pale and worn. He walked up to the sideboard,\nand tearing a piece from the loaf he devoured it voraciously,\nwashing it down with a long draught of water.\n\n\"You are hungry,\" I remarked.\n\n\"Starving. It had escaped my memory. I have had nothing since\nbreakfast.\"\n\n\"Nothing?\"\n\n\"Not a bite. I had no time to think of it.\"\n\n\"And how have you succeeded?\"\n\n\"Well.\"\n\n\"You have a clue?\"\n\n\"I have them in the hollow of my hand. Young Openshaw shall not\nlong remain unavenged. Why, Watson, let us put their own devilish\ntrade-mark upon them. It is well thought of!\"\n\n\"What do you mean?\"\n\nHe took an orange from the cupboard, and tearing it to pieces he\nsqueezed out the pips upon the table. Of these he took five and\nthrust them into an envelope. On the inside of the flap he wrote\n\"S. H. for J. O.\" Then he sealed it and addressed it to \"Captain\nJames Calhoun, Barque 'Lone Star,' Savannah, Georgia.\"\n\n\"That will await him when he enters port,\" said he, chuckling.\n\"It may give him a sleepless night. He will find it as sure a\nprecursor of his fate as Openshaw did before him.\"\n\n\"And who is this Captain Calhoun?\"\n\n\"The leader of the gang. I shall have the others, but he first.\"\n\n\"How did you trace it, then?\"\n\nHe took a large sheet of paper from his pocket, all covered with\ndates and names.\n\n\"I have spent the whole day,\" said he, \"over Lloyd's registers\nand files of the old papers, following the future career of every\nvessel which touched at Pondicherry in January and February in\n'83. There were thirty-six ships of fair tonnage which were\nreported there during those months. Of these, one, the 'Lone Star,'\ninstantly attracted my attention, since, although it was reported\nas having cleared from London, the name is that which is given to\none of the states of the Union.\"\n\n\"Texas, I think.\"\n\n\"I was not and am not sure which; but I knew that the ship must\nhave an American origin.\"\n\n\"What then?\"\n\n\"I searched the Dundee records, and when I found that the barque\n'Lone Star' was there in January, '85, my suspicion became a\ncertainty. I then inquired as to the vessels which lay at present\nin the port of London.\"\n\n\"Yes?\"\n\n\"The 'Lone Star' had arrived here last week. I went down to the\nAlbert Dock and found that she had been taken down the river by\nthe early tide this morning, homeward bound to Savannah. I wired\nto Gravesend and learned that she had passed some time ago, and\nas the wind is easterly I have no doubt that she is now past the\nGoodwins and not very far from the Isle of Wight.\"\n\n\"What will you do, then?\"\n\n\"Oh, I have my hand upon him. He and the two mates, are as I\nlearn, the only native-born Americans in the ship. The others are\nFinns and Germans. I know, also, that they were all three away\nfrom the ship last night. I had it from the stevedore who has\nbeen loading their cargo. By the time that their sailing-ship\nreaches Savannah the mail-boat will have carried this letter, and\nthe cable will have informed the police of Savannah that these\nthree gentlemen are badly wanted here upon a charge of murder.\"\n\nThere is ever a flaw, however, in the best laid of human plans,\nand the murderers of John Openshaw were never to receive the\norange pips which would show them that another, as cunning and as\nresolute as themselves, was upon their track. Very long and very\nsevere were the equinoctial gales that year. We waited long for\nnews of the \"Lone Star\" of Savannah, but none ever reached us. We\ndid at last hear that somewhere far out in the Atlantic a\nshattered stern-post of a boat was seen swinging in the trough\nof a wave, with the letters \"L. S.\" carved upon it, and that is\nall which we shall ever know of the fate of the \"Lone Star.\"\n\n\n\nADVENTURE VI. THE MAN WITH THE TWISTED LIP\n\nIsa Whitney, brother of the late Elias Whitney, D.D., Principal\nof the Theological College of St. George's, was much addicted to\nopium. The habit grew upon him, as I understand, from some\nfoolish freak when he was at college; for having read De\nQuincey's description of his dreams and sensations, he had\ndrenched his tobacco with laudanum in an attempt to produce the\nsame effects. He found, as so many more have done, that the\npractice is easier to attain than to get rid of, and for many\nyears he continued to be a slave to the drug, an object of\nmingled horror and pity to his friends and relatives. I can see\nhim now, with yellow, pasty face, drooping lids, and pin-point\npupils, all huddled in a chair, the wreck and ruin of a noble\nman.\n\nOne night--it was in June, '89--there came a ring to my bell,\nabout the hour when a man gives his first yawn and glances at the\nclock. I sat up in my chair, and my wife laid her needle-work\ndown in her lap and made a little face of disappointment.\n\n\"A patient!\" said she. \"You'll have to go out.\"\n\nI groaned, for I was newly come back from a weary day.\n\nWe heard the door open, a few hurried words, and then quick steps\nupon the linoleum. Our own door flew open, and a lady, clad in\nsome dark-coloured stuff, with a black veil, entered the room.\n\n\"You will excuse my calling so late,\" she began, and then,\nsuddenly losing her self-control, she ran forward, threw her arms\nabout my wife's neck, and sobbed upon her shoulder. \"Oh, I'm in\nsuch trouble!\" she cried; \"I do so want a little help.\"\n\n\"Why,\" said my wife, pulling up her veil, \"it is Kate Whitney.\nHow you startled me, Kate! I had not an idea who you were when\nyou came in.\"\n\n\"I didn't know what to do, so I came straight to you.\" That was\nalways the way. Folk who were in grief came to my wife like birds\nto a light-house.\n\n\"It was very sweet of you to come. Now, you must have some wine\nand water, and sit here comfortably and tell us all about it. Or\nshould you rather that I sent James off to bed?\"\n\n\"Oh, no, no! I want the doctor's advice and help, too. It's about\nIsa. He has not been home for two days. I am so frightened about\nhim!\"\n\nIt was not the first time that she had spoken to us of her\nhusband's trouble, to me as a doctor, to my wife as an old friend\nand school companion. We soothed and comforted her by such words\nas we could find. Did she know where her husband was? Was it\npossible that we could bring him back to her?\n\nIt seems that it was. She had the surest information that of late\nhe had, when the fit was on him, made use of an opium den in the\nfarthest east of the City. Hitherto his orgies had always been\nconfined to one day, and he had come back, twitching and\nshattered, in the evening. But now the spell had been upon him\neight-and-forty hours, and he lay there, doubtless among the\ndregs of the docks, breathing in the poison or sleeping off the\neffects. There he was to be found, she was sure of it, at the Bar\nof Gold, in Upper Swandam Lane. But what was she to do? How could\nshe, a young and timid woman, make her way into such a place and\npluck her husband out from among the ruffians who surrounded him?\n\nThere was the case, and of course there was but one way out of\nit. Might I not escort her to this place? And then, as a second\nthought, why should she come at all? I was Isa Whitney's medical\nadviser, and as such I had influence over him. I could manage it\nbetter if I were alone. I promised her on my word that I would\nsend him home in a cab within two hours if he were indeed at the\naddress which she had given me. And so in ten minutes I had left\nmy armchair and cheery sitting-room behind me, and was speeding\neastward in a hansom on a strange errand, as it seemed to me at\nthe time, though the future only could show how strange it was to\nbe.\n\nBut there was no great difficulty in the first stage of my\nadventure. Upper Swandam Lane is a vile alley lurking behind the\nhigh wharves which line the north side of the river to the east\nof London Bridge. Between a slop-shop and a gin-shop, approached\nby a steep flight of steps leading down to a black gap like the\nmouth of a cave, I found the den of which I was in search.\nOrdering my cab to wait, I passed down the steps, worn hollow in\nthe centre by the ceaseless tread of drunken feet; and by the\nlight of a flickering oil-lamp above the door I found the latch\nand made my way into a long, low room, thick and heavy with the\nbrown opium smoke, and terraced with wooden berths, like the\nforecastle of an emigrant ship.\n\nThrough the gloom one could dimly catch a glimpse of bodies lying\nin strange fantastic poses, bowed shoulders, bent knees, heads\nthrown back, and chins pointing upward, with here and there a\ndark, lack-lustre eye turned upon the newcomer. Out of the black\nshadows there glimmered little red circles of light, now bright,\nnow faint, as the burning poison waxed or waned in the bowls of\nthe metal pipes. The most lay silent, but some muttered to\nthemselves, and others talked together in a strange, low,\nmonotonous voice, their conversation coming in gushes, and then\nsuddenly tailing off into silence, each mumbling out his own\nthoughts and paying little heed to the words of his neighbour. At\nthe farther end was a small brazier of burning charcoal, beside\nwhich on a three-legged wooden stool there sat a tall, thin old\nman, with his jaw resting upon his two fists, and his elbows upon\nhis knees, staring into the fire.\n\nAs I entered, a sallow Malay attendant had hurried up with a pipe\nfor me and a supply of the drug, beckoning me to an empty berth.\n\n\"Thank you. I have not come to stay,\" said I. \"There is a friend\nof mine here, Mr. Isa Whitney, and I wish to speak with him.\"\n\nThere was a movement and an exclamation from my right, and\npeering through the gloom, I saw Whitney, pale, haggard, and\nunkempt, staring out at me.\n\n\"My God! It's Watson,\" said he. He was in a pitiable state of\nreaction, with every nerve in a twitter. \"I say, Watson, what\no'clock is it?\"\n\n\"Nearly eleven.\"\n\n\"Of what day?\"\n\n\"Of Friday, June 19th.\"\n\n\"Good heavens! I thought it was Wednesday. It is Wednesday. What\nd'you want to frighten a chap for?\" He sank his face onto his\narms and began to sob in a high treble key.\n\n\"I tell you that it is Friday, man. Your wife has been waiting\nthis two days for you. You should be ashamed of yourself!\"\n\n\"So I am. But you've got mixed, Watson, for I have only been here\na few hours, three pipes, four pipes--I forget how many. But I'll\ngo home with you. I wouldn't frighten Kate--poor little Kate.\nGive me your hand! Have you a cab?\"\n\n\"Yes, I have one waiting.\"\n\n\"Then I shall go in it. But I must owe something. Find what I\nowe, Watson. I am all off colour. I can do nothing for myself.\"\n\nI walked down the narrow passage between the double row of\nsleepers, holding my breath to keep out the vile, stupefying\nfumes of the drug, and looking about for the manager. As I passed\nthe tall man who sat by the brazier I felt a sudden pluck at my\nskirt, and a low voice whispered, \"Walk past me, and then look\nback at me.\" The words fell quite distinctly upon my ear. I\nglanced down. They could only have come from the old man at my\nside, and yet he sat now as absorbed as ever, very thin, very\nwrinkled, bent with age, an opium pipe dangling down from between\nhis knees, as though it had dropped in sheer lassitude from his\nfingers. I took two steps forward and looked back. It took all my\nself-control to prevent me from breaking out into a cry of\nastonishment. He had turned his back so that none could see him\nbut I. His form had filled out, his wrinkles were gone, the dull\neyes had regained their fire, and there, sitting by the fire and\ngrinning at my surprise, was none other than Sherlock Holmes. He\nmade a slight motion to me to approach him, and instantly, as he\nturned his face half round to the company once more, subsided\ninto a doddering, loose-lipped senility.\n\n\"Holmes!\" I whispered, \"what on earth are you doing in this den?\"\n\n\"As low as you can,\" he answered; \"I have excellent ears. If you\nwould have the great kindness to get rid of that sottish friend\nof yours I should be exceedingly glad to have a little talk with\nyou.\"\n\n\"I have a cab outside.\"\n\n\"Then pray send him home in it. You may safely trust him, for he\nappears to be too limp to get into any mischief. I should\nrecommend you also to send a note by the cabman to your wife to\nsay that you have thrown in your lot with me. If you will wait\noutside, I shall be with you in five minutes.\"\n\nIt was difficult to refuse any of Sherlock Holmes' requests, for\nthey were always so exceedingly definite, and put forward with\nsuch a quiet air of mastery. I felt, however, that when Whitney\nwas once confined in the cab my mission was practically\naccomplished; and for the rest, I could not wish anything better\nthan to be associated with my friend in one of those singular\nadventures which were the normal condition of his existence. In a\nfew minutes I had written my note, paid Whitney's bill, led him\nout to the cab, and seen him driven through the darkness. In a\nvery short time a decrepit figure had emerged from the opium den,\nand I was walking down the street with Sherlock Holmes. For two\nstreets he shuffled along with a bent back and an uncertain foot.\nThen, glancing quickly round, he straightened himself out and\nburst into a hearty fit of laughter.\n\n\"I suppose, Watson,\" said he, \"that you imagine that I have added\nopium-smoking to cocaine injections, and all the other little\nweaknesses on which you have favoured me with your medical\nviews.\"\n\n\"I was certainly surprised to find you there.\"\n\n\"But not more so than I to find you.\"\n\n\"I came to find a friend.\"\n\n\"And I to find an enemy.\"\n\n\"An enemy?\"\n\n\"Yes; one of my natural enemies, or, shall I say, my natural\nprey. Briefly, Watson, I am in the midst of a very remarkable\ninquiry, and I have hoped to find a clue in the incoherent\nramblings of these sots, as I have done before now. Had I been\nrecognised in that den my life would not have been worth an\nhour's purchase; for I have used it before now for my own\npurposes, and the rascally Lascar who runs it has sworn to have\nvengeance upon me. There is a trap-door at the back of that\nbuilding, near the corner of Paul's Wharf, which could tell some\nstrange tales of what has passed through it upon the moonless\nnights.\"\n\n\"What! You do not mean bodies?\"\n\n\"Ay, bodies, Watson. We should be rich men if we had 1000 pounds\nfor every poor devil who has been done to death in that den. It\nis the vilest murder-trap on the whole riverside, and I fear that\nNeville St. Clair has entered it never to leave it more. But our\ntrap should be here.\" He put his two forefingers between his\nteeth and whistled shrilly--a signal which was answered by a\nsimilar whistle from the distance, followed shortly by the rattle\nof wheels and the clink of horses' hoofs.\n\n\"Now, Watson,\" said Holmes, as a tall dog-cart dashed up through\nthe gloom, throwing out two golden tunnels of yellow light from\nits side lanterns. \"You'll come with me, won't you?\"\n\n\"If I can be of use.\"\n\n\"Oh, a trusty comrade is always of use; and a chronicler still\nmore so. My room at The Cedars is a double-bedded one.\"\n\n\"The Cedars?\"\n\n\"Yes; that is Mr. St. Clair's house. I am staying there while I\nconduct the inquiry.\"\n\n\"Where is it, then?\"\n\n\"Near Lee, in Kent. We have a seven-mile drive before us.\"\n\n\"But I am all in the dark.\"\n\n\"Of course you are. You'll know all about it presently. Jump up\nhere. All right, John; we shall not need you. Here's half a\ncrown. Look out for me to-morrow, about eleven. Give her her\nhead. So long, then!\"\n\nHe flicked the horse with his whip, and we dashed away through\nthe endless succession of sombre and deserted streets, which\nwidened gradually, until we were flying across a broad\nbalustraded bridge, with the murky river flowing sluggishly\nbeneath us. Beyond lay another dull wilderness of bricks and\nmortar, its silence broken only by the heavy, regular footfall of\nthe policeman, or the songs and shouts of some belated party of\nrevellers. A dull wrack was drifting slowly across the sky, and a\nstar or two twinkled dimly here and there through the rifts of\nthe clouds. Holmes drove in silence, with his head sunk upon his\nbreast, and the air of a man who is lost in thought, while I sat\nbeside him, curious to learn what this new quest might be which\nseemed to tax his powers so sorely, and yet afraid to break in\nupon the current of his thoughts. We had driven several miles,\nand were beginning to get to the fringe of the belt of suburban\nvillas, when he shook himself, shrugged his shoulders, and lit up\nhis pipe with the air of a man who has satisfied himself that he\nis acting for the best.\n\n\"You have a grand gift of silence, Watson,\" said he. \"It makes\nyou quite invaluable as a companion. 'Pon my word, it is a great\nthing for me to have someone to talk to, for my own thoughts are\nnot over-pleasant. I was wondering what I should say to this dear\nlittle woman to-night when she meets me at the door.\"\n\n\"You forget that I know nothing about it.\"\n\n\"I shall just have time to tell you the facts of the case before\nwe get to Lee. It seems absurdly simple, and yet, somehow I can\nget nothing to go upon. There's plenty of thread, no doubt, but I\ncan't get the end of it into my hand. Now, I'll state the case\nclearly and concisely to you, Watson, and maybe you can see a\nspark where all is dark to me.\"\n\n\"Proceed, then.\"\n\n\"Some years ago--to be definite, in May, 1884--there came to Lee\na gentleman, Neville St. Clair by name, who appeared to have\nplenty of money. He took a large villa, laid out the grounds very\nnicely, and lived generally in good style. By degrees he made\nfriends in the neighbourhood, and in 1887 he married the daughter\nof a local brewer, by whom he now has two children. He had no\noccupation, but was interested in several companies and went into\ntown as a rule in the morning, returning by the 5:14 from Cannon\nStreet every night. Mr. St. Clair is now thirty-seven years of\nage, is a man of temperate habits, a good husband, a very\naffectionate father, and a man who is popular with all who know\nhim. I may add that his whole debts at the present moment, as far\nas we have been able to ascertain, amount to 88 pounds 10s., while\nhe has 220 pounds standing to his credit in the Capital and\nCounties Bank. There is no reason, therefore, to think that money\ntroubles have been weighing upon his mind.\n\n\"Last Monday Mr. Neville St. Clair went into town rather earlier\nthan usual, remarking before he started that he had two important\ncommissions to perform, and that he would bring his little boy\nhome a box of bricks. Now, by the merest chance, his wife\nreceived a telegram upon this same Monday, very shortly after his\ndeparture, to the effect that a small parcel of considerable\nvalue which she had been expecting was waiting for her at the\noffices of the Aberdeen Shipping Company. Now, if you are well up\nin your London, you will know that the office of the company is\nin Fresno Street, which branches out of Upper Swandam Lane, where\nyou found me to-night. Mrs. St. Clair had her lunch, started for\nthe City, did some shopping, proceeded to the company's office,\ngot her packet, and found herself at exactly 4:35 walking through\nSwandam Lane on her way back to the station. Have you followed me\nso far?\"\n\n\"It is very clear.\"\n\n\"If you remember, Monday was an exceedingly hot day, and Mrs. St.\nClair walked slowly, glancing about in the hope of seeing a cab,\nas she did not like the neighbourhood in which she found herself.\nWhile she was walking in this way down Swandam Lane, she suddenly\nheard an ejaculation or cry, and was struck cold to see her\nhusband looking down at her and, as it seemed to her, beckoning\nto her from a second-floor window. The window was open, and she\ndistinctly saw his face, which she describes as being terribly\nagitated. He waved his hands frantically to her, and then\nvanished from the window so suddenly that it seemed to her that\nhe had been plucked back by some irresistible force from behind.\nOne singular point which struck her quick feminine eye was that\nalthough he wore some dark coat, such as he had started to town\nin, he had on neither collar nor necktie.\n\n\"Convinced that something was amiss with him, she rushed down the\nsteps--for the house was none other than the opium den in which\nyou found me to-night--and running through the front room she\nattempted to ascend the stairs which led to the first floor. At\nthe foot of the stairs, however, she met this Lascar scoundrel of\nwhom I have spoken, who thrust her back and, aided by a Dane, who\nacts as assistant there, pushed her out into the street. Filled\nwith the most maddening doubts and fears, she rushed down the\nlane and, by rare good-fortune, met in Fresno Street a number of\nconstables with an inspector, all on their way to their beat. The\ninspector and two men accompanied her back, and in spite of the\ncontinued resistance of the proprietor, they made their way to\nthe room in which Mr. St. Clair had last been seen. There was no\nsign of him there. In fact, in the whole of that floor there was\nno one to be found save a crippled wretch of hideous aspect, who,\nit seems, made his home there. Both he and the Lascar stoutly\nswore that no one else had been in the front room during the\nafternoon. So determined was their denial that the inspector was\nstaggered, and had almost come to believe that Mrs. St. Clair had\nbeen deluded when, with a cry, she sprang at a small deal box\nwhich lay upon the table and tore the lid from it. Out there fell\na cascade of children's bricks. It was the toy which he had\npromised to bring home.\n\n\"This discovery, and the evident confusion which the cripple\nshowed, made the inspector realise that the matter was serious.\nThe rooms were carefully examined, and results all pointed to an\nabominable crime. The front room was plainly furnished as a\nsitting-room and led into a small bedroom, which looked out upon\nthe back of one of the wharves. Between the wharf and the bedroom\nwindow is a narrow strip, which is dry at low tide but is covered\nat high tide with at least four and a half feet of water. The\nbedroom window was a broad one and opened from below. On\nexamination traces of blood were to be seen upon the windowsill,\nand several scattered drops were visible upon the wooden floor of\nthe bedroom. Thrust away behind a curtain in the front room were\nall the clothes of Mr. Neville St. Clair, with the exception of\nhis coat. His boots, his socks, his hat, and his watch--all were\nthere. There were no signs of violence upon any of these\ngarments, and there were no other traces of Mr. Neville St.\nClair. Out of the window he must apparently have gone for no\nother exit could be discovered, and the ominous bloodstains upon\nthe sill gave little promise that he could save himself by\nswimming, for the tide was at its very highest at the moment of\nthe tragedy.\n\n\"And now as to the villains who seemed to be immediately\nimplicated in the matter. The Lascar was known to be a man of the\nvilest antecedents, but as, by Mrs. St. Clair's story, he was\nknown to have been at the foot of the stair within a very few\nseconds of her husband's appearance at the window, he could\nhardly have been more than an accessory to the crime. His defence\nwas one of absolute ignorance, and he protested that he had no\nknowledge as to the doings of Hugh Boone, his lodger, and that he\ncould not account in any way for the presence of the missing\ngentleman's clothes.\n\n\"So much for the Lascar manager. Now for the sinister cripple who\nlives upon the second floor of the opium den, and who was\ncertainly the last human being whose eyes rested upon Neville St.\nClair. His name is Hugh Boone, and his hideous face is one which\nis familiar to every man who goes much to the City. He is a\nprofessional beggar, though in order to avoid the police\nregulations he pretends to a small trade in wax vestas. Some\nlittle distance down Threadneedle Street, upon the left-hand\nside, there is, as you may have remarked, a small angle in the\nwall. Here it is that this creature takes his daily seat,\ncross-legged with his tiny stock of matches on his lap, and as he\nis a piteous spectacle a small rain of charity descends into the\ngreasy leather cap which lies upon the pavement beside him. I\nhave watched the fellow more than once before ever I thought of\nmaking his professional acquaintance, and I have been surprised\nat the harvest which he has reaped in a short time. His\nappearance, you see, is so remarkable that no one can pass him\nwithout observing him. A shock of orange hair, a pale face\ndisfigured by a horrible scar, which, by its contraction, has\nturned up the outer edge of his upper lip, a bulldog chin, and a\npair of very penetrating dark eyes, which present a singular\ncontrast to the colour of his hair, all mark him out from amid\nthe common crowd of mendicants and so, too, does his wit, for he\nis ever ready with a reply to any piece of chaff which may be\nthrown at him by the passers-by. This is the man whom we now\nlearn to have been the lodger at the opium den, and to have been\nthe last man to see the gentleman of whom we are in quest.\"\n\n\"But a cripple!\" said I. \"What could he have done single-handed\nagainst a man in the prime of life?\"\n\n\"He is a cripple in the sense that he walks with a limp; but in\nother respects he appears to be a powerful and well-nurtured man.\nSurely your medical experience would tell you, Watson, that\nweakness in one limb is often compensated for by exceptional\nstrength in the others.\"\n\n\"Pray continue your narrative.\"\n\n\"Mrs. St. Clair had fainted at the sight of the blood upon the\nwindow, and she was escorted home in a cab by the police, as her\npresence could be of no help to them in their investigations.\nInspector Barton, who had charge of the case, made a very careful\nexamination of the premises, but without finding anything which\nthrew any light upon the matter. One mistake had been made in not\narresting Boone instantly, as he was allowed some few minutes\nduring which he might have communicated with his friend the\nLascar, but this fault was soon remedied, and he was seized and\nsearched, without anything being found which could incriminate\nhim. There were, it is true, some blood-stains upon his right\nshirt-sleeve, but he pointed to his ring-finger, which had been\ncut near the nail, and explained that the bleeding came from\nthere, adding that he had been to the window not long before, and\nthat the stains which had been observed there came doubtless from\nthe same source. He denied strenuously having ever seen Mr.\nNeville St. Clair and swore that the presence of the clothes in\nhis room was as much a mystery to him as to the police. As to\nMrs. St. Clair's assertion that she had actually seen her husband\nat the window, he declared that she must have been either mad or\ndreaming. He was removed, loudly protesting, to the\npolice-station, while the inspector remained upon the premises in\nthe hope that the ebbing tide might afford some fresh clue.\n\n\"And it did, though they hardly found upon the mud-bank what they\nhad feared to find. It was Neville St. Clair's coat, and not\nNeville St. Clair, which lay uncovered as the tide receded. And\nwhat do you think they found in the pockets?\"\n\n\"I cannot imagine.\"\n\n\"No, I don't think you would guess. Every pocket stuffed with\npennies and half-pennies--421 pennies and 270 half-pennies. It\nwas no wonder that it had not been swept away by the tide. But a\nhuman body is a different matter. There is a fierce eddy between\nthe wharf and the house. It seemed likely enough that the\nweighted coat had remained when the stripped body had been sucked\naway into the river.\"\n\n\"But I understand that all the other clothes were found in the\nroom. Would the body be dressed in a coat alone?\"\n\n\"No, sir, but the facts might be met speciously enough. Suppose\nthat this man Boone had thrust Neville St. Clair through the\nwindow, there is no human eye which could have seen the deed.\nWhat would he do then? It would of course instantly strike him\nthat he must get rid of the tell-tale garments. He would seize\nthe coat, then, and be in the act of throwing it out, when it\nwould occur to him that it would swim and not sink. He has little\ntime, for he has heard the scuffle downstairs when the wife tried\nto force her way up, and perhaps he has already heard from his\nLascar confederate that the police are hurrying up the street.\nThere is not an instant to be lost. He rushes to some secret\nhoard, where he has accumulated the fruits of his beggary, and he\nstuffs all the coins upon which he can lay his hands into the\npockets to make sure of the coat's sinking. He throws it out, and\nwould have done the same with the other garments had not he heard\nthe rush of steps below, and only just had time to close the\nwindow when the police appeared.\"\n\n\"It certainly sounds feasible.\"\n\n\"Well, we will take it as a working hypothesis for want of a\nbetter. Boone, as I have told you, was arrested and taken to the\nstation, but it could not be shown that there had ever before\nbeen anything against him. He had for years been known as a\nprofessional beggar, but his life appeared to have been a very\nquiet and innocent one. There the matter stands at present, and\nthe questions which have to be solved--what Neville St. Clair was\ndoing in the opium den, what happened to him when there, where is\nhe now, and what Hugh Boone had to do with his disappearance--are\nall as far from a solution as ever. I confess that I cannot\nrecall any case within my experience which looked at the first\nglance so simple and yet which presented such difficulties.\"\n\nWhile Sherlock Holmes had been detailing this singular series of\nevents, we had been whirling through the outskirts of the great\ntown until the last straggling houses had been left behind, and\nwe rattled along with a country hedge upon either side of us.\nJust as he finished, however, we drove through two scattered\nvillages, where a few lights still glimmered in the windows.\n\n\"We are on the outskirts of Lee,\" said my companion. \"We have\ntouched on three English counties in our short drive, starting in\nMiddlesex, passing over an angle of Surrey, and ending in Kent.\nSee that light among the trees? That is The Cedars, and beside\nthat lamp sits a woman whose anxious ears have already, I have\nlittle doubt, caught the clink of our horse's feet.\"\n\n\"But why are you not conducting the case from Baker Street?\" I\nasked.\n\n\"Because there are many inquiries which must be made out here.\nMrs. St. Clair has most kindly put two rooms at my disposal, and\nyou may rest assured that she will have nothing but a welcome for\nmy friend and colleague. I hate to meet her, Watson, when I have\nno news of her husband. Here we are. Whoa, there, whoa!\"\n\nWe had pulled up in front of a large villa which stood within its\nown grounds. A stable-boy had run out to the horse's head, and\nspringing down, I followed Holmes up the small, winding\ngravel-drive which led to the house. As we approached, the door\nflew open, and a little blonde woman stood in the opening, clad\nin some sort of light mousseline de soie, with a touch of fluffy\npink chiffon at her neck and wrists. She stood with her figure\noutlined against the flood of light, one hand upon the door, one\nhalf-raised in her eagerness, her body slightly bent, her head\nand face protruded, with eager eyes and parted lips, a standing\nquestion.\n\n\"Well?\" she cried, \"well?\" And then, seeing that there were two\nof us, she gave a cry of hope which sank into a groan as she saw\nthat my companion shook his head and shrugged his shoulders.\n\n\"No good news?\"\n\n\"None.\"\n\n\"No bad?\"\n\n\"No.\"\n\n\"Thank God for that. But come in. You must be weary, for you have\nhad a long day.\"\n\n\"This is my friend, Dr. Watson. He has been of most vital use to\nme in several of my cases, and a lucky chance has made it\npossible for me to bring him out and associate him with this\ninvestigation.\"\n\n\"I am delighted to see you,\" said she, pressing my hand warmly.\n\"You will, I am sure, forgive anything that may be wanting in our\narrangements, when you consider the blow which has come so\nsuddenly upon us.\"\n\n\"My dear madam,\" said I, \"I am an old campaigner, and if I were\nnot I can very well see that no apology is needed. If I can be of\nany assistance, either to you or to my friend here, I shall be\nindeed happy.\"\n\n\"Now, Mr. Sherlock Holmes,\" said the lady as we entered a\nwell-lit dining-room, upon the table of which a cold supper had\nbeen laid out, \"I should very much like to ask you one or two\nplain questions, to which I beg that you will give a plain\nanswer.\"\n\n\"Certainly, madam.\"\n\n\"Do not trouble about my feelings. I am not hysterical, nor given\nto fainting. I simply wish to hear your real, real opinion.\"\n\n\"Upon what point?\"\n\n\"In your heart of hearts, do you think that Neville is alive?\"\n\nSherlock Holmes seemed to be embarrassed by the question.\n\"Frankly, now!\" she repeated, standing upon the rug and looking\nkeenly down at him as he leaned back in a basket-chair.\n\n\"Frankly, then, madam, I do not.\"\n\n\"You think that he is dead?\"\n\n\"I do.\"\n\n\"Murdered?\"\n\n\"I don't say that. Perhaps.\"\n\n\"And on what day did he meet his death?\"\n\n\"On Monday.\"\n\n\"Then perhaps, Mr. Holmes, you will be good enough to explain how\nit is that I have received a letter from him to-day.\"\n\nSherlock Holmes sprang out of his chair as if he had been\ngalvanised.\n\n\"What!\" he roared.\n\n\"Yes, to-day.\" She stood smiling, holding up a little slip of\npaper in the air.\n\n\"May I see it?\"\n\n\"Certainly.\"\n\nHe snatched it from her in his eagerness, and smoothing it out\nupon the table he drew over the lamp and examined it intently. I\nhad left my chair and was gazing at it over his shoulder. The\nenvelope was a very coarse one and was stamped with the Gravesend\npostmark and with the date of that very day, or rather of the day\nbefore, for it was considerably after midnight.\n\n\"Coarse writing,\" murmured Holmes. \"Surely this is not your\nhusband's writing, madam.\"\n\n\"No, but the enclosure is.\"\n\n\"I perceive also that whoever addressed the envelope had to go\nand inquire as to the address.\"\n\n\"How can you tell that?\"\n\n\"The name, you see, is in perfectly black ink, which has dried\nitself. The rest is of the greyish colour, which shows that\nblotting-paper has been used. If it had been written straight\noff, and then blotted, none would be of a deep black shade. This\nman has written the name, and there has then been a pause before\nhe wrote the address, which can only mean that he was not\nfamiliar with it. It is, of course, a trifle, but there is\nnothing so important as trifles. Let us now see the letter. Ha!\nthere has been an enclosure here!\"\n\n\"Yes, there was a ring. His signet-ring.\"\n\n\"And you are sure that this is your husband's hand?\"\n\n\"One of his hands.\"\n\n\"One?\"\n\n\"His hand when he wrote hurriedly. It is very unlike his usual\nwriting, and yet I know it well.\"\n\n\"'Dearest do not be frightened. All will come well. There is a\nhuge error which it may take some little time to rectify.\nWait in patience.--NEVILLE.' Written in pencil upon the fly-leaf\nof a book, octavo size, no water-mark. Hum! Posted to-day in\nGravesend by a man with a dirty thumb. Ha! And the flap has been\ngummed, if I am not very much in error, by a person who had been\nchewing tobacco. And you have no doubt that it is your husband's\nhand, madam?\"\n\n\"None. Neville wrote those words.\"\n\n\"And they were posted to-day at Gravesend. Well, Mrs. St. Clair,\nthe clouds lighten, though I should not venture to say that the\ndanger is over.\"\n\n\"But he must be alive, Mr. Holmes.\"\n\n\"Unless this is a clever forgery to put us on the wrong scent.\nThe ring, after all, proves nothing. It may have been taken from\nhim.\"\n\n\"No, no; it is, it is his very own writing!\"\n\n\"Very well. It may, however, have been written on Monday and only\nposted to-day.\"\n\n\"That is possible.\"\n\n\"If so, much may have happened between.\"\n\n\"Oh, you must not discourage me, Mr. Holmes. I know that all is\nwell with him. There is so keen a sympathy between us that I\nshould know if evil came upon him. On the very day that I saw him\nlast he cut himself in the bedroom, and yet I in the dining-room\nrushed upstairs instantly with the utmost certainty that\nsomething had happened. Do you think that I would respond to such\na trifle and yet be ignorant of his death?\"\n\n\"I have seen too much not to know that the impression of a woman\nmay be more valuable than the conclusion of an analytical\nreasoner. And in this letter you certainly have a very strong\npiece of evidence to corroborate your view. But if your husband\nis alive and able to write letters, why should he remain away\nfrom you?\"\n\n\"I cannot imagine. It is unthinkable.\"\n\n\"And on Monday he made no remarks before leaving you?\"\n\n\"No.\"\n\n\"And you were surprised to see him in Swandam Lane?\"\n\n\"Very much so.\"\n\n\"Was the window open?\"\n\n\"Yes.\"\n\n\"Then he might have called to you?\"\n\n\"He might.\"\n\n\"He only, as I understand, gave an inarticulate cry?\"\n\n\"Yes.\"\n\n\"A call for help, you thought?\"\n\n\"Yes. He waved his hands.\"\n\n\"But it might have been a cry of surprise. Astonishment at the\nunexpected sight of you might cause him to throw up his hands?\"\n\n\"It is possible.\"\n\n\"And you thought he was pulled back?\"\n\n\"He disappeared so suddenly.\"\n\n\"He might have leaped back. You did not see anyone else in the\nroom?\"\n\n\"No, but this horrible man confessed to having been there, and\nthe Lascar was at the foot of the stairs.\"\n\n\"Quite so. Your husband, as far as you could see, had his\nordinary clothes on?\"\n\n\"But without his collar or tie. I distinctly saw his bare\nthroat.\"\n\n\"Had he ever spoken of Swandam Lane?\"\n\n\"Never.\"\n\n\"Had he ever showed any signs of having taken opium?\"\n\n\"Never.\"\n\n\"Thank you, Mrs. St. Clair. Those are the principal points about\nwhich I wished to be absolutely clear. We shall now have a little\nsupper and then retire, for we may have a very busy day\nto-morrow.\"\n\nA large and comfortable double-bedded room had been placed at our\ndisposal, and I was quickly between the sheets, for I was weary\nafter my night of adventure. Sherlock Holmes was a man, however,\nwho, when he had an unsolved problem upon his mind, would go for\ndays, and even for a week, without rest, turning it over,\nrearranging his facts, looking at it from every point of view\nuntil he had either fathomed it or convinced himself that his\ndata were insufficient. It was soon evident to me that he was now\npreparing for an all-night sitting. He took off his coat and\nwaistcoat, put on a large blue dressing-gown, and then wandered\nabout the room collecting pillows from his bed and cushions from\nthe sofa and armchairs. With these he constructed a sort of\nEastern divan, upon which he perched himself cross-legged, with\nan ounce of shag tobacco and a box of matches laid out in front\nof him. In the dim light of the lamp I saw him sitting there, an\nold briar pipe between his lips, his eyes fixed vacantly upon the\ncorner of the ceiling, the blue smoke curling up from him,\nsilent, motionless, with the light shining upon his strong-set\naquiline features. So he sat as I dropped off to sleep, and so he\nsat when a sudden ejaculation caused me to wake up, and I found\nthe summer sun shining into the apartment. The pipe was still\nbetween his lips, the smoke still curled upward, and the room was\nfull of a dense tobacco haze, but nothing remained of the heap of\nshag which I had seen upon the previous night.\n\n\"Awake, Watson?\" he asked.\n\n\"Yes.\"\n\n\"Game for a morning drive?\"\n\n\"Certainly.\"\n\n\"Then dress. No one is stirring yet, but I know where the\nstable-boy sleeps, and we shall soon have the trap out.\" He\nchuckled to himself as he spoke, his eyes twinkled, and he seemed\na different man to the sombre thinker of the previous night.\n\nAs I dressed I glanced at my watch. It was no wonder that no one\nwas stirring. It was twenty-five minutes past four. I had hardly\nfinished when Holmes returned with the news that the boy was\nputting in the horse.\n\n\"I want to test a little theory of mine,\" said he, pulling on his\nboots. \"I think, Watson, that you are now standing in the\npresence of one of the most absolute fools in Europe. I deserve\nto be kicked from here to Charing Cross. But I think I have the\nkey of the affair now.\"\n\n\"And where is it?\" I asked, smiling.\n\n\"In the bathroom,\" he answered. \"Oh, yes, I am not joking,\" he\ncontinued, seeing my look of incredulity. \"I have just been\nthere, and I have taken it out, and I have got it in this\nGladstone bag. Come on, my boy, and we shall see whether it will\nnot fit the lock.\"\n\nWe made our way downstairs as quietly as possible, and out into\nthe bright morning sunshine. In the road stood our horse and\ntrap, with the half-clad stable-boy waiting at the head. We both\nsprang in, and away we dashed down the London Road. A few country\ncarts were stirring, bearing in vegetables to the metropolis, but\nthe lines of villas on either side were as silent and lifeless as\nsome city in a dream.\n\n\"It has been in some points a singular case,\" said Holmes,\nflicking the horse on into a gallop. \"I confess that I have been\nas blind as a mole, but it is better to learn wisdom late than\nnever to learn it at all.\"\n\nIn town the earliest risers were just beginning to look sleepily\nfrom their windows as we drove through the streets of the Surrey\nside. Passing down the Waterloo Bridge Road we crossed over the\nriver, and dashing up Wellington Street wheeled sharply to the\nright and found ourselves in Bow Street. Sherlock Holmes was well\nknown to the force, and the two constables at the door saluted\nhim. One of them held the horse's head while the other led us in.\n\n\"Who is on duty?\" asked Holmes.\n\n\"Inspector Bradstreet, sir.\"\n\n\"Ah, Bradstreet, how are you?\" A tall, stout official had come\ndown the stone-flagged passage, in a peaked cap and frogged\njacket. \"I wish to have a quiet word with you, Bradstreet.\"\n\"Certainly, Mr. Holmes. Step into my room here.\" It was a small,\noffice-like room, with a huge ledger upon the table, and a\ntelephone projecting from the wall. The inspector sat down at his\ndesk.\n\n\"What can I do for you, Mr. Holmes?\"\n\n\"I called about that beggarman, Boone--the one who was charged\nwith being concerned in the disappearance of Mr. Neville St.\nClair, of Lee.\"\n\n\"Yes. He was brought up and remanded for further inquiries.\"\n\n\"So I heard. You have him here?\"\n\n\"In the cells.\"\n\n\"Is he quiet?\"\n\n\"Oh, he gives no trouble. But he is a dirty scoundrel.\"\n\n\"Dirty?\"\n\n\"Yes, it is all we can do to make him wash his hands, and his\nface is as black as a tinker's. Well, when once his case has been\nsettled, he will have a regular prison bath; and I think, if you\nsaw him, you would agree with me that he needed it.\"\n\n\"I should like to see him very much.\"\n\n\"Would you? That is easily done. Come this way. You can leave\nyour bag.\"\n\n\"No, I think that I'll take it.\"\n\n\"Very good. Come this way, if you please.\" He led us down a\npassage, opened a barred door, passed down a winding stair, and\nbrought us to a whitewashed corridor with a line of doors on each\nside.\n\n\"The third on the right is his,\" said the inspector. \"Here it\nis!\" He quietly shot back a panel in the upper part of the door\nand glanced through.\n\n\"He is asleep,\" said he. \"You can see him very well.\"\n\nWe both put our eyes to the grating. The prisoner lay with his\nface towards us, in a very deep sleep, breathing slowly and\nheavily. He was a middle-sized man, coarsely clad as became his\ncalling, with a coloured shirt protruding through the rent in his\ntattered coat. He was, as the inspector had said, extremely\ndirty, but the grime which covered his face could not conceal its\nrepulsive ugliness. A broad wheal from an old scar ran right\nacross it from eye to chin, and by its contraction had turned up\none side of the upper lip, so that three teeth were exposed in a\nperpetual snarl. A shock of very bright red hair grew low over\nhis eyes and forehead.\n\n\"He's a beauty, isn't he?\" said the inspector.\n\n\"He certainly needs a wash,\" remarked Holmes. \"I had an idea that\nhe might, and I took the liberty of bringing the tools with me.\"\nHe opened the Gladstone bag as he spoke, and took out, to my\nastonishment, a very large bath-sponge.\n\n\"He! he! You are a funny one,\" chuckled the inspector.\n\n\"Now, if you will have the great goodness to open that door very\nquietly, we will soon make him cut a much more respectable\nfigure.\"\n\n\"Well, I don't know why not,\" said the inspector. \"He doesn't\nlook a credit to the Bow Street cells, does he?\" He slipped his\nkey into the lock, and we all very quietly entered the cell. The\nsleeper half turned, and then settled down once more into a deep\nslumber. Holmes stooped to the water-jug, moistened his sponge,\nand then rubbed it twice vigorously across and down the\nprisoner's face.\n\n\"Let me introduce you,\" he shouted, \"to Mr. Neville St. Clair, of\nLee, in the county of Kent.\"\n\nNever in my life have I seen such a sight. The man's face peeled\noff under the sponge like the bark from a tree. Gone was the\ncoarse brown tint! Gone, too, was the horrid scar which had\nseamed it across, and the twisted lip which had given the\nrepulsive sneer to the face! A twitch brought away the tangled\nred hair, and there, sitting up in his bed, was a pale,\nsad-faced, refined-looking man, black-haired and smooth-skinned,\nrubbing his eyes and staring about him with sleepy bewilderment.\nThen suddenly realising the exposure, he broke into a scream and\nthrew himself down with his face to the pillow.\n\n\"Great heavens!\" cried the inspector, \"it is, indeed, the missing\nman. I know him from the photograph.\"\n\nThe prisoner turned with the reckless air of a man who abandons\nhimself to his destiny. \"Be it so,\" said he. \"And pray what am I\ncharged with?\"\n\n\"With making away with Mr. Neville St.-- Oh, come, you can't be\ncharged with that unless they make a case of attempted suicide of\nit,\" said the inspector with a grin. \"Well, I have been\ntwenty-seven years in the force, but this really takes the cake.\"\n\n\"If I am Mr. Neville St. Clair, then it is obvious that no crime\nhas been committed, and that, therefore, I am illegally\ndetained.\"\n\n\"No crime, but a very great error has been committed,\" said\nHolmes. \"You would have done better to have trusted your wife.\"\n\n\"It was not the wife; it was the children,\" groaned the prisoner.\n\"God help me, I would not have them ashamed of their father. My\nGod! What an exposure! What can I do?\"\n\nSherlock Holmes sat down beside him on the couch and patted him\nkindly on the shoulder.\n\n\"If you leave it to a court of law to clear the matter up,\" said\nhe, \"of course you can hardly avoid publicity. On the other hand,\nif you convince the police authorities that there is no possible\ncase against you, I do not know that there is any reason that the\ndetails should find their way into the papers. Inspector\nBradstreet would, I am sure, make notes upon anything which you\nmight tell us and submit it to the proper authorities. The case\nwould then never go into court at all.\"\n\n\"God bless you!\" cried the prisoner passionately. \"I would have\nendured imprisonment, ay, even execution, rather than have left\nmy miserable secret as a family blot to my children.\n\n\"You are the first who have ever heard my story. My father was a\nschoolmaster in Chesterfield, where I received an excellent\neducation. I travelled in my youth, took to the stage, and\nfinally became a reporter on an evening paper in London. One day\nmy editor wished to have a series of articles upon begging in the\nmetropolis, and I volunteered to supply them. There was the point\nfrom which all my adventures started. It was only by trying\nbegging as an amateur that I could get the facts upon which to\nbase my articles. When an actor I had, of course, learned all the\nsecrets of making up, and had been famous in the green-room for\nmy skill. I took advantage now of my attainments. I painted my\nface, and to make myself as pitiable as possible I made a good\nscar and fixed one side of my lip in a twist by the aid of a\nsmall slip of flesh-coloured plaster. Then with a red head of\nhair, and an appropriate dress, I took my station in the business\npart of the city, ostensibly as a match-seller but really as a\nbeggar. For seven hours I plied my trade, and when I returned\nhome in the evening I found to my surprise that I had received no\nless than 26s. 4d.\n\n\"I wrote my articles and thought little more of the matter until,\nsome time later, I backed a bill for a friend and had a writ\nserved upon me for 25 pounds. I was at my wit's end where to get\nthe money, but a sudden idea came to me. I begged a fortnight's\ngrace from the creditor, asked for a holiday from my employers,\nand spent the time in begging in the City under my disguise. In\nten days I had the money and had paid the debt.\n\n\"Well, you can imagine how hard it was to settle down to arduous\nwork at 2 pounds a week when I knew that I could earn as much in\na day by smearing my face with a little paint, laying my cap on\nthe ground, and sitting still. It was a long fight between my\npride and the money, but the dollars won at last, and I threw up\nreporting and sat day after day in the corner which I had first\nchosen, inspiring pity by my ghastly face and filling my pockets\nwith coppers. Only one man knew my secret. He was the keeper of a\nlow den in which I used to lodge in Swandam Lane, where I could\nevery morning emerge as a squalid beggar and in the evenings\ntransform myself into a well-dressed man about town. This fellow,\na Lascar, was well paid by me for his rooms, so that I knew that\nmy secret was safe in his possession.\n\n\"Well, very soon I found that I was saving considerable sums of\nmoney. I do not mean that any beggar in the streets of London\ncould earn 700 pounds a year--which is less than my average\ntakings--but I had exceptional advantages in my power of making\nup, and also in a facility of repartee, which improved by\npractice and made me quite a recognised character in the City.\nAll day a stream of pennies, varied by silver, poured in upon me,\nand it was a very bad day in which I failed to take 2 pounds.\n\n\"As I grew richer I grew more ambitious, took a house in the\ncountry, and eventually married, without anyone having a\nsuspicion as to my real occupation. My dear wife knew that I had\nbusiness in the City. She little knew what.\n\n\"Last Monday I had finished for the day and was dressing in my\nroom above the opium den when I looked out of my window and saw,\nto my horror and astonishment, that my wife was standing in the\nstreet, with her eyes fixed full upon me. I gave a cry of\nsurprise, threw up my arms to cover my face, and, rushing to my\nconfidant, the Lascar, entreated him to prevent anyone from\ncoming up to me. I heard her voice downstairs, but I knew that\nshe could not ascend. Swiftly I threw off my clothes, pulled on\nthose of a beggar, and put on my pigments and wig. Even a wife's\neyes could not pierce so complete a disguise. But then it\noccurred to me that there might be a search in the room, and that\nthe clothes might betray me. I threw open the window, reopening\nby my violence a small cut which I had inflicted upon myself in\nthe bedroom that morning. Then I seized my coat, which was\nweighted by the coppers which I had just transferred to it from\nthe leather bag in which I carried my takings. I hurled it out of\nthe window, and it disappeared into the Thames. The other clothes\nwould have followed, but at that moment there was a rush of\nconstables up the stair, and a few minutes after I found, rather,\nI confess, to my relief, that instead of being identified as Mr.\nNeville St. Clair, I was arrested as his murderer.\n\n\"I do not know that there is anything else for me to explain. I\nwas determined to preserve my disguise as long as possible, and\nhence my preference for a dirty face. Knowing that my wife would\nbe terribly anxious, I slipped off my ring and confided it to the\nLascar at a moment when no constable was watching me, together\nwith a hurried scrawl, telling her that she had no cause to\nfear.\"\n\n\"That note only reached her yesterday,\" said Holmes.\n\n\"Good God! What a week she must have spent!\"\n\n\"The police have watched this Lascar,\" said Inspector Bradstreet,\n\"and I can quite understand that he might find it difficult to\npost a letter unobserved. Probably he handed it to some sailor\ncustomer of his, who forgot all about it for some days.\"\n\n\"That was it,\" said Holmes, nodding approvingly; \"I have no doubt\nof it. But have you never been prosecuted for begging?\"\n\n\"Many times; but what was a fine to me?\"\n\n\"It must stop here, however,\" said Bradstreet. \"If the police are\nto hush this thing up, there must be no more of Hugh Boone.\"\n\n\"I have sworn it by the most solemn oaths which a man can take.\"\n\n\"In that case I think that it is probable that no further steps\nmay be taken. But if you are found again, then all must come out.\nI am sure, Mr. Holmes, that we are very much indebted to you for\nhaving cleared the matter up. I wish I knew how you reach your\nresults.\"\n\n\"I reached this one,\" said my friend, \"by sitting upon five\npillows and consuming an ounce of shag. I think, Watson, that if\nwe drive to Baker Street we shall just be in time for breakfast.\"\n\n\n\nVII. THE ADVENTURE OF THE BLUE CARBUNCLE\n\nI had called upon my friend Sherlock Holmes upon the second\nmorning after Christmas, with the intention of wishing him the\ncompliments of the season. He was lounging upon the sofa in a\npurple dressing-gown, a pipe-rack within his reach upon the\nright, and a pile of crumpled morning papers, evidently newly\nstudied, near at hand. Beside the couch was a wooden chair, and\non the angle of the back hung a very seedy and disreputable\nhard-felt hat, much the worse for wear, and cracked in several\nplaces. A lens and a forceps lying upon the seat of the chair\nsuggested that the hat had been suspended in this manner for the\npurpose of examination.\n\n\"You are engaged,\" said I; \"perhaps I interrupt you.\"\n\n\"Not at all. I am glad to have a friend with whom I can discuss\nmy results. The matter is a perfectly trivial one\"--he jerked his\nthumb in the direction of the old hat--\"but there are points in\nconnection with it which are not entirely devoid of interest and\neven of instruction.\"\n\nI seated myself in his armchair and warmed my hands before his\ncrackling fire, for a sharp frost had set in, and the windows\nwere thick with the ice crystals. \"I suppose,\" I remarked, \"that,\nhomely as it looks, this thing has some deadly story linked on to\nit--that it is the clue which will guide you in the solution of\nsome mystery and the punishment of some crime.\"\n\n\"No, no. No crime,\" said Sherlock Holmes, laughing. \"Only one of\nthose whimsical little incidents which will happen when you have\nfour million human beings all jostling each other within the\nspace of a few square miles. Amid the action and reaction of so\ndense a swarm of humanity, every possible combination of events\nmay be expected to take place, and many a little problem will be\npresented which may be striking and bizarre without being\ncriminal. We have already had experience of such.\"\n\n\"So much so,\" I remarked, \"that of the last six cases which I\nhave added to my notes, three have been entirely free of any\nlegal crime.\"\n\n\"Precisely. You allude to my attempt to recover the Irene Adler\npapers, to the singular case of Miss Mary Sutherland, and to the\nadventure of the man with the twisted lip. Well, I have no doubt\nthat this small matter will fall into the same innocent category.\nYou know Peterson, the commissionaire?\"\n\n\"Yes.\"\n\n\"It is to him that this trophy belongs.\"\n\n\"It is his hat.\"\n\n\"No, no, he found it. Its owner is unknown. I beg that you will\nlook upon it not as a battered billycock but as an intellectual\nproblem. And, first, as to how it came here. It arrived upon\nChristmas morning, in company with a good fat goose, which is, I\nhave no doubt, roasting at this moment in front of Peterson's\nfire. The facts are these: about four o'clock on Christmas\nmorning, Peterson, who, as you know, is a very honest fellow, was\nreturning from some small jollification and was making his way\nhomeward down Tottenham Court Road. In front of him he saw, in\nthe gaslight, a tallish man, walking with a slight stagger, and\ncarrying a white goose slung over his shoulder. As he reached the\ncorner of Goodge Street, a row broke out between this stranger\nand a little knot of roughs. One of the latter knocked off the\nman's hat, on which he raised his stick to defend himself and,\nswinging it over his head, smashed the shop window behind him.\nPeterson had rushed forward to protect the stranger from his\nassailants; but the man, shocked at having broken the window, and\nseeing an official-looking person in uniform rushing towards him,\ndropped his goose, took to his heels, and vanished amid the\nlabyrinth of small streets which lie at the back of Tottenham\nCourt Road. The roughs had also fled at the appearance of\nPeterson, so that he was left in possession of the field of\nbattle, and also of the spoils of victory in the shape of this\nbattered hat and a most unimpeachable Christmas goose.\"\n\n\"Which surely he restored to their owner?\"\n\n\"My dear fellow, there lies the problem. It is true that 'For\nMrs. Henry Baker' was printed upon a small card which was tied to\nthe bird's left leg, and it is also true that the initials 'H.\nB.' are legible upon the lining of this hat, but as there are\nsome thousands of Bakers, and some hundreds of Henry Bakers in\nthis city of ours, it is not easy to restore lost property to any\none of them.\"\n\n\"What, then, did Peterson do?\"\n\n\"He brought round both hat and goose to me on Christmas morning,\nknowing that even the smallest problems are of interest to me.\nThe goose we retained until this morning, when there were signs\nthat, in spite of the slight frost, it would be well that it\nshould be eaten without unnecessary delay. Its finder has carried\nit off, therefore, to fulfil the ultimate destiny of a goose,\nwhile I continue to retain the hat of the unknown gentleman who\nlost his Christmas dinner.\"\n\n\"Did he not advertise?\"\n\n\"No.\"\n\n\"Then, what clue could you have as to his identity?\"\n\n\"Only as much as we can deduce.\"\n\n\"From his hat?\"\n\n\"Precisely.\"\n\n\"But you are joking. What can you gather from this old battered\nfelt?\"\n\n\"Here is my lens. You know my methods. What can you gather\nyourself as to the individuality of the man who has worn this\narticle?\"\n\nI took the tattered object in my hands and turned it over rather\nruefully. It was a very ordinary black hat of the usual round\nshape, hard and much the worse for wear. The lining had been of\nred silk, but was a good deal discoloured. There was no maker's\nname; but, as Holmes had remarked, the initials \"H. B.\" were\nscrawled upon one side. It was pierced in the brim for a\nhat-securer, but the elastic was missing. For the rest, it was\ncracked, exceedingly dusty, and spotted in several places,\nalthough there seemed to have been some attempt to hide the\ndiscoloured patches by smearing them with ink.\n\n\"I can see nothing,\" said I, handing it back to my friend.\n\n\"On the contrary, Watson, you can see everything. You fail,\nhowever, to reason from what you see. You are too timid in\ndrawing your inferences.\"\n\n\"Then, pray tell me what it is that you can infer from this hat?\"\n\nHe picked it up and gazed at it in the peculiar introspective\nfashion which was characteristic of him. \"It is perhaps less\nsuggestive than it might have been,\" he remarked, \"and yet there\nare a few inferences which are very distinct, and a few others\nwhich represent at least a strong balance of probability. That\nthe man was highly intellectual is of course obvious upon the\nface of it, and also that he was fairly well-to-do within the\nlast three years, although he has now fallen upon evil days. He\nhad foresight, but has less now than formerly, pointing to a\nmoral retrogression, which, when taken with the decline of his\nfortunes, seems to indicate some evil influence, probably drink,\nat work upon him. This may account also for the obvious fact that\nhis wife has ceased to love him.\"\n\n\"My dear Holmes!\"\n\n\"He has, however, retained some degree of self-respect,\" he\ncontinued, disregarding my remonstrance. \"He is a man who leads a\nsedentary life, goes out little, is out of training entirely, is\nmiddle-aged, has grizzled hair which he has had cut within the\nlast few days, and which he anoints with lime-cream. These are\nthe more patent facts which are to be deduced from his hat. Also,\nby the way, that it is extremely improbable that he has gas laid\non in his house.\"\n\n\"You are certainly joking, Holmes.\"\n\n\"Not in the least. Is it possible that even now, when I give you\nthese results, you are unable to see how they are attained?\"\n\n\"I have no doubt that I am very stupid, but I must confess that I\nam unable to follow you. For example, how did you deduce that\nthis man was intellectual?\"\n\nFor answer Holmes clapped the hat upon his head. It came right\nover the forehead and settled upon the bridge of his nose. \"It is\na question of cubic capacity,\" said he; \"a man with so large a\nbrain must have something in it.\"\n\n\"The decline of his fortunes, then?\"\n\n\"This hat is three years old. These flat brims curled at the edge\ncame in then. It is a hat of the very best quality. Look at the\nband of ribbed silk and the excellent lining. If this man could\nafford to buy so expensive a hat three years ago, and has had no\nhat since, then he has assuredly gone down in the world.\"\n\n\"Well, that is clear enough, certainly. But how about the\nforesight and the moral retrogression?\"\n\nSherlock Holmes laughed. \"Here is the foresight,\" said he putting\nhis finger upon the little disc and loop of the hat-securer.\n\"They are never sold upon hats. If this man ordered one, it is a\nsign of a certain amount of foresight, since he went out of his\nway to take this precaution against the wind. But since we see\nthat he has broken the elastic and has not troubled to replace\nit, it is obvious that he has less foresight now than formerly,\nwhich is a distinct proof of a weakening nature. On the other\nhand, he has endeavoured to conceal some of these stains upon the\nfelt by daubing them with ink, which is a sign that he has not\nentirely lost his self-respect.\"\n\n\"Your reasoning is certainly plausible.\"\n\n\"The further points, that he is middle-aged, that his hair is\ngrizzled, that it has been recently cut, and that he uses\nlime-cream, are all to be gathered from a close examination of the\nlower part of the lining. The lens discloses a large number of\nhair-ends, clean cut by the scissors of the barber. They all\nappear to be adhesive, and there is a distinct odour of\nlime-cream. This dust, you will observe, is not the gritty, grey\ndust of the street but the fluffy brown dust of the house,\nshowing that it has been hung up indoors most of the time, while\nthe marks of moisture upon the inside are proof positive that the\nwearer perspired very freely, and could therefore, hardly be in\nthe best of training.\"\n\n\"But his wife--you said that she had ceased to love him.\"\n\n\"This hat has not been brushed for weeks. When I see you, my dear\nWatson, with a week's accumulation of dust upon your hat, and\nwhen your wife allows you to go out in such a state, I shall fear\nthat you also have been unfortunate enough to lose your wife's\naffection.\"\n\n\"But he might be a bachelor.\"\n\n\"Nay, he was bringing home the goose as a peace-offering to his\nwife. Remember the card upon the bird's leg.\"\n\n\"You have an answer to everything. But how on earth do you deduce\nthat the gas is not laid on in his house?\"\n\n\"One tallow stain, or even two, might come by chance; but when I\nsee no less than five, I think that there can be little doubt\nthat the individual must be brought into frequent contact with\nburning tallow--walks upstairs at night probably with his hat in\none hand and a guttering candle in the other. Anyhow, he never\ngot tallow-stains from a gas-jet. Are you satisfied?\"\n\n\"Well, it is very ingenious,\" said I, laughing; \"but since, as\nyou said just now, there has been no crime committed, and no harm\ndone save the loss of a goose, all this seems to be rather a\nwaste of energy.\"\n\nSherlock Holmes had opened his mouth to reply, when the door flew\nopen, and Peterson, the commissionaire, rushed into the apartment\nwith flushed cheeks and the face of a man who is dazed with\nastonishment.\n\n\"The goose, Mr. Holmes! The goose, sir!\" he gasped.\n\n\"Eh? What of it, then? Has it returned to life and flapped off\nthrough the kitchen window?\" Holmes twisted himself round upon\nthe sofa to get a fairer view of the man's excited face.\n\n\"See here, sir! See what my wife found in its crop!\" He held out\nhis hand and displayed upon the centre of the palm a brilliantly\nscintillating blue stone, rather smaller than a bean in size, but\nof such purity and radiance that it twinkled like an electric\npoint in the dark hollow of his hand.\n\nSherlock Holmes sat up with a whistle. \"By Jove, Peterson!\" said\nhe, \"this is treasure trove indeed. I suppose you know what you\nhave got?\"\n\n\"A diamond, sir? A precious stone. It cuts into glass as though\nit were putty.\"\n\n\"It's more than a precious stone. It is the precious stone.\"\n\n\"Not the Countess of Morcar's blue carbuncle!\" I ejaculated.\n\n\"Precisely so. I ought to know its size and shape, seeing that I\nhave read the advertisement about it in The Times every day\nlately. It is absolutely unique, and its value can only be\nconjectured, but the reward offered of 1000 pounds is certainly\nnot within a twentieth part of the market price.\"\n\n\"A thousand pounds! Great Lord of mercy!\" The commissionaire\nplumped down into a chair and stared from one to the other of us.\n\n\"That is the reward, and I have reason to know that there are\nsentimental considerations in the background which would induce\nthe Countess to part with half her fortune if she could but\nrecover the gem.\"\n\n\"It was lost, if I remember aright, at the Hotel Cosmopolitan,\" I\nremarked.\n\n\"Precisely so, on December 22nd, just five days ago. John Horner,\na plumber, was accused of having abstracted it from the lady's\njewel-case. The evidence against him was so strong that the case\nhas been referred to the Assizes. I have some account of the\nmatter here, I believe.\" He rummaged amid his newspapers,\nglancing over the dates, until at last he smoothed one out,\ndoubled it over, and read the following paragraph:\n\n\"Hotel Cosmopolitan Jewel Robbery. John Horner, 26, plumber, was\nbrought up upon the charge of having upon the 22nd inst.,\nabstracted from the jewel-case of the Countess of Morcar the\nvaluable gem known as the blue carbuncle. James Ryder,\nupper-attendant at the hotel, gave his evidence to the effect\nthat he had shown Horner up to the dressing-room of the Countess\nof Morcar upon the day of the robbery in order that he might\nsolder the second bar of the grate, which was loose. He had\nremained with Horner some little time, but had finally been\ncalled away. On returning, he found that Horner had disappeared,\nthat the bureau had been forced open, and that the small morocco\ncasket in which, as it afterwards transpired, the Countess was\naccustomed to keep her jewel, was lying empty upon the\ndressing-table. Ryder instantly gave the alarm, and Horner was\narrested the same evening; but the stone could not be found\neither upon his person or in his rooms. Catherine Cusack, maid to\nthe Countess, deposed to having heard Ryder's cry of dismay on\ndiscovering the robbery, and to having rushed into the room,\nwhere she found matters as described by the last witness.\nInspector Bradstreet, B division, gave evidence as to the arrest\nof Horner, who struggled frantically, and protested his innocence\nin the strongest terms. Evidence of a previous conviction for\nrobbery having been given against the prisoner, the magistrate\nrefused to deal summarily with the offence, but referred it to\nthe Assizes. Horner, who had shown signs of intense emotion\nduring the proceedings, fainted away at the conclusion and was\ncarried out of court.\"\n\n\"Hum! So much for the police-court,\" said Holmes thoughtfully,\ntossing aside the paper. \"The question for us now to solve is the\nsequence of events leading from a rifled jewel-case at one end to\nthe crop of a goose in Tottenham Court Road at the other. You\nsee, Watson, our little deductions have suddenly assumed a much\nmore important and less innocent aspect. Here is the stone; the\nstone came from the goose, and the goose came from Mr. Henry\nBaker, the gentleman with the bad hat and all the other\ncharacteristics with which I have bored you. So now we must set\nourselves very seriously to finding this gentleman and\nascertaining what part he has played in this little mystery. To\ndo this, we must try the simplest means first, and these lie\nundoubtedly in an advertisement in all the evening papers. If\nthis fail, I shall have recourse to other methods.\"\n\n\"What will you say?\"\n\n\"Give me a pencil and that slip of paper. Now, then: 'Found at\nthe corner of Goodge Street, a goose and a black felt hat. Mr.\nHenry Baker can have the same by applying at 6:30 this evening at\n221B, Baker Street.' That is clear and concise.\"\n\n\"Very. But will he see it?\"\n\n\"Well, he is sure to keep an eye on the papers, since, to a poor\nman, the loss was a heavy one. He was clearly so scared by his\nmischance in breaking the window and by the approach of Peterson\nthat he thought of nothing but flight, but since then he must\nhave bitterly regretted the impulse which caused him to drop his\nbird. Then, again, the introduction of his name will cause him to\nsee it, for everyone who knows him will direct his attention to\nit. Here you are, Peterson, run down to the advertising agency\nand have this put in the evening papers.\"\n\n\"In which, sir?\"\n\n\"Oh, in the Globe, Star, Pall Mall, St. James's, Evening News,\nStandard, Echo, and any others that occur to you.\"\n\n\"Very well, sir. And this stone?\"\n\n\"Ah, yes, I shall keep the stone. Thank you. And, I say,\nPeterson, just buy a goose on your way back and leave it here\nwith me, for we must have one to give to this gentleman in place\nof the one which your family is now devouring.\"\n\nWhen the commissionaire had gone, Holmes took up the stone and\nheld it against the light. \"It's a bonny thing,\" said he. \"Just\nsee how it glints and sparkles. Of course it is a nucleus and\nfocus of crime. Every good stone is. They are the devil's pet\nbaits. In the larger and older jewels every facet may stand for a\nbloody deed. This stone is not yet twenty years old. It was found\nin the banks of the Amoy River in southern China and is remarkable\nin having every characteristic of the carbuncle, save that it is\nblue in shade instead of ruby red. In spite of its youth, it has\nalready a sinister history. There have been two murders, a\nvitriol-throwing, a suicide, and several robberies brought about\nfor the sake of this forty-grain weight of crystallised charcoal.\nWho would think that so pretty a toy would be a purveyor to the\ngallows and the prison? I'll lock it up in my strong box now and\ndrop a line to the Countess to say that we have it.\"\n\n\"Do you think that this man Horner is innocent?\"\n\n\"I cannot tell.\"\n\n\"Well, then, do you imagine that this other one, Henry Baker, had\nanything to do with the matter?\"\n\n\"It is, I think, much more likely that Henry Baker is an\nabsolutely innocent man, who had no idea that the bird which he\nwas carrying was of considerably more value than if it were made\nof solid gold. That, however, I shall determine by a very simple\ntest if we have an answer to our advertisement.\"\n\n\"And you can do nothing until then?\"\n\n\"Nothing.\"\n\n\"In that case I shall continue my professional round. But I shall\ncome back in the evening at the hour you have mentioned, for I\nshould like to see the solution of so tangled a business.\"\n\n\"Very glad to see you. I dine at seven. There is a woodcock, I\nbelieve. By the way, in view of recent occurrences, perhaps I\nought to ask Mrs. Hudson to examine its crop.\"\n\nI had been delayed at a case, and it was a little after half-past\nsix when I found myself in Baker Street once more. As I\napproached the house I saw a tall man in a Scotch bonnet with a\ncoat which was buttoned up to his chin waiting outside in the\nbright semicircle which was thrown from the fanlight. Just as I\narrived the door was opened, and we were shown up together to\nHolmes' room.\n\n\"Mr. Henry Baker, I believe,\" said he, rising from his armchair\nand greeting his visitor with the easy air of geniality which he\ncould so readily assume. \"Pray take this chair by the fire, Mr.\nBaker. It is a cold night, and I observe that your circulation is\nmore adapted for summer than for winter. Ah, Watson, you have\njust come at the right time. Is that your hat, Mr. Baker?\"\n\n\"Yes, sir, that is undoubtedly my hat.\"\n\nHe was a large man with rounded shoulders, a massive head, and a\nbroad, intelligent face, sloping down to a pointed beard of\ngrizzled brown. A touch of red in nose and cheeks, with a slight\ntremor of his extended hand, recalled Holmes' surmise as to his\nhabits. His rusty black frock-coat was buttoned right up in\nfront, with the collar turned up, and his lank wrists protruded\nfrom his sleeves without a sign of cuff or shirt. He spoke in a\nslow staccato fashion, choosing his words with care, and gave the\nimpression generally of a man of learning and letters who had had\nill-usage at the hands of fortune.\n\n\"We have retained these things for some days,\" said Holmes,\n\"because we expected to see an advertisement from you giving your\naddress. I am at a loss to know now why you did not advertise.\"\n\nOur visitor gave a rather shamefaced laugh. \"Shillings have not\nbeen so plentiful with me as they once were,\" he remarked. \"I had\nno doubt that the gang of roughs who assaulted me had carried off\nboth my hat and the bird. I did not care to spend more money in a\nhopeless attempt at recovering them.\"\n\n\"Very naturally. By the way, about the bird, we were compelled to\neat it.\"\n\n\"To eat it!\" Our visitor half rose from his chair in his\nexcitement.\n\n\"Yes, it would have been of no use to anyone had we not done so.\nBut I presume that this other goose upon the sideboard, which is\nabout the same weight and perfectly fresh, will answer your\npurpose equally well?\"\n\n\"Oh, certainly, certainly,\" answered Mr. Baker with a sigh of\nrelief.\n\n\"Of course, we still have the feathers, legs, crop, and so on of\nyour own bird, so if you wish--\"\n\nThe man burst into a hearty laugh. \"They might be useful to me as\nrelics of my adventure,\" said he, \"but beyond that I can hardly\nsee what use the disjecta membra of my late acquaintance are\ngoing to be to me. No, sir, I think that, with your permission, I\nwill confine my attentions to the excellent bird which I perceive\nupon the sideboard.\"\n\nSherlock Holmes glanced sharply across at me with a slight shrug\nof his shoulders.\n\n\"There is your hat, then, and there your bird,\" said he. \"By the\nway, would it bore you to tell me where you got the other one\nfrom? I am somewhat of a fowl fancier, and I have seldom seen a\nbetter grown goose.\"\n\n\"Certainly, sir,\" said Baker, who had risen and tucked his newly\ngained property under his arm. \"There are a few of us who\nfrequent the Alpha Inn, near the Museum--we are to be found in\nthe Museum itself during the day, you understand. This year our\ngood host, Windigate by name, instituted a goose club, by which,\non consideration of some few pence every week, we were each to\nreceive a bird at Christmas. My pence were duly paid, and the\nrest is familiar to you. I am much indebted to you, sir, for a\nScotch bonnet is fitted neither to my years nor my gravity.\" With\na comical pomposity of manner he bowed solemnly to both of us and\nstrode off upon his way.\n\n\"So much for Mr. Henry Baker,\" said Holmes when he had closed the\ndoor behind him. \"It is quite certain that he knows nothing\nwhatever about the matter. Are you hungry, Watson?\"\n\n\"Not particularly.\"\n\n\"Then I suggest that we turn our dinner into a supper and follow\nup this clue while it is still hot.\"\n\n\"By all means.\"\n\nIt was a bitter night, so we drew on our ulsters and wrapped\ncravats about our throats. Outside, the stars were shining coldly\nin a cloudless sky, and the breath of the passers-by blew out\ninto smoke like so many pistol shots. Our footfalls rang out\ncrisply and loudly as we swung through the doctors' quarter,\nWimpole Street, Harley Street, and so through Wigmore Street into\nOxford Street. In a quarter of an hour we were in Bloomsbury at\nthe Alpha Inn, which is a small public-house at the corner of one\nof the streets which runs down into Holborn. Holmes pushed open\nthe door of the private bar and ordered two glasses of beer from\nthe ruddy-faced, white-aproned landlord.\n\n\"Your beer should be excellent if it is as good as your geese,\"\nsaid he.\n\n\"My geese!\" The man seemed surprised.\n\n\"Yes. I was speaking only half an hour ago to Mr. Henry Baker,\nwho was a member of your goose club.\"\n\n\"Ah! yes, I see. But you see, sir, them's not our geese.\"\n\n\"Indeed! Whose, then?\"\n\n\"Well, I got the two dozen from a salesman in Covent Garden.\"\n\n\"Indeed? I know some of them. Which was it?\"\n\n\"Breckinridge is his name.\"\n\n\"Ah! I don't know him. Well, here's your good health landlord,\nand prosperity to your house. Good-night.\"\n\n\"Now for Mr. Breckinridge,\" he continued, buttoning up his coat\nas we came out into the frosty air. \"Remember, Watson that though\nwe have so homely a thing as a goose at one end of this chain, we\nhave at the other a man who will certainly get seven years' penal\nservitude unless we can establish his innocence. It is possible\nthat our inquiry may but confirm his guilt; but, in any case, we\nhave a line of investigation which has been missed by the police,\nand which a singular chance has placed in our hands. Let us\nfollow it out to the bitter end. Faces to the south, then, and\nquick march!\"\n\nWe passed across Holborn, down Endell Street, and so through a\nzigzag of slums to Covent Garden Market. One of the largest\nstalls bore the name of Breckinridge upon it, and the proprietor\na horsey-looking man, with a sharp face and trim side-whiskers was\nhelping a boy to put up the shutters.\n\n\"Good-evening. It's a cold night,\" said Holmes.\n\nThe salesman nodded and shot a questioning glance at my\ncompanion.\n\n\"Sold out of geese, I see,\" continued Holmes, pointing at the\nbare slabs of marble.\n\n\"Let you have five hundred to-morrow morning.\"\n\n\"That's no good.\"\n\n\"Well, there are some on the stall with the gas-flare.\"\n\n\"Ah, but I was recommended to you.\"\n\n\"Who by?\"\n\n\"The landlord of the Alpha.\"\n\n\"Oh, yes; I sent him a couple of dozen.\"\n\n\"Fine birds they were, too. Now where did you get them from?\"\n\nTo my surprise the question provoked a burst of anger from the\nsalesman.\n\n\"Now, then, mister,\" said he, with his head cocked and his arms\nakimbo, \"what are you driving at? Let's have it straight, now.\"\n\n\"It is straight enough. I should like to know who sold you the\ngeese which you supplied to the Alpha.\"\n\n\"Well then, I shan't tell you. So now!\"\n\n\"Oh, it is a matter of no importance; but I don't know why you\nshould be so warm over such a trifle.\"\n\n\"Warm! You'd be as warm, maybe, if you were as pestered as I am.\nWhen I pay good money for a good article there should be an end\nof the business; but it's 'Where are the geese?' and 'Who did you\nsell the geese to?' and 'What will you take for the geese?' One\nwould think they were the only geese in the world, to hear the\nfuss that is made over them.\"\n\n\"Well, I have no connection with any other people who have been\nmaking inquiries,\" said Holmes carelessly. \"If you won't tell us\nthe bet is off, that is all. But I'm always ready to back my\nopinion on a matter of fowls, and I have a fiver on it that the\nbird I ate is country bred.\"\n\n\"Well, then, you've lost your fiver, for it's town bred,\" snapped\nthe salesman.\n\n\"It's nothing of the kind.\"\n\n\"I say it is.\"\n\n\"I don't believe it.\"\n\n\"D'you think you know more about fowls than I, who have handled\nthem ever since I was a nipper? I tell you, all those birds that\nwent to the Alpha were town bred.\"\n\n\"You'll never persuade me to believe that.\"\n\n\"Will you bet, then?\"\n\n\"It's merely taking your money, for I know that I am right. But\nI'll have a sovereign on with you, just to teach you not to be\nobstinate.\"\n\nThe salesman chuckled grimly. \"Bring me the books, Bill,\" said\nhe.\n\nThe small boy brought round a small thin volume and a great\ngreasy-backed one, laying them out together beneath the hanging\nlamp.\n\n\"Now then, Mr. Cocksure,\" said the salesman, \"I thought that I\nwas out of geese, but before I finish you'll find that there is\nstill one left in my shop. You see this little book?\"\n\n\"Well?\"\n\n\"That's the list of the folk from whom I buy. D'you see? Well,\nthen, here on this page are the country folk, and the numbers\nafter their names are where their accounts are in the big ledger.\nNow, then! You see this other page in red ink? Well, that is a\nlist of my town suppliers. Now, look at that third name. Just\nread it out to me.\"\n\n\"Mrs. Oakshott, 117, Brixton Road--249,\" read Holmes.\n\n\"Quite so. Now turn that up in the ledger.\"\n\nHolmes turned to the page indicated. \"Here you are, 'Mrs.\nOakshott, 117, Brixton Road, egg and poultry supplier.'\"\n\n\"Now, then, what's the last entry?\"\n\n\"'December 22nd. Twenty-four geese at 7s. 6d.'\"\n\n\"Quite so. There you are. And underneath?\"\n\n\"'Sold to Mr. Windigate of the Alpha, at 12s.'\"\n\n\"What have you to say now?\"\n\nSherlock Holmes looked deeply chagrined. He drew a sovereign from\nhis pocket and threw it down upon the slab, turning away with the\nair of a man whose disgust is too deep for words. A few yards off\nhe stopped under a lamp-post and laughed in the hearty, noiseless\nfashion which was peculiar to him.\n\n\"When you see a man with whiskers of that cut and the 'Pink 'un'\nprotruding out of his pocket, you can always draw him by a bet,\"\nsaid he. \"I daresay that if I had put 100 pounds down in front of\nhim, that man would not have given me such complete information\nas was drawn from him by the idea that he was doing me on a\nwager. Well, Watson, we are, I fancy, nearing the end of our\nquest, and the only point which remains to be determined is\nwhether we should go on to this Mrs. Oakshott to-night, or\nwhether we should reserve it for to-morrow. It is clear from what\nthat surly fellow said that there are others besides ourselves\nwho are anxious about the matter, and I should--\"\n\nHis remarks were suddenly cut short by a loud hubbub which broke\nout from the stall which we had just left. Turning round we saw a\nlittle rat-faced fellow standing in the centre of the circle of\nyellow light which was thrown by the swinging lamp, while\nBreckinridge, the salesman, framed in the door of his stall, was\nshaking his fists fiercely at the cringing figure.\n\n\"I've had enough of you and your geese,\" he shouted. \"I wish you\nwere all at the devil together. If you come pestering me any more\nwith your silly talk I'll set the dog at you. You bring Mrs.\nOakshott here and I'll answer her, but what have you to do with\nit? Did I buy the geese off you?\"\n\n\"No; but one of them was mine all the same,\" whined the little\nman.\n\n\"Well, then, ask Mrs. Oakshott for it.\"\n\n\"She told me to ask you.\"\n\n\"Well, you can ask the King of Proosia, for all I care. I've had\nenough of it. Get out of this!\" He rushed fiercely forward, and\nthe inquirer flitted away into the darkness.\n\n\"Ha! this may save us a visit to Brixton Road,\" whispered Holmes.\n\"Come with me, and we will see what is to be made of this\nfellow.\" Striding through the scattered knots of people who\nlounged round the flaring stalls, my companion speedily overtook\nthe little man and touched him upon the shoulder. He sprang\nround, and I could see in the gas-light that every vestige of\ncolour had been driven from his face.\n\n\"Who are you, then? What do you want?\" he asked in a quavering\nvoice.\n\n\"You will excuse me,\" said Holmes blandly, \"but I could not help\noverhearing the questions which you put to the salesman just now.\nI think that I could be of assistance to you.\"\n\n\"You? Who are you? How could you know anything of the matter?\"\n\n\"My name is Sherlock Holmes. It is my business to know what other\npeople don't know.\"\n\n\"But you can know nothing of this?\"\n\n\"Excuse me, I know everything of it. You are endeavouring to\ntrace some geese which were sold by Mrs. Oakshott, of Brixton\nRoad, to a salesman named Breckinridge, by him in turn to Mr.\nWindigate, of the Alpha, and by him to his club, of which Mr.\nHenry Baker is a member.\"\n\n\"Oh, sir, you are the very man whom I have longed to meet,\" cried\nthe little fellow with outstretched hands and quivering fingers.\n\"I can hardly explain to you how interested I am in this matter.\"\n\nSherlock Holmes hailed a four-wheeler which was passing. \"In that\ncase we had better discuss it in a cosy room rather than in this\nwind-swept market-place,\" said he. \"But pray tell me, before we\ngo farther, who it is that I have the pleasure of assisting.\"\n\nThe man hesitated for an instant. \"My name is John Robinson,\" he\nanswered with a sidelong glance.\n\n\"No, no; the real name,\" said Holmes sweetly. \"It is always\nawkward doing business with an alias.\"\n\nA flush sprang to the white cheeks of the stranger. \"Well then,\"\nsaid he, \"my real name is James Ryder.\"\n\n\"Precisely so. Head attendant at the Hotel Cosmopolitan. Pray\nstep into the cab, and I shall soon be able to tell you\neverything which you would wish to know.\"\n\nThe little man stood glancing from one to the other of us with\nhalf-frightened, half-hopeful eyes, as one who is not sure\nwhether he is on the verge of a windfall or of a catastrophe.\nThen he stepped into the cab, and in half an hour we were back in\nthe sitting-room at Baker Street. Nothing had been said during\nour drive, but the high, thin breathing of our new companion, and\nthe claspings and unclaspings of his hands, spoke of the nervous\ntension within him.\n\n\"Here we are!\" said Holmes cheerily as we filed into the room.\n\"The fire looks very seasonable in this weather. You look cold,\nMr. Ryder. Pray take the basket-chair. I will just put on my\nslippers before we settle this little matter of yours. Now, then!\nYou want to know what became of those geese?\"\n\n\"Yes, sir.\"\n\n\"Or rather, I fancy, of that goose. It was one bird, I imagine in\nwhich you were interested--white, with a black bar across the\ntail.\"\n\nRyder quivered with emotion. \"Oh, sir,\" he cried, \"can you tell\nme where it went to?\"\n\n\"It came here.\"\n\n\"Here?\"\n\n\"Yes, and a most remarkable bird it proved. I don't wonder that\nyou should take an interest in it. It laid an egg after it was\ndead--the bonniest, brightest little blue egg that ever was seen.\nI have it here in my museum.\"\n\nOur visitor staggered to his feet and clutched the mantelpiece\nwith his right hand. Holmes unlocked his strong-box and held up\nthe blue carbuncle, which shone out like a star, with a cold,\nbrilliant, many-pointed radiance. Ryder stood glaring with a\ndrawn face, uncertain whether to claim or to disown it.\n\n\"The game's up, Ryder,\" said Holmes quietly. \"Hold up, man, or\nyou'll be into the fire! Give him an arm back into his chair,\nWatson. He's not got blood enough to go in for felony with\nimpunity. Give him a dash of brandy. So! Now he looks a little\nmore human. What a shrimp it is, to be sure!\"\n\nFor a moment he had staggered and nearly fallen, but the brandy\nbrought a tinge of colour into his cheeks, and he sat staring\nwith frightened eyes at his accuser.\n\n\"I have almost every link in my hands, and all the proofs which I\ncould possibly need, so there is little which you need tell me.\nStill, that little may as well be cleared up to make the case\ncomplete. You had heard, Ryder, of this blue stone of the\nCountess of Morcar's?\"\n\n\"It was Catherine Cusack who told me of it,\" said he in a\ncrackling voice.\n\n\"I see--her ladyship's waiting-maid. Well, the temptation of\nsudden wealth so easily acquired was too much for you, as it has\nbeen for better men before you; but you were not very scrupulous\nin the means you used. It seems to me, Ryder, that there is the\nmaking of a very pretty villain in you. You knew that this man\nHorner, the plumber, had been concerned in some such matter\nbefore, and that suspicion would rest the more readily upon him.\nWhat did you do, then? You made some small job in my lady's\nroom--you and your confederate Cusack--and you managed that he\nshould be the man sent for. Then, when he had left, you rifled\nthe jewel-case, raised the alarm, and had this unfortunate man\narrested. You then--\"\n\nRyder threw himself down suddenly upon the rug and clutched at my\ncompanion's knees. \"For God's sake, have mercy!\" he shrieked.\n\"Think of my father! Of my mother! It would break their hearts. I\nnever went wrong before! I never will again. I swear it. I'll\nswear it on a Bible. Oh, don't bring it into court! For Christ's\nsake, don't!\"\n\n\"Get back into your chair!\" said Holmes sternly. \"It is very well\nto cringe and crawl now, but you thought little enough of this\npoor Horner in the dock for a crime of which he knew nothing.\"\n\n\"I will fly, Mr. Holmes. I will leave the country, sir. Then the\ncharge against him will break down.\"\n\n\"Hum! We will talk about that. And now let us hear a true account\nof the next act. How came the stone into the goose, and how came\nthe goose into the open market? Tell us the truth, for there lies\nyour only hope of safety.\"\n\nRyder passed his tongue over his parched lips. \"I will tell you\nit just as it happened, sir,\" said he. \"When Horner had been\narrested, it seemed to me that it would be best for me to get\naway with the stone at once, for I did not know at what moment\nthe police might not take it into their heads to search me and my\nroom. There was no place about the hotel where it would be safe.\nI went out, as if on some commission, and I made for my sister's\nhouse. She had married a man named Oakshott, and lived in Brixton\nRoad, where she fattened fowls for the market. All the way there\nevery man I met seemed to me to be a policeman or a detective;\nand, for all that it was a cold night, the sweat was pouring down\nmy face before I came to the Brixton Road. My sister asked me\nwhat was the matter, and why I was so pale; but I told her that I\nhad been upset by the jewel robbery at the hotel. Then I went\ninto the back yard and smoked a pipe and wondered what it would\nbe best to do.\n\n\"I had a friend once called Maudsley, who went to the bad, and\nhas just been serving his time in Pentonville. One day he had met\nme, and fell into talk about the ways of thieves, and how they\ncould get rid of what they stole. I knew that he would be true to\nme, for I knew one or two things about him; so I made up my mind\nto go right on to Kilburn, where he lived, and take him into my\nconfidence. He would show me how to turn the stone into money.\nBut how to get to him in safety? I thought of the agonies I had\ngone through in coming from the hotel. I might at any moment be\nseized and searched, and there would be the stone in my waistcoat\npocket. I was leaning against the wall at the time and looking at\nthe geese which were waddling about round my feet, and suddenly\nan idea came into my head which showed me how I could beat the\nbest detective that ever lived.\n\n\"My sister had told me some weeks before that I might have the\npick of her geese for a Christmas present, and I knew that she\nwas always as good as her word. I would take my goose now, and in\nit I would carry my stone to Kilburn. There was a little shed in\nthe yard, and behind this I drove one of the birds--a fine big\none, white, with a barred tail. I caught it, and prying its bill\nopen, I thrust the stone down its throat as far as my finger\ncould reach. The bird gave a gulp, and I felt the stone pass\nalong its gullet and down into its crop. But the creature flapped\nand struggled, and out came my sister to know what was the\nmatter. As I turned to speak to her the brute broke loose and\nfluttered off among the others.\n\n\"'Whatever were you doing with that bird, Jem?' says she.\n\n\"'Well,' said I, 'you said you'd give me one for Christmas, and I\nwas feeling which was the fattest.'\n\n\"'Oh,' says she, 'we've set yours aside for you--Jem's bird, we\ncall it. It's the big white one over yonder. There's twenty-six\nof them, which makes one for you, and one for us, and two dozen\nfor the market.'\n\n\"'Thank you, Maggie,' says I; 'but if it is all the same to you,\nI'd rather have that one I was handling just now.'\n\n\"'The other is a good three pound heavier,' said she, 'and we\nfattened it expressly for you.'\n\n\"'Never mind. I'll have the other, and I'll take it now,' said I.\n\n\"'Oh, just as you like,' said she, a little huffed. 'Which is it\nyou want, then?'\n\n\"'That white one with the barred tail, right in the middle of the\nflock.'\n\n\"'Oh, very well. Kill it and take it with you.'\n\n\"Well, I did what she said, Mr. Holmes, and I carried the bird\nall the way to Kilburn. I told my pal what I had done, for he was\na man that it was easy to tell a thing like that to. He laughed\nuntil he choked, and we got a knife and opened the goose. My\nheart turned to water, for there was no sign of the stone, and I\nknew that some terrible mistake had occurred. I left the bird,\nrushed back to my sister's, and hurried into the back yard. There\nwas not a bird to be seen there.\n\n\"'Where are they all, Maggie?' I cried.\n\n\"'Gone to the dealer's, Jem.'\n\n\"'Which dealer's?'\n\n\"'Breckinridge, of Covent Garden.'\n\n\"'But was there another with a barred tail?' I asked, 'the same\nas the one I chose?'\n\n\"'Yes, Jem; there were two barred-tailed ones, and I could never\ntell them apart.'\n\n\"Well, then, of course I saw it all, and I ran off as hard as my\nfeet would carry me to this man Breckinridge; but he had sold the\nlot at once, and not one word would he tell me as to where they\nhad gone. You heard him yourselves to-night. Well, he has always\nanswered me like that. My sister thinks that I am going mad.\nSometimes I think that I am myself. And now--and now I am myself\na branded thief, without ever having touched the wealth for which\nI sold my character. God help me! God help me!\" He burst into\nconvulsive sobbing, with his face buried in his hands.\n\nThere was a long silence, broken only by his heavy breathing and\nby the measured tapping of Sherlock Holmes' finger-tips upon the\nedge of the table. Then my friend rose and threw open the door.\n\n\"Get out!\" said he.\n\n\"What, sir! Oh, Heaven bless you!\"\n\n\"No more words. Get out!\"\n\nAnd no more words were needed. There was a rush, a clatter upon\nthe stairs, the bang of a door, and the crisp rattle of running\nfootfalls from the street.\n\n\"After all, Watson,\" said Holmes, reaching up his hand for his\nclay pipe, \"I am not retained by the police to supply their\ndeficiencies. If Horner were in danger it would be another thing;\nbut this fellow will not appear against him, and the case must\ncollapse. I suppose that I am commuting a felony, but it is just\npossible that I am saving a soul. This fellow will not go wrong\nagain; he is too terribly frightened. Send him to gaol now, and\nyou make him a gaol-bird for life. Besides, it is the season of\nforgiveness. Chance has put in our way a most singular and\nwhimsical problem, and its solution is its own reward. If you\nwill have the goodness to touch the bell, Doctor, we will begin\nanother investigation, in which, also a bird will be the chief\nfeature.\"\n\n\n\nVIII. THE ADVENTURE OF THE SPECKLED BAND\n\nOn glancing over my notes of the seventy odd cases in which I\nhave during the last eight years studied the methods of my friend\nSherlock Holmes, I find many tragic, some comic, a large number\nmerely strange, but none commonplace; for, working as he did\nrather for the love of his art than for the acquirement of\nwealth, he refused to associate himself with any investigation\nwhich did not tend towards the unusual, and even the fantastic.\nOf all these varied cases, however, I cannot recall any which\npresented more singular features than that which was associated\nwith the well-known Surrey family of the Roylotts of Stoke Moran.\nThe events in question occurred in the early days of my\nassociation with Holmes, when we were sharing rooms as bachelors\nin Baker Street. It is possible that I might have placed them\nupon record before, but a promise of secrecy was made at the\ntime, from which I have only been freed during the last month by\nthe untimely death of the lady to whom the pledge was given. It\nis perhaps as well that the facts should now come to light, for I\nhave reasons to know that there are widespread rumours as to the\ndeath of Dr. Grimesby Roylott which tend to make the matter even\nmore terrible than the truth.\n\nIt was early in April in the year '83 that I woke one morning to\nfind Sherlock Holmes standing, fully dressed, by the side of my\nbed. He was a late riser, as a rule, and as the clock on the\nmantelpiece showed me that it was only a quarter-past seven, I\nblinked up at him in some surprise, and perhaps just a little\nresentment, for I was myself regular in my habits.\n\n\"Very sorry to knock you up, Watson,\" said he, \"but it's the\ncommon lot this morning. Mrs. Hudson has been knocked up, she\nretorted upon me, and I on you.\"\n\n\"What is it, then--a fire?\"\n\n\"No; a client. It seems that a young lady has arrived in a\nconsiderable state of excitement, who insists upon seeing me. She\nis waiting now in the sitting-room. Now, when young ladies wander\nabout the metropolis at this hour of the morning, and knock\nsleepy people up out of their beds, I presume that it is\nsomething very pressing which they have to communicate. Should it\nprove to be an interesting case, you would, I am sure, wish to\nfollow it from the outset. I thought, at any rate, that I should\ncall you and give you the chance.\"\n\n\"My dear fellow, I would not miss it for anything.\"\n\nI had no keener pleasure than in following Holmes in his\nprofessional investigations, and in admiring the rapid\ndeductions, as swift as intuitions, and yet always founded on a\nlogical basis with which he unravelled the problems which were\nsubmitted to him. I rapidly threw on my clothes and was ready in\na few minutes to accompany my friend down to the sitting-room. A\nlady dressed in black and heavily veiled, who had been sitting in\nthe window, rose as we entered.\n\n\"Good-morning, madam,\" said Holmes cheerily. \"My name is Sherlock\nHolmes. This is my intimate friend and associate, Dr. Watson,\nbefore whom you can speak as freely as before myself. Ha! I am\nglad to see that Mrs. Hudson has had the good sense to light the\nfire. Pray draw up to it, and I shall order you a cup of hot\ncoffee, for I observe that you are shivering.\"\n\n\"It is not cold which makes me shiver,\" said the woman in a low\nvoice, changing her seat as requested.\n\n\"What, then?\"\n\n\"It is fear, Mr. Holmes. It is terror.\" She raised her veil as\nshe spoke, and we could see that she was indeed in a pitiable\nstate of agitation, her face all drawn and grey, with restless\nfrightened eyes, like those of some hunted animal. Her features\nand figure were those of a woman of thirty, but her hair was shot\nwith premature grey, and her expression was weary and haggard.\nSherlock Holmes ran her over with one of his quick,\nall-comprehensive glances.\n\n\"You must not fear,\" said he soothingly, bending forward and\npatting her forearm. \"We shall soon set matters right, I have no\ndoubt. You have come in by train this morning, I see.\"\n\n\"You know me, then?\"\n\n\"No, but I observe the second half of a return ticket in the palm\nof your left glove. You must have started early, and yet you had\na good drive in a dog-cart, along heavy roads, before you reached\nthe station.\"\n\nThe lady gave a violent start and stared in bewilderment at my\ncompanion.\n\n\"There is no mystery, my dear madam,\" said he, smiling. \"The left\narm of your jacket is spattered with mud in no less than seven\nplaces. The marks are perfectly fresh. There is no vehicle save a\ndog-cart which throws up mud in that way, and then only when you\nsit on the left-hand side of the driver.\"\n\n\"Whatever your reasons may be, you are perfectly correct,\" said\nshe. \"I started from home before six, reached Leatherhead at\ntwenty past, and came in by the first train to Waterloo. Sir, I\ncan stand this strain no longer; I shall go mad if it continues.\nI have no one to turn to--none, save only one, who cares for me,\nand he, poor fellow, can be of little aid. I have heard of you,\nMr. Holmes; I have heard of you from Mrs. Farintosh, whom you\nhelped in the hour of her sore need. It was from her that I had\nyour address. Oh, sir, do you not think that you could help me,\ntoo, and at least throw a little light through the dense darkness\nwhich surrounds me? At present it is out of my power to reward\nyou for your services, but in a month or six weeks I shall be\nmarried, with the control of my own income, and then at least you\nshall not find me ungrateful.\"\n\nHolmes turned to his desk and, unlocking it, drew out a small\ncase-book, which he consulted.\n\n\"Farintosh,\" said he. \"Ah yes, I recall the case; it was\nconcerned with an opal tiara. I think it was before your time,\nWatson. I can only say, madam, that I shall be happy to devote\nthe same care to your case as I did to that of your friend. As to\nreward, my profession is its own reward; but you are at liberty\nto defray whatever expenses I may be put to, at the time which\nsuits you best. And now I beg that you will lay before us\neverything that may help us in forming an opinion upon the\nmatter.\"\n\n\"Alas!\" replied our visitor, \"the very horror of my situation\nlies in the fact that my fears are so vague, and my suspicions\ndepend so entirely upon small points, which might seem trivial to\nanother, that even he to whom of all others I have a right to\nlook for help and advice looks upon all that I tell him about it\nas the fancies of a nervous woman. He does not say so, but I can\nread it from his soothing answers and averted eyes. But I have\nheard, Mr. Holmes, that you can see deeply into the manifold\nwickedness of the human heart. You may advise me how to walk amid\nthe dangers which encompass me.\"\n\n\"I am all attention, madam.\"\n\n\"My name is Helen Stoner, and I am living with my stepfather, who\nis the last survivor of one of the oldest Saxon families in\nEngland, the Roylotts of Stoke Moran, on the western border of\nSurrey.\"\n\nHolmes nodded his head. \"The name is familiar to me,\" said he.\n\n\"The family was at one time among the richest in England, and the\nestates extended over the borders into Berkshire in the north,\nand Hampshire in the west. In the last century, however, four\nsuccessive heirs were of a dissolute and wasteful disposition,\nand the family ruin was eventually completed by a gambler in the\ndays of the Regency. Nothing was left save a few acres of ground,\nand the two-hundred-year-old house, which is itself crushed under\na heavy mortgage. The last squire dragged out his existence\nthere, living the horrible life of an aristocratic pauper; but\nhis only son, my stepfather, seeing that he must adapt himself to\nthe new conditions, obtained an advance from a relative, which\nenabled him to take a medical degree and went out to Calcutta,\nwhere, by his professional skill and his force of character, he\nestablished a large practice. In a fit of anger, however, caused\nby some robberies which had been perpetrated in the house, he\nbeat his native butler to death and narrowly escaped a capital\nsentence. As it was, he suffered a long term of imprisonment and\nafterwards returned to England a morose and disappointed man.\n\n\"When Dr. Roylott was in India he married my mother, Mrs. Stoner,\nthe young widow of Major-General Stoner, of the Bengal Artillery.\nMy sister Julia and I were twins, and we were only two years old\nat the time of my mother's re-marriage. She had a considerable\nsum of money--not less than 1000 pounds a year--and this she\nbequeathed to Dr. Roylott entirely while we resided with him,\nwith a provision that a certain annual sum should be allowed to\neach of us in the event of our marriage. Shortly after our return\nto England my mother died--she was killed eight years ago in a\nrailway accident near Crewe. Dr. Roylott then abandoned his\nattempts to establish himself in practice in London and took us\nto live with him in the old ancestral house at Stoke Moran. The\nmoney which my mother had left was enough for all our wants, and\nthere seemed to be no obstacle to our happiness.\n\n\"But a terrible change came over our stepfather about this time.\nInstead of making friends and exchanging visits with our\nneighbours, who had at first been overjoyed to see a Roylott of\nStoke Moran back in the old family seat, he shut himself up in\nhis house and seldom came out save to indulge in ferocious\nquarrels with whoever might cross his path. Violence of temper\napproaching to mania has been hereditary in the men of the\nfamily, and in my stepfather's case it had, I believe, been\nintensified by his long residence in the tropics. A series of\ndisgraceful brawls took place, two of which ended in the\npolice-court, until at last he became the terror of the village,\nand the folks would fly at his approach, for he is a man of\nimmense strength, and absolutely uncontrollable in his anger.\n\n\"Last week he hurled the local blacksmith over a parapet into a\nstream, and it was only by paying over all the money which I\ncould gather together that I was able to avert another public\nexposure. He had no friends at all save the wandering gipsies,\nand he would give these vagabonds leave to encamp upon the few\nacres of bramble-covered land which represent the family estate,\nand would accept in return the hospitality of their tents,\nwandering away with them sometimes for weeks on end. He has a\npassion also for Indian animals, which are sent over to him by a\ncorrespondent, and he has at this moment a cheetah and a baboon,\nwhich wander freely over his grounds and are feared by the\nvillagers almost as much as their master.\n\n\"You can imagine from what I say that my poor sister Julia and I\nhad no great pleasure in our lives. No servant would stay with\nus, and for a long time we did all the work of the house. She was\nbut thirty at the time of her death, and yet her hair had already\nbegun to whiten, even as mine has.\"\n\n\"Your sister is dead, then?\"\n\n\"She died just two years ago, and it is of her death that I wish\nto speak to you. You can understand that, living the life which I\nhave described, we were little likely to see anyone of our own\nage and position. We had, however, an aunt, my mother's maiden\nsister, Miss Honoria Westphail, who lives near Harrow, and we\nwere occasionally allowed to pay short visits at this lady's\nhouse. Julia went there at Christmas two years ago, and met there\na half-pay major of marines, to whom she became engaged. My\nstepfather learned of the engagement when my sister returned and\noffered no objection to the marriage; but within a fortnight of\nthe day which had been fixed for the wedding, the terrible event\noccurred which has deprived me of my only companion.\"\n\nSherlock Holmes had been leaning back in his chair with his eyes\nclosed and his head sunk in a cushion, but he half opened his\nlids now and glanced across at his visitor.\n\n\"Pray be precise as to details,\" said he.\n\n\"It is easy for me to be so, for every event of that dreadful\ntime is seared into my memory. The manor-house is, as I have\nalready said, very old, and only one wing is now inhabited. The\nbedrooms in this wing are on the ground floor, the sitting-rooms\nbeing in the central block of the buildings. Of these bedrooms\nthe first is Dr. Roylott's, the second my sister's, and the third\nmy own. There is no communication between them, but they all open\nout into the same corridor. Do I make myself plain?\"\n\n\"Perfectly so.\"\n\n\"The windows of the three rooms open out upon the lawn. That\nfatal night Dr. Roylott had gone to his room early, though we\nknew that he had not retired to rest, for my sister was troubled\nby the smell of the strong Indian cigars which it was his custom\nto smoke. She left her room, therefore, and came into mine, where\nshe sat for some time, chatting about her approaching wedding. At\neleven o'clock she rose to leave me, but she paused at the door\nand looked back.\n\n\"'Tell me, Helen,' said she, 'have you ever heard anyone whistle\nin the dead of the night?'\n\n\"'Never,' said I.\n\n\"'I suppose that you could not possibly whistle, yourself, in\nyour sleep?'\n\n\"'Certainly not. But why?'\n\n\"'Because during the last few nights I have always, about three\nin the morning, heard a low, clear whistle. I am a light sleeper,\nand it has awakened me. I cannot tell where it came from--perhaps\nfrom the next room, perhaps from the lawn. I thought that I would\njust ask you whether you had heard it.'\n\n\"'No, I have not. It must be those wretched gipsies in the\nplantation.'\n\n\"'Very likely. And yet if it were on the lawn, I wonder that you\ndid not hear it also.'\n\n\"'Ah, but I sleep more heavily than you.'\n\n\"'Well, it is of no great consequence, at any rate.' She smiled\nback at me, closed my door, and a few moments later I heard her\nkey turn in the lock.\"\n\n\"Indeed,\" said Holmes. \"Was it your custom always to lock\nyourselves in at night?\"\n\n\"Always.\"\n\n\"And why?\"\n\n\"I think that I mentioned to you that the doctor kept a cheetah\nand a baboon. We had no feeling of security unless our doors were\nlocked.\"\n\n\"Quite so. Pray proceed with your statement.\"\n\n\"I could not sleep that night. A vague feeling of impending\nmisfortune impressed me. My sister and I, you will recollect,\nwere twins, and you know how subtle are the links which bind two\nsouls which are so closely allied. It was a wild night. The wind\nwas howling outside, and the rain was beating and splashing\nagainst the windows. Suddenly, amid all the hubbub of the gale,\nthere burst forth the wild scream of a terrified woman. I knew\nthat it was my sister's voice. I sprang from my bed, wrapped a\nshawl round me, and rushed into the corridor. As I opened my door\nI seemed to hear a low whistle, such as my sister described, and\na few moments later a clanging sound, as if a mass of metal had\nfallen. As I ran down the passage, my sister's door was unlocked,\nand revolved slowly upon its hinges. I stared at it\nhorror-stricken, not knowing what was about to issue from it. By\nthe light of the corridor-lamp I saw my sister appear at the\nopening, her face blanched with terror, her hands groping for\nhelp, her whole figure swaying to and fro like that of a\ndrunkard. I ran to her and threw my arms round her, but at that\nmoment her knees seemed to give way and she fell to the ground.\nShe writhed as one who is in terrible pain, and her limbs were\ndreadfully convulsed. At first I thought that she had not\nrecognised me, but as I bent over her she suddenly shrieked out\nin a voice which I shall never forget, 'Oh, my God! Helen! It was\nthe band! The speckled band!' There was something else which she\nwould fain have said, and she stabbed with her finger into the\nair in the direction of the doctor's room, but a fresh convulsion\nseized her and choked her words. I rushed out, calling loudly for\nmy stepfather, and I met him hastening from his room in his\ndressing-gown. When he reached my sister's side she was\nunconscious, and though he poured brandy down her throat and sent\nfor medical aid from the village, all efforts were in vain, for\nshe slowly sank and died without having recovered her\nconsciousness. Such was the dreadful end of my beloved sister.\"\n\n\"One moment,\" said Holmes, \"are you sure about this whistle and\nmetallic sound? Could you swear to it?\"\n\n\"That was what the county coroner asked me at the inquiry. It is\nmy strong impression that I heard it, and yet, among the crash of\nthe gale and the creaking of an old house, I may possibly have\nbeen deceived.\"\n\n\"Was your sister dressed?\"\n\n\"No, she was in her night-dress. In her right hand was found the\ncharred stump of a match, and in her left a match-box.\"\n\n\"Showing that she had struck a light and looked about her when\nthe alarm took place. That is important. And what conclusions did\nthe coroner come to?\"\n\n\"He investigated the case with great care, for Dr. Roylott's\nconduct had long been notorious in the county, but he was unable\nto find any satisfactory cause of death. My evidence showed that\nthe door had been fastened upon the inner side, and the windows\nwere blocked by old-fashioned shutters with broad iron bars,\nwhich were secured every night. The walls were carefully sounded,\nand were shown to be quite solid all round, and the flooring was\nalso thoroughly examined, with the same result. The chimney is\nwide, but is barred up by four large staples. It is certain,\ntherefore, that my sister was quite alone when she met her end.\nBesides, there were no marks of any violence upon her.\"\n\n\"How about poison?\"\n\n\"The doctors examined her for it, but without success.\"\n\n\"What do you think that this unfortunate lady died of, then?\"\n\n\"It is my belief that she died of pure fear and nervous shock,\nthough what it was that frightened her I cannot imagine.\"\n\n\"Were there gipsies in the plantation at the time?\"\n\n\"Yes, there are nearly always some there.\"\n\n\"Ah, and what did you gather from this allusion to a band--a\nspeckled band?\"\n\n\"Sometimes I have thought that it was merely the wild talk of\ndelirium, sometimes that it may have referred to some band of\npeople, perhaps to these very gipsies in the plantation. I do not\nknow whether the spotted handkerchiefs which so many of them wear\nover their heads might have suggested the strange adjective which\nshe used.\"\n\nHolmes shook his head like a man who is far from being satisfied.\n\n\"These are very deep waters,\" said he; \"pray go on with your\nnarrative.\"\n\n\"Two years have passed since then, and my life has been until\nlately lonelier than ever. A month ago, however, a dear friend,\nwhom I have known for many years, has done me the honour to ask\nmy hand in marriage. His name is Armitage--Percy Armitage--the\nsecond son of Mr. Armitage, of Crane Water, near Reading. My\nstepfather has offered no opposition to the match, and we are to\nbe married in the course of the spring. Two days ago some repairs\nwere started in the west wing of the building, and my bedroom\nwall has been pierced, so that I have had to move into the\nchamber in which my sister died, and to sleep in the very bed in\nwhich she slept. Imagine, then, my thrill of terror when last\nnight, as I lay awake, thinking over her terrible fate, I\nsuddenly heard in the silence of the night the low whistle which\nhad been the herald of her own death. I sprang up and lit the\nlamp, but nothing was to be seen in the room. I was too shaken to\ngo to bed again, however, so I dressed, and as soon as it was\ndaylight I slipped down, got a dog-cart at the Crown Inn, which\nis opposite, and drove to Leatherhead, from whence I have come on\nthis morning with the one object of seeing you and asking your\nadvice.\"\n\n\"You have done wisely,\" said my friend. \"But have you told me\nall?\"\n\n\"Yes, all.\"\n\n\"Miss Roylott, you have not. You are screening your stepfather.\"\n\n\"Why, what do you mean?\"\n\nFor answer Holmes pushed back the frill of black lace which\nfringed the hand that lay upon our visitor's knee. Five little\nlivid spots, the marks of four fingers and a thumb, were printed\nupon the white wrist.\n\n\"You have been cruelly used,\" said Holmes.\n\nThe lady coloured deeply and covered over her injured wrist. \"He\nis a hard man,\" she said, \"and perhaps he hardly knows his own\nstrength.\"\n\nThere was a long silence, during which Holmes leaned his chin\nupon his hands and stared into the crackling fire.\n\n\"This is a very deep business,\" he said at last. \"There are a\nthousand details which I should desire to know before I decide\nupon our course of action. Yet we have not a moment to lose. If\nwe were to come to Stoke Moran to-day, would it be possible for\nus to see over these rooms without the knowledge of your\nstepfather?\"\n\n\"As it happens, he spoke of coming into town to-day upon some\nmost important business. It is probable that he will be away all\nday, and that there would be nothing to disturb you. We have a\nhousekeeper now, but she is old and foolish, and I could easily\nget her out of the way.\"\n\n\"Excellent. You are not averse to this trip, Watson?\"\n\n\"By no means.\"\n\n\"Then we shall both come. What are you going to do yourself?\"\n\n\"I have one or two things which I would wish to do now that I am\nin town. But I shall return by the twelve o'clock train, so as to\nbe there in time for your coming.\"\n\n\"And you may expect us early in the afternoon. I have myself some\nsmall business matters to attend to. Will you not wait and\nbreakfast?\"\n\n\"No, I must go. My heart is lightened already since I have\nconfided my trouble to you. I shall look forward to seeing you\nagain this afternoon.\" She dropped her thick black veil over her\nface and glided from the room.\n\n\"And what do you think of it all, Watson?\" asked Sherlock Holmes,\nleaning back in his chair.\n\n\"It seems to me to be a most dark and sinister business.\"\n\n\"Dark enough and sinister enough.\"\n\n\"Yet if the lady is correct in saying that the flooring and walls\nare sound, and that the door, window, and chimney are impassable,\nthen her sister must have been undoubtedly alone when she met her\nmysterious end.\"\n\n\"What becomes, then, of these nocturnal whistles, and what of the\nvery peculiar words of the dying woman?\"\n\n\"I cannot think.\"\n\n\"When you combine the ideas of whistles at night, the presence of\na band of gipsies who are on intimate terms with this old doctor,\nthe fact that we have every reason to believe that the doctor has\nan interest in preventing his stepdaughter's marriage, the dying\nallusion to a band, and, finally, the fact that Miss Helen Stoner\nheard a metallic clang, which might have been caused by one of\nthose metal bars that secured the shutters falling back into its\nplace, I think that there is good ground to think that the\nmystery may be cleared along those lines.\"\n\n\"But what, then, did the gipsies do?\"\n\n\"I cannot imagine.\"\n\n\"I see many objections to any such theory.\"\n\n\"And so do I. It is precisely for that reason that we are going\nto Stoke Moran this day. I want to see whether the objections are\nfatal, or if they may be explained away. But what in the name of\nthe devil!\"\n\nThe ejaculation had been drawn from my companion by the fact that\nour door had been suddenly dashed open, and that a huge man had\nframed himself in the aperture. His costume was a peculiar\nmixture of the professional and of the agricultural, having a\nblack top-hat, a long frock-coat, and a pair of high gaiters,\nwith a hunting-crop swinging in his hand. So tall was he that his\nhat actually brushed the cross bar of the doorway, and his\nbreadth seemed to span it across from side to side. A large face,\nseared with a thousand wrinkles, burned yellow with the sun, and\nmarked with every evil passion, was turned from one to the other\nof us, while his deep-set, bile-shot eyes, and his high, thin,\nfleshless nose, gave him somewhat the resemblance to a fierce old\nbird of prey.\n\n\"Which of you is Holmes?\" asked this apparition.\n\n\"My name, sir; but you have the advantage of me,\" said my\ncompanion quietly.\n\n\"I am Dr. Grimesby Roylott, of Stoke Moran.\"\n\n\"Indeed, Doctor,\" said Holmes blandly. \"Pray take a seat.\"\n\n\"I will do nothing of the kind. My stepdaughter has been here. I\nhave traced her. What has she been saying to you?\"\n\n\"It is a little cold for the time of the year,\" said Holmes.\n\n\"What has she been saying to you?\" screamed the old man\nfuriously.\n\n\"But I have heard that the crocuses promise well,\" continued my\ncompanion imperturbably.\n\n\"Ha! You put me off, do you?\" said our new visitor, taking a step\nforward and shaking his hunting-crop. \"I know you, you scoundrel!\nI have heard of you before. You are Holmes, the meddler.\"\n\nMy friend smiled.\n\n\"Holmes, the busybody!\"\n\nHis smile broadened.\n\n\"Holmes, the Scotland Yard Jack-in-office!\"\n\nHolmes chuckled heartily. \"Your conversation is most\nentertaining,\" said he. \"When you go out close the door, for\nthere is a decided draught.\"\n\n\"I will go when I have said my say. Don't you dare to meddle with\nmy affairs. I know that Miss Stoner has been here. I traced her!\nI am a dangerous man to fall foul of! See here.\" He stepped\nswiftly forward, seized the poker, and bent it into a curve with\nhis huge brown hands.\n\n\"See that you keep yourself out of my grip,\" he snarled, and\nhurling the twisted poker into the fireplace he strode out of the\nroom.\n\n\"He seems a very amiable person,\" said Holmes, laughing. \"I am\nnot quite so bulky, but if he had remained I might have shown him\nthat my grip was not much more feeble than his own.\" As he spoke\nhe picked up the steel poker and, with a sudden effort,\nstraightened it out again.\n\n\"Fancy his having the insolence to confound me with the official\ndetective force! This incident gives zest to our investigation,\nhowever, and I only trust that our little friend will not suffer\nfrom her imprudence in allowing this brute to trace her. And now,\nWatson, we shall order breakfast, and afterwards I shall walk\ndown to Doctors' Commons, where I hope to get some data which may\nhelp us in this matter.\"\n\n\nIt was nearly one o'clock when Sherlock Holmes returned from his\nexcursion. He held in his hand a sheet of blue paper, scrawled\nover with notes and figures.\n\n\"I have seen the will of the deceased wife,\" said he. \"To\ndetermine its exact meaning I have been obliged to work out the\npresent prices of the investments with which it is concerned. The\ntotal income, which at the time of the wife's death was little\nshort of 1100 pounds, is now, through the fall in agricultural\nprices, not more than 750 pounds. Each daughter can claim an\nincome of 250 pounds, in case of marriage. It is evident,\ntherefore, that if both girls had married, this beauty would have\nhad a mere pittance, while even one of them would cripple him to\na very serious extent. My morning's work has not been wasted,\nsince it has proved that he has the very strongest motives for\nstanding in the way of anything of the sort. And now, Watson,\nthis is too serious for dawdling, especially as the old man is\naware that we are interesting ourselves in his affairs; so if you\nare ready, we shall call a cab and drive to Waterloo. I should be\nvery much obliged if you would slip your revolver into your\npocket. An Eley's No. 2 is an excellent argument with gentlemen\nwho can twist steel pokers into knots. That and a tooth-brush\nare, I think, all that we need.\"\n\nAt Waterloo we were fortunate in catching a train for\nLeatherhead, where we hired a trap at the station inn and drove\nfor four or five miles through the lovely Surrey lanes. It was a\nperfect day, with a bright sun and a few fleecy clouds in the\nheavens. The trees and wayside hedges were just throwing out\ntheir first green shoots, and the air was full of the pleasant\nsmell of the moist earth. To me at least there was a strange\ncontrast between the sweet promise of the spring and this\nsinister quest upon which we were engaged. My companion sat in\nthe front of the trap, his arms folded, his hat pulled down over\nhis eyes, and his chin sunk upon his breast, buried in the\ndeepest thought. Suddenly, however, he started, tapped me on the\nshoulder, and pointed over the meadows.\n\n\"Look there!\" said he.\n\nA heavily timbered park stretched up in a gentle slope,\nthickening into a grove at the highest point. From amid the\nbranches there jutted out the grey gables and high roof-tree of a\nvery old mansion.\n\n\"Stoke Moran?\" said he.\n\n\"Yes, sir, that be the house of Dr. Grimesby Roylott,\" remarked\nthe driver.\n\n\"There is some building going on there,\" said Holmes; \"that is\nwhere we are going.\"\n\n\"There's the village,\" said the driver, pointing to a cluster of\nroofs some distance to the left; \"but if you want to get to the\nhouse, you'll find it shorter to get over this stile, and so by\nthe foot-path over the fields. There it is, where the lady is\nwalking.\"\n\n\"And the lady, I fancy, is Miss Stoner,\" observed Holmes, shading\nhis eyes. \"Yes, I think we had better do as you suggest.\"\n\nWe got off, paid our fare, and the trap rattled back on its way\nto Leatherhead.\n\n\"I thought it as well,\" said Holmes as we climbed the stile,\n\"that this fellow should think we had come here as architects, or\non some definite business. It may stop his gossip.\nGood-afternoon, Miss Stoner. You see that we have been as good as\nour word.\"\n\nOur client of the morning had hurried forward to meet us with a\nface which spoke her joy. \"I have been waiting so eagerly for\nyou,\" she cried, shaking hands with us warmly. \"All has turned\nout splendidly. Dr. Roylott has gone to town, and it is unlikely\nthat he will be back before evening.\"\n\n\"We have had the pleasure of making the doctor's acquaintance,\"\nsaid Holmes, and in a few words he sketched out what had\noccurred. Miss Stoner turned white to the lips as she listened.\n\n\"Good heavens!\" she cried, \"he has followed me, then.\"\n\n\"So it appears.\"\n\n\"He is so cunning that I never know when I am safe from him. What\nwill he say when he returns?\"\n\n\"He must guard himself, for he may find that there is someone\nmore cunning than himself upon his track. You must lock yourself\nup from him to-night. If he is violent, we shall take you away to\nyour aunt's at Harrow. Now, we must make the best use of our\ntime, so kindly take us at once to the rooms which we are to\nexamine.\"\n\nThe building was of grey, lichen-blotched stone, with a high\ncentral portion and two curving wings, like the claws of a crab,\nthrown out on each side. In one of these wings the windows were\nbroken and blocked with wooden boards, while the roof was partly\ncaved in, a picture of ruin. The central portion was in little\nbetter repair, but the right-hand block was comparatively modern,\nand the blinds in the windows, with the blue smoke curling up\nfrom the chimneys, showed that this was where the family resided.\nSome scaffolding had been erected against the end wall, and the\nstone-work had been broken into, but there were no signs of any\nworkmen at the moment of our visit. Holmes walked slowly up and\ndown the ill-trimmed lawn and examined with deep attention the\noutsides of the windows.\n\n\"This, I take it, belongs to the room in which you used to sleep,\nthe centre one to your sister's, and the one next to the main\nbuilding to Dr. Roylott's chamber?\"\n\n\"Exactly so. But I am now sleeping in the middle one.\"\n\n\"Pending the alterations, as I understand. By the way, there does\nnot seem to be any very pressing need for repairs at that end\nwall.\"\n\n\"There were none. I believe that it was an excuse to move me from\nmy room.\"\n\n\"Ah! that is suggestive. Now, on the other side of this narrow\nwing runs the corridor from which these three rooms open. There\nare windows in it, of course?\"\n\n\"Yes, but very small ones. Too narrow for anyone to pass\nthrough.\"\n\n\"As you both locked your doors at night, your rooms were\nunapproachable from that side. Now, would you have the kindness\nto go into your room and bar your shutters?\"\n\nMiss Stoner did so, and Holmes, after a careful examination\nthrough the open window, endeavoured in every way to force the\nshutter open, but without success. There was no slit through\nwhich a knife could be passed to raise the bar. Then with his\nlens he tested the hinges, but they were of solid iron, built\nfirmly into the massive masonry. \"Hum!\" said he, scratching his\nchin in some perplexity, \"my theory certainly presents some\ndifficulties. No one could pass these shutters if they were\nbolted. Well, we shall see if the inside throws any light upon\nthe matter.\"\n\nA small side door led into the whitewashed corridor from which\nthe three bedrooms opened. Holmes refused to examine the third\nchamber, so we passed at once to the second, that in which Miss\nStoner was now sleeping, and in which her sister had met with her\nfate. It was a homely little room, with a low ceiling and a\ngaping fireplace, after the fashion of old country-houses. A\nbrown chest of drawers stood in one corner, a narrow\nwhite-counterpaned bed in another, and a dressing-table on the\nleft-hand side of the window. These articles, with two small\nwicker-work chairs, made up all the furniture in the room save\nfor a square of Wilton carpet in the centre. The boards round and\nthe panelling of the walls were of brown, worm-eaten oak, so old\nand discoloured that it may have dated from the original building\nof the house. Holmes drew one of the chairs into a corner and sat\nsilent, while his eyes travelled round and round and up and down,\ntaking in every detail of the apartment.\n\n\"Where does that bell communicate with?\" he asked at last\npointing to a thick bell-rope which hung down beside the bed, the\ntassel actually lying upon the pillow.\n\n\"It goes to the housekeeper's room.\"\n\n\"It looks newer than the other things?\"\n\n\"Yes, it was only put there a couple of years ago.\"\n\n\"Your sister asked for it, I suppose?\"\n\n\"No, I never heard of her using it. We used always to get what we\nwanted for ourselves.\"\n\n\"Indeed, it seemed unnecessary to put so nice a bell-pull there.\nYou will excuse me for a few minutes while I satisfy myself as to\nthis floor.\" He threw himself down upon his face with his lens in\nhis hand and crawled swiftly backward and forward, examining\nminutely the cracks between the boards. Then he did the same with\nthe wood-work with which the chamber was panelled. Finally he\nwalked over to the bed and spent some time in staring at it and\nin running his eye up and down the wall. Finally he took the\nbell-rope in his hand and gave it a brisk tug.\n\n\"Why, it's a dummy,\" said he.\n\n\"Won't it ring?\"\n\n\"No, it is not even attached to a wire. This is very interesting.\nYou can see now that it is fastened to a hook just above where\nthe little opening for the ventilator is.\"\n\n\"How very absurd! I never noticed that before.\"\n\n\"Very strange!\" muttered Holmes, pulling at the rope. \"There are\none or two very singular points about this room. For example,\nwhat a fool a builder must be to open a ventilator into another\nroom, when, with the same trouble, he might have communicated\nwith the outside air!\"\n\n\"That is also quite modern,\" said the lady.\n\n\"Done about the same time as the bell-rope?\" remarked Holmes.\n\n\"Yes, there were several little changes carried out about that\ntime.\"\n\n\"They seem to have been of a most interesting character--dummy\nbell-ropes, and ventilators which do not ventilate. With your\npermission, Miss Stoner, we shall now carry our researches into\nthe inner apartment.\"\n\nDr. Grimesby Roylott's chamber was larger than that of his\nstep-daughter, but was as plainly furnished. A camp-bed, a small\nwooden shelf full of books, mostly of a technical character, an\narmchair beside the bed, a plain wooden chair against the wall, a\nround table, and a large iron safe were the principal things\nwhich met the eye. Holmes walked slowly round and examined each\nand all of them with the keenest interest.\n\n\"What's in here?\" he asked, tapping the safe.\n\n\"My stepfather's business papers.\"\n\n\"Oh! you have seen inside, then?\"\n\n\"Only once, some years ago. I remember that it was full of\npapers.\"\n\n\"There isn't a cat in it, for example?\"\n\n\"No. What a strange idea!\"\n\n\"Well, look at this!\" He took up a small saucer of milk which\nstood on the top of it.\n\n\"No; we don't keep a cat. But there is a cheetah and a baboon.\"\n\n\"Ah, yes, of course! Well, a cheetah is just a big cat, and yet a\nsaucer of milk does not go very far in satisfying its wants, I\ndaresay. There is one point which I should wish to determine.\" He\nsquatted down in front of the wooden chair and examined the seat\nof it with the greatest attention.\n\n\"Thank you. That is quite settled,\" said he, rising and putting\nhis lens in his pocket. \"Hullo! Here is something interesting!\"\n\nThe object which had caught his eye was a small dog lash hung on\none corner of the bed. The lash, however, was curled upon itself\nand tied so as to make a loop of whipcord.\n\n\"What do you make of that, Watson?\"\n\n\"It's a common enough lash. But I don't know why it should be\ntied.\"\n\n\"That is not quite so common, is it? Ah, me! it's a wicked world,\nand when a clever man turns his brains to crime it is the worst\nof all. I think that I have seen enough now, Miss Stoner, and\nwith your permission we shall walk out upon the lawn.\"\n\nI had never seen my friend's face so grim or his brow so dark as\nit was when we turned from the scene of this investigation. We\nhad walked several times up and down the lawn, neither Miss\nStoner nor myself liking to break in upon his thoughts before he\nroused himself from his reverie.\n\n\"It is very essential, Miss Stoner,\" said he, \"that you should\nabsolutely follow my advice in every respect.\"\n\n\"I shall most certainly do so.\"\n\n\"The matter is too serious for any hesitation. Your life may\ndepend upon your compliance.\"\n\n\"I assure you that I am in your hands.\"\n\n\"In the first place, both my friend and I must spend the night in\nyour room.\"\n\nBoth Miss Stoner and I gazed at him in astonishment.\n\n\"Yes, it must be so. Let me explain. I believe that that is the\nvillage inn over there?\"\n\n\"Yes, that is the Crown.\"\n\n\"Very good. Your windows would be visible from there?\"\n\n\"Certainly.\"\n\n\"You must confine yourself to your room, on pretence of a\nheadache, when your stepfather comes back. Then when you hear him\nretire for the night, you must open the shutters of your window,\nundo the hasp, put your lamp there as a signal to us, and then\nwithdraw quietly with everything which you are likely to want\ninto the room which you used to occupy. I have no doubt that, in\nspite of the repairs, you could manage there for one night.\"\n\n\"Oh, yes, easily.\"\n\n\"The rest you will leave in our hands.\"\n\n\"But what will you do?\"\n\n\"We shall spend the night in your room, and we shall investigate\nthe cause of this noise which has disturbed you.\"\n\n\"I believe, Mr. Holmes, that you have already made up your mind,\"\nsaid Miss Stoner, laying her hand upon my companion's sleeve.\n\n\"Perhaps I have.\"\n\n\"Then, for pity's sake, tell me what was the cause of my sister's\ndeath.\"\n\n\"I should prefer to have clearer proofs before I speak.\"\n\n\"You can at least tell me whether my own thought is correct, and\nif she died from some sudden fright.\"\n\n\"No, I do not think so. I think that there was probably some more\ntangible cause. And now, Miss Stoner, we must leave you for if\nDr. Roylott returned and saw us our journey would be in vain.\nGood-bye, and be brave, for if you will do what I have told you,\nyou may rest assured that we shall soon drive away the dangers\nthat threaten you.\"\n\nSherlock Holmes and I had no difficulty in engaging a bedroom and\nsitting-room at the Crown Inn. They were on the upper floor, and\nfrom our window we could command a view of the avenue gate, and\nof the inhabited wing of Stoke Moran Manor House. At dusk we saw\nDr. Grimesby Roylott drive past, his huge form looming up beside\nthe little figure of the lad who drove him. The boy had some\nslight difficulty in undoing the heavy iron gates, and we heard\nthe hoarse roar of the doctor's voice and saw the fury with which\nhe shook his clinched fists at him. The trap drove on, and a few\nminutes later we saw a sudden light spring up among the trees as\nthe lamp was lit in one of the sitting-rooms.\n\n\"Do you know, Watson,\" said Holmes as we sat together in the\ngathering darkness, \"I have really some scruples as to taking you\nto-night. There is a distinct element of danger.\"\n\n\"Can I be of assistance?\"\n\n\"Your presence might be invaluable.\"\n\n\"Then I shall certainly come.\"\n\n\"It is very kind of you.\"\n\n\"You speak of danger. You have evidently seen more in these rooms\nthan was visible to me.\"\n\n\"No, but I fancy that I may have deduced a little more. I imagine\nthat you saw all that I did.\"\n\n\"I saw nothing remarkable save the bell-rope, and what purpose\nthat could answer I confess is more than I can imagine.\"\n\n\"You saw the ventilator, too?\"\n\n\"Yes, but I do not think that it is such a very unusual thing to\nhave a small opening between two rooms. It was so small that a\nrat could hardly pass through.\"\n\n\"I knew that we should find a ventilator before ever we came to\nStoke Moran.\"\n\n\"My dear Holmes!\"\n\n\"Oh, yes, I did. You remember in her statement she said that her\nsister could smell Dr. Roylott's cigar. Now, of course that\nsuggested at once that there must be a communication between the\ntwo rooms. It could only be a small one, or it would have been\nremarked upon at the coroner's inquiry. I deduced a ventilator.\"\n\n\"But what harm can there be in that?\"\n\n\"Well, there is at least a curious coincidence of dates. A\nventilator is made, a cord is hung, and a lady who sleeps in the\nbed dies. Does not that strike you?\"\n\n\"I cannot as yet see any connection.\"\n\n\"Did you observe anything very peculiar about that bed?\"\n\n\"No.\"\n\n\"It was clamped to the floor. Did you ever see a bed fastened\nlike that before?\"\n\n\"I cannot say that I have.\"\n\n\"The lady could not move her bed. It must always be in the same\nrelative position to the ventilator and to the rope--or so we may\ncall it, since it was clearly never meant for a bell-pull.\"\n\n\"Holmes,\" I cried, \"I seem to see dimly what you are hinting at.\nWe are only just in time to prevent some subtle and horrible\ncrime.\"\n\n\"Subtle enough and horrible enough. When a doctor does go wrong\nhe is the first of criminals. He has nerve and he has knowledge.\nPalmer and Pritchard were among the heads of their profession.\nThis man strikes even deeper, but I think, Watson, that we shall\nbe able to strike deeper still. But we shall have horrors enough\nbefore the night is over; for goodness' sake let us have a quiet\npipe and turn our minds for a few hours to something more\ncheerful.\"\n\n\nAbout nine o'clock the light among the trees was extinguished,\nand all was dark in the direction of the Manor House. Two hours\npassed slowly away, and then, suddenly, just at the stroke of\neleven, a single bright light shone out right in front of us.\n\n\"That is our signal,\" said Holmes, springing to his feet; \"it\ncomes from the middle window.\"\n\nAs we passed out he exchanged a few words with the landlord,\nexplaining that we were going on a late visit to an acquaintance,\nand that it was possible that we might spend the night there. A\nmoment later we were out on the dark road, a chill wind blowing\nin our faces, and one yellow light twinkling in front of us\nthrough the gloom to guide us on our sombre errand.\n\nThere was little difficulty in entering the grounds, for\nunrepaired breaches gaped in the old park wall. Making our way\namong the trees, we reached the lawn, crossed it, and were about\nto enter through the window when out from a clump of laurel\nbushes there darted what seemed to be a hideous and distorted\nchild, who threw itself upon the grass with writhing limbs and\nthen ran swiftly across the lawn into the darkness.\n\n\"My God!\" I whispered; \"did you see it?\"\n\nHolmes was for the moment as startled as I. His hand closed like\na vice upon my wrist in his agitation. Then he broke into a low\nlaugh and put his lips to my ear.\n\n\"It is a nice household,\" he murmured. \"That is the baboon.\"\n\nI had forgotten the strange pets which the doctor affected. There\nwas a cheetah, too; perhaps we might find it upon our shoulders\nat any moment. I confess that I felt easier in my mind when,\nafter following Holmes' example and slipping off my shoes, I\nfound myself inside the bedroom. My companion noiselessly closed\nthe shutters, moved the lamp onto the table, and cast his eyes\nround the room. All was as we had seen it in the daytime. Then\ncreeping up to me and making a trumpet of his hand, he whispered\ninto my ear again so gently that it was all that I could do to\ndistinguish the words:\n\n\"The least sound would be fatal to our plans.\"\n\nI nodded to show that I had heard.\n\n\"We must sit without light. He would see it through the\nventilator.\"\n\nI nodded again.\n\n\"Do not go asleep; your very life may depend upon it. Have your\npistol ready in case we should need it. I will sit on the side of\nthe bed, and you in that chair.\"\n\nI took out my revolver and laid it on the corner of the table.\n\nHolmes had brought up a long thin cane, and this he placed upon\nthe bed beside him. By it he laid the box of matches and the\nstump of a candle. Then he turned down the lamp, and we were left\nin darkness.\n\nHow shall I ever forget that dreadful vigil? I could not hear a\nsound, not even the drawing of a breath, and yet I knew that my\ncompanion sat open-eyed, within a few feet of me, in the same\nstate of nervous tension in which I was myself. The shutters cut\noff the least ray of light, and we waited in absolute darkness.\n\nFrom outside came the occasional cry of a night-bird, and once at\nour very window a long drawn catlike whine, which told us that\nthe cheetah was indeed at liberty. Far away we could hear the\ndeep tones of the parish clock, which boomed out every quarter of\nan hour. How long they seemed, those quarters! Twelve struck, and\none and two and three, and still we sat waiting silently for\nwhatever might befall.\n\nSuddenly there was the momentary gleam of a light up in the\ndirection of the ventilator, which vanished immediately, but was\nsucceeded by a strong smell of burning oil and heated metal.\nSomeone in the next room had lit a dark-lantern. I heard a gentle\nsound of movement, and then all was silent once more, though the\nsmell grew stronger. For half an hour I sat with straining ears.\nThen suddenly another sound became audible--a very gentle,\nsoothing sound, like that of a small jet of steam escaping\ncontinually from a kettle. The instant that we heard it, Holmes\nsprang from the bed, struck a match, and lashed furiously with\nhis cane at the bell-pull.\n\n\"You see it, Watson?\" he yelled. \"You see it?\"\n\nBut I saw nothing. At the moment when Holmes struck the light I\nheard a low, clear whistle, but the sudden glare flashing into my\nweary eyes made it impossible for me to tell what it was at which\nmy friend lashed so savagely. I could, however, see that his face\nwas deadly pale and filled with horror and loathing. He had\nceased to strike and was gazing up at the ventilator when\nsuddenly there broke from the silence of the night the most\nhorrible cry to which I have ever listened. It swelled up louder\nand louder, a hoarse yell of pain and fear and anger all mingled\nin the one dreadful shriek. They say that away down in the\nvillage, and even in the distant parsonage, that cry raised the\nsleepers from their beds. It struck cold to our hearts, and I\nstood gazing at Holmes, and he at me, until the last echoes of it\nhad died away into the silence from which it rose.\n\n\"What can it mean?\" I gasped.\n\n\"It means that it is all over,\" Holmes answered. \"And perhaps,\nafter all, it is for the best. Take your pistol, and we will\nenter Dr. Roylott's room.\"\n\nWith a grave face he lit the lamp and led the way down the\ncorridor. Twice he struck at the chamber door without any reply\nfrom within. Then he turned the handle and entered, I at his\nheels, with the cocked pistol in my hand.\n\nIt was a singular sight which met our eyes. On the table stood a\ndark-lantern with the shutter half open, throwing a brilliant\nbeam of light upon the iron safe, the door of which was ajar.\nBeside this table, on the wooden chair, sat Dr. Grimesby Roylott\nclad in a long grey dressing-gown, his bare ankles protruding\nbeneath, and his feet thrust into red heelless Turkish slippers.\nAcross his lap lay the short stock with the long lash which we\nhad noticed during the day. His chin was cocked upward and his\neyes were fixed in a dreadful, rigid stare at the corner of the\nceiling. Round his brow he had a peculiar yellow band, with\nbrownish speckles, which seemed to be bound tightly round his\nhead. As we entered he made neither sound nor motion.\n\n\"The band! the speckled band!\" whispered Holmes.\n\nI took a step forward. In an instant his strange headgear began\nto move, and there reared itself from among his hair the squat\ndiamond-shaped head and puffed neck of a loathsome serpent.\n\n\"It is a swamp adder!\" cried Holmes; \"the deadliest snake in\nIndia. He has died within ten seconds of being bitten. Violence\ndoes, in truth, recoil upon the violent, and the schemer falls\ninto the pit which he digs for another. Let us thrust this\ncreature back into its den, and we can then remove Miss Stoner to\nsome place of shelter and let the county police know what has\nhappened.\"\n\nAs he spoke he drew the dog-whip swiftly from the dead man's lap,\nand throwing the noose round the reptile's neck he drew it from\nits horrid perch and, carrying it at arm's length, threw it into\nthe iron safe, which he closed upon it.\n\nSuch are the true facts of the death of Dr. Grimesby Roylott, of\nStoke Moran. It is not necessary that I should prolong a\nnarrative which has already run to too great a length by telling\nhow we broke the sad news to the terrified girl, how we conveyed\nher by the morning train to the care of her good aunt at Harrow,\nof how the slow process of official inquiry came to the\nconclusion that the doctor met his fate while indiscreetly\nplaying with a dangerous pet. The little which I had yet to learn\nof the case was told me by Sherlock Holmes as we travelled back\nnext day.\n\n\"I had,\" said he, \"come to an entirely erroneous conclusion which\nshows, my dear Watson, how dangerous it always is to reason from\ninsufficient data. The presence of the gipsies, and the use of\nthe word 'band,' which was used by the poor girl, no doubt, to\nexplain the appearance which she had caught a hurried glimpse of\nby the light of her match, were sufficient to put me upon an\nentirely wrong scent. I can only claim the merit that I instantly\nreconsidered my position when, however, it became clear to me\nthat whatever danger threatened an occupant of the room could not\ncome either from the window or the door. My attention was\nspeedily drawn, as I have already remarked to you, to this\nventilator, and to the bell-rope which hung down to the bed. The\ndiscovery that this was a dummy, and that the bed was clamped to\nthe floor, instantly gave rise to the suspicion that the rope was\nthere as a bridge for something passing through the hole and\ncoming to the bed. The idea of a snake instantly occurred to me,\nand when I coupled it with my knowledge that the doctor was\nfurnished with a supply of creatures from India, I felt that I\nwas probably on the right track. The idea of using a form of\npoison which could not possibly be discovered by any chemical\ntest was just such a one as would occur to a clever and ruthless\nman who had had an Eastern training. The rapidity with which such\na poison would take effect would also, from his point of view, be\nan advantage. It would be a sharp-eyed coroner, indeed, who could\ndistinguish the two little dark punctures which would show where\nthe poison fangs had done their work. Then I thought of the\nwhistle. Of course he must recall the snake before the morning\nlight revealed it to the victim. He had trained it, probably by\nthe use of the milk which we saw, to return to him when summoned.\nHe would put it through this ventilator at the hour that he\nthought best, with the certainty that it would crawl down the\nrope and land on the bed. It might or might not bite the\noccupant, perhaps she might escape every night for a week, but\nsooner or later she must fall a victim.\n\n\"I had come to these conclusions before ever I had entered his\nroom. An inspection of his chair showed me that he had been in\nthe habit of standing on it, which of course would be necessary\nin order that he should reach the ventilator. The sight of the\nsafe, the saucer of milk, and the loop of whipcord were enough to\nfinally dispel any doubts which may have remained. The metallic\nclang heard by Miss Stoner was obviously caused by her stepfather\nhastily closing the door of his safe upon its terrible occupant.\nHaving once made up my mind, you know the steps which I took in\norder to put the matter to the proof. I heard the creature hiss\nas I have no doubt that you did also, and I instantly lit the\nlight and attacked it.\"\n\n\"With the result of driving it through the ventilator.\"\n\n\"And also with the result of causing it to turn upon its master\nat the other side. Some of the blows of my cane came home and\nroused its snakish temper, so that it flew upon the first person\nit saw. In this way I am no doubt indirectly responsible for Dr.\nGrimesby Roylott's death, and I cannot say that it is likely to\nweigh very heavily upon my conscience.\"\n\n\n\nIX. THE ADVENTURE OF THE ENGINEER'S THUMB\n\nOf all the problems which have been submitted to my friend, Mr.\nSherlock Holmes, for solution during the years of our intimacy,\nthere were only two which I was the means of introducing to his\nnotice--that of Mr. Hatherley's thumb, and that of Colonel\nWarburton's madness. Of these the latter may have afforded a\nfiner field for an acute and original observer, but the other was\nso strange in its inception and so dramatic in its details that\nit may be the more worthy of being placed upon record, even if it\ngave my friend fewer openings for those deductive methods of\nreasoning by which he achieved such remarkable results. The story\nhas, I believe, been told more than once in the newspapers, but,\nlike all such narratives, its effect is much less striking when\nset forth en bloc in a single half-column of print than when the\nfacts slowly evolve before your own eyes, and the mystery clears\ngradually away as each new discovery furnishes a step which leads\non to the complete truth. At the time the circumstances made a\ndeep impression upon me, and the lapse of two years has hardly\nserved to weaken the effect.\n\nIt was in the summer of '89, not long after my marriage, that the\nevents occurred which I am now about to summarise. I had returned\nto civil practice and had finally abandoned Holmes in his Baker\nStreet rooms, although I continually visited him and occasionally\neven persuaded him to forgo his Bohemian habits so far as to come\nand visit us. My practice had steadily increased, and as I\nhappened to live at no very great distance from Paddington\nStation, I got a few patients from among the officials. One of\nthese, whom I had cured of a painful and lingering disease, was\nnever weary of advertising my virtues and of endeavouring to send\nme on every sufferer over whom he might have any influence.\n\nOne morning, at a little before seven o'clock, I was awakened by\nthe maid tapping at the door to announce that two men had come\nfrom Paddington and were waiting in the consulting-room. I\ndressed hurriedly, for I knew by experience that railway cases\nwere seldom trivial, and hastened downstairs. As I descended, my\nold ally, the guard, came out of the room and closed the door\ntightly behind him.\n\n\"I've got him here,\" he whispered, jerking his thumb over his\nshoulder; \"he's all right.\"\n\n\"What is it, then?\" I asked, for his manner suggested that it was\nsome strange creature which he had caged up in my room.\n\n\"It's a new patient,\" he whispered. \"I thought I'd bring him\nround myself; then he couldn't slip away. There he is, all safe\nand sound. I must go now, Doctor; I have my dooties, just the\nsame as you.\" And off he went, this trusty tout, without even\ngiving me time to thank him.\n\nI entered my consulting-room and found a gentleman seated by the\ntable. He was quietly dressed in a suit of heather tweed with a\nsoft cloth cap which he had laid down upon my books. Round one of\nhis hands he had a handkerchief wrapped, which was mottled all\nover with bloodstains. He was young, not more than\nfive-and-twenty, I should say, with a strong, masculine face; but\nhe was exceedingly pale and gave me the impression of a man who\nwas suffering from some strong agitation, which it took all his\nstrength of mind to control.\n\n\"I am sorry to knock you up so early, Doctor,\" said he, \"but I\nhave had a very serious accident during the night. I came in by\ntrain this morning, and on inquiring at Paddington as to where I\nmight find a doctor, a worthy fellow very kindly escorted me\nhere. I gave the maid a card, but I see that she has left it upon\nthe side-table.\"\n\nI took it up and glanced at it. \"Mr. Victor Hatherley, hydraulic\nengineer, 16A, Victoria Street (3rd floor).\" That was the name,\nstyle, and abode of my morning visitor. \"I regret that I have\nkept you waiting,\" said I, sitting down in my library-chair. \"You\nare fresh from a night journey, I understand, which is in itself\na monotonous occupation.\"\n\n\"Oh, my night could not be called monotonous,\" said he, and\nlaughed. He laughed very heartily, with a high, ringing note,\nleaning back in his chair and shaking his sides. All my medical\ninstincts rose up against that laugh.\n\n\"Stop it!\" I cried; \"pull yourself together!\" and I poured out\nsome water from a caraffe.\n\nIt was useless, however. He was off in one of those hysterical\noutbursts which come upon a strong nature when some great crisis\nis over and gone. Presently he came to himself once more, very\nweary and pale-looking.\n\n\"I have been making a fool of myself,\" he gasped.\n\n\"Not at all. Drink this.\" I dashed some brandy into the water,\nand the colour began to come back to his bloodless cheeks.\n\n\"That's better!\" said he. \"And now, Doctor, perhaps you would\nkindly attend to my thumb, or rather to the place where my thumb\nused to be.\"\n\nHe unwound the handkerchief and held out his hand. It gave even\nmy hardened nerves a shudder to look at it. There were four\nprotruding fingers and a horrid red, spongy surface where the\nthumb should have been. It had been hacked or torn right out from\nthe roots.\n\n\"Good heavens!\" I cried, \"this is a terrible injury. It must have\nbled considerably.\"\n\n\"Yes, it did. I fainted when it was done, and I think that I must\nhave been senseless for a long time. When I came to I found that\nit was still bleeding, so I tied one end of my handkerchief very\ntightly round the wrist and braced it up with a twig.\"\n\n\"Excellent! You should have been a surgeon.\"\n\n\"It is a question of hydraulics, you see, and came within my own\nprovince.\"\n\n\"This has been done,\" said I, examining the wound, \"by a very\nheavy and sharp instrument.\"\n\n\"A thing like a cleaver,\" said he.\n\n\"An accident, I presume?\"\n\n\"By no means.\"\n\n\"What! a murderous attack?\"\n\n\"Very murderous indeed.\"\n\n\"You horrify me.\"\n\nI sponged the wound, cleaned it, dressed it, and finally covered\nit over with cotton wadding and carbolised bandages. He lay back\nwithout wincing, though he bit his lip from time to time.\n\n\"How is that?\" I asked when I had finished.\n\n\"Capital! Between your brandy and your bandage, I feel a new man.\nI was very weak, but I have had a good deal to go through.\"\n\n\"Perhaps you had better not speak of the matter. It is evidently\ntrying to your nerves.\"\n\n\"Oh, no, not now. I shall have to tell my tale to the police;\nbut, between ourselves, if it were not for the convincing\nevidence of this wound of mine, I should be surprised if they\nbelieved my statement, for it is a very extraordinary one, and I\nhave not much in the way of proof with which to back it up; and,\neven if they believe me, the clues which I can give them are so\nvague that it is a question whether justice will be done.\"\n\n\"Ha!\" cried I, \"if it is anything in the nature of a problem\nwhich you desire to see solved, I should strongly recommend you\nto come to my friend, Mr. Sherlock Holmes, before you go to the\nofficial police.\"\n\n\"Oh, I have heard of that fellow,\" answered my visitor, \"and I\nshould be very glad if he would take the matter up, though of\ncourse I must use the official police as well. Would you give me\nan introduction to him?\"\n\n\"I'll do better. I'll take you round to him myself.\"\n\n\"I should be immensely obliged to you.\"\n\n\"We'll call a cab and go together. We shall just be in time to\nhave a little breakfast with him. Do you feel equal to it?\"\n\n\"Yes; I shall not feel easy until I have told my story.\"\n\n\"Then my servant will call a cab, and I shall be with you in an\ninstant.\" I rushed upstairs, explained the matter shortly to my\nwife, and in five minutes was inside a hansom, driving with my\nnew acquaintance to Baker Street.\n\nSherlock Holmes was, as I expected, lounging about his\nsitting-room in his dressing-gown, reading the agony column of The\nTimes and smoking his before-breakfast pipe, which was composed\nof all the plugs and dottles left from his smokes of the day\nbefore, all carefully dried and collected on the corner of the\nmantelpiece. He received us in his quietly genial fashion,\nordered fresh rashers and eggs, and joined us in a hearty meal.\nWhen it was concluded he settled our new acquaintance upon the\nsofa, placed a pillow beneath his head, and laid a glass of\nbrandy and water within his reach.\n\n\"It is easy to see that your experience has been no common one,\nMr. Hatherley,\" said he. \"Pray, lie down there and make yourself\nabsolutely at home. Tell us what you can, but stop when you are\ntired and keep up your strength with a little stimulant.\"\n\n\"Thank you,\" said my patient, \"but I have felt another man since\nthe doctor bandaged me, and I think that your breakfast has\ncompleted the cure. I shall take up as little of your valuable\ntime as possible, so I shall start at once upon my peculiar\nexperiences.\"\n\nHolmes sat in his big armchair with the weary, heavy-lidded\nexpression which veiled his keen and eager nature, while I sat\nopposite to him, and we listened in silence to the strange story\nwhich our visitor detailed to us.\n\n\"You must know,\" said he, \"that I am an orphan and a bachelor,\nresiding alone in lodgings in London. By profession I am a\nhydraulic engineer, and I have had considerable experience of my\nwork during the seven years that I was apprenticed to Venner &\nMatheson, the well-known firm, of Greenwich. Two years ago,\nhaving served my time, and having also come into a fair sum of\nmoney through my poor father's death, I determined to start in\nbusiness for myself and took professional chambers in Victoria\nStreet.\n\n\"I suppose that everyone finds his first independent start in\nbusiness a dreary experience. To me it has been exceptionally so.\nDuring two years I have had three consultations and one small\njob, and that is absolutely all that my profession has brought\nme. My gross takings amount to 27 pounds 10s. Every day, from\nnine in the morning until four in the afternoon, I waited in my\nlittle den, until at last my heart began to sink, and I came to\nbelieve that I should never have any practice at all.\n\n\"Yesterday, however, just as I was thinking of leaving the\noffice, my clerk entered to say there was a gentleman waiting who\nwished to see me upon business. He brought up a card, too, with\nthe name of 'Colonel Lysander Stark' engraved upon it. Close at\nhis heels came the colonel himself, a man rather over the middle\nsize, but of an exceeding thinness. I do not think that I have\never seen so thin a man. His whole face sharpened away into nose\nand chin, and the skin of his cheeks was drawn quite tense over\nhis outstanding bones. Yet this emaciation seemed to be his\nnatural habit, and due to no disease, for his eye was bright, his\nstep brisk, and his bearing assured. He was plainly but neatly\ndressed, and his age, I should judge, would be nearer forty than\nthirty.\n\n\"'Mr. Hatherley?' said he, with something of a German accent.\n'You have been recommended to me, Mr. Hatherley, as being a man\nwho is not only proficient in his profession but is also discreet\nand capable of preserving a secret.'\n\n\"I bowed, feeling as flattered as any young man would at such an\naddress. 'May I ask who it was who gave me so good a character?'\n\n\"'Well, perhaps it is better that I should not tell you that just\nat this moment. I have it from the same source that you are both\nan orphan and a bachelor and are residing alone in London.'\n\n\"'That is quite correct,' I answered; 'but you will excuse me if\nI say that I cannot see how all this bears upon my professional\nqualifications. I understand that it was on a professional matter\nthat you wished to speak to me?'\n\n\"'Undoubtedly so. But you will find that all I say is really to\nthe point. I have a professional commission for you, but absolute\nsecrecy is quite essential--absolute secrecy, you understand, and\nof course we may expect that more from a man who is alone than\nfrom one who lives in the bosom of his family.'\n\n\"'If I promise to keep a secret,' said I, 'you may absolutely\ndepend upon my doing so.'\n\n\"He looked very hard at me as I spoke, and it seemed to me that I\nhad never seen so suspicious and questioning an eye.\n\n\"'Do you promise, then?' said he at last.\n\n\"'Yes, I promise.'\n\n\"'Absolute and complete silence before, during, and after? No\nreference to the matter at all, either in word or writing?'\n\n\"'I have already given you my word.'\n\n\"'Very good.' He suddenly sprang up, and darting like lightning\nacross the room he flung open the door. The passage outside was\nempty.\n\n\"'That's all right,' said he, coming back. 'I know that clerks are\nsometimes curious as to their master's affairs. Now we can talk\nin safety.' He drew up his chair very close to mine and began to\nstare at me again with the same questioning and thoughtful look.\n\n\"A feeling of repulsion, and of something akin to fear had begun\nto rise within me at the strange antics of this fleshless man.\nEven my dread of losing a client could not restrain me from\nshowing my impatience.\n\n\"'I beg that you will state your business, sir,' said I; 'my time\nis of value.' Heaven forgive me for that last sentence, but the\nwords came to my lips.\n\n\"'How would fifty guineas for a night's work suit you?' he asked.\n\n\"'Most admirably.'\n\n\"'I say a night's work, but an hour's would be nearer the mark. I\nsimply want your opinion about a hydraulic stamping machine which\nhas got out of gear. If you show us what is wrong we shall soon\nset it right ourselves. What do you think of such a commission as\nthat?'\n\n\"'The work appears to be light and the pay munificent.'\n\n\"'Precisely so. We shall want you to come to-night by the last\ntrain.'\n\n\"'Where to?'\n\n\"'To Eyford, in Berkshire. It is a little place near the borders\nof Oxfordshire, and within seven miles of Reading. There is a\ntrain from Paddington which would bring you there at about\n11:15.'\n\n\"'Very good.'\n\n\"'I shall come down in a carriage to meet you.'\n\n\"'There is a drive, then?'\n\n\"'Yes, our little place is quite out in the country. It is a good\nseven miles from Eyford Station.'\n\n\"'Then we can hardly get there before midnight. I suppose there\nwould be no chance of a train back. I should be compelled to stop\nthe night.'\n\n\"'Yes, we could easily give you a shake-down.'\n\n\"'That is very awkward. Could I not come at some more convenient\nhour?'\n\n\"'We have judged it best that you should come late. It is to\nrecompense you for any inconvenience that we are paying to you, a\nyoung and unknown man, a fee which would buy an opinion from the\nvery heads of your profession. Still, of course, if you would\nlike to draw out of the business, there is plenty of time to do\nso.'\n\n\"I thought of the fifty guineas, and of how very useful they\nwould be to me. 'Not at all,' said I, 'I shall be very happy to\naccommodate myself to your wishes. I should like, however, to\nunderstand a little more clearly what it is that you wish me to\ndo.'\n\n\"'Quite so. It is very natural that the pledge of secrecy which\nwe have exacted from you should have aroused your curiosity. I\nhave no wish to commit you to anything without your having it all\nlaid before you. I suppose that we are absolutely safe from\neavesdroppers?'\n\n\"'Entirely.'\n\n\"'Then the matter stands thus. You are probably aware that\nfuller's-earth is a valuable product, and that it is only found\nin one or two places in England?'\n\n\"'I have heard so.'\n\n\"'Some little time ago I bought a small place--a very small\nplace--within ten miles of Reading. I was fortunate enough to\ndiscover that there was a deposit of fuller's-earth in one of my\nfields. On examining it, however, I found that this deposit was a\ncomparatively small one, and that it formed a link between two\nvery much larger ones upon the right and left--both of them,\nhowever, in the grounds of my neighbours. These good people were\nabsolutely ignorant that their land contained that which was\nquite as valuable as a gold-mine. Naturally, it was to my\ninterest to buy their land before they discovered its true value,\nbut unfortunately I had no capital by which I could do this. I\ntook a few of my friends into the secret, however, and they\nsuggested that we should quietly and secretly work our own little\ndeposit and that in this way we should earn the money which would\nenable us to buy the neighbouring fields. This we have now been\ndoing for some time, and in order to help us in our operations we\nerected a hydraulic press. This press, as I have already\nexplained, has got out of order, and we wish your advice upon the\nsubject. We guard our secret very jealously, however, and if it\nonce became known that we had hydraulic engineers coming to our\nlittle house, it would soon rouse inquiry, and then, if the facts\ncame out, it would be good-bye to any chance of getting these\nfields and carrying out our plans. That is why I have made you\npromise me that you will not tell a human being that you are\ngoing to Eyford to-night. I hope that I make it all plain?'\n\n\"'I quite follow you,' said I. 'The only point which I could not\nquite understand was what use you could make of a hydraulic press\nin excavating fuller's-earth, which, as I understand, is dug out\nlike gravel from a pit.'\n\n\"'Ah!' said he carelessly, 'we have our own process. We compress\nthe earth into bricks, so as to remove them without revealing\nwhat they are. But that is a mere detail. I have taken you fully\ninto my confidence now, Mr. Hatherley, and I have shown you how I\ntrust you.' He rose as he spoke. 'I shall expect you, then, at\nEyford at 11:15.'\n\n\"'I shall certainly be there.'\n\n\"'And not a word to a soul.' He looked at me with a last long,\nquestioning gaze, and then, pressing my hand in a cold, dank\ngrasp, he hurried from the room.\n\n\"Well, when I came to think it all over in cool blood I was very\nmuch astonished, as you may both think, at this sudden commission\nwhich had been intrusted to me. On the one hand, of course, I was\nglad, for the fee was at least tenfold what I should have asked\nhad I set a price upon my own services, and it was possible that\nthis order might lead to other ones. On the other hand, the face\nand manner of my patron had made an unpleasant impression upon\nme, and I could not think that his explanation of the\nfuller's-earth was sufficient to explain the necessity for my\ncoming at midnight, and his extreme anxiety lest I should tell\nanyone of my errand. However, I threw all fears to the winds, ate\na hearty supper, drove to Paddington, and started off, having\nobeyed to the letter the injunction as to holding my tongue.\n\n\"At Reading I had to change not only my carriage but my station.\nHowever, I was in time for the last train to Eyford, and I\nreached the little dim-lit station after eleven o'clock. I was the\nonly passenger who got out there, and there was no one upon the\nplatform save a single sleepy porter with a lantern. As I passed\nout through the wicket gate, however, I found my acquaintance of\nthe morning waiting in the shadow upon the other side. Without a\nword he grasped my arm and hurried me into a carriage, the door\nof which was standing open. He drew up the windows on either\nside, tapped on the wood-work, and away we went as fast as the\nhorse could go.\"\n\n\"One horse?\" interjected Holmes.\n\n\"Yes, only one.\"\n\n\"Did you observe the colour?\"\n\n\"Yes, I saw it by the side-lights when I was stepping into the\ncarriage. It was a chestnut.\"\n\n\"Tired-looking or fresh?\"\n\n\"Oh, fresh and glossy.\"\n\n\"Thank you. I am sorry to have interrupted you. Pray continue\nyour most interesting statement.\"\n\n\"Away we went then, and we drove for at least an hour. Colonel\nLysander Stark had said that it was only seven miles, but I\nshould think, from the rate that we seemed to go, and from the\ntime that we took, that it must have been nearer twelve. He sat\nat my side in silence all the time, and I was aware, more than\nonce when I glanced in his direction, that he was looking at me\nwith great intensity. The country roads seem to be not very good\nin that part of the world, for we lurched and jolted terribly. I\ntried to look out of the windows to see something of where we\nwere, but they were made of frosted glass, and I could make out\nnothing save the occasional bright blur of a passing light. Now\nand then I hazarded some remark to break the monotony of the\njourney, but the colonel answered only in monosyllables, and the\nconversation soon flagged. At last, however, the bumping of the\nroad was exchanged for the crisp smoothness of a gravel-drive,\nand the carriage came to a stand. Colonel Lysander Stark sprang\nout, and, as I followed after him, pulled me swiftly into a porch\nwhich gaped in front of us. We stepped, as it were, right out of\nthe carriage and into the hall, so that I failed to catch the\nmost fleeting glance of the front of the house. The instant that\nI had crossed the threshold the door slammed heavily behind us,\nand I heard faintly the rattle of the wheels as the carriage\ndrove away.\n\n\"It was pitch dark inside the house, and the colonel fumbled\nabout looking for matches and muttering under his breath.\nSuddenly a door opened at the other end of the passage, and a\nlong, golden bar of light shot out in our direction. It grew\nbroader, and a woman appeared with a lamp in her hand, which she\nheld above her head, pushing her face forward and peering at us.\nI could see that she was pretty, and from the gloss with which\nthe light shone upon her dark dress I knew that it was a rich\nmaterial. She spoke a few words in a foreign tongue in a tone as\nthough asking a question, and when my companion answered in a\ngruff monosyllable she gave such a start that the lamp nearly\nfell from her hand. Colonel Stark went up to her, whispered\nsomething in her ear, and then, pushing her back into the room\nfrom whence she had come, he walked towards me again with the\nlamp in his hand.\n\n\"'Perhaps you will have the kindness to wait in this room for a\nfew minutes,' said he, throwing open another door. It was a\nquiet, little, plainly furnished room, with a round table in the\ncentre, on which several German books were scattered. Colonel\nStark laid down the lamp on the top of a harmonium beside the\ndoor. 'I shall not keep you waiting an instant,' said he, and\nvanished into the darkness.\n\n\"I glanced at the books upon the table, and in spite of my\nignorance of German I could see that two of them were treatises\non science, the others being volumes of poetry. Then I walked\nacross to the window, hoping that I might catch some glimpse of\nthe country-side, but an oak shutter, heavily barred, was folded\nacross it. It was a wonderfully silent house. There was an old\nclock ticking loudly somewhere in the passage, but otherwise\neverything was deadly still. A vague feeling of uneasiness began\nto steal over me. Who were these German people, and what were\nthey doing living in this strange, out-of-the-way place? And\nwhere was the place? I was ten miles or so from Eyford, that was\nall I knew, but whether north, south, east, or west I had no\nidea. For that matter, Reading, and possibly other large towns,\nwere within that radius, so the place might not be so secluded,\nafter all. Yet it was quite certain, from the absolute stillness,\nthat we were in the country. I paced up and down the room,\nhumming a tune under my breath to keep up my spirits and feeling\nthat I was thoroughly earning my fifty-guinea fee.\n\n\"Suddenly, without any preliminary sound in the midst of the\nutter stillness, the door of my room swung slowly open. The woman\nwas standing in the aperture, the darkness of the hall behind\nher, the yellow light from my lamp beating upon her eager and\nbeautiful face. I could see at a glance that she was sick with\nfear, and the sight sent a chill to my own heart. She held up one\nshaking finger to warn me to be silent, and she shot a few\nwhispered words of broken English at me, her eyes glancing back,\nlike those of a frightened horse, into the gloom behind her.\n\n\"'I would go,' said she, trying hard, as it seemed to me, to\nspeak calmly; 'I would go. I should not stay here. There is no\ngood for you to do.'\n\n\"'But, madam,' said I, 'I have not yet done what I came for. I\ncannot possibly leave until I have seen the machine.'\n\n\"'It is not worth your while to wait,' she went on. 'You can pass\nthrough the door; no one hinders.' And then, seeing that I smiled\nand shook my head, she suddenly threw aside her constraint and\nmade a step forward, with her hands wrung together. 'For the love\nof Heaven!' she whispered, 'get away from here before it is too\nlate!'\n\n\"But I am somewhat headstrong by nature, and the more ready to\nengage in an affair when there is some obstacle in the way. I\nthought of my fifty-guinea fee, of my wearisome journey, and of\nthe unpleasant night which seemed to be before me. Was it all to\ngo for nothing? Why should I slink away without having carried\nout my commission, and without the payment which was my due? This\nwoman might, for all I knew, be a monomaniac. With a stout\nbearing, therefore, though her manner had shaken me more than I\ncared to confess, I still shook my head and declared my intention\nof remaining where I was. She was about to renew her entreaties\nwhen a door slammed overhead, and the sound of several footsteps\nwas heard upon the stairs. She listened for an instant, threw up\nher hands with a despairing gesture, and vanished as suddenly and\nas noiselessly as she had come.\n\n\"The newcomers were Colonel Lysander Stark and a short thick man\nwith a chinchilla beard growing out of the creases of his double\nchin, who was introduced to me as Mr. Ferguson.\n\n\"'This is my secretary and manager,' said the colonel. 'By the\nway, I was under the impression that I left this door shut just\nnow. I fear that you have felt the draught.'\n\n\"'On the contrary,' said I, 'I opened the door myself because I\nfelt the room to be a little close.'\n\n\"He shot one of his suspicious looks at me. 'Perhaps we had\nbetter proceed to business, then,' said he. 'Mr. Ferguson and I\nwill take you up to see the machine.'\n\n\"'I had better put my hat on, I suppose.'\n\n\"'Oh, no, it is in the house.'\n\n\"'What, you dig fuller's-earth in the house?'\n\n\"'No, no. This is only where we compress it. But never mind that.\nAll we wish you to do is to examine the machine and to let us\nknow what is wrong with it.'\n\n\"We went upstairs together, the colonel first with the lamp, the\nfat manager and I behind him. It was a labyrinth of an old house,\nwith corridors, passages, narrow winding staircases, and little\nlow doors, the thresholds of which were hollowed out by the\ngenerations who had crossed them. There were no carpets and no\nsigns of any furniture above the ground floor, while the plaster\nwas peeling off the walls, and the damp was breaking through in\ngreen, unhealthy blotches. I tried to put on as unconcerned an\nair as possible, but I had not forgotten the warnings of the\nlady, even though I disregarded them, and I kept a keen eye upon\nmy two companions. Ferguson appeared to be a morose and silent\nman, but I could see from the little that he said that he was at\nleast a fellow-countryman.\n\n\"Colonel Lysander Stark stopped at last before a low door, which\nhe unlocked. Within was a small, square room, in which the three\nof us could hardly get at one time. Ferguson remained outside,\nand the colonel ushered me in.\n\n\"'We are now,' said he, 'actually within the hydraulic press, and\nit would be a particularly unpleasant thing for us if anyone were\nto turn it on. The ceiling of this small chamber is really the\nend of the descending piston, and it comes down with the force of\nmany tons upon this metal floor. There are small lateral columns\nof water outside which receive the force, and which transmit and\nmultiply it in the manner which is familiar to you. The machine\ngoes readily enough, but there is some stiffness in the working\nof it, and it has lost a little of its force. Perhaps you will\nhave the goodness to look it over and to show us how we can set\nit right.'\n\n\"I took the lamp from him, and I examined the machine very\nthoroughly. It was indeed a gigantic one, and capable of\nexercising enormous pressure. When I passed outside, however, and\npressed down the levers which controlled it, I knew at once by\nthe whishing sound that there was a slight leakage, which allowed\na regurgitation of water through one of the side cylinders. An\nexamination showed that one of the india-rubber bands which was\nround the head of a driving-rod had shrunk so as not quite to\nfill the socket along which it worked. This was clearly the cause\nof the loss of power, and I pointed it out to my companions, who\nfollowed my remarks very carefully and asked several practical\nquestions as to how they should proceed to set it right. When I\nhad made it clear to them, I returned to the main chamber of the\nmachine and took a good look at it to satisfy my own curiosity.\nIt was obvious at a glance that the story of the fuller's-earth\nwas the merest fabrication, for it would be absurd to suppose\nthat so powerful an engine could be designed for so inadequate a\npurpose. The walls were of wood, but the floor consisted of a\nlarge iron trough, and when I came to examine it I could see a\ncrust of metallic deposit all over it. I had stooped and was\nscraping at this to see exactly what it was when I heard a\nmuttered exclamation in German and saw the cadaverous face of the\ncolonel looking down at me.\n\n\"'What are you doing there?' he asked.\n\n\"I felt angry at having been tricked by so elaborate a story as\nthat which he had told me. 'I was admiring your fuller's-earth,'\nsaid I; 'I think that I should be better able to advise you as to\nyour machine if I knew what the exact purpose was for which it\nwas used.'\n\n\"The instant that I uttered the words I regretted the rashness of\nmy speech. His face set hard, and a baleful light sprang up in\nhis grey eyes.\n\n\"'Very well,' said he, 'you shall know all about the machine.' He\ntook a step backward, slammed the little door, and turned the key\nin the lock. I rushed towards it and pulled at the handle, but it\nwas quite secure, and did not give in the least to my kicks and\nshoves. 'Hullo!' I yelled. 'Hullo! Colonel! Let me out!'\n\n\"And then suddenly in the silence I heard a sound which sent my\nheart into my mouth. It was the clank of the levers and the swish\nof the leaking cylinder. He had set the engine at work. The lamp\nstill stood upon the floor where I had placed it when examining\nthe trough. By its light I saw that the black ceiling was coming\ndown upon me, slowly, jerkily, but, as none knew better than\nmyself, with a force which must within a minute grind me to a\nshapeless pulp. I threw myself, screaming, against the door, and\ndragged with my nails at the lock. I implored the colonel to let\nme out, but the remorseless clanking of the levers drowned my\ncries. The ceiling was only a foot or two above my head, and with\nmy hand upraised I could feel its hard, rough surface. Then it\nflashed through my mind that the pain of my death would depend\nvery much upon the position in which I met it. If I lay on my\nface the weight would come upon my spine, and I shuddered to\nthink of that dreadful snap. Easier the other way, perhaps; and\nyet, had I the nerve to lie and look up at that deadly black\nshadow wavering down upon me? Already I was unable to stand\nerect, when my eye caught something which brought a gush of hope\nback to my heart.\n\n\"I have said that though the floor and ceiling were of iron, the\nwalls were of wood. As I gave a last hurried glance around, I saw\na thin line of yellow light between two of the boards, which\nbroadened and broadened as a small panel was pushed backward. For\nan instant I could hardly believe that here was indeed a door\nwhich led away from death. The next instant I threw myself\nthrough, and lay half-fainting upon the other side. The panel had\nclosed again behind me, but the crash of the lamp, and a few\nmoments afterwards the clang of the two slabs of metal, told me\nhow narrow had been my escape.\n\n\"I was recalled to myself by a frantic plucking at my wrist, and\nI found myself lying upon the stone floor of a narrow corridor,\nwhile a woman bent over me and tugged at me with her left hand,\nwhile she held a candle in her right. It was the same good friend\nwhose warning I had so foolishly rejected.\n\n\"'Come! come!' she cried breathlessly. 'They will be here in a\nmoment. They will see that you are not there. Oh, do not waste\nthe so-precious time, but come!'\n\n\"This time, at least, I did not scorn her advice. I staggered to\nmy feet and ran with her along the corridor and down a winding\nstair. The latter led to another broad passage, and just as we\nreached it we heard the sound of running feet and the shouting of\ntwo voices, one answering the other from the floor on which  we\nwere and from the one beneath. My guide stopped and looked about\nher like one  who is at her wit's end. Then she threw open a door\nwhich led into a bedroom, through the window of which the moon\nwas shining brightly.\n\n\"'It is your only chance,' said she. 'It is high, but it may be\nthat you can jump it.'\n\n\"As she spoke a light sprang into view at the further end of the\npassage, and I saw the lean figure of Colonel Lysander Stark\nrushing forward with a lantern in one hand and a weapon like a\nbutcher's cleaver in the other. I rushed across the bedroom,\nflung open the window, and looked out. How quiet and sweet and\nwholesome the garden looked in the moonlight, and it could not be\nmore than thirty feet down. I clambered out upon the sill, but I\nhesitated to jump until I should have heard what passed between\nmy saviour and the ruffian who pursued me. If she were ill-used,\nthen at any risks I was determined to go back to her assistance.\nThe thought had hardly flashed through my mind before he was at\nthe door, pushing his way past her; but she threw her arms round\nhim and tried to hold him back.\n\n\"'Fritz! Fritz!' she cried in English, 'remember your promise\nafter the last time. You said it should not be again. He will be\nsilent! Oh, he will be silent!'\n\n\"'You are mad, Elise!' he shouted, struggling to break away from\nher. 'You will be the ruin of us. He has seen too much. Let me\npass, I say!' He dashed her to one side, and, rushing to the\nwindow, cut at me with his heavy weapon. I had let myself go, and\nwas hanging by the hands to the sill, when his blow fell. I was\nconscious of a dull pain, my grip loosened, and I fell into the\ngarden below.\n\n\"I was shaken but not hurt by the fall; so I picked myself up and\nrushed off among the bushes as hard as I could run, for I\nunderstood that I was far from being out of danger yet. Suddenly,\nhowever, as I ran, a deadly dizziness and sickness came over me.\nI glanced down at my hand, which was throbbing painfully, and\nthen, for the first time, saw that my thumb had been cut off and\nthat the blood was pouring from my wound. I endeavoured to tie my\nhandkerchief round it, but there came a sudden buzzing in my\nears, and next moment I fell in a dead faint among the\nrose-bushes.\n\n\"How long I remained unconscious I cannot tell. It must have been\na very long time, for the moon had sunk, and a bright morning was\nbreaking when I came to myself. My clothes were all sodden with\ndew, and my coat-sleeve was drenched with blood from my wounded\nthumb. The smarting of it recalled in an instant all the\nparticulars of my night's adventure, and I sprang to my feet with\nthe feeling that I might hardly yet be safe from my pursuers. But\nto my astonishment, when I came to look round me, neither house\nnor garden were to be seen. I had been lying in an angle of the\nhedge close by the highroad, and just a little lower down was a\nlong building, which proved, upon my approaching it, to be the\nvery station at which I had arrived upon the previous night. Were\nit not for the ugly wound upon my hand, all that had passed\nduring those dreadful hours might have been an evil dream.\n\n\"Half dazed, I went into the station and asked about the morning\ntrain. There would be one to Reading in less than an hour. The\nsame porter was on duty, I found, as had been there when I\narrived. I inquired of him whether he had ever heard of Colonel\nLysander Stark. The name was strange to him. Had he observed a\ncarriage the night before waiting for me? No, he had not. Was\nthere a police-station anywhere near? There was one about three\nmiles off.\n\n\"It was too far for me to go, weak and ill as I was. I determined\nto wait until I got back to town before telling my story to the\npolice. It was a little past six when I arrived, so I went first\nto have my wound dressed, and then the doctor was kind enough to\nbring me along here. I put the case into your hands and shall do\nexactly what you advise.\"\n\nWe both sat in silence for some little time after listening to\nthis extraordinary narrative. Then Sherlock Holmes pulled down\nfrom the shelf one of the ponderous commonplace books in which he\nplaced his cuttings.\n\n\"Here is an advertisement which will interest you,\" said he. \"It\nappeared in all the papers about a year ago. Listen to this:\n'Lost, on the 9th inst., Mr. Jeremiah Hayling, aged\ntwenty-six, a hydraulic engineer. Left his lodgings at ten\no'clock at night, and has not been heard of since. Was\ndressed in,' etc., etc. Ha! That represents the last time that\nthe colonel needed to have his machine overhauled, I fancy.\"\n\n\"Good heavens!\" cried my patient. \"Then that explains what the\ngirl said.\"\n\n\"Undoubtedly. It is quite clear that the colonel was a cool and\ndesperate man, who was absolutely determined that nothing should\nstand in the way of his little game, like those out-and-out\npirates who will leave no survivor from a captured ship. Well,\nevery moment now is precious, so if you feel equal to it we shall\ngo down to Scotland Yard at once as a preliminary to starting for\nEyford.\"\n\nSome three hours or so afterwards we were all in the train\ntogether, bound from Reading to the little Berkshire village.\nThere were Sherlock Holmes, the hydraulic engineer, Inspector\nBradstreet, of Scotland Yard, a plain-clothes man, and myself.\nBradstreet had spread an ordnance map of the county out upon the\nseat and was busy with his compasses drawing a circle with Eyford\nfor its centre.\n\n\"There you are,\" said he. \"That circle is drawn at a radius of\nten miles from the village. The place we want must be somewhere\nnear that line. You said ten miles, I think, sir.\"\n\n\"It was an hour's good drive.\"\n\n\"And you think that they brought you back all that way when you\nwere unconscious?\"\n\n\"They must have done so. I have a confused memory, too, of having\nbeen lifted and conveyed somewhere.\"\n\n\"What I cannot understand,\" said I, \"is why they should have\nspared you when they found you lying fainting in the garden.\nPerhaps the villain was softened by the woman's entreaties.\"\n\n\"I hardly think that likely. I never saw a more inexorable face\nin my life.\"\n\n\"Oh, we shall soon clear up all that,\" said Bradstreet. \"Well, I\nhave drawn my circle, and I only wish I knew at what point upon\nit the folk that we are in search of are to be found.\"\n\n\"I think I could lay my finger on it,\" said Holmes quietly.\n\n\"Really, now!\" cried the inspector, \"you have formed your\nopinion! Come, now, we shall see who agrees with you. I say it is\nsouth, for the country is more deserted there.\"\n\n\"And I say east,\" said my patient.\n\n\"I am for west,\" remarked the plain-clothes man. \"There are\nseveral quiet little villages up there.\"\n\n\"And I am for north,\" said I, \"because there are no hills there,\nand our friend says that he did not notice the carriage go up\nany.\"\n\n\"Come,\" cried the inspector, laughing; \"it's a very pretty\ndiversity of opinion. We have boxed the compass among us. Who do\nyou give your casting vote to?\"\n\n\"You are all wrong.\"\n\n\"But we can't all be.\"\n\n\"Oh, yes, you can. This is my point.\" He placed his finger in the\ncentre of the circle. \"This is where we shall find them.\"\n\n\"But the twelve-mile drive?\" gasped Hatherley.\n\n\"Six out and six back. Nothing simpler. You say yourself that the\nhorse was fresh and glossy when you got in. How could it be that\nif it had gone twelve miles over heavy roads?\"\n\n\"Indeed, it is a likely ruse enough,\" observed Bradstreet\nthoughtfully. \"Of course there can be no doubt as to the nature\nof this gang.\"\n\n\"None at all,\" said Holmes. \"They are coiners on a large scale,\nand have used the machine to form the amalgam which has taken the\nplace of silver.\"\n\n\"We have known for some time that a clever gang was at work,\"\nsaid the inspector. \"They have been turning out half-crowns by\nthe thousand. We even traced them as far as Reading, but could\nget no farther, for they had covered their traces in a way that\nshowed that they were very old hands. But now, thanks to this\nlucky chance, I think that we have got them right enough.\"\n\nBut the inspector was mistaken, for those criminals were not\ndestined to fall into the hands of justice. As we rolled into\nEyford Station we saw a gigantic column of smoke which streamed\nup from behind a small clump of trees in the neighbourhood and\nhung like an immense ostrich feather over the landscape.\n\n\"A house on fire?\" asked Bradstreet as the train steamed off\nagain on its way.\n\n\"Yes, sir!\" said the station-master.\n\n\"When did it break out?\"\n\n\"I hear that it was during the night, sir, but it has got worse,\nand the whole place is in a blaze.\"\n\n\"Whose house is it?\"\n\n\"Dr. Becher's.\"\n\n\"Tell me,\" broke in the engineer, \"is Dr. Becher a German, very\nthin, with a long, sharp nose?\"\n\nThe station-master laughed heartily. \"No, sir, Dr. Becher is an\nEnglishman, and there isn't a man in the parish who has a\nbetter-lined waistcoat. But he has a gentleman staying with him,\na patient, as I understand, who is a foreigner, and he looks as\nif a little good Berkshire beef would do him no harm.\"\n\nThe station-master had not finished his speech before we were all\nhastening in the direction of the fire. The road topped a low\nhill, and there was a great widespread whitewashed building in\nfront of us, spouting fire at every chink and window, while in\nthe garden in front three fire-engines were vainly striving to\nkeep the flames under.\n\n\"That's it!\" cried Hatherley, in intense excitement. \"There is\nthe gravel-drive, and there are the rose-bushes where I lay. That\nsecond window is the one that I jumped from.\"\n\n\"Well, at least,\" said Holmes, \"you have had your revenge upon\nthem. There can be no question that it was your oil-lamp which,\nwhen it was crushed in the press, set fire to the wooden walls,\nthough no doubt they were too excited in the chase after you to\nobserve it at the time. Now keep your eyes open in this crowd for\nyour friends of last night, though I very much fear that they are\na good hundred miles off by now.\"\n\nAnd Holmes' fears came to be realised, for from that day to this\nno word has ever been heard either of the beautiful woman, the\nsinister German, or the morose Englishman. Early that morning a\npeasant had met a cart containing several people and some very\nbulky boxes driving rapidly in the direction of Reading, but\nthere all traces of the fugitives disappeared, and even Holmes'\ningenuity failed ever to discover the least clue as to their\nwhereabouts.\n\nThe firemen had been much perturbed at the strange arrangements\nwhich they had found within, and still more so by discovering a\nnewly severed human thumb upon a window-sill of the second floor.\nAbout sunset, however, their efforts were at last successful, and\nthey subdued the flames, but not before the roof had fallen in,\nand the whole place been reduced to such absolute ruin that, save\nsome twisted cylinders and iron piping, not a trace remained of\nthe machinery which had cost our unfortunate acquaintance so\ndearly. Large masses of nickel and of tin were discovered stored\nin an out-house, but no coins were to be found, which may have\nexplained the presence of those bulky boxes which have been\nalready referred to.\n\nHow our hydraulic engineer had been conveyed from the garden to\nthe spot where he recovered his senses might have remained\nforever a mystery were it not for the soft mould, which told us a\nvery plain tale. He had evidently been carried down by two\npersons, one of whom had remarkably small feet and the other\nunusually large ones. On the whole, it was most probable that the\nsilent Englishman, being less bold or less murderous than his\ncompanion, had assisted the woman to bear the unconscious man out\nof the way of danger.\n\n\"Well,\" said our engineer ruefully as we took our seats to return\nonce more to London, \"it has been a pretty business for me! I\nhave lost my thumb and I have lost a fifty-guinea fee, and what\nhave I gained?\"\n\n\"Experience,\" said Holmes, laughing. \"Indirectly it may be of\nvalue, you know; you have only to put it into words to gain the\nreputation of being excellent company for the remainder of your\nexistence.\"\n\n\n\nX. THE ADVENTURE OF THE NOBLE BACHELOR\n\nThe Lord St. Simon marriage, and its curious termination, have\nlong ceased to be a subject of interest in those exalted circles\nin which the unfortunate bridegroom moves. Fresh scandals have\neclipsed it, and their more piquant details have drawn the\ngossips away from this four-year-old drama. As I have reason to\nbelieve, however, that the full facts have never been revealed to\nthe general public, and as my friend Sherlock Holmes had a\nconsiderable share in clearing the matter up, I feel that no\nmemoir of him would be complete without some little sketch of\nthis remarkable episode.\n\nIt was a few weeks before my own marriage, during the days when I\nwas still sharing rooms with Holmes in Baker Street, that he came\nhome from an afternoon stroll to find a letter on the table\nwaiting for him. I had remained indoors all day, for the weather\nhad taken a sudden turn to rain, with high autumnal winds, and\nthe Jezail bullet which I had brought back in one of my limbs as\na relic of my Afghan campaign throbbed with dull persistence.\nWith my body in one easy-chair and my legs upon another, I had\nsurrounded myself with a cloud of newspapers until at last,\nsaturated with the news of the day, I tossed them all aside and\nlay listless, watching the huge crest and monogram upon the\nenvelope upon the table and wondering lazily who my friend's\nnoble correspondent could be.\n\n\"Here is a very fashionable epistle,\" I remarked as he entered.\n\"Your morning letters, if I remember right, were from a\nfish-monger and a tide-waiter.\"\n\n\"Yes, my correspondence has certainly the charm of variety,\" he\nanswered, smiling, \"and the humbler are usually the more\ninteresting. This looks like one of those unwelcome social\nsummonses which call upon a man either to be bored or to lie.\"\n\nHe broke the seal and glanced over the contents.\n\n\"Oh, come, it may prove to be something of interest, after all.\"\n\n\"Not social, then?\"\n\n\"No, distinctly professional.\"\n\n\"And from a noble client?\"\n\n\"One of the highest in England.\"\n\n\"My dear fellow, I congratulate you.\"\n\n\"I assure you, Watson, without affectation, that the status of my\nclient is a matter of less moment to me than the interest of his\ncase. It is just possible, however, that that also may not be\nwanting in this new investigation. You have been reading the\npapers diligently of late, have you not?\"\n\n\"It looks like it,\" said I ruefully, pointing to a huge bundle in\nthe corner. \"I have had nothing else to do.\"\n\n\"It is fortunate, for you will perhaps be able to post me up. I\nread nothing except the criminal news and the agony column. The\nlatter is always instructive. But if you have followed recent\nevents so closely you must have read about Lord St. Simon and his\nwedding?\"\n\n\"Oh, yes, with the deepest interest.\"\n\n\"That is well. The letter which I hold in my hand is from Lord\nSt. Simon. I will read it to you, and in return you must turn\nover these papers and let me have whatever bears upon the matter.\nThis is what he says:\n\n\"'MY DEAR MR. SHERLOCK HOLMES:--Lord Backwater tells me that I\nmay place implicit reliance upon your judgment and discretion. I\nhave determined, therefore, to call upon you and to consult you\nin reference to the very painful event which has occurred in\nconnection with my wedding. Mr. Lestrade, of Scotland Yard, is\nacting already in the matter, but he assures me that he sees no\nobjection to your co-operation, and that he even thinks that\nit might be of some assistance. I will call at four o'clock in\nthe afternoon, and, should you have any other engagement at that\ntime, I hope that you will postpone it, as this matter is of\nparamount importance. Yours faithfully, ST. SIMON.'\n\n\"It is dated from Grosvenor Mansions, written with a quill pen,\nand the noble lord has had the misfortune to get a smear of ink\nupon the outer side of his right little finger,\" remarked Holmes\nas he folded up the epistle.\n\n\"He says four o'clock. It is three now. He will be here in an\nhour.\"\n\n\"Then I have just time, with your assistance, to get clear upon\nthe subject. Turn over those papers and arrange the extracts in\ntheir order of time, while I take a glance as to who our client\nis.\" He picked a red-covered volume from a line of books of\nreference beside the mantelpiece. \"Here he is,\" said he, sitting\ndown and flattening it out upon his knee. \"'Lord Robert Walsingham\nde Vere St. Simon, second son of the Duke of Balmoral.' Hum! 'Arms:\nAzure, three caltrops in chief over a fess sable. Born in 1846.'\nHe's forty-one years of age, which is mature for marriage. Was\nUnder-Secretary for the colonies in a late administration. The\nDuke, his father, was at one time Secretary for Foreign Affairs.\nThey inherit Plantagenet blood by direct descent, and Tudor on\nthe distaff side. Ha! Well, there is nothing very instructive in\nall this. I think that I must turn to you Watson, for something\nmore solid.\"\n\n\"I have very little difficulty in finding what I want,\" said I,\n\"for the facts are quite recent, and the matter struck me as\nremarkable. I feared to refer them to you, however, as I knew\nthat you had an inquiry on hand and that you disliked the\nintrusion of other matters.\"\n\n\"Oh, you mean the little problem of the Grosvenor Square\nfurniture van. That is quite cleared up now--though, indeed, it\nwas obvious from the first. Pray give me the results of your\nnewspaper selections.\"\n\n\"Here is the first notice which I can find. It is in the personal\ncolumn of the Morning Post, and dates, as you see, some weeks\nback: 'A marriage has been arranged,' it says, 'and will, if\nrumour is correct, very shortly take place, between Lord Robert\nSt. Simon, second son of the Duke of Balmoral, and Miss Hatty\nDoran, the only daughter of Aloysius Doran. Esq., of San\nFrancisco, Cal., U.S.A.' That is all.\"\n\n\"Terse and to the point,\" remarked Holmes, stretching his long,\nthin legs towards the fire.\n\n\"There was a paragraph amplifying this in one of the society\npapers of the same week. Ah, here it is: 'There will soon be a\ncall for protection in the marriage market, for the present\nfree-trade principle appears to tell heavily against our home\nproduct. One by one the management of the noble houses of Great\nBritain is passing into the hands of our fair cousins from across\nthe Atlantic. An important addition has been made during the last\nweek to the list of the prizes which have been borne away by\nthese charming invaders. Lord St. Simon, who has shown himself\nfor over twenty years proof against the little god's arrows, has\nnow definitely announced his approaching marriage with Miss Hatty\nDoran, the fascinating daughter of a California millionaire. Miss\nDoran, whose graceful figure and striking face attracted much\nattention at the Westbury House festivities, is an only child,\nand it is currently reported that her dowry will run to\nconsiderably over the six figures, with expectancies for the\nfuture. As it is an open secret that the Duke of Balmoral has\nbeen compelled to sell his pictures within the last few years,\nand as Lord St. Simon has no property of his own save the small\nestate of Birchmoor, it is obvious that the Californian heiress\nis not the only gainer by an alliance which will enable her to\nmake the easy and common transition from a Republican lady to a\nBritish peeress.'\"\n\n\"Anything else?\" asked Holmes, yawning.\n\n\"Oh, yes; plenty. Then there is another note in the Morning Post\nto say that the marriage would be an absolutely quiet one, that it\nwould be at St. George's, Hanover Square, that only half a dozen\nintimate friends would be invited, and that the party would\nreturn to the furnished house at Lancaster Gate which has been\ntaken by Mr. Aloysius Doran. Two days later--that is, on\nWednesday last--there is a curt announcement that the wedding had\ntaken place, and that the honeymoon would be passed at Lord\nBackwater's place, near Petersfield. Those are all the notices\nwhich appeared before the disappearance of the bride.\"\n\n\"Before the what?\" asked Holmes with a start.\n\n\"The vanishing of the lady.\"\n\n\"When did she vanish, then?\"\n\n\"At the wedding breakfast.\"\n\n\"Indeed. This is more interesting than it promised to be; quite\ndramatic, in fact.\"\n\n\"Yes; it struck me as being a little out of the common.\"\n\n\"They often vanish before the ceremony, and occasionally during\nthe honeymoon; but I cannot call to mind anything quite so prompt\nas this. Pray let me have the details.\"\n\n\"I warn you that they are very incomplete.\"\n\n\"Perhaps we may make them less so.\"\n\n\"Such as they are, they are set forth in a single article of a\nmorning paper of yesterday, which I will read to you. It is\nheaded, 'Singular Occurrence at a Fashionable Wedding':\n\n\"'The family of Lord Robert St. Simon has been thrown into the\ngreatest consternation by the strange and painful episodes which\nhave taken place in connection with his wedding. The ceremony, as\nshortly announced in the papers of yesterday, occurred on the\nprevious morning; but it is only now that it has been possible to\nconfirm the strange rumours which have been so persistently\nfloating about. In spite of the attempts of the friends to hush\nthe matter up, so much public attention has now been drawn to it\nthat no good purpose can be served by affecting to disregard what\nis a common subject for conversation.\n\n\"'The ceremony, which was performed at St. George's, Hanover\nSquare, was a very quiet one, no one being present save the\nfather of the bride, Mr. Aloysius Doran, the Duchess of Balmoral,\nLord Backwater, Lord Eustace and Lady Clara St. Simon (the\nyounger brother and sister of the bridegroom), and Lady Alicia\nWhittington. The whole party proceeded afterwards to the house of\nMr. Aloysius Doran, at Lancaster Gate, where breakfast had been\nprepared. It appears that some little trouble was caused by a\nwoman, whose name has not been ascertained, who endeavoured to\nforce her way into the house after the bridal party, alleging\nthat she had some claim upon Lord St. Simon. It was only after a\npainful and prolonged scene that she was ejected by the butler\nand the footman. The bride, who had fortunately entered the house\nbefore this unpleasant interruption, had sat down to breakfast\nwith the rest, when she complained of a sudden indisposition and\nretired to her room. Her prolonged absence having caused some\ncomment, her father followed her, but learned from her maid that\nshe had only come up to her chamber for an instant, caught up an\nulster and bonnet, and hurried down to the passage. One of the\nfootmen declared that he had seen a lady leave the house thus\napparelled, but had refused to credit that it was his mistress,\nbelieving her to be with the company. On ascertaining that his\ndaughter had disappeared, Mr. Aloysius Doran, in conjunction with\nthe bridegroom, instantly put themselves in communication with\nthe police, and very energetic inquiries are being made, which\nwill probably result in a speedy clearing up of this very\nsingular business. Up to a late hour last night, however, nothing\nhad transpired as to the whereabouts of the missing lady. There\nare rumours of foul play in the matter, and it is said that the\npolice have caused the arrest of the woman who had caused the\noriginal disturbance, in the belief that, from jealousy or some\nother motive, she may have been concerned in the strange\ndisappearance of the bride.'\"\n\n\"And is that all?\"\n\n\"Only one little item in another of the morning papers, but it is\na suggestive one.\"\n\n\"And it is--\"\n\n\"That Miss Flora Millar, the lady who had caused the disturbance,\nhas actually been arrested. It appears that she was formerly a\ndanseuse at the Allegro, and that she has known the bridegroom\nfor some years. There are no further particulars, and the whole\ncase is in your hands now--so far as it has been set forth in the\npublic press.\"\n\n\"And an exceedingly interesting case it appears to be. I would\nnot have missed it for worlds. But there is a ring at the bell,\nWatson, and as the clock makes it a few minutes after four, I\nhave no doubt that this will prove to be our noble client. Do not\ndream of going, Watson, for I very much prefer having a witness,\nif only as a check to my own memory.\"\n\n\"Lord Robert St. Simon,\" announced our page-boy, throwing open\nthe door. A gentleman entered, with a pleasant, cultured face,\nhigh-nosed and pale, with something perhaps of petulance about\nthe mouth, and with the steady, well-opened eye of a man whose\npleasant lot it had ever been to command and to be obeyed. His\nmanner was brisk, and yet his general appearance gave an undue\nimpression of age, for he had a slight forward stoop and a little\nbend of the knees as he walked. His hair, too, as he swept off\nhis very curly-brimmed hat, was grizzled round the edges and thin\nupon the top. As to his dress, it was careful to the verge of\nfoppishness, with high collar, black frock-coat, white waistcoat,\nyellow gloves, patent-leather shoes, and light-coloured gaiters.\nHe advanced slowly into the room, turning his head from left to\nright, and swinging in his right hand the cord which held his\ngolden eyeglasses.\n\n\"Good-day, Lord St. Simon,\" said Holmes, rising and bowing. \"Pray\ntake the basket-chair. This is my friend and colleague, Dr.\nWatson. Draw up a little to the fire, and we will talk this\nmatter over.\"\n\n\"A most painful matter to me, as you can most readily imagine,\nMr. Holmes. I have been cut to the quick. I understand that you\nhave already managed several delicate cases of this sort, sir,\nthough I presume that they were hardly from the same class of\nsociety.\"\n\n\"No, I am descending.\"\n\n\"I beg pardon.\"\n\n\"My last client of the sort was a king.\"\n\n\"Oh, really! I had no idea. And which king?\"\n\n\"The King of Scandinavia.\"\n\n\"What! Had he lost his wife?\"\n\n\"You can understand,\" said Holmes suavely, \"that I extend to the\naffairs of my other clients the same secrecy which I promise to\nyou in yours.\"\n\n\"Of course! Very right! very right! I'm sure I beg pardon. As to\nmy own case, I am ready to give you any information which may\nassist you in forming an opinion.\"\n\n\"Thank you. I have already learned all that is in the public\nprints, nothing more. I presume that I may take it as correct--this\narticle, for example, as to the disappearance of the bride.\"\n\nLord St. Simon glanced over it. \"Yes, it is correct, as far as it\ngoes.\"\n\n\"But it needs a great deal of supplementing before anyone could\noffer an opinion. I think that I may arrive at my facts most\ndirectly by questioning you.\"\n\n\"Pray do so.\"\n\n\"When did you first meet Miss Hatty Doran?\"\n\n\"In San Francisco, a year ago.\"\n\n\"You were travelling in the States?\"\n\n\"Yes.\"\n\n\"Did you become engaged then?\"\n\n\"No.\"\n\n\"But you were on a friendly footing?\"\n\n\"I was amused by her society, and she could see that I was\namused.\"\n\n\"Her father is very rich?\"\n\n\"He is said to be the richest man on the Pacific slope.\"\n\n\"And how did he make his money?\"\n\n\"In mining. He had nothing a few years ago. Then he struck gold,\ninvested it, and came up by leaps and bounds.\"\n\n\"Now, what is your own impression as to the young lady's--your\nwife's character?\"\n\nThe nobleman swung his glasses a little faster and stared down\ninto the fire. \"You see, Mr. Holmes,\" said he, \"my wife was\ntwenty before her father became a rich man. During that time she\nran free in a mining camp and wandered through woods or\nmountains, so that her education has come from Nature rather than\nfrom the schoolmaster. She is what we call in England a tomboy,\nwith a strong nature, wild and free, unfettered by any sort of\ntraditions. She is impetuous--volcanic, I was about to say. She\nis swift in making up her mind and fearless in carrying out her\nresolutions. On the other hand, I would not have given her the\nname which I have the honour to bear\"--he gave a little stately\ncough--\"had not I thought her to be at bottom a noble woman. I\nbelieve that she is capable of heroic self-sacrifice and that\nanything dishonourable would be repugnant to her.\"\n\n\"Have you her photograph?\"\n\n\"I brought this with me.\" He opened a locket and showed us the\nfull face of a very lovely woman. It was not a photograph but an\nivory miniature, and the artist had brought out the full effect\nof the lustrous black hair, the large dark eyes, and the\nexquisite mouth. Holmes gazed long and earnestly at it. Then he\nclosed the locket and handed it back to Lord St. Simon.\n\n\"The young lady came to London, then, and you renewed your\nacquaintance?\"\n\n\"Yes, her father brought her over for this last London season. I\nmet her several times, became engaged to her, and have now\nmarried her.\"\n\n\"She brought, I understand, a considerable dowry?\"\n\n\"A fair dowry. Not more than is usual in my family.\"\n\n\"And this, of course, remains to you, since the marriage is a\nfait accompli?\"\n\n\"I really have made no inquiries on the subject.\"\n\n\"Very naturally not. Did you see Miss Doran on the day before the\nwedding?\"\n\n\"Yes.\"\n\n\"Was she in good spirits?\"\n\n\"Never better. She kept talking of what we should do in our\nfuture lives.\"\n\n\"Indeed! That is very interesting. And on the morning of the\nwedding?\"\n\n\"She was as bright as possible--at least until after the\nceremony.\"\n\n\"And did you observe any change in her then?\"\n\n\"Well, to tell the truth, I saw then the first signs that I had\never seen that her temper was just a little sharp. The incident\nhowever, was too trivial to relate and can have no possible\nbearing upon the case.\"\n\n\"Pray let us have it, for all that.\"\n\n\"Oh, it is childish. She dropped her bouquet as we went towards\nthe vestry. She was passing the front pew at the time, and it\nfell over into the pew. There was a moment's delay, but the\ngentleman in the pew handed it up to her again, and it did not\nappear to be the worse for the fall. Yet when I spoke to her of\nthe matter, she answered me abruptly; and in the carriage, on our\nway home, she seemed absurdly agitated over this trifling cause.\"\n\n\"Indeed! You say that there was a gentleman in the pew. Some of\nthe general public were present, then?\"\n\n\"Oh, yes. It is impossible to exclude them when the church is\nopen.\"\n\n\"This gentleman was not one of your wife's friends?\"\n\n\"No, no; I call him a gentleman by courtesy, but he was quite a\ncommon-looking person. I hardly noticed his appearance. But\nreally I think that we are wandering rather far from the point.\"\n\n\"Lady St. Simon, then, returned from the wedding in a less\ncheerful frame of mind than she had gone to it. What did she do\non re-entering her father's house?\"\n\n\"I saw her in conversation with her maid.\"\n\n\"And who is her maid?\"\n\n\"Alice is her name. She is an American and came from California\nwith her.\"\n\n\"A confidential servant?\"\n\n\"A little too much so. It seemed to me that her mistress allowed\nher to take great liberties. Still, of course, in America they\nlook upon these things in a different way.\"\n\n\"How long did she speak to this Alice?\"\n\n\"Oh, a few minutes. I had something else to think of.\"\n\n\"You did not overhear what they said?\"\n\n\"Lady St. Simon said something about 'jumping a claim.' She was\naccustomed to use slang of the kind. I have no idea what she\nmeant.\"\n\n\"American slang is very expressive sometimes. And what did your\nwife do when she finished speaking to her maid?\"\n\n\"She walked into the breakfast-room.\"\n\n\"On your arm?\"\n\n\"No, alone. She was very independent in little matters like that.\nThen, after we had sat down for ten minutes or so, she rose\nhurriedly, muttered some words of apology, and left the room. She\nnever came back.\"\n\n\"But this maid, Alice, as I understand, deposes that she went to\nher room, covered her bride's dress with a long ulster, put on a\nbonnet, and went out.\"\n\n\"Quite so. And she was afterwards seen walking into Hyde Park in\ncompany with Flora Millar, a woman who is now in custody, and who\nhad already made a disturbance at Mr. Doran's house that\nmorning.\"\n\n\"Ah, yes. I should like a few particulars as to this young lady,\nand your relations to her.\"\n\nLord St. Simon shrugged his shoulders and raised his eyebrows.\n\"We have been on a friendly footing for some years--I may say on\na very friendly footing. She used to be at the Allegro. I have\nnot treated her ungenerously, and she had no just cause of\ncomplaint against me, but you know what women are, Mr. Holmes.\nFlora was a dear little thing, but exceedingly hot-headed and\ndevotedly attached to me. She wrote me dreadful letters when she\nheard that I was about to be married, and, to tell the truth, the\nreason why I had the marriage celebrated so quietly was that I\nfeared lest there might be a scandal in the church. She came to\nMr. Doran's door just after we returned, and she endeavoured to\npush her way in, uttering very abusive expressions towards my\nwife, and even threatening her, but I had foreseen the\npossibility of something of the sort, and I had two police\nfellows there in private clothes, who soon pushed her out again.\nShe was quiet when she saw that there was no good in making a\nrow.\"\n\n\"Did your wife hear all this?\"\n\n\"No, thank goodness, she did not.\"\n\n\"And she was seen walking with this very woman afterwards?\"\n\n\"Yes. That is what Mr. Lestrade, of Scotland Yard, looks upon as\nso serious. It is thought that Flora decoyed my wife out and laid\nsome terrible trap for her.\"\n\n\"Well, it is a possible supposition.\"\n\n\"You think so, too?\"\n\n\"I did not say a probable one. But you do not yourself look upon\nthis as likely?\"\n\n\"I do not think Flora would hurt a fly.\"\n\n\"Still, jealousy is a strange transformer of characters. Pray\nwhat is your own theory as to what took place?\"\n\n\"Well, really, I came to seek a theory, not to propound one. I\nhave given you all the facts. Since you ask me, however, I may\nsay that it has occurred to me as possible that the excitement of\nthis affair, the consciousness that she had made so immense a\nsocial stride, had the effect of causing some little nervous\ndisturbance in my wife.\"\n\n\"In short, that she had become suddenly deranged?\"\n\n\"Well, really, when I consider that she has turned her back--I\nwill not say upon me, but upon so much that many have aspired to\nwithout success--I can hardly explain it in any other fashion.\"\n\n\"Well, certainly that is also a conceivable hypothesis,\" said\nHolmes, smiling. \"And now, Lord St. Simon, I think that I have\nnearly all my data. May I ask whether you were seated at the\nbreakfast-table so that you could see out of the window?\"\n\n\"We could see the other side of the road and the Park.\"\n\n\"Quite so. Then I do not think that I need to detain you longer.\nI shall communicate with you.\"\n\n\"Should you be fortunate enough to solve this problem,\" said our\nclient, rising.\n\n\"I have solved it.\"\n\n\"Eh? What was that?\"\n\n\"I say that I have solved it.\"\n\n\"Where, then, is my wife?\"\n\n\"That is a detail which I shall speedily supply.\"\n\nLord St. Simon shook his head. \"I am afraid that it will take\nwiser heads than yours or mine,\" he remarked, and bowing in a\nstately, old-fashioned manner he departed.\n\n\"It is very good of Lord St. Simon to honour my head by putting\nit on a level with his own,\" said Sherlock Holmes, laughing. \"I\nthink that I shall have a whisky and soda and a cigar after all\nthis cross-questioning. I had formed my conclusions as to the\ncase before our client came into the room.\"\n\n\"My dear Holmes!\"\n\n\"I have notes of several similar cases, though none, as I\nremarked before, which were quite as prompt. My whole examination\nserved to turn my conjecture into a certainty. Circumstantial\nevidence is occasionally very convincing, as when you find a\ntrout in the milk, to quote Thoreau's example.\"\n\n\"But I have heard all that you have heard.\"\n\n\"Without, however, the knowledge of pre-existing cases which\nserves me so well. There was a parallel instance in Aberdeen some\nyears back, and something on very much the same lines at Munich\nthe year after the Franco-Prussian War. It is one of these\ncases--but, hullo, here is Lestrade! Good-afternoon, Lestrade!\nYou will find an extra tumbler upon the sideboard, and there are\ncigars in the box.\"\n\nThe official detective was attired in a pea-jacket and cravat,\nwhich gave him a decidedly nautical appearance, and he carried a\nblack canvas bag in his hand. With a short greeting he seated\nhimself and lit the cigar which had been offered to him.\n\n\"What's up, then?\" asked Holmes with a twinkle in his eye. \"You\nlook dissatisfied.\"\n\n\"And I feel dissatisfied. It is this infernal St. Simon marriage\ncase. I can make neither head nor tail of the business.\"\n\n\"Really! You surprise me.\"\n\n\"Who ever heard of such a mixed affair? Every clue seems to slip\nthrough my fingers. I have been at work upon it all day.\"\n\n\"And very wet it seems to have made you,\" said Holmes laying his\nhand upon the arm of the pea-jacket.\n\n\"Yes, I have been dragging the Serpentine.\"\n\n\"In heaven's name, what for?\"\n\n\"In search of the body of Lady St. Simon.\"\n\nSherlock Holmes leaned back in his chair and laughed heartily.\n\n\"Have you dragged the basin of Trafalgar Square fountain?\" he\nasked.\n\n\"Why? What do you mean?\"\n\n\"Because you have just as good a chance of finding this lady in\nthe one as in the other.\"\n\nLestrade shot an angry glance at my companion. \"I suppose you\nknow all about it,\" he snarled.\n\n\"Well, I have only just heard the facts, but my mind is made up.\"\n\n\"Oh, indeed! Then you think that the Serpentine plays no part in\nthe matter?\"\n\n\"I think it very unlikely.\"\n\n\"Then perhaps you will kindly explain how it is that we found\nthis in it?\" He opened his bag as he spoke, and tumbled onto the\nfloor a wedding-dress of watered silk, a pair of white satin\nshoes and a bride's wreath and veil, all discoloured and soaked\nin water. \"There,\" said he, putting a new wedding-ring upon the\ntop of the pile. \"There is a little nut for you to crack, Master\nHolmes.\"\n\n\"Oh, indeed!\" said my friend, blowing blue rings into the air.\n\"You dragged them from the Serpentine?\"\n\n\"No. They were found floating near the margin by a park-keeper.\nThey have been identified as her clothes, and it seemed to me\nthat if the clothes were there the body would not be far off.\"\n\n\"By the same brilliant reasoning, every man's body is to be found\nin the neighbourhood of his wardrobe. And pray what did you hope\nto arrive at through this?\"\n\n\"At some evidence implicating Flora Millar in the disappearance.\"\n\n\"I am afraid that you will find it difficult.\"\n\n\"Are you, indeed, now?\" cried Lestrade with some bitterness. \"I\nam afraid, Holmes, that you are not very practical with your\ndeductions and your inferences. You have made two blunders in as\nmany minutes. This dress does implicate Miss Flora Millar.\"\n\n\"And how?\"\n\n\"In the dress is a pocket. In the pocket is a card-case. In the\ncard-case is a note. And here is the very note.\" He slapped it\ndown upon the table in front of him. \"Listen to this: 'You will\nsee me when all is ready. Come at once. F.H.M.' Now my theory all\nalong has been that Lady St. Simon was decoyed away by Flora\nMillar, and that she, with confederates, no doubt, was\nresponsible for her disappearance. Here, signed with her\ninitials, is the very note which was no doubt quietly slipped\ninto her hand at the door and which lured her within their\nreach.\"\n\n\"Very good, Lestrade,\" said Holmes, laughing. \"You really are\nvery fine indeed. Let me see it.\" He took up the paper in a\nlistless way, but his attention instantly became riveted, and he\ngave a little cry of satisfaction. \"This is indeed important,\"\nsaid he.\n\n\"Ha! you find it so?\"\n\n\"Extremely so. I congratulate you warmly.\"\n\nLestrade rose in his triumph and bent his head to look. \"Why,\" he\nshrieked, \"you're looking at the wrong side!\"\n\n\"On the contrary, this is the right side.\"\n\n\"The right side? You're mad! Here is the note written in pencil\nover here.\"\n\n\"And over here is what appears to be the fragment of a hotel\nbill, which interests me deeply.\"\n\n\"There's nothing in it. I looked at it before,\" said Lestrade.\n\"'Oct. 4th, rooms 8s., breakfast 2s. 6d., cocktail 1s., lunch 2s.\n6d., glass sherry, 8d.' I see nothing in that.\"\n\n\"Very likely not. It is most important, all the same. As to the\nnote, it is important also, or at least the initials are, so I\ncongratulate you again.\"\n\n\"I've wasted time enough,\" said Lestrade, rising. \"I believe in\nhard work and not in sitting by the fire spinning fine theories.\nGood-day, Mr. Holmes, and we shall see which gets to the bottom\nof the matter first.\" He gathered up the garments, thrust them\ninto the bag, and made for the door.\n\n\"Just one hint to you, Lestrade,\" drawled Holmes before his rival\nvanished; \"I will tell you the true solution of the matter. Lady\nSt. Simon is a myth. There is not, and there never has been, any\nsuch person.\"\n\nLestrade looked sadly at my companion. Then he turned to me,\ntapped his forehead three times, shook his head solemnly, and\nhurried away.\n\nHe had hardly shut the door behind him when Holmes rose to put on\nhis overcoat. \"There is something in what the fellow says about\noutdoor work,\" he remarked, \"so I think, Watson, that I must\nleave you to your papers for a little.\"\n\nIt was after five o'clock when Sherlock Holmes left me, but I had\nno time to be lonely, for within an hour there arrived a\nconfectioner's man with a very large flat box. This he unpacked\nwith the help of a youth whom he had brought with him, and\npresently, to my very great astonishment, a quite epicurean\nlittle cold supper began to be laid out upon our humble\nlodging-house mahogany. There were a couple of brace of cold\nwoodcock, a pheasant, a pâté de foie gras pie with a group of\nancient and cobwebby bottles. Having laid out all these luxuries,\nmy two visitors vanished away, like the genii of the Arabian\nNights, with no explanation save that the things had been paid\nfor and were ordered to this address.\n\nJust before nine o'clock Sherlock Holmes stepped briskly into the\nroom. His features were gravely set, but there was a light in his\neye which made me think that he had not been disappointed in his\nconclusions.\n\n\"They have laid the supper, then,\" he said, rubbing his hands.\n\n\"You seem to expect company. They have laid for five.\"\n\n\"Yes, I fancy we may have some company dropping in,\" said he. \"I\nam surprised that Lord St. Simon has not already arrived. Ha! I\nfancy that I hear his step now upon the stairs.\"\n\nIt was indeed our visitor of the afternoon who came bustling in,\ndangling his glasses more vigorously than ever, and with a very\nperturbed expression upon his aristocratic features.\n\n\"My messenger reached you, then?\" asked Holmes.\n\n\"Yes, and I confess that the contents startled me beyond measure.\nHave you good authority for what you say?\"\n\n\"The best possible.\"\n\nLord St. Simon sank into a chair and passed his hand over his\nforehead.\n\n\"What will the Duke say,\" he murmured, \"when he hears that one of\nthe family has been subjected to such humiliation?\"\n\n\"It is the purest accident. I cannot allow that there is any\nhumiliation.\"\n\n\"Ah, you look on these things from another standpoint.\"\n\n\"I fail to see that anyone is to blame. I can hardly see how the\nlady could have acted otherwise, though her abrupt method of\ndoing it was undoubtedly to be regretted. Having no mother, she\nhad no one to advise her at such a crisis.\"\n\n\"It was a slight, sir, a public slight,\" said Lord St. Simon,\ntapping his fingers upon the table.\n\n\"You must make allowance for this poor girl, placed in so\nunprecedented a position.\"\n\n\"I will make no allowance. I am very angry indeed, and I have\nbeen shamefully used.\"\n\n\"I think that I heard a ring,\" said Holmes. \"Yes, there are steps\non the landing. If I cannot persuade you to take a lenient view\nof the matter, Lord St. Simon, I have brought an advocate here\nwho may be more successful.\" He opened the door and ushered in a\nlady and gentleman. \"Lord St. Simon,\" said he \"allow me to\nintroduce you to Mr. and Mrs. Francis Hay Moulton. The lady, I\nthink, you have already met.\"\n\nAt the sight of these newcomers our client had sprung from his\nseat and stood very erect, with his eyes cast down and his hand\nthrust into the breast of his frock-coat, a picture of offended\ndignity. The lady had taken a quick step forward and had held out\nher hand to him, but he still refused to raise his eyes. It was\nas well for his resolution, perhaps, for her pleading face was\none which it was hard to resist.\n\n\"You're angry, Robert,\" said she. \"Well, I guess you have every\ncause to be.\"\n\n\"Pray make no apology to me,\" said Lord St. Simon bitterly.\n\n\"Oh, yes, I know that I have treated you real bad and that I\nshould have spoken to you before I went; but I was kind of\nrattled, and from the time when I saw Frank here again I just\ndidn't know what I was doing or saying. I only wonder I didn't\nfall down and do a faint right there before the altar.\"\n\n\"Perhaps, Mrs. Moulton, you would like my friend and me to leave\nthe room while you explain this matter?\"\n\n\"If I may give an opinion,\" remarked the strange gentleman,\n\"we've had just a little too much secrecy over this business\nalready. For my part, I should like all Europe and America to\nhear the rights of it.\" He was a small, wiry, sunburnt man,\nclean-shaven, with a sharp face and alert manner.\n\n\"Then I'll tell our story right away,\" said the lady. \"Frank here\nand I met in '84, in McQuire's camp, near the Rockies, where pa\nwas working a claim. We were engaged to each other, Frank and I;\nbut then one day father struck a rich pocket and made a pile,\nwhile poor Frank here had a claim that petered out and came to\nnothing. The richer pa grew the poorer was Frank; so at last pa\nwouldn't hear of our engagement lasting any longer, and he took\nme away to 'Frisco. Frank wouldn't throw up his hand, though; so\nhe followed me there, and he saw me without pa knowing anything\nabout it. It would only have made him mad to know, so we just\nfixed it all up for ourselves. Frank said that he would go and\nmake his pile, too, and never come back to claim me until he had\nas much as pa. So then I promised to wait for him to the end of\ntime and pledged myself not to marry anyone else while he lived.\n'Why shouldn't we be married right away, then,' said he, 'and\nthen I will feel sure of you; and I won't claim to be your\nhusband until I come back?' Well, we talked it over, and he had\nfixed it all up so nicely, with a clergyman all ready in waiting,\nthat we just did it right there; and then Frank went off to seek\nhis fortune, and I went back to pa.\n\n\"The next I heard of Frank was that he was in Montana, and then\nhe went prospecting in Arizona, and then I heard of him from New\nMexico. After that came a long newspaper story about how a\nminers' camp had been attacked by Apache Indians, and there was\nmy Frank's name among the killed. I fainted dead away, and I was\nvery sick for months after. Pa thought I had a decline and took\nme to half the doctors in 'Frisco. Not a word of news came for a\nyear and more, so that I never doubted that Frank was really\ndead. Then Lord St. Simon came to 'Frisco, and we came to London,\nand a marriage was arranged, and pa was very pleased, but I felt\nall the time that no man on this earth would ever take the place\nin my heart that had been given to my poor Frank.\n\n\"Still, if I had married Lord St. Simon, of course I'd have done\nmy duty by him. We can't command our love, but we can our\nactions. I went to the altar with him with the intention to make\nhim just as good a wife as it was in me to be. But you may\nimagine what I felt when, just as I came to the altar rails, I\nglanced back and saw Frank standing and looking at me out of the\nfirst pew. I thought it was his ghost at first; but when I looked\nagain there he was still, with a kind of question in his eyes, as\nif to ask me whether I were glad or sorry to see him. I wonder I\ndidn't drop. I know that everything was turning round, and the\nwords of the clergyman were just like the buzz of a bee in my\near. I didn't know what to do. Should I stop the service and make\na scene in the church? I glanced at him again, and he seemed to\nknow what I was thinking, for he raised his finger to his lips to\ntell me to be still. Then I saw him scribble on a piece of paper,\nand I knew that he was writing me a note. As I passed his pew on\nthe way out I dropped my bouquet over to him, and he slipped the\nnote into my hand when he returned me the flowers. It was only a\nline asking me to join him when he made the sign to me to do so.\nOf course I never doubted for a moment that my first duty was now\nto him, and I determined to do just whatever he might direct.\n\n\"When I got back I told my maid, who had known him in California,\nand had always been his friend. I ordered her to say nothing, but\nto get a few things packed and my ulster ready. I know I ought to\nhave spoken to Lord St. Simon, but it was dreadful hard before\nhis mother and all those great people. I just made up my mind to\nrun away and explain afterwards. I hadn't been at the table ten\nminutes before I saw Frank out of the window at the other side of\nthe road. He beckoned to me and then began walking into the Park.\nI slipped out, put on my things, and followed him. Some woman\ncame talking something or other about Lord St. Simon to\nme--seemed to me from the little I heard as if he had a little\nsecret of his own before marriage also--but I managed to get away\nfrom her and soon overtook Frank. We got into a cab together, and\naway we drove to some lodgings he had taken in Gordon Square, and\nthat was my true wedding after all those years of waiting. Frank\nhad been a prisoner among the Apaches, had escaped, came on to\n'Frisco, found that I had given him up for dead and had gone to\nEngland, followed me there, and had come upon me at last on the\nvery morning of my second wedding.\"\n\n\"I saw it in a paper,\" explained the American. \"It gave the name\nand the church but not where the lady lived.\"\n\n\"Then we had a talk as to what we should do, and Frank was all\nfor openness, but I was so ashamed of it all that I felt as if I\nshould like to vanish away and never see any of them again--just\nsending a line to pa, perhaps, to show him that I was alive. It\nwas awful to me to think of all those lords and ladies sitting\nround that breakfast-table and waiting for me to come back. So\nFrank took my wedding-clothes and things and made a bundle of\nthem, so that I should not be traced, and dropped them away\nsomewhere where no one could find them. It is likely that we\nshould have gone on to Paris to-morrow, only that this good\ngentleman, Mr. Holmes, came round to us this evening, though how\nhe found us is more than I can think, and he showed us very\nclearly and kindly that I was wrong and that Frank was right, and\nthat we should be putting ourselves in the wrong if we were so\nsecret. Then he offered to give us a chance of talking to Lord\nSt. Simon alone, and so we came right away round to his rooms at\nonce. Now, Robert, you have heard it all, and I am very sorry if\nI have given you pain, and I hope that you do not think very\nmeanly of me.\"\n\nLord St. Simon had by no means relaxed his rigid attitude, but\nhad listened with a frowning brow and a compressed lip to this\nlong narrative.\n\n\"Excuse me,\" he said, \"but it is not my custom to discuss my most\nintimate personal affairs in this public manner.\"\n\n\"Then you won't forgive me? You won't shake hands before I go?\"\n\n\"Oh, certainly, if it would give you any pleasure.\" He put out\nhis hand and coldly grasped that which she extended to him.\n\n\"I had hoped,\" suggested Holmes, \"that you would have joined us\nin a friendly supper.\"\n\n\"I think that there you ask a little too much,\" responded his\nLordship. \"I may be forced to acquiesce in these recent\ndevelopments, but I can hardly be expected to make merry over\nthem. I think that with your permission I will now wish you all a\nvery good-night.\" He included us all in a sweeping bow and\nstalked out of the room.\n\n\"Then I trust that you at least will honour me with your\ncompany,\" said Sherlock Holmes. \"It is always a joy to meet an\nAmerican, Mr. Moulton, for I am one of those who believe that the\nfolly of a monarch and the blundering of a minister in far-gone\nyears will not prevent our children from being some day citizens\nof the same world-wide country under a flag which shall be a\nquartering of the Union Jack with the Stars and Stripes.\"\n\n\"The case has been an interesting one,\" remarked Holmes when our\nvisitors had left us, \"because it serves to show very clearly how\nsimple the explanation may be of an affair which at first sight\nseems to be almost inexplicable. Nothing could be more natural\nthan the sequence of events as narrated by this lady, and nothing\nstranger than the result when viewed, for instance, by Mr.\nLestrade of Scotland Yard.\"\n\n\"You were not yourself at fault at all, then?\"\n\n\"From the first, two facts were very obvious to me, the one that\nthe lady had been quite willing to undergo the wedding ceremony,\nthe other that she had repented of it within a few minutes of\nreturning home. Obviously something had occurred during the\nmorning, then, to cause her to change her mind. What could that\nsomething be? She could not have spoken to anyone when she was\nout, for she had been in the company of the bridegroom. Had she\nseen someone, then? If she had, it must be someone from America\nbecause she had spent so short a time in this country that she\ncould hardly have allowed anyone to acquire so deep an influence\nover her that the mere sight of him would induce her to change\nher plans so completely. You see we have already arrived, by a\nprocess of exclusion, at the idea that she might have seen an\nAmerican. Then who could this American be, and why should he\npossess so much influence over her? It might be a lover; it might\nbe a husband. Her young womanhood had, I knew, been spent in\nrough scenes and under strange conditions. So far I had got\nbefore I ever heard Lord St. Simon's narrative. When he told us\nof a man in a pew, of the change in the bride's manner, of so\ntransparent a device for obtaining a note as the dropping of a\nbouquet, of her resort to her confidential maid, and of her very\nsignificant allusion to claim-jumping--which in miners' parlance\nmeans taking possession of that which another person has a prior\nclaim to--the whole situation became absolutely clear. She had\ngone off with a man, and the man was either a lover or was a\nprevious husband--the chances being in favour of the latter.\"\n\n\"And how in the world did you find them?\"\n\n\"It might have been difficult, but friend Lestrade held\ninformation in his hands the value of which he did not himself\nknow. The initials were, of course, of the highest importance,\nbut more valuable still was it to know that within a week he had\nsettled his bill at one of the most select London hotels.\"\n\n\"How did you deduce the select?\"\n\n\"By the select prices. Eight shillings for a bed and eightpence\nfor a glass of sherry pointed to one of the most expensive\nhotels. There are not many in London which charge at that rate.\nIn the second one which I visited in Northumberland Avenue, I\nlearned by an inspection of the book that Francis H. Moulton, an\nAmerican gentleman, had left only the day before, and on looking\nover the entries against him, I came upon the very items which I\nhad seen in the duplicate bill. His letters were to be forwarded\nto 226 Gordon Square; so thither I travelled, and being fortunate\nenough to find the loving couple at home, I ventured to give them\nsome paternal advice and to point out to them that it would be\nbetter in every way that they should make their position a little\nclearer both to the general public and to Lord St. Simon in\nparticular. I invited them to meet him here, and, as you see, I\nmade him keep the appointment.\"\n\n\"But with no very good result,\" I remarked. \"His conduct was\ncertainly not very gracious.\"\n\n\"Ah, Watson,\" said Holmes, smiling, \"perhaps you would not be\nvery gracious either, if, after all the trouble of wooing and\nwedding, you found yourself deprived in an instant of wife and of\nfortune. I think that we may judge Lord St. Simon very mercifully\nand thank our stars that we are never likely to find ourselves in\nthe same position. Draw your chair up and hand me my violin, for\nthe only problem we have still to solve is how to while away\nthese bleak autumnal evenings.\"\n\n\n\nXI. THE ADVENTURE OF THE BERYL CORONET\n\n\"Holmes,\" said I as I stood one morning in our bow-window looking\ndown the street, \"here is a madman coming along. It seems rather\nsad that his relatives should allow him to come out alone.\"\n\nMy friend rose lazily from his armchair and stood with his hands\nin the pockets of his dressing-gown, looking over my shoulder. It\nwas a bright, crisp February morning, and the snow of the day\nbefore still lay deep upon the ground, shimmering brightly in the\nwintry sun. Down the centre of Baker Street it had been ploughed\ninto a brown crumbly band by the traffic, but at either side and\non the heaped-up edges of the foot-paths it still lay as white as\nwhen it fell. The grey pavement had been cleaned and scraped, but\nwas still dangerously slippery, so that there were fewer\npassengers than usual. Indeed, from the direction of the\nMetropolitan Station no one was coming save the single gentleman\nwhose eccentric conduct had drawn my attention.\n\nHe was a man of about fifty, tall, portly, and imposing, with a\nmassive, strongly marked face and a commanding figure. He was\ndressed in a sombre yet rich style, in black frock-coat, shining\nhat, neat brown gaiters, and well-cut pearl-grey trousers. Yet\nhis actions were in absurd contrast to the dignity of his dress\nand features, for he was running hard, with occasional little\nsprings, such as a weary man gives who is little accustomed to\nset any tax upon his legs. As he ran he jerked his hands up and\ndown, waggled his head, and writhed his face into the most\nextraordinary contortions.\n\n\"What on earth can be the matter with him?\" I asked. \"He is\nlooking up at the numbers of the houses.\"\n\n\"I believe that he is coming here,\" said Holmes, rubbing his\nhands.\n\n\"Here?\"\n\n\"Yes; I rather think he is coming to consult me professionally. I\nthink that I recognise the symptoms. Ha! did I not tell you?\" As\nhe spoke, the man, puffing and blowing, rushed at our door and\npulled at our bell until the whole house resounded with the\nclanging.\n\nA few moments later he was in our room, still puffing, still\ngesticulating, but with so fixed a look of grief and despair in\nhis eyes that our smiles were turned in an instant to horror and\npity. For a while he could not get his words out, but swayed his\nbody and plucked at his hair like one who has been driven to the\nextreme limits of his reason. Then, suddenly springing to his\nfeet, he beat his head against the wall with such force that we\nboth rushed upon him and tore him away to the centre of the room.\nSherlock Holmes pushed him down into the easy-chair and, sitting\nbeside him, patted his hand and chatted with him in the easy,\nsoothing tones which he knew so well how to employ.\n\n\"You have come to me to tell your story, have you not?\" said he.\n\"You are fatigued with your haste. Pray wait until you have\nrecovered yourself, and then I shall be most happy to look into\nany little problem which you may submit to me.\"\n\nThe man sat for a minute or more with a heaving chest, fighting\nagainst his emotion. Then he passed his handkerchief over his\nbrow, set his lips tight, and turned his face towards us.\n\n\"No doubt you think me mad?\" said he.\n\n\"I see that you have had some great trouble,\" responded Holmes.\n\n\"God knows I have!--a trouble which is enough to unseat my\nreason, so sudden and so terrible is it. Public disgrace I might\nhave faced, although I am a man whose character has never yet\nborne a stain. Private affliction also is the lot of every man;\nbut the two coming together, and in so frightful a form, have\nbeen enough to shake my very soul. Besides, it is not I alone.\nThe very noblest in the land may suffer unless some way be found\nout of this horrible affair.\"\n\n\"Pray compose yourself, sir,\" said Holmes, \"and let me have a\nclear account of who you are and what it is that has befallen\nyou.\"\n\n\"My name,\" answered our visitor, \"is probably familiar to your\nears. I am Alexander Holder, of the banking firm of Holder &\nStevenson, of Threadneedle Street.\"\n\nThe name was indeed well known to us as belonging to the senior\npartner in the second largest private banking concern in the City\nof London. What could have happened, then, to bring one of the\nforemost citizens of London to this most pitiable pass? We\nwaited, all curiosity, until with another effort he braced\nhimself to tell his story.\n\n\"I feel that time is of value,\" said he; \"that is why I hastened\nhere when the police inspector suggested that I should secure\nyour co-operation. I came to Baker Street by the Underground and\nhurried from there on foot, for the cabs go slowly through this\nsnow. That is why I was so out of breath, for I am a man who\ntakes very little exercise. I feel better now, and I will put the\nfacts before you as shortly and yet as clearly as I can.\n\n\"It is, of course, well known to you that in a successful banking\nbusiness as much depends upon our being able to find remunerative\ninvestments for our funds as upon our increasing our connection\nand the number of our depositors. One of our most lucrative means\nof laying out money is in the shape of loans, where the security\nis unimpeachable. We have done a good deal in this direction\nduring the last few years, and there are many noble families to\nwhom we have advanced large sums upon the security of their\npictures, libraries, or plate.\n\n\"Yesterday morning I was seated in my office at the bank when a\ncard was brought in to me by one of the clerks. I started when I\nsaw the name, for it was that of none other than--well, perhaps\neven to you I had better say no more than that it was a name\nwhich is a household word all over the earth--one of the highest,\nnoblest, most exalted names in England. I was overwhelmed by the\nhonour and attempted, when he entered, to say so, but he plunged\nat once into business with the air of a man who wishes to hurry\nquickly through a disagreeable task.\n\n\"'Mr. Holder,' said he, 'I have been informed that you are in the\nhabit of advancing money.'\n\n\"'The firm does so when the security is good.' I answered.\n\n\"'It is absolutely essential to me,' said he, 'that I should have\n50,000 pounds at once. I could, of course, borrow so trifling a\nsum ten times over from my friends, but I much prefer to make it\na matter of business and to carry out that business myself. In my\nposition you can readily understand that it is unwise to place\none's self under obligations.'\n\n\"'For how long, may I ask, do you want this sum?' I asked.\n\n\"'Next Monday I have a large sum due to me, and I shall then most\ncertainly repay what you advance, with whatever interest you\nthink it right to charge. But it is very essential to me that the\nmoney should be paid at once.'\n\n\"'I should be happy to advance it without further parley from my\nown private purse,' said I, 'were it not that the strain would be\nrather more than it could bear. If, on the other hand, I am to do\nit in the name of the firm, then in justice to my partner I must\ninsist that, even in your case, every businesslike precaution\nshould be taken.'\n\n\"'I should much prefer to have it so,' said he, raising up a\nsquare, black morocco case which he had laid beside his chair.\n'You have doubtless heard of the Beryl Coronet?'\n\n\"'One of the most precious public possessions of the empire,'\nsaid I.\n\n\"'Precisely.' He opened the case, and there, imbedded in soft,\nflesh-coloured velvet, lay the magnificent piece of jewellery\nwhich he had named. 'There are thirty-nine enormous beryls,' said\nhe, 'and the price of the gold chasing is incalculable. The\nlowest estimate would put the worth of the coronet at double the\nsum which I have asked. I am prepared to leave it with you as my\nsecurity.'\n\n\"I took the precious case into my hands and looked in some\nperplexity from it to my illustrious client.\n\n\"'You doubt its value?' he asked.\n\n\"'Not at all. I only doubt--'\n\n\"'The propriety of my leaving it. You may set your mind at rest\nabout that. I should not dream of doing so were it not absolutely\ncertain that I should be able in four days to reclaim it. It is a\npure matter of form. Is the security sufficient?'\n\n\"'Ample.'\n\n\"'You understand, Mr. Holder, that I am giving you a strong proof\nof the confidence which I have in you, founded upon all that I\nhave heard of you. I rely upon you not only to be discreet and to\nrefrain from all gossip upon the matter but, above all, to\npreserve this coronet with every possible precaution because I\nneed not say that a great public scandal would be caused if any\nharm were to befall it. Any injury to it would be almost as\nserious as its complete loss, for there are no beryls in the\nworld to match these, and it would be impossible to replace them.\nI leave it with you, however, with every confidence, and I shall\ncall for it in person on Monday morning.'\n\n\"Seeing that my client was anxious to leave, I said no more but,\ncalling for my cashier, I ordered him to pay over fifty 1000\npound notes. When I was alone once more, however, with the\nprecious case lying upon the table in front of me, I could not\nbut think with some misgivings of the immense responsibility\nwhich it entailed upon me. There could be no doubt that, as it\nwas a national possession, a horrible scandal would ensue if any\nmisfortune should occur to it. I already regretted having ever\nconsented to take charge of it. However, it was too late to alter\nthe matter now, so I locked it up in my private safe and turned\nonce more to my work.\n\n\"When evening came I felt that it would be an imprudence to leave\nso precious a thing in the office behind me. Bankers' safes had\nbeen forced before now, and why should not mine be? If so, how\nterrible would be the position in which I should find myself! I\ndetermined, therefore, that for the next few days I would always\ncarry the case backward and forward with me, so that it might\nnever be really out of my reach. With this intention, I called a\ncab and drove out to my house at Streatham, carrying the jewel\nwith me. I did not breathe freely until I had taken it upstairs\nand locked it in the bureau of my dressing-room.\n\n\"And now a word as to my household, Mr. Holmes, for I wish you to\nthoroughly understand the situation. My groom and my page sleep\nout of the house, and may be set aside altogether. I have three\nmaid-servants who have been with me a number of years and whose\nabsolute reliability is quite above suspicion. Another, Lucy\nParr, the second waiting-maid, has only been in my service a few\nmonths. She came with an excellent character, however, and has\nalways given me satisfaction. She is a very pretty girl and has\nattracted admirers who have occasionally hung about the place.\nThat is the only drawback which we have found to her, but we\nbelieve her to be a thoroughly good girl in every way.\n\n\"So much for the servants. My family itself is so small that it\nwill not take me long to describe it. I am a widower and have an\nonly son, Arthur. He has been a disappointment to me, Mr.\nHolmes--a grievous disappointment. I have no doubt that I am\nmyself to blame. People tell me that I have spoiled him. Very\nlikely I have. When my dear wife died I felt that he was all I\nhad to love. I could not bear to see the smile fade even for a\nmoment from his face. I have never denied him a wish. Perhaps it\nwould have been better for both of us had I been sterner, but I\nmeant it for the best.\n\n\"It was naturally my intention that he should succeed me in my\nbusiness, but he was not of a business turn. He was wild,\nwayward, and, to speak the truth, I could not trust him in the\nhandling of large sums of money. When he was young he became a\nmember of an aristocratic club, and there, having charming\nmanners, he was soon the intimate of a number of men with long\npurses and expensive habits. He learned to play heavily at cards\nand to squander money on the turf, until he had again and again\nto come to me and implore me to give him an advance upon his\nallowance, that he might settle his debts of honour. He tried\nmore than once to break away from the dangerous company which he\nwas keeping, but each time the influence of his friend, Sir\nGeorge Burnwell, was enough to draw him back again.\n\n\"And, indeed, I could not wonder that such a man as Sir George\nBurnwell should gain an influence over him, for he has frequently\nbrought him to my house, and I have found myself that I could\nhardly resist the fascination of his manner. He is older than\nArthur, a man of the world to his finger-tips, one who had been\neverywhere, seen everything, a brilliant talker, and a man of\ngreat personal beauty. Yet when I think of him in cold blood, far\naway from the glamour of his presence, I am convinced from his\ncynical speech and the look which I have caught in his eyes that\nhe is one who should be deeply distrusted. So I think, and so,\ntoo, thinks my little Mary, who has a woman's quick insight into\ncharacter.\n\n\"And now there is only she to be described. She is my niece; but\nwhen my brother died five years ago and left her alone in the\nworld I adopted her, and have looked upon her ever since as my\ndaughter. She is a sunbeam in my house--sweet, loving, beautiful,\na wonderful manager and housekeeper, yet as tender and quiet and\ngentle as a woman could be. She is my right hand. I do not know\nwhat I could do without her. In only one matter has she ever gone\nagainst my wishes. Twice my boy has asked her to marry him, for\nhe loves her devotedly, but each time she has refused him. I\nthink that if anyone could have drawn him into the right path it\nwould have been she, and that his marriage might have changed his\nwhole life; but now, alas! it is too late--forever too late!\n\n\"Now, Mr. Holmes, you know the people who live under my roof, and\nI shall continue with my miserable story.\n\n\"When we were taking coffee in the drawing-room that night after\ndinner, I told Arthur and Mary my experience, and of the precious\ntreasure which we had under our roof, suppressing only the name\nof my client. Lucy Parr, who had brought in the coffee, had, I am\nsure, left the room; but I cannot swear that the door was closed.\nMary and Arthur were much interested and wished to see the famous\ncoronet, but I thought it better not to disturb it.\n\n\"'Where have you put it?' asked Arthur.\n\n\"'In my own bureau.'\n\n\"'Well, I hope to goodness the house won't be burgled during the\nnight.' said he.\n\n\"'It is locked up,' I answered.\n\n\"'Oh, any old key will fit that bureau. When I was a youngster I\nhave opened it myself with the key of the box-room cupboard.'\n\n\"He often had a wild way of talking, so that I thought little of\nwhat he said. He followed me to my room, however, that night with\na very grave face.\n\n\"'Look here, dad,' said he with his eyes cast down, 'can you let\nme have 200 pounds?'\n\n\"'No, I cannot!' I answered sharply. 'I have been far too\ngenerous with you in money matters.'\n\n\"'You have been very kind,' said he, 'but I must have this money,\nor else I can never show my face inside the club again.'\n\n\"'And a very good thing, too!' I cried.\n\n\"'Yes, but you would not have me leave it a dishonoured man,'\nsaid he. 'I could not bear the disgrace. I must raise the money\nin some way, and if you will not let me have it, then I must try\nother means.'\n\n\"I was very angry, for this was the third demand during the\nmonth. 'You shall not have a farthing from me,' I cried, on which\nhe bowed and left the room without another word.\n\n\"When he was gone I unlocked my bureau, made sure that my\ntreasure was safe, and locked it again. Then I started to go\nround the house to see that all was secure--a duty which I\nusually leave to Mary but which I thought it well to perform\nmyself that night. As I came down the stairs I saw Mary herself\nat the side window of the hall, which she closed and fastened as\nI approached.\n\n\"'Tell me, dad,' said she, looking, I thought, a little\ndisturbed, 'did you give Lucy, the maid, leave to go out\nto-night?'\n\n\"'Certainly not.'\n\n\"'She came in just now by the back door. I have no doubt that she\nhas only been to the side gate to see someone, but I think that\nit is hardly safe and should be stopped.'\n\n\"'You must speak to her in the morning, or I will if you prefer\nit. Are you sure that everything is fastened?'\n\n\"'Quite sure, dad.'\n\n\"'Then, good-night.' I kissed her and went up to my bedroom\nagain, where I was soon asleep.\n\n\"I am endeavouring to tell you everything, Mr. Holmes, which may\nhave any bearing upon the case, but I beg that you will question\nme upon any point which I do not make clear.\"\n\n\"On the contrary, your statement is singularly lucid.\"\n\n\"I come to a part of my story now in which I should wish to be\nparticularly so. I am not a very heavy sleeper, and the anxiety\nin my mind tended, no doubt, to make me even less so than usual.\nAbout two in the morning, then, I was awakened by some sound in\nthe house. It had ceased ere I was wide awake, but it had left an\nimpression behind it as though a window had gently closed\nsomewhere. I lay listening with all my ears. Suddenly, to my\nhorror, there was a distinct sound of footsteps moving softly in\nthe next room. I slipped out of bed, all palpitating with fear,\nand peeped round the corner of my dressing-room door.\n\n\"'Arthur!' I screamed, 'you villain! you thief! How dare you\ntouch that coronet?'\n\n\"The gas was half up, as I had left it, and my unhappy boy,\ndressed only in his shirt and trousers, was standing beside the\nlight, holding the coronet in his hands. He appeared to be\nwrenching at it, or bending it with all his strength. At my cry\nhe dropped it from his grasp and turned as pale as death. I\nsnatched it up and examined it. One of the gold corners, with\nthree of the beryls in it, was missing.\n\n\"'You blackguard!' I shouted, beside myself with rage. 'You have\ndestroyed it! You have dishonoured me forever! Where are the\njewels which you have stolen?'\n\n\"'Stolen!' he cried.\n\n\"'Yes, thief!' I roared, shaking him by the shoulder.\n\n\"'There are none missing. There cannot be any missing,' said he.\n\n\"'There are three missing. And you know where they are. Must I\ncall you a liar as well as a thief? Did I not see you trying to\ntear off another piece?'\n\n\"'You have called me names enough,' said he, 'I will not stand it\nany longer. I shall not say another word about this business,\nsince you have chosen to insult me. I will leave your house in\nthe morning and make my own way in the world.'\n\n\"'You shall leave it in the hands of the police!' I cried\nhalf-mad with grief and rage. 'I shall have this matter probed to\nthe bottom.'\n\n\"'You shall learn nothing from me,' said he with a passion such\nas I should not have thought was in his nature. 'If you choose to\ncall the police, let the police find what they can.'\n\n\"By this time the whole house was astir, for I had raised my\nvoice in my anger. Mary was the first to rush into my room, and,\nat the sight of the coronet and of Arthur's face, she read the\nwhole story and, with a scream, fell down senseless on the\nground. I sent the house-maid for the police and put the\ninvestigation into their hands at once. When the inspector and a\nconstable entered the house, Arthur, who had stood sullenly with\nhis arms folded, asked me whether it was my intention to charge\nhim with theft. I answered that it had ceased to be a private\nmatter, but had become a public one, since the ruined coronet was\nnational property. I was determined that the law should have its\nway in everything.\n\n\"'At least,' said he, 'you will not have me arrested at once. It\nwould be to your advantage as well as mine if I might leave the\nhouse for five minutes.'\n\n\"'That you may get away, or perhaps that you may conceal what you\nhave stolen,' said I. And then, realising the dreadful position\nin which I was placed, I implored him to remember that not only\nmy honour but that of one who was far greater than I was at\nstake; and that he threatened to raise a scandal which would\nconvulse the nation. He might avert it all if he would but tell\nme what he had done with the three missing stones.\n\n\"'You may as well face the matter,' said I; 'you have been caught\nin the act, and no confession could make your guilt more heinous.\nIf you but make such reparation as is in your power, by telling\nus where the beryls are, all shall be forgiven and forgotten.'\n\n\"'Keep your forgiveness for those who ask for it,' he answered,\nturning away from me with a sneer. I saw that he was too hardened\nfor any words of mine to influence him. There was but one way for\nit. I called in the inspector and gave him into custody. A search\nwas made at once not only of his person but of his room and of\nevery portion of the house where he could possibly have concealed\nthe gems; but no trace of them could be found, nor would the\nwretched boy open his mouth for all our persuasions and our\nthreats. This morning he was removed to a cell, and I, after\ngoing through all the police formalities, have hurried round to\nyou to implore you to use your skill in unravelling the matter.\nThe police have openly confessed that they can at present make\nnothing of it. You may go to any expense which you think\nnecessary. I have already offered a reward of 1000 pounds. My\nGod, what shall I do! I have lost my honour, my gems, and my son\nin one night. Oh, what shall I do!\"\n\nHe put a hand on either side of his head and rocked himself to\nand fro, droning to himself like a child whose grief has got\nbeyond words.\n\nSherlock Holmes sat silent for some few minutes, with his brows\nknitted and his eyes fixed upon the fire.\n\n\"Do you receive much company?\" he asked.\n\n\"None save my partner with his family and an occasional friend of\nArthur's. Sir George Burnwell has been several times lately. No\none else, I think.\"\n\n\"Do you go out much in society?\"\n\n\"Arthur does. Mary and I stay at home. We neither of us care for\nit.\"\n\n\"That is unusual in a young girl.\"\n\n\"She is of a quiet nature. Besides, she is not so very young. She\nis four-and-twenty.\"\n\n\"This matter, from what you say, seems to have been a shock to\nher also.\"\n\n\"Terrible! She is even more affected than I.\"\n\n\"You have neither of you any doubt as to your son's guilt?\"\n\n\"How can we have when I saw him with my own eyes with the coronet\nin his hands.\"\n\n\"I hardly consider that a conclusive proof. Was the remainder of\nthe coronet at all injured?\"\n\n\"Yes, it was twisted.\"\n\n\"Do you not think, then, that he might have been trying to\nstraighten it?\"\n\n\"God bless you! You are doing what you can for him and for me.\nBut it is too heavy a task. What was he doing there at all? If\nhis purpose were innocent, why did he not say so?\"\n\n\"Precisely. And if it were guilty, why did he not invent a lie?\nHis silence appears to me to cut both ways. There are several\nsingular points about the case. What did the police think of the\nnoise which awoke you from your sleep?\"\n\n\"They considered that it might be caused by Arthur's closing his\nbedroom door.\"\n\n\"A likely story! As if a man bent on felony would slam his door\nso as to wake a household. What did they say, then, of the\ndisappearance of these gems?\"\n\n\"They are still sounding the planking and probing the furniture\nin the hope of finding them.\"\n\n\"Have they thought of looking outside the house?\"\n\n\"Yes, they have shown extraordinary energy. The whole garden has\nalready been minutely examined.\"\n\n\"Now, my dear sir,\" said Holmes, \"is it not obvious to you now\nthat this matter really strikes very much deeper than either you\nor the police were at first inclined to think? It appeared to you\nto be a simple case; to me it seems exceedingly complex. Consider\nwhat is involved by your theory. You suppose that your son came\ndown from his bed, went, at great risk, to your dressing-room,\nopened your bureau, took out your coronet, broke off by main\nforce a small portion of it, went off to some other place,\nconcealed three gems out of the thirty-nine, with such skill that\nnobody can find them, and then returned with the other thirty-six\ninto the room in which he exposed himself to the greatest danger\nof being discovered. I ask you now, is such a theory tenable?\"\n\n\"But what other is there?\" cried the banker with a gesture of\ndespair. \"If his motives were innocent, why does he not explain\nthem?\"\n\n\"It is our task to find that out,\" replied Holmes; \"so now, if\nyou please, Mr. Holder, we will set off for Streatham together,\nand devote an hour to glancing a little more closely into\ndetails.\"\n\nMy friend insisted upon my accompanying them in their expedition,\nwhich I was eager enough to do, for my curiosity and sympathy\nwere deeply stirred by the story to which we had listened. I\nconfess that the guilt of the banker's son appeared to me to be\nas obvious as it did to his unhappy father, but still I had such\nfaith in Holmes' judgment that I felt that there must be some\ngrounds for hope as long as he was dissatisfied with the accepted\nexplanation. He hardly spoke a word the whole way out to the\nsouthern suburb, but sat with his chin upon his breast and his\nhat drawn over his eyes, sunk in the deepest thought. Our client\nappeared to have taken fresh heart at the little glimpse of hope\nwhich had been presented to him, and he even broke into a\ndesultory chat with me over his business affairs. A short railway\njourney and a shorter walk brought us to Fairbank, the modest\nresidence of the great financier.\n\nFairbank was a good-sized square house of white stone, standing\nback a little from the road. A double carriage-sweep, with a\nsnow-clad lawn, stretched down in front to two large iron gates\nwhich closed the entrance. On the right side was a small wooden\nthicket, which led into a narrow path between two neat hedges\nstretching from the road to the kitchen door, and forming the\ntradesmen's entrance. On the left ran a lane which led to the\nstables, and was not itself within the grounds at all, being a\npublic, though little used, thoroughfare. Holmes left us standing\nat the door and walked slowly all round the house, across the\nfront, down the tradesmen's path, and so round by the garden\nbehind into the stable lane. So long was he that Mr. Holder and I\nwent into the dining-room and waited by the fire until he should\nreturn. We were sitting there in silence when the door opened and\na young lady came in. She was rather above the middle height,\nslim, with dark hair and eyes, which seemed the darker against\nthe absolute pallor of her skin. I do not think that I have ever\nseen such deadly paleness in a woman's face. Her lips, too, were\nbloodless, but her eyes were flushed with crying. As she swept\nsilently into the room she impressed me with a greater sense of\ngrief than the banker had done in the morning, and it was the\nmore striking in her as she was evidently a woman of strong\ncharacter, with immense capacity for self-restraint. Disregarding\nmy presence, she went straight to her uncle and passed her hand\nover his head with a sweet womanly caress.\n\n\"You have given orders that Arthur should be liberated, have you\nnot, dad?\" she asked.\n\n\"No, no, my girl, the matter must be probed to the bottom.\"\n\n\"But I am so sure that he is innocent. You know what woman's\ninstincts are. I know that he has done no harm and that you will\nbe sorry for having acted so harshly.\"\n\n\"Why is he silent, then, if he is innocent?\"\n\n\"Who knows? Perhaps because he was so angry that you should\nsuspect him.\"\n\n\"How could I help suspecting him, when I actually saw him with\nthe coronet in his hand?\"\n\n\"Oh, but he had only picked it up to look at it. Oh, do, do take\nmy word for it that he is innocent. Let the matter drop and say\nno more. It is so dreadful to think of our dear Arthur in\nprison!\"\n\n\"I shall never let it drop until the gems are found--never, Mary!\nYour affection for Arthur blinds you as to the awful consequences\nto me. Far from hushing the thing up, I have brought a gentleman\ndown from London to inquire more deeply into it.\"\n\n\"This gentleman?\" she asked, facing round to me.\n\n\"No, his friend. He wished us to leave him alone. He is round in\nthe stable lane now.\"\n\n\"The stable lane?\" She raised her dark eyebrows. \"What can he\nhope to find there? Ah! this, I suppose, is he. I trust, sir,\nthat you will succeed in proving, what I feel sure is the truth,\nthat my cousin Arthur is innocent of this crime.\"\n\n\"I fully share your opinion, and I trust, with you, that we may\nprove it,\" returned Holmes, going back to the mat to knock the\nsnow from his shoes. \"I believe I have the honour of addressing\nMiss Mary Holder. Might I ask you a question or two?\"\n\n\"Pray do, sir, if it may help to clear this horrible affair up.\"\n\n\"You heard nothing yourself last night?\"\n\n\"Nothing, until my uncle here began to speak loudly. I heard\nthat, and I came down.\"\n\n\"You shut up the windows and doors the night before. Did you\nfasten all the windows?\"\n\n\"Yes.\"\n\n\"Were they all fastened this morning?\"\n\n\"Yes.\"\n\n\"You have a maid who has a sweetheart? I think that you remarked\nto your uncle last night that she had been out to see him?\"\n\n\"Yes, and she was the girl who waited in the drawing-room, and\nwho may have heard uncle's remarks about the coronet.\"\n\n\"I see. You infer that she may have gone out to tell her\nsweetheart, and that the two may have planned the robbery.\"\n\n\"But what is the good of all these vague theories,\" cried the\nbanker impatiently, \"when I have told you that I saw Arthur with\nthe coronet in his hands?\"\n\n\"Wait a little, Mr. Holder. We must come back to that. About this\ngirl, Miss Holder. You saw her return by the kitchen door, I\npresume?\"\n\n\"Yes; when I went to see if the door was fastened for the night I\nmet her slipping in. I saw the man, too, in the gloom.\"\n\n\"Do you know him?\"\n\n\"Oh, yes! he is the green-grocer who brings our vegetables round.\nHis name is Francis Prosper.\"\n\n\"He stood,\" said Holmes, \"to the left of the door--that is to\nsay, farther up the path than is necessary to reach the door?\"\n\n\"Yes, he did.\"\n\n\"And he is a man with a wooden leg?\"\n\nSomething like fear sprang up in the young lady's expressive\nblack eyes. \"Why, you are like a magician,\" said she. \"How do you\nknow that?\" She smiled, but there was no answering smile in\nHolmes' thin, eager face.\n\n\"I should be very glad now to go upstairs,\" said he. \"I shall\nprobably wish to go over the outside of the house again. Perhaps\nI had better take a look at the lower windows before I go up.\"\n\nHe walked swiftly round from one to the other, pausing only at\nthe large one which looked from the hall onto the stable lane.\nThis he opened and made a very careful examination of the sill\nwith his powerful magnifying lens. \"Now we shall go upstairs,\"\nsaid he at last.\n\nThe banker's dressing-room was a plainly furnished little\nchamber, with a grey carpet, a large bureau, and a long mirror.\nHolmes went to the bureau first and looked hard at the lock.\n\n\"Which key was used to open it?\" he asked.\n\n\"That which my son himself indicated--that of the cupboard of the\nlumber-room.\"\n\n\"Have you it here?\"\n\n\"That is it on the dressing-table.\"\n\nSherlock Holmes took it up and opened the bureau.\n\n\"It is a noiseless lock,\" said he. \"It is no wonder that it did\nnot wake you. This case, I presume, contains the coronet. We must\nhave a look at it.\" He opened the case, and taking out the diadem\nhe laid it upon the table. It was a magnificent specimen of the\njeweller's art, and the thirty-six stones were the finest that I\nhave ever seen. At one side of the coronet was a cracked edge,\nwhere a corner holding three gems had been torn away.\n\n\"Now, Mr. Holder,\" said Holmes, \"here is the corner which\ncorresponds to that which has been so unfortunately lost. Might I\nbeg that you will break it off.\"\n\nThe banker recoiled in horror. \"I should not dream of trying,\"\nsaid he.\n\n\"Then I will.\" Holmes suddenly bent his strength upon it, but\nwithout result. \"I feel it give a little,\" said he; \"but, though\nI am exceptionally strong in the fingers, it would take me all my\ntime to break it. An ordinary man could not do it. Now, what do\nyou think would happen if I did break it, Mr. Holder? There would\nbe a noise like a pistol shot. Do you tell me that all this\nhappened within a few yards of your bed and that you heard\nnothing of it?\"\n\n\"I do not know what to think. It is all dark to me.\"\n\n\"But perhaps it may grow lighter as we go. What do you think,\nMiss Holder?\"\n\n\"I confess that I still share my uncle's perplexity.\"\n\n\"Your son had no shoes or slippers on when you saw him?\"\n\n\"He had nothing on save only his trousers and shirt.\"\n\n\"Thank you. We have certainly been favoured with extraordinary\nluck during this inquiry, and it will be entirely our own fault\nif we do not succeed in clearing the matter up. With your\npermission, Mr. Holder, I shall now continue my investigations\noutside.\"\n\nHe went alone, at his own request, for he explained that any\nunnecessary footmarks might make his task more difficult. For an\nhour or more he was at work, returning at last with his feet\nheavy with snow and his features as inscrutable as ever.\n\n\"I think that I have seen now all that there is to see, Mr.\nHolder,\" said he; \"I can serve you best by returning to my\nrooms.\"\n\n\"But the gems, Mr. Holmes. Where are they?\"\n\n\"I cannot tell.\"\n\nThe banker wrung his hands. \"I shall never see them again!\" he\ncried. \"And my son? You give me hopes?\"\n\n\"My opinion is in no way altered.\"\n\n\"Then, for God's sake, what was this dark business which was\nacted in my house last night?\"\n\n\"If you can call upon me at my Baker Street rooms to-morrow\nmorning between nine and ten I shall be happy to do what I can to\nmake it clearer. I understand that you give me carte blanche to\nact for you, provided only that I get back the gems, and that you\nplace no limit on the sum I may draw.\"\n\n\"I would give my fortune to have them back.\"\n\n\"Very good. I shall look into the matter between this and then.\nGood-bye; it is just possible that I may have to come over here\nagain before evening.\"\n\nIt was obvious to me that my companion's mind was now made up\nabout the case, although what his conclusions were was more than\nI could even dimly imagine. Several times during our homeward\njourney I endeavoured to sound him upon the point, but he always\nglided away to some other topic, until at last I gave it over in\ndespair. It was not yet three when we found ourselves in our\nrooms once more. He hurried to his chamber and was down again in\na few minutes dressed as a common loafer. With his collar turned\nup, his shiny, seedy coat, his red cravat, and his worn boots, he\nwas a perfect sample of the class.\n\n\"I think that this should do,\" said he, glancing into the glass\nabove the fireplace. \"I only wish that you could come with me,\nWatson, but I fear that it won't do. I may be on the trail in\nthis matter, or I may be following a will-o'-the-wisp, but I\nshall soon know which it is. I hope that I may be back in a few\nhours.\" He cut a slice of beef from the joint upon the sideboard,\nsandwiched it between two rounds of bread, and thrusting this\nrude meal into his pocket he started off upon his expedition.\n\nI had just finished my tea when he returned, evidently in\nexcellent spirits, swinging an old elastic-sided boot in his\nhand. He chucked it down into a corner and helped himself to a\ncup of tea.\n\n\"I only looked in as I passed,\" said he. \"I am going right on.\"\n\n\"Where to?\"\n\n\"Oh, to the other side of the West End. It may be some time\nbefore I get back. Don't wait up for me in case I should be\nlate.\"\n\n\"How are you getting on?\"\n\n\"Oh, so so. Nothing to complain of. I have been out to Streatham\nsince I saw you last, but I did not call at the house. It is a\nvery sweet little problem, and I would not have missed it for a\ngood deal. However, I must not sit gossiping here, but must get\nthese disreputable clothes off and return to my highly\nrespectable self.\"\n\nI could see by his manner that he had stronger reasons for\nsatisfaction than his words alone would imply. His eyes twinkled,\nand there was even a touch of colour upon his sallow cheeks. He\nhastened upstairs, and a few minutes later I heard the slam of\nthe hall door, which told me that he was off once more upon his\ncongenial hunt.\n\nI waited until midnight, but there was no sign of his return, so\nI retired to my room. It was no uncommon thing for him to be away\nfor days and nights on end when he was hot upon a scent, so that\nhis lateness caused me no surprise. I do not know at what hour he\ncame in, but when I came down to breakfast in the morning there\nhe was with a cup of coffee in one hand and the paper in the\nother, as fresh and trim as possible.\n\n\"You will excuse my beginning without you, Watson,\" said he, \"but\nyou remember that our client has rather an early appointment this\nmorning.\"\n\n\"Why, it is after nine now,\" I answered. \"I should not be\nsurprised if that were he. I thought I heard a ring.\"\n\nIt was, indeed, our friend the financier. I was shocked by the\nchange which had come over him, for his face which was naturally\nof a broad and massive mould, was now pinched and fallen in,\nwhile his hair seemed to me at least a shade whiter. He entered\nwith a weariness and lethargy which was even more painful than\nhis violence of the morning before, and he dropped heavily into\nthe armchair which I pushed forward for him.\n\n\"I do not know what I have done to be so severely tried,\" said\nhe. \"Only two days ago I was a happy and prosperous man, without\na care in the world. Now I am left to a lonely and dishonoured\nage. One sorrow comes close upon the heels of another. My niece,\nMary, has deserted me.\"\n\n\"Deserted you?\"\n\n\"Yes. Her bed this morning had not been slept in, her room was\nempty, and a note for me lay upon the hall table. I had said to\nher last night, in sorrow and not in anger, that if she had\nmarried my boy all might have been well with him. Perhaps it was\nthoughtless of me to say so. It is to that remark that she refers\nin this note:\n\n\"'MY DEAREST UNCLE:--I feel that I have brought trouble upon you,\nand that if I had acted differently this terrible misfortune\nmight never have occurred. I cannot, with this thought in my\nmind, ever again be happy under your roof, and I feel that I must\nleave you forever. Do not worry about my future, for that is\nprovided for; and, above all, do not search for me, for it will\nbe fruitless labour and an ill-service to me. In life or in\ndeath, I am ever your loving,--MARY.'\n\n\"What could she mean by that note, Mr. Holmes? Do you think it\npoints to suicide?\"\n\n\"No, no, nothing of the kind. It is perhaps the best possible\nsolution. I trust, Mr. Holder, that you are nearing the end of\nyour troubles.\"\n\n\"Ha! You say so! You have heard something, Mr. Holmes; you have\nlearned something! Where are the gems?\"\n\n\"You would not think 1000 pounds apiece an excessive sum for\nthem?\"\n\n\"I would pay ten.\"\n\n\"That would be unnecessary. Three thousand will cover the matter.\nAnd there is a little reward, I fancy. Have you your check-book?\nHere is a pen. Better make it out for 4000 pounds.\"\n\nWith a dazed face the banker made out the required check. Holmes\nwalked over to his desk, took out a little triangular piece of\ngold with three gems in it, and threw it down upon the table.\n\nWith a shriek of joy our client clutched it up.\n\n\"You have it!\" he gasped. \"I am saved! I am saved!\"\n\nThe reaction of joy was as passionate as his grief had been, and\nhe hugged his recovered gems to his bosom.\n\n\"There is one other thing you owe, Mr. Holder,\" said Sherlock\nHolmes rather sternly.\n\n\"Owe!\" He caught up a pen. \"Name the sum, and I will pay it.\"\n\n\"No, the debt is not to me. You owe a very humble apology to that\nnoble lad, your son, who has carried himself in this matter as I\nshould be proud to see my own son do, should I ever chance to\nhave one.\"\n\n\"Then it was not Arthur who took them?\"\n\n\"I told you yesterday, and I repeat to-day, that it was not.\"\n\n\"You are sure of it! Then let us hurry to him at once to let him\nknow that the truth is known.\"\n\n\"He knows it already. When I had cleared it all up I had an\ninterview with him, and finding that he would not tell me the\nstory, I told it to him, on which he had to confess that I was\nright and to add the very few details which were not yet quite\nclear to me. Your news of this morning, however, may open his\nlips.\"\n\n\"For heaven's sake, tell me, then, what is this extraordinary\nmystery!\"\n\n\"I will do so, and I will show you the steps by which I reached\nit. And let me say to you, first, that which it is hardest for me\nto say and for you to hear: there has been an understanding\nbetween Sir George Burnwell and your niece Mary. They have now\nfled together.\"\n\n\"My Mary? Impossible!\"\n\n\"It is unfortunately more than possible; it is certain. Neither\nyou nor your son knew the true character of this man when you\nadmitted him into your family circle. He is one of the most\ndangerous men in England--a ruined gambler, an absolutely\ndesperate villain, a man without heart or conscience. Your niece\nknew nothing of such men. When he breathed his vows to her, as he\nhad done to a hundred before her, she flattered herself that she\nalone had touched his heart. The devil knows best what he said,\nbut at least she became his tool and was in the habit of seeing\nhim nearly every evening.\"\n\n\"I cannot, and I will not, believe it!\" cried the banker with an\nashen face.\n\n\"I will tell you, then, what occurred in your house last night.\nYour niece, when you had, as she thought, gone to your room,\nslipped down and talked to her lover through the window which\nleads into the stable lane. His footmarks had pressed right\nthrough the snow, so long had he stood there. She told him of the\ncoronet. His wicked lust for gold kindled at the news, and he\nbent her to his will. I have no doubt that she loved you, but\nthere are women in whom the love of a lover extinguishes all\nother loves, and I think that she must have been one. She had\nhardly listened to his instructions when she saw you coming\ndownstairs, on which she closed the window rapidly and told you\nabout one of the servants' escapade with her wooden-legged lover,\nwhich was all perfectly true.\n\n\"Your boy, Arthur, went to bed after his interview with you but\nhe slept badly on account of his uneasiness about his club debts.\nIn the middle of the night he heard a soft tread pass his door,\nso he rose and, looking out, was surprised to see his cousin\nwalking very stealthily along the passage until she disappeared\ninto your dressing-room. Petrified with astonishment, the lad\nslipped on some clothes and waited there in the dark to see what\nwould come of this strange affair. Presently she emerged from the\nroom again, and in the light of the passage-lamp your son saw\nthat she carried the precious coronet in her hands. She passed\ndown the stairs, and he, thrilling with horror, ran along and\nslipped behind the curtain near your door, whence he could see\nwhat passed in the hall beneath. He saw her stealthily open the\nwindow, hand out the coronet to someone in the gloom, and then\nclosing it once more hurry back to her room, passing quite close\nto where he stood hid behind the curtain.\n\n\"As long as she was on the scene he could not take any action\nwithout a horrible exposure of the woman whom he loved. But the\ninstant that she was gone he realised how crushing a misfortune\nthis would be for you, and how all-important it was to set it\nright. He rushed down, just as he was, in his bare feet, opened\nthe window, sprang out into the snow, and ran down the lane,\nwhere he could see a dark figure in the moonlight. Sir George\nBurnwell tried to get away, but Arthur caught him, and there was\na struggle between them, your lad tugging at one side of the\ncoronet, and his opponent at the other. In the scuffle, your son\nstruck Sir George and cut him over the eye. Then something\nsuddenly snapped, and your son, finding that he had the coronet\nin his hands, rushed back, closed the window, ascended to your\nroom, and had just observed that the coronet had been twisted in\nthe struggle and was endeavouring to straighten it when you\nappeared upon the scene.\"\n\n\"Is it possible?\" gasped the banker.\n\n\"You then roused his anger by calling him names at a moment when\nhe felt that he had deserved your warmest thanks. He could not\nexplain the true state of affairs without betraying one who\ncertainly deserved little enough consideration at his hands. He\ntook the more chivalrous view, however, and preserved her\nsecret.\"\n\n\"And that was why she shrieked and fainted when she saw the\ncoronet,\" cried Mr. Holder. \"Oh, my God! what a blind fool I have\nbeen! And his asking to be allowed to go out for five minutes!\nThe dear fellow wanted to see if the missing piece were at the\nscene of the struggle. How cruelly I have misjudged him!\"\n\n\"When I arrived at the house,\" continued Holmes, \"I at once went\nvery carefully round it to observe if there were any traces in\nthe snow which might help me. I knew that none had fallen since\nthe evening before, and also that there had been a strong frost\nto preserve impressions. I passed along the tradesmen's path, but\nfound it all trampled down and indistinguishable. Just beyond it,\nhowever, at the far side of the kitchen door, a woman had stood\nand talked with a man, whose round impressions on one side showed\nthat he had a wooden leg. I could even tell that they had been\ndisturbed, for the woman had run back swiftly to the door, as was\nshown by the deep toe and light heel marks, while Wooden-leg had\nwaited a little, and then had gone away. I thought at the time\nthat this might be the maid and her sweetheart, of whom you had\nalready spoken to me, and inquiry showed it was so. I passed\nround the garden without seeing anything more than random tracks,\nwhich I took to be the police; but when I got into the stable\nlane a very long and complex story was written in the snow in\nfront of me.\n\n\"There was a double line of tracks of a booted man, and a second\ndouble line which I saw with delight belonged to a man with naked\nfeet. I was at once convinced from what you had told me that the\nlatter was your son. The first had walked both ways, but the\nother had run swiftly, and as his tread was marked in places over\nthe depression of the boot, it was obvious that he had passed\nafter the other. I followed them up and found they led to the\nhall window, where Boots had worn all the snow away while\nwaiting. Then I walked to the other end, which was a hundred\nyards or more down the lane. I saw where Boots had faced round,\nwhere the snow was cut up as though there had been a struggle,\nand, finally, where a few drops of blood had fallen, to show me\nthat I was not mistaken. Boots had then run down the lane, and\nanother little smudge of blood showed that it was he who had been\nhurt. When he came to the highroad at the other end, I found that\nthe pavement had been cleared, so there was an end to that clue.\n\n\"On entering the house, however, I examined, as you remember, the\nsill and framework of the hall window with my lens, and I could\nat once see that someone had passed out. I could distinguish the\noutline of an instep where the wet foot had been placed in coming\nin. I was then beginning to be able to form an opinion as to what\nhad occurred. A man had waited outside the window; someone had\nbrought the gems; the deed had been overseen by your son; he had\npursued the thief; had struggled with him; they had each tugged\nat the coronet, their united strength causing injuries which\nneither alone could have effected. He had returned with the\nprize, but had left a fragment in the grasp of his opponent. So\nfar I was clear. The question now was, who was the man and who\nwas it brought him the coronet?\n\n\"It is an old maxim of mine that when you have excluded the\nimpossible, whatever remains, however improbable, must be the\ntruth. Now, I knew that it was not you who had brought it down,\nso there only remained your niece and the maids. But if it were\nthe maids, why should your son allow himself to be accused in\ntheir place? There could be no possible reason. As he loved his\ncousin, however, there was an excellent explanation why he should\nretain her secret--the more so as the secret was a disgraceful\none. When I remembered that you had seen her at that window, and\nhow she had fainted on seeing the coronet again, my conjecture\nbecame a certainty.\n\n\"And who could it be who was her confederate? A lover evidently,\nfor who else could outweigh the love and gratitude which she must\nfeel to you? I knew that you went out little, and that your\ncircle of friends was a very limited one. But among them was Sir\nGeorge Burnwell. I had heard of him before as being a man of evil\nreputation among women. It must have been he who wore those boots\nand retained the missing gems. Even though he knew that Arthur\nhad discovered him, he might still flatter himself that he was\nsafe, for the lad could not say a word without compromising his\nown family.\n\n\"Well, your own good sense will suggest what measures I took\nnext. I went in the shape of a loafer to Sir George's house,\nmanaged to pick up an acquaintance with his valet, learned that\nhis master had cut his head the night before, and, finally, at\nthe expense of six shillings, made all sure by buying a pair of\nhis cast-off shoes. With these I journeyed down to Streatham and\nsaw that they exactly fitted the tracks.\"\n\n\"I saw an ill-dressed vagabond in the lane yesterday evening,\"\nsaid Mr. Holder.\n\n\"Precisely. It was I. I found that I had my man, so I came home\nand changed my clothes. It was a delicate part which I had to\nplay then, for I saw that a prosecution must be avoided to avert\nscandal, and I knew that so astute a villain would see that our\nhands were tied in the matter. I went and saw him. At first, of\ncourse, he denied everything. But when I gave him every\nparticular that had occurred, he tried to bluster and took down a\nlife-preserver from the wall. I knew my man, however, and I\nclapped a pistol to his head before he could strike. Then he\nbecame a little more reasonable. I told him that we would give\nhim a price for the stones he held--1000 pounds apiece. That\nbrought out the first signs of grief that he had shown. 'Why,\ndash it all!' said he, 'I've let them go at six hundred for the\nthree!' I soon managed to get the address of the receiver who had\nthem, on promising him that there would be no prosecution. Off I\nset to him, and after much chaffering I got our stones at 1000\npounds apiece. Then I looked in upon your son, told him that all\nwas right, and eventually got to my bed about two o'clock, after\nwhat I may call a really hard day's work.\"\n\n\"A day which has saved England from a great public scandal,\" said\nthe banker, rising. \"Sir, I cannot find words to thank you, but\nyou shall not find me ungrateful for what you have done. Your\nskill has indeed exceeded all that I have heard of it. And now I\nmust fly to my dear boy to apologise to him for the wrong which I\nhave done him. As to what you tell me of poor Mary, it goes to my\nvery heart. Not even your skill can inform me where she is now.\"\n\n\"I think that we may safely say,\" returned Holmes, \"that she is\nwherever Sir George Burnwell is. It is equally certain, too, that\nwhatever her sins are, they will soon receive a more than\nsufficient punishment.\"\n\n\n\nXII. THE ADVENTURE OF THE COPPER BEECHES\n\n\"To the man who loves art for its own sake,\" remarked Sherlock\nHolmes, tossing aside the advertisement sheet of the Daily\nTelegraph, \"it is frequently in its least important and lowliest\nmanifestations that the keenest pleasure is to be derived. It is\npleasant to me to observe, Watson, that you have so far grasped\nthis truth that in these little records of our cases which you\nhave been good enough to draw up, and, I am bound to say,\noccasionally to embellish, you have given prominence not so much\nto the many causes célèbres and sensational trials in which I\nhave figured but rather to those incidents which may have been\ntrivial in themselves, but which have given room for those\nfaculties of deduction and of logical synthesis which I have made\nmy special province.\"\n\n\"And yet,\" said I, smiling, \"I cannot quite hold myself absolved\nfrom the charge of sensationalism which has been urged against my\nrecords.\"\n\n\"You have erred, perhaps,\" he observed, taking up a glowing\ncinder with the tongs and lighting with it the long cherry-wood\npipe which was wont to replace his clay when he was in a\ndisputatious rather than a meditative mood--\"you have erred\nperhaps in attempting to put colour and life into each of your\nstatements instead of confining yourself to the task of placing\nupon record that severe reasoning from cause to effect which is\nreally the only notable feature about the thing.\"\n\n\"It seems to me that I have done you full justice in the matter,\"\nI remarked with some coldness, for I was repelled by the egotism\nwhich I had more than once observed to be a strong factor in my\nfriend's singular character.\n\n\"No, it is not selfishness or conceit,\" said he, answering, as\nwas his wont, my thoughts rather than my words. \"If I claim full\njustice for my art, it is because it is an impersonal thing--a\nthing beyond myself. Crime is common. Logic is rare. Therefore it\nis upon the logic rather than upon the crime that you should\ndwell. You have degraded what should have been a course of\nlectures into a series of tales.\"\n\nIt was a cold morning of the early spring, and we sat after\nbreakfast on either side of a cheery fire in the old room at\nBaker Street. A thick fog rolled down between the lines of\ndun-coloured houses, and the opposing windows loomed like dark,\nshapeless blurs through the heavy yellow wreaths. Our gas was lit\nand shone on the white cloth and glimmer of china and metal, for\nthe table had not been cleared yet. Sherlock Holmes had been\nsilent all the morning, dipping continuously into the\nadvertisement columns of a succession of papers until at last,\nhaving apparently given up his search, he had emerged in no very\nsweet temper to lecture me upon my literary shortcomings.\n\n\"At the same time,\" he remarked after a pause, during which he\nhad sat puffing at his long pipe and gazing down into the fire,\n\"you can hardly be open to a charge of sensationalism, for out of\nthese cases which you have been so kind as to interest yourself\nin, a fair proportion do not treat of crime, in its legal sense,\nat all. The small matter in which I endeavoured to help the King\nof Bohemia, the singular experience of Miss Mary Sutherland, the\nproblem connected with the man with the twisted lip, and the\nincident of the noble bachelor, were all matters which are\noutside the pale of the law. But in avoiding the sensational, I\nfear that you may have bordered on the trivial.\"\n\n\"The end may have been so,\" I answered, \"but the methods I hold\nto have been novel and of interest.\"\n\n\"Pshaw, my dear fellow, what do the public, the great unobservant\npublic, who could hardly tell a weaver by his tooth or a\ncompositor by his left thumb, care about the finer shades of\nanalysis and deduction! But, indeed, if you are trivial, I cannot\nblame you, for the days of the great cases are past. Man, or at\nleast criminal man, has lost all enterprise and originality. As\nto my own little practice, it seems to be degenerating into an\nagency for recovering lost lead pencils and giving advice to\nyoung ladies from boarding-schools. I think that I have touched\nbottom at last, however. This note I had this morning marks my\nzero-point, I fancy. Read it!\" He tossed a crumpled letter across\nto me.\n\nIt was dated from Montague Place upon the preceding evening, and\nran thus:\n\n\"DEAR MR. HOLMES:--I am very anxious to consult you as to whether\nI should or should not accept a situation which has been offered\nto me as governess. I shall call at half-past ten to-morrow if I\ndo not inconvenience you. Yours faithfully,\n                                               \"VIOLET HUNTER.\"\n\n\"Do you know the young lady?\" I asked.\n\n\"Not I.\"\n\n\"It is half-past ten now.\"\n\n\"Yes, and I have no doubt that is her ring.\"\n\n\"It may turn out to be of more interest than you think. You\nremember that the affair of the blue carbuncle, which appeared to\nbe a mere whim at first, developed into a serious investigation.\nIt may be so in this case, also.\"\n\n\"Well, let us hope so. But our doubts will very soon be solved,\nfor here, unless I am much mistaken, is the person in question.\"\n\nAs he spoke the door opened and a young lady entered the room.\nShe was plainly but neatly dressed, with a bright, quick face,\nfreckled like a plover's egg, and with the brisk manner of a\nwoman who has had her own way to make in the world.\n\n\"You will excuse my troubling you, I am sure,\" said she, as my\ncompanion rose to greet her, \"but I have had a very strange\nexperience, and as I have no parents or relations of any sort\nfrom whom I could ask advice, I thought that perhaps you would be\nkind enough to tell me what I should do.\"\n\n\"Pray take a seat, Miss Hunter. I shall be happy to do anything\nthat I can to serve you.\"\n\nI could see that Holmes was favourably impressed by the manner\nand speech of his new client. He looked her over in his searching\nfashion, and then composed himself, with his lids drooping and\nhis finger-tips together, to listen to her story.\n\n\"I have been a governess for five years,\" said she, \"in the\nfamily of Colonel Spence Munro, but two months ago the colonel\nreceived an appointment at Halifax, in Nova Scotia, and took his\nchildren over to America with him, so that I found myself without\na situation. I advertised, and I answered advertisements, but\nwithout success. At last the little money which I had saved began\nto run short, and I was at my wit's end as to what I should do.\n\n\"There is a well-known agency for governesses in the West End\ncalled Westaway's, and there I used to call about once a week in\norder to see whether anything had turned up which might suit me.\nWestaway was the name of the founder of the business, but it is\nreally managed by Miss Stoper. She sits in her own little office,\nand the ladies who are seeking employment wait in an anteroom,\nand are then shown in one by one, when she consults her ledgers\nand sees whether she has anything which would suit them.\n\n\"Well, when I called last week I was shown into the little office\nas usual, but I found that Miss Stoper was not alone. A\nprodigiously stout man with a very smiling face and a great heavy\nchin which rolled down in fold upon fold over his throat sat at\nher elbow with a pair of glasses on his nose, looking very\nearnestly at the ladies who entered. As I came in he gave quite a\njump in his chair and turned quickly to Miss Stoper.\n\n\"'That will do,' said he; 'I could not ask for anything better.\nCapital! capital!' He seemed quite enthusiastic and rubbed his\nhands together in the most genial fashion. He was such a\ncomfortable-looking man that it was quite a pleasure to look at\nhim.\n\n\"'You are looking for a situation, miss?' he asked.\n\n\"'Yes, sir.'\n\n\"'As governess?'\n\n\"'Yes, sir.'\n\n\"'And what salary do you ask?'\n\n\"'I had 4 pounds a month in my last place with Colonel Spence\nMunro.'\n\n\"'Oh, tut, tut! sweating--rank sweating!' he cried, throwing his\nfat hands out into the air like a man who is in a boiling\npassion. 'How could anyone offer so pitiful a sum to a lady with\nsuch attractions and accomplishments?'\n\n\"'My accomplishments, sir, may be less than you imagine,' said I.\n'A little French, a little German, music, and drawing--'\n\n\"'Tut, tut!' he cried. 'This is all quite beside the question.\nThe point is, have you or have you not the bearing and deportment\nof a lady? There it is in a nutshell. If you have not, you are\nnot fitted for the rearing of a child who may some day play a\nconsiderable part in the history of the country. But if you have\nwhy, then, how could any gentleman ask you to condescend to\naccept anything under the three figures? Your salary with me,\nmadam, would commence at 100 pounds a year.'\n\n\"You may imagine, Mr. Holmes, that to me, destitute as I was,\nsuch an offer seemed almost too good to be true. The gentleman,\nhowever, seeing perhaps the look of incredulity upon my face,\nopened a pocket-book and took out a note.\n\n\"'It is also my custom,' said he, smiling in the most pleasant\nfashion until his eyes were just two little shining slits amid\nthe white creases of his face, 'to advance to my young ladies\nhalf their salary beforehand, so that they may meet any little\nexpenses of their journey and their wardrobe.'\n\n\"It seemed to me that I had never met so fascinating and so\nthoughtful a man. As I was already in debt to my tradesmen, the\nadvance was a great convenience, and yet there was something\nunnatural about the whole transaction which made me wish to know\na little more before I quite committed myself.\n\n\"'May I ask where you live, sir?' said I.\n\n\"'Hampshire. Charming rural place. The Copper Beeches, five miles\non the far side of Winchester. It is the most lovely country, my\ndear young lady, and the dearest old country-house.'\n\n\"'And my duties, sir? I should be glad to know what they would\nbe.'\n\n\"'One child--one dear little romper just six years old. Oh, if\nyou could see him killing cockroaches with a slipper! Smack!\nsmack! smack! Three gone before you could wink!' He leaned back\nin his chair and laughed his eyes into his head again.\n\n\"I was a little startled at the nature of the child's amusement,\nbut the father's laughter made me think that perhaps he was\njoking.\n\n\"'My sole duties, then,' I asked, 'are to take charge of a single\nchild?'\n\n\"'No, no, not the sole, not the sole, my dear young lady,' he\ncried. 'Your duty would be, as I am sure your good sense would\nsuggest, to obey any little commands my wife might give, provided\nalways that they were such commands as a lady might with\npropriety obey. You see no difficulty, heh?'\n\n\"'I should be happy to make myself useful.'\n\n\"'Quite so. In dress now, for example. We are faddy people, you\nknow--faddy but kind-hearted. If you were asked to wear any dress\nwhich we might give you, you would not object to our little whim.\nHeh?'\n\n\"'No,' said I, considerably astonished at his words.\n\n\"'Or to sit here, or sit there, that would not be offensive to\nyou?'\n\n\"'Oh, no.'\n\n\"'Or to cut your hair quite short before you come to us?'\n\n\"I could hardly believe my ears. As you may observe, Mr. Holmes,\nmy hair is somewhat luxuriant, and of a rather peculiar tint of\nchestnut. It has been considered artistic. I could not dream of\nsacrificing it in this offhand fashion.\n\n\"'I am afraid that that is quite impossible,' said I. He had been\nwatching me eagerly out of his small eyes, and I could see a\nshadow pass over his face as I spoke.\n\n\"'I am afraid that it is quite essential,' said he. 'It is a\nlittle fancy of my wife's, and ladies' fancies, you know, madam,\nladies' fancies must be consulted. And so you won't cut your\nhair?'\n\n\"'No, sir, I really could not,' I answered firmly.\n\n\"'Ah, very well; then that quite settles the matter. It is a\npity, because in other respects you would really have done very\nnicely. In that case, Miss Stoper, I had best inspect a few more\nof your young ladies.'\n\n\"The manageress had sat all this while busy with her papers\nwithout a word to either of us, but she glanced at me now with so\nmuch annoyance upon her face that I could not help suspecting\nthat she had lost a handsome commission through my refusal.\n\n\"'Do you desire your name to be kept upon the books?' she asked.\n\n\"'If you please, Miss Stoper.'\n\n\"'Well, really, it seems rather useless, since you refuse the\nmost excellent offers in this fashion,' said she sharply. 'You\ncan hardly expect us to exert ourselves to find another such\nopening for you. Good-day to you, Miss Hunter.' She struck a gong\nupon the table, and I was shown out by the page.\n\n\"Well, Mr. Holmes, when I got back to my lodgings and found\nlittle enough in the cupboard, and two or three bills upon the\ntable, I began to ask myself whether I had not done a very\nfoolish thing. After all, if these people had strange fads and\nexpected obedience on the most extraordinary matters, they were\nat least ready to pay for their eccentricity. Very few\ngovernesses in England are getting 100 pounds a year. Besides,\nwhat use was my hair to me? Many people are improved by wearing\nit short and perhaps I should be among the number. Next day I was\ninclined to think that I had made a mistake, and by the day after\nI was sure of it. I had almost overcome my pride so far as to go\nback to the agency and inquire whether the place was still open\nwhen I received this letter from the gentleman himself. I have it\nhere and I will read it to you:\n\n                       \"'The Copper Beeches, near Winchester.\n\"'DEAR MISS HUNTER:--Miss Stoper has very kindly given me your\naddress, and I write from here to ask you whether you have\nreconsidered your decision. My wife is very anxious that you\nshould come, for she has been much attracted by my description of\nyou. We are willing to give 30 pounds a quarter, or 120 pounds a\nyear, so as to recompense you for any little inconvenience which\nour fads may cause you. They are not very exacting, after all. My\nwife is fond of a particular shade of electric blue and would\nlike you to wear such a dress indoors in the morning. You need\nnot, however, go to the expense of purchasing one, as we have one\nbelonging to my dear daughter Alice (now in Philadelphia), which\nwould, I should think, fit you very well. Then, as to sitting\nhere or there, or amusing yourself in any manner indicated, that\nneed cause you no inconvenience. As regards your hair, it is no\ndoubt a pity, especially as I could not help remarking its beauty\nduring our short interview, but I am afraid that I must remain\nfirm upon this point, and I only hope that the increased salary\nmay recompense you for the loss. Your duties, as far as the child\nis concerned, are very light. Now do try to come, and I shall\nmeet you with the dog-cart at Winchester. Let me know your train.\nYours faithfully, JEPHRO RUCASTLE.'\n\n\"That is the letter which I have just received, Mr. Holmes, and\nmy mind is made up that I will accept it. I thought, however,\nthat before taking the final step I should like to submit the\nwhole matter to your consideration.\"\n\n\"Well, Miss Hunter, if your mind is made up, that settles the\nquestion,\" said Holmes, smiling.\n\n\"But you would not advise me to refuse?\"\n\n\"I confess that it is not the situation which I should like to\nsee a sister of mine apply for.\"\n\n\"What is the meaning of it all, Mr. Holmes?\"\n\n\"Ah, I have no data. I cannot tell. Perhaps you have yourself\nformed some opinion?\"\n\n\"Well, there seems to me to be only one possible solution. Mr.\nRucastle seemed to be a very kind, good-natured man. Is it not\npossible that his wife is a lunatic, that he desires to keep the\nmatter quiet for fear she should be taken to an asylum, and that\nhe humours her fancies in every way in order to prevent an\noutbreak?\"\n\n\"That is a possible solution--in fact, as matters stand, it is\nthe most probable one. But in any case it does not seem to be a\nnice household for a young lady.\"\n\n\"But the money, Mr. Holmes, the money!\"\n\n\"Well, yes, of course the pay is good--too good. That is what\nmakes me uneasy. Why should they give you 120 pounds a year, when\nthey could have their pick for 40 pounds? There must be some\nstrong reason behind.\"\n\n\"I thought that if I told you the circumstances you would\nunderstand afterwards if I wanted your help. I should feel so\nmuch stronger if I felt that you were at the back of me.\"\n\n\"Oh, you may carry that feeling away with you. I assure you that\nyour little problem promises to be the most interesting which has\ncome my way for some months. There is something distinctly novel\nabout some of the features. If you should find yourself in doubt\nor in danger--\"\n\n\"Danger! What danger do you foresee?\"\n\nHolmes shook his head gravely. \"It would cease to be a danger if\nwe could define it,\" said he. \"But at any time, day or night, a\ntelegram would bring me down to your help.\"\n\n\"That is enough.\" She rose briskly from her chair with the\nanxiety all swept from her face. \"I shall go down to Hampshire\nquite easy in my mind now. I shall write to Mr. Rucastle at once,\nsacrifice my poor hair to-night, and start for Winchester\nto-morrow.\" With a few grateful words to Holmes she bade us both\ngood-night and bustled off upon her way.\n\n\"At least,\" said I as we heard her quick, firm steps descending\nthe stairs, \"she seems to be a young lady who is very well able\nto take care of herself.\"\n\n\"And she would need to be,\" said Holmes gravely. \"I am much\nmistaken if we do not hear from her before many days are past.\"\n\nIt was not very long before my friend's prediction was fulfilled.\nA fortnight went by, during which I frequently found my thoughts\nturning in her direction and wondering what strange side-alley of\nhuman experience this lonely woman had strayed into. The unusual\nsalary, the curious conditions, the light duties, all pointed to\nsomething abnormal, though whether a fad or a plot, or whether\nthe man were a philanthropist or a villain, it was quite beyond\nmy powers to determine. As to Holmes, I observed that he sat\nfrequently for half an hour on end, with knitted brows and an\nabstracted air, but he swept the matter away with a wave of his\nhand when I mentioned it. \"Data! data! data!\" he cried\nimpatiently. \"I can't make bricks without clay.\" And yet he would\nalways wind up by muttering that no sister of his should ever\nhave accepted such a situation.\n\nThe telegram which we eventually received came late one night\njust as I was thinking of turning in and Holmes was settling down\nto one of those all-night chemical researches which he frequently\nindulged in, when I would leave him stooping over a retort and a\ntest-tube at night and find him in the same position when I came\ndown to breakfast in the morning. He opened the yellow envelope,\nand then, glancing at the message, threw it across to me.\n\n\"Just look up the trains in Bradshaw,\" said he, and turned back\nto his chemical studies.\n\nThe summons was a brief and urgent one.\n\n\"Please be at the Black Swan Hotel at Winchester at midday\nto-morrow,\" it said. \"Do come! I am at my wit's end.  HUNTER.\"\n\n\"Will you come with me?\" asked Holmes, glancing up.\n\n\"I should wish to.\"\n\n\"Just look it up, then.\"\n\n\"There is a train at half-past nine,\" said I, glancing over my\nBradshaw. \"It is due at Winchester at 11:30.\"\n\n\"That will do very nicely. Then perhaps I had better postpone my\nanalysis of the acetones, as we may need to be at our best in the\nmorning.\"\n\nBy eleven o'clock the next day we were well upon our way to the\nold English capital. Holmes had been buried in the morning papers\nall the way down, but after we had passed the Hampshire border he\nthrew them down and began to admire the scenery. It was an ideal\nspring day, a light blue sky, flecked with little fleecy white\nclouds drifting across from west to east. The sun was shining\nvery brightly, and yet there was an exhilarating nip in the air,\nwhich set an edge to a man's energy. All over the countryside,\naway to the rolling hills around Aldershot, the little red and\ngrey roofs of the farm-steadings peeped out from amid the light\ngreen of the new foliage.\n\n\"Are they not fresh and beautiful?\" I cried with all the\nenthusiasm of a man fresh from the fogs of Baker Street.\n\nBut Holmes shook his head gravely.\n\n\"Do you know, Watson,\" said he, \"that it is one of the curses of\na mind with a turn like mine that I must look at everything with\nreference to my own special subject. You look at these scattered\nhouses, and you are impressed by their beauty. I look at them,\nand the only thought which comes to me is a feeling of their\nisolation and of the impunity with which crime may be committed\nthere.\"\n\n\"Good heavens!\" I cried. \"Who would associate crime with these\ndear old homesteads?\"\n\n\"They always fill me with a certain horror. It is my belief,\nWatson, founded upon my experience, that the lowest and vilest\nalleys in London do not present a more dreadful record of sin\nthan does the smiling and beautiful countryside.\"\n\n\"You horrify me!\"\n\n\"But the reason is very obvious. The pressure of public opinion\ncan do in the town what the law cannot accomplish. There is no\nlane so vile that the scream of a tortured child, or the thud of\na drunkard's blow, does not beget sympathy and indignation among\nthe neighbours, and then the whole machinery of justice is ever\nso close that a word of complaint can set it going, and there is\nbut a step between the crime and the dock. But look at these\nlonely houses, each in its own fields, filled for the most part\nwith poor ignorant folk who know little of the law. Think of the\ndeeds of hellish cruelty, the hidden wickedness which may go on,\nyear in, year out, in such places, and none the wiser. Had this\nlady who appeals to us for help gone to live in Winchester, I\nshould never have had a fear for her. It is the five miles of\ncountry which makes the danger. Still, it is clear that she is\nnot personally threatened.\"\n\n\"No. If she can come to Winchester to meet us she can get away.\"\n\n\"Quite so. She has her freedom.\"\n\n\"What CAN be the matter, then? Can you suggest no explanation?\"\n\n\"I have devised seven separate explanations, each of which would\ncover the facts as far as we know them. But which of these is\ncorrect can only be determined by the fresh information which we\nshall no doubt find waiting for us. Well, there is the tower of\nthe cathedral, and we shall soon learn all that Miss Hunter has\nto tell.\"\n\nThe Black Swan is an inn of repute in the High Street, at no\ndistance from the station, and there we found the young lady\nwaiting for us. She had engaged a sitting-room, and our lunch\nawaited us upon the table.\n\n\"I am so delighted that you have come,\" she said earnestly. \"It\nis so very kind of you both; but indeed I do not know what I\nshould do. Your advice will be altogether invaluable to me.\"\n\n\"Pray tell us what has happened to you.\"\n\n\"I will do so, and I must be quick, for I have promised Mr.\nRucastle to be back before three. I got his leave to come into\ntown this morning, though he little knew for what purpose.\"\n\n\"Let us have everything in its due order.\" Holmes thrust his long\nthin legs out towards the fire and composed himself to listen.\n\n\"In the first place, I may say that I have met, on the whole,\nwith no actual ill-treatment from Mr. and Mrs. Rucastle. It is\nonly fair to them to say that. But I cannot understand them, and\nI am not easy in my mind about them.\"\n\n\"What can you not understand?\"\n\n\"Their reasons for their conduct. But you shall have it all just\nas it occurred. When I came down, Mr. Rucastle met me here and\ndrove me in his dog-cart to the Copper Beeches. It is, as he\nsaid, beautifully situated, but it is not beautiful in itself,\nfor it is a large square block of a house, whitewashed, but all\nstained and streaked with damp and bad weather. There are grounds\nround it, woods on three sides, and on the fourth a field which\nslopes down to the Southampton highroad, which curves past about\na hundred yards from the front door. This ground in front belongs\nto the house, but the woods all round are part of Lord\nSoutherton's preserves. A clump of copper beeches immediately in\nfront of the hall door has given its name to the place.\n\n\"I was driven over by my employer, who was as amiable as ever,\nand was introduced by him that evening to his wife and the child.\nThere was no truth, Mr. Holmes, in the conjecture which seemed to\nus to be probable in your rooms at Baker Street. Mrs. Rucastle is\nnot mad. I found her to be a silent, pale-faced woman, much\nyounger than her husband, not more than thirty, I should think,\nwhile he can hardly be less than forty-five. From their\nconversation I have gathered that they have been married about\nseven years, that he was a widower, and that his only child by\nthe first wife was the daughter who has gone to Philadelphia. Mr.\nRucastle told me in private that the reason why she had left them\nwas that she had an unreasoning aversion to her stepmother. As\nthe daughter could not have been less than twenty, I can quite\nimagine that her position must have been uncomfortable with her\nfather's young wife.\n\n\"Mrs. Rucastle seemed to me to be colourless in mind as well as\nin feature. She impressed me neither favourably nor the reverse.\nShe was a nonentity. It was easy to see that she was passionately\ndevoted both to her husband and to her little son. Her light grey\neyes wandered continually from one to the other, noting every\nlittle want and forestalling it if possible. He was kind to her\nalso in his bluff, boisterous fashion, and on the whole they\nseemed to be a happy couple. And yet she had some secret sorrow,\nthis woman. She would often be lost in deep thought, with the\nsaddest look upon her face. More than once I have surprised her\nin tears. I have thought sometimes that it was the disposition of\nher child which weighed upon her mind, for I have never met so\nutterly spoiled and so ill-natured a little creature. He is small\nfor his age, with a head which is quite disproportionately large.\nHis whole life appears to be spent in an alternation between\nsavage fits of passion and gloomy intervals of sulking. Giving\npain to any creature weaker than himself seems to be his one idea\nof amusement, and he shows quite remarkable talent in planning\nthe capture of mice, little birds, and insects. But I would\nrather not talk about the creature, Mr. Holmes, and, indeed, he\nhas little to do with my story.\"\n\n\"I am glad of all details,\" remarked my friend, \"whether they\nseem to you to be relevant or not.\"\n\n\"I shall try not to miss anything of importance. The one\nunpleasant thing about the house, which struck me at once, was\nthe appearance and conduct of the servants. There are only two, a\nman and his wife. Toller, for that is his name, is a rough,\nuncouth man, with grizzled hair and whiskers, and a perpetual\nsmell of drink. Twice since I have been with them he has been\nquite drunk, and yet Mr. Rucastle seemed to take no notice of it.\nHis wife is a very tall and strong woman with a sour face, as\nsilent as Mrs. Rucastle and much less amiable. They are a most\nunpleasant couple, but fortunately I spend most of my time in the\nnursery and my own room, which are next to each other in one\ncorner of the building.\n\n\"For two days after my arrival at the Copper Beeches my life was\nvery quiet; on the third, Mrs. Rucastle came down just after\nbreakfast and whispered something to her husband.\n\n\"'Oh, yes,' said he, turning to me, 'we are very much obliged to\nyou, Miss Hunter, for falling in with our whims so far as to cut\nyour hair. I assure you that it has not detracted in the tiniest\niota from your appearance. We shall now see how the electric-blue\ndress will become you. You will find it laid out upon the bed in\nyour room, and if you would be so good as to put it on we should\nboth be extremely obliged.'\n\n\"The dress which I found waiting for me was of a peculiar shade\nof blue. It was of excellent material, a sort of beige, but it\nbore unmistakable signs of having been worn before. It could not\nhave been a better fit if I had been measured for it. Both Mr.\nand Mrs. Rucastle expressed a delight at the look of it, which\nseemed quite exaggerated in its vehemence. They were waiting for\nme in the drawing-room, which is a very large room, stretching\nalong the entire front of the house, with three long windows\nreaching down to the floor. A chair had been placed close to the\ncentral window, with its back turned towards it. In this I was\nasked to sit, and then Mr. Rucastle, walking up and down on the\nother side of the room, began to tell me a series of the funniest\nstories that I have ever listened to. You cannot imagine how\ncomical he was, and I laughed until I was quite weary. Mrs.\nRucastle, however, who has evidently no sense of humour, never so\nmuch as smiled, but sat with her hands in her lap, and a sad,\nanxious look upon her face. After an hour or so, Mr. Rucastle\nsuddenly remarked that it was time to commence the duties of the\nday, and that I might change my dress and go to little Edward in\nthe nursery.\n\n\"Two days later this same performance was gone through under\nexactly similar circumstances. Again I changed my dress, again I\nsat in the window, and again I laughed very heartily at the funny\nstories of which my employer had an immense répertoire, and which\nhe told inimitably. Then he handed me a yellow-backed novel, and\nmoving my chair a little sideways, that my own shadow might not\nfall upon the page, he begged me to read aloud to him. I read for\nabout ten minutes, beginning in the heart of a chapter, and then\nsuddenly, in the middle of a sentence, he ordered me to cease and\nto change my dress.\n\n\"You can easily imagine, Mr. Holmes, how curious I became as to\nwhat the meaning of this extraordinary performance could possibly\nbe. They were always very careful, I observed, to turn my face\naway from the window, so that I became consumed with the desire\nto see what was going on behind my back. At first it seemed to be\nimpossible, but I soon devised a means. My hand-mirror had been\nbroken, so a happy thought seized me, and I concealed a piece of\nthe glass in my handkerchief. On the next occasion, in the midst\nof my laughter, I put my handkerchief up to my eyes, and was able\nwith a little management to see all that there was behind me. I\nconfess that I was disappointed. There was nothing. At least that\nwas my first impression. At the second glance, however, I\nperceived that there was a man standing in the Southampton Road,\na small bearded man in a grey suit, who seemed to be looking in\nmy direction. The road is an important highway, and there are\nusually people there. This man, however, was leaning against the\nrailings which bordered our field and was looking earnestly up. I\nlowered my handkerchief and glanced at Mrs. Rucastle to find her\neyes fixed upon me with a most searching gaze. She said nothing,\nbut I am convinced that she had divined that I had a mirror in my\nhand and had seen what was behind me. She rose at once.\n\n\"'Jephro,' said she, 'there is an impertinent fellow upon the\nroad there who stares up at Miss Hunter.'\n\n\"'No friend of yours, Miss Hunter?' he asked.\n\n\"'No, I know no one in these parts.'\n\n\"'Dear me! How very impertinent! Kindly turn round and motion to\nhim to go away.'\n\n\"'Surely it would be better to take no notice.'\n\n\"'No, no, we should have him loitering here always. Kindly turn\nround and wave him away like that.'\n\n\"I did as I was told, and at the same instant Mrs. Rucastle drew\ndown the blind. That was a week ago, and from that time I have\nnot sat again in the window, nor have I worn the blue dress, nor\nseen the man in the road.\"\n\n\"Pray continue,\" said Holmes. \"Your narrative promises to be a\nmost interesting one.\"\n\n\"You will find it rather disconnected, I fear, and there may\nprove to be little relation between the different incidents of\nwhich I speak. On the very first day that I was at the Copper\nBeeches, Mr. Rucastle took me to a small outhouse which stands\nnear the kitchen door. As we approached it I heard the sharp\nrattling of a chain, and the sound as of a large animal moving\nabout.\n\n\"'Look in here!' said Mr. Rucastle, showing me a slit between two\nplanks. 'Is he not a beauty?'\n\n\"I looked through and was conscious of two glowing eyes, and of a\nvague figure huddled up in the darkness.\n\n\"'Don't be frightened,' said my employer, laughing at the start\nwhich I had given. 'It's only Carlo, my mastiff. I call him mine,\nbut really old Toller, my groom, is the only man who can do\nanything with him. We feed him once a day, and not too much then,\nso that he is always as keen as mustard. Toller lets him loose\nevery night, and God help the trespasser whom he lays his fangs\nupon. For goodness' sake don't you ever on any pretext set your\nfoot over the threshold at night, for it's as much as your life\nis worth.'\n\n\"The warning was no idle one, for two nights later I happened to\nlook out of my bedroom window about two o'clock in the morning.\nIt was a beautiful moonlight night, and the lawn in front of the\nhouse was silvered over and almost as bright as day. I was\nstanding, rapt in the peaceful beauty of the scene, when I was\naware that something was moving under the shadow of the copper\nbeeches. As it emerged into the moonshine I saw what it was. It\nwas a giant dog, as large as a calf, tawny tinted, with hanging\njowl, black muzzle, and huge projecting bones. It walked slowly\nacross the lawn and vanished into the shadow upon the other side.\nThat dreadful sentinel sent a chill to my heart which I do not\nthink that any burglar could have done.\n\n\"And now I have a very strange experience to tell you. I had, as\nyou know, cut off my hair in London, and I had placed it in a\ngreat coil at the bottom of my trunk. One evening, after the\nchild was in bed, I began to amuse myself by examining the\nfurniture of my room and by rearranging my own little things.\nThere was an old chest of drawers in the room, the two upper ones\nempty and open, the lower one locked. I had filled the first two\nwith my linen, and as I had still much to pack away I was\nnaturally annoyed at not having the use of the third drawer. It\nstruck me that it might have been fastened by a mere oversight,\nso I took out my bunch of keys and tried to open it. The very\nfirst key fitted to perfection, and I drew the drawer open. There\nwas only one thing in it, but I am sure that you would never\nguess what it was. It was my coil of hair.\n\n\"I took it up and examined it. It was of the same peculiar tint,\nand the same thickness. But then the impossibility of the thing\nobtruded itself upon me. How could my hair have been locked in\nthe drawer? With trembling hands I undid my trunk, turned out the\ncontents, and drew from the bottom my own hair. I laid the two\ntresses together, and I assure you that they were identical. Was\nit not extraordinary? Puzzle as I would, I could make nothing at\nall of what it meant. I returned the strange hair to the drawer,\nand I said nothing of the matter to the Rucastles as I felt that\nI had put myself in the wrong by opening a drawer which they had\nlocked.\n\n\"I am naturally observant, as you may have remarked, Mr. Holmes,\nand I soon had a pretty good plan of the whole house in my head.\nThere was one wing, however, which appeared not to be inhabited\nat all. A door which faced that which led into the quarters of\nthe Tollers opened into this suite, but it was invariably locked.\nOne day, however, as I ascended the stair, I met Mr. Rucastle\ncoming out through this door, his keys in his hand, and a look on\nhis face which made him a very different person to the round,\njovial man to whom I was accustomed. His cheeks were red, his\nbrow was all crinkled with anger, and the veins stood out at his\ntemples with passion. He locked the door and hurried past me\nwithout a word or a look.\n\n\"This aroused my curiosity, so when I went out for a walk in the\ngrounds with my charge, I strolled round to the side from which I\ncould see the windows of this part of the house. There were four\nof them in a row, three of which were simply dirty, while the\nfourth was shuttered up. They were evidently all deserted. As I\nstrolled up and down, glancing at them occasionally, Mr. Rucastle\ncame out to me, looking as merry and jovial as ever.\n\n\"'Ah!' said he, 'you must not think me rude if I passed you\nwithout a word, my dear young lady. I was preoccupied with\nbusiness matters.'\n\n\"I assured him that I was not offended. 'By the way,' said I,\n'you seem to have quite a suite of spare rooms up there, and one\nof them has the shutters up.'\n\n\"He looked surprised and, as it seemed to me, a little startled\nat my remark.\n\n\"'Photography is one of my hobbies,' said he. 'I have made my\ndark room up there. But, dear me! what an observant young lady we\nhave come upon. Who would have believed it? Who would have ever\nbelieved it?' He spoke in a jesting tone, but there was no jest\nin his eyes as he looked at me. I read suspicion there and\nannoyance, but no jest.\n\n\"Well, Mr. Holmes, from the moment that I understood that there\nwas something about that suite of rooms which I was not to know,\nI was all on fire to go over them. It was not mere curiosity,\nthough I have my share of that. It was more a feeling of duty--a\nfeeling that some good might come from my penetrating to this\nplace. They talk of woman's instinct; perhaps it was woman's\ninstinct which gave me that feeling. At any rate, it was there,\nand I was keenly on the lookout for any chance to pass the\nforbidden door.\n\n\"It was only yesterday that the chance came. I may tell you that,\nbesides Mr. Rucastle, both Toller and his wife find something to\ndo in these deserted rooms, and I once saw him carrying a large\nblack linen bag with him through the door. Recently he has been\ndrinking hard, and yesterday evening he was very drunk; and when\nI came upstairs there was the key in the door. I have no doubt at\nall that he had left it there. Mr. and Mrs. Rucastle were both\ndownstairs, and the child was with them, so that I had an\nadmirable opportunity. I turned the key gently in the lock,\nopened the door, and slipped through.\n\n\"There was a little passage in front of me, unpapered and\nuncarpeted, which turned at a right angle at the farther end.\nRound this corner were three doors in a line, the first and third\nof which were open. They each led into an empty room, dusty and\ncheerless, with two windows in the one and one in the other, so\nthick with dirt that the evening light glimmered dimly through\nthem. The centre door was closed, and across the outside of it\nhad been fastened one of the broad bars of an iron bed, padlocked\nat one end to a ring in the wall, and fastened at the other with\nstout cord. The door itself was locked as well, and the key was\nnot there. This barricaded door corresponded clearly with the\nshuttered window outside, and yet I could see by the glimmer from\nbeneath it that the room was not in darkness. Evidently there was\na skylight which let in light from above. As I stood in the\npassage gazing at the sinister door and wondering what secret it\nmight veil, I suddenly heard the sound of steps within the room\nand saw a shadow pass backward and forward against the little\nslit of dim light which shone out from under the door. A mad,\nunreasoning terror rose up in me at the sight, Mr. Holmes. My\noverstrung nerves failed me suddenly, and I turned and ran--ran\nas though some dreadful hand were behind me clutching at the\nskirt of my dress. I rushed down the passage, through the door,\nand straight into the arms of Mr. Rucastle, who was waiting\noutside.\n\n\"'So,' said he, smiling, 'it was you, then. I thought that it\nmust be when I saw the door open.'\n\n\"'Oh, I am so frightened!' I panted.\n\n\"'My dear young lady! my dear young lady!'--you cannot think how\ncaressing and soothing his manner was--'and what has frightened\nyou, my dear young lady?'\n\n\"But his voice was just a little too coaxing. He overdid it. I\nwas keenly on my guard against him.\n\n\"'I was foolish enough to go into the empty wing,' I answered.\n'But it is so lonely and eerie in this dim light that I was\nfrightened and ran out again. Oh, it is so dreadfully still in\nthere!'\n\n\"'Only that?' said he, looking at me keenly.\n\n\"'Why, what did you think?' I asked.\n\n\"'Why do you think that I lock this door?'\n\n\"'I am sure that I do not know.'\n\n\"'It is to keep people out who have no business there. Do you\nsee?' He was still smiling in the most amiable manner.\n\n\"'I am sure if I had known--'\n\n\"'Well, then, you know now. And if you ever put your foot over\nthat threshold again'--here in an instant the smile hardened into\na grin of rage, and he glared down at me with the face of a\ndemon--'I'll throw you to the mastiff.'\n\n\"I was so terrified that I do not know what I did. I suppose that\nI must have rushed past him into my room. I remember nothing\nuntil I found myself lying on my bed trembling all over. Then I\nthought of you, Mr. Holmes. I could not live there longer without\nsome advice. I was frightened of the house, of the man, of the\nwoman, of the servants, even of the child. They were all horrible\nto me. If I could only bring you down all would be well. Of\ncourse I might have fled from the house, but my curiosity was\nalmost as strong as my fears. My mind was soon made up. I would\nsend you a wire. I put on my hat and cloak, went down to the\noffice, which is about half a mile from the house, and then\nreturned, feeling very much easier. A horrible doubt came into my\nmind as I approached the door lest the dog might be loose, but I\nremembered that Toller had drunk himself into a state of\ninsensibility that evening, and I knew that he was the only one\nin the household who had any influence with the savage creature,\nor who would venture to set him free. I slipped in in safety and\nlay awake half the night in my joy at the thought of seeing you.\nI had no difficulty in getting leave to come into Winchester this\nmorning, but I must be back before three o'clock, for Mr. and\nMrs. Rucastle are going on a visit, and will be away all the\nevening, so that I must look after the child. Now I have told you\nall my adventures, Mr. Holmes, and I should be very glad if you\ncould tell me what it all means, and, above all, what I should\ndo.\"\n\nHolmes and I had listened spellbound to this extraordinary story.\nMy friend rose now and paced up and down the room, his hands in\nhis pockets, and an expression of the most profound gravity upon\nhis face.\n\n\"Is Toller still drunk?\" he asked.\n\n\"Yes. I heard his wife tell Mrs. Rucastle that she could do\nnothing with him.\"\n\n\"That is well. And the Rucastles go out to-night?\"\n\n\"Yes.\"\n\n\"Is there a cellar with a good strong lock?\"\n\n\"Yes, the wine-cellar.\"\n\n\"You seem to me to have acted all through this matter like a very\nbrave and sensible girl, Miss Hunter. Do you think that you could\nperform one more feat? I should not ask it of you if I did not\nthink you a quite exceptional woman.\"\n\n\"I will try. What is it?\"\n\n\"We shall be at the Copper Beeches by seven o'clock, my friend\nand I. The Rucastles will be gone by that time, and Toller will,\nwe hope, be incapable. There only remains Mrs. Toller, who might\ngive the alarm. If you could send her into the cellar on some\nerrand, and then turn the key upon her, you would facilitate\nmatters immensely.\"\n\n\"I will do it.\"\n\n\"Excellent! We shall then look thoroughly into the affair. Of\ncourse there is only one feasible explanation. You have been\nbrought there to personate someone, and the real person is\nimprisoned in this chamber. That is obvious. As to who this\nprisoner is, I have no doubt that it is the daughter, Miss Alice\nRucastle, if I remember right, who was said to have gone to\nAmerica. You were chosen, doubtless, as resembling her in height,\nfigure, and the colour of your hair. Hers had been cut off, very\npossibly in some illness through which she has passed, and so, of\ncourse, yours had to be sacrificed also. By a curious chance you\ncame upon her tresses. The man in the road was undoubtedly some\nfriend of hers--possibly her fiancé--and no doubt, as you wore\nthe girl's dress and were so like her, he was convinced from your\nlaughter, whenever he saw you, and afterwards from your gesture,\nthat Miss Rucastle was perfectly happy, and that she no longer\ndesired his attentions. The dog is let loose at night to prevent\nhim from endeavouring to communicate with her. So much is fairly\nclear. The most serious point in the case is the disposition of\nthe child.\"\n\n\"What on earth has that to do with it?\" I ejaculated.\n\n\"My dear Watson, you as a medical man are continually gaining\nlight as to the tendencies of a child by the study of the\nparents. Don't you see that the converse is equally valid. I have\nfrequently gained my first real insight into the character of\nparents by studying their children. This child's disposition is\nabnormally cruel, merely for cruelty's sake, and whether he\nderives this from his smiling father, as I should suspect, or\nfrom his mother, it bodes evil for the poor girl who is in their\npower.\"\n\n\"I am sure that you are right, Mr. Holmes,\" cried our client. \"A\nthousand things come back to me which make me certain that you\nhave hit it. Oh, let us lose not an instant in bringing help to\nthis poor creature.\"\n\n\"We must be circumspect, for we are dealing with a very cunning\nman. We can do nothing until seven o'clock. At that hour we shall\nbe with you, and it will not be long before we solve the\nmystery.\"\n\nWe were as good as our word, for it was just seven when we\nreached the Copper Beeches, having put up our trap at a wayside\npublic-house. The group of trees, with their dark leaves shining\nlike burnished metal in the light of the setting sun, were\nsufficient to mark the house even had Miss Hunter not been\nstanding smiling on the door-step.\n\n\"Have you managed it?\" asked Holmes.\n\nA loud thudding noise came from somewhere downstairs. \"That is\nMrs. Toller in the cellar,\" said she. \"Her husband lies snoring\non the kitchen rug. Here are his keys, which are the duplicates\nof Mr. Rucastle's.\"\n\n\"You have done well indeed!\" cried Holmes with enthusiasm. \"Now\nlead the way, and we shall soon see the end of this black\nbusiness.\"\n\nWe passed up the stair, unlocked the door, followed on down a\npassage, and found ourselves in front of the barricade which Miss\nHunter had described. Holmes cut the cord and removed the\ntransverse bar. Then he tried the various keys in the lock, but\nwithout success. No sound came from within, and at the silence\nHolmes' face clouded over.\n\n\"I trust that we are not too late,\" said he. \"I think, Miss\nHunter, that we had better go in without you. Now, Watson, put\nyour shoulder to it, and we shall see whether we cannot make our\nway in.\"\n\nIt was an old rickety door and gave at once before our united\nstrength. Together we rushed into the room. It was empty. There\nwas no furniture save a little pallet bed, a small table, and a\nbasketful of linen. The skylight above was open, and the prisoner\ngone.\n\n\"There has been some villainy here,\" said Holmes; \"this beauty\nhas guessed Miss Hunter's intentions and has carried his victim\noff.\"\n\n\"But how?\"\n\n\"Through the skylight. We shall soon see how he managed it.\" He\nswung himself up onto the roof. \"Ah, yes,\" he cried, \"here's the\nend of a long light ladder against the eaves. That is how he did\nit.\"\n\n\"But it is impossible,\" said Miss Hunter; \"the ladder was not\nthere when the Rucastles went away.\"\n\n\"He has come back and done it. I tell you that he is a clever and\ndangerous man. I should not be very much surprised if this were\nhe whose step I hear now upon the stair. I think, Watson, that it\nwould be as well for you to have your pistol ready.\"\n\nThe words were hardly out of his mouth before a man appeared at\nthe door of the room, a very fat and burly man, with a heavy\nstick in his hand. Miss Hunter screamed and shrunk against the\nwall at the sight of him, but Sherlock Holmes sprang forward and\nconfronted him.\n\n\"You villain!\" said he, \"where's your daughter?\"\n\nThe fat man cast his eyes round, and then up at the open\nskylight.\n\n\"It is for me to ask you that,\" he shrieked, \"you thieves! Spies\nand thieves! I have caught you, have I? You are in my power. I'll\nserve you!\" He turned and clattered down the stairs as hard as he\ncould go.\n\n\"He's gone for the dog!\" cried Miss Hunter.\n\n\"I have my revolver,\" said I.\n\n\"Better close the front door,\" cried Holmes, and we all rushed\ndown the stairs together. We had hardly reached the hall when we\nheard the baying of a hound, and then a scream of agony, with a\nhorrible worrying sound which it was dreadful to listen to. An\nelderly man with a red face and shaking limbs came staggering out\nat a side door.\n\n\"My God!\" he cried. \"Someone has loosed the dog. It's not been\nfed for two days. Quick, quick, or it'll be too late!\"\n\nHolmes and I rushed out and round the angle of the house, with\nToller hurrying behind us. There was the huge famished brute, its\nblack muzzle buried in Rucastle's throat, while he writhed and\nscreamed upon the ground. Running up, I blew its brains out, and\nit fell over with its keen white teeth still meeting in the great\ncreases of his neck. With much labour we separated them and\ncarried him, living but horribly mangled, into the house. We laid\nhim upon the drawing-room sofa, and having dispatched the sobered\nToller to bear the news to his wife, I did what I could to\nrelieve his pain. We were all assembled round him when the door\nopened, and a tall, gaunt woman entered the room.\n\n\"Mrs. Toller!\" cried Miss Hunter.\n\n\"Yes, miss. Mr. Rucastle let me out when he came back before he\nwent up to you. Ah, miss, it is a pity you didn't let me know\nwhat you were planning, for I would have told you that your pains\nwere wasted.\"\n\n\"Ha!\" said Holmes, looking keenly at her. \"It is clear that Mrs.\nToller knows more about this matter than anyone else.\"\n\n\"Yes, sir, I do, and I am ready enough to tell what I know.\"\n\n\"Then, pray, sit down, and let us hear it for there are several\npoints on which I must confess that I am still in the dark.\"\n\n\"I will soon make it clear to you,\" said she; \"and I'd have done\nso before now if I could ha' got out from the cellar. If there's\npolice-court business over this, you'll remember that I was the\none that stood your friend, and that I was Miss Alice's friend\ntoo.\n\n\"She was never happy at home, Miss Alice wasn't, from the time\nthat her father married again. She was slighted like and had no\nsay in anything, but it never really became bad for her until\nafter she met Mr. Fowler at a friend's house. As well as I could\nlearn, Miss Alice had rights of her own by will, but she was so\nquiet and patient, she was, that she never said a word about them\nbut just left everything in Mr. Rucastle's hands. He knew he was\nsafe with her; but when there was a chance of a husband coming\nforward, who would ask for all that the law would give him, then\nher father thought it time to put a stop on it. He wanted her to\nsign a paper, so that whether she married or not, he could use\nher money. When she wouldn't do it, he kept on worrying her until\nshe got brain-fever, and for six weeks was at death's door. Then\nshe got better at last, all worn to a shadow, and with her\nbeautiful hair cut off; but that didn't make no change in her\nyoung man, and he stuck to her as true as man could be.\"\n\n\"Ah,\" said Holmes, \"I think that what you have been good enough\nto tell us makes the matter fairly clear, and that I can deduce\nall that remains. Mr. Rucastle then, I presume, took to this\nsystem of imprisonment?\"\n\n\"Yes, sir.\"\n\n\"And brought Miss Hunter down from London in order to get rid of\nthe disagreeable persistence of Mr. Fowler.\"\n\n\"That was it, sir.\"\n\n\"But Mr. Fowler being a persevering man, as a good seaman should\nbe, blockaded the house, and having met you succeeded by certain\narguments, metallic or otherwise, in convincing you that your\ninterests were the same as his.\"\n\n\"Mr. Fowler was a very kind-spoken, free-handed gentleman,\" said\nMrs. Toller serenely.\n\n\"And in this way he managed that your good man should have no\nwant of drink, and that a ladder should be ready at the moment\nwhen your master had gone out.\"\n\n\"You have it, sir, just as it happened.\"\n\n\"I am sure we owe you an apology, Mrs. Toller,\" said Holmes, \"for\nyou have certainly cleared up everything which puzzled us. And\nhere comes the country surgeon and Mrs. Rucastle, so I think,\nWatson, that we had best escort Miss Hunter back to Winchester,\nas it seems to me that our locus standi now is rather a\nquestionable one.\"\n\nAnd thus was solved the mystery of the sinister house with the\ncopper beeches in front of the door. Mr. Rucastle survived, but\nwas always a broken man, kept alive solely through the care of\nhis devoted wife. They still live with their old servants, who\nprobably know so much of Rucastle's past life that he finds it\ndifficult to part from them. Mr. Fowler and Miss Rucastle were\nmarried, by special license, in Southampton the day after their\nflight, and he is now the holder of a government appointment in\nthe island of Mauritius. As to Miss Violet Hunter, my friend\nHolmes, rather to my disappointment, manifested no further\ninterest in her when once she had ceased to be the centre of one\nof his problems, and she is now the head of a private school at\nWalsall, where I believe that she has met with considerable success.\n\n\n\n\n\n\n\n\n\nEnd of the Project Gutenberg EBook of The Adventures of Sherlock Holmes, by \nArthur Conan Doyle\n\n*** END OF THIS PROJECT GUTENBERG EBOOK THE ADVENTURES OF SHERLOCK HOLMES ***\n\n***** This file should be named 1661-8.txt or 1661-8.zip *****\nThis and all associated files of various formats will be found in:\n        http://www.gutenberg.org/1/6/6/1661/\n\nProduced by an anonymous Project Gutenberg volunteer and Jose Menendez\n\nUpdated editions will replace the previous one--the old editions\nwill be renamed.\n\nCreating the works from public domain print editions means that no\none owns a United States copyright in these works, so the Foundation\n(and you!) can copy and distribute it in the United States without\npermission and without paying copyright royalties.  Special rules,\nset forth in the General Terms of Use part of this license, apply to\ncopying and distributing Project Gutenberg-tm electronic works to\nprotect the PROJECT GUTENBERG-tm concept and trademark.  Project\nGutenberg is a registered trademark, and may not be used if you\ncharge for the eBooks, unless you receive specific permission.  If you\ndo not charge anything for copies of this eBook, complying with the\nrules is very easy.  You may use this eBook for nearly any purpose\nsuch as creation of derivative works, reports, performances and\nresearch.  They may be modified and printed and given away--you may do\npractically ANYTHING with public domain eBooks.  Redistribution is\nsubject to the trademark license, especially commercial\nredistribution.\n\n\n\n*** START: FULL LICENSE ***\n\nTHE FULL PROJECT GUTENBERG LICENSE\nPLEASE READ THIS BEFORE YOU DISTRIBUTE OR USE THIS WORK\n\nTo protect the Project Gutenberg-tm mission of promoting the free\ndistribution of electronic works, by using or distributing this work\n(or any other work associated in any way with the phrase \"Project\nGutenberg\"), you agree to comply with all the terms of the Full Project\nGutenberg-tm License (available with this file or online at\nhttp://gutenberg.net/license).\n\n\nSection 1.  General Terms of Use and Redistributing Project Gutenberg-tm\nelectronic works\n\n1.A.  By reading or using any part of this Project Gutenberg-tm\nelectronic work, you indicate that you have read, understand, agree to\nand accept all the terms of this license and intellectual property\n(trademark/copyright) agreement.  If you do not agree to abide by all\nthe terms of this agreement, you must cease using and return or destroy\nall copies of Project Gutenberg-tm electronic works in your possession.\nIf you paid a fee for obtaining a copy of or access to a Project\nGutenberg-tm electronic work and you do not agree to be bound by the\nterms of this agreement, you may obtain a refund from the person or\nentity to whom you paid the fee as set forth in paragraph 1.E.8.\n\n1.B.  \"Project Gutenberg\" is a registered trademark.  It may only be\nused on or associated in any way with an electronic work by people who\nagree to be bound by the terms of this agreement.  There are a few\nthings that you can do with most Project Gutenberg-tm electronic works\neven without complying with the full terms of this agreement.  See\nparagraph 1.C below.  There are a lot of things you can do with Project\nGutenberg-tm electronic works if you follow the terms of this agreement\nand help preserve free future access to Project Gutenberg-tm electronic\nworks.  See paragraph 1.E below.\n\n1.C.  The Project Gutenberg Literary Archive Foundation (\"the Foundation\"\nor PGLAF), owns a compilation copyright in the collection of Project\nGutenberg-tm electronic works.  Nearly all the individual works in the\ncollection are in the public domain in the United States.  If an\nindividual work is in the public domain in the United States and you are\nlocated in the United States, we do not claim a right to prevent you from\ncopying, distributing, performing, displaying or creating derivative\nworks based on the work as long as all references to Project Gutenberg\nare removed.  Of course, we hope that you will support the Project\nGutenberg-tm mission of promoting free access to electronic works by\nfreely sharing Project Gutenberg-tm works in compliance with the terms of\nthis agreement for keeping the Project Gutenberg-tm name associated with\nthe work.  You can easily comply with the terms of this agreement by\nkeeping this work in the same format with its attached full Project\nGutenberg-tm License when you share it without charge with others.\n\n1.D.  The copyright laws of the place where you are located also govern\nwhat you can do with this work.  Copyright laws in most countries are in\na constant state of change.  If you are outside the United States, check\nthe laws of your country in addition to the terms of this agreement\nbefore downloading, copying, displaying, performing, distributing or\ncreating derivative works based on this work or any other Project\nGutenberg-tm work.  The Foundation makes no representations concerning\nthe copyright status of any work in any country outside the United\nStates.\n\n1.E.  Unless you have removed all references to Project Gutenberg:\n\n1.E.1.  The following sentence, with active links to, or other immediate\naccess to, the full Project Gutenberg-tm License must appear prominently\nwhenever any copy of a Project Gutenberg-tm work (any work on which the\nphrase \"Project Gutenberg\" appears, or with which the phrase \"Project\nGutenberg\" is associated) is accessed, displayed, performed, viewed,\ncopied or distributed:\n\nThis eBook is for the use of anyone anywhere at no cost and with\nalmost no restrictions whatsoever.  You may copy it, give it away or\nre-use it under the terms of the Project Gutenberg License included\nwith this eBook or online at www.gutenberg.net\n\n1.E.2.  If an individual Project Gutenberg-tm electronic work is derived\nfrom the public domain (does not contain a notice indicating that it is\nposted with permission of the copyright holder), the work can be copied\nand distributed to anyone in the United States without paying any fees\nor charges.  If you are redistributing or providing access to a work\nwith the phrase \"Project Gutenberg\" associated with or appearing on the\nwork, you must comply either with the requirements of paragraphs 1.E.1\nthrough 1.E.7 or obtain permission for the use of the work and the\nProject Gutenberg-tm trademark as set forth in paragraphs 1.E.8 or\n1.E.9.\n\n1.E.3.  If an individual Project Gutenberg-tm electronic work is posted\nwith the permission of the copyright holder, your use and distribution\nmust comply with both paragraphs 1.E.1 through 1.E.7 and any additional\nterms imposed by the copyright holder.  Additional terms will be linked\nto the Project Gutenberg-tm License for all works posted with the\npermission of the copyright holder found at the beginning of this work.\n\n1.E.4.  Do not unlink or detach or remove the full Project Gutenberg-tm\nLicense terms from this work, or any files containing a part of this\nwork or any other work associated with Project Gutenberg-tm.\n\n1.E.5.  Do not copy, display, perform, distribute or redistribute this\nelectronic work, or any part of this electronic work, without\nprominently displaying the sentence set forth in paragraph 1.E.1 with\nactive links or immediate access to the full terms of the Project\nGutenberg-tm License.\n\n1.E.6.  You may convert to and distribute this work in any binary,\ncompressed, marked up, nonproprietary or proprietary form, including any\nword processing or hypertext form.  However, if you provide access to or\ndistribute copies of a Project Gutenberg-tm work in a format other than\n\"Plain Vanilla ASCII\" or other format used in the official version\nposted on the official Project Gutenberg-tm web site (www.gutenberg.net),\nyou must, at no additional cost, fee or expense to the user, provide a\ncopy, a means of exporting a copy, or a means of obtaining a copy upon\nrequest, of the work in its original \"Plain Vanilla ASCII\" or other\nform.  Any alternate format must include the full Project Gutenberg-tm\nLicense as specified in paragraph 1.E.1.\n\n1.E.7.  Do not charge a fee for access to, viewing, displaying,\nperforming, copying or distributing any Project Gutenberg-tm works\nunless you comply with paragraph 1.E.8 or 1.E.9.\n\n1.E.8.  You may charge a reasonable fee for copies of or providing\naccess to or distributing Project Gutenberg-tm electronic works provided\nthat\n\n- You pay a royalty fee of 20% of the gross profits you derive from\n     the use of Project Gutenberg-tm works calculated using the method\n     you already use to calculate your applicable taxes.  The fee is\n     owed to the owner of the Project Gutenberg-tm trademark, but he\n     has agreed to donate royalties under this paragraph to the\n     Project Gutenberg Literary Archive Foundation.  Royalty payments\n     must be paid within 60 days following each date on which you\n     prepare (or are legally required to prepare) your periodic tax\n     returns.  Royalty payments should be clearly marked as such and\n     sent to the Project Gutenberg Literary Archive Foundation at the\n     address specified in Section 4, \"Information about donations to\n     the Project Gutenberg Literary Archive Foundation.\"\n\n- You provide a full refund of any money paid by a user who notifies\n     you in writing (or by e-mail) within 30 days of receipt that s/he\n     does not agree to the terms of the full Project Gutenberg-tm\n     License.  You must require such a user to return or\n     destroy all copies of the works possessed in a physical medium\n     and discontinue all use of and all access to other copies of\n     Project Gutenberg-tm works.\n\n- You provide, in accordance with paragraph 1.F.3, a full refund of any\n     money paid for a work or a replacement copy, if a defect in the\n     electronic work is discovered and reported to you within 90 days\n     of receipt of the work.\n\n- You comply with all other terms of this agreement for free\n     distribution of Project Gutenberg-tm works.\n\n1.E.9.  If you wish to charge a fee or distribute a Project Gutenberg-tm\nelectronic work or group of works on different terms than are set\nforth in this agreement, you must obtain permission in writing from\nboth the Project Gutenberg Literary Archive Foundation and Michael\nHart, the owner of the Project Gutenberg-tm trademark.  Contact the\nFoundation as set forth in Section 3 below.\n\n1.F.\n\n1.F.1.  Project Gutenberg volunteers and employees expend considerable\neffort to identify, do copyright research on, transcribe and proofread\npublic domain works in creating the Project Gutenberg-tm\ncollection.  Despite these efforts, Project Gutenberg-tm electronic\nworks, and the medium on which they may be stored, may contain\n\"Defects,\" such as, but not limited to, incomplete, inaccurate or\ncorrupt data, transcription errors, a copyright or other intellectual\nproperty infringement, a defective or damaged disk or other medium, a\ncomputer virus, or computer codes that damage or cannot be read by\nyour equipment.\n\n1.F.2.  LIMITED WARRANTY, DISCLAIMER OF DAMAGES - Except for the \"Right\nof Replacement or Refund\" described in paragraph 1.F.3, the Project\nGutenberg Literary Archive Foundation, the owner of the Project\nGutenberg-tm trademark, and any other party distributing a Project\nGutenberg-tm electronic work under this agreement, disclaim all\nliability to you for damages, costs and expenses, including legal\nfees.  YOU AGREE THAT YOU HAVE NO REMEDIES FOR NEGLIGENCE, STRICT\nLIABILITY, BREACH OF WARRANTY OR BREACH OF CONTRACT EXCEPT THOSE\nPROVIDED IN PARAGRAPH 1.F.3.  YOU AGREE THAT THE FOUNDATION, THE\nTRADEMARK OWNER, AND ANY DISTRIBUTOR UNDER THIS AGREEMENT WILL NOT BE\nLIABLE TO YOU FOR ACTUAL, DIRECT, INDIRECT, CONSEQUENTIAL, PUNITIVE OR\nINCIDENTAL DAMAGES EVEN IF YOU GIVE NOTICE OF THE POSSIBILITY OF SUCH\nDAMAGE.\n\n1.F.3.  LIMITED RIGHT OF REPLACEMENT OR REFUND - If you discover a\ndefect in this electronic work within 90 days of receiving it, you can\nreceive a refund of the money (if any) you paid for it by sending a\nwritten explanation to the person you received the work from.  If you\nreceived the work on a physical medium, you must return the medium with\nyour written explanation.  The person or entity that provided you with\nthe defective work may elect to provide a replacement copy in lieu of a\nrefund.  If you received the work electronically, the person or entity\nproviding it to you may choose to give you a second opportunity to\nreceive the work electronically in lieu of a refund.  If the second copy\nis also defective, you may demand a refund in writing without further\nopportunities to fix the problem.\n\n1.F.4.  Except for the limited right of replacement or refund set forth\nin paragraph 1.F.3, this work is provided to you 'AS-IS' WITH NO OTHER\nWARRANTIES OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\nWARRANTIES OF MERCHANTIBILITY OR FITNESS FOR ANY PURPOSE.\n\n1.F.5.  Some states do not allow disclaimers of certain implied\nwarranties or the exclusion or limitation of certain types of damages.\nIf any disclaimer or limitation set forth in this agreement violates the\nlaw of the state applicable to this agreement, the agreement shall be\ninterpreted to make the maximum disclaimer or limitation permitted by\nthe applicable state law.  The invalidity or unenforceability of any\nprovision of this agreement shall not void the remaining provisions.\n\n1.F.6.  INDEMNITY - You agree to indemnify and hold the Foundation, the\ntrademark owner, any agent or employee of the Foundation, anyone\nproviding copies of Project Gutenberg-tm electronic works in accordance\nwith this agreement, and any volunteers associated with the production,\npromotion and distribution of Project Gutenberg-tm electronic works,\nharmless from all liability, costs and expenses, including legal fees,\nthat arise directly or indirectly from any of the following which you do\nor cause to occur: (a) distribution of this or any Project Gutenberg-tm\nwork, (b) alteration, modification, or additions or deletions to any\nProject Gutenberg-tm work, and (c) any Defect you cause.\n\n\nSection  2.  Information about the Mission of Project Gutenberg-tm\n\nProject Gutenberg-tm is synonymous with the free distribution of\nelectronic works in formats readable by the widest variety of computers\nincluding obsolete, old, middle-aged and new computers.  It exists\nbecause of the efforts of hundreds of volunteers and donations from\npeople in all walks of life.\n\nVolunteers and financial support to provide volunteers with the\nassistance they need are critical to reaching Project Gutenberg-tm's\ngoals and ensuring that the Project Gutenberg-tm collection will\nremain freely available for generations to come.  In 2001, the Project\nGutenberg Literary Archive Foundation was created to provide a secure\nand permanent future for Project Gutenberg-tm and future generations.\nTo learn more about the Project Gutenberg Literary Archive Foundation\nand how your efforts and donations can help, see Sections 3 and 4\nand the Foundation web page at http://www.pglaf.org.\n\n\nSection 3.  Information about the Project Gutenberg Literary Archive\nFoundation\n\nThe Project Gutenberg Literary Archive Foundation is a non profit\n501(c)(3) educational corporation organized under the laws of the\nstate of Mississippi and granted tax exempt status by the Internal\nRevenue Service.  The Foundation's EIN or federal tax identification\nnumber is 64-6221541.  Its 501(c)(3) letter is posted at\nhttp://pglaf.org/fundraising.  Contributions to the Project Gutenberg\nLiterary Archive Foundation are tax deductible to the full extent\npermitted by U.S. federal laws and your state's laws.\n\nThe Foundation's principal office is located at 4557 Melan Dr. S.\nFairbanks, AK, 99712., but its volunteers and employees are scattered\nthroughout numerous locations.  Its business office is located at\n809 North 1500 West, Salt Lake City, UT 84116, (801) 596-1887, email\nbusiness@pglaf.org.  Email contact links and up to date contact\ninformation can be found at the Foundation's web site and official\npage at http://pglaf.org\n\nFor additional contact information:\n     Dr. Gregory B. Newby\n     Chief Executive and Director\n     gbnewby@pglaf.org\n\n\nSection 4.  Information about Donations to the Project Gutenberg\nLiterary Archive Foundation\n\nProject Gutenberg-tm depends upon and cannot survive without wide\nspread public support and donations to carry out its mission of\nincreasing the number of public domain and licensed works that can be\nfreely distributed in machine readable form accessible by the widest\narray of equipment including outdated equipment.  Many small donations\n($1 to $5,000) are particularly important to maintaining tax exempt\nstatus with the IRS.\n\nThe Foundation is committed to complying with the laws regulating\ncharities and charitable donations in all 50 states of the United\nStates.  Compliance requirements are not uniform and it takes a\nconsiderable effort, much paperwork and many fees to meet and keep up\nwith these requirements.  We do not solicit donations in locations\nwhere we have not received written confirmation of compliance.  To\nSEND DONATIONS or determine the status of compliance for any\nparticular state visit http://pglaf.org\n\nWhile we cannot and do not solicit contributions from states where we\nhave not met the solicitation requirements, we know of no prohibition\nagainst accepting unsolicited donations from donors in such states who\napproach us with offers to donate.\n\nInternational donations are gratefully accepted, but we cannot make\nany statements concerning tax treatment of donations received from\noutside the United States.  U.S. laws alone swamp our small staff.\n\nPlease check the Project Gutenberg Web pages for current donation\nmethods and addresses.  Donations are accepted in a number of other\nways including including checks, online payments and credit card\ndonations.  To donate, please visit: http://pglaf.org/donate\n\n\nSection 5.  General Information About Project Gutenberg-tm electronic\nworks.\n\nProfessor Michael S. Hart is the originator of the Project Gutenberg-tm\nconcept of a library of electronic works that could be freely shared\nwith anyone.  For thirty years, he produced and distributed Project\nGutenberg-tm eBooks with only a loose network of volunteer support.\n\n\nProject Gutenberg-tm eBooks are often created from several printed\neditions, all of which are confirmed as Public Domain in the U.S.\nunless a copyright notice is included.  Thus, we do not necessarily\nkeep eBooks in compliance with any particular paper edition.\n\n\nMost people start at our Web site which has the main PG search facility:\n\n     http://www.gutenberg.net\n\nThis Web site includes information about Project Gutenberg-tm,\nincluding how to make donations to the Project Gutenberg Literary\nArchive Foundation, how to help produce our new eBooks, and how to\nsubscribe to our email newsletter to hear about new eBooks.\n"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/test-folder/ipfs-add.js",
    "content": "#!/usr/bin/env node\n\n'use strict'\n\nconst ipfs = require('../src')('localhost', 5001)\nconst files = process.argv.slice(2)\n\nipfs.add(files, { recursive: true }, function (err, res) {\n  if (err || !res) return console.log(err)\n\n  for (let i = 0; i < res.length; i++) {\n    console.log('added', res[i].Hash, res[i].Name)\n  }\n})\n"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/test-folder/jungle.txt",
    "content": "Mowgli's Brothers\n\n     Now Rann the Kite brings home the night\n        That Mang the Bat sets free--\n     The herds are shut in byre and hut\n        For loosed till dawn are we.\n     This is the hour of pride and power,\n        Talon and tush and claw.\n     Oh, hear the call!--Good hunting all\n        That keep the Jungle Law!\n     Night-Song in the Jungle\n\nIt was seven o'clock of a very warm evening in the Seeonee hills when\nFather Wolf woke up from his day's rest, scratched himself, yawned, and\nspread out his paws one after the other to get rid of the sleepy feeling\nin their tips. Mother Wolf lay with her big gray nose dropped across her\nfour tumbling, squealing cubs, and the moon shone into the mouth of the\ncave where they all lived. \"Augrh!\" said Father Wolf. \"It is time to\nhunt again.\" He was going to spring down hill when a little shadow with\na bushy tail crossed the threshold and whined: \"Good luck go with you, O\nChief of the Wolves. And good luck and strong white teeth go with noble\nchildren that they may never forget the hungry in this world.\"\n\nIt was the jackal--Tabaqui, the Dish-licker--and the wolves of India\ndespise Tabaqui because he runs about making mischief, and telling\ntales, and eating rags and pieces of leather from the village\nrubbish-heaps. But they are afraid of him too, because Tabaqui, more\nthan anyone else in the jungle, is apt to go mad, and then he forgets\nthat he was ever afraid of anyone, and runs through the forest biting\neverything in his way. Even the tiger runs and hides when little Tabaqui\ngoes mad, for madness is the most disgraceful thing that can overtake\na wild creature. We call it hydrophobia, but they call it dewanee--the\nmadness--and run.\n\n\"Enter, then, and look,\" said Father Wolf stiffly, \"but there is no food\nhere.\"\n\n\"For a wolf, no,\" said Tabaqui, \"but for so mean a person as myself a\ndry bone is a good feast. Who are we, the Gidur-log [the jackal people],\nto pick and choose?\" He scuttled to the back of the cave, where he\nfound the bone of a buck with some meat on it, and sat cracking the end\nmerrily.\n\n\"All thanks for this good meal,\" he said, licking his lips. \"How\nbeautiful are the noble children! How large are their eyes! And so young\ntoo! Indeed, indeed, I might have remembered that the children of kings\n"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/test-folder/pp.txt",
    "content": "PRIDE AND PREJUDICE\n\nBy Jane Austen\n\n\n\nChapter 1\n\n\nIt is a truth universally acknowledged, that a single man in possession\nof a good fortune, must be in want of a wife.\n\nHowever little known the feelings or views of such a man may be on his\nfirst entering a neighbourhood, this truth is so well fixed in the minds\nof the surrounding families, that he is considered the rightful property\nof some one or other of their daughters.\n\n\"My dear Mr. Bennet,\" said his lady to him one day, \"have you heard that\nNetherfield Park is let at last?\"\n\nMr. Bennet replied that he had not.\n\n\"But it is,\" returned she; \"for Mrs. Long has just been here, and she\ntold me all about it.\"\n\nMr. Bennet made no answer.\n\n\"Do you not want to know who has taken it?\" cried his wife impatiently.\n\n\"_You_ want to tell me, and I have no objection to hearing it.\"\n\nThis was invitation enough.\n\n\"Why, my dear, you must know, Mrs. Long says that Netherfield is taken\nby a young man of large fortune from the north of England; that he came\ndown on Monday in a chaise and four to see the place, and was so much\ndelighted with it, that he agreed with Mr. Morris immediately; that he\nis to take possession before Michaelmas, and some of his servants are to\nbe in the house by the end of next week.\"\n\n\"What is his name?\"\n\n\"Bingley.\"\n\n\"Is he married or single?\"\n\n\"Oh! Single, my dear, to be sure! A single man of large fortune; four or\nfive thousand a year. What a fine thing for our girls!\"\n\n\"How so? How can it affect them?\"\n\n\"My dear Mr. Bennet,\" replied his wife, \"how can you be so tiresome! You\nmust know that I am thinking of his marrying one of them.\"\n\n\"Is that his design in settling here?\"\n\n\"Design! Nonsense, how can you talk so! But it is very likely that he\n_may_ fall in love with one of them, and therefore you must visit him as\nsoon as he comes.\"\n\n\"I see no occasion for that. You and the girls may go, or you may send\nthem by themselves, which perhaps will be still better, for as you are\nas handsome as any of them, Mr. Bingley may like you the best of the\nparty.\"\n\n\"My dear, you flatter me. I certainly _have_ had my share of beauty, but\nI do not pretend to be anything extraordinary now. When a woman has five\ngrown-up daughters, she ought to give over thinking of her own beauty.\"\n\n\"In such cases, a woman has not often much beauty to think of.\"\n\n\"But, my dear, you must indeed go and see Mr. Bingley when he comes into\nthe neighbourhood.\"\n\n\"It is more than I engage for, I assure you.\"\n\n\"But consider your daughters. Only think what an establishment it would\nbe for one of them. Sir William and Lady Lucas are determined to\ngo, merely on that account, for in general, you know, they visit no\nnewcomers. Indeed you must go, for it will be impossible for _us_ to\nvisit him if you do not.\"\n\n\"You are over-scrupulous, surely. I dare say Mr. Bingley will be very\nglad to see you; and I will send a few lines by you to assure him of my\nhearty consent to his marrying whichever he chooses of the girls; though\nI must throw in a good word for my little Lizzy.\"\n\n\"I desire you will do no such thing. Lizzy is not a bit better than the\nothers; and I am sure she is not half so handsome as Jane, nor half so\ngood-humoured as Lydia. But you are always giving _her_ the preference.\"\n\n\"They have none of them much to recommend them,\" replied he; \"they are\nall silly and ignorant like other girls; but Lizzy has something more of\nquickness than her sisters.\"\n\n\"Mr. Bennet, how _can_ you abuse your own children in such a way? You\ntake delight in vexing me. You have no compassion for my poor nerves.\"\n\n\"You mistake me, my dear. I have a high respect for your nerves. They\nare my old friends. I have heard you mention them with consideration\nthese last twenty years at least.\"\n\n\"Ah, you do not know what I suffer.\"\n\n\"But I hope you will get over it, and live to see many young men of four\nthousand a year come into the neighbourhood.\"\n\n\"It will be no use to us, if twenty such should come, since you will not\nvisit them.\"\n\n\"Depend upon it, my dear, that when there are twenty, I will visit them\nall.\"\n\nMr. Bennet was so odd a mixture of quick parts, sarcastic humour,\nreserve, and caprice, that the experience of three-and-twenty years had\nbeen insufficient to make his wife understand his character. _Her_ mind\nwas less difficult to develop. She was a woman of mean understanding,\nlittle information, and uncertain temper. When she was discontented,\nshe fancied herself nervous. The business of her life was to get her\ndaughters married; its solace was visiting and news.\n"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/weird name folder [v0]/add",
    "content": "\n\nconst ipfs = require('../src')('localhost', 5001)\n\nconst f1 = 'Hello'\nconst f2 = 'World'\n\nipfs.add([Uint8Array.from(f1), Uint8Array,from(f2)], function (err, res) {\n  if (err || !res) return console.log(err)\n\n  for (let i = 0; i < res.length; i++) {\n    console.log(res[i])\n  }\n})\n\nipfs.add(['./files/hello.txt', './files/ipfs.txt'], function (err, res) {\n  if (err || !res) return console.log(err)\n\n  for (let i = 0; i < res.length; i++) {\n    console.log(res[i])\n  }\n})\n"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/weird name folder [v0]/cat",
    "content": "\n\nconst ipfs = require('../src')('localhost', 5001)\n\nconst hash = [\n  'QmdFyxZXsFiP4csgfM5uPu99AvFiKH62CSPDw5TP92nr7w',\n  'QmY9cxiHqTFoWamkQVkpmmqzBrY3hCBEL2XNu3NtX74Fuu'\n]\n\nipfs.cat(hash, function (err, res) {\n  if (err || !res) return console.log(err)\n\n  if (res.readable) {\n    res.pipe(process.stdout)\n  } else {\n    console.log(res)\n  }\n})\n"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/weird name folder [v0]/files/hello.txt",
    "content": "Hello\n"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/weird name folder [v0]/files/ipfs.txt",
    "content": "IPFS\n"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/weird name folder [v0]/hello-link",
    "content": "Hello\n"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/weird name folder [v0]/ipfs-add",
    "content": "#!/usr/bin/env node\n\n\n\nconst ipfs = require('../src')('localhost', 5001)\nconst files = process.argv.slice(2)\n\nipfs.add(files, {recursive: true}, function (err, res) {\n  if (err || !res) return console.log(err)\n\n  for (let i = 0; i < res.length; i++) {\n    console.log('added', res[i].Hash, res[i].Name)\n  }\n})\n"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/weird name folder [v0]/ls",
    "content": "\n\nconst ipfs = require('../src')('localhost', 5001)\n\nconst hash = ['QmdbHK6gMiecyjjSoPnfJg6iKMF7v6E2NkoBgGpmyCoevh']\n\nipfs.ls(hash, function (err, res) {\n  if (err || !res) return console.log(err)\n\n  res.Objects.forEach(function (node) {\n    console.log(node.Hash)\n\n    console.log('Links [%d]', node.Links.length)\n    node.Links.forEach(function (link, i) {\n      console.log('[%d]', i, link)\n    })\n  })\n})\n"
  },
  {
    "path": "packages/interface-ipfs-core/test/fixtures/weird name folder [v0]/version",
    "content": "\n\nconst ipfs = require('../src')('localhost', 5001)\n\nipfs.commands(function (err, res) {\n  if (err) throw err\n  console.log(res)\n})\n"
  },
  {
    "path": "packages/interface-ipfs-core/test/interface.spec.js",
    "content": ""
  },
  {
    "path": "packages/interface-ipfs-core/tsconfig.json",
    "content": "{\n  \"extends\": \"aegir/src/config/tsconfig.aegir.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"emitDeclarationOnly\": true\n  },\n  \"include\": [\n    \"src\",\n    \"test\"\n  ],\n  \"exclude\": [\n    \"test/fixtures/*\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../ipfs-core-types\"\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/ipfs/.aegir.js",
    "content": "import getPort from 'aegir/get-port'\nimport { createServer } from 'ipfsd-ctl'\nimport EchoServer from 'aegir/echo-server'\nimport { sigServer } from '@libp2p/webrtc-star-signalling-server'\nimport path from 'path'\nimport { fileURLToPath } from 'url'\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url))\n\n/** @type {import('aegir').Options[\"build\"][\"config\"]} */\nconst esbuild = {\n  inject: [path.join(__dirname, '../../scripts/node-globals.js')]\n}\n\n/** @type {import('aegir').PartialOptions} */\nexport default {\n  test: {\n    browser: {\n      config: {\n        assets: '..',\n        buildConfig: esbuild\n      }\n    },\n    before: async (options) => {\n      const MockPreloadNode = await import('./test/utils/mock-preload-node.js')\n      const { PinningService } = await import('./test/utils/mock-pinning-service.js')\n\n      const echoServer = new EchoServer()\n      const preloadNode = MockPreloadNode.createNode()\n      const pinningService = await PinningService.start()\n\n      await preloadNode.start()\n      await echoServer.start()\n\n      if (options.runner !== 'node') {\n        const ipfsClient = await import('ipfs-client')\n\n        const ipfsdPort = await getPort()\n        const signalAPort = await getPort()\n        const signalBPort = await getPort()\n        const sigServerA = await sigServer({\n          host: '127.0.0.1',\n          port: signalAPort,\n          metrics: false\n        })\n        // the second signalling server is needed for the interface test 'should list peers only once even if they have multiple addresses'\n        const sigServerB = await sigServer({\n          host: '127.0.0.1',\n          port: signalBPort,\n          metrics: false\n        })\n        const ipfsdServer = await createServer({\n          host: '127.0.0.1',\n          port: ipfsdPort\n        }, {\n          type: 'js',\n          ipfsModule: await import(path.join(__dirname, 'src', 'index.js')),\n          ipfsHttpModule: await import('ipfs-http-client'),\n          ipfsBin: path.join(__dirname, 'src', 'cli.js'),\n          ipfsOptions: {\n            libp2p: {\n              dialer: {\n                dialTimeout: 60e3 // increase timeout because travis is slow\n              }\n            }\n          }\n        }, {\n          go: {\n            ipfsBin: (await import('go-ipfs')).default.path()\n          },\n          js: {\n            ipfsClientModule: {\n              create: ipfsClient.create\n            }\n          }\n        }).start()\n        return {\n          env: {\n            PINNING_SERVICE_ENDPOINT: pinningService.endpoint,\n            PINNING_SERVICE_KEY: pinningService.token,\n            ECHO_SERVER: `http://${echoServer.host}:${echoServer.port}`,\n            IPFSD_SERVER: `http://127.0.0.1:${ipfsdPort}`,\n            SIGNALA_SERVER: `/ip4/127.0.0.1/tcp/${signalAPort}/ws/p2p-webrtc-star`,\n            SIGNALB_SERVER: `/ip4/127.0.0.1/tcp/${signalBPort}/ws/p2p-webrtc-star`\n          },\n          echoServer,\n          preloadNode,\n          pinningService,\n          ipfsdServer,\n          sigServerA,\n          sigServerB\n        }\n      }\n      return {\n        env: {\n          PINNING_SERVICE_ENDPOINT: pinningService.endpoint,\n          PINNING_SERVICE_KEY: pinningService.token,\n          ECHO_SERVER: `http://${echoServer.host}:${echoServer.port}`\n        },\n        echoServer,\n        preloadNode,\n        pinningService\n      }\n    },\n    after: async (options, beforeResult) => {\n      const { PinningService } = await import('./test/utils/mock-pinning-service.js')\n\n      await beforeResult.echoServer.stop()\n      await beforeResult.preloadNode.stop()\n      await PinningService.stop(beforeResult.pinningService)\n\n      if (options.runner !== 'node') {\n        await beforeResult.ipfsdServer.stop()\n        await beforeResult.sigServerA.stop()\n        await beforeResult.sigServerB.stop()\n      }\n    }\n  },\n  build: {\n    bundlesizeMax: '477KB',\n    config: esbuild\n  },\n  dependencyCheck: {\n    ignore: [\n      'assert',\n      'cross-env',\n      'rimraf',\n      'url',\n      'wrtc',\n      'electron-webrtc',\n      'ipfs-interop'\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/ipfs/CHANGELOG.md",
    "content": "# Change Log\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n\n### [0.66.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-v0.66.0...ipfs-v0.66.1) (2023-05-25)\n\n\n### Bug Fixes\n\n* add deprecation notice to readmes ([#4362](https://www.github.com/ipfs/js-ipfs/issues/4362)) ([7b79c1b](https://www.github.com/ipfs/js-ipfs/commit/7b79c1b8df5c818dc124b346ea28330455732d5c))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-cli bumped from ^0.16.0 to ^0.16.1\n    * ipfs-core bumped from ^0.18.0 to ^0.18.1\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.158.0 to ^0.158.1\n    * ipfs-client bumped from ^0.10.0 to ^0.10.1\n    * ipfs-core-types bumped from ^0.14.0 to ^0.14.1\n    * ipfs-http-client bumped from ^60.0.0 to ^60.0.1\n\n## [0.66.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-v0.65.0...ipfs-v0.66.0) (2023-01-11)\n\n\n### ⚠ BREAKING CHANGES\n\n* update multiformats to v11.x.x and related depenendcies (#4277)\n\n### Bug Fixes\n\n* update multiformats to v11.x.x and related depenendcies ([#4277](https://www.github.com/ipfs/js-ipfs/issues/4277)) ([521c84a](https://www.github.com/ipfs/js-ipfs/commit/521c84a958b04d61702577a5adce28519c1b2a3b))\n* use aegir to publish RCs ([#4284](https://www.github.com/ipfs/js-ipfs/issues/4284)) ([6d90cbf](https://www.github.com/ipfs/js-ipfs/commit/6d90cbf321a7dbf4b1084ba20f0c514dc08d8d0a))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-cli bumped from ^0.15.0 to ^0.16.0\n    * ipfs-core bumped from ^0.17.0 to ^0.18.0\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.157.0 to ^0.158.0\n    * ipfs-client bumped from ^0.9.2 to ^0.10.0\n    * ipfs-core-types bumped from ^0.13.0 to ^0.14.0\n    * ipfs-http-client bumped from ^59.0.0 to ^60.0.0\n\n## [0.65.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-v0.64.2...ipfs-v0.65.0) (2022-10-24)\n\n\n### ⚠ BREAKING CHANGES\n\n* ipfs is now bundled with libp2p@0.40.x which has different config\n\n### Features\n\n* upgrade libp2p to 0.40.x ([#4237](https://www.github.com/ipfs/js-ipfs/issues/4237)) ([0cee4a4](https://www.github.com/ipfs/js-ipfs/commit/0cee4a4c55767022584dcbade0b0b9b43326f9c9))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-cli bumped from ^0.14.2 to ^0.15.0\n    * ipfs-core bumped from ^0.16.1 to ^0.17.0\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.156.1 to ^0.157.0\n    * ipfs-client bumped from ^0.9.1 to ^0.9.2\n    * ipfs-core-types bumped from ^0.12.1 to ^0.13.0\n    * ipfs-http-client bumped from ^58.0.1 to ^59.0.0\n\n### [0.64.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-v0.64.1...ipfs-v0.64.2) (2022-09-21)\n\n\n### Bug Fixes\n\n* update @multiformats/multiadd to 11.0.0 ([2a830bf](https://www.github.com/ipfs/js-ipfs/commit/2a830bf58a5929fcce51dede871c99f62192fbda))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-cli bumped from ^0.14.1 to ^0.14.2\n    * ipfs-core bumped from ^0.16.0 to ^0.16.1\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.156.0 to ^0.156.1\n    * ipfs-client bumped from ^0.9.0 to ^0.9.1\n    * ipfs-core-types bumped from ^0.12.0 to ^0.12.1\n    * ipfs-http-client bumped from ^58.0.0 to ^58.0.1\n\n### [0.64.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-v0.64.0...ipfs-v0.64.1) (2022-09-16)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-cli bumped from ^0.14.0 to ^0.14.1\n\n## [0.64.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-v0.63.5...ipfs-v0.64.0) (2022-09-06)\n\n\n### ⚠ BREAKING CHANGES\n\n* update to libp2p@0.38.x (#4151)\n\n### deps\n\n* update to libp2p@0.38.x ([#4151](https://www.github.com/ipfs/js-ipfs/issues/4151)) ([39dbf70](https://www.github.com/ipfs/js-ipfs/commit/39dbf708ec31b263115e44f420651fa4e056a89e))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-cli bumped from ^0.13.0 to ^0.14.0\n    * ipfs-core bumped from ^0.15.0 to ^0.16.0\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.155.0 to ^0.156.0\n    * ipfs-client bumped from ^0.8.0 to ^0.9.0\n    * ipfs-core-types bumped from ^0.11.0 to ^0.12.0\n    * ipfs-http-client bumped from ^57.0.0 to ^58.0.0\n\n### [0.63.5](https://www.github.com/ipfs/js-ipfs/compare/ipfs-v0.63.4...ipfs-v0.63.5) (2022-06-24)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-cli bumped from ^0.13.4 to ^0.13.5\n    * ipfs-core bumped from ^0.15.3 to ^0.15.4\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.155.1 to ^0.155.2\n    * ipfs-client bumped from ^0.8.2 to ^0.8.3\n    * ipfs-http-client bumped from ^57.0.2 to ^57.0.3\n\n### [0.63.4](https://www.github.com/ipfs/js-ipfs/compare/ipfs-v0.63.3...ipfs-v0.63.4) (2022-06-22)\n\n\n### Bug Fixes\n\n* use default ws filters instead of connecting to everything ([#4142](https://www.github.com/ipfs/js-ipfs/issues/4142)) ([7be50bd](https://www.github.com/ipfs/js-ipfs/commit/7be50bd157b984d4607545bb78d22cd33de933fa)), closes [#4141](https://www.github.com/ipfs/js-ipfs/issues/4141)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-cli bumped from ^0.13.3 to ^0.13.4\n    * ipfs-core bumped from ^0.15.2 to ^0.15.3\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.155.0 to ^0.155.1\n    * ipfs-client bumped from ^0.8.1 to ^0.8.2\n    * ipfs-core-types bumped from ^0.11.0 to ^0.11.1\n    * ipfs-http-client bumped from ^57.0.1 to ^57.0.2\n\n### [0.63.3](https://www.github.com/ipfs/js-ipfs/compare/ipfs-v0.63.2...ipfs-v0.63.3) (2022-06-13)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-cli bumped from ^0.13.2 to ^0.13.3\n    * ipfs-core bumped from ^0.15.1 to ^0.15.2\n\n## [0.63.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-v0.63.1...ipfs-v0.63.2) (2022-06-01)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-cli bumped from ^0.13.1 to ^0.13.2\n    * ipfs-core bumped from ^0.15.0 to ^0.15.1\n  * devDependencies\n    * ipfs-client bumped from ^0.8.0 to ^0.8.1\n    * ipfs-http-client bumped from ^57.0.0 to ^57.0.1\n\n## [0.63.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-v0.63.0...ipfs-v0.63.1) (2022-05-30)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-cli bumped from ^0.13.0 to ^0.13.1\n\n## [0.63.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-v0.62.3...ipfs-v0.63.0) (2022-05-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* This module is now ESM only and there return types of some methods have changed.  See the [upgrade guide](https://github.com/ipfs/js-ipfs/blob/master/docs/upgrading/v0.62-v0.63.md) for more info.\n\n### Features\n\n* update to libp2p 0.37.x ([#4092](https://www.github.com/ipfs/js-ipfs/issues/4092)) ([74aee8b](https://www.github.com/ipfs/js-ipfs/commit/74aee8b3d78f233c3199a3e9a6c0ac628a31a433))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-cli bumped from ^0.12.3 to ^0.13.0\n    * ipfs-core bumped from ^0.14.3 to ^0.15.0\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.154.2 to ^0.155.0\n    * ipfs-client bumped from ^0.7.8 to ^0.8.0\n    * ipfs-core-types bumped from ^0.10.3 to ^0.11.0\n    * ipfs-http-client bumped from ^56.0.3 to ^57.0.0\n\n### [0.62.3](https://www.github.com/ipfs/js-ipfs/compare/ipfs-v0.62.2...ipfs-v0.62.3) (2022-04-20)\n\n\n### Bug Fixes\n\n* exclude fs from bundle ([#4076](https://www.github.com/ipfs/js-ipfs/issues/4076)) ([6c3cb73](https://www.github.com/ipfs/js-ipfs/commit/6c3cb73db7b46211c88431273f61f04463a4f80d))\n* upgrade dep of ipfs-utils ^9.0.2->^9.0.6 ([#4086](https://www.github.com/ipfs/js-ipfs/issues/4086)) ([8f7ce23](https://www.github.com/ipfs/js-ipfs/commit/8f7ce23c18be12bdc52b98bfccbd0a5a2a9c9f7e)), closes [#4080](https://www.github.com/ipfs/js-ipfs/issues/4080)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-cli bumped from ^0.12.2 to ^0.12.3\n    * ipfs-core bumped from ^0.14.2 to ^0.14.3\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.154.2 to ^0.154.3\n    * ipfs-client bumped from ^0.7.8 to ^0.7.9\n    * ipfs-core-types bumped from ^0.10.2 to ^0.10.3\n    * ipfs-http-client bumped from ^56.0.2 to ^56.0.3\n\n### [0.62.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-v0.62.1...ipfs-v0.62.2) (2022-03-01)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-cli bumped from ^0.12.1 to ^0.12.2\n    * ipfs-core bumped from ^0.14.1 to ^0.14.2\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.154.1 to ^0.154.2\n    * ipfs-client bumped from ^0.7.7 to ^0.7.8\n    * ipfs-core-types bumped from ^0.10.1 to ^0.10.2\n    * ipfs-http-client bumped from ^56.0.1 to ^56.0.2\n\n### [0.62.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-v0.62.0...ipfs-v0.62.1) (2022-02-06)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-cli bumped from ^0.12.0 to ^0.12.1\n    * ipfs-core bumped from ^0.14.0 to ^0.14.1\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.154.0 to ^0.154.1\n    * ipfs-client bumped from ^0.7.6 to ^0.7.7\n    * ipfs-core-types bumped from ^0.10.0 to ^0.10.1\n    * ipfs-http-client bumped from ^56.0.0 to ^56.0.1\n\n## [0.62.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-v0.61.0...ipfs-v0.62.0) (2022-01-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* peerstore methods are now all async, the repo is migrated to v12\n\n### Features\n\n* libp2p async peerstore ([#4018](https://www.github.com/ipfs/js-ipfs/issues/4018)) ([a6b201a](https://www.github.com/ipfs/js-ipfs/commit/a6b201af2c3697430ab0ebe002dd573d185f1ac0))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-cli bumped from ^0.11.0 to ^0.12.0\n    * ipfs-core bumped from ^0.13.0 to ^0.14.0\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.153.0 to ^0.154.0\n    * ipfs-client bumped from ^0.7.5 to ^0.7.6\n    * ipfs-core-types bumped from ^0.9.0 to ^0.10.0\n    * ipfs-http-client bumped from ^55.0.0 to ^56.0.0\n\n## [0.61.0](https://github.com/ipfs/js-ipfs/compare/ipfs@0.60.2...ipfs@0.61.0) (2021-12-15)\n\n\n### Bug Fixes\n\n* **pubsub:** multibase in pubsub http rpc ([#3922](https://github.com/ipfs/js-ipfs/issues/3922)) ([6eeaca4](https://github.com/ipfs/js-ipfs/commit/6eeaca452c36fa13be42d704575c577e4ca938f1))\n\n\n### Features\n\n* dht client ([#3947](https://github.com/ipfs/js-ipfs/issues/3947)) ([62d8ecb](https://github.com/ipfs/js-ipfs/commit/62d8ecbc723e693a2544e69172d99c576d187c23))\n* update DAG API to match go-ipfs@0.10 changes ([#3917](https://github.com/ipfs/js-ipfs/issues/3917)) ([38c01be](https://github.com/ipfs/js-ipfs/commit/38c01be03b4fd5f401cd9b698cfdb4237d835b01))\n\n\n### BREAKING CHANGES\n\n* **pubsub:** We had to make breaking changes to `pubsub` commands sent over HTTP RPC  to fix data corruption caused by topic names and payload bytes that included `\\n`. More details in https://github.com/ipfs/go-ipfs/issues/7939 and https://github.com/ipfs/go-ipfs/pull/8183\n* `ipfs.dag.put` no longer accepts a `format` arg, it is now `storeCodec` and `inputCodec`.  `'json'` has become `'dag-json'`, `'cbor'` has become `'dag-cbor'` and so on\n* The DHT API has been refactored to return async iterators of query events\n\n## [0.60.2](https://github.com/ipfs/js-ipfs/compare/ipfs@0.60.1...ipfs@0.60.2) (2021-11-24)\n\n**Note:** Version bump only for package ipfs\n\n\n\n\n\n## [0.60.1](https://github.com/ipfs/js-ipfs/compare/ipfs@0.60.0...ipfs@0.60.1) (2021-11-19)\n\n**Note:** Version bump only for package ipfs\n\n\n\n\n\n## [0.60.0](https://github.com/ipfs/js-ipfs/compare/ipfs@0.59.1...ipfs@0.60.0) (2021-11-12)\n\n\n### Bug Fixes\n\n* do not accept single items for ipfs.add ([#3900](https://github.com/ipfs/js-ipfs/issues/3900)) ([04e3cf3](https://github.com/ipfs/js-ipfs/commit/04e3cf3f46b585c4644cba70516f375e95361f52))\n\n\n### BREAKING CHANGES\n\n* errors will now be thrown if multiple items are passed to `ipfs.add` or single items to `ipfs.addAll` (n.b. you can still pass a list of a single item to `ipfs.addAll`)\n\n\n\n\n\n## [0.59.1](https://github.com/ipfs/js-ipfs/compare/ipfs@0.59.0...ipfs@0.59.1) (2021-09-28)\n\n**Note:** Version bump only for package ipfs\n\n\n\n\n\n## [0.59.0](https://github.com/ipfs/js-ipfs/compare/ipfs@0.58.6...ipfs@0.59.0) (2021-09-24)\n\n\n### Features\n\n* pull in new globSource ([#3889](https://github.com/ipfs/js-ipfs/issues/3889)) ([be4a542](https://github.com/ipfs/js-ipfs/commit/be4a5428ebc4b05a2edd9a91bf9df6416c1a8c2b))\n* switch to esm ([#3879](https://github.com/ipfs/js-ipfs/issues/3879)) ([9a40109](https://github.com/ipfs/js-ipfs/commit/9a40109632e5b4837eb77a2f57dbc77fbf1fe099))\n\n\n### BREAKING CHANGES\n\n* the globSource api has changed from `globSource(dir, opts)` to `globSource(dir, pattern, opts)`\n* There are no default exports and everything is now dual published as ESM/CJS\n\n\n\n\n\n## [0.58.6](https://github.com/ipfs/js-ipfs/compare/ipfs@0.58.5...ipfs@0.58.6) (2021-09-17)\n\n**Note:** Version bump only for package ipfs\n\n\n\n\n\n## [0.58.5](https://github.com/ipfs/js-ipfs/compare/ipfs@0.58.4...ipfs@0.58.5) (2021-09-17)\n\n**Note:** Version bump only for package ipfs\n\n\n\n\n\n## [0.58.4](https://github.com/ipfs/js-ipfs/compare/ipfs@0.58.3...ipfs@0.58.4) (2021-09-08)\n\n**Note:** Version bump only for package ipfs\n\n\n\n\n\n## [0.58.3](https://github.com/ipfs/js-ipfs/compare/ipfs@0.58.2...ipfs@0.58.3) (2021-09-02)\n\n\n### Bug Fixes\n\n* remove use of instanceof for CID class ([#3847](https://github.com/ipfs/js-ipfs/issues/3847)) ([ebbb12d](https://github.com/ipfs/js-ipfs/commit/ebbb12db523c53ce8e4ddae5266cd9acb3504431))\n\n\n\n\n\n## [0.58.2](https://github.com/ipfs/js-ipfs/compare/ipfs@0.58.1...ipfs@0.58.2) (2021-08-25)\n\n**Note:** Version bump only for package ipfs\n\n\n\n\n\n## [0.58.1](https://github.com/ipfs/js-ipfs/compare/ipfs@0.58.0...ipfs@0.58.1) (2021-08-17)\n\n**Note:** Version bump only for package ipfs\n\n\n\n\n\n## [0.58.0](https://github.com/ipfs/js-ipfs/compare/ipfs@0.57.0...ipfs@0.58.0) (2021-08-17)\n\n\n### Features\n\n* pubsub over gRPC ([#3813](https://github.com/ipfs/js-ipfs/issues/3813)) ([e7d5509](https://github.com/ipfs/js-ipfs/commit/e7d5509c87e87aed6be3c1d0b2a01ab74cdc1ed9)), closes [#3741](https://github.com/ipfs/js-ipfs/issues/3741)\n\n\n\n\n\n## [0.57.0](https://github.com/ipfs/js-ipfs/compare/ipfs@0.56.1...ipfs@0.57.0) (2021-08-11)\n\n\n### Features\n\n* make ipfs.get output tarballs ([#3785](https://github.com/ipfs/js-ipfs/issues/3785)) ([1ad6001](https://github.com/ipfs/js-ipfs/commit/1ad60018d39d5b46c484756631e30e1989fd8eba))\n\n\n### BREAKING CHANGES\n\n* the output type of `ipfs.get` has changed and the `recursive` option has been removed from `ipfs.ls` since it was not supported everywhere\n\n\n\n\n\n## [0.56.1](https://github.com/ipfs/js-ipfs/compare/ipfs@0.56.0...ipfs@0.56.1) (2021-07-30)\n\n**Note:** Version bump only for package ipfs\n\n\n\n\n\n## [0.56.0](https://github.com/ipfs/js-ipfs/compare/ipfs@0.55.4...ipfs@0.56.0) (2021-07-27)\n\n\n### Features\n\n* upgrade to the new multiformats ([#3556](https://github.com/ipfs/js-ipfs/issues/3556)) ([d13d15f](https://github.com/ipfs/js-ipfs/commit/d13d15f022a87d04a35f0f7822142f9cb898479c))\n\n\n### BREAKING CHANGES\n\n* ipld-formats no longer supported, use multiformat BlockCodecs instead\n\nCo-authored-by: Rod Vagg <rod@vagg.org>\nCo-authored-by: achingbrain <alex@achingbrain.net>\n\n\n\n\n\n## [0.55.4](https://github.com/ipfs/js-ipfs/compare/ipfs@0.55.3...ipfs@0.55.4) (2021-06-18)\n\n**Note:** Version bump only for package ipfs\n\n\n\n\n\n## [0.55.3](https://github.com/ipfs/js-ipfs/compare/ipfs@0.55.2...ipfs@0.55.3) (2021-06-05)\n\n\n### Bug Fixes\n\n* stalling subscription on (node) http-client when daemon is stopped ([#3468](https://github.com/ipfs/js-ipfs/issues/3468)) ([0266abf](https://github.com/ipfs/js-ipfs/commit/0266abf0c4b817636172f78c6e91eb4dd5aad451)), closes [#3465](https://github.com/ipfs/js-ipfs/issues/3465)\n\n\n\n\n\n## [0.55.2](https://github.com/ipfs/js-ipfs/compare/ipfs@0.55.1...ipfs@0.55.2) (2021-05-26)\n\n**Note:** Version bump only for package ipfs\n\n\n\n\n\n## [0.55.1](https://github.com/ipfs/js-ipfs/compare/ipfs@0.55.0...ipfs@0.55.1) (2021-05-11)\n\n**Note:** Version bump only for package ipfs\n\n\n\n\n\n## [0.55.0](https://github.com/ipfs/js-ipfs/compare/ipfs@0.54.4...ipfs@0.55.0) (2021-05-10)\n\n\n### Bug Fixes\n\n* mark ipld options as partial ([#3669](https://github.com/ipfs/js-ipfs/issues/3669)) ([f98af8e](https://github.com/ipfs/js-ipfs/commit/f98af8ed24784929898bb5d33a64dc442c77074d))\n* only accept cid for ipfs.dag.get ([#3675](https://github.com/ipfs/js-ipfs/issues/3675)) ([bb8f8bc](https://github.com/ipfs/js-ipfs/commit/bb8f8bc501ffc1ee0f064ba61ec0bca4015bf6ad)), closes [#3637](https://github.com/ipfs/js-ipfs/issues/3637)\n\n\n### chore\n\n* update node version in docker build ([#3603](https://github.com/ipfs/js-ipfs/issues/3603)) ([087fd1e](https://github.com/ipfs/js-ipfs/commit/087fd1eb402d1b933730e09c1d0cfb21067e9992))\n* upgrade deps with new typedefs ([#3550](https://github.com/ipfs/js-ipfs/issues/3550)) ([a418a52](https://github.com/ipfs/js-ipfs/commit/a418a521574c878d7aabd0ad2fd8d516908a3756))\n\n\n### BREAKING CHANGES\n\n* Minimum supported node version is 14\n* all core api methods now have types, some method signatures have changed, named exports are now used by the http, grpc and ipfs client modules\n\n\n\n\n\n## [0.54.4](https://github.com/ipfs/js-ipfs/compare/ipfs@0.54.3...ipfs@0.54.4) (2021-03-10)\n\n**Note:** Version bump only for package ipfs\n\n\n\n\n\n## [0.54.3](https://github.com/ipfs/js-ipfs/compare/ipfs@0.54.2...ipfs@0.54.3) (2021-03-09)\n\n\n### Bug Fixes\n\n* update to new aegir ([#3528](https://github.com/ipfs/js-ipfs/issues/3528)) ([49f7880](https://github.com/ipfs/js-ipfs/commit/49f78807d7e26483bd926b45cc7e0f797d77e41b))\n\n\n\n\n\n## [0.54.2](https://github.com/ipfs/js-ipfs/compare/ipfs@0.54.1...ipfs@0.54.2) (2021-02-08)\n\n**Note:** Version bump only for package ipfs\n\n\n\n\n\n## [0.54.1](https://github.com/ipfs/js-ipfs/compare/ipfs@0.54.0...ipfs@0.54.1) (2021-02-02)\n\n**Note:** Version bump only for package ipfs\n\n\n\n\n\n## [0.54.0](https://github.com/ipfs/js-ipfs/compare/ipfs@0.53.2...ipfs@0.54.0) (2021-02-01)\n\n\n### Bug Fixes\n\n* updates webpack example to use v5 ([#3512](https://github.com/ipfs/js-ipfs/issues/3512)) ([c7110db](https://github.com/ipfs/js-ipfs/commit/c7110db71b5c0f0f9f415f31f91b5b228341e13e)), closes [#3511](https://github.com/ipfs/js-ipfs/issues/3511)\n\n\n### chore\n\n* update deps ([#3514](https://github.com/ipfs/js-ipfs/issues/3514)) ([061d77c](https://github.com/ipfs/js-ipfs/commit/061d77cc03f40af5a3bc3590481e1e5836e7f0d8))\n\n\n### Features\n\n* support  remote pinning services in ipfs-http-client ([#3293](https://github.com/ipfs/js-ipfs/issues/3293)) ([ba240fd](https://github.com/ipfs/js-ipfs/commit/ba240fdf93edc88028315483240d7822a7ca88ed))\n\n\n### BREAKING CHANGES\n\n* ipfs-repo upgrade requires repo migration to v10\n\n\n\n\n\n## [0.53.2](https://github.com/ipfs/js-ipfs/compare/ipfs@0.53.1...ipfs@0.53.2) (2021-01-22)\n\n**Note:** Version bump only for package ipfs\n\n\n\n\n\n## [0.53.1](https://github.com/ipfs/js-ipfs/compare/ipfs@0.53.0...ipfs@0.53.1) (2021-01-20)\n\n**Note:** Version bump only for package ipfs\n\n\n\n\n\n## [0.53.0](https://github.com/ipfs/js-ipfs/compare/ipfs@0.52.3...ipfs@0.53.0) (2021-01-15)\n\n\n### chore\n\n* update libp2p to 0.30 ([#3427](https://github.com/ipfs/js-ipfs/issues/3427)) ([a39e6fb](https://github.com/ipfs/js-ipfs/commit/a39e6fb372bf9e7782462b6a4b7530a3f8c9b3f1))\n\n\n### Features\n\n* add grpc server and client ([#3403](https://github.com/ipfs/js-ipfs/issues/3403)) ([a9027e0](https://github.com/ipfs/js-ipfs/commit/a9027e0ec0cea9a4f34b4f2f52e09abb35237384)), closes [#2519](https://github.com/ipfs/js-ipfs/issues/2519) [#2838](https://github.com/ipfs/js-ipfs/issues/2838) [#2943](https://github.com/ipfs/js-ipfs/issues/2943) [#2854](https://github.com/ipfs/js-ipfs/issues/2854) [#2864](https://github.com/ipfs/js-ipfs/issues/2864)\n\n\n### BREAKING CHANGES\n\n* The websocket transport will only dial DNS+WSS addresses - see https://github.com/libp2p/js-libp2p-websockets/releases/tag/v0.15.0\n\nCo-authored-by: Hugo Dias <hugomrdias@gmail.com>\n\n\n\n\n\n## [0.52.3](https://github.com/ipfs/js-ipfs/compare/ipfs@0.52.2...ipfs@0.52.3) (2020-12-16)\n\n\n### Bug Fixes\n\n* export IPFS type ([#3447](https://github.com/ipfs/js-ipfs/issues/3447)) ([cacbfc6](https://github.com/ipfs/js-ipfs/commit/cacbfc6e87eabee0e2a6df2056ac5cc993690a0d)), closes [#3439](https://github.com/ipfs/js-ipfs/issues/3439)\n* fix ipfs.ls() for a single file object ([#3440](https://github.com/ipfs/js-ipfs/issues/3440)) ([f243dd1](https://github.com/ipfs/js-ipfs/commit/f243dd1c37fcb9786d77d129cd9b238457d18a15))\n\n\n\n\n\n## [0.52.2](https://github.com/ipfs/js-ipfs/compare/ipfs@0.52.1...ipfs@0.52.2) (2020-11-25)\n\n**Note:** Version bump only for package ipfs\n\n\n\n\n\n## [0.52.1](https://github.com/ipfs/js-ipfs/compare/ipfs@0.52.0...ipfs@0.52.1) (2020-11-16)\n\n\n### Bug Fixes\n\n* report ipfs.add progress over http ([#3310](https://github.com/ipfs/js-ipfs/issues/3310)) ([39cad4b](https://github.com/ipfs/js-ipfs/commit/39cad4b76b950ea6a76477fd01f8631b8bd9aa1e))\n\n\n\n\n\n## [0.52.0](https://github.com/ipfs/js-ipfs/compare/ipfs@0.51.0...ipfs@0.52.0) (2020-11-09)\n\n\n### Bug Fixes\n\n* typedef resolution & add examples that use types ([#3359](https://github.com/ipfs/js-ipfs/issues/3359)) ([dc2795a](https://github.com/ipfs/js-ipfs/commit/dc2795a4f3b515683d09967ce611bf87d5e67f86)), closes [#3356](https://github.com/ipfs/js-ipfs/issues/3356) [#3358](https://github.com/ipfs/js-ipfs/issues/3358)\n\n\n### Features\n\n* remove all esoteric ipld formats ([#3360](https://github.com/ipfs/js-ipfs/issues/3360)) ([a542882](https://github.com/ipfs/js-ipfs/commit/a5428820a5b157fbb298b8eb49978e08157beca3)), closes [#3347](https://github.com/ipfs/js-ipfs/issues/3347)\n\n\n### BREAKING CHANGES\n\n* only dag-pb, dag-cbor and raw formats are supported out of the box, any others will need to be configured during node startup.\n\n\n\n\n\n## [0.51.0](https://github.com/ipfs/js-ipfs/compare/ipfs@0.50.2...ipfs@0.51.0) (2020-10-28)\n\n\n### Bug Fixes\n\n* disable cors by default ([#3275](https://github.com/ipfs/js-ipfs/issues/3275)) ([3ff833d](https://github.com/ipfs/js-ipfs/commit/3ff833db6444a3e931db9b76bf74c3420e57ee02))\n* types path for ipfs-core ([#3356](https://github.com/ipfs/js-ipfs/issues/3356)) ([a6bcad5](https://github.com/ipfs/js-ipfs/commit/a6bcad5d9e63a74897715e6bf66ff213424faa66))\n* use fetch in electron renderer and electron-fetch in main ([#3251](https://github.com/ipfs/js-ipfs/issues/3251)) ([639d71f](https://github.com/ipfs/js-ipfs/commit/639d71f7ac8f66d9633e753a2a6be927e14a5af0))\n\n\n### Features\n\n* remove support for SECIO ([#3295](https://github.com/ipfs/js-ipfs/issues/3295)) ([5f5ef7e](https://github.com/ipfs/js-ipfs/commit/5f5ef7ee6cc6dc634cc6adbede0602492490a85d))\n* type check & generate defs from jsdoc ([#3281](https://github.com/ipfs/js-ipfs/issues/3281)) ([bbcaf34](https://github.com/ipfs/js-ipfs/commit/bbcaf34111251b142273a5675f4754ff68bd9fa0))\n\n\n### BREAKING CHANGES\n\n* this removes support for SECIO making Noise the only security transport.\n\nCloses https://github.com/ipfs/js-ipfs/issues/3210\n\nCo-authored-by: achingbrain <alex@achingbrain.net>\n* - CORS origins will need to be [configured manually](https://github.com/ipfs/js-ipfs/blob/master/packages/ipfs-http-client/README.md#cors) before use with ipfs-http-client\n\n\n\n\n\n## [0.50.2](https://github.com/ipfs/js-ipfs/compare/ipfs@0.50.1...ipfs@0.50.2) (2020-09-09)\n\n**Note:** Version bump only for package ipfs\n\n\n\n\n\n## [0.50.1](https://github.com/ipfs/js-ipfs/compare/ipfs@0.50.0...ipfs@0.50.1) (2020-09-04)\n\n**Note:** Version bump only for package ipfs\n\n\n\n\n\n## [0.50.0](https://github.com/ipfs/js-ipfs/compare/ipfs@0.49.1...ipfs@0.50.0) (2020-09-03)\n\n\n### Features\n\n* add protocol list to ipfs id ([#3250](https://github.com/ipfs/js-ipfs/issues/3250)) ([1b6cf60](https://github.com/ipfs/js-ipfs/commit/1b6cf600a6b1348199457ca1fe6f314b6eff8c46))\n* add typeScript support ([#3236](https://github.com/ipfs/js-ipfs/issues/3236)) ([be26dd7](https://github.com/ipfs/js-ipfs/commit/be26dd723ed8c76efee149a993a8ade7f75f960e)), closes [#2945](https://github.com/ipfs/js-ipfs/issues/2945) [#1166](https://github.com/ipfs/js-ipfs/issues/1166)\n* add typescript support ([#3267](https://github.com/ipfs/js-ipfs/issues/3267)) ([6816bc6](https://github.com/ipfs/js-ipfs/commit/6816bc64ccb9bf852c2b9a26d9ddd19b9439dae6)), closes [#2945](https://github.com/ipfs/js-ipfs/issues/2945) [#1166](https://github.com/ipfs/js-ipfs/issues/1166)\n* ipns publish example ([#3207](https://github.com/ipfs/js-ipfs/issues/3207)) ([91faec6](https://github.com/ipfs/js-ipfs/commit/91faec6e3d89b0d9883b8d7815c276d44048e739))\n* store pins in datastore instead of a DAG ([#2771](https://github.com/ipfs/js-ipfs/issues/2771)) ([64b7fe4](https://github.com/ipfs/js-ipfs/commit/64b7fe41738cbe96d5a9075f0c01156c6f889c40))\n* update hapi to v20 ([#3245](https://github.com/ipfs/js-ipfs/issues/3245)) ([1aeef89](https://github.com/ipfs/js-ipfs/commit/1aeef89c73f42a2f6cceb7f0598400141ce40e23))\n* update to libp2p@0.29.0 ([63d4d35](https://github.com/ipfs/js-ipfs/commit/63d4d353c606e4fd487811d8a0014bb2173f11be))\n\n\n\n\n\n## [0.49.1](https://github.com/ipfs/js-ipfs/compare/ipfs@0.49.0...ipfs@0.49.1) (2020-08-24)\n\n\n### Bug Fixes\n\n* validate ipns records with inline public keys ([#3224](https://github.com/ipfs/js-ipfs/issues/3224)) ([5cc0e08](https://github.com/ipfs/js-ipfs/commit/5cc0e086b036e7ba40b09768b67b7067adca43c1))\n\n\n\n\n\n## [0.49.0](https://github.com/ipfs/js-ipfs/compare/ipfs@0.48.1...ipfs@0.49.0) (2020-08-12)\n\n\n### Bug Fixes\n\n* make execa a dep, it's used in ipfs config edit ([#3193](https://github.com/ipfs/js-ipfs/issues/3193)) ([19b8113](https://github.com/ipfs/js-ipfs/commit/19b81130a7311744cdd6b5bc2170d3939aeae1b6))\n* require command for key and pin subcommands ([#3196](https://github.com/ipfs/js-ipfs/issues/3196)) ([5449044](https://github.com/ipfs/js-ipfs/commit/5449044919b8440c1129d9cbf1ec650f4f5a993d))\n* send blobs when running ipfs-http-client in the browser ([#3184](https://github.com/ipfs/js-ipfs/issues/3184)) ([6b24463](https://github.com/ipfs/js-ipfs/commit/6b24463431497bd13b579a730ad7063345729ad9)), closes [#3138](https://github.com/ipfs/js-ipfs/issues/3138)\n* support keychain without pass ([#3212](https://github.com/ipfs/js-ipfs/issues/3212)) ([7e0e85c](https://github.com/ipfs/js-ipfs/commit/7e0e85c2f003a09845b1dbe4200ca61366933b05))\n* **docs:** update webrtc config example to use correct case ([6a498e9](https://github.com/ipfs/js-ipfs/commit/6a498e92c00a784867053cddf9dcf4c1f510cf55))\n* **docs:** update webrtc instructions for node in faq ([#3183](https://github.com/ipfs/js-ipfs/issues/3183)) ([8f5a19f](https://github.com/ipfs/js-ipfs/commit/8f5a19ff08023e22fb3c4ab9dcac1e7baa097d09))\n\n\n### Features\n\n* prioritize noise over secio ([#3216](https://github.com/ipfs/js-ipfs/issues/3216)) ([f3a67c4](https://github.com/ipfs/js-ipfs/commit/f3a67c43c3d3423df29b5e10f82fa483d31289b2))\n* share IPFS node between browser tabs ([#3081](https://github.com/ipfs/js-ipfs/issues/3081)) ([1b8b1b8](https://github.com/ipfs/js-ipfs/commit/1b8b1b822a252498889c54972a1f57e1fedc39d0)), closes [#3022](https://github.com/ipfs/js-ipfs/issues/3022)\n\n\n### BREAKING CHANGES\n\n* remove support for key.export over the http api\n\n\n\n\n\n## [0.48.1](https://github.com/ipfs/js-ipfs/compare/ipfs@0.48.0...ipfs@0.48.1) (2020-07-21)\n\n\n### Bug Fixes\n\n* update bitswap to fix [#3182](https://github.com/ipfs/js-ipfs/issues/3182) and crypto for go-ipfs interop ([9fdbde8](https://github.com/ipfs/js-ipfs/commit/9fdbde80e976063ab56410a4d8af1ba955e32307))\n\n\n\n\n\n## [0.48.0](https://github.com/ipfs/js-ipfs/compare/ipfs@0.47.0...ipfs@0.48.0) (2020-07-16)\n\n\n### Bug Fixes\n\n* do not list raw nodes in a dag as directories ([#3155](https://github.com/ipfs/js-ipfs/issues/3155)) ([585a142](https://github.com/ipfs/js-ipfs/commit/585a142d3c2317e80f37d6195ce24ed3146112e5))\n* error when no command specified ([#3145](https://github.com/ipfs/js-ipfs/issues/3145)) ([4309e10](https://github.com/ipfs/js-ipfs/commit/4309e1004bb77ee276b57228c35a921fb780a227))\n* optional arguments go in the options object ([#3118](https://github.com/ipfs/js-ipfs/issues/3118)) ([8cb8c73](https://github.com/ipfs/js-ipfs/commit/8cb8c73037e44894d756b70f344b3282463206f9))\n* peer ids are strings now ([#3162](https://github.com/ipfs/js-ipfs/issues/3162)) ([281bfe6](https://github.com/ipfs/js-ipfs/commit/281bfe60f079011d0ada783a82d1f030d08a89f2))\n* still load dag-pb, dag-cbor and raw when specifying custom formats ([#3132](https://github.com/ipfs/js-ipfs/issues/3132)) ([a96e3bc](https://github.com/ipfs/js-ipfs/commit/a96e3bc9e3763004beafc24b98efa85ffa665622)), closes [#3129](https://github.com/ipfs/js-ipfs/issues/3129)\n* unhandledpromiserejection in electron tests ([#3146](https://github.com/ipfs/js-ipfs/issues/3146)) ([4c0c67f](https://github.com/ipfs/js-ipfs/commit/4c0c67f023c75bbcb56b0520b31f1334480a5130))\n* use post for preloading ([#3149](https://github.com/ipfs/js-ipfs/issues/3149)) ([c9700f7](https://github.com/ipfs/js-ipfs/commit/c9700f78cefc523f6140361a90099c4991b427a7))\n\n\n### Features\n\n* add interface and http client versions to version output ([#3125](https://github.com/ipfs/js-ipfs/issues/3125)) ([65f8b23](https://github.com/ipfs/js-ipfs/commit/65f8b23f550f939e94aaf6939894a513519e6d68)), closes [#2878](https://github.com/ipfs/js-ipfs/issues/2878)\n* add size-only flag to cli repo stat command ([#3143](https://github.com/ipfs/js-ipfs/issues/3143)) ([b4d3bf8](https://github.com/ipfs/js-ipfs/commit/b4d3bf80e7cd5820e2561fc957a9f0f17235df05))\n* enable DHT by Routing.Type config key ([#3153](https://github.com/ipfs/js-ipfs/issues/3153)) ([dfe15d7](https://github.com/ipfs/js-ipfs/commit/dfe15d7422579afce8860f6321575454826d1844))\n* store blocks by multihash instead of CID ([#3124](https://github.com/ipfs/js-ipfs/issues/3124)) ([03b17f5](https://github.com/ipfs/js-ipfs/commit/03b17f5e2d290e84aa0cb541079b79e468e7d1bd))\n* turn on delegate nodes by default ([#3148](https://github.com/ipfs/js-ipfs/issues/3148)) ([3fd2ca8](https://github.com/ipfs/js-ipfs/commit/3fd2ca8c7bb3a907cc74d48516481fae01d47327))\n\n\n\n\n\n## [0.47.0](https://github.com/ipfs/js-ipfs/compare/ipfs@0.46.0...ipfs@0.47.0) (2020-06-24)\n\n\n### Bug Fixes\n\n* libp2p now requires encryption module ([#3085](https://github.com/ipfs/js-ipfs/issues/3085)) ([c567282](https://github.com/ipfs/js-ipfs/commit/c56728209f0eea63d00c68163c74cfdd350de69c))\n\n\n### Features\n\n* add config.getAll ([#3071](https://github.com/ipfs/js-ipfs/issues/3071)) ([16587f1](https://github.com/ipfs/js-ipfs/commit/16587f16e1b3ae525c099b1975748510638aceee))\n* libp2p noise as fallback for secio ([#3074](https://github.com/ipfs/js-ipfs/issues/3074)) ([660d3db](https://github.com/ipfs/js-ipfs/commit/660d3db9a47bff652057762b52a25529ab37117f))\n* persist peerstore ([#3072](https://github.com/ipfs/js-ipfs/issues/3072)) ([b404974](https://github.com/ipfs/js-ipfs/commit/b40497427b7d33f52803c8fa14cc73be7f872d65))\n* webui v2.9.0 ([#3054](https://github.com/ipfs/js-ipfs/issues/3054)) ([5d9d331](https://github.com/ipfs/js-ipfs/commit/5d9d331ed42f3ac9efc243878011db871b742a4e))\n\n\n\n\n\n## [0.46.0](https://github.com/ipfs/js-ipfs/compare/ipfs@0.45.0...ipfs@0.46.0) (2020-06-05)\n\n\n### Bug Fixes\n\n* handle optional key to config.get ([#3069](https://github.com/ipfs/js-ipfs/issues/3069)) ([d043138](https://github.com/ipfs/js-ipfs/commit/d043138be2c0c7fd458131d56e235edec1504ca3))\n\n\n### Features\n\n* sync with go-ipfs 0.5 ([#3013](https://github.com/ipfs/js-ipfs/issues/3013)) ([0900bb9](https://github.com/ipfs/js-ipfs/commit/0900bb9b8123edb689a137a006c5507d8503f693))\n\n\n\n\n\n## [0.45.0](https://github.com/ipfs/js-ipfs/compare/ipfs@0.44.0...ipfs@0.45.0) (2020-05-29)\n\n\n### Features\n\n* upgrade bitswap to use 1.2.0 and better wantlist performance ([18283dd](https://github.com/ipfs/js-ipfs/commit/18283dd8fb70af5ed93236482b2a5f89515c24e0))\n\n\n\n\n\n## [0.44.0](https://github.com/ipfs/js-ipfs/compare/ipfs@0.43.3...ipfs@0.44.0) (2020-05-18)\n\n\n### Bug Fixes\n\n* fixes browser script tag example ([#3034](https://github.com/ipfs/js-ipfs/issues/3034)) ([ee8b769](https://github.com/ipfs/js-ipfs/commit/ee8b769b96f7e3c8414bbf85853ab4e21e8fd11c)), closes [#3027](https://github.com/ipfs/js-ipfs/issues/3027)\n* remove ipld all formats and fix traverse ipld example ([#3025](https://github.com/ipfs/js-ipfs/issues/3025)) ([e6079c1](https://github.com/ipfs/js-ipfs/commit/e6079c17d5656e92dd5191f0581000c6a782c7ed))\n* remove node globals ([#2932](https://github.com/ipfs/js-ipfs/issues/2932)) ([d0d2f74](https://github.com/ipfs/js-ipfs/commit/d0d2f74cef4e439c6d2baadba1f1f9f52534fcba))\n\n\n### Features\n\n* cancellable api calls ([#2993](https://github.com/ipfs/js-ipfs/issues/2993)) ([2b24f59](https://github.com/ipfs/js-ipfs/commit/2b24f590041a0df9da87b75ae2344232fe22fe3a)), closes [#3015](https://github.com/ipfs/js-ipfs/issues/3015)\n\n\n\n\n\n## [0.43.3](https://github.com/ipfs/js-ipfs/compare/ipfs@0.43.2...ipfs@0.43.3) (2020-05-05)\n\n**Note:** Version bump only for package ipfs\n\n\n\n\n\n## [0.43.2](https://github.com/ipfs/js-ipfs/compare/ipfs@0.43.1...ipfs@0.43.2) (2020-05-05)\n\n\n### Bug Fixes\n\n* pass headers to request ([#3018](https://github.com/ipfs/js-ipfs/issues/3018)) ([3ba00f8](https://github.com/ipfs/js-ipfs/commit/3ba00f8c6a8a057c5776d539a671a74d9565fb29)), closes [#3017](https://github.com/ipfs/js-ipfs/issues/3017)\n\n\n\n\n\n## [0.43.1](https://github.com/ipfs/js-ipfs/compare/ipfs@0.43.0...ipfs@0.43.1) (2020-04-28)\n\n\n### Bug Fixes\n\n* correct dht reference in ipns routing ([#2996](https://github.com/ipfs/js-ipfs/issues/2996)) ([d2579c0](https://github.com/ipfs/js-ipfs/commit/d2579c0e8f1e81c1a2df578d46459c7a1eeeba53))\n\n\n### Features\n\n* webui v2.7.5, with feeling ([#2984](https://github.com/ipfs/js-ipfs/issues/2984)) ([2e0a114](https://github.com/ipfs/js-ipfs/commit/2e0a1144d9405f1a34fcd038361ad075968d841f))\n\n\n\n\n\n## [0.43.0](https://github.com/ipfs/js-ipfs/compare/ipfs@0.42.1...ipfs@0.43.0) (2020-04-16)\n\n\n### Bug Fixes\n\n* make http api only accept POST requests ([#2977](https://github.com/ipfs/js-ipfs/issues/2977)) ([943d4a8](https://github.com/ipfs/js-ipfs/commit/943d4a8cf2d4c4ff5ecd4814c59cb0aae0cfa1fd))\n* regression that dht could not be enabled through conf ([#2976](https://github.com/ipfs/js-ipfs/issues/2976)) ([9d88a2e](https://github.com/ipfs/js-ipfs/commit/9d88a2ebbad4dfa58df351d31d201eaf2aaf78dc))\n\n\n### BREAKING CHANGES\n\n* Where we used to accept all and any HTTP methods, now only POST is\naccepted.  The API client will now only send POST requests too.\n\n* test: add tests to make sure we are post-only\n\n* chore: upgrade ipfs-utils\n\n* fix: return 405 instead of 404 for bad methods\n\n* fix: reject browsers that do not send an origin\n\nAlso fixes running interface tests over http in browsers against\njs-ipfs\n\n\n\n\n\n## [0.42.1](https://github.com/ipfs/js-ipfs/compare/ipfs@0.42.0...ipfs@0.42.1) (2020-04-08)\n\n\n### Bug Fixes\n\n* use correct name for webrtc transport config ([#2966](https://github.com/ipfs/js-ipfs/issues/2966)) ([83ca42a](https://github.com/ipfs/js-ipfs/commit/83ca42a83fbe43a93d3d66d7c117123c9423359b)), closes [#2963](https://github.com/ipfs/js-ipfs/issues/2963)\n\n\n\n\n\n# 0.42.0 (2020-03-31)\n\n\n### Bug Fixes\n\n* add default args for ipfs.add ([#2950](https://github.com/ipfs/js-ipfs/issues/2950)) ([a01f5b6](https://github.com/ipfs/js-ipfs/commit/a01f5b63cd92d225b10eff497f79caf4baab1973))\n* add default for cid base and fix cid version override ([d951993](https://github.com/ipfs/js-ipfs/commit/d9519931642fbeabd4a04940e67911e346106814))\n* dont include util.textencoder in the browser ([#2919](https://github.com/ipfs/js-ipfs/issues/2919)) ([3207e3b](https://github.com/ipfs/js-ipfs/commit/3207e3b35c9c250332c03dd2a066e8ebcda35e43))\n* error when command is unknown ([#2916](https://github.com/ipfs/js-ipfs/issues/2916)) ([743a7fc](https://github.com/ipfs/js-ipfs/commit/743a7fc1630e753568e8a56a8f3580cb2b8d50ad))\n* multiaddr validation to add peer id for listening ([#2833](https://github.com/ipfs/js-ipfs/issues/2833)) ([78cbec1](https://github.com/ipfs/js-ipfs/commit/78cbec159ed84b1bc4cd86eb17d3c2d050827a6d))\n* only start prometheus metrics in one place ([#2954](https://github.com/ipfs/js-ipfs/issues/2954)) ([d52a41e](https://github.com/ipfs/js-ipfs/commit/d52a41e1db601db55cf8433c9a91c2ee6b9b3e09)), closes [#2019](https://github.com/ipfs/js-ipfs/issues/2019)\n* reuse columns value from process.stdout ([e8646d8](https://github.com/ipfs/js-ipfs/commit/e8646d874bbbdf51aa1c8df83f8d5e52a45592be))\n* tag stdin with mtime and mode when piping to cli 'add' ([#2832](https://github.com/ipfs/js-ipfs/issues/2832)) ([8c97de1](https://github.com/ipfs/js-ipfs/commit/8c97de1e85c8544976a8240bf72e850f0e49a2b0)), closes [#2763](https://github.com/ipfs/js-ipfs/issues/2763)\n\n\n### chore\n\n* move mfs and multipart files into core ([#2811](https://github.com/ipfs/js-ipfs/issues/2811)) ([82b9e08](https://github.com/ipfs/js-ipfs/commit/82b9e085330e6c6290e6f3dd29678247984ffdce))\n* update dep version and ignore interop test for raw leaves ([#2747](https://github.com/ipfs/js-ipfs/issues/2747)) ([6376cec](https://github.com/ipfs/js-ipfs/commit/6376cec2b4beccef4751c498088f600ec7788118))\n\n\n### Features\n\n* remove ky from http-client and utils ([#2810](https://github.com/ipfs/js-ipfs/issues/2810)) ([9bc9625](https://github.com/ipfs/js-ipfs/commit/9bc96252686d0bbbfdb2a3300bb17b80eafdaf00)), closes [#2801](https://github.com/ipfs/js-ipfs/issues/2801)\n* support mtime-nsecs in mfs cli ([#2958](https://github.com/ipfs/js-ipfs/issues/2958)) ([69c091d](https://github.com/ipfs/js-ipfs/commit/69c091da963d974e75638a63c36140c8e9d3c4e0)), closes [#2803](https://github.com/ipfs/js-ipfs/issues/2803)\n\n\n### BREAKING CHANGES\n\n* When the path passed to `ipfs.files.stat(path)` was a hamt sharded dir, the resovled\nvalue returned by js-ipfs previously had a `type` property of with a value of\n`'hamt-sharded-directory'`.  To bring it in line with go-ipfs this value is now\n`'directory'`.\n* Files that fit into one block imported with either `--cid-version=1`\nor `--raw-leaves=true` previously returned a CID that resolved to\na raw node (e.g. a buffer). Returned CIDs now resolve to a `dag-pb`\nnode that contains a UnixFS entry. This is to allow setting metadata\non small files with CIDv1.\n\n\n\n\n\n<a name=\"0.41.0\"></a>\n## [0.41.0](https://github.com/ipfs/js-ipfs/compare/v0.41.0-rc.2...v0.41.0) (2020-02-13)\n\n\n### Bug Fixes\n\n* await on things that need awaiting on ([#2773](https://github.com/ipfs/js-ipfs/issues/2773)) ([b94fe54](https://github.com/ipfs/js-ipfs/commit/b94fe54))\n\n\n\n<a name=\"0.41.0-rc.2\"></a>\n## [0.41.0-rc.2](https://github.com/ipfs/js-ipfs/compare/v0.41.0-rc.1...v0.41.0-rc.2) (2020-02-11)\n\n\n\n<a name=\"0.41.0-rc.1\"></a>\n## [0.41.0-rc.1](https://github.com/ipfs/js-ipfs/compare/v0.41.0-rc.0...v0.41.0-rc.1) (2020-02-10)\n\n\n### Bug Fixes\n\n* block put with CID as string ([#2760](https://github.com/ipfs/js-ipfs/issues/2760)) ([cc9a933](https://github.com/ipfs/js-ipfs/commit/cc9a933))\n* report correct swarm addresses after listening on new addrs ([#2749](https://github.com/ipfs/js-ipfs/issues/2749)) ([41a7e55](https://github.com/ipfs/js-ipfs/commit/41a7e55)), closes [#2508](https://github.com/ipfs/js-ipfs/issues/2508)\n\n\n### Features\n\n* use it-tar ([#2758](https://github.com/ipfs/js-ipfs/issues/2758)) ([eb33a63](https://github.com/ipfs/js-ipfs/commit/eb33a63))\n\n\n\n<a name=\"0.41.0-rc.0\"></a>\n## [0.41.0-rc.0](https://github.com/ipfs/js-ipfs/compare/v0.40.0...v0.41.0-rc.0) (2020-02-03)\n\n\n### Bug Fixes\n\n* bad merge ([714e540](https://github.com/ipfs/js-ipfs/commit/714e540))\n* correct redirect when it loads webui ([#2697](https://github.com/ipfs/js-ipfs/issues/2697)) ([#2698](https://github.com/ipfs/js-ipfs/issues/2698)) ([3516bb8](https://github.com/ipfs/js-ipfs/commit/3516bb8))\n* ipfs-repo version ([f08758e](https://github.com/ipfs/js-ipfs/commit/f08758e))\n* limit SW registration to content root ([#2682](https://github.com/ipfs/js-ipfs/issues/2682)) ([feba661](https://github.com/ipfs/js-ipfs/commit/feba661)), closes [/github.com/ipfs/go-ipfs/issues/4025#issuecomment-342250616](https://github.com//github.com/ipfs/go-ipfs/issues/4025/issues/issuecomment-342250616)\n* repo gc error key name ([#2618](https://github.com/ipfs/js-ipfs/issues/2618)) ([5a1d266](https://github.com/ipfs/js-ipfs/commit/5a1d266)), closes [/docs.ipfs.io/reference/api/http/#api-v0](https://github.com//docs.ipfs.io/reference/api/http//issues/api-v0)\n* support legacy links in cbor data ([#2631](https://github.com/ipfs/js-ipfs/issues/2631)) ([f98023b](https://github.com/ipfs/js-ipfs/commit/f98023b))\n\n\n### Code Refactoring\n\n* return peer IDs as strings not CIDs ([#2729](https://github.com/ipfs/js-ipfs/issues/2729)) ([16d540c](https://github.com/ipfs/js-ipfs/commit/16d540c))\n\n\n### Features\n\n* add --hidden flag to cli add command ([#2649](https://github.com/ipfs/js-ipfs/issues/2649)) ([ed886f4](https://github.com/ipfs/js-ipfs/commit/ed886f4))\n* add alias for `ipfs repo stat --human`. ([#2609](https://github.com/ipfs/js-ipfs/issues/2609)) ([f81086c](https://github.com/ipfs/js-ipfs/commit/f81086c))\n* add bitswap stat human option in CLI ([#2619](https://github.com/ipfs/js-ipfs/issues/2619)) ([6a2ea52](https://github.com/ipfs/js-ipfs/commit/6a2ea52))\n* add human flag to repo stat cli command ([#2630](https://github.com/ipfs/js-ipfs/issues/2630)) ([39bc5b4](https://github.com/ipfs/js-ipfs/commit/39bc5b4))\n* pass libp2pOptions to the bundle function ([#2591](https://github.com/ipfs/js-ipfs/issues/2591)) ([e8e9b91](https://github.com/ipfs/js-ipfs/commit/e8e9b91))\n* return CID of flushed path from ipfs.files.flush ([#2715](https://github.com/ipfs/js-ipfs/issues/2715)) ([5db7c29](https://github.com/ipfs/js-ipfs/commit/5db7c29))\n* support -X special permissions arg to files.chmod ([#2719](https://github.com/ipfs/js-ipfs/issues/2719)) ([d6ece05](https://github.com/ipfs/js-ipfs/commit/d6ece05))\n* support UnixFSv1.5 metadata ([#2621](https://github.com/ipfs/js-ipfs/issues/2621)) ([acbda68](https://github.com/ipfs/js-ipfs/commit/acbda68)), closes [ipfs/js-datastore-pubsub#20](https://github.com/ipfs/js-datastore-pubsub/issues/20)\n* web ui 2.7.1 ([#2599](https://github.com/ipfs/js-ipfs/issues/2599)) ([06340ec](https://github.com/ipfs/js-ipfs/commit/06340ec))\n* web ui 2.7.2 ([#2651](https://github.com/ipfs/js-ipfs/issues/2651)) ([7a87d8f](https://github.com/ipfs/js-ipfs/commit/7a87d8f))\n\n\n### Performance Improvements\n\n* expose importer concurrency controls when adding files ([#2637](https://github.com/ipfs/js-ipfs/issues/2637)) ([1d19c4f](https://github.com/ipfs/js-ipfs/commit/1d19c4f))\n\n\n### BREAKING CHANGES\n\n* Where `PeerID`s were previously [CID](https://www.npmjs.com/package/cids)s, now they are Strings\n\n- `ipfs.bitswap.stat().peers[n]` is now a String (was a CID)\n- `ipfs.dht.findPeer().id` is now a String (was a CID)\n- `ipfs.dht.findProvs()[n].id` is now a String (was a CID)\n- `ipfs.dht.provide()[n].id` is now a String (was a CID)\n- `ipfs.dht.put()[n].id` is now a String (was a CID)\n- `ipfs.dht.query()[n].id` is now a String (was a CID)\n- `ipfs.id().id` is now a String (was a CID)\n- `ipfs.id().addresses[n]` are now [Multiaddr](https://www.npmjs.com/package/multiaddr)s (were Strings)\n* Removes all `codec`/`format` options, everything is `dag-pb` now.\n\n\n\n<a name=\"0.40.0\"></a>\n## [0.40.0](https://github.com/ipfs/js-ipfs/compare/v0.40.0-rc.1...v0.40.0) (2019-12-02)\n\n\n\n<a name=\"0.40.0-rc.1\"></a>\n## [0.40.0-rc.1](https://github.com/ipfs/js-ipfs/compare/v0.40.0-rc.0...v0.40.0-rc.1) (2019-11-28)\n\n\n### Bug Fixes\n\n* support legacy links in cbor data ([#2631](https://github.com/ipfs/js-ipfs/issues/2631)) ([3f446d6](https://github.com/ipfs/js-ipfs/commit/3f446d6))\n\n\n\n<a name=\"0.40.0-rc.0\"></a>\n## [0.40.0-rc.0](https://github.com/ipfs/js-ipfs/compare/v0.39.0...v0.40.0-rc.0) (2019-11-11)\n\n\n### Bug Fixes\n\n* add profiles docs, support in validation and tests ([#2545](https://github.com/ipfs/js-ipfs/issues/2545)) ([37073e6](https://github.com/ipfs/js-ipfs/commit/37073e6))\n* choose import strategy in ipfs.add ([#2541](https://github.com/ipfs/js-ipfs/issues/2541)) ([bba1537](https://github.com/ipfs/js-ipfs/commit/bba1537))\n* dht.provide() should accept string keys ([#2573](https://github.com/ipfs/js-ipfs/issues/2573)) ([#2589](https://github.com/ipfs/js-ipfs/issues/2589)) ([53c2144](https://github.com/ipfs/js-ipfs/commit/53c2144))\n* fix ls crash ([#2546](https://github.com/ipfs/js-ipfs/issues/2546)) ([09041c3](https://github.com/ipfs/js-ipfs/commit/09041c3)), closes [ipfs/js-ipfs-unixfs-exporter#24](https://github.com/ipfs/js-ipfs-unixfs-exporter/issues/24)\n* make init options look like go-ipfs ([#2544](https://github.com/ipfs/js-ipfs/issues/2544)) ([13a8289](https://github.com/ipfs/js-ipfs/commit/13a8289))\n* remove superfluous backtick ([3bd47c3](https://github.com/ipfs/js-ipfs/commit/3bd47c3))\n* revert evergreen webui ([#2557](https://github.com/ipfs/js-ipfs/issues/2557)) ([16806d9](https://github.com/ipfs/js-ipfs/commit/16806d9))\n* **package:** update [@hapi](https://github.com/hapi)/ammo to version 4.0.0 ([#2538](https://github.com/ipfs/js-ipfs/issues/2538)) ([da78142](https://github.com/ipfs/js-ipfs/commit/da78142))\n\n\n### Features\n\n* add mssing `dag put` and `dag resolve` cli commands ([#2521](https://github.com/ipfs/js-ipfs/issues/2521)) ([8759bf8](https://github.com/ipfs/js-ipfs/commit/8759bf8))\n* evergreen web ui ([#2520](https://github.com/ipfs/js-ipfs/issues/2520)) ([069bf73](https://github.com/ipfs/js-ipfs/commit/069bf73))\n* integrate ipfs-repo-migrations tool ([#2527](https://github.com/ipfs/js-ipfs/issues/2527)) ([1d12ffb](https://github.com/ipfs/js-ipfs/commit/1d12ffb))\n* support CIDs in /ipns/ content paths ([#2566](https://github.com/ipfs/js-ipfs/issues/2566)) ([4fa39fb](https://github.com/ipfs/js-ipfs/commit/4fa39fb))\n* web ui 2.6.0 ([#2576](https://github.com/ipfs/js-ipfs/issues/2576)) ([a61d510](https://github.com/ipfs/js-ipfs/commit/a61d510))\n\n\n\n<a name=\"0.39.0\"></a>\n## [0.39.0](https://github.com/ipfs/js-ipfs/compare/v0.39.0-rc.2...v0.39.0) (2019-10-23)\n\n\n\n<a name=\"0.39.0-rc.2\"></a>\n## [0.39.0-rc.2](https://github.com/ipfs/js-ipfs/compare/v0.39.0-rc.1...v0.39.0-rc.2) (2019-10-17)\n\n\n### Bug Fixes\n\n* add profiles docs, support in validation and tests ([#2545](https://github.com/ipfs/js-ipfs/issues/2545)) ([e081e16](https://github.com/ipfs/js-ipfs/commit/e081e16))\n* choose import strategy in ipfs.add ([#2541](https://github.com/ipfs/js-ipfs/issues/2541)) ([e2e6701](https://github.com/ipfs/js-ipfs/commit/e2e6701))\n* fix ls crash ([#2546](https://github.com/ipfs/js-ipfs/issues/2546)) ([83eb99b](https://github.com/ipfs/js-ipfs/commit/83eb99b)), closes [ipfs/js-ipfs-unixfs-exporter#24](https://github.com/ipfs/js-ipfs-unixfs-exporter/issues/24)\n* make init options look like go-ipfs ([#2544](https://github.com/ipfs/js-ipfs/issues/2544)) ([d4d6dfe](https://github.com/ipfs/js-ipfs/commit/d4d6dfe))\n\n\n\n<a name=\"0.39.0-rc.1\"></a>\n## [0.39.0-rc.1](https://github.com/ipfs/js-ipfs/compare/v0.39.0-rc.0...v0.39.0-rc.1) (2019-10-15)\n\n\n\n<a name=\"0.39.0-rc.0\"></a>\n## [0.39.0-rc.0](https://github.com/ipfs/js-ipfs/compare/v0.38.0...v0.39.0-rc.0) (2019-10-08)\n\n\n### Bug Fixes\n\n* limit concurrent HTTP requests in browser ([#2304](https://github.com/ipfs/js-ipfs/issues/2304)) ([cf38aea](https://github.com/ipfs/js-ipfs/commit/cf38aea))\n* only try to get ipfs if argv is present ([#2504](https://github.com/ipfs/js-ipfs/issues/2504)) ([1281b9f](https://github.com/ipfs/js-ipfs/commit/1281b9f))\n* pull in preconfigured chai from interface tests ([#2510](https://github.com/ipfs/js-ipfs/issues/2510)) ([8c01259](https://github.com/ipfs/js-ipfs/commit/8c01259))\n\n\n### Features\n\n* Add config profile endpoint and CLI ([#2165](https://github.com/ipfs/js-ipfs/issues/2165)) ([7314f0d](https://github.com/ipfs/js-ipfs/commit/7314f0d))\n* allow daemon to init and start in a single cmd ([#2428](https://github.com/ipfs/js-ipfs/issues/2428)) ([16d5e7b](https://github.com/ipfs/js-ipfs/commit/16d5e7b))\n* support block.rm over http api ([#2514](https://github.com/ipfs/js-ipfs/issues/2514)) ([c9be79e](https://github.com/ipfs/js-ipfs/commit/c9be79e))\n* web ui 2.5.3 ([4f398fc](https://github.com/ipfs/js-ipfs/commit/4f398fc))\n* web ui 2.5.4 ([#2478](https://github.com/ipfs/js-ipfs/issues/2478)) ([bff402c](https://github.com/ipfs/js-ipfs/commit/bff402c))\n\n\n### Reverts\n\n* e ([55c4446](https://github.com/ipfs/js-ipfs/commit/55c4446))\n\n\n\n<a name=\"0.38.0\"></a>\n## [0.38.0](https://github.com/ipfs/js-ipfs/compare/v0.38.0-rc.6...v0.38.0) (2019-09-30)\n\n\n\n<a name=\"0.38.0-rc.6\"></a>\n## [0.38.0-rc.6](https://github.com/ipfs/js-ipfs/compare/v0.38.0-rc.5...v0.38.0-rc.6) (2019-09-25)\n\n\n\n<a name=\"0.38.0-rc.5\"></a>\n## [0.38.0-rc.5](https://github.com/ipfs/js-ipfs/compare/v0.38.0-rc.4...v0.38.0-rc.5) (2019-09-18)\n\n\n\n<a name=\"0.38.0-rc.4\"></a>\n## [0.38.0-rc.4](https://github.com/ipfs/js-ipfs/compare/v0.38.0-rc.3...v0.38.0-rc.4) (2019-09-17)\n\n\n\n<a name=\"0.38.0-rc.3\"></a>\n## [0.38.0-rc.3](https://github.com/ipfs/js-ipfs/compare/v0.38.0-rc.2...v0.38.0-rc.3) (2019-09-17)\n\n\n\n<a name=\"0.38.0-rc.2\"></a>\n## [0.38.0-rc.2](https://github.com/ipfs/js-ipfs/compare/v0.38.0-rc.1...v0.38.0-rc.2) (2019-09-16)\n\n\n\n## [0.38.0-rc.1](https://github.com/ipfs/js-ipfs/compare/v0.38.0-rc.0...v0.38.0-rc.1) (2019-09-13)\n\n\n\n<a name=\"0.38.0-rc.0\"></a>\n## [0.38.0-rc.0](https://github.com/ipfs/js-ipfs/compare/v0.38.0-pre.1...v0.38.0-rc.0) (2019-09-09)\n\n\n### Bug Fixes\n\n* **tests:** remove Math.random from  tests ([#2431](https://github.com/ipfs/js-ipfs/issues/2431)) ([60bf020](https://github.com/ipfs/js-ipfs/commit/60bf020))\n\n\n### Features\n\n* add support for ipns and recursive to ipfs resolve ([#2297](https://github.com/ipfs/js-ipfs/issues/2297)) ([039675e](https://github.com/ipfs/js-ipfs/commit/039675e))\n* enable pubsub via config file and enabled by default ([#2427](https://github.com/ipfs/js-ipfs/issues/2427)) ([27751cf](https://github.com/ipfs/js-ipfs/commit/27751cf)), closes [ipfs/js-ipfsd-ctl#366](https://github.com/ipfs/js-ipfsd-ctl/issues/366) [ipfs/go-ipfs#6621](https://github.com/ipfs/go-ipfs/issues/6621)\n* support adding async iterators ([#2379](https://github.com/ipfs/js-ipfs/issues/2379)) ([3878f0f](https://github.com/ipfs/js-ipfs/commit/3878f0f))\n* web ui 2.5.1 ([#2434](https://github.com/ipfs/js-ipfs/issues/2434)) ([39ef553](https://github.com/ipfs/js-ipfs/commit/39ef553))\n\n\n### BREAKING CHANGES\n\n* pubsub is now enabled by default and the experimental flag was removed\n* `recursive` is now `true` by default in `ipfs resolve`\n\n\n\n<a name=\"0.38.0-pre.1\"></a>\n## [0.38.0-pre.1](https://github.com/ipfs/js-ipfs/compare/v0.38.0-pre.0...v0.38.0-pre.1) (2019-09-02)\n\n\n### Bug Fixes\n\n* **package:** update ipfs-http-client to version 34.0.0 ([#2407](https://github.com/ipfs/js-ipfs/issues/2407)) ([f7e5094](https://github.com/ipfs/js-ipfs/commit/f7e5094))\n\n\n### Features\n\n* gossipsub as default pubsub ([#2298](https://github.com/ipfs/js-ipfs/issues/2298)) ([902e045](https://github.com/ipfs/js-ipfs/commit/902e045))\n\n\n### Reverts\n\n* update of ipfs-http-client ([#2412](https://github.com/ipfs/js-ipfs/issues/2412)) ([f6cf876](https://github.com/ipfs/js-ipfs/commit/f6cf876))\n\n\n### BREAKING CHANGES\n\n* The default pubsub implementation has changed from floodsub to [gossipsub](https://github.com/ChainSafe/gossipsub-js). Additionally, to enable pubsub programmatically set `pubsub.enabled: true` instead of `EXPERIMENTAL.pubsub: true` or via the CLI pass `--enable-pubsub` instead of `--enable-pubsub-experiment` to `jsipfs daemon`.\n\n\n\n<a name=\"0.38.0-pre.0\"></a>\n## [0.38.0-pre.0](https://github.com/ipfs/js-ipfs/compare/v0.37.1...v0.38.0-pre.0) (2019-08-27)\n\n\n### Bug Fixes\n\n* make progress bar work again when adding files ([#2386](https://github.com/ipfs/js-ipfs/issues/2386)) ([f6dcb0f](https://github.com/ipfs/js-ipfs/commit/f6dcb0f)), closes [#2379](https://github.com/ipfs/js-ipfs/issues/2379)\n* really allow logo to appear on npm ([02e521e](https://github.com/ipfs/js-ipfs/commit/02e521e))\n\n\n### Features\n\n* garbage collection ([#2022](https://github.com/ipfs/js-ipfs/issues/2022)) ([44045b0](https://github.com/ipfs/js-ipfs/commit/44045b0))\n\n\n### BREAKING CHANGES\n\n* Order of output from the CLI command `ipfs add` may change between invocations given the same files since it is not longer buffered and sorted.\n\nPreviously, js-ipfs buffered all hashes of added files and sorted them before outputting to the terminal, this might be because the `glob` module does not return stable results.  This gives a poor user experience as you see nothing then everything, compared to `go-ipfs` which prints out the hashes as they become available.  The tradeoff is the order might be slightly different between invocations, though the hashes are always the same as we sort DAGNode links before serializing so it doesn't matter what order you import them in.\n\n\n\n<a name=\"0.37.1\"></a>\n## [0.37.1](https://github.com/ipfs/js-ipfs/compare/v0.37.0...v0.37.1) (2019-08-23)\n\n\n### Bug Fixes\n\n* create HTTP servers in series ([#2388](https://github.com/ipfs/js-ipfs/issues/2388)) ([970a269](https://github.com/ipfs/js-ipfs/commit/970a269))\n* enable preload on MFS commands that accept IPFS paths ([#2355](https://github.com/ipfs/js-ipfs/issues/2355)) ([0e0d1dd](https://github.com/ipfs/js-ipfs/commit/0e0d1dd))\n* **package:** update yargs to version 14.0.0 ([#2371](https://github.com/ipfs/js-ipfs/issues/2371)) ([5aadb2d](https://github.com/ipfs/js-ipfs/commit/5aadb2d))\n* do not load all of a DAG into memory when pinning ([#2372](https://github.com/ipfs/js-ipfs/issues/2372)) ([f357c28](https://github.com/ipfs/js-ipfs/commit/f357c28)), closes [#2310](https://github.com/ipfs/js-ipfs/issues/2310)\n* preload addreses with trailing slash ([#2377](https://github.com/ipfs/js-ipfs/issues/2377)) ([c607971](https://github.com/ipfs/js-ipfs/commit/c607971)), closes [#2333](https://github.com/ipfs/js-ipfs/issues/2333)\n\n\n### Features\n\n* allow controlling preload from cli and http api ([#2384](https://github.com/ipfs/js-ipfs/issues/2384)) ([5878a0a](https://github.com/ipfs/js-ipfs/commit/5878a0a))\n* resolution of .eth names via .eth.link ([#2373](https://github.com/ipfs/js-ipfs/issues/2373)) ([7e02140](https://github.com/ipfs/js-ipfs/commit/7e02140))\n\n\n\n<a name=\"0.37.0\"></a>\n## [0.37.0](https://github.com/ipfs/js-ipfs/compare/v0.37.0-rc.1...v0.37.0) (2019-08-06)\n\n\n\n<a name=\"0.37.0-rc.1\"></a>\n## [0.37.0-rc.1](https://github.com/ipfs/js-ipfs/compare/v0.37.0-rc.0...v0.37.0-rc.1) (2019-08-06)\n\n\n### Bug Fixes\n\n* add CORS headers to gateway responses ([#2254](https://github.com/ipfs/js-ipfs/issues/2254)) ([5156a47](https://github.com/ipfs/js-ipfs/commit/5156a47))\n* disable socket timeout for pubsub subscriptions ([#2303](https://github.com/ipfs/js-ipfs/issues/2303)) ([3583cc2](https://github.com/ipfs/js-ipfs/commit/3583cc2))\n* move mfs cmds and safer exit ([#1981](https://github.com/ipfs/js-ipfs/issues/1981)) ([fee0141](https://github.com/ipfs/js-ipfs/commit/fee0141))\n* swarm.peers latency value when unknown ([#2336](https://github.com/ipfs/js-ipfs/issues/2336)) ([6248ec1](https://github.com/ipfs/js-ipfs/commit/6248ec1))\n* use ephemeral ports in API/Gateway bind test ([#2305](https://github.com/ipfs/js-ipfs/issues/2305)) ([24679af](https://github.com/ipfs/js-ipfs/commit/24679af))\n* **package:** update ipfs-mfs to version 0.12.0 ([#2275](https://github.com/ipfs/js-ipfs/issues/2275)) ([c15f146](https://github.com/ipfs/js-ipfs/commit/c15f146))\n\n\n\n<a name=\"0.37.0-rc.0\"></a>\n## [0.37.0-rc.0](https://github.com/ipfs/js-ipfs/compare/v0.36.4...v0.37.0-rc.0) (2019-07-17)\n\n\n### Bug Fixes\n\n* allow setting Addresses.Delegates ([#2253](https://github.com/ipfs/js-ipfs/issues/2253)) ([58a9bc4](https://github.com/ipfs/js-ipfs/commit/58a9bc4))\n* browser video streaming example ([#2267](https://github.com/ipfs/js-ipfs/issues/2267)) ([f5cf216](https://github.com/ipfs/js-ipfs/commit/f5cf216))\n* clean repo func on windows ([#2243](https://github.com/ipfs/js-ipfs/issues/2243)) ([26b92a1](https://github.com/ipfs/js-ipfs/commit/26b92a1))\n* failing test on config ([#2205](https://github.com/ipfs/js-ipfs/issues/2205)) ([5ed9532](https://github.com/ipfs/js-ipfs/commit/5ed9532))\n* ipns reference to libp2p dht config ([#2182](https://github.com/ipfs/js-ipfs/issues/2182)) ([e46e6ad](https://github.com/ipfs/js-ipfs/commit/e46e6ad))\n* passed config validation ([#2270](https://github.com/ipfs/js-ipfs/issues/2270)) ([80e7d81](https://github.com/ipfs/js-ipfs/commit/80e7d81))\n* pin type filtering in pin.ls ([#2228](https://github.com/ipfs/js-ipfs/issues/2228)) ([afdfe7f](https://github.com/ipfs/js-ipfs/commit/afdfe7f))\n* **gateway:** disable compression ([#2245](https://github.com/ipfs/js-ipfs/issues/2245)) ([4ee28e0](https://github.com/ipfs/js-ipfs/commit/4ee28e0))\n* **package:** update file-type to version 12.0.0 ([#2176](https://github.com/ipfs/js-ipfs/issues/2176)) ([3e63ef2](https://github.com/ipfs/js-ipfs/commit/3e63ef2))\n\n\n### Code Refactoring\n\n* **gateway:** return implicit index.html ([#2217](https://github.com/ipfs/js-ipfs/issues/2217)) ([8519886](https://github.com/ipfs/js-ipfs/commit/8519886))\n\n\n### Features\n\n* add delegate routers to libp2p config ([#2195](https://github.com/ipfs/js-ipfs/issues/2195)) ([1aaaab9](https://github.com/ipfs/js-ipfs/commit/1aaaab9))\n* add HTTP Gateway support for /ipns/ paths  ([#2020](https://github.com/ipfs/js-ipfs/issues/2020)) ([43ac305](https://github.com/ipfs/js-ipfs/commit/43ac305)), closes [#1989](https://github.com/ipfs/js-ipfs/issues/1989)\n* add support for ipns name resolve /ipns/<fqdn> ([#2002](https://github.com/ipfs/js-ipfs/issues/2002)) ([5044a30](https://github.com/ipfs/js-ipfs/commit/5044a30)), closes [#1918](https://github.com/ipfs/js-ipfs/issues/1918)\n* randomly pick preload node ([#2194](https://github.com/ipfs/js-ipfs/issues/2194)) ([f596b01](https://github.com/ipfs/js-ipfs/commit/f596b01))\n* ready promise ([#2094](https://github.com/ipfs/js-ipfs/issues/2094)) ([e0994f2](https://github.com/ipfs/js-ipfs/commit/e0994f2))\n* update Web UI to v2.4.6 ([#2147](https://github.com/ipfs/js-ipfs/issues/2147)) ([a1a9fe3](https://github.com/ipfs/js-ipfs/commit/a1a9fe3))\n\n\n### BREAKING CHANGES\n\n* **gateway:** Gateway now implicitly responds with the contents of `/index.html` when accessing a directory `/` instead of redirecting to `/index.html`.\n\nThis changes current logic (redirect to index.html) to match what\ngo-ipfs does (return index.html without changing URL)\n\nWe also ensure directory URLs always end with '/'\n\nLicense: MIT\nSigned-off-by: Marcin Rataj <lidel@lidel.org>\n\n\n\n<a name=\"0.36.4\"></a>\n## [0.36.4](https://github.com/ipfs/js-ipfs/compare/v0.36.3...v0.36.4) (2019-06-18)\n\n\n\n<a name=\"0.36.3\"></a>\n## [0.36.3](https://github.com/ipfs/js-ipfs/compare/v0.36.2...v0.36.3) (2019-05-30)\n\n\n### Bug Fixes\n\n* double callback in object.links for cbor data ([#2111](https://github.com/ipfs/js-ipfs/issues/2111)) ([5d080c0](https://github.com/ipfs/js-ipfs/commit/5d080c0))\n* fixes rabin chunker truncating files ([#2114](https://github.com/ipfs/js-ipfs/issues/2114)) ([76689ff](https://github.com/ipfs/js-ipfs/commit/76689ff))\n* **package:** update bignumber.js to version 9.0.0 ([#2123](https://github.com/ipfs/js-ipfs/issues/2123)) ([37903ad](https://github.com/ipfs/js-ipfs/commit/37903ad))\n\n\n\n<a name=\"0.36.2\"></a>\n## [0.36.2](https://github.com/ipfs/js-ipfs/compare/v0.36.1...v0.36.2) (2019-05-24)\n\n\n### Bug Fixes\n\n* file support when added as object ([#2105](https://github.com/ipfs/js-ipfs/issues/2105)) ([ba80e40](https://github.com/ipfs/js-ipfs/commit/ba80e40))\n* upgrade electron examples ([#2104](https://github.com/ipfs/js-ipfs/issues/2104)) ([67e1b59](https://github.com/ipfs/js-ipfs/commit/67e1b59))\n\n\n\n<a name=\"0.36.1\"></a>\n## [0.36.1](https://github.com/ipfs/js-ipfs/compare/v0.36.0...v0.36.1) (2019-05-22)\n\n\n### Bug Fixes\n\n* **cli:** make swarm addrs more resilient ([#2083](https://github.com/ipfs/js-ipfs/issues/2083)) ([3792b68](https://github.com/ipfs/js-ipfs/commit/3792b68))\n\n\n\n<a name=\"0.36.0\"></a>\n## [0.36.0](https://github.com/ipfs/js-ipfs/compare/v0.36.0-rc.0...v0.36.0) (2019-05-22)\n\n\n### Bug Fixes\n\n* use trickle builder in daemon mode too ([#2085](https://github.com/ipfs/js-ipfs/issues/2085)) ([62b873f](https://github.com/ipfs/js-ipfs/commit/62b873f))\n* **package:** update libp2p-kad-dht to version 0.15.0 ([#2049](https://github.com/ipfs/js-ipfs/issues/2049)) ([5905760](https://github.com/ipfs/js-ipfs/commit/5905760))\n* browser-mfs example ([#2089](https://github.com/ipfs/js-ipfs/issues/2089)) ([e7d6d3a](https://github.com/ipfs/js-ipfs/commit/e7d6d3a))\n* error when preloding is disabled in the browser ([#2086](https://github.com/ipfs/js-ipfs/issues/2086)) ([a56bcbf](https://github.com/ipfs/js-ipfs/commit/a56bcbf))\n* traverse-ipld-graphs (tree) example ([#2088](https://github.com/ipfs/js-ipfs/issues/2088)) ([b5c652f](https://github.com/ipfs/js-ipfs/commit/b5c652f))\n* update option in exchange files in browser example ([#2087](https://github.com/ipfs/js-ipfs/issues/2087)) ([63469ed](https://github.com/ipfs/js-ipfs/commit/63469ed))\n\n\n\n<a name=\"0.36.0-rc.0\"></a>\n## [0.36.0-rc.0](https://github.com/ipfs/js-ipfs/compare/v0.36.0-pre.0...v0.36.0-rc.0) (2019-05-21)\n\n\n### Code Refactoring\n\n* update ipld formats, async/await mfs/unixfs & base32 cids ([#2068](https://github.com/ipfs/js-ipfs/issues/2068)) ([813048f](https://github.com/ipfs/js-ipfs/commit/813048f)), closes [ipld/js-ipld-dag-pb#137](https://github.com/ipld/js-ipld-dag-pb/issues/137) [ipfs/interface-js-ipfs-core#473](https://github.com/ipfs/interface-js-ipfs-core/issues/473) [ipfs/js-ipfs-http-client#1010](https://github.com/ipfs/js-ipfs-http-client/issues/1010) [ipfs/js-ipfs-http-response#25](https://github.com/ipfs/js-ipfs-http-response/issues/25) [#1995](https://github.com/ipfs/js-ipfs/issues/1995)\n\n\n### BREAKING CHANGES\n\n* The default string encoding for version 1 CIDs has changed to `base32`.\n\nIPLD formats have been updated to the latest versions. IPLD nodes returned by `ipfs.dag` and `ipfs.object` commands have significant breaking changes. If you are using these commands in your application you are likely to encounter the following changes to `dag-pb` nodes (the default node type that IPFS creates):\n\n* `DAGNode` properties have been renamed as follows:\n    * `data` => `Data`\n    * `links` => `Links`\n    * `size` => `size` (Note: no change)\n* `DAGLink` properties have been renamed as follows:\n    * `cid` => `Hash`\n    * `name` => `Name`\n    * `size` => `Tsize`\n\nSee CHANGELOGs for each IPLD format for it's respective changes, you can read more about the [`dag-pb` changes in the CHANGELOG](https://github.com/ipld/js-ipld-dag-pb/blob/master)\n\nLicense: MIT\nSigned-off-by: Alan Shaw <alan.shaw@protocol.ai>\n\n\n\n<a name=\"0.36.0-pre.0\"></a>\n## [0.36.0-pre.0](https://github.com/ipfs/js-ipfs/compare/v0.35.0...v0.36.0-pre.0) (2019-05-17)\n\n\n### Bug Fixes\n\n* **package:** update ipfs-http-client to version 31.0.0 ([#2052](https://github.com/ipfs/js-ipfs/issues/2052)) ([906f8d0](https://github.com/ipfs/js-ipfs/commit/906f8d0))\n* correctly validate ipld config ([#2033](https://github.com/ipfs/js-ipfs/issues/2033)) ([eebc17a](https://github.com/ipfs/js-ipfs/commit/eebc17a))\n* **package:** update hapi-pino to version 6.0.0 ([#2043](https://github.com/ipfs/js-ipfs/issues/2043)) ([f4e3bd0](https://github.com/ipfs/js-ipfs/commit/f4e3bd0))\n\n\n### Features\n\n* add support for File DOM API to files-regular ([#2013](https://github.com/ipfs/js-ipfs/issues/2013)) ([0a08192](https://github.com/ipfs/js-ipfs/commit/0a08192))\n* implement ipfs refs and refs local ([#2004](https://github.com/ipfs/js-ipfs/issues/2004)) ([6dc9075](https://github.com/ipfs/js-ipfs/commit/6dc9075))\n* **gateway:** add streaming, conditional and range requests ([#1989](https://github.com/ipfs/js-ipfs/issues/1989)) ([48a8e75](https://github.com/ipfs/js-ipfs/commit/48a8e75))\n\n\n\n<a name=\"0.35.0\"></a>\n## [0.35.0](https://github.com/ipfs/js-ipfs/compare/v0.35.0-rc.7...v0.35.0) (2019-04-12)\n\n\n\n<a name=\"0.35.0-rc.7\"></a>\n## [0.35.0-rc.7](https://github.com/ipfs/js-ipfs/compare/v0.35.0-rc.6...v0.35.0-rc.7) (2019-04-12)\n\n\n### Bug Fixes\n\n* flakey windows test ([#1987](https://github.com/ipfs/js-ipfs/issues/1987)) ([9708c0a](https://github.com/ipfs/js-ipfs/commit/9708c0a))\n* really disable DHT ([#1991](https://github.com/ipfs/js-ipfs/issues/1991)) ([2470be8](https://github.com/ipfs/js-ipfs/commit/2470be8))\n* remove non default ipld formats in the browser ([#1980](https://github.com/ipfs/js-ipfs/issues/1980)) ([4376121](https://github.com/ipfs/js-ipfs/commit/4376121))\n\n\n### BREAKING CHANGES\n\n* Browser application bundles now include only `ipld-dag-pb`, `ipld-dag-cbor` and `ipld-raw` IPLD codecs. Other codecs should be added manually, see https://github.com/ipfs/js-ipfs/blob/master/README.md#optionsipld for details.\n\n* In Node.js `require('ipfs')`\n    * all IPLD formats included\n* In browser application bundle `require('ipfs')` bundled with webpack/browserify/etc.\n    * only `ipld-dag-pb`, `ipld-dag-cbor` and `ipld-raw` included\n* CDN bundle `<script src=\"https://cdn.jsdelivr.net/npm/ipfs/dist/index.min.js\"></script>`\n    * all IPLD formats included\n\nCo-Authored-By: hugomrdias <mail@hugodias.me>\n\n\n\n<a name=\"0.35.0-rc.6\"></a>\n## [0.35.0-rc.6](https://github.com/ipfs/js-ipfs/compare/v0.35.0-rc.5...v0.35.0-rc.6) (2019-04-11)\n\n\n### Bug Fixes\n\n* avoid logging http errors when its logger is not on ([#1977](https://github.com/ipfs/js-ipfs/issues/1977)) ([20beea2](https://github.com/ipfs/js-ipfs/commit/20beea2))\n\n\n### Features\n\n* recursive dnslink lookups ([#1935](https://github.com/ipfs/js-ipfs/issues/1935)) ([d5a1b89](https://github.com/ipfs/js-ipfs/commit/d5a1b89))\n* use libp2p auto dial ([#1983](https://github.com/ipfs/js-ipfs/issues/1983)) ([7f1fb26](https://github.com/ipfs/js-ipfs/commit/7f1fb26))\n\n\n\n<a name=\"0.35.0-rc.5\"></a>\n## [0.35.0-rc.5](https://github.com/ipfs/js-ipfs/compare/v0.35.0-rc.4...v0.35.0-rc.5) (2019-04-04)\n\n\n### Bug Fixes\n\n* force browserify to load Buffer module ([#1969](https://github.com/ipfs/js-ipfs/issues/1969)) ([3654e50](https://github.com/ipfs/js-ipfs/commit/3654e50))\n* stop IPNS republisher ASAP ([#1976](https://github.com/ipfs/js-ipfs/issues/1976)) ([68561c8](https://github.com/ipfs/js-ipfs/commit/68561c8))\n* update link for multihashes ([#1975](https://github.com/ipfs/js-ipfs/issues/1975)) ([4a01bf6](https://github.com/ipfs/js-ipfs/commit/4a01bf6))\n\n\n### Features\n\n* expose multihashing-async along with other deps ([#1974](https://github.com/ipfs/js-ipfs/issues/1974)) ([6667966](https://github.com/ipfs/js-ipfs/commit/6667966)), closes [#1973](https://github.com/ipfs/js-ipfs/issues/1973)\n\n\n\n<a name=\"0.35.0-rc.4\"></a>\n## [0.35.0-rc.4](https://github.com/ipfs/js-ipfs/compare/v0.35.0-rc.3...v0.35.0-rc.4) (2019-03-28)\n\n\n### Bug Fixes\n\n* CLI parsing of --silent arg ([#1955](https://github.com/ipfs/js-ipfs/issues/1955)) ([1c07779](https://github.com/ipfs/js-ipfs/commit/1c07779)), closes [#1947](https://github.com/ipfs/js-ipfs/issues/1947)\n\n\n### Code Refactoring\n\n* swap joi-browser with superstruct ([#1961](https://github.com/ipfs/js-ipfs/issues/1961)) ([8fb5825](https://github.com/ipfs/js-ipfs/commit/8fb5825))\n\n\n### Performance Improvements\n\n* reduce bundle size ([#1959](https://github.com/ipfs/js-ipfs/issues/1959)) ([a3b6235](https://github.com/ipfs/js-ipfs/commit/a3b6235))\n\n\n### BREAKING CHANGES\n\n* Constructor config validation is now a bit more strict - it does not allow `null` values or unknown properties.\n\n\n\n<a name=\"0.35.0-rc.3\"></a>\n## [0.35.0-rc.3](https://github.com/ipfs/js-ipfs/compare/v0.35.0-rc.2...v0.35.0-rc.3) (2019-03-21)\n\n\n### Bug Fixes\n\n* name resolve arg parsing ([#1958](https://github.com/ipfs/js-ipfs/issues/1958)) ([924690e](https://github.com/ipfs/js-ipfs/commit/924690e))\n\n\n\n<a name=\"0.35.0-rc.2\"></a>\n## [0.35.0-rc.2](https://github.com/ipfs/js-ipfs/compare/v0.35.0-rc.1...v0.35.0-rc.2) (2019-03-21)\n\n\n\n<a name=\"0.35.0-rc.1\"></a>\n## [0.35.0-rc.1](https://github.com/ipfs/js-ipfs/compare/v0.35.0-rc.0...v0.35.0-rc.1) (2019-03-20)\n\n\n### Bug Fixes\n\n* cat deeply nested file ([#1920](https://github.com/ipfs/js-ipfs/issues/1920)) ([dcb453a](https://github.com/ipfs/js-ipfs/commit/dcb453a))\n* handle subdomains for ipfs.dns ([#1933](https://github.com/ipfs/js-ipfs/issues/1933)) ([29072a5](https://github.com/ipfs/js-ipfs/commit/29072a5))\n* only dial to unconnected peers ([#1914](https://github.com/ipfs/js-ipfs/issues/1914)) ([1478652](https://github.com/ipfs/js-ipfs/commit/1478652))\n\n\n### Features\n\n* add HTTP DAG API ([#1930](https://github.com/ipfs/js-ipfs/issues/1930)) ([a033e8b](https://github.com/ipfs/js-ipfs/commit/a033e8b))\n* display version info when starting daemon ([#1915](https://github.com/ipfs/js-ipfs/issues/1915)) ([6b789ee](https://github.com/ipfs/js-ipfs/commit/6b789ee))\n* provide access to multicodec ([#1921](https://github.com/ipfs/js-ipfs/issues/1921)) ([ceec0bc](https://github.com/ipfs/js-ipfs/commit/ceec0bc)), closes [#1913](https://github.com/ipfs/js-ipfs/issues/1913)\n* **issue-1852:** support multiple API and Gateway addresses ([#1903](https://github.com/ipfs/js-ipfs/issues/1903)) ([4ad104d](https://github.com/ipfs/js-ipfs/commit/4ad104d)), closes [#1852](https://github.com/ipfs/js-ipfs/issues/1852)\n\n\n### Performance Improvements\n\n* lower connection manager limits ([#1926](https://github.com/ipfs/js-ipfs/issues/1926)) ([7926349](https://github.com/ipfs/js-ipfs/commit/7926349))\n\n\n\n<a name=\"0.35.0-rc.0\"></a>\n## [0.35.0-rc.0](https://github.com/ipfs/js-ipfs/compare/v0.35.0-pre.0...v0.35.0-rc.0) (2019-03-06)\n\n\n### Bug Fixes\n\n* add support for resolving to the middle of an IPLD block ([#1841](https://github.com/ipfs/js-ipfs/issues/1841)) ([fc08243](https://github.com/ipfs/js-ipfs/commit/fc08243))\n* dht browser disabled ([#1879](https://github.com/ipfs/js-ipfs/issues/1879)) ([7c5a843](https://github.com/ipfs/js-ipfs/commit/7c5a843))\n* ipv6 multiaddr in stdout ([#1854](https://github.com/ipfs/js-ipfs/issues/1854)) ([35fd541](https://github.com/ipfs/js-ipfs/commit/35fd541)), closes [#1853](https://github.com/ipfs/js-ipfs/issues/1853)\n* make clear pins function in tests serial ([#1910](https://github.com/ipfs/js-ipfs/issues/1910)) ([503e5ac](https://github.com/ipfs/js-ipfs/commit/503e5ac)), closes [#1890](https://github.com/ipfs/js-ipfs/issues/1890)\n* pin.rm test EPERM rename ([#1889](https://github.com/ipfs/js-ipfs/issues/1889)) ([c60de74](https://github.com/ipfs/js-ipfs/commit/c60de74))\n* temporarily disable random walk dht discovery ([#1907](https://github.com/ipfs/js-ipfs/issues/1907)) ([3fff46a](https://github.com/ipfs/js-ipfs/commit/3fff46a))\n\n\n### Code Refactoring\n\n* export types and utilities statically ([#1908](https://github.com/ipfs/js-ipfs/issues/1908)) ([79d7fef](https://github.com/ipfs/js-ipfs/commit/79d7fef))\n\n\n### Features\n\n* add `--enable-preload` to enable/disable preloading for daemons ([#1909](https://github.com/ipfs/js-ipfs/issues/1909)) ([9470900](https://github.com/ipfs/js-ipfs/commit/9470900))\n* limit connections number ([#1872](https://github.com/ipfs/js-ipfs/issues/1872)) ([bebce7f](https://github.com/ipfs/js-ipfs/commit/bebce7f))\n\n\n### BREAKING CHANGES\n\n* `ipfs.util.isIPFS` and `ipfs.util.crypto` have moved to static exports and should be accessed via `const { isIPFS, crypto } = require('ipfs')`.\n\nThe modules available under `ipfs.types.*` have also become static exports.\n\nLicense: MIT\nSigned-off-by: Alan Shaw <alan.shaw@protocol.ai>\n* `ipfs.resolve` now supports resolving to the middle of an IPLD block instead of erroring.\n\nGiven:\n\n```js\nb = {\"c\": \"some value\"}\na = {\"b\": {\"/\": cidOf(b) }}\n```\n\n`ipfs resolve /ipld/cidOf(a)/b/c` should return `/ipld/cidOf(b)/c`. That is, it resolves the path as much as it can.\n\nPreviously it would simply fail with an error.\n\nLicense: MIT\nSigned-off-by: Alan Shaw <alan.shaw@protocol.ai>\n\n\n\n<a name=\"0.35.0-pre.0\"></a>\n## [0.35.0-pre.0](https://github.com/ipfs/js-ipfs/compare/v0.34.4...v0.35.0-pre.0) (2019-02-11)\n\n\n### Bug Fixes\n\n* add missing libp2p-websocket-star dep ([#1869](https://github.com/ipfs/js-ipfs/issues/1869)) ([7cba3dd](https://github.com/ipfs/js-ipfs/commit/7cba3dd))\n* path to cid-tool commands ([#1866](https://github.com/ipfs/js-ipfs/issues/1866)) ([506f5be](https://github.com/ipfs/js-ipfs/commit/506f5be))\n* swallowed errors ([#1860](https://github.com/ipfs/js-ipfs/issues/1860)) ([47e2b9e](https://github.com/ipfs/js-ipfs/commit/47e2b9e)), closes [#1835](https://github.com/ipfs/js-ipfs/issues/1835) [#1858](https://github.com/ipfs/js-ipfs/issues/1858)\n\n\n### Chores\n\n* rename local option to offline ([#1850](https://github.com/ipfs/js-ipfs/issues/1850)) ([bbe561b](https://github.com/ipfs/js-ipfs/commit/bbe561b))\n\n\n### Features\n\n* interoperable DHT ([#856](https://github.com/ipfs/js-ipfs/issues/856)) ([77a0957](https://github.com/ipfs/js-ipfs/commit/77a0957))\n\n\n### BREAKING CHANGES\n\n* `--local` option has been renamed to `--offline`\n\n\n\n<a name=\"0.34.4\"></a>\n## [0.34.4](https://github.com/ipfs/js-ipfs/compare/v0.34.3...v0.34.4) (2019-01-24)\n\n\n### Features\n\n* support _dnslink subdomain specified dnslinks ([#1843](https://github.com/ipfs/js-ipfs/issues/1843)) ([a17253e](https://github.com/ipfs/js-ipfs/commit/a17253e))\n\n\n\n<a name=\"0.34.3\"></a>\n## [0.34.3](https://github.com/ipfs/js-ipfs/compare/v0.34.2...v0.34.3) (2019-01-24)\n\n\n### Bug Fixes\n\n* add cors support for preload-mock-server and update aegir ([#1839](https://github.com/ipfs/js-ipfs/issues/1839)) ([2d45c9d](https://github.com/ipfs/js-ipfs/commit/2d45c9d))\n\n\n\n<a name=\"0.34.2\"></a>\n## [0.34.2](https://github.com/ipfs/js-ipfs/compare/v0.34.1...v0.34.2) (2019-01-21)\n\n\n### Bug Fixes\n\n* race condition causing Database is not open error ([#1834](https://github.com/ipfs/js-ipfs/issues/1834)) ([6066c97](https://github.com/ipfs/js-ipfs/commit/6066c97))\n\n\n### Features\n\n* use ws-star-multi instead of ws-star ([#1793](https://github.com/ipfs/js-ipfs/issues/1793)) ([21fd4d1](https://github.com/ipfs/js-ipfs/commit/21fd4d1))\n\n\n\n<a name=\"0.34.1\"></a>\n## [0.34.1](https://github.com/ipfs/js-ipfs/compare/v0.34.0...v0.34.1) (2019-01-21)\n\n\n### Features\n\n* pipe to add ([#1833](https://github.com/ipfs/js-ipfs/issues/1833)) ([ea53071](https://github.com/ipfs/js-ipfs/commit/ea53071))\n\n\n\n<a name=\"0.34.0\"></a>\n## [0.34.0](https://github.com/ipfs/js-ipfs/compare/v0.34.0-rc.1...v0.34.0) (2019-01-17)\n\n\n\n<a name=\"0.34.0-rc.1\"></a>\n## [0.34.0-rc.1](https://github.com/ipfs/js-ipfs/compare/v0.34.0-rc.0...v0.34.0-rc.1) (2019-01-15)\n\n\n### Bug Fixes\n\n* sharness tests ([#1787](https://github.com/ipfs/js-ipfs/issues/1787)) ([48d3e2b](https://github.com/ipfs/js-ipfs/commit/48d3e2b))\n\n\n### Code Refactoring\n\n* switch to bignumber.js ([#1803](https://github.com/ipfs/js-ipfs/issues/1803)) ([6de6adf](https://github.com/ipfs/js-ipfs/commit/6de6adf))\n\n\n### Features\n\n* update to Web UI v2.3.2 ([#1807](https://github.com/ipfs/js-ipfs/issues/1807)) ([8ca6471](https://github.com/ipfs/js-ipfs/commit/8ca6471))\n* update Web UI to v2.3.0 ([#1786](https://github.com/ipfs/js-ipfs/issues/1786)) ([7bcc496](https://github.com/ipfs/js-ipfs/commit/7bcc496))\n\n\n### BREAKING CHANGES\n\n* All API methods that returned [`big.js`](https://github.com/MikeMcl/big.js/) instances now return [`bignumber.js`](https://github.com/MikeMcl/bignumber.js/) instances.\n\nLicense: MIT\nSigned-off-by: Alan Shaw <alan.shaw@protocol.ai>\n\n\n\n<a name=\"0.34.0-rc.0\"></a>\n## [0.34.0-rc.0](https://github.com/ipfs/js-ipfs/compare/v0.34.0-pre.0...v0.34.0-rc.0) (2018-12-18)\n\n\n### Bug Fixes\n\n* link to Github profile for David Dias ([3659d7e](https://github.com/ipfs/js-ipfs/commit/3659d7e))\n* streaming cat over http api ([#1760](https://github.com/ipfs/js-ipfs/issues/1760)) ([3ded576](https://github.com/ipfs/js-ipfs/commit/3ded576))\n\n\n### Features\n\n* add `addFromFs` method ([#1777](https://github.com/ipfs/js-ipfs/issues/1777)) ([7315aa1](https://github.com/ipfs/js-ipfs/commit/7315aa1))\n* add from url/stream ([#1773](https://github.com/ipfs/js-ipfs/issues/1773)) ([b6a7ab6](https://github.com/ipfs/js-ipfs/commit/b6a7ab6))\n* add slient option ([#1712](https://github.com/ipfs/js-ipfs/issues/1712)) ([593334b](https://github.com/ipfs/js-ipfs/commit/593334b))\n* cid base option ([#1552](https://github.com/ipfs/js-ipfs/issues/1552)) ([6d46e2e](https://github.com/ipfs/js-ipfs/commit/6d46e2e)), closes [/github.com/ipfs/go-ipfs/issues/5349#issuecomment-445104823](https://github.com//github.com/ipfs/go-ipfs/issues/5349/issues/issuecomment-445104823)\n\n\n\n<a name=\"0.34.0-pre.0\"></a>\n## [0.34.0-pre.0](https://github.com/ipfs/js-ipfs/compare/v0.33.1...v0.34.0-pre.0) (2018-12-07)\n\n\n### Bug Fixes\n\n* add dash case to pin cli ([#1719](https://github.com/ipfs/js-ipfs/issues/1719)) ([eacd580](https://github.com/ipfs/js-ipfs/commit/eacd580))\n* add missing dependencies ([#1663](https://github.com/ipfs/js-ipfs/issues/1663)) ([4bcf4a7](https://github.com/ipfs/js-ipfs/commit/4bcf4a7))\n* allow disabling mfs preload from config ([#1733](https://github.com/ipfs/js-ipfs/issues/1733)) ([5f66538](https://github.com/ipfs/js-ipfs/commit/5f66538))\n* better error message when pubsub is not enabled ([#1729](https://github.com/ipfs/js-ipfs/issues/1729)) ([5237dd9](https://github.com/ipfs/js-ipfs/commit/5237dd9))\n* examples after files API refactor ([#1740](https://github.com/ipfs/js-ipfs/issues/1740)) ([34ec036](https://github.com/ipfs/js-ipfs/commit/34ec036))\n* ipns datastore key ([#1741](https://github.com/ipfs/js-ipfs/issues/1741)) ([a39770e](https://github.com/ipfs/js-ipfs/commit/a39770e))\n* make circuit relay test ([#1710](https://github.com/ipfs/js-ipfs/issues/1710)) ([345ce91](https://github.com/ipfs/js-ipfs/commit/345ce91))\n* remove electron-webrtc and wrtc for now ([#1718](https://github.com/ipfs/js-ipfs/issues/1718)) ([b6b50d5](https://github.com/ipfs/js-ipfs/commit/b6b50d5))\n\n\n### Code Refactoring\n\n* files API ([#1720](https://github.com/ipfs/js-ipfs/issues/1720)) ([a82a5dc](https://github.com/ipfs/js-ipfs/commit/a82a5dc))\n* object APIs write methods now return CIDs ([#1730](https://github.com/ipfs/js-ipfs/issues/1730)) ([ac5fa8e](https://github.com/ipfs/js-ipfs/commit/ac5fa8e)), closes [/github.com/ipfs/interface-ipfs-core/pull/388#pullrequestreview-173866270](https://github.com//github.com/ipfs/interface-ipfs-core/pull/388/issues/pullrequestreview-173866270)\n\n\n### Features\n\n* ipns over dht ([#1725](https://github.com/ipfs/js-ipfs/issues/1725)) ([1a943f8](https://github.com/ipfs/js-ipfs/commit/1a943f8))\n* ipns over pubsub ([#1559](https://github.com/ipfs/js-ipfs/issues/1559)) ([8712542](https://github.com/ipfs/js-ipfs/commit/8712542))\n* Web UI updated to v2.2.0 ([#1711](https://github.com/ipfs/js-ipfs/issues/1711)) ([b2158bc](https://github.com/ipfs/js-ipfs/commit/b2158bc))\n\n\n### Performance Improvements\n\n* lazy load IPLD formats ([#1704](https://github.com/ipfs/js-ipfs/issues/1704)) ([aefb261](https://github.com/ipfs/js-ipfs/commit/aefb261))\n\n\n### BREAKING CHANGES\n\n* Object API refactor.\n\nObject API methods that write DAG nodes now return a [CID](https://www.npmjs.com/package/cids) instead of a DAG node. Affected methods:\n\n* `ipfs.object.new`\n* `ipfs.object.patch.addLink`\n* `ipfs.object.patch.appendData`\n* `ipfs.object.patch.rmLink`\n* `ipfs.object.patch.setData`\n* `ipfs.object.put`\n\nExample:\n\n```js\n// Before\nconst dagNode = await ipfs.object.new()\n```\n\n```js\n// After\nconst cid = await ipfs.object.new() // now returns a CID\nconst dagNode = await ipfs.object.get(cid) // fetch the DAG node that was created\n```\n\nIMPORTANT: `DAGNode` instances, which are part of the IPLD dag-pb format have been refactored.\n\nThese instances no longer have `multihash`, `cid` or `serialized` properties.\n\nThis effects the following API methods that return these types of objects:\n\n* `ipfs.object.get`\n* `ipfs.dag.get`\n\nSee https://github.com/ipld/js-ipld-dag-pb/pull/99 for more information.\n\nLicense: MIT\nSigned-off-by: Alan Shaw <alan.shaw@protocol.ai>\n* Files API methods `add*`, `cat*`, `get*` have moved from `files` to the root namespace.\n\nSpecifically, the following changes have been made:\n\n* `ipfs.files.add` => `ipfs.add`\n* `ipfs.files.addPullStream` => `ipfs.addPullStream`\n* `ipfs.files.addReadableStream` => `ipfs.addReadableStream`\n* `ipfs.files.cat` => `ipfs.cat`\n* `ipfs.files.catPullStream` => `ipfs.catPullStream`\n* `ipfs.files.catReadableStream` => `ipfs.catReadableStream`\n* `ipfs.files.get` => `ipfs.get`\n* `ipfs.files.getPullStream` => `ipfs.getPullStream`\n* `ipfs.files.getReadableStream` => `ipfs.getReadableStream`\n\nLicense: MIT\nSigned-off-by: Alan Shaw <alan.shaw@protocol.ai>\n\n\n\n<a name=\"0.33.1\"></a>\n## [0.33.1](https://github.com/ipfs/js-ipfs/compare/v0.33.0...v0.33.1) (2018-11-05)\n\n\n### Bug Fixes\n\n* over eager preload ([#1693](https://github.com/ipfs/js-ipfs/issues/1693)) ([f14c20d](https://github.com/ipfs/js-ipfs/commit/f14c20d))\n\n\n\n<a name=\"0.33.0\"></a>\n## [0.33.0](https://github.com/ipfs/js-ipfs/compare/v0.33.0-rc.4...v0.33.0) (2018-11-01)\n\n\n\n<a name=\"0.33.0-rc.4\"></a>\n## [0.33.0-rc.4](https://github.com/ipfs/js-ipfs/compare/v0.33.0-rc.3...v0.33.0-rc.4) (2018-11-01)\n\n\n### Bug Fixes\n\n* remove accidentally committed code ([66fa8ef](https://github.com/ipfs/js-ipfs/commit/66fa8ef))\n* remove local option from global commands ([#1648](https://github.com/ipfs/js-ipfs/issues/1648)) ([8e963f9](https://github.com/ipfs/js-ipfs/commit/8e963f9))\n* remove npm script ([df32ac4](https://github.com/ipfs/js-ipfs/commit/df32ac4))\n* remove unused deps ([f7189fb](https://github.com/ipfs/js-ipfs/commit/f7189fb))\n* use class is function on ipns ([#1617](https://github.com/ipfs/js-ipfs/issues/1617)) ([c240d49](https://github.com/ipfs/js-ipfs/commit/c240d49)), closes [js-peer-id#84](https://github.com/js-peer-id/issues/84) [interface-datastore#24](https://github.com/interface-datastore/issues/24) [#1615](https://github.com/ipfs/js-ipfs/issues/1615)\n\n\n### Chores\n\n* remove ipld formats re-export ([#1626](https://github.com/ipfs/js-ipfs/issues/1626)) ([3ee7b5e](https://github.com/ipfs/js-ipfs/commit/3ee7b5e))\n* update to js-ipld 0.19 ([#1668](https://github.com/ipfs/js-ipfs/issues/1668)) ([74edafd](https://github.com/ipfs/js-ipfs/commit/74edafd))\n\n\n### Features\n\n* add support to pass config in the init cmd ([#1662](https://github.com/ipfs/js-ipfs/issues/1662)) ([588891c](https://github.com/ipfs/js-ipfs/commit/588891c))\n* get Ping to work properly ([27d5a57](https://github.com/ipfs/js-ipfs/commit/27d5a57))\n\n\n### BREAKING CHANGES\n\n* dag-cbor nodes now represent links as CID objects\n\nThe API for [dag-cbor](https://github.com/ipld/js-ipld-dag-cbor) changed.\nLinks are no longer represented as JSON objects (`{\"/\": \"base-encoded-cid\"}`,\nbut as [CID objects](https://github.com/ipld/js-cid). `ipfs.dag.get()` and now always return links as CID objects. `ipfs.dag.put()` also expects links to be represented as CID objects. The old-style JSON objects representation is still\nsupported, but deprecated.\n\nPrior to this change:\n\n```js\nconst cid = new CID('QmXed8RihWcWFXRRmfSRG9yFjEbXNxu1bDwgCFAN8Dxcq5')\n// Link as JSON object representation\nconst putCid = await ipfs.dag.put({link: {'/': cid.toBaseEncodedString()}})\nconst result = await ipfs.dag.get(putCid)\nconsole.log(result.value)\n\n```\n\nOutput:\n\n```js\n{ link:\n   { '/':\n      <Buffer 12 20 8a…> } }\n```\n\nNow:\n\n```js\nconst cid = new CID('QmXed8RihWcWFXRRmfSRG9yFjEbXNxu1bDwgCFAN8Dxcq5')\n// Link as CID object\nconst putCid = await ipfs.dag.put({link: cid})\nconst result = await ipfs.dag.get(putCid)\nconsole.log(result.value)\n```\n\nOutput:\n\n```js\n{ link:\n   CID {\n     codec: 'dag-pb',\n     version: 0,\n     multihash:\n      <Buffer 12 20 8a…> } }\n```\n\nSee https://github.com/ipld/ipld/issues/44 for more information on why this\nchange was made.\n* remove `types.dagCBOR` and `types.dagPB` from public API\n\nIf you need the `ipld-dag-cbor` or `ipld-dag-pb` module in the Browser,\nyou need to bundle them yourself.\n\n\n\n<a name=\"0.33.0-rc.3\"></a>\n## [0.33.0-rc.3](https://github.com/ipfs/js-ipfs/compare/v0.33.0-rc.2...v0.33.0-rc.3) (2018-10-24)\n\n\n\n<a name=\"0.33.0-rc.2\"></a>\n## [0.33.0-rc.2](https://github.com/ipfs/js-ipfs/compare/v0.33.0-rc.1...v0.33.0-rc.2) (2018-10-23)\n\n\n\n<a name=\"0.33.0-rc.1\"></a>\n## [0.33.0-rc.1](https://github.com/ipfs/js-ipfs/compare/v0.32.3...v0.33.0-rc.1) (2018-10-19)\n\n\n### Bug Fixes\n\n* make ipfs.ping() options optional ([#1627](https://github.com/ipfs/js-ipfs/issues/1627)) ([08f06b6](https://github.com/ipfs/js-ipfs/commit/08f06b6)), closes [#1616](https://github.com/ipfs/js-ipfs/issues/1616)\n\n\n### Features\n\n* **gateway:** X-Ipfs-Path, Etag, Cache-Control, Suborigin ([#1537](https://github.com/ipfs/js-ipfs/issues/1537)) ([fc5bef7](https://github.com/ipfs/js-ipfs/commit/fc5bef7))\n* add cid command ([#1560](https://github.com/ipfs/js-ipfs/issues/1560)) ([a22c791](https://github.com/ipfs/js-ipfs/commit/a22c791))\n* show Web UI url in daemon output ([#1595](https://github.com/ipfs/js-ipfs/issues/1595)) ([9a82b05](https://github.com/ipfs/js-ipfs/commit/9a82b05))\n* update to Web UI 2.0 ([#1647](https://github.com/ipfs/js-ipfs/issues/1647)) ([aea85aa](https://github.com/ipfs/js-ipfs/commit/aea85aa))\n\n\n\n<a name=\"0.32.3\"></a>\n## [0.32.3](https://github.com/ipfs/js-ipfs/compare/v0.32.2...v0.32.3) (2018-09-28)\n\n\n### Bug Fixes\n\n* allow null/undefined options ([#1581](https://github.com/ipfs/js-ipfs/issues/1581)) ([c73bd2f](https://github.com/ipfs/js-ipfs/commit/c73bd2f)), closes [#1574](https://github.com/ipfs/js-ipfs/issues/1574)\n* block.put with non default options ([#1600](https://github.com/ipfs/js-ipfs/issues/1600)) ([4ba0a24](https://github.com/ipfs/js-ipfs/commit/4ba0a24))\n* ipns datastore get not found ([#1558](https://github.com/ipfs/js-ipfs/issues/1558)) ([4e99cf5](https://github.com/ipfs/js-ipfs/commit/4e99cf5))\n* report correct size for raw dag nodes ([#1591](https://github.com/ipfs/js-ipfs/issues/1591)) ([549f2f6](https://github.com/ipfs/js-ipfs/commit/549f2f6)), closes [#1585](https://github.com/ipfs/js-ipfs/issues/1585)\n* revert libp2p records being signed for ipns ([#1570](https://github.com/ipfs/js-ipfs/issues/1570)) ([855b3bd](https://github.com/ipfs/js-ipfs/commit/855b3bd))\n\n\n\n<a name=\"0.32.2\"></a>\n## [0.32.2](https://github.com/ipfs/js-ipfs/compare/v0.32.1...v0.32.2) (2018-09-19)\n\n\n### Bug Fixes\n\n* coerce key gen size to number ([#1582](https://github.com/ipfs/js-ipfs/issues/1582)) ([25d820d](https://github.com/ipfs/js-ipfs/commit/25d820d))\n\n\n\n<a name=\"0.32.1\"></a>\n## [0.32.1](https://github.com/ipfs/js-ipfs/compare/v0.32.0...v0.32.1) (2018-09-18)\n\n\n### Bug Fixes\n\n* add libp2p-crypto to deps list ([#1572](https://github.com/ipfs/js-ipfs/issues/1572)) ([7eaf571](https://github.com/ipfs/js-ipfs/commit/7eaf571)), closes [#1571](https://github.com/ipfs/js-ipfs/issues/1571)\n* enable tests in node that were not being included ([#1499](https://github.com/ipfs/js-ipfs/issues/1499)) ([2585431](https://github.com/ipfs/js-ipfs/commit/2585431))\n* fix `block rm` command ([#1576](https://github.com/ipfs/js-ipfs/issues/1576)) ([af30ea5](https://github.com/ipfs/js-ipfs/commit/af30ea5))\n* mfs preload test ([#1551](https://github.com/ipfs/js-ipfs/issues/1551)) ([7c7a5a6](https://github.com/ipfs/js-ipfs/commit/7c7a5a6))\n\n\n### Performance Improvements\n\n* faster startup time ([#1542](https://github.com/ipfs/js-ipfs/issues/1542)) ([2790e6d](https://github.com/ipfs/js-ipfs/commit/2790e6d))\n\n\n\n<a name=\"0.32.0\"></a>\n## [0.32.0](https://github.com/ipfs/js-ipfs/compare/v0.32.0-rc.2...v0.32.0) (2018-09-11)\n\n\n### Bug Fixes\n\n* ipns publish resolve option overwritten ([#1556](https://github.com/ipfs/js-ipfs/issues/1556)) ([ef7d2c8](https://github.com/ipfs/js-ipfs/commit/ef7d2c8))\n\n\n### Features\n\n* Added `ipfs.name.publish` and `ipfs.name.resolve`. This only works on your local node for the moment until the DHT lands. [API docs can be found here](https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/SPEC/NAME.md).\n* Added `ipfs.resolve` API. Note that this is a partial implementation allowing you to resolve IPFS paths like `/ipfs/QmRootHash/path/to/file` to `/ipfs/QmFileHash`. It does not support IPNS yet.\n* `ipfs.files.add*` now supports a `chunker` option, see [the API docs](https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/SPEC/FILES.md#filesadd) for details\n\n\n<a name=\"0.31.7\"></a>\n## [0.31.7](https://github.com/ipfs/js-ipfs/compare/v0.31.6...v0.31.7) (2018-08-20)\n\n\n### Bug Fixes\n\n* fails to start when preload disabled ([#1516](https://github.com/ipfs/js-ipfs/issues/1516)) ([511ab47](https://github.com/ipfs/js-ipfs/commit/511ab47)), closes [#1514](https://github.com/ipfs/js-ipfs/issues/1514)\n* npm publishes examples folder ([#1513](https://github.com/ipfs/js-ipfs/issues/1513)) ([4a68ac1](https://github.com/ipfs/js-ipfs/commit/4a68ac1))\n\n\n\n<a name=\"0.31.6\"></a>\n## [0.31.6](https://github.com/ipfs/js-ipfs/compare/v0.31.5...v0.31.6) (2018-08-17)\n\n\n### Features\n\n* adds data-encoding argument to control data encoding ([#1420](https://github.com/ipfs/js-ipfs/issues/1420)) ([1eb8485](https://github.com/ipfs/js-ipfs/commit/1eb8485))\n\n\n\n<a name=\"0.31.5\"></a>\n## [0.31.5](https://github.com/ipfs/js-ipfs/compare/v0.31.4...v0.31.5) (2018-08-17)\n\n\n### Bug Fixes\n\n* add missing space after emoji ([5cde7c1](https://github.com/ipfs/js-ipfs/commit/5cde7c1))\n* improper input validation ([#1506](https://github.com/ipfs/js-ipfs/issues/1506)) ([91a482b](https://github.com/ipfs/js-ipfs/commit/91a482b))\n* object.patch.rmLink not working ([#1508](https://github.com/ipfs/js-ipfs/issues/1508)) ([afd3255](https://github.com/ipfs/js-ipfs/commit/afd3255))\n* stub out call to fetch for ipfs.dns test in browser ([#1512](https://github.com/ipfs/js-ipfs/issues/1512)) ([86c3d81](https://github.com/ipfs/js-ipfs/commit/86c3d81))\n\n\n\n<a name=\"0.31.4\"></a>\n## [0.31.4](https://github.com/ipfs/js-ipfs/compare/v0.31.3...v0.31.4) (2018-08-09)\n\n\n### Bug Fixes\n\n* consistent badge style in docs ([#1494](https://github.com/ipfs/js-ipfs/issues/1494)) ([4a72e23](https://github.com/ipfs/js-ipfs/commit/4a72e23))\n* files.ls and files.read*Stream tests ([#1493](https://github.com/ipfs/js-ipfs/issues/1493)) ([a0bc79b](https://github.com/ipfs/js-ipfs/commit/a0bc79b))\n\n\n\n<a name=\"0.31.3\"></a>\n## [0.31.3](https://github.com/ipfs/js-ipfs/compare/v0.31.2...v0.31.3) (2018-08-09)\n\n\n### Bug Fixes\n\n* failing tests in master ([#1488](https://github.com/ipfs/js-ipfs/issues/1488)) ([e607560](https://github.com/ipfs/js-ipfs/commit/e607560))\n* **dag:** check dag.put options for plain object ([#1480](https://github.com/ipfs/js-ipfs/issues/1480)) ([d0b671b](https://github.com/ipfs/js-ipfs/commit/d0b671b)), closes [#1479](https://github.com/ipfs/js-ipfs/issues/1479)\n* **dht:** allow for options object in `findProvs()` API ([#1457](https://github.com/ipfs/js-ipfs/issues/1457)) ([99911b1](https://github.com/ipfs/js-ipfs/commit/99911b1)), closes [#1322](https://github.com/ipfs/js-ipfs/issues/1322)\n\n\n\n<a name=\"0.31.2\"></a>\n## [0.31.2](https://github.com/ipfs/js-ipfs/compare/v0.31.1...v0.31.2) (2018-08-02)\n\n\n### Bug Fixes\n\n* fix content-type by doing a fall-back using extensions ([#1482](https://github.com/ipfs/js-ipfs/issues/1482)) ([d528b3f](https://github.com/ipfs/js-ipfs/commit/d528b3f))\n\n\n\n<a name=\"0.31.1\"></a>\n## [0.31.1](https://github.com/ipfs/js-ipfs/compare/v0.31.0...v0.31.1) (2018-07-29)\n\n\n### Bug Fixes\n\n* logo link ([a9219ad](https://github.com/ipfs/js-ipfs/commit/a9219ad))\n* XMLHTTPRequest is deprecated and unavailable in service workers ([#1478](https://github.com/ipfs/js-ipfs/issues/1478)) ([7d6f0ca](https://github.com/ipfs/js-ipfs/commit/7d6f0ca))\n\n\n\n<a name=\"0.31.0\"></a>\n## [0.31.0](https://github.com/ipfs/js-ipfs/compare/v0.30.1...v0.31.0) (2018-07-29)\n\n\n### Bug Fixes\n\n* emit boot error only once ([#1472](https://github.com/ipfs/js-ipfs/issues/1472)) ([45b80a0](https://github.com/ipfs/js-ipfs/commit/45b80a0))\n\n\n### Features\n\n* preload content ([#1464](https://github.com/ipfs/js-ipfs/issues/1464)) ([bffe080](https://github.com/ipfs/js-ipfs/commit/bffe080)), closes [#1459](https://github.com/ipfs/js-ipfs/issues/1459)\n* preload on content fetch requests ([#1475](https://github.com/ipfs/js-ipfs/issues/1475)) ([649b755](https://github.com/ipfs/js-ipfs/commit/649b755)), closes [#1473](https://github.com/ipfs/js-ipfs/issues/1473)\n* remove decomissioned bootstrappers ([e3868f4](https://github.com/ipfs/js-ipfs/commit/e3868f4))\n* rm decomissioned bootstrappers - nodejs ([90e9f68](https://github.com/ipfs/js-ipfs/commit/90e9f68))\n* support --raw-leaves ([#1454](https://github.com/ipfs/js-ipfs/issues/1454)) ([1f63e8c](https://github.com/ipfs/js-ipfs/commit/1f63e8c))\n\n\n### Reverts\n\n* docs: add migration note about upgrading from < 0.30.0 ([#1450](https://github.com/ipfs/js-ipfs/issues/1450)) ([#1456](https://github.com/ipfs/js-ipfs/issues/1456)) ([f4344b0](https://github.com/ipfs/js-ipfs/commit/f4344b0))\n\n\n\n<a name=\"0.30.1\"></a>\n## [0.30.1](https://github.com/ipfs/js-ipfs/compare/v0.30.0...v0.30.1) (2018-07-17)\n\n\n### Bug Fixes\n\n* aegir docs fails if outer funtion is called pin ([#1429](https://github.com/ipfs/js-ipfs/issues/1429)) ([a08a17d](https://github.com/ipfs/js-ipfs/commit/a08a17d))\n* double pre start ([#1437](https://github.com/ipfs/js-ipfs/issues/1437)) ([e6ad63e](https://github.com/ipfs/js-ipfs/commit/e6ad63e))\n* fixing circuit-relaying example ([#1443](https://github.com/ipfs/js-ipfs/issues/1443)) ([a681fc5](https://github.com/ipfs/js-ipfs/commit/a681fc5)), closes [#1423](https://github.com/ipfs/js-ipfs/issues/1423)\n\n\n\n<a name=\"0.30.0\"></a>\n## [0.30.0](https://github.com/ipfs/js-ipfs/compare/v0.29.3...v0.30.0) (2018-07-09)\n\n\n### Bug Fixes\n\n* allow put empty block & add X-Stream-Output header on get ([#1408](https://github.com/ipfs/js-ipfs/issues/1408)) ([52f7aa7](https://github.com/ipfs/js-ipfs/commit/52f7aa7))\n* broken contributing links ([#1386](https://github.com/ipfs/js-ipfs/issues/1386)) ([cd449ff](https://github.com/ipfs/js-ipfs/commit/cd449ff))\n* do not stringify output of object data ([#1398](https://github.com/ipfs/js-ipfs/issues/1398)) ([4e51a69](https://github.com/ipfs/js-ipfs/commit/4e51a69))\n* **dag:** fix default hash algorithm for put() api ([#1419](https://github.com/ipfs/js-ipfs/issues/1419)) ([1a36375](https://github.com/ipfs/js-ipfs/commit/1a36375))\n* **dag:** make options in `put` API optional ([#1415](https://github.com/ipfs/js-ipfs/issues/1415)) ([d299ed7](https://github.com/ipfs/js-ipfs/commit/d299ed7)), closes [#1395](https://github.com/ipfs/js-ipfs/issues/1395)\n* **tests:** loosen assertion for bitswap.stat test ([#1404](https://github.com/ipfs/js-ipfs/issues/1404)) ([4290256](https://github.com/ipfs/js-ipfs/commit/4290256))\n* update hlsjs-ipfs-loader version ([#1422](https://github.com/ipfs/js-ipfs/issues/1422)) ([6b14812](https://github.com/ipfs/js-ipfs/commit/6b14812))\n\n\n### Features\n\n* (BREAKING CHANGE) new libp2p configuration ([#1401](https://github.com/ipfs/js-ipfs/issues/1401)) ([9c60909](https://github.com/ipfs/js-ipfs/commit/9c60909))\n* expose libp2p connection manager configuration options ([#1410](https://github.com/ipfs/js-ipfs/issues/1410)) ([2615f76](https://github.com/ipfs/js-ipfs/commit/2615f76))\n* implement bitswap.wantlist peerid and bitswap.unwant ([#1349](https://github.com/ipfs/js-ipfs/issues/1349)) ([45b705d](https://github.com/ipfs/js-ipfs/commit/45b705d))\n* mfs implementation ([#1360](https://github.com/ipfs/js-ipfs/issues/1360)) ([871d24e](https://github.com/ipfs/js-ipfs/commit/871d24e)), closes [#1425](https://github.com/ipfs/js-ipfs/issues/1425)\n* modular interface tests ([#1389](https://github.com/ipfs/js-ipfs/issues/1389)) ([18888be](https://github.com/ipfs/js-ipfs/commit/18888be))\n* pin API ([#1045](https://github.com/ipfs/js-ipfs/issues/1045)) ([2a5cc5e](https://github.com/ipfs/js-ipfs/commit/2a5cc5e)), closes [#1249](https://github.com/ipfs/js-ipfs/issues/1249)\n\n\n### Performance Improvements\n\n* use lodash ([#1414](https://github.com/ipfs/js-ipfs/issues/1414)) ([5637330](https://github.com/ipfs/js-ipfs/commit/5637330))\n\n\n### BREAKING CHANGES\n\n* libp2p configuration has changed\n\n    * old: `libp2p.modules.discovery`\n    * new: `libp2p.modules.peerDiscovery`\n\nLicense: MIT\nSigned-off-by: David Dias <mail@daviddias.me>\n\nLicense: MIT\nSigned-off-by: Alan Shaw <alan@tableflip.io>\n\n\n\n<a name=\"0.29.3\"></a>\n## [0.29.3](https://github.com/ipfs/js-ipfs/compare/v0.29.2...v0.29.3) (2018-06-04)\n\n\n### Bug Fixes\n\n* **repo:** do not hang on calls to repo gc ([9fff46f](https://github.com/ipfs/js-ipfs/commit/9fff46f))\n\n\n\n<a name=\"0.29.2\"></a>\n## [0.29.2](https://github.com/ipfs/js-ipfs/compare/v0.29.1...v0.29.2) (2018-06-01)\n\n\n### Bug Fixes\n\n* adds missing breaking changes for 0.29 to changelog ([#1370](https://github.com/ipfs/js-ipfs/issues/1370)) ([61ba99e](https://github.com/ipfs/js-ipfs/commit/61ba99e))\n* dont fail on uninitialized repo ([#1374](https://github.com/ipfs/js-ipfs/issues/1374)) ([6f0a95b](https://github.com/ipfs/js-ipfs/commit/6f0a95b))\n\n\n\n<a name=\"0.29.1\"></a>\n## [0.29.1](https://github.com/ipfs/js-ipfs/compare/v0.29.0...v0.29.1) (2018-05-30)\n\n\n### Bug Fixes\n\n* check for repo uninitialized error ([dcf5ea5](https://github.com/ipfs/js-ipfs/commit/dcf5ea5))\n* update ipfs-repo errors require ([4d1318d](https://github.com/ipfs/js-ipfs/commit/4d1318d))\n\n\n\n<a name=\"0.29.0\"></a>\n## [0.29.0](https://github.com/ipfs/js-ipfs/compare/v0.28.2...v0.29.0) (2018-05-29)\n\n\n### Bug Fixes\n\n* Add ipfs path to cli help ([64c3bfb](https://github.com/ipfs/js-ipfs/commit/64c3bfb))\n* change ^ to ~ on 0.x.x deps ([#1345](https://github.com/ipfs/js-ipfs/issues/1345)) ([de95989](https://github.com/ipfs/js-ipfs/commit/de95989))\n* change default config from JSON file to JS module to prevent having it doubly used ([#1324](https://github.com/ipfs/js-ipfs/issues/1324)) ([c3d2d1e](https://github.com/ipfs/js-ipfs/commit/c3d2d1e)), closes [#1316](https://github.com/ipfs/js-ipfs/issues/1316)\n* changes peer prop in return value from swarm.peers to be a PeerId  ([#1252](https://github.com/ipfs/js-ipfs/issues/1252)) ([e174866](https://github.com/ipfs/js-ipfs/commit/e174866))\n* configure webpack to not use esmodules in dependencies ([4486acc](https://github.com/ipfs/js-ipfs/commit/4486acc))\n* Display error when using unkown cli option ([a849d2f](https://github.com/ipfs/js-ipfs/commit/a849d2f))\n* docker init script sed in non existent file ([#1246](https://github.com/ipfs/js-ipfs/issues/1246)) ([75d47c3](https://github.com/ipfs/js-ipfs/commit/75d47c3))\n* files.add with pull streams ([0e601a7](https://github.com/ipfs/js-ipfs/commit/0e601a7))\n* make pubsub.unsubscribe async and alter pubsub.subscribe signature ([a115829](https://github.com/ipfs/js-ipfs/commit/a115829))\n* remove unused var ([#1273](https://github.com/ipfs/js-ipfs/issues/1273)) ([c1e8db1](https://github.com/ipfs/js-ipfs/commit/c1e8db1))\n* typo ([#1367](https://github.com/ipfs/js-ipfs/issues/1367)) ([2679129](https://github.com/ipfs/js-ipfs/commit/2679129))\n* use async/setImmediate vs process.nextTick ([af55608](https://github.com/ipfs/js-ipfs/commit/af55608))\n\n\n### Features\n\n* .stats.bw* - Bandwidth Stats ([#1230](https://github.com/ipfs/js-ipfs/issues/1230)) ([9694925](https://github.com/ipfs/js-ipfs/commit/9694925))\n* add ability to files.cat with a cid instance ([2e332c8](https://github.com/ipfs/js-ipfs/commit/2e332c8))\n* Add support for specifying hash algorithms in files.add ([a2954cb](https://github.com/ipfs/js-ipfs/commit/a2954cb))\n* allow dht to be enabled via cli arg ([#1340](https://github.com/ipfs/js-ipfs/issues/1340)) ([7bb838f](https://github.com/ipfs/js-ipfs/commit/7bb838f))\n* Allows for byte offsets when using ipfs.files.cat and friends to request slices of files ([a93971a](https://github.com/ipfs/js-ipfs/commit/a93971a))\n* Circuit Relay ([#1063](https://github.com/ipfs/js-ipfs/issues/1063)) ([f7eaa43](https://github.com/ipfs/js-ipfs/commit/f7eaa43))\n* cli: add IPFS_PATH info to init command help ([#1274](https://github.com/ipfs/js-ipfs/issues/1274)) ([e189b72](https://github.com/ipfs/js-ipfs/commit/e189b72))\n* handle SIGHUP ([7a817cf](https://github.com/ipfs/js-ipfs/commit/7a817cf))\n* ipfs.ping cli, http-api and core ([#1342](https://github.com/ipfs/js-ipfs/issues/1342)) ([b8171b1](https://github.com/ipfs/js-ipfs/commit/b8171b1))\n* jsipfs add --only-hash ([#1233](https://github.com/ipfs/js-ipfs/issues/1233)) ([#1266](https://github.com/ipfs/js-ipfs/issues/1266)) ([bddc5b4](https://github.com/ipfs/js-ipfs/commit/bddc5b4))\n* Provide access to bundled libraries when in browser ([#1297](https://github.com/ipfs/js-ipfs/issues/1297)) ([4905c2d](https://github.com/ipfs/js-ipfs/commit/4905c2d))\n* use class-is for type checks ([5b2cf8c](https://github.com/ipfs/js-ipfs/commit/5b2cf8c))\n* wrap with directory ([#1329](https://github.com/ipfs/js-ipfs/issues/1329)) ([47285a7](https://github.com/ipfs/js-ipfs/commit/47285a7))\n\n\n### Performance Improvements\n\n* **cli:** load only sub-system modules and inline require ipfs ([3820be0](https://github.com/ipfs/js-ipfs/commit/3820be0))\n\n\n### BREAKING CHANGES\n\n1. Argument order for `pubsub.subscribe` has changed:\n    * Old: `pubsub.subscribe(topic, [options], handler, [callback]): Promise`\n    * New: `pubsub.subscribe(topic, handler, [options], [callback]): Promise`\n2. The `pubsub.unsubscribe` method has become async meaning that it now takes a callback or returns a promise:\n    * Old: `pubsub.unsubscribe(topic, handler): undefined`\n    * New: `pubsub.unsubscribe(topic, handler, [callback]): Promise`\n3. Property names on response objects for `ping` are now lowered:\n    * Old: `{ Success, Time, Text }`\n    * New: `{ success, time, text }`\n4. In the CLI, `jsipfs object data` no longer returns a newline after the end of the returned data\n\n\n\n<a name=\"0.28.2\"></a>\n## [0.28.2](https://github.com/ipfs/js-ipfs/compare/v0.28.1...v0.28.2) (2018-03-14)\n\n\n### Bug Fixes\n\n* match error if repo doesnt exist ([#1262](https://github.com/ipfs/js-ipfs/issues/1262)) ([aea69d3](https://github.com/ipfs/js-ipfs/commit/aea69d3))\n* reinstates the non local block check in dht.provide ([#1250](https://github.com/ipfs/js-ipfs/issues/1250)) ([5b736a8](https://github.com/ipfs/js-ipfs/commit/5b736a8))\n\n\n### Features\n\n* add config validation ([#1239](https://github.com/ipfs/js-ipfs/issues/1239)) ([a32dce7](https://github.com/ipfs/js-ipfs/commit/a32dce7))\n\n\n\n<a name=\"0.28.1\"></a>\n## [0.28.1](https://github.com/ipfs/js-ipfs/compare/v0.28.0...v0.28.1) (2018-03-09)\n\n\n### Bug Fixes\n\n* **gateway:** catch stream2 error ([#1243](https://github.com/ipfs/js-ipfs/issues/1243)) ([5b40b41](https://github.com/ipfs/js-ipfs/commit/5b40b41))\n* accept objects in file.add ([#1257](https://github.com/ipfs/js-ipfs/issues/1257)) ([d32dad9](https://github.com/ipfs/js-ipfs/commit/d32dad9))\n\n\n\n<a name=\"0.28.0\"></a>\n## [0.28.0](https://github.com/ipfs/js-ipfs/compare/v0.27.7...v0.28.0) (2018-03-01)\n\n\n### Bug Fixes\n\n* **cli:** show help for subcommands ([8c63f8f](https://github.com/ipfs/js-ipfs/commit/8c63f8f))\n* (cli/init) use cross-platform path separator ([bbb7cc5](https://github.com/ipfs/js-ipfs/commit/bbb7cc5))\n* **dag:** print data in a readable way if it is JSON ([42545dc](https://github.com/ipfs/js-ipfs/commit/42545dc))\n* bootstrap ([d527b45](https://github.com/ipfs/js-ipfs/commit/d527b45))\n* now properly fix bootstrap in core ([9f39a6f](https://github.com/ipfs/js-ipfs/commit/9f39a6f))\n* Remove scape characteres from error message. ([68e7b5a](https://github.com/ipfs/js-ipfs/commit/68e7b5a))\n* Return swarm http errors as json ([d3a0ae1](https://github.com/ipfs/js-ipfs/commit/d3a0ae1)), closes [#1176](https://github.com/ipfs/js-ipfs/issues/1176)\n* stats tests ([a0fd355](https://github.com/ipfs/js-ipfs/commit/a0fd355))\n* use \"ipld\" instead of \"ipld-resolver\" ([e7f0432](https://github.com/ipfs/js-ipfs/commit/e7f0432))\n\n\n### Features\n\n* `ipfs version` flags + `ipfs repo version` ([#1181](https://github.com/ipfs/js-ipfs/issues/1181)) ([#1188](https://github.com/ipfs/js-ipfs/issues/1188)) ([494da7f](https://github.com/ipfs/js-ipfs/commit/494da7f))\n* Add /ip6 addresses to bootstrap ([3bca165](https://github.com/ipfs/js-ipfs/commit/3bca165)), closes [#706](https://github.com/ipfs/js-ipfs/issues/706)\n* all pubsub tests passing with libp2p pubsub ([6fe015f](https://github.com/ipfs/js-ipfs/commit/6fe015f))\n* Bootstrap API compliance ([#1218](https://github.com/ipfs/js-ipfs/issues/1218)) ([9a445d1](https://github.com/ipfs/js-ipfs/commit/9a445d1))\n* Implementation of the ipfs.key API ([#1133](https://github.com/ipfs/js-ipfs/issues/1133)) ([d945fce](https://github.com/ipfs/js-ipfs/commit/d945fce))\n* improved multiaddr validation. ([d9744a1](https://github.com/ipfs/js-ipfs/commit/d9744a1))\n* ipfs shutdown ([#1200](https://github.com/ipfs/js-ipfs/issues/1200)) ([95365fa](https://github.com/ipfs/js-ipfs/commit/95365fa))\n* jsipfs ls -r (Recursive list directory) ([#1222](https://github.com/ipfs/js-ipfs/issues/1222)) ([0f1e00f](https://github.com/ipfs/js-ipfs/commit/0f1e00f))\n* latest libp2p + other deps. Fix bugs in tests along the way ([4b79066](https://github.com/ipfs/js-ipfs/commit/4b79066))\n* reworking tests with new ipfsd-ctl ([#1167](https://github.com/ipfs/js-ipfs/issues/1167)) ([d16a129](https://github.com/ipfs/js-ipfs/commit/d16a129))\n* stats API (stats.bitswap and stats.repo) ([#1198](https://github.com/ipfs/js-ipfs/issues/1198)) ([905bdc0](https://github.com/ipfs/js-ipfs/commit/905bdc0))\n* support Jenkins ([bc66e9f](https://github.com/ipfs/js-ipfs/commit/bc66e9f))\n* use PubSub API directly from libp2p ([6b9fc95](https://github.com/ipfs/js-ipfs/commit/6b9fc95))\n* use reduces keysize ([#1232](https://github.com/ipfs/js-ipfs/issues/1232)) ([7f69628](https://github.com/ipfs/js-ipfs/commit/7f69628))\n\n\n\n<a name=\"0.27.7\"></a>\n## [0.27.7](https://github.com/ipfs/js-ipfs/compare/v0.27.6...v0.27.7) (2018-01-16)\n\n\n### Features\n\n* /api/v0/dns ([#1172](https://github.com/ipfs/js-ipfs/issues/1172)) ([639024c](https://github.com/ipfs/js-ipfs/commit/639024c))\n\n\n\n<a name=\"0.27.6\"></a>\n## [0.27.6](https://github.com/ipfs/js-ipfs/compare/v0.27.5...v0.27.6) (2018-01-07)\n\n\n### Bug Fixes\n\n* cli files on Windows ([#1159](https://github.com/ipfs/js-ipfs/issues/1159)) ([1b98fa1](https://github.com/ipfs/js-ipfs/commit/1b98fa1))\n\n\n\n<a name=\"0.27.5\"></a>\n## [0.27.5](https://github.com/ipfs/js-ipfs/compare/v0.27.4...v0.27.5) (2017-12-18)\n\n\n### Bug Fixes\n\n* cat: test file existence after filtering ([#1148](https://github.com/ipfs/js-ipfs/issues/1148)) ([34f28ef](https://github.com/ipfs/js-ipfs/commit/34f28ef)), closes [#1142](https://github.com/ipfs/js-ipfs/issues/1142)\n* ipfs.ls: allow any depth ([#1152](https://github.com/ipfs/js-ipfs/issues/1152)) ([279af78](https://github.com/ipfs/js-ipfs/commit/279af78)), closes [#1079](https://github.com/ipfs/js-ipfs/issues/1079)\n* use new bitswap stats ([#1151](https://github.com/ipfs/js-ipfs/issues/1151)) ([e223888](https://github.com/ipfs/js-ipfs/commit/e223888))\n* **files.add:** directory with odd name ([#1155](https://github.com/ipfs/js-ipfs/issues/1155)) ([058c674](https://github.com/ipfs/js-ipfs/commit/058c674))\n\n\n\n<a name=\"0.27.4\"></a>\n## [0.27.4](https://github.com/ipfs/js-ipfs/compare/v0.27.3...v0.27.4) (2017-12-13)\n\n\n### Bug Fixes\n\n* files.cat: detect and handle rrors when unknown path and cat dir ([#1143](https://github.com/ipfs/js-ipfs/issues/1143)) ([120d291](https://github.com/ipfs/js-ipfs/commit/120d291))\n* fix bug introduced by 1143 ([#1146](https://github.com/ipfs/js-ipfs/issues/1146)) ([12cdc08](https://github.com/ipfs/js-ipfs/commit/12cdc08))\n\n\n\n<a name=\"0.27.3\"></a>\n## [0.27.3](https://github.com/ipfs/js-ipfs/compare/v0.27.2...v0.27.3) (2017-12-10)\n\n\n### Bug Fixes\n\n* config handler should check if value is null ([#1134](https://github.com/ipfs/js-ipfs/issues/1134)) ([0444c42](https://github.com/ipfs/js-ipfs/commit/0444c42))\n* **pubsub:** subscribe promises ([#1141](https://github.com/ipfs/js-ipfs/issues/1141)) ([558017d](https://github.com/ipfs/js-ipfs/commit/558017d))\n\n\n\n<a name=\"0.27.2\"></a>\n## [0.27.2](https://github.com/ipfs/js-ipfs/compare/v0.27.1...v0.27.2) (2017-12-09)\n\n\n\n<a name=\"0.27.1\"></a>\n## [0.27.1](https://github.com/ipfs/js-ipfs/compare/v0.27.0...v0.27.1) (2017-12-07)\n\n\n### Bug Fixes\n\n* **pubsub.peers:** remove the requirement for a topic ([#1125](https://github.com/ipfs/js-ipfs/issues/1125)) ([5601c26](https://github.com/ipfs/js-ipfs/commit/5601c26))\n\n\n\n<a name=\"0.27.0\"></a>\n## [0.27.0](https://github.com/ipfs/js-ipfs/compare/v0.26.0...v0.27.0) (2017-12-04)\n\n\n### Bug Fixes\n\n* fix the welcome message and throw error when trying to cat a non-exis… ([#1032](https://github.com/ipfs/js-ipfs/issues/1032)) ([25fb390](https://github.com/ipfs/js-ipfs/commit/25fb390)), closes [#1031](https://github.com/ipfs/js-ipfs/issues/1031)\n* make offline error retain stack ([#1056](https://github.com/ipfs/js-ipfs/issues/1056)) ([dce6a49](https://github.com/ipfs/js-ipfs/commit/dce6a49))\n* pre 1.0.0 deps should be always installed with ~ and not ^ ([c672af7](https://github.com/ipfs/js-ipfs/commit/c672af7))\n* progress bar flakiness ([#1042](https://github.com/ipfs/js-ipfs/issues/1042)) ([d7732c3](https://github.com/ipfs/js-ipfs/commit/d7732c3))\n* promisify .block (get, put, rm, stat) ([#1085](https://github.com/ipfs/js-ipfs/issues/1085)) ([cafa52b](https://github.com/ipfs/js-ipfs/commit/cafa52b))\n* **files.add:** glob needs a POSIX path ([#1108](https://github.com/ipfs/js-ipfs/issues/1108)) ([9c29a23](https://github.com/ipfs/js-ipfs/commit/9c29a23))\n* promisify node.stop ([#1082](https://github.com/ipfs/js-ipfs/issues/1082)) ([9b385ae](https://github.com/ipfs/js-ipfs/commit/9b385ae))\n* pubsub message fields ([#1077](https://github.com/ipfs/js-ipfs/issues/1077)) ([9de6f4c](https://github.com/ipfs/js-ipfs/commit/9de6f4c))\n* removed error handler that was hiding errors ([#1120](https://github.com/ipfs/js-ipfs/issues/1120)) ([58ded8d](https://github.com/ipfs/js-ipfs/commit/58ded8d))\n* Typo ([#1044](https://github.com/ipfs/js-ipfs/issues/1044)) ([179b6a4](https://github.com/ipfs/js-ipfs/commit/179b6a4))\n* update *-star multiaddrs to explicity say that they need tcp and a port ([#1117](https://github.com/ipfs/js-ipfs/issues/1117)) ([9eda8a8](https://github.com/ipfs/js-ipfs/commit/9eda8a8))\n\n\n### Features\n\n* accept additional transports ([6613aa6](https://github.com/ipfs/js-ipfs/commit/6613aa6))\n* add circuit relay and aegir 12 (+ big refactor) ([104ef1e](https://github.com/ipfs/js-ipfs/commit/104ef1e))\n* add WebUI Path ([#1124](https://github.com/ipfs/js-ipfs/issues/1124)) ([8041b48](https://github.com/ipfs/js-ipfs/commit/8041b48))\n* adding appveyor support ([#1054](https://github.com/ipfs/js-ipfs/issues/1054)) ([b92bdfe](https://github.com/ipfs/js-ipfs/commit/b92bdfe))\n* agent version with package number ([#1121](https://github.com/ipfs/js-ipfs/issues/1121)) ([550f955](https://github.com/ipfs/js-ipfs/commit/550f955))\n* cli --api option ([#1087](https://github.com/ipfs/js-ipfs/issues/1087)) ([1b1fa05](https://github.com/ipfs/js-ipfs/commit/1b1fa05))\n* complete PubSub implementation  ([ac95601](https://github.com/ipfs/js-ipfs/commit/ac95601))\n* implement \"ipfs file ls\" ([#1078](https://github.com/ipfs/js-ipfs/issues/1078)) ([6db3fb8](https://github.com/ipfs/js-ipfs/commit/6db3fb8))\n* implementing the new streaming interfaces ([#1086](https://github.com/ipfs/js-ipfs/issues/1086)) ([2c4b8b3](https://github.com/ipfs/js-ipfs/commit/2c4b8b3))\n* ipfs.ls ([#1073](https://github.com/ipfs/js-ipfs/issues/1073)) ([35687cb](https://github.com/ipfs/js-ipfs/commit/35687cb))\n* make js-ipfs daemon stop with same SIG as go-ipfs ([#1067](https://github.com/ipfs/js-ipfs/issues/1067)) ([7dd4e01](https://github.com/ipfs/js-ipfs/commit/7dd4e01))\n* WebSocketStar ([#1090](https://github.com/ipfs/js-ipfs/issues/1090)) ([33e9949](https://github.com/ipfs/js-ipfs/commit/33e9949))\n* windows interop ([#1065](https://github.com/ipfs/js-ipfs/issues/1065)) ([d8197f9](https://github.com/ipfs/js-ipfs/commit/d8197f9))\n\n\n\n<a name=\"0.26.0\"></a>\n## [0.26.0](https://github.com/ipfs/js-ipfs/compare/v0.25.4...v0.26.0) (2017-09-13)\n\n\n### Bug Fixes\n\n* strips trailing slash from path ([#985](https://github.com/ipfs/js-ipfs/issues/985)) ([bfc58d6](https://github.com/ipfs/js-ipfs/commit/bfc58d6))\n\n\n### Features\n\n* Add --cid-version option to ipfs files add +  decodeURIComponent for file and directory names ([7544b7b](https://github.com/ipfs/js-ipfs/commit/7544b7b))\n* add gateway to ipfs daemon ([9f2006e](https://github.com/ipfs/js-ipfs/commit/9f2006e)), closes [#1006](https://github.com/ipfs/js-ipfs/issues/1006) [#1008](https://github.com/ipfs/js-ipfs/issues/1008) [#1009](https://github.com/ipfs/js-ipfs/issues/1009)\n* adds quiet flags ([#1001](https://github.com/ipfs/js-ipfs/issues/1001)) ([d21b492](https://github.com/ipfs/js-ipfs/commit/d21b492))\n* complete the migration to p2p-webrtc-star ([#984](https://github.com/ipfs/js-ipfs/issues/984)) ([1e5dd2c](https://github.com/ipfs/js-ipfs/commit/1e5dd2c))\n\n\n\n<a name=\"0.25.4\"></a>\n## [0.25.4](https://github.com/ipfs/js-ipfs/compare/v0.25.3...v0.25.4) (2017-09-01)\n\n\n### Features\n\n* add multiaddrs for bootstrapers gateway  ([a15bee9](https://github.com/ipfs/js-ipfs/commit/a15bee9))\n\n\n\n<a name=\"0.25.3\"></a>\n## [0.25.3](https://github.com/ipfs/js-ipfs/compare/v0.25.2...v0.25.3) (2017-09-01)\n\n\n### Bug Fixes\n\n* config, dangling comma ([4eb63c5](https://github.com/ipfs/js-ipfs/commit/4eb63c5))\n* only show connected addrs for peers in swarm.peers ([d939323](https://github.com/ipfs/js-ipfs/commit/d939323))\n* remove shutdown bootstrapers from bootstrappers list ([5ec27a3](https://github.com/ipfs/js-ipfs/commit/5ec27a3))\n\n\n### Features\n\n* add instrumentation ([8f0254e](https://github.com/ipfs/js-ipfs/commit/8f0254e))\n\n\n\n<a name=\"0.25.2\"></a>\n## [0.25.2](https://github.com/ipfs/js-ipfs/compare/v0.25.1...v0.25.2) (2017-08-26)\n\n\n\n<a name=\"0.25.1\"></a>\n## [0.25.1](https://github.com/ipfs/js-ipfs/compare/v0.25.0...v0.25.1) (2017-07-26)\n\n\n### Bug Fixes\n\n* js-ipfs daemon config params ([#914](https://github.com/ipfs/js-ipfs/issues/914)) ([e00b96f](https://github.com/ipfs/js-ipfs/commit/e00b96f)), closes [#868](https://github.com/ipfs/js-ipfs/issues/868)\n* remove non existent commands ([#925](https://github.com/ipfs/js-ipfs/issues/925)) ([b7e8e88](https://github.com/ipfs/js-ipfs/commit/b7e8e88))\n* stream issue, do not use isstream, use is-stream ([#937](https://github.com/ipfs/js-ipfs/issues/937)) ([da66b1f](https://github.com/ipfs/js-ipfs/commit/da66b1f))\n\n\n### Features\n\n* new print func for the CLI ([#931](https://github.com/ipfs/js-ipfs/issues/931)) ([a5e75e0](https://github.com/ipfs/js-ipfs/commit/a5e75e0))\n* no more need for webcrypto-ossl ([bc8ffee](https://github.com/ipfs/js-ipfs/commit/bc8ffee))\n\n\n\n<a name=\"0.25.0\"></a>\n## [0.25.0](https://github.com/ipfs/js-ipfs/compare/v0.24.1...v0.25.0) (2017-07-12)\n\n\n### Bug Fixes\n\n* **bootstrap:add:** prevent duplicate inserts ([#893](https://github.com/ipfs/js-ipfs/issues/893)) ([ce504cd](https://github.com/ipfs/js-ipfs/commit/ce504cd))\n* **swarm:** move isConnected filter from addrs to peers ([#901](https://github.com/ipfs/js-ipfs/issues/901)) ([e2f371b](https://github.com/ipfs/js-ipfs/commit/e2f371b))\n* circle ci, thanks victor! ([b074966](https://github.com/ipfs/js-ipfs/commit/b074966))\n* do not let lodash mess with libp2p modules ([1f68b9b](https://github.com/ipfs/js-ipfs/commit/1f68b9b))\n* is online is only online if libp2p is online ([#891](https://github.com/ipfs/js-ipfs/issues/891)) ([8b0f996](https://github.com/ipfs/js-ipfs/commit/8b0f996))\n* issue [#905](https://github.com/ipfs/js-ipfs/issues/905) ([#906](https://github.com/ipfs/js-ipfs/issues/906)) ([cbcf90e](https://github.com/ipfs/js-ipfs/commit/cbcf90e))\n* setImmediate polyfilled in node.id() ([#909](https://github.com/ipfs/js-ipfs/issues/909)) ([ebaf9a0](https://github.com/ipfs/js-ipfs/commit/ebaf9a0))\n* succeed when stopping already stopped ([74f3185](https://github.com/ipfs/js-ipfs/commit/74f3185))\n\n\n### Features\n\n* adapted to new ipfs-repo API ([#887](https://github.com/ipfs/js-ipfs/issues/887)) ([4e39d2c](https://github.com/ipfs/js-ipfs/commit/4e39d2c))\n* block get pipe fix ([#903](https://github.com/ipfs/js-ipfs/issues/903)) ([8063f6b](https://github.com/ipfs/js-ipfs/commit/8063f6b))\n\n\n\n<a name=\"0.24.1\"></a>\n## [0.24.1](https://github.com/ipfs/js-ipfs/compare/0.24.1...v0.24.1) (2017-05-29)\n\n\n\n<a name=\"0.24.0\"></a>\n## [0.24.0](https://github.com/ipfs/js-ipfs/compare/v0.23.1...v0.24.0) (2017-05-24)\n\n\n### Bug Fixes\n\n* cli flag typos ([c5bb0b9](https://github.com/ipfs/js-ipfs/commit/c5bb0b9))\n* example, now files from datatransfer is a FileList which is not an array ([d7c9eec](https://github.com/ipfs/js-ipfs/commit/d7c9eec))\n* issue-858 ([481933a](https://github.com/ipfs/js-ipfs/commit/481933a))\n* last touches for dns websockets bootstrapers ([3b680a7](https://github.com/ipfs/js-ipfs/commit/3b680a7))\n* linting ([68ee42e](https://github.com/ipfs/js-ipfs/commit/68ee42e))\n* make start an async event ([78ba1e8](https://github.com/ipfs/js-ipfs/commit/78ba1e8))\n* missing import ([6aa914d](https://github.com/ipfs/js-ipfs/commit/6aa914d))\n* options to the HTTP API ([f1eb595](https://github.com/ipfs/js-ipfs/commit/f1eb595))\n* removed hard-coded timeout on test and liting fixes ([0a3bbcb](https://github.com/ipfs/js-ipfs/commit/0a3bbcb))\n* run webworker tests ([23c84f6](https://github.com/ipfs/js-ipfs/commit/23c84f6))\n* **object.get:** treat ipfs hash strings as default base58 encoded ([7b3caef](https://github.com/ipfs/js-ipfs/commit/7b3caef))\n* update bootstrapers ([7e7d9eb](https://github.com/ipfs/js-ipfs/commit/7e7d9eb))\n\n\n### Features\n\n* add dns ws bootstrappers ([a856578](https://github.com/ipfs/js-ipfs/commit/a856578))\n* add WebRTC by default as a multiaddr ([4ea1571](https://github.com/ipfs/js-ipfs/commit/4ea1571))\n* add websocket bootstrapers to the config ([602d033](https://github.com/ipfs/js-ipfs/commit/602d033))\n* DHT integration PART I ([860165c](https://github.com/ipfs/js-ipfs/commit/860165c))\n* new libp2p-api ([7bf75d1](https://github.com/ipfs/js-ipfs/commit/7bf75d1))\n* update to new libp2p events for peers ([ca88706](https://github.com/ipfs/js-ipfs/commit/ca88706))\n* update to the latest libp2p ([aca4297](https://github.com/ipfs/js-ipfs/commit/aca4297))\n\n\n\n<a name=\"0.23.1\"></a>\n## [0.23.1](https://github.com/ipfs/js-ipfs/compare/v0.23.0...v0.23.1) (2017-03-27)\n\n\n### Bug Fixes\n\n* added backpressure to the add stream ([#810](https://github.com/ipfs/js-ipfs/issues/810)) ([31dbabc](https://github.com/ipfs/js-ipfs/commit/31dbabc))\n\n\n\n<a name=\"0.23.0\"></a>\n## [0.23.0](https://github.com/ipfs/js-ipfs/compare/v0.22.1...v0.23.0) (2017-03-24)\n\n\n### Bug Fixes\n\n* **files.add:** error on invalid input ([#782](https://github.com/ipfs/js-ipfs/issues/782)) ([c851ca0](https://github.com/ipfs/js-ipfs/commit/c851ca0))\n* give the daemon time to spawn ([2bf32cd](https://github.com/ipfs/js-ipfs/commit/2bf32cd))\n* linting on transfer-files example ([f876171](https://github.com/ipfs/js-ipfs/commit/f876171))\n* offer an init event to monitor when repo is there and avoid setTimeout ([c4130b9](https://github.com/ipfs/js-ipfs/commit/c4130b9))\n* pull-stream-to-stream replaced with duplex stream ([#809](https://github.com/ipfs/js-ipfs/issues/809)) ([4b064a1](https://github.com/ipfs/js-ipfs/commit/4b064a1))\n\n\n### Features\n\n* bootstrap is enabled by default now ([64cde5d](https://github.com/ipfs/js-ipfs/commit/64cde5d))\n* bootstrap is enabled by default now ([2642417](https://github.com/ipfs/js-ipfs/commit/2642417))\n* datastore, ipfs-block and all the deps that were updated ([68d92b6](https://github.com/ipfs/js-ipfs/commit/68d92b6))\n* no need anymore to append ipfs/Qmhash to webrtc-star multiaddrs ([a77ae3c](https://github.com/ipfs/js-ipfs/commit/a77ae3c))\n\n\n\n<a name=\"0.22.1\"></a>\n## [0.22.1](https://github.com/ipfs/js-ipfs/compare/v0.22.0...v0.22.1) (2017-02-24)\n\n\n### Bug Fixes\n\n* interop tests with multiplex passing ([cb109fc](https://github.com/ipfs/js-ipfs/commit/cb109fc))\n\n\n### Features\n\n* **core:** allow IPFS object to be created without supplying configOpts ([f620d71](https://github.com/ipfs/js-ipfs/commit/f620d71))\n* **deps:** update multiplex libp2p-ipfs deps ([5605148](https://github.com/ipfs/js-ipfs/commit/5605148))\n\n\n\n<a name=\"0.22.0\"></a>\n## [0.22.0](https://github.com/ipfs/js-ipfs/compare/v0.21.8...v0.22.0) (2017-02-15)\n\n\n### Bug Fixes\n\n* lint ([ffc120a](https://github.com/ipfs/js-ipfs/commit/ffc120a))\n* make sure all deps are up to date, expose Buffer type ([7eb630d](https://github.com/ipfs/js-ipfs/commit/7eb630d))\n* readable-stream needs to be 1.1.14 ([e999f05](https://github.com/ipfs/js-ipfs/commit/e999f05))\n* tidy dag cli up ([b90ba76](https://github.com/ipfs/js-ipfs/commit/b90ba76))\n\n\n### Features\n\n* **breaking change:** experimental config options ([#749](https://github.com/ipfs/js-ipfs/issues/749)) ([69fa802](https://github.com/ipfs/js-ipfs/commit/69fa802))\n* **dag:** basics (get, put) ([#746](https://github.com/ipfs/js-ipfs/issues/746)) ([e5ec0cf](https://github.com/ipfs/js-ipfs/commit/e5ec0cf))\n* **dag:** Resolve API ([#751](https://github.com/ipfs/js-ipfs/issues/751)) ([4986908](https://github.com/ipfs/js-ipfs/commit/4986908))\n* merge of get and resolve ([#761](https://github.com/ipfs/js-ipfs/issues/761)) ([b081e35](https://github.com/ipfs/js-ipfs/commit/b081e35))\n\n\n\n<a name=\"0.21.8\"></a>\n## [0.21.8](https://github.com/ipfs/js-ipfs/compare/v0.21.7...v0.21.8) (2017-01-31)\n\n\n### Features\n\n* add CLI support for different hash func and type ([#748](https://github.com/ipfs/js-ipfs/issues/748)) ([a6c522f](https://github.com/ipfs/js-ipfs/commit/a6c522f))\n\n\n\n<a name=\"0.21.7\"></a>\n## [0.21.7](https://github.com/ipfs/js-ipfs/compare/v0.21.6...v0.21.7) (2017-01-30)\n\n\n### Bug Fixes\n\n* default config file ([01ef4b5](https://github.com/ipfs/js-ipfs/commit/01ef4b5))\n\n\n\n<a name=\"0.21.6\"></a>\n## [0.21.6](https://github.com/ipfs/js-ipfs/compare/v0.21.5...v0.21.6) (2017-01-29)\n\n\n### Features\n\n* bootstrap as an option ([#735](https://github.com/ipfs/js-ipfs/issues/735)) ([03362a3](https://github.com/ipfs/js-ipfs/commit/03362a3))\n\n\n\n<a name=\"0.21.5\"></a>\n## [0.21.5](https://github.com/ipfs/js-ipfs/compare/v0.21.4...v0.21.5) (2017-01-29)\n\n\n### Bug Fixes\n\n* differenciate default config in browser and in node ([#734](https://github.com/ipfs/js-ipfs/issues/734)) ([17ccc8b](https://github.com/ipfs/js-ipfs/commit/17ccc8b))\n\n\n\n<a name=\"0.21.4\"></a>\n## [0.21.4](https://github.com/ipfs/js-ipfs/compare/v0.21.3...v0.21.4) (2017-01-28)\n\n\n### Bug Fixes\n\n* ipfs.id does not double append ipfs/<id> anymore ([#732](https://github.com/ipfs/js-ipfs/issues/732)) ([718394a](https://github.com/ipfs/js-ipfs/commit/718394a))\n\n\n\n<a name=\"0.21.3\"></a>\n## [0.21.3](https://github.com/ipfs/js-ipfs/compare/v0.21.2...v0.21.3) (2017-01-25)\n\n\n\n<a name=\"0.21.2\"></a>\n## [0.21.2](https://github.com/ipfs/js-ipfs/compare/v0.21.1...v0.21.2) (2017-01-23)\n\n\n\n<a name=\"0.21.1\"></a>\n## [0.21.1](https://github.com/ipfs/js-ipfs/compare/v0.21.0...v0.21.1) (2017-01-23)\n\n\n\n<a name=\"0.21.0\"></a>\n## [0.21.0](https://github.com/ipfs/js-ipfs/compare/v0.20.4...v0.21.0) (2017-01-17)\n\n\n### Bug Fixes\n\n* point to a specific go-ipfs version (still waiting for another 0.4.5 pre release though ([19dbb1e](https://github.com/ipfs/js-ipfs/commit/19dbb1e))\n\n\n\n<a name=\"0.20.4\"></a>\n## [0.20.4](https://github.com/ipfs/js-ipfs/compare/v0.20.2...v0.20.4) (2016-12-26)\n\n\n### Bug Fixes\n\n* bitswap wantlist http endpoint ([58f0885](https://github.com/ipfs/js-ipfs/commit/58f0885))\n* bitswap wantlist stats ([9db86f5](https://github.com/ipfs/js-ipfs/commit/9db86f5))\n* change default values of js-ipfs to avoid clash with go-ipfs + clean the browserify example ([6d52e1c](https://github.com/ipfs/js-ipfs/commit/6d52e1c))\n* npm scripts ([eadcec0](https://github.com/ipfs/js-ipfs/commit/eadcec0))\n* pass a first arg to bitswap to be removed after new bitswap is merged, so that tests pass now ([bddcee7](https://github.com/ipfs/js-ipfs/commit/bddcee7))\n\n\n### Features\n\n* **init:** add empty unixfs dir to match go-ipfs ([a967bb0](https://github.com/ipfs/js-ipfs/commit/a967bb0))\n* **object:** add template option to object.new ([9058118](https://github.com/ipfs/js-ipfs/commit/9058118))\n* add multicastdns to the mix ([c2ddefb](https://github.com/ipfs/js-ipfs/commit/c2ddefb))\n\n\n\n<a name=\"0.20.2\"></a>\n## [0.20.2](https://github.com/ipfs/js-ipfs/compare/v0.20.1...v0.20.2) (2016-12-09)\n\n\n### Bug Fixes\n\n* **cli:** Tell user to init repo if not initialized when starting daemon ([fa7e275](https://github.com/ipfs/js-ipfs/commit/fa7e275))\n\n\n\n<a name=\"0.20.1\"></a>\n## [0.20.1](https://github.com/ipfs/js-ipfs/compare/v0.19.0...v0.20.1) (2016-11-28)\n\n\n\n<a name=\"0.19.0\"></a>\n## [0.19.0](https://github.com/ipfs/js-ipfs/compare/v0.18.0...v0.19.0) (2016-11-26)\n\n\n### Bug Fixes\n\n* addLink and rmLink ([7fad4d8](https://github.com/ipfs/js-ipfs/commit/7fad4d8))\n* apply CR ([698f708](https://github.com/ipfs/js-ipfs/commit/698f708))\n* **lint:** install missing plugin ([20e3d2e](https://github.com/ipfs/js-ipfs/commit/20e3d2e))\n* **lint:** use eslint directly ([443dd9e](https://github.com/ipfs/js-ipfs/commit/443dd9e))\n* **lint and polish:** add a little more comments ([d6ce83d](https://github.com/ipfs/js-ipfs/commit/d6ce83d))\n\n\n### Features\n\n* **cli:** migrate to awesome-dag-pb ([3bb3ba8](https://github.com/ipfs/js-ipfs/commit/3bb3ba8))\n* **core:** migrate to awesome dag-pb ([db550a1](https://github.com/ipfs/js-ipfs/commit/db550a1))\n* **examples:** add a getting-started example ([7485ac5](https://github.com/ipfs/js-ipfs/commit/7485ac5))\n* **http:** migrate to awesome dag-pb ([ca9935f](https://github.com/ipfs/js-ipfs/commit/ca9935f))\n* **swarm:** update swarm.peers to new api ([265a77a](https://github.com/ipfs/js-ipfs/commit/265a77a))\n\n\n\n<a name=\"0.18.0\"></a>\n## [0.18.0](https://github.com/ipfs/js-ipfs/compare/v0.17.0...v0.18.0) (2016-11-12)\n\n\n### Bug Fixes\n\n* async .key ([2d2185b](https://github.com/ipfs/js-ipfs/commit/2d2185b))\n* don't break backwards compatibility on the Block API ([3674b8e](https://github.com/ipfs/js-ipfs/commit/3674b8e))\n* **cli:** alias add, cat and get to top-level cli ([6ad325b](https://github.com/ipfs/js-ipfs/commit/6ad325b))\n\n\n### Features\n\n* block API uses CIDs ([2eeea35](https://github.com/ipfs/js-ipfs/commit/2eeea35))\n* migrate cli to use new async DAGNode interface ([1b0b22d](https://github.com/ipfs/js-ipfs/commit/1b0b22d))\n* migrate core to use new async DAGNode interface ([254afdc](https://github.com/ipfs/js-ipfs/commit/254afdc))\n* migrate files to use IPLD Resolver ([0fb1a1a](https://github.com/ipfs/js-ipfs/commit/0fb1a1a))\n* migrate http-api to use new async DAGNode interface ([01e56ec](https://github.com/ipfs/js-ipfs/commit/01e56ec))\n* migrate init to IPLD resolver ([61d1084](https://github.com/ipfs/js-ipfs/commit/61d1084))\n* object API internals updated to use CID ([5cb10cc](https://github.com/ipfs/js-ipfs/commit/5cb10cc))\n* update cli and http to support new ipld block api with IPLD ([5dbb799](https://github.com/ipfs/js-ipfs/commit/5dbb799))\n* **http:** better error messages ([cd7f77d](https://github.com/ipfs/js-ipfs/commit/cd7f77d))\n* **http:** set default headers for browsers ([6a21cd0](https://github.com/ipfs/js-ipfs/commit/6a21cd0))\n\n\n\n<a name=\"0.17.0\"></a>\n## [0.17.0](https://github.com/ipfs/js-ipfs/compare/v0.16.0...v0.17.0) (2016-10-10)\n\n\n### Bug Fixes\n\n* **cli:** Fix issue with right cwd not being set ([e5f5e1b](https://github.com/ipfs/js-ipfs/commit/e5f5e1b))\n* **deps:** move blob stores to dependencies ([8f33d11](https://github.com/ipfs/js-ipfs/commit/8f33d11))\n* **files.get:** fix the command ([7015586](https://github.com/ipfs/js-ipfs/commit/7015586))\n\n\n### Features\n\n* **http-api:** add joi validation to bootstrap ([028a98c](https://github.com/ipfs/js-ipfs/commit/028a98c))\n\n\n\n<a name=\"0.16.0\"></a>\n## [0.16.0](https://github.com/ipfs/js-ipfs/compare/v0.15.0...v0.16.0) (2016-09-15)\n\n\n### Bug Fixes\n\n* **cli:** add output for cli init ([29c9793](https://github.com/ipfs/js-ipfs/commit/29c9793))\n* always use files.cat ([5b8da13](https://github.com/ipfs/js-ipfs/commit/5b8da13))\n* **cli:** make ipfs files add work online and offline ([3edc2b9](https://github.com/ipfs/js-ipfs/commit/3edc2b9)), closes [#480](https://github.com/ipfs/js-ipfs/issues/480)\n* **cli:** pipe content to the cli from cat it is a stream ([3e4e2fd](https://github.com/ipfs/js-ipfs/commit/3e4e2fd))\n* **cli:** use right argument for cli .cat ([2bf49ea](https://github.com/ipfs/js-ipfs/commit/2bf49ea))\n* **cli:** use right argument for cli .cat ([dd3fe88](https://github.com/ipfs/js-ipfs/commit/dd3fe88))\n* **config:** better http-api and interface-ipfs-core compliant ([2beac9c](https://github.com/ipfs/js-ipfs/commit/2beac9c))\n* **http:** get handler reads the stream ([b0a6db9](https://github.com/ipfs/js-ipfs/commit/b0a6db9))\n* **swarm:** fix cli commands and enable tests ([6effa19](https://github.com/ipfs/js-ipfs/commit/6effa19))\n* **version:** better http-api and interface-ipfs-core compliant ([0ee7215](https://github.com/ipfs/js-ipfs/commit/0ee7215))\n\n\n### Features\n\n* **add:** add the http endpoint for files.add ([e29f429](https://github.com/ipfs/js-ipfs/commit/e29f429))\n* **files:** get interface-ipfs-core files tests pass through http-api ([11cb4ca](https://github.com/ipfs/js-ipfs/commit/11cb4ca))\n* **files:** interface-ipfs-core tests over ipfs-api ([001a6eb](https://github.com/ipfs/js-ipfs/commit/001a6eb))\n* **swarm:** interface-ipfs-core swarm compatibility ([3b32dfd](https://github.com/ipfs/js-ipfs/commit/3b32dfd))\n* **swarm:** make interface-ipfs-core compliant ([ef729bb](https://github.com/ipfs/js-ipfs/commit/ef729bb)), closes [#439](https://github.com/ipfs/js-ipfs/issues/439)\n* **tests:** waste less time generating keys ([cb10ab7](https://github.com/ipfs/js-ipfs/commit/cb10ab7))\n\n\n\n<a name=\"0.15.0\"></a>\n## [0.15.0](https://github.com/ipfs/js-ipfs/compare/v0.14.3...v0.15.0) (2016-09-09)\n\n\n### Bug Fixes\n\n* **cli:** fix the files API commands ([138f519](https://github.com/ipfs/js-ipfs/commit/138f519))\n* **config:** support null values (0 or empty string) on get and set ([a3d98a8](https://github.com/ipfs/js-ipfs/commit/a3d98a8))\n* **repo:** init does not break if no opts are passed. Fixes [#349](https://github.com/ipfs/js-ipfs/issues/349) ([ca700cc](https://github.com/ipfs/js-ipfs/commit/ca700cc))\n* **style:** apply CR ([97af048](https://github.com/ipfs/js-ipfs/commit/97af048))\n* **test:** make the version test fetch the version from package.json instead of a hardcoded value ([50c9f7c](https://github.com/ipfs/js-ipfs/commit/50c9f7c))\n\n\n### Features\n\n* **bitswap tests, config, id:** cope with the nuances of the config API (.replace) and make necessary changes to make it all work again ([cc0c8fd](https://github.com/ipfs/js-ipfs/commit/cc0c8fd))\n* **block-core:** add compliance with interface-ipfs-core on block-API ([5e6387d](https://github.com/ipfs/js-ipfs/commit/5e6387d))\n* **block-http:** tests passing according with compliance ([a4071f0](https://github.com/ipfs/js-ipfs/commit/a4071f0))\n* **config:** make the config impl spec compliant ([76b6670](https://github.com/ipfs/js-ipfs/commit/76b6670))\n* **config-http:** return error if value is invalid ([f7a668d](https://github.com/ipfs/js-ipfs/commit/f7a668d))\n* **factory:** add ipfs factory to files ([eba0398](https://github.com/ipfs/js-ipfs/commit/eba0398))\n* **factory:** add ipfs factory, verify it works with object tests ([3db096e](https://github.com/ipfs/js-ipfs/commit/3db096e))\n* **files.add:** update API to conform latest interface-ipfs-core updates ([28b0bb7](https://github.com/ipfs/js-ipfs/commit/28b0bb7))\n* **http:** Refactor inject tests, made them all pass again ([31f673d](https://github.com/ipfs/js-ipfs/commit/31f673d))\n* **http:** refactor ipfs-api tests and make them all pass again ([56904fd](https://github.com/ipfs/js-ipfs/commit/56904fd))\n* **object-http:** support protobuf encoded values ([5f02303](https://github.com/ipfs/js-ipfs/commit/5f02303))\n* **roadmap:** update ([418660f](https://github.com/ipfs/js-ipfs/commit/418660f))\n* **roadmap:** update roadmap ms2 with extra added goals ([ac5352e](https://github.com/ipfs/js-ipfs/commit/ac5352e))\n* disable PhantomJS ([921b11e](https://github.com/ipfs/js-ipfs/commit/921b11e))\n* **tests:** all tests running ([44dba6c](https://github.com/ipfs/js-ipfs/commit/44dba6c))\n* **tests:** factory-http ([08a4b19](https://github.com/ipfs/js-ipfs/commit/08a4b19))\n\n\n\n<a name=\"0.14.3\"></a>\n## [0.14.3](https://github.com/ipfs/js-ipfs/compare/v0.14.2...v0.14.3) (2016-08-10)\n\n\n### Features\n\n* **interface:** update interface-ipfs-core to v0.6.0 ([d855740](https://github.com/ipfs/js-ipfs/commit/d855740))\n\n\n\n<a name=\"0.14.2\"></a>\n## [0.14.2](https://github.com/ipfs/js-ipfs/compare/v0.14.1...v0.14.2) (2016-08-09)\n\n\n### Bug Fixes\n\n* upgrade aegir and ensure glob is mocked ([3c70eaa](https://github.com/ipfs/js-ipfs/commit/3c70eaa)), closes [#354](https://github.com/ipfs/js-ipfs/issues/354) [#353](https://github.com/ipfs/js-ipfs/issues/353)\n* **cli:** replace ronin with yargs ([cba42ca](https://github.com/ipfs/js-ipfs/commit/cba42ca)), closes [#331](https://github.com/ipfs/js-ipfs/issues/331)\n* **version:** return actual js-ipfs version ([6377ab2](https://github.com/ipfs/js-ipfs/commit/6377ab2)), closes [#377](https://github.com/ipfs/js-ipfs/issues/377)\n* use static version of package.json ([3ffdc27](https://github.com/ipfs/js-ipfs/commit/3ffdc27))\n\n\n### Features\n\n* update all dependencies ([b90747e](https://github.com/ipfs/js-ipfs/commit/b90747e))\n\n\n\n<a name=\"0.14.1\"></a>\n## [0.14.1](https://github.com/ipfs/js-ipfs/compare/v0.14.0...v0.14.1) (2016-06-29)\n\n\n\n<a name=\"0.14.0\"></a>\n## [0.14.0](https://github.com/ipfs/js-ipfs/compare/v0.13.0...v0.14.0) (2016-06-27)\n\n\n\n<a name=\"0.13.0\"></a>\n## [0.13.0](https://github.com/ipfs/js-ipfs/compare/v0.12.0...v0.13.0) (2016-06-07)\n\n\n\n<a name=\"0.12.0\"></a>\n## [0.12.0](https://github.com/ipfs/js-ipfs/compare/v0.11.1...v0.12.0) (2016-06-06)\n\n\n### Bug Fixes\n\n* handle new wantlist format ([7850dbb](https://github.com/ipfs/js-ipfs/commit/7850dbb))\n\n\n\n<a name=\"0.11.1\"></a>\n## [0.11.1](https://github.com/ipfs/js-ipfs/compare/v0.11.0...v0.11.1) (2016-05-30)\n\n\n\n<a name=\"0.11.0\"></a>\n## [0.11.0](https://github.com/ipfs/js-ipfs/compare/v0.10.3...v0.11.0) (2016-05-27)\n\n\n\n<a name=\"0.10.3\"></a>\n## [0.10.3](https://github.com/ipfs/js-ipfs/compare/v0.10.2...v0.10.3) (2016-05-26)\n\n\n\n<a name=\"0.10.2\"></a>\n## [0.10.2](https://github.com/ipfs/js-ipfs/compare/v0.10.1...v0.10.2) (2016-05-26)\n\n\n### Bug Fixes\n\n* use passed in repo location in the browser ([4b55102](https://github.com/ipfs/js-ipfs/commit/4b55102))\n\n\n\n<a name=\"0.10.1\"></a>\n## [0.10.1](https://github.com/ipfs/js-ipfs/compare/v0.10.0...v0.10.1) (2016-05-25)\n\n\n\n<a name=\"0.10.0\"></a>\n## [0.10.0](https://github.com/ipfs/js-ipfs/compare/v0.9.0...v0.10.0) (2016-05-24)\n\n\n\n<a name=\"0.9.0\"></a>\n## [0.9.0](https://github.com/ipfs/js-ipfs/compare/v0.8.0...v0.9.0) (2016-05-24)\n\n\n\n<a name=\"0.8.0\"></a>\n## [0.8.0](https://github.com/ipfs/js-ipfs/compare/v0.7.0...v0.8.0) (2016-05-23)\n\n\n\n<a name=\"0.7.0\"></a>\n## [0.7.0](https://github.com/ipfs/js-ipfs/compare/v0.6.1...v0.7.0) (2016-05-21)\n\n\n\n<a name=\"0.6.1\"></a>\n### [0.6.1](https://github.com/ipfs/js-ipfs/compare/v0.6.0...v0.6.1) (2016-05-19)\n\n\n\n<a name=\"0.6.0\"></a>\n## [0.6.0](https://github.com/ipfs/js-ipfs/compare/v0.5.0...v0.6.0) (2016-05-19)\n\n\n\n<a name=\"0.5.0\"></a>\n## [0.5.0](https://github.com/ipfs/js-ipfs/compare/v0.4.10...v0.5.0) (2016-05-16)\n\n\n### Bug Fixes\n\n* **files:add:** simplify checkPath ([46d9e6a](https://github.com/ipfs/js-ipfs/commit/46d9e6a))\n* **files:get:** simplify checkArgs ([7f89bfb](https://github.com/ipfs/js-ipfs/commit/7f89bfb))\n* **http:object:** proper handling of empty args ([9763f86](https://github.com/ipfs/js-ipfs/commit/9763f86))\n\n\n### Features\n\n* integrate libp2p-ipfs-browser ([6022b46](https://github.com/ipfs/js-ipfs/commit/6022b46))\n* make core/object satisfy interface-ipfs-core ([96013bb](https://github.com/ipfs/js-ipfs/commit/96013bb))\n\n\n\n<a name=\"0.4.10\"></a>\n### [0.4.10](https://github.com/ipfs/js-ipfs/compare/v0.4.9...v0.4.10) (2016-05-08)\n\n\n### Bug Fixes\n\n* **cli:** self host cmds listing ([a415dc1](https://github.com/ipfs/js-ipfs/commit/a415dc1))\n* **core:** consistent repo.exists checks ([3d1e6b0](https://github.com/ipfs/js-ipfs/commit/3d1e6b0))\n\n\n\n<a name=\"0.4.9\"></a>\n### [0.4.9](https://github.com/ipfs/js-ipfs/compare/v0.4.8...v0.4.9) (2016-04-28)\n\n\n\n<a name=\"0.4.8\"></a>\n### [0.4.8](https://github.com/ipfs/js-ipfs/compare/v0.4.7...v0.4.8) (2016-04-28)\n\n\n\n<a name=\"0.4.7\"></a>\n### [0.4.7](https://github.com/ipfs/js-ipfs/compare/v0.4.6...v0.4.7) (2016-04-25)\n\n\n\n<a name=\"0.4.6\"></a>\n### [0.4.6](https://github.com/ipfs/js-ipfs/compare/v0.4.4...v0.4.6) (2016-04-22)\n\n\n\n<a name=\"0.4.4\"></a>\n### [0.4.4](https://github.com/ipfs/js-ipfs/compare/v0.4.3...v0.4.4) (2016-03-22)\n\n\n\n<a name=\"0.4.3\"></a>\n### [0.4.3](https://github.com/ipfs/js-ipfs/compare/v0.4.2...v0.4.3) (2016-03-21)\n\n\n\n<a name=\"0.4.2\"></a>\n### [0.4.2](https://github.com/ipfs/js-ipfs/compare/v0.4.1...v0.4.2) (2016-03-21)\n\n\n\n<a name=\"0.4.1\"></a>\n### [0.4.1](https://github.com/ipfs/js-ipfs/compare/v0.4.0...v0.4.1) (2016-03-16)\n\n\n\n<a name=\"0.4.0\"></a>\n## [0.4.0](https://github.com/ipfs/js-ipfs/compare/v0.3.1...v0.4.0) (2016-02-23)\n\n\n\n<a name=\"0.3.1\"></a>\n### [0.3.1](https://github.com/ipfs/js-ipfs/compare/v0.3.0...v0.3.1) (2016-02-19)\n\n\n\n<a name=\"0.3.0\"></a>\n## [0.3.0](https://github.com/ipfs/js-ipfs/compare/v0.2.3...v0.3.0) (2016-02-03)\n\n\n\n<a name=\"0.2.3\"></a>\n### [0.2.3](https://github.com/ipfs/js-ipfs/compare/v0.2.2...v0.2.3) (2016-01-31)\n\n\n\n<a name=\"0.2.2\"></a>\n### [0.2.2](https://github.com/ipfs/js-ipfs/compare/v0.2.1...v0.2.2) (2016-01-28)\n\n\n\n<a name=\"0.2.1\"></a>\n### [0.2.1](https://github.com/ipfs/js-ipfs/compare/v0.2.0...v0.2.1) (2016-01-28)\n\n\n\n<a name=\"0.2.0\"></a>\n## [0.2.0](https://github.com/ipfs/js-ipfs/compare/v0.0.3...v0.2.0) (2016-01-27)\n\n\n\n<a name=\"0.0.3\"></a>\n### [0.0.3](https://github.com/ipfs/js-ipfs/compare/v0.0.2...v0.0.3) (2016-01-15)\n\n\n\n<a name=\"0.0.2\"></a>\n## 0.0.2 (2016-01-11)"
  },
  {
    "path": "packages/ipfs/CODE_OF_CONDUCT.md",
    "content": "# Contributor Code of Conduct\n\nThe `js-ipfs` project follows the [`IPFS Community Code of Conduct`](https://github.com/ipfs/community/blob/master/code-of-conduct.md)\n"
  },
  {
    "path": "packages/ipfs/CONTRIBUTING.md",
    "content": "# Contributing guidelines\n\nIPFS as a project, including js-ipfs and all of its modules, follows the [standard IPFS Community contributing guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md).\n\nWe also adhere to the [IPFS JavaScript Community contributing guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING_JS.md) which provide additional information of how to collaborate and contribute in the JavaScript implementation of IPFS.\n\nWe appreciate your time and attention for going over these. Please open an issue on [ipfs/community](https://github.com/ipfs/community) if you have any question.\n\nThank you.\n"
  },
  {
    "path": "packages/ipfs/COPYRIGHT",
    "content": "This project is transitioning from an MIT-only license to a dual MIT/Apache-2.0 license.\nUnless otherwise noted, all code contributed prior to 2019-11-21 and not contributed by\na user listed in [this signoff issue](https://github.com/ipfs/js-ipfs/issues/2624) is\nlicensed under MIT-only. All new contributions (and past contributions since 2019-11-21)\nare licensed under a dual MIT/Apache-2.0 license.\n"
  },
  {
    "path": "packages/ipfs/LICENSE",
    "content": "This project is dual licensed under MIT and Apache-2.0.\n\nMIT: https://www.opensource.org/licenses/mit\nApache-2.0: https://www.apache.org/licenses/license-2.0\n"
  },
  {
    "path": "packages/ipfs/LICENSE-APACHE",
    "content": "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\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless 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.\n"
  },
  {
    "path": "packages/ipfs/LICENSE-MIT",
    "content": "The MIT License (MIT)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "packages/ipfs/Makefile",
    "content": "all: help\n\ntest: test_expensive\n\ntest_short: test_sharness_short\n\ntest_expensive: test_sharness_expensive\n\ntest_sharness_short:\n\t$(MAKE) -j1 -C test/sharness/\n\ntest_sharness_expensive:\n\tTEST_EXPENSIVE=1 $(MAKE) -j1 -C test/sharness/\n\nhelp:\n\t@echo 'TESTING TARGETS:'\n\t@echo ''\n\t@echo '  test                    - Run expensive tests'\n\t@echo '  test_short              - Run short tests and sharness tests'\n\t@echo '  test_expensive          - Run a few extras'\n\t@echo '  test_sharness_short'\n\t@echo '  test_sharness_expensive'\n\t@echo ''\n"
  },
  {
    "path": "packages/ipfs/README.md",
    "content": "> # ⛔️ DEPRECATED: [js-IPFS](https://github.com/ipfs/js-ipfs) has been superseded by [Helia](https://github.com/ipfs/helia)\n>\n> 📚 [Learn more about this deprecation](https://github.com/ipfs/js-ipfs/issues/4336) or [how to migrate](https://github.com/ipfs/helia/wiki/Migrating-from-js-IPFS)\n>\n> ⚠️ If you continue using this repo, please note that security fixes will not be provided\n\n# ipfs <!-- omit in toc -->\n\n[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech)\n[![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech)\n[![codecov](https://img.shields.io/codecov/c/github/ipfs/js-ipfs.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipfs)\n[![CI](https://img.shields.io/github/actions/workflow/status/ipfs/js-ipfs/test.yml?branch=master\\&style=flat-square)](https://github.com/ipfs/js-ipfs/actions/workflows/test.yml?query=branch%3Amaster)\n\n> JavaScript implementation of the IPFS specification\n\n## Table of contents <!-- omit in toc -->\n\n- [Install](#install)\n- [Getting Started](#getting-started)\n  - [Next Steps](#next-steps)\n- [Want to hack on IPFS?](#want-to-hack-on-ipfs)\n- [License](#license)\n- [Contribute](#contribute)\n\n## Install\n\n```console\n$ npm i ipfs\n```\n\n<p align=\"center\">\n  <a href=\"https://js.ipfs.io\" title=\"JS IPFS\">\n    <img src=\"https://ipfs.io/ipfs/Qme6KJdKcp85TYbLxuLV7oQzMiLremD7HMoXLZEmgo6Rnh/js-ipfs-sticker.png\" alt=\"IPFS in JavaScript logo\" width=\"244\" />\n  </a>\n</p>\n\n<h3 align=\"center\">An <a href=\"https://docs.ipfs.io/basics/ipfs-implementations/\">IPFS protocol implementation</a> written in JS</h3>\n\n<p align=\"center\">\n  <a href=\"https://github.com/ipfs/js-ipfs/tree/master/packages/interface-ipfs-core\"><img src=\"https://img.shields.io/badge/interface--ipfs--core-API%20Docs-blue.svg\"></a>\n  <a href=\"https://travis-ci.com/ipfs/js-ipfs?branch=master\"><img src=\"https://badgen.net/travis/ipfs/js-ipfs?branch=master\" /></a>\n  <a href=\"https://codecov.io/gh/ipfs/js-ipfs\"><img src=\"https://badgen.net/codecov/c/github/ipfs/js-ipfs\" /></a>\n  <a href=\"https://bundlephobia.com/result?p=ipfs\"><img src=\"https://badgen.net/bundlephobia/minzip/ipfs\"></a>\n  <a href=\"https://david-dm.org/ipfs/js-ipfs?path=packages/ipfs\"><img src=\"https://david-dm.org/ipfs/js-ipfs.svg?style=flat&path=packages/ipfs\" /></a>\n  <a href=\"https://github.com/feross/standard\"><img src=\"https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat\"></a>\n  <a href=\"\"><img src=\"https://img.shields.io/badge/npm-%3E%3D6.0.0-orange.svg?style=flat\" /></a>\n  <a href=\"\"><img src=\"https://img.shields.io/badge/Node.js-%3E%3D10.0.0-orange.svg?style=flat\" /></a>\n  <a href=\"https://www.npmjs.com/package/ipfs\"><img src=\"https://img.shields.io/npm/dm/ipfs.svg\" /></a>\n  <a href=\"https://www.jsdelivr.com/package/npm/ipfs\"><img src=\"https://data.jsdelivr.com/v1/package/npm/ipfs/badge\"/></a>\n  <br>\n</p>\n\n`ipfs` is the core API, a CLI and a HTTP server that functions as a HTTP to IPFS bridge and an RPC endpoint.\n\nIf you want to integrate IPFS into your application without including a CLI or HTTP server, see the [ipfs-core](https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-core) module.\n\n## Getting Started\n\nInstalling `ipfs` globally will give you the `jsipfs` command which you can use to start a daemon running:\n\n```console\n$ npm install -g ipfs\n$ jsipfs daemon\nInitializing IPFS daemon...\njs-ipfs version: x.x.x\nSystem version: x64/darwin\nNode.js version: x.x.x\nSwarm listening on /ip4/127.0\n.... more output\n```\n\nYou can then add a file:\n\n```console\n$ jsipfs add ./hello-world.txt\nadded QmXXY5ZxbtuYj6DnfApLiGstzPN7fvSyigrRee3hDWPCaf hello-world.txt\n```\n\n### Next Steps\n\n- Read the [docs](https://github.com/ipfs/js-ipfs/tree/master/docs)\n- Look into the [examples](https://github.com/ipfs-examples/js-ipfs-examples) to learn how to spawn an IPFS node in Node.js and in the Browser\n- Consult the [Core API docs](https://github.com/ipfs/js-ipfs/tree/master/docs/core-api) to see what you can do with an IPFS node\n- Head over to <https://proto.school> to take interactive tutorials that cover core IPFS APIs\n- Check out <https://docs.ipfs.io> for tips, how-tos and more\n- See <https://blog.ipfs.io> for news and more\n- Need help? Please ask 'How do I?' questions on <https://discuss.ipfs.io>\n\n## Want to hack on IPFS?\n\n[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md)\n\nThis IPFS implementation in JavaScript needs your help!  There are a few things you can do right now to help out:\n\nRead the [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md) and [JavaScript Contributing Guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING_JS.md).\n\n- **Check out existing issues** The [issue list](https://github.com/ipfs/js-ipfs/issues) has many that are marked as ['help wanted'](https://github.com/ipfs/js-ipfs/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22help+wanted%22) or ['difficulty:easy'](https://github.com/ipfs/js-ipfs/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Adifficulty%3Aeasy) which make great starting points for development, many of which can be tackled with no prior IPFS knowledge\n- **Perform code reviews** More eyes will help\n  a. speed the project along\n  b. ensure quality, and\n  c. reduce possible future bugs.\n- **Add tests**. There can never be enough tests.\n\nFind out about chat channels, the IPFS newsletter, the IPFS blog, and more in the [IPFS community space](https://docs.ipfs.io/community/).\n\n## License\n\nLicensed under either of\n\n- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / <http://www.apache.org/licenses/LICENSE-2.0>)\n- MIT ([LICENSE-MIT](LICENSE-MIT) / <http://opensource.org/licenses/MIT>)\n\n## Contribute\n\nContributions welcome! Please check out [the issues](https://github.com/ipfs/js-ipfs/issues).\n\nAlso 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.\n\nPlease 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).\n\nUnless 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.\n\n[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md)\n"
  },
  {
    "path": "packages/ipfs/init-and-daemon.sh",
    "content": "#! /usr/bin/env bash\n\nset -e\n\nif [ -n \"$IPFS_PATH\" ]; then\n  echo \"Using $IPFS_PATH as IPFS repository\"\nelse\n  echo \"You need to set IPFS_PATH environment variable to use this script\"\n  exit 1\nfi\n\n# Initialize the repo but ignore if error if it already exists\n# This can be the case when we restart a container without stopping/removing it\nnode src/cli.js init || true\n\nif [ -n \"$IPFS_API_HOST\" ]; then\n  sed -i.bak \"s/127.0.0.1/$IPFS_API_HOST/g\" $IPFS_PATH/config\nfi\n\nnode src/cli.js daemon\n"
  },
  {
    "path": "packages/ipfs/maintainer.json",
    "content": "{\n  \"repoLeadMaintainer\": {\n    \"name\": \"Alan Shaw\",\n    \"email\": \"alan.shaw@protocol.ai\",\n    \"username\": \"alanshaw\"\n  },\n  \"workingGroup\": {\n    \"name\": \"JS IPFS\",\n    \"entryPoint\": \"https://github.com/ipfs/js-core\"\n  }\n}\n"
  },
  {
    "path": "packages/ipfs/package.json",
    "content": "{\n  \"name\": \"ipfs\",\n  \"version\": \"0.66.1\",\n  \"description\": \"JavaScript implementation of the IPFS specification\",\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"homepage\": \"https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs#readme\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ipfs/js-ipfs.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ipfs/js-ipfs/issues\"\n  },\n  \"keywords\": [\n    \"IPFS\"\n  ],\n  \"engines\": {\n    \"node\": \">=16.0.0\",\n    \"npm\": \">=7.0.0\"\n  },\n  \"bin\": {\n    \"jsipfs\": \"src/cli.js\"\n  },\n  \"type\": \"module\",\n  \"types\": \"./dist/src/index.d.ts\",\n  \"typesVersions\": {\n    \"*\": {\n      \"*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ],\n      \"src/*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ]\n    }\n  },\n  \"files\": [\n    \"src\",\n    \"dist\",\n    \"!dist/test\",\n    \"!**/*.tsbuildinfo\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/src/index.d.ts\",\n      \"import\": \"./src/index.js\"\n    },\n    \"./path\": {\n      \"types\": \"./src/path.d.ts\",\n      \"browser\": \"./src/path.browser.js\",\n      \"import\": \"./src/path.js\"\n    }\n  },\n  \"eslintConfig\": {\n    \"extends\": \"ipfs\",\n    \"parserOptions\": {\n      \"sourceType\": \"module\"\n    }\n  },\n  \"scripts\": {\n    \"build\": \"aegir build\",\n    \"prepublishOnly\": \"node scripts/update-version.js\",\n    \"lint\": \"aegir lint\",\n    \"test:interface:core\": \"aegir test -f test/interface-core.js\",\n    \"test:interface:client\": \"aegir test -f test/interface-client.js\",\n    \"test:interface:http-js\": \"aegir test -f test/interface-http-js.js\",\n    \"test:interface:http-go\": \"aegir test -f test/interface-http-go.js\",\n    \"test:interop\": \"cross-env DEBUG=$DEBUG IPFS_LOGGING=$IPFS_LOGGING IPFS_JS_EXEC=$PWD/src/cli.js KUBO_RPC_MODULE=$PWD/../ipfs-http-client/src/index.js LIBP2P_TCP_REUSEPORT=false ipfs-interop\",\n    \"test:external\": \"aegir test-dependant\",\n    \"clean\": \"aegir clean\",\n    \"dep-check\": \"aegir dep-check -i ipfs-core-types -i @types/*\"\n  },\n  \"dependencies\": {\n    \"@libp2p/logger\": \"^2.0.5\",\n    \"ipfs-cli\": \"^0.16.1\",\n    \"ipfs-core\": \"^0.18.1\",\n    \"semver\": \"^7.3.2\",\n    \"update-notifier\": \"^6.0.0\"\n  },\n  \"devDependencies\": {\n    \"@libp2p/webrtc-star-signalling-server\": \"^3.0.0\",\n    \"@libp2p/websockets\": \"^5.0.0\",\n    \"@types/semver\": \"^7.3.4\",\n    \"@types/update-notifier\": \"^6.0.1\",\n    \"aegir\": \"^37.11.0\",\n    \"cross-env\": \"^7.0.0\",\n    \"go-ipfs\": \"^0.12.0\",\n    \"interface-ipfs-core\": \"^0.158.1\",\n    \"ipfs-client\": \"^0.10.1\",\n    \"ipfs-core-types\": \"^0.14.1\",\n    \"ipfs-http-client\": \"^60.0.1\",\n    \"ipfs-interop\": \"^10.0.0\",\n    \"ipfs-utils\": \"^9.0.13\",\n    \"ipfsd-ctl\": \"^13.0.0\",\n    \"iso-url\": \"^1.0.0\",\n    \"kubo-rpc-client\": \"^3.0.0\",\n    \"merge-options\": \"^3.0.4\",\n    \"mock-ipfs-pinning-service\": \"^0.4.2\",\n    \"url\": \"^0.11.0\"\n  },\n  \"optionalDependencies\": {\n    \"electron-webrtc\": \"^0.3.0\",\n    \"wrtc\": \"^0.4.6\"\n  },\n  \"browser\": {\n    \"./src/cli.js\": false,\n    \"./src/path.js\": \"./src/path.browser.js\",\n    \"go-ipfs\": false\n  }\n}\n"
  },
  {
    "path": "packages/ipfs/scripts/update-version.js",
    "content": "import { readFile, writeFile } from 'fs/promises'\n\nconst pkg = JSON.parse(\n  await readFile(\n    new URL('../package.json', import.meta.url)\n  )\n)\n\nawait writeFile(\n  new URL('../src/package.js', import.meta.url),\n  `\nexport const name = '${pkg.name}'\nexport const version = '${pkg.version}'\nexport const node = '${pkg.engines.node}'\n`\n)\n"
  },
  {
    "path": "packages/ipfs/src/cli.js",
    "content": "#! /usr/bin/env node\n\n/* eslint-disable no-console */\n\n/**\n * Handle any uncaught errors\n *\n * @param {any} err\n * @param {string} [origin]\n */\nimport semver from 'semver'\nimport * as pkg from './package.js'\nimport { logger } from '@libp2p/logger'\n\nimport { print, getIpfs, getRepoPath } from 'ipfs-cli/utils'\nimport { cli } from 'ipfs-cli'\n\nimport updateNotifier from 'update-notifier'\n\n/**\n * @param {any} err\n * @param {string} origin\n */\nconst onUncaughtException = (err, origin) => {\n  if (!origin || origin === 'uncaughtException') {\n    console.error(err)\n    process.exit(1)\n  }\n}\n\n/**\n * Handle any uncaught errors\n *\n * @param {any} err\n */\nconst onUnhandledRejection = (err) => {\n  console.error(err)\n  process.exit(1)\n}\n\nprocess.once('uncaughtException', onUncaughtException)\nprocess.once('unhandledRejection', onUnhandledRejection)\n\nif (process.env.DEBUG) {\n  process.on('warning', err => {\n    console.error(err.stack)\n  })\n}\n\nconst log = logger('ipfs:cli')\n\nprocess.title = pkg.name\n\n// Check for node version\nif (!semver.satisfies(process.versions.node, pkg.node)) {\n  console.error(`Please update your Node.js version to ${pkg.node}`)\n  process.exit(1)\n}\n\n// If we're not running an rc, check if an update is available and notify\nif (!pkg.version.includes('-rc')) {\n  const oneWeek = 1000 * 60 * 60 * 24 * 7\n  updateNotifier({ pkg, updateCheckInterval: oneWeek }).notify()\n}\n\n/**\n * @param {string[]} argv\n */\nasync function main (argv) {\n  let exitCode = 0\n  let ctx = {\n    print,\n    getStdin: () => process.stdin,\n    repoPath: getRepoPath(),\n    cleanup: () => {},\n    isDaemon: false,\n    /** @type {import('ipfs-core-types').IPFS | undefined} */\n    ipfs: undefined\n  }\n\n  const command = argv.slice(2)\n\n  try {\n    await cli(command, async (argv) => {\n      if (!['daemon', 'init'].includes(command[0])) {\n        // @ts-expect-error argv as no properties in common\n        const { ipfs, isDaemon, cleanup } = await getIpfs(argv)\n\n        ctx = {\n          ...ctx,\n          ipfs,\n          isDaemon,\n          cleanup\n        }\n      }\n\n      argv.ctx = ctx\n    })\n  } catch (/** @type {any} */ err) {\n    // TODO: export errors from ipfs-repo to use .code constants\n    if (err.code === 'ERR_INVALID_REPO_VERSION') {\n      err.message = 'Incompatible repo version. Migration needed. Pass --migrate for automatic migration'\n    }\n\n    if (err.code === 'ERR_NOT_ENABLED') {\n      err.message = `no IPFS repo found in ${ctx.repoPath}.\\nplease run: 'ipfs init'`\n    }\n\n    // Handle yargs errors\n    if (err.code === 'ERR_YARGS') {\n      err.yargs.showHelp()\n      ctx.print.error('\\n')\n      ctx.print.error(`Error: ${err.message}`)\n    } else if (log.enabled) {\n      // Handle commands handler errors\n      log(err)\n    } else {\n      ctx.print.error(err.message)\n    }\n\n    exitCode = 1\n  } finally {\n    await ctx.cleanup()\n  }\n\n  if (command[0] === 'daemon') {\n    // don't shut down the daemon process\n    return\n  }\n\n  process.exit(exitCode)\n}\n\nmain(process.argv)\n"
  },
  {
    "path": "packages/ipfs/src/index.js",
    "content": "import {\n  create as createImport,\n  globSource as globSourceImport,\n  urlSource as urlSourceImport\n} from 'ipfs-core'\nimport {\n  path as pathImport\n} from './path.js'\n\n/**\n * @typedef {import('ipfs-core-types').IPFS} IPFS\n */\n\nexport const create = createImport\nexport const globSource = globSourceImport\nexport const urlSource = urlSourceImport\nexport const path = pathImport\n"
  },
  {
    "path": "packages/ipfs/src/package.js",
    "content": "\nexport const name = 'ipfs'\nexport const version = '0.62.2'\nexport const node = '>=15.0.0'\n"
  },
  {
    "path": "packages/ipfs/src/path.browser.js",
    "content": "\nexport function path () {\n  throw new Error('Not supported in browsers')\n}\n"
  },
  {
    "path": "packages/ipfs/src/path.js",
    "content": "import fs from 'fs'\nimport Path from 'path'\n\nexport function path () {\n  const paths = []\n\n  // simulate node's node_modules lookup\n  for (let i = 0; i < process.cwd().split(Path.sep).length; i++) {\n    const dots = new Array(i).fill('..')\n\n    paths.push(\n      Path.resolve(\n        Path.join(process.cwd(), ...dots, 'node_modules', 'ipfs')\n      )\n    )\n  }\n\n  const resourcePath = paths.find(path => fs.existsSync(path))\n\n  if (!resourcePath) {\n    throw new Error(`Could not find ipfs module in paths: \\n${paths.join('\\n')}`)\n  }\n\n  const pkg = JSON.parse(fs.readFileSync(resourcePath + Path.sep + 'package.json', {\n    encoding: 'utf-8'\n  }))\n\n  const bin = pkg.bin.jsipfs\n\n  return Path.resolve(Path.join(resourcePath, bin))\n}\n"
  },
  {
    "path": "packages/ipfs/test/interface-client.js",
    "content": "/* eslint-env mocha, browser */\n\nimport * as tests from 'interface-ipfs-core'\nimport { isNode } from 'ipfs-utils/src/env.js'\nimport { factory } from './utils/factory.js'\nimport * as ipfsClientModule from 'ipfs-client'\n\ndescribe('interface-ipfs-core ipfs-client tests', () => {\n  const commonFactory = factory({\n    type: 'js',\n    ipfsClientModule\n  })\n\n  tests.files(commonFactory, {\n    skip: [{\n      name: '.files.chmod',\n      reason: 'not implemented'\n    }, {\n      name: '.files.cp',\n      reason: 'not implemented'\n    }, {\n      name: '.files.mkdir',\n      reason: 'not implemented'\n    }, {\n      name: '.files.stat',\n      reason: 'not implemented'\n    }, {\n      name: '.files.touch',\n      reason: 'not implemented'\n    }, {\n      name: '.files.rm',\n      reason: 'not implemented'\n    }, {\n      name: '.files.read',\n      reason: 'not implemented'\n    }, {\n      name: '.files.mv',\n      reason: 'not implemented'\n    }, {\n      name: '.files.flush',\n      reason: 'not implemented'\n    }].concat(isNode\n      ? []\n      : [{\n          name: 'should make directory and specify mtime as hrtime',\n          reason: 'Not designed to run in the browser'\n        }, {\n          name: 'should set mtime as hrtime',\n          reason: 'Not designed to run in the browser'\n        }, {\n          name: 'should write file and specify mtime as hrtime',\n          reason: 'Not designed to run in the browser'\n        }])\n  })\n\n  tests.root(commonFactory, {\n    skip: [\n      {\n        name: 'add',\n        reason: 'not implemented'\n      },\n      {\n        name: 'should add with only-hash=true',\n        reason: 'ipfs.object.get is not implemented'\n      },\n      {\n        name: 'should add a directory with only-hash=true',\n        reason: 'ipfs.object.get is not implemented'\n      },\n      {\n        name: 'should add with mtime as hrtime',\n        reason: 'process.hrtime is not a function in browser'\n      },\n      {\n        name: 'should add from a URL with only-hash=true',\n        reason: 'ipfs.object.get is not implemented'\n      },\n      {\n        name: 'should cat with a Uint8Array multihash',\n        reason: 'Passing CID as Uint8Array is not supported'\n      },\n      {\n        name: 'should add from a HTTP URL',\n        reason: 'https://github.com/ipfs/js-ipfs/issues/3195'\n      },\n      {\n        name: 'should add from a HTTP URL with redirection',\n        reason: 'https://github.com/ipfs/js-ipfs/issues/3195'\n      },\n      {\n        name: 'should add from a URL with only-hash=true',\n        reason: 'https://github.com/ipfs/js-ipfs/issues/3195'\n      },\n      {\n        name: 'should add from a URL with wrap-with-directory=true',\n        reason: 'https://github.com/ipfs/js-ipfs/issues/3195'\n      },\n      {\n        name: 'should add from a URL with wrap-with-directory=true and URL-escaped file name',\n        reason: 'https://github.com/ipfs/js-ipfs/issues/3195'\n      },\n      {\n        name: 'should not add from an invalid url',\n        reason: 'https://github.com/ipfs/js-ipfs/issues/3195'\n      },\n      {\n        name: 'should be able to add dir without sharding',\n        reason: 'Cannot spawn IPFS with different args'\n      },\n      {\n        name: 'with sharding',\n        reason: 'TODO: allow spawning new daemons with different config'\n      },\n      {\n        name: 'get',\n        reason: 'Not implemented'\n      },\n      {\n        name: 'refs',\n        reason: 'Not implemented'\n      },\n      {\n        name: 'refsLocal',\n        reason: 'Not implemented'\n      }\n    ]\n  })\n\n  tests.miscellaneous(commonFactory, {\n    skip: [\n      {\n        name: '.dns',\n        reason: 'Not implemented'\n      },\n      {\n        name: '.resolve',\n        reason: 'Not implemented'\n      },\n      {\n        name: '.stop',\n        reason: 'Not implemented'\n      },\n      {\n        name: '.version',\n        reason: 'Not implemented'\n      }\n    ]\n  })\n\n  tests.pubsub(factory({\n    type: 'js',\n    ipfsClientModule\n  }))\n})\n"
  },
  {
    "path": "packages/ipfs/test/interface-core.js",
    "content": "/* eslint-env mocha, browser */\n\nimport * as tests from 'interface-ipfs-core'\nimport { isNode } from 'ipfs-utils/src/env.js'\nimport { factory } from './utils/factory.js'\nimport * as ipfsClientModule from 'ipfs-client'\n\n/** @typedef { import(\"ipfsd-ctl\").ControllerOptions } ControllerOptions */\n\ndescribe('interface-ipfs-core tests', function () {\n  const commonFactory = factory({\n    ipfsClientModule\n  })\n\n  tests.root(commonFactory, {\n    skip: isNode\n      ? []\n      : [{\n          name: 'should add with mtime as hrtime',\n          reason: 'Not designed to run in the browser'\n        }]\n  })\n\n  tests.bitswap(commonFactory)\n\n  tests.block(commonFactory)\n\n  tests.bootstrap(commonFactory)\n\n  tests.config(commonFactory)\n\n  tests.dag(commonFactory)\n\n  tests.dht(commonFactory)\n\n  tests.files(factory(), {\n    skip: isNode\n      ? null\n      : [{\n          name: 'should make directory and specify mtime as hrtime',\n          reason: 'Not designed to run in the browser'\n        }, {\n          name: 'should set mtime as hrtime',\n          reason: 'Not designed to run in the browser'\n        }, {\n          name: 'should write file and specify mtime as hrtime',\n          reason: 'Not designed to run in the browser'\n        }]\n  })\n\n  tests.key(commonFactory)\n\n  tests.miscellaneous(commonFactory, {\n    skip: [\n      {\n        name: 'should include the ipfs-http-client version',\n        reason: 'Value is added by the HTTP RPC API server which is not part of ipfs-core'\n      }\n    ]\n  })\n\n  tests.name(factory({\n    ipfsOptions: {\n      offline: true\n    }\n  }))\n\n  tests.namePubsub(factory({\n    ipfsOptions: {\n      EXPERIMENTAL: {\n        ipnsPubsub: true\n      }\n    }\n  }))\n\n  tests.object(commonFactory)\n\n  tests.pin(commonFactory, {\n    skip: [{\n      name: '.pin.remote.service',\n      reason: 'Not implemented'\n    }, {\n      name: '.pin.remote.add',\n      reason: 'Not implemented'\n    }, {\n      name: '.pin.remote.ls',\n      reason: 'Not implemented'\n    }, {\n      name: '.pin.remote.rm',\n      reason: 'Not implemented'\n    }, {\n      name: '.pin.remote.rmAll',\n      reason: 'Not implemented'\n    }]\n  })\n\n  tests.ping(commonFactory)\n\n  tests.pubsub(commonFactory, {\n    skip: [\n      ...(isNode\n        ? []\n        : [\n            {\n              name: 'should receive messages from a different node',\n              reason: 'https://github.com/ipfs/js-ipfs/issues/2662'\n            },\n            {\n              name: 'should round trip a non-utf8 binary buffer',\n              reason: 'https://github.com/ipfs/js-ipfs/issues/2662'\n            },\n            {\n              name: 'should receive multiple messages',\n              reason: 'https://github.com/ipfs/js-ipfs/issues/2662'\n            },\n            {\n              name: 'should send/receive 100 messages',\n              reason: 'https://github.com/ipfs/js-ipfs/issues/2662'\n            }])\n    ]\n  })\n\n  tests.repo(commonFactory)\n\n  tests.stats(commonFactory)\n\n  tests.swarm(commonFactory)\n})\n"
  },
  {
    "path": "packages/ipfs/test/interface-http-go.js",
    "content": "/* eslint-env mocha */\n\nimport * as tests from 'interface-ipfs-core'\nimport { factory } from './utils/factory.js'\nconst isWindows = globalThis.process && globalThis.process.platform && globalThis.process.platform === 'win32'\nconst isFirefox = globalThis.navigator?.userAgent?.toLowerCase().includes('firefox')\n\n/** @typedef {import(\"ipfsd-ctl\").ControllerOptions} ControllerOptions */\n\ndescribe('interface-ipfs-core over ipfs-http-client tests against go-ipfs', () => {\n  const commonFactory = factory({\n    type: 'go'\n  })\n\n  tests.root(commonFactory, {\n    skip: [\n      {\n        name: 'should add with mode as string',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should add with mode as number',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should add with mtime as Date',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should add with mtime as { nsecs, secs }',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should add with mtime as timespec',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should add with mtime as hrtime',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should export a chunk of a file',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should ls with metadata',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should ls single file with metadata',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should ls single file without containing directory with metadata',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should override raw leaves when file is smaller than one block and metadata is present',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should add directories with metadata',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should support bidirectional streaming',\n        reason: 'Not supported by http'\n      },\n      {\n        name: 'should error during add-all stream',\n        reason: 'Not supported by http'\n      }\n    ].concat(isFirefox\n      ? [{\n          name: 'should add a BIG Uint8Array',\n          reason: 'https://github.com/microsoft/playwright/issues/4704#issuecomment-826782602'\n        }, {\n          name: 'should add a BIG Uint8Array with progress enabled',\n          reason: 'https://github.com/microsoft/playwright/issues/4704#issuecomment-826782602'\n        }, {\n          name: 'should add big files',\n          reason: 'https://github.com/microsoft/playwright/issues/4704#issuecomment-826782602'\n        }]\n      : [])\n  })\n\n  tests.bitswap(commonFactory, {\n    skip: [\n      {\n        name: '.bitswap.unwant',\n        reason: 'TODO not implemented in go-ipfs yet'\n      }\n    ]\n  })\n\n  tests.block(commonFactory)\n\n  tests.bootstrap(commonFactory)\n\n  tests.config(commonFactory, {\n    skip: [\n      // config.replace\n      {\n        name: 'replace',\n        reason: 'FIXME Waiting for fix on go-ipfs https://github.com/ipfs/js-ipfs-http-client/pull/307#discussion_r69281789 and https://github.com/ipfs/go-ipfs/issues/2927'\n      },\n      {\n        name: 'should list config profiles',\n        reason: 'TODO: Not implemented in go-ipfs'\n      },\n      {\n        name: 'should strip private key from diff output',\n        reason: 'TODO: Not implemented in go-ipfs'\n      }\n    ]\n  })\n\n  tests.dag(commonFactory, {\n    skip: [\n      // dag.get:\n      {\n        name: 'should get only a CID, due to resolving locally only',\n        reason: 'FIXME: go-ipfs does not support localResolve option'\n      },\n      {\n        name: 'should get a node added as CIDv0 with a CIDv1',\n        reason: 'go-ipfs doesn\\'t use CIDv0 for DAG API anymore'\n      }\n    ]\n  })\n\n  tests.dht(commonFactory, {\n    skip: [\n      {\n        name: 'should error when DHT not available',\n        reason: 'go returns a query error'\n      }\n    ]\n  })\n\n  tests.files(commonFactory, {\n    skip: [\n      {\n        name: 'should ls directory',\n        reason: 'TODO unskip when go-ipfs supports --long https://github.com/ipfs/go-ipfs/pull/6528'\n      },\n      {\n        name: 'should list a file directly',\n        reason: 'TODO unskip when go-ipfs supports --long https://github.com/ipfs/go-ipfs/pull/6528'\n      },\n      {\n        name: 'should ls directory and include metadata',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should read from outside of mfs',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should ls from outside of mfs',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should update the mode for a file',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should update the mode for a directory',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should update the mode for a hamt-sharded-directory',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should update modes with basic symbolic notation that adds bits',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should update modes with basic symbolic notation that removes bits',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should update modes with basic symbolic notation that overrides bits',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should update modes with multiple symbolic notation',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should update modes with special symbolic notation',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should apply special execute permissions to world',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should apply special execute permissions to user',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should apply special execute permissions to user and group',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should apply special execute permissions to sharded directories',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should update file mtime',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should update directory mtime',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should update the mtime for a hamt-sharded-directory',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should create an empty file',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should make directory and specify mode',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should make directory and specify mtime',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should write file and specify mode',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should write file and specify mtime',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should respect metadata when copying files',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should respect metadata when copying directories',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should respect metadata when copying from outside of mfs',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should have default mtime',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should set mtime as Date',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should set mtime as { nsecs, secs }',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should set mtime as timespec',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should set mtime as hrtime',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should make directory and have default mode',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should make directory and specify mode as string',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should make directory and specify mode as number',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should make directory and specify mtime as Date',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should make directory and specify mtime as { nsecs, secs }',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should make directory and specify mtime as timespec',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should make directory and specify mtime as hrtime',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should write file and specify mode as a string',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should write file and specify mode as a number',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should write file and specify mtime as Date',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should write file and specify mtime as { nsecs, secs }',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should write file and specify mtime as timespec',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should write file and specify mtime as hrtime',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should stat file with mode',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should stat file with mtime',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should stat dir with mode',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should stat dir with mtime',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should stat sharded dir with mode',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should stat sharded dir with mtime',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'lists a raw node',\n        reason: 'TODO go-ipfs does not support ipfs paths for all mfs commands'\n      },\n      {\n        name: 'lists a raw node in an mfs directory',\n        reason: 'TODO go-ipfs does not support non-ipfs nodes in mfs'\n      },\n      {\n        name: 'writes a small file with an escaped slash in the title',\n        reason: 'TODO go-ipfs does not support escapes in paths'\n      },\n      {\n        name: 'overwrites a file with a different CID version',\n        reason: 'TODO go-ipfs does not support changing the CID version'\n      },\n      {\n        name: 'partially overwrites a file with a different CID version',\n        reason: 'TODO go-ipfs does not support changing the CID version'\n      },\n      {\n        name: 'refuses to copy multiple files to a non-existent child directory',\n        reason: 'TODO go-ipfs does not support copying multiple files at once'\n      },\n      {\n        name: 'refuses to copy files to an unreadable node',\n        reason: 'TODO go-ipfs does not support identity format, maybe in 0.5.0?'\n      },\n      {\n        name: 'copies a file to a pre-existing directory',\n        reason: 'TODO go-ipfs does not copying files into existing directories if the directory is specify as the target path'\n      },\n      {\n        name: 'copies multiple files to new location',\n        reason: 'TODO go-ipfs does not support copying multiple files at once'\n      },\n      {\n        name: 'copies files to deep mfs paths and creates intermediate directories',\n        reason: 'TODO go-ipfs does not support the parents flag in the cp command'\n      },\n      {\n        name: 'copies a sharded directory to a normal directory',\n        reason: 'TODO go-ipfs does not copying files into existing directories if the directory is specify as the target path'\n      },\n      {\n        name: 'copies a normal directory to a sharded directory',\n        reason: 'TODO go-ipfs does not copying files into existing directories if the directory is specify as the target path'\n      },\n      {\n        name: 'removes multiple files',\n        reason: 'TODO go-ipfs does not support removing multiple files'\n      },\n      {\n        name: 'results in the same hash as a sharded directory created by the importer when removing a file',\n        reason: 'TODO go-ipfs errors out with HTTPError: Could not convert value \"85675\" to type \"bool\" (for option \"-size\")'\n      },\n      {\n        name: 'results in the same hash as a sharded directory created by the importer when removing a subshard',\n        reason: 'TODO go-ipfs errors out with HTTPError: Could not convert value \"2109\" to type \"bool\" (for option \"-size\")'\n      },\n      {\n        name: 'results in the same hash as a sharded directory created by the importer when removing a file from a subshard of a subshard',\n        reason: 'TODO go-ipfs errors out with HTTPError: Could not convert value \"170441\" to type \"bool\" (for option \"-size\")'\n      },\n      {\n        name: 'results in the same hash as a sharded directory created by the importer when removing a subshard of a subshard',\n        reason: 'TODO go-ipfs errors out with HTTPError: Could not convert value \"11463\" to type \"bool\" (for option \"-size\")'\n      },\n      {\n        name: 'results in the same hash as a sharded directory created by the importer when adding a new file',\n        reason: 'TODO go-ipfs errors out with HTTPError: Could not convert value \"5835\" to type \"bool\" (for option \"-size\")'\n      },\n      {\n        name: 'results in the same hash as a sharded directory created by the importer when creating a new subshard',\n        reason: 'TODO go-ipfs errors out with HTTPError: Could not convert value \"8038\" to type \"bool\" (for option \"-size\")'\n      },\n      {\n        name: ' results in the same hash as a sharded directory created by the importer when adding a file to a subshard',\n        reason: 'TODO go-ipfs errors out with HTTPError: Could not convert value \"6620\" to type \"bool\" (for option \"-size\")'\n      },\n      {\n        name: 'results in the same hash as a sharded directory created by the importer when adding a file to a subshard',\n        reason: 'HTTPError: Could not convert value \"6620\" to type \"bool\" (for option \"-size\")'\n      },\n      {\n        name: 'results in the same hash as a sharded directory created by the importer when adding a file to a subshard of a subshard',\n        reason: 'HTTPError: Could not convert value \"170441\" to type \"bool\" (for option \"-size\")'\n      },\n      {\n        name: 'stats a dag-cbor node',\n        reason: 'TODO go-ipfs does not support non-dag-pb nodes in mfs'\n      },\n      {\n        name: 'stats an identity CID',\n        reason: 'TODO go-ipfs does not support non-dag-pb nodes in mfs'\n      },\n      {\n        name: 'limits how many bytes to write to a file (Really large file)',\n        reason: 'TODO go-ipfs drops the connection'\n      }\n    ]\n      .concat(isFirefox\n        ? [{\n            name: 'overwrites start of a file without truncating (Really large file)',\n            reason: 'https://github.com/microsoft/playwright/issues/4704#issuecomment-826782602'\n          }, {\n            name: 'limits how many bytes to write to a file (Really large file)',\n            reason: 'https://github.com/microsoft/playwright/issues/4704#issuecomment-826782602'\n          }, {\n            name: 'pads the start of a new file when an offset is specified (Really large file)',\n            reason: 'https://github.com/microsoft/playwright/issues/4704#issuecomment-826782602'\n          }, {\n            name: 'expands a file when an offset is specified (Really large file)',\n            reason: 'https://github.com/microsoft/playwright/issues/4704#issuecomment-826782602'\n          }, {\n            name: 'expands a file when an offset is specified and the offset is longer than the file (Really large file)',\n            reason: 'https://github.com/microsoft/playwright/issues/4704#issuecomment-826782602'\n          }, {\n            name: 'truncates a file after writing (Really large file)',\n            reason: 'https://github.com/microsoft/playwright/issues/4704#issuecomment-826782602'\n          }, {\n            name: 'writes a file with raw blocks for newly created leaf nodes (Really large file)',\n            reason: 'https://github.com/microsoft/playwright/issues/4704#issuecomment-826782602'\n          }]\n        : [])\n  })\n\n  tests.key(commonFactory, {\n    skip: [\n      // key.export\n      {\n        name: 'export',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      // key.import\n      {\n        name: 'import',\n        reason: 'TODO not implemented in go-ipfs yet'\n      }\n    ]\n  })\n\n  tests.miscellaneous(commonFactory, {\n    skip: [\n      {\n        name: 'should include the interface-ipfs-core version',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should include the ipfs-http-client version',\n        reason: 'TODO not implemented in go-ipfs yet'\n      },\n      {\n        name: 'should have protocols property',\n        reason: 'TODO not implemented in go-ipfs yet'\n      }\n    ]\n  })\n\n  tests.name(factory({\n    type: 'go',\n    ipfsOptions: {\n      offline: true\n    }\n  }))\n\n  tests.namePubsub(factory({\n    type: 'go',\n    ipfsOptions: {\n      EXPERIMENTAL: {\n        ipnsPubsub: true\n      }\n    }\n  }), {\n    skip: [\n      // name.pubsub.cancel\n      {\n        name: 'should cancel a subscription correctly returning true',\n        reason: 'go-ipfs is really slow for publishing and resolving ipns records, unless in offline mode'\n      },\n      // name.pubsub.subs\n      {\n        name: 'should get the list of subscriptions updated after a resolve',\n        reason: 'go-ipfs is really slow for publishing and resolving ipns records, unless in offline mode'\n      },\n      // name.pubsub\n      {\n        name: 'should publish and then resolve correctly',\n        reason: 'js-ipfs and go-ipfs behaviour differs'\n      },\n      {\n        name: 'should self resolve, publish and then resolve correctly',\n        reason: 'js-ipfs and go-ipfs behaviour differs'\n      },\n      {\n        name: 'should handle event on publish correctly',\n        reason: 'js-ipfs and go-ipfs behaviour differs'\n      }\n    ]\n  })\n\n  tests.object(commonFactory, {\n    skip: [\n      {\n        name: 'should get data by base58 encoded multihash string',\n        reason: 'FIXME go-ipfs throws invalid encoding: base58'\n      },\n      {\n        name: 'should get object by base58 encoded multihash',\n        reason: 'FIXME go-ipfs throws invalid encoding: base58'\n      },\n      {\n        name: 'should get object by base58 encoded multihash',\n        reason: 'FIXME go-ipfs throws invalid encoding: base58'\n      },\n      {\n        name: 'should get object by base58 encoded multihash string',\n        reason: 'FIXME go-ipfs throws invalid encoding: base58'\n      },\n      {\n        name: 'should get links by base58 encoded multihash',\n        reason: 'FIXME go-ipfs throws invalid encoding: base58'\n      },\n      {\n        name: 'should get links by base58 encoded multihash string',\n        reason: 'FIXME go-ipfs throws invalid encoding: base58'\n      },\n      {\n        name: 'should put a Protobuf encoded Uint8Array',\n        reason: 'FIXME go-ipfs throws invalid encoding: protobuf'\n      }\n    ]\n      .concat(isFirefox\n        ? [{\n            name: 'should supply unaltered data',\n            reason: 'https://github.com/microsoft/playwright/issues/4704#issuecomment-826782602'\n          }]\n        : [])\n  })\n\n  tests.pin(commonFactory, {\n    skip: [\n      {\n        name: 'should list pins with metadata',\n        reason: 'not implemented in go-ipfs'\n      }\n    ]\n  })\n\n  tests.ping(commonFactory, {\n    skip: [\n      {\n        name: 'should fail when pinging a peer that is not available',\n        reason: 'FIXME go-ipfs return success with text: Looking up peer <cid>'\n      }\n    ]\n  })\n\n  tests.pubsub(factory({\n    type: 'go'\n  }, {\n    go: {\n      args: ['--enable-pubsub-experiment']\n    }\n  }), {\n    skip: [{\n      name: 'should receive messages from a different node on lots of topics',\n      reason: 'HTTP clients cannot hold this many connections open'\n    }].concat(\n      isWindows\n        ? [{\n            name: 'should send/receive 100 messages',\n            reason: 'FIXME https://github.com/ipfs/interface-ipfs-core/pull/188#issuecomment-354673246 and https://github.com/ipfs/go-ipfs/issues/4778'\n          },\n          {\n            name: 'should receive multiple messages',\n            reason: 'FIXME https://github.com/ipfs/interface-ipfs-core/pull/188#issuecomment-354673246 and https://github.com/ipfs/go-ipfs/issues/4778'\n          }]\n        : []\n    )\n  })\n\n  tests.repo(commonFactory)\n\n  tests.stats(commonFactory)\n\n  tests.swarm(commonFactory)\n})\n"
  },
  {
    "path": "packages/ipfs/test/interface-http-js.js",
    "content": "/* eslint-env mocha */\n\nimport * as tests from 'interface-ipfs-core'\nimport { isNode, isBrowser, isWebWorker } from 'ipfs-utils/src/env.js'\nimport { factory } from './utils/factory.js'\nconst isFirefox = globalThis.navigator?.userAgent?.toLowerCase().includes('firefox')\n\n/** @typedef { import(\"ipfsd-ctl\").ControllerOptions } ControllerOptions */\n\ndescribe('interface-ipfs-core over ipfs-http-client tests against js-ipfs', function () {\n  this.timeout(20000)\n\n  const commonFactory = factory({\n    type: 'js'\n  })\n\n  tests.root(commonFactory, {\n    skip: [\n      {\n        name: 'should support bidirectional streaming',\n        reason: 'Not supported by http'\n      },\n      {\n        name: 'should error during add-all stream',\n        reason: 'Not supported by http'\n      }]\n      .concat(isNode\n        ? [{\n            name: 'should fail when passed invalid input',\n            reason: 'node-fetch cannot detect errors in streaming bodies - https://github.com/node-fetch/node-fetch/issues/753'\n          }, {\n            name: 'should not add from an invalid url',\n            reason: 'node-fetch cannot detect errors in streaming bodies - https://github.com/node-fetch/node-fetch/issues/753'\n          }]\n        : [{\n            name: 'should add with mtime as hrtime',\n            reason: 'Not designed to run in the browser'\n          }])\n      .concat(isFirefox\n        ? [{\n            name: 'should add a BIG Uint8Array',\n            reason: 'https://github.com/microsoft/playwright/issues/4704#issuecomment-826782602'\n          }, {\n            name: 'should add a BIG Uint8Array with progress enabled',\n            reason: 'https://github.com/microsoft/playwright/issues/4704#issuecomment-826782602'\n          }, {\n            name: 'should add big files',\n            reason: 'https://github.com/microsoft/playwright/issues/4704#issuecomment-826782602'\n          }]\n        : [])\n  })\n\n  tests.bitswap(commonFactory)\n\n  tests.block(commonFactory)\n\n  tests.bootstrap(commonFactory)\n\n  tests.config(commonFactory)\n\n  tests.dag(commonFactory, {\n    skip: [{\n      name: 'should get only a CID, due to resolving locally only',\n      reason: 'Local resolve option is not implemented yet'\n    }]\n  })\n\n  tests.dht(commonFactory)\n\n  tests.files(commonFactory, {\n    skip: (isBrowser || isWebWorker\n      ? [{\n          name: 'should make directory and specify mtime as hrtime',\n          reason: 'Not designed to run in the browser'\n        }, {\n          name: 'should write file and specify mtime as hrtime',\n          reason: 'Not designed to run in the browser'\n        }, {\n          name: 'should set mtime as hrtime',\n          reason: 'Not designed to run in the browser'\n        }]\n      : [])\n      .concat(isFirefox\n        ? [{\n            name: 'overwrites start of a file without truncating (Really large file)',\n            reason: 'https://github.com/microsoft/playwright/issues/4704#issuecomment-826782602'\n          }, {\n            name: 'limits how many bytes to write to a file (Really large file)',\n            reason: 'https://github.com/microsoft/playwright/issues/4704#issuecomment-826782602'\n          }, {\n            name: 'pads the start of a new file when an offset is specified (Really large file)',\n            reason: 'https://github.com/microsoft/playwright/issues/4704#issuecomment-826782602'\n          }, {\n            name: 'expands a file when an offset is specified (Really large file)',\n            reason: 'https://github.com/microsoft/playwright/issues/4704#issuecomment-826782602'\n          }, {\n            name: 'expands a file when an offset is specified and the offset is longer than the file (Really large file)',\n            reason: 'https://github.com/microsoft/playwright/issues/4704#issuecomment-826782602'\n          }, {\n            name: 'truncates a file after writing (Really large file)',\n            reason: 'https://github.com/microsoft/playwright/issues/4704#issuecomment-826782602'\n          }, {\n            name: 'writes a file with raw blocks for newly created leaf nodes (Really large file)',\n            reason: 'https://github.com/microsoft/playwright/issues/4704#issuecomment-826782602'\n          }]\n        : [])\n  })\n\n  tests.key(commonFactory)\n\n  tests.miscellaneous(commonFactory)\n\n  tests.name(factory({\n    type: 'js',\n    ipfsOptions: {\n      offline: true\n    }\n  }))\n\n  tests.namePubsub(factory({\n    type: 'js',\n    ipfsBin: './src/cli.js',\n    ipfsOptions: {\n      EXPERIMENTAL: {\n        ipnsPubsub: true\n      }\n    }\n  }))\n\n  tests.object(commonFactory, {\n    skip: isFirefox\n      ? [{\n          name: 'should supply unaltered data',\n          reason: 'https://github.com/microsoft/playwright/issues/4704#issuecomment-826782602'\n        }]\n      : []\n  })\n\n  tests.pin(commonFactory, {\n    skip: [{\n      name: 'should throw an error on missing direct pins for existing path',\n      reason: 'FIXME: fetch does not yet support HTTP trailers https://github.com/ipfs/js-ipfs/issues/2519'\n    }, {\n      name: 'should throw an error on missing link for a specific path',\n      reason: 'FIXME: fetch does not yet support HTTP trailers https://github.com/ipfs/js-ipfs/issues/2519'\n    }, {\n      name: '.pin.remote.service',\n      reason: 'Not implemented'\n    }, {\n      name: '.pin.remote.add',\n      reason: 'Not implemented'\n    }, {\n      name: '.pin.remote.ls',\n      reason: 'Not implemented'\n    }, {\n      name: '.pin.remote.rm',\n      reason: 'Not implemented'\n    }, {\n      name: '.pin.remote.rmAll',\n      reason: 'Not implemented'\n    }]\n  })\n\n  tests.ping(commonFactory, {\n    skip: [{\n      name: 'should fail when pinging a peer that is not available',\n      reason: 'FIXME: fetch does not yet support HTTP trailers https://github.com/ipfs/js-ipfs/issues/2519'\n    }]\n  })\n\n  tests.pubsub(factory({\n    type: 'js'\n  }, {\n    go: {\n      args: ['--enable-pubsub-experiment']\n    }\n  }), {\n    skip: [{\n      name: 'should receive messages from a different node on lots of topics',\n      reason: 'HTTP clients cannot hold this many connections open'\n    }]\n  })\n\n  tests.repo(commonFactory)\n\n  tests.stats(commonFactory)\n\n  tests.swarm(commonFactory)\n})\n"
  },
  {
    "path": "packages/ipfs/test/utils/factory.js",
    "content": "import { createFactory } from 'ipfsd-ctl'\nimport mergeOpts from 'merge-options'\nimport { isNode, isBrowser } from 'ipfs-utils/src/env.js'\nimport * as ipfsHttpModule from 'ipfs-http-client'\nimport * as ipfsModule from 'ipfs-core'\n// @ts-expect-error no types\nimport goIpfs from 'go-ipfs'\nimport path, { dirname } from 'path'\nimport { fileURLToPath } from 'url'\nimport { webSockets } from '@libp2p/websockets'\nimport { all as WebSocketsFiltersAll } from '@libp2p/websockets/filters'\n\nconst merge = mergeOpts.bind({ ignoreUndefined: true })\nlet __dirname = ''\n\nif (isNode) {\n  __dirname = dirname(fileURLToPath(import.meta.url))\n}\n\nconst commonOptions = {\n  test: true,\n  type: 'proc',\n  ipfsHttpModule,\n  ipfsModule,\n  ipfsOptions: {\n    pass: 'ipfs-is-awesome-software',\n    libp2p: {\n      dialer: {\n        dialTimeout: 60e3 // increase timeout because travis is slow\n      },\n      transports: [\n        webSockets({\n          filter: WebSocketsFiltersAll\n        })\n      ]\n    }\n  },\n  endpoint: process.env.IPFSD_SERVER\n}\n\nconst commonOverrides = {\n  js: {\n    ...(isNode\n      ? {\n          ipfsBin: path.resolve(path.join(__dirname, '../../src/cli.js'))\n        }\n      : {}),\n    ...(isBrowser\n      ? {\n          remote: true\n        }\n      : {})\n  },\n  proc: {\n    ...(isBrowser\n      ? {\n          ipfsOptions: {\n            config: {\n              Addresses: {\n                Swarm: [\n                  process.env.SIGNALA_SERVER\n                ]\n              }\n            }\n          }\n        }\n      : {})\n  },\n  go: {\n    ipfsBin: isNode ? goIpfs.path() : undefined\n  }\n}\n\nexport const factory = (options = {}, overrides = {}) => {\n  return createFactory(\n    merge(commonOptions, options),\n    merge(commonOverrides, overrides)\n  )\n}\n"
  },
  {
    "path": "packages/ipfs/test/utils/mock-pinning-service.js",
    "content": "\nimport http from 'http'\n// @ts-expect-error no types\nimport { setup } from 'mock-ipfs-pinning-service'\nimport getPort from 'aegir/get-port'\n\nconst defaultPort = 1139\nconst defaultToken = 'secret'\n\nexport class PinningService {\n  /**\n   * @param {object} options\n   * @param {number} [options.port]\n   * @param {string|null} [options.token]\n   * @returns {Promise<PinningService>}\n   */\n  static async start ({ port = defaultPort, token = defaultToken } = {}) {\n    const service = await setup({ token })\n    const server = http.createServer(service)\n    const host = '127.0.0.1'\n    port = await getPort(port)\n    await new Promise(resolve => server.listen(port, host, () => {\n      resolve(null)\n    }))\n\n    return new PinningService({ server, host, port, token })\n  }\n\n  /**\n   * @param {PinningService} service\n   * @returns {Promise<void>}\n   */\n  static stop (service) {\n    return new Promise((resolve, reject) => {\n      service.server.close((/** @type {any} */ error) => {\n        if (error) {\n          reject(error)\n        } else {\n          resolve()\n        }\n      })\n    })\n  }\n\n  /**\n   * @param {object} config\n   * @param {any} config.server\n   * @param {string} config.host\n   * @param {number} config.port\n   * @param {any} config.token\n   */\n  constructor ({ server, host, port, token }) {\n    this.server = server\n    this.host = host\n    this.port = port\n    this.token = token\n  }\n\n  get endpoint () {\n    return `http://${this.host}:${this.port}`\n  }\n}\n"
  },
  {
    "path": "packages/ipfs/test/utils/mock-preload-node.js",
    "content": "/* eslint-env browser */\n\nimport http from 'http'\nimport { URL } from 'iso-url'\nimport getPort from 'aegir/get-port'\n\nexport const defaultPort = 1138\nexport const defaultAddr = `/dnsaddr/localhost/tcp/${defaultPort}`\n\n// Create a mock preload IPFS node with a gateway that'll respond 200 to a\n// request for /api/v0/refs?arg=*. It remembers the preload CIDs it has been\n// called with, and you can ask it for them and also clear them by issuing a\n// GET/DELETE request to /cids.\nexport function createNode () {\n  /** @type {string[]} */\n  let cids = []\n\n  const server = http.createServer((req, res) => {\n    res.setHeader('Access-Control-Allow-Origin', '*')\n    res.setHeader('Access-Control-Request-Method', '*')\n    res.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET, DELETE')\n    res.setHeader('Access-Control-Allow-Headers', '*')\n\n    if (req.method === 'OPTIONS') {\n      res.writeHead(200)\n      res.end()\n      return\n    }\n\n    if (`${req.url}`.startsWith('/api/v0/refs')) {\n      const arg = new URL(`https://ipfs.io${req.url}`).searchParams.get('arg')\n\n      if (arg) {\n        cids.push(arg)\n      }\n    } else if (req.method === 'DELETE' && req.url === '/cids') {\n      res.statusCode = 204\n      cids = []\n    } else if (req.method === 'GET' && req.url === '/cids') {\n      res.setHeader('Content-Type', 'application/json')\n      res.write(JSON.stringify(cids))\n    } else {\n      res.statusCode = 500\n    }\n\n    res.end()\n  })\n\n  // @ts-expect-error\n  server.start = async () => {\n    const port = await getPort(defaultPort)\n    return new Promise((resolve, reject) => {\n      server.listen(port, '127.0.0.1', (/** @type {any} */ err) => {\n        if (err) {\n          return reject(err)\n        }\n        resolve(null)\n      })\n    })\n  }\n  // @ts-expect-error\n  server.stop = () => new Promise(resolve => server.close(resolve))\n\n  return server\n}\n"
  },
  {
    "path": "packages/ipfs/tsconfig.json",
    "content": "{\n  \"extends\": \"aegir/src/config/tsconfig.aegir.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"emitDeclarationOnly\": true\n  },\n  \"include\": [\n    \"src\",\n    \"test\",\n    \"package.json\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../interface-ipfs-core\"\n    },\n    {\n      \"path\": \"../ipfs-cli\"\n    },\n    {\n      \"path\": \"../ipfs-client\"\n    },\n    {\n      \"path\": \"../ipfs-core\"\n    },\n    {\n      \"path\": \"../ipfs-core-types\"\n    },\n    {\n      \"path\": \"../ipfs-http-client\"\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/ipfs-cli/CHANGELOG.md",
    "content": "# Change Log\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n### [0.16.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-cli-v0.16.0...ipfs-cli-v0.16.1) (2023-05-25)\n\n\n### Bug Fixes\n\n* add deprecation notice to readmes ([#4362](https://www.github.com/ipfs/js-ipfs/issues/4362)) ([7b79c1b](https://www.github.com/ipfs/js-ipfs/commit/7b79c1b8df5c818dc124b346ea28330455732d5c))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core bumped from ^0.18.0 to ^0.18.1\n    * ipfs-core-types bumped from ^0.14.0 to ^0.14.1\n    * ipfs-core-utils bumped from ^0.18.0 to ^0.18.1\n    * ipfs-daemon bumped from ^0.16.0 to ^0.16.1\n    * ipfs-http-client bumped from ^60.0.0 to ^60.0.1\n\n## [0.16.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-cli-v0.15.0...ipfs-cli-v0.16.0) (2023-01-11)\n\n\n### ⚠ BREAKING CHANGES\n\n* update multiformats to v11.x.x and related depenendcies (#4277)\n\n### Bug Fixes\n\n* update multiformats to v11.x.x and related depenendcies ([#4277](https://www.github.com/ipfs/js-ipfs/issues/4277)) ([521c84a](https://www.github.com/ipfs/js-ipfs/commit/521c84a958b04d61702577a5adce28519c1b2a3b))\n* use aegir to publish RCs ([#4284](https://www.github.com/ipfs/js-ipfs/issues/4284)) ([6d90cbf](https://www.github.com/ipfs/js-ipfs/commit/6d90cbf321a7dbf4b1084ba20f0c514dc08d8d0a))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core bumped from ^0.17.0 to ^0.18.0\n    * ipfs-core-types bumped from ^0.13.0 to ^0.14.0\n    * ipfs-core-utils bumped from ^0.17.0 to ^0.18.0\n    * ipfs-daemon bumped from ^0.15.0 to ^0.16.0\n    * ipfs-http-client bumped from ^59.0.0 to ^60.0.0\n\n## [0.15.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-cli-v0.14.2...ipfs-cli-v0.15.0) (2022-10-24)\n\n\n### ⚠ BREAKING CHANGES\n\n* ipfs is now bundled with libp2p@0.40.x which has different config\n\n### Features\n\n* upgrade libp2p to 0.40.x ([#4237](https://www.github.com/ipfs/js-ipfs/issues/4237)) ([0cee4a4](https://www.github.com/ipfs/js-ipfs/commit/0cee4a4c55767022584dcbade0b0b9b43326f9c9))\n\n\n### Bug Fixes\n\n* replace slice with subarray for increased performance ([#4210](https://www.github.com/ipfs/js-ipfs/issues/4210)) ([dfc43d4](https://www.github.com/ipfs/js-ipfs/commit/dfc43d4e9be67fdf25553677f469379d966ff806))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core bumped from ^0.16.1 to ^0.17.0\n    * ipfs-core-types bumped from ^0.12.1 to ^0.13.0\n    * ipfs-core-utils bumped from ^0.16.1 to ^0.17.0\n    * ipfs-daemon bumped from ^0.14.2 to ^0.15.0\n    * ipfs-http-client bumped from ^58.0.1 to ^59.0.0\n\n### [0.14.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-cli-v0.14.1...ipfs-cli-v0.14.2) (2022-09-21)\n\n\n### Bug Fixes\n\n* update @multiformats/multiadd to 11.0.0 ([2a830bf](https://www.github.com/ipfs/js-ipfs/commit/2a830bf58a5929fcce51dede871c99f62192fbda))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core bumped from ^0.16.0 to ^0.16.1\n    * ipfs-core-types bumped from ^0.12.0 to ^0.12.1\n    * ipfs-core-utils bumped from ^0.16.0 to ^0.16.1\n    * ipfs-daemon bumped from ^0.14.1 to ^0.14.2\n    * ipfs-http-client bumped from ^58.0.0 to ^58.0.1\n\n### [0.14.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-cli-v0.14.0...ipfs-cli-v0.14.1) (2022-09-16)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-daemon bumped from ^0.14.0 to ^0.14.1\n\n## [0.14.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-cli-v0.13.5...ipfs-cli-v0.14.0) (2022-09-06)\n\n\n### ⚠ BREAKING CHANGES\n\n* update to libp2p@0.38.x (#4151)\n\n### deps\n\n* update to libp2p@0.38.x ([#4151](https://www.github.com/ipfs/js-ipfs/issues/4151)) ([39dbf70](https://www.github.com/ipfs/js-ipfs/commit/39dbf708ec31b263115e44f420651fa4e056a89e))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core bumped from ^0.15.0 to ^0.16.0\n    * ipfs-core-types bumped from ^0.11.0 to ^0.12.0\n    * ipfs-core-utils bumped from ^0.15.0 to ^0.16.0\n    * ipfs-daemon bumped from ^0.13.0 to ^0.14.0\n    * ipfs-http-client bumped from ^57.0.0 to ^58.0.0\n\n### [0.13.5](https://www.github.com/ipfs/js-ipfs/compare/ipfs-cli-v0.13.4...ipfs-cli-v0.13.5) (2022-06-24)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core bumped from ^0.15.3 to ^0.15.4\n    * ipfs-daemon bumped from ^0.13.4 to ^0.13.5\n    * ipfs-http-client bumped from ^57.0.2 to ^57.0.3\n\n### [0.13.4](https://www.github.com/ipfs/js-ipfs/compare/ipfs-cli-v0.13.3...ipfs-cli-v0.13.4) (2022-06-22)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core bumped from ^0.15.2 to ^0.15.3\n    * ipfs-core-types bumped from ^0.11.0 to ^0.11.1\n    * ipfs-core-utils bumped from ^0.15.0 to ^0.15.1\n    * ipfs-daemon bumped from ^0.13.3 to ^0.13.4\n    * ipfs-http-client bumped from ^57.0.1 to ^57.0.2\n\n### [0.13.3](https://www.github.com/ipfs/js-ipfs/compare/ipfs-cli-v0.13.2...ipfs-cli-v0.13.3) (2022-06-13)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core bumped from ^0.15.1 to ^0.15.2\n    * ipfs-daemon bumped from ^0.13.2 to ^0.13.3\n\n### [0.13.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-cli-v0.13.1...ipfs-cli-v0.13.2) (2022-06-01)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core bumped from ^0.15.0 to ^0.15.1\n    * ipfs-daemon bumped from ^0.13.1 to ^0.13.2\n    * ipfs-http-client bumped from ^57.0.0 to ^57.0.1\n\n### [0.13.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-cli-v0.13.0...ipfs-cli-v0.13.1) (2022-05-30)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-daemon bumped from ^0.13.0 to ^0.13.1\n\n## [0.13.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-cli-v0.12.3...ipfs-cli-v0.13.0) (2022-05-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* This module is now ESM only and there return types of some methods have changed\n\n### Features\n\n* update to libp2p 0.37.x ([#4092](https://www.github.com/ipfs/js-ipfs/issues/4092)) ([74aee8b](https://www.github.com/ipfs/js-ipfs/commit/74aee8b3d78f233c3199a3e9a6c0ac628a31a433))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core bumped from ^0.14.3 to ^0.15.0\n    * ipfs-core-types bumped from ^0.10.3 to ^0.11.0\n    * ipfs-core-utils bumped from ^0.14.3 to ^0.15.0\n    * ipfs-daemon bumped from ^0.12.2 to ^0.13.0\n    * ipfs-http-client bumped from ^56.0.3 to ^57.0.0\n\n### [0.12.3](https://www.github.com/ipfs/js-ipfs/compare/ipfs-cli-v0.12.2...ipfs-cli-v0.12.3) (2022-04-20)\n\n\n### Bug Fixes\n\n* upgrade dep of ipfs-utils ^9.0.2->^9.0.6 ([#4086](https://www.github.com/ipfs/js-ipfs/issues/4086)) ([8f7ce23](https://www.github.com/ipfs/js-ipfs/commit/8f7ce23c18be12bdc52b98bfccbd0a5a2a9c9f7e)), closes [#4080](https://www.github.com/ipfs/js-ipfs/issues/4080)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core bumped from ^0.14.2 to ^0.14.3\n    * ipfs-core-types bumped from ^0.10.2 to ^0.10.3\n    * ipfs-core-utils bumped from ^0.14.2 to ^0.14.3\n    * ipfs-daemon bumped from ^0.12.2 to ^0.12.3\n    * ipfs-http-client bumped from ^56.0.2 to ^56.0.3\n\n### [0.12.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-cli-v0.12.1...ipfs-cli-v0.12.2) (2022-03-01)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core bumped from ^0.14.1 to ^0.14.2\n    * ipfs-core-types bumped from ^0.10.1 to ^0.10.2\n    * ipfs-core-utils bumped from ^0.14.1 to ^0.14.2\n    * ipfs-daemon bumped from ^0.12.1 to ^0.12.2\n    * ipfs-http-client bumped from ^56.0.1 to ^56.0.2\n\n### [0.12.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-cli-v0.12.0...ipfs-cli-v0.12.1) (2022-02-06)\n\n\n### Bug Fixes\n\n* **dag:** replace custom dag walk with multiformats/traversal ([#3950](https://www.github.com/ipfs/js-ipfs/issues/3950)) ([596b1f4](https://www.github.com/ipfs/js-ipfs/commit/596b1f48a014083b1736e4ad7e746c652d2583b1))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core bumped from ^0.14.0 to ^0.14.1\n    * ipfs-core-types bumped from ^0.10.0 to ^0.10.1\n    * ipfs-core-utils bumped from ^0.14.0 to ^0.14.1\n    * ipfs-daemon bumped from ^0.12.0 to ^0.12.1\n    * ipfs-http-client bumped from ^56.0.0 to ^56.0.1\n\n## [0.12.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-cli-v0.11.0...ipfs-cli-v0.12.0) (2022-01-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* peerstore methods are now all async, the repo is migrated to v12\n\n### Features\n\n* libp2p async peerstore ([#4018](https://www.github.com/ipfs/js-ipfs/issues/4018)) ([a6b201a](https://www.github.com/ipfs/js-ipfs/commit/a6b201af2c3697430ab0ebe002dd573d185f1ac0))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core bumped from ^0.13.0 to ^0.14.0\n    * ipfs-core-types bumped from ^0.9.0 to ^0.10.0\n    * ipfs-core-utils bumped from ^0.13.0 to ^0.14.0\n    * ipfs-daemon bumped from ^0.11.0 to ^0.12.0\n    * ipfs-http-client bumped from ^55.0.0 to ^56.0.0\n\n\n## [0.11.0](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.10.2...ipfs-cli@0.11.0) (2021-12-15)\n\n\n### Bug Fixes\n\n* ensure directory is passed ([#3968](https://github.com/ipfs/js-ipfs/issues/3968)) ([80ac58c](https://github.com/ipfs/js-ipfs/commit/80ac58ca27cc9f21823a23d1e6357f738fdb6781))\n* **pubsub:** multibase in pubsub http rpc ([#3922](https://github.com/ipfs/js-ipfs/issues/3922)) ([6eeaca4](https://github.com/ipfs/js-ipfs/commit/6eeaca452c36fa13be42d704575c577e4ca938f1))\n\n\n### chore\n\n* Bump @ipld/dag-cbor to v7 ([#3977](https://github.com/ipfs/js-ipfs/issues/3977)) ([73476f5](https://github.com/ipfs/js-ipfs/commit/73476f55e39ecfb01eb2b4880637aad658f51bc2))\n\n\n### Features\n\n* dht client ([#3947](https://github.com/ipfs/js-ipfs/issues/3947)) ([62d8ecb](https://github.com/ipfs/js-ipfs/commit/62d8ecbc723e693a2544e69172d99c576d187c23))\n* update DAG API to match go-ipfs@0.10 changes ([#3917](https://github.com/ipfs/js-ipfs/issues/3917)) ([38c01be](https://github.com/ipfs/js-ipfs/commit/38c01be03b4fd5f401cd9b698cfdb4237d835b01))\n\n\n### BREAKING CHANGES\n\n* **pubsub:** We had to make breaking changes to `pubsub` commands sent over HTTP RPC  to fix data corruption caused by topic names and payload bytes that included `\\n`. More details in https://github.com/ipfs/go-ipfs/issues/7939 and https://github.com/ipfs/go-ipfs/pull/8183\n* On decode of CBOR blocks, `undefined` values will be coerced to `null`\n* `ipfs.dag.put` no longer accepts a `format` arg, it is now `storeCodec` and `inputCodec`.  `'json'` has become `'dag-json'`, `'cbor'` has become `'dag-cbor'` and so on\n* The DHT API has been refactored to return async iterators of query events\n\n\n## [0.10.2](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.10.1...ipfs-cli@0.10.2) (2021-11-24)\n\n**Note:** Version bump only for package ipfs-cli\n\n\n\n\n\n## [0.10.1](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.10.0...ipfs-cli@0.10.1) (2021-11-19)\n\n**Note:** Version bump only for package ipfs-cli\n\n\n\n\n\n## [0.10.0](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.9.1...ipfs-cli@0.10.0) (2021-11-12)\n\n\n### Bug Fixes\n\n* do not accept single items for ipfs.add ([#3900](https://github.com/ipfs/js-ipfs/issues/3900)) ([04e3cf3](https://github.com/ipfs/js-ipfs/commit/04e3cf3f46b585c4644cba70516f375e95361f52))\n\n\n### BREAKING CHANGES\n\n* errors will now be thrown if multiple items are passed to `ipfs.add` or single items to `ipfs.addAll` (n.b. you can still pass a list of a single item to `ipfs.addAll`)\n\n\n\n\n\n### [0.9.1](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.9.0...ipfs-cli@0.9.1) (2021-09-28)\n\n**Note:** Version bump only for package ipfs-cli\n\n\n\n\n\n## [0.9.0](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.8.8...ipfs-cli@0.9.0) (2021-09-24)\n\n\n### Features\n\n* pull in new globSource ([#3889](https://github.com/ipfs/js-ipfs/issues/3889)) ([be4a542](https://github.com/ipfs/js-ipfs/commit/be4a5428ebc4b05a2edd9a91bf9df6416c1a8c2b))\n* switch to esm ([#3879](https://github.com/ipfs/js-ipfs/issues/3879)) ([9a40109](https://github.com/ipfs/js-ipfs/commit/9a40109632e5b4837eb77a2f57dbc77fbf1fe099))\n\n\n### BREAKING CHANGES\n\n* the globSource api has changed from `globSource(dir, opts)` to `globSource(dir, pattern, opts)`\n* There are no default exports and everything is now dual published as ESM/CJS\n\n\n\n\n\n### [0.8.8](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.8.7...ipfs-cli@0.8.8) (2021-09-17)\n\n**Note:** Version bump only for package ipfs-cli\n\n\n\n\n\n### [0.8.7](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.8.6...ipfs-cli@0.8.7) (2021-09-17)\n\n**Note:** Version bump only for package ipfs-cli\n\n\n\n\n\n### [0.8.6](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.8.5...ipfs-cli@0.8.6) (2021-09-08)\n\n**Note:** Version bump only for package ipfs-cli\n\n\n\n\n\n### [0.8.5](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.8.4...ipfs-cli@0.8.5) (2021-09-02)\n\n\n### Bug Fixes\n\n* declare types in .ts files ([#3840](https://github.com/ipfs/js-ipfs/issues/3840)) ([eba5fe6](https://github.com/ipfs/js-ipfs/commit/eba5fe6832858107b3e1ae02c99de674622f12b4))\n* remove use of instanceof for CID class ([#3847](https://github.com/ipfs/js-ipfs/issues/3847)) ([ebbb12d](https://github.com/ipfs/js-ipfs/commit/ebbb12db523c53ce8e4ddae5266cd9acb3504431))\n\n\n\n\n\n### [0.8.4](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.8.3...ipfs-cli@0.8.4) (2021-08-25)\n\n\n### Bug Fixes\n\n* grpc server may not be enabled ([#3834](https://github.com/ipfs/js-ipfs/issues/3834)) ([533845e](https://github.com/ipfs/js-ipfs/commit/533845e3d140459ca383b1538e571d08850c0ef8))\n\n\n\n\n\n### [0.8.3](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.8.1...ipfs-cli@0.8.3) (2021-08-17)\n\n**Note:** Version bump only for package ipfs-cli\n\n\n\n\n\n### [0.8.1](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.8.0...ipfs-cli@0.8.1) (2021-08-17)\n\n\n### Bug Fixes\n\n* pin nanoid version ([#3807](https://github.com/ipfs/js-ipfs/issues/3807)) ([474523a](https://github.com/ipfs/js-ipfs/commit/474523ab8702729f697843d433a7a08baf2d101f))\n\n\n\n\n\n## [0.8.0](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.7.1...ipfs-cli@0.8.0) (2021-08-11)\n\n\n### Features\n\n* ed25519 keys by default ([#3693](https://github.com/ipfs/js-ipfs/issues/3693)) ([33fa734](https://github.com/ipfs/js-ipfs/commit/33fa7341c3baaf0926d887c071cc6fbce5ac49a8))\n* make ipfs.get output tarballs ([#3785](https://github.com/ipfs/js-ipfs/issues/3785)) ([1ad6001](https://github.com/ipfs/js-ipfs/commit/1ad60018d39d5b46c484756631e30e1989fd8eba))\n\n\n### BREAKING CHANGES\n\n* the output type of `ipfs.get` has changed and the `recursive` option has been removed from `ipfs.ls` since it was not supported everywhere\n\n\n\n\n\n### [0.7.1](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.7.0...ipfs-cli@0.7.1) (2021-07-30)\n\n**Note:** Version bump only for package ipfs-cli\n\n\n\n\n\n## [0.7.0](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.6.2...ipfs-cli@0.7.0) (2021-07-27)\n\n\n### Bug Fixes\n\n* make \"ipfs resolve\" cli command recursive by default ([#3707](https://github.com/ipfs/js-ipfs/issues/3707)) ([399ce36](https://github.com/ipfs/js-ipfs/commit/399ce367a1dbc531b52fe228ee4212008c9a1091)), closes [#3692](https://github.com/ipfs/js-ipfs/issues/3692)\n\n\n### Features\n\n* implement dag import/export ([#3728](https://github.com/ipfs/js-ipfs/issues/3728)) ([700765b](https://github.com/ipfs/js-ipfs/commit/700765be2634fa5d2d71d8b87cf68c9cd328d2c4)), closes [#2953](https://github.com/ipfs/js-ipfs/issues/2953) [#2745](https://github.com/ipfs/js-ipfs/issues/2745)\n* upgrade to the new multiformats ([#3556](https://github.com/ipfs/js-ipfs/issues/3556)) ([d13d15f](https://github.com/ipfs/js-ipfs/commit/d13d15f022a87d04a35f0f7822142f9cb898479c))\n\n\n### BREAKING CHANGES\n\n* resolve is now recursive by default\n\nCo-authored-by: Alex Potsides <alex@achingbrain.net>\n* ipld-formats no longer supported, use multiformat BlockCodecs instead\n\nCo-authored-by: Rod Vagg <rod@vagg.org>\nCo-authored-by: achingbrain <alex@achingbrain.net>\n\n\n\n\n\n### [0.6.2](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.6.1...ipfs-cli@0.6.2) (2021-06-18)\n\n**Note:** Version bump only for package ipfs-cli\n\n\n\n\n\n### [0.6.1](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.6.0...ipfs-cli@0.6.1) (2021-06-05)\n\n\n### Bug Fixes\n\n* stalling subscription on (node) http-client when daemon is stopped ([#3468](https://github.com/ipfs/js-ipfs/issues/3468)) ([0266abf](https://github.com/ipfs/js-ipfs/commit/0266abf0c4b817636172f78c6e91eb4dd5aad451)), closes [#3465](https://github.com/ipfs/js-ipfs/issues/3465)\n\n\n\n\n\n## [0.6.0](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.5.1...ipfs-cli@0.6.0) (2021-05-26)\n\n\n### Features\n\n* allow passing the id of a network peer to ipfs.id ([#3386](https://github.com/ipfs/js-ipfs/issues/3386)) ([00fd709](https://github.com/ipfs/js-ipfs/commit/00fd709a7b71e7cf354ea452ebce460dd7375d34))\n\n\n\n\n\n### [0.5.1](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.5.0...ipfs-cli@0.5.1) (2021-05-11)\n\n**Note:** Version bump only for package ipfs-cli\n\n\n\n\n\n## [0.5.0](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.4.4...ipfs-cli@0.5.0) (2021-05-10)\n\n\n### Bug Fixes\n\n* mark ipld options as partial ([#3669](https://github.com/ipfs/js-ipfs/issues/3669)) ([f98af8e](https://github.com/ipfs/js-ipfs/commit/f98af8ed24784929898bb5d33a64dc442c77074d))\n* update ipfs repo ([#3671](https://github.com/ipfs/js-ipfs/issues/3671)) ([9029ee5](https://github.com/ipfs/js-ipfs/commit/9029ee591fa74ea65c9600f2d249897e933416fa))\n* update types after feedback from ceramic ([#3657](https://github.com/ipfs/js-ipfs/issues/3657)) ([0ddbb1b](https://github.com/ipfs/js-ipfs/commit/0ddbb1b1deb4e40dac3e365d7f98a5f174c2ce8f)), closes [#3640](https://github.com/ipfs/js-ipfs/issues/3640)\n\n\n### chore\n\n* upgrade deps with new typedefs ([#3550](https://github.com/ipfs/js-ipfs/issues/3550)) ([a418a52](https://github.com/ipfs/js-ipfs/commit/a418a521574c878d7aabd0ad2fd8d516908a3756))\n\n\n### BREAKING CHANGES\n\n* all core api methods now have types, some method signatures have changed, named exports are now used by the http, grpc and ipfs client modules\n\n\n\n\n\n### [0.4.4](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.4.3...ipfs-cli@0.4.4) (2021-03-10)\n\n**Note:** Version bump only for package ipfs-cli\n\n\n\n\n\n### [0.4.3](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.4.2...ipfs-cli@0.4.3) (2021-03-09)\n\n\n### Bug Fixes\n\n* update to new aegir ([#3528](https://github.com/ipfs/js-ipfs/issues/3528)) ([49f7880](https://github.com/ipfs/js-ipfs/commit/49f78807d7e26483bd926b45cc7e0f797d77e41b))\n\n\n\n\n\n### [0.4.2](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.4.1...ipfs-cli@0.4.2) (2021-02-08)\n\n**Note:** Version bump only for package ipfs-cli\n\n\n\n\n\n### [0.4.1](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.4.0...ipfs-cli@0.4.1) (2021-02-02)\n\n**Note:** Version bump only for package ipfs-cli\n\n\n\n\n\n## [0.4.0](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.3.2...ipfs-cli@0.4.0) (2021-02-01)\n\n\n### Bug Fixes\n\n* updates webpack example to use v5 ([#3512](https://github.com/ipfs/js-ipfs/issues/3512)) ([c7110db](https://github.com/ipfs/js-ipfs/commit/c7110db71b5c0f0f9f415f31f91b5b228341e13e)), closes [#3511](https://github.com/ipfs/js-ipfs/issues/3511)\n\n\n### chore\n\n* update deps ([#3514](https://github.com/ipfs/js-ipfs/issues/3514)) ([061d77c](https://github.com/ipfs/js-ipfs/commit/061d77cc03f40af5a3bc3590481e1e5836e7f0d8))\n\n\n### BREAKING CHANGES\n\n* ipfs-repo upgrade requires repo migration to v10\n\n\n\n\n\n### [0.3.2](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.3.1...ipfs-cli@0.3.2) (2021-01-22)\n\n**Note:** Version bump only for package ipfs-cli\n\n\n\n\n\n### [0.3.1](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.3.0...ipfs-cli@0.3.1) (2021-01-20)\n\n**Note:** Version bump only for package ipfs-cli\n\n\n\n\n\n## [0.3.0](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.2.3...ipfs-cli@0.3.0) (2021-01-15)\n\n\n### Features\n\n* add grpc server and client ([#3403](https://github.com/ipfs/js-ipfs/issues/3403)) ([a9027e0](https://github.com/ipfs/js-ipfs/commit/a9027e0ec0cea9a4f34b4f2f52e09abb35237384)), closes [#2519](https://github.com/ipfs/js-ipfs/issues/2519) [#2838](https://github.com/ipfs/js-ipfs/issues/2838) [#2943](https://github.com/ipfs/js-ipfs/issues/2943) [#2854](https://github.com/ipfs/js-ipfs/issues/2854) [#2864](https://github.com/ipfs/js-ipfs/issues/2864)\n* allow passing a http.Agent to the grpc client ([#3477](https://github.com/ipfs/js-ipfs/issues/3477)) ([c5f0bc5](https://github.com/ipfs/js-ipfs/commit/c5f0bc5eeee15369b7d02901035b04184a8608d2)), closes [#3474](https://github.com/ipfs/js-ipfs/issues/3474)\n\n\n\n\n\n### [0.2.3](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.2.2...ipfs-cli@0.2.3) (2020-12-16)\n\n\n### Bug Fixes\n\n* regressions introduced by new releases of CID & multicodec ([#3442](https://github.com/ipfs/js-ipfs/issues/3442)) ([b5152d8](https://github.com/ipfs/js-ipfs/commit/b5152d8cc93ecc8d39fc353ea66d7eaf1661e3c0)), closes [/github.com/multiformats/js-cid/commit/0e11f035c9230e7f6d79c159ace9b80de88cb5eb#diff-25a6634263c1b1f6fc4697a04e2b9904ea4b042a89af59dc93ec1f5d44848a26](https://github.com//github.com/multiformats/js-cid/commit/0e11f035c9230e7f6d79c159ace9b80de88cb5eb/issues/diff-25a6634263c1b1f6fc4697a04e2b9904ea4b042a89af59dc93ec1f5d44848a26)\n\n\n\n\n\n### [0.2.2](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.2.1...ipfs-cli@0.2.2) (2020-11-25)\n\n\n### Bug Fixes\n\n* do not write to prefix outside of output directory ([#3417](https://github.com/ipfs/js-ipfs/issues/3417)) ([75dd865](https://github.com/ipfs/js-ipfs/commit/75dd86529650b039be21b05b92a6413269baa4ab))\n* strip control characters from user output ([#3420](https://github.com/ipfs/js-ipfs/issues/3420)) ([d13b064](https://github.com/ipfs/js-ipfs/commit/d13b064882751b00c48d42aeb309131fde0dd5c8))\n\n\n\n\n\n### [0.2.1](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.2.0...ipfs-cli@0.2.1) (2020-11-16)\n\n\n### Bug Fixes\n\n* correct raw leaves setting ([#3401](https://github.com/ipfs/js-ipfs/issues/3401)) ([c0703ef](https://github.com/ipfs/js-ipfs/commit/c0703ef78626a91186e0c7c3374584283367c064))\n* report ipfs.add progress over http ([#3310](https://github.com/ipfs/js-ipfs/issues/3310)) ([39cad4b](https://github.com/ipfs/js-ipfs/commit/39cad4b76b950ea6a76477fd01f8631b8bd9aa1e))\n\n\n\n\n\n## [0.2.0](https://github.com/ipfs/js-ipfs/compare/ipfs-cli@0.1.0...ipfs-cli@0.2.0) (2020-11-09)\n\n\n### Bug Fixes\n\n* remove electron-webrtc dependency ([#3378](https://github.com/ipfs/js-ipfs/issues/3378)) ([2bd5368](https://github.com/ipfs/js-ipfs/commit/2bd53686003527a102db9df92cedad4c6d9164f9)), closes [#3376](https://github.com/ipfs/js-ipfs/issues/3376)\n\n\n### BREAKING CHANGES\n\n* electron-webrtc was accidentally bundled with ipfs, now it needs installing separately\n\n\n\n\n\n# 0.1.0 (2020-10-28)\n\n\n### Bug Fixes\n\n* error invalid version triggered in cli pin add/rm ([#3306](https://github.com/ipfs/js-ipfs/issues/3306)) ([69757f3](https://github.com/ipfs/js-ipfs/commit/69757f3c321c5d135ebde7a262c169427e4f1105)), closes [/github.com/ipfs/js-ipfs/blob/master/docs/core-api/PIN.md#returns-1](https://github.com//github.com/ipfs/js-ipfs/blob/master/docs/core-api/PIN.md/issues/returns-1)\n* use fetch in electron renderer and electron-fetch in main ([#3251](https://github.com/ipfs/js-ipfs/issues/3251)) ([639d71f](https://github.com/ipfs/js-ipfs/commit/639d71f7ac8f66d9633e753a2a6be927e14a5af0))\n\n\n### Features\n\n* enable custom formats for dag put and get ([#3347](https://github.com/ipfs/js-ipfs/issues/3347)) ([3250ff4](https://github.com/ipfs/js-ipfs/commit/3250ff453a1d3275cc4ab746f59f9f70abd5cc5f))\n* type check & generate defs from jsdoc ([#3281](https://github.com/ipfs/js-ipfs/issues/3281)) ([bbcaf34](https://github.com/ipfs/js-ipfs/commit/bbcaf34111251b142273a5675f4754ff68bd9fa0))"
  },
  {
    "path": "packages/ipfs-cli/CODE_OF_CONDUCT.md",
    "content": "# Contributor Code of Conduct\n\nThe `js-ipfs` project follows the [`IPFS Community Code of Conduct`](https://github.com/ipfs/community/blob/master/code-of-conduct.md)\n"
  },
  {
    "path": "packages/ipfs-cli/CONTRIBUTING.md",
    "content": "# Contributing guidelines\n\nIPFS as a project, including js-ipfs and all of its modules, follows the [standard IPFS Community contributing guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md).\n\nWe also adhere to the [IPFS JavaScript Community contributing guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING_JS.md) which provide additional information of how to collaborate and contribute in the JavaScript implementation of IPFS.\n\nWe appreciate your time and attention for going over these. Please open an issue on [ipfs/community](https://github.com/ipfs/community) if you have any question.\n\nThank you.\n"
  },
  {
    "path": "packages/ipfs-cli/COPYRIGHT",
    "content": "This project is transitioning from an MIT-only license to a dual MIT/Apache-2.0 license.\nUnless otherwise noted, all code contributed prior to 2019-11-21 and not contributed by\na user listed in [this signoff issue](https://github.com/ipfs/js-ipfs/issues/2624) is\nlicensed under MIT-only. All new contributions (and past contributions since 2019-11-21)\nare licensed under a dual MIT/Apache-2.0 license.\n"
  },
  {
    "path": "packages/ipfs-cli/LICENSE",
    "content": "This project is dual licensed under MIT and Apache-2.0.\n\nMIT: https://www.opensource.org/licenses/mit\nApache-2.0: https://www.apache.org/licenses/license-2.0\n"
  },
  {
    "path": "packages/ipfs-cli/LICENSE-APACHE",
    "content": "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\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless 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.\n"
  },
  {
    "path": "packages/ipfs-cli/LICENSE-MIT",
    "content": "The MIT License (MIT)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "packages/ipfs-cli/README.md",
    "content": "> # ⛔️ DEPRECATED: [js-IPFS](https://github.com/ipfs/js-ipfs) has been superseded by [Helia](https://github.com/ipfs/helia)\n>\n> 📚 [Learn more about this deprecation](https://github.com/ipfs/js-ipfs/issues/4336) or [how to migrate](https://github.com/ipfs/helia/wiki/Migrating-from-js-IPFS)\n>\n> ⚠️ If you continue using this repo, please note that security fixes will not be provided\n\n# ipfs-cli <!-- omit in toc -->\n\n[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech)\n[![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech)\n[![codecov](https://img.shields.io/codecov/c/github/ipfs/js-ipfs.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipfs)\n[![CI](https://img.shields.io/github/actions/workflow/status/ipfs/js-ipfs/test.yml?branch=master\\&style=flat-square)](https://github.com/ipfs/js-ipfs/actions/workflows/test.yml?query=branch%3Amaster)\n\n> JavaScript implementation of the IPFS specification\n\n## Table of contents <!-- omit in toc -->\n\n- [Install](#install)\n- [License](#license)\n- [Contribute](#contribute)\n\n## Install\n\n```console\n$ npm i ipfs-cli\n```\n\n```console\n$ npm install -g ipfs\n// npm install output\n$ jsipfs daemon\n```\n\n## License\n\nLicensed under either of\n\n- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / <http://www.apache.org/licenses/LICENSE-2.0>)\n- MIT ([LICENSE-MIT](LICENSE-MIT) / <http://opensource.org/licenses/MIT>)\n\n## Contribute\n\nContributions welcome! Please check out [the issues](https://github.com/ipfs/js-ipfs/issues).\n\nAlso 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.\n\nPlease 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).\n\nUnless 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.\n\n[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md)\n"
  },
  {
    "path": "packages/ipfs-cli/package.json",
    "content": "{\n  \"name\": \"ipfs-cli\",\n  \"version\": \"0.16.1\",\n  \"description\": \"JavaScript implementation of the IPFS specification\",\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"homepage\": \"https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-cli#readme\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ipfs/js-ipfs.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ipfs/js-ipfs/issues\"\n  },\n  \"keywords\": [\n    \"IPFS\"\n  ],\n  \"engines\": {\n    \"node\": \">=16.0.0\",\n    \"npm\": \">=7.0.0\"\n  },\n  \"type\": \"module\",\n  \"types\": \"./dist/src/index.d.ts\",\n  \"typesVersions\": {\n    \"*\": {\n      \"*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ],\n      \"src/*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ]\n    }\n  },\n  \"files\": [\n    \"src\",\n    \"dist\",\n    \"!dist/test\",\n    \"!**/*.tsbuildinfo\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/src/index.d.ts\",\n      \"import\": \"./src/index.js\"\n    },\n    \"./utils\": {\n      \"types\": \"./src/utils.d.ts\",\n      \"import\": \"./src/utils.js\"\n    }\n  },\n  \"eslintConfig\": {\n    \"extends\": \"ipfs\",\n    \"parserOptions\": {\n      \"sourceType\": \"module\"\n    }\n  },\n  \"scripts\": {\n    \"lint\": \"aegir lint\",\n    \"test\": \"aegir test -t node --cov\",\n    \"test:node\": \"aegir test -t node --cov\",\n    \"clean\": \"aegir clean\",\n    \"dep-check\": \"aegir dep-check -i ipfs-core-types\",\n    \"build\": \"aegir build --no-bundle\"\n  },\n  \"dependencies\": {\n    \"@ipld/dag-cbor\": \"^9.0.0\",\n    \"@ipld/dag-json\": \"^10.0.0\",\n    \"@ipld/dag-pb\": \"^4.0.0\",\n    \"@libp2p/logger\": \"^2.0.5\",\n    \"@libp2p/peer-id\": \"^2.0.0\",\n    \"@multiformats/mafmt\": \"^11.0.2\",\n    \"@multiformats/multiaddr\": \"^11.1.5\",\n    \"@multiformats/multiaddr-to-uri\": \"^9.0.1\",\n    \"byteman\": \"^1.3.5\",\n    \"execa\": \"^6.1.0\",\n    \"get-folder-size\": \"^4.0.0\",\n    \"ipfs-core\": \"^0.18.1\",\n    \"ipfs-core-types\": \"^0.14.1\",\n    \"ipfs-core-utils\": \"^0.18.1\",\n    \"ipfs-daemon\": \"^0.16.1\",\n    \"ipfs-http-client\": \"^60.0.1\",\n    \"ipfs-utils\": \"^9.0.13\",\n    \"it-concat\": \"^3.0.1\",\n    \"it-merge\": \"^2.0.0\",\n    \"it-pipe\": \"^2.0.3\",\n    \"it-split\": \"^2.0.0\",\n    \"it-tar\": \"^6.0.0\",\n    \"jsondiffpatch\": \"^0.4.1\",\n    \"multiformats\": \"^11.0.0\",\n    \"parse-duration\": \"^1.0.0\",\n    \"pretty-bytes\": \"^6.0.0\",\n    \"progress\": \"^2.0.3\",\n    \"stream-to-it\": \"^0.2.2\",\n    \"uint8arrays\": \"^4.0.2\",\n    \"yargs\": \"^17.4.0\"\n  },\n  \"devDependencies\": {\n    \"@libp2p/crypto\": \"^1.0.7\",\n    \"@types/get-folder-size\": \"^3.0.1\",\n    \"@types/ncp\": \"^2.0.5\",\n    \"@types/progress\": \"^2.0.3\",\n    \"@types/rimraf\": \"^3.0.1\",\n    \"@types/yargs\": \"^17.0.10\",\n    \"aegir\": \"^37.11.0\",\n    \"ipfs-repo\": \"^17.0.0\",\n    \"it-all\": \"^2.0.0\",\n    \"it-first\": \"^2.0.0\",\n    \"it-map\": \"^2.0.0\",\n    \"it-to-buffer\": \"^3.0.0\",\n    \"nanoid\": \"^4.0.0\",\n    \"ncp\": \"^2.0.0\",\n    \"pako\": \"^2.0.4\",\n    \"rimraf\": \"^3.0.2\",\n    \"sinon\": \"^15.0.1\",\n    \"string-argv\": \"^0.3.1\",\n    \"temp-write\": \"^5.0.0\"\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-cli/src/command-alias.js",
    "content": "\n/**\n * @type {Record<string, [src: string, target: string]>}\n */\nconst aliases = {\n  // We need to be able to show help text for both the `refs` command and the\n  // `refs local` command, but with yargs `refs` cannot be both a command and\n  // a command directory. So alias `refs local` to `refs-local`\n  'refs-local': ['refs', 'local']\n}\n\n/**\n * Replace multi-word command with alias\n * eg replace `refs local` with `refs-local`\n *\n * @param {string[]} args\n */\nexport default function (args) {\n  for (const [alias, original] of Object.entries(aliases)) {\n    if (arrayMatch(args, original)) {\n      return [alias, ...args.slice(original.length)]\n    }\n  }\n\n  return args\n}\n\n/**\n * eg arrayMatch([1, 2, 3], [1, 2]) => true\n *\n * @param {string[]} arr\n * @param {string[]} sub\n */\nfunction arrayMatch (arr, sub) {\n  if (sub.length > arr.length) {\n    return false\n  }\n\n  for (let i = 0; i < sub.length; i++) {\n    if (arr[i] !== sub[i]) {\n      return false\n    }\n  }\n\n  return true\n}\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/add.js",
    "content": "/* eslint-disable complexity */\n\nimport getFolderSize from 'get-folder-size'\n// @ts-expect-error no types\nimport byteman from 'byteman'\nimport {\n  createProgressBar,\n  coerceMtime,\n  coerceMtimeNsecs,\n  stripControlCharacters\n} from '../utils.js'\nimport globSource from 'ipfs-utils/src/files/glob-source.js'\nimport parseDuration from 'parse-duration'\nimport merge from 'it-merge'\nimport fs from 'fs'\nimport path from 'path'\n\n/**\n * @param {string[]} paths\n */\nasync function getTotalBytes (paths) {\n  const sizes = await Promise.all(paths.map(p => getFolderSize(p)))\n  return sizes.reduce((total, { size }) => total + size, 0)\n}\n\n/**\n * @param {string} target\n * @param {object} options\n * @param {boolean} [options.recursive]\n * @param {boolean} [options.hidden]\n * @param {boolean} [options.preserveMode]\n * @param {boolean} [options.preserveMtime]\n * @param {number} [options.mode]\n * @param {import('ipfs-unixfs').MtimeLike} [options.mtime]\n */\nasync function * getSource (target, options = {}) {\n  const absolutePath = path.resolve(target)\n  const stats = await fs.promises.stat(absolutePath)\n\n  if (stats.isFile()) {\n    let mtime = options.mtime\n    let mode = options.mode\n\n    if (options.preserveMtime) {\n      mtime = stats.mtime\n    }\n\n    if (options.preserveMode) {\n      mode = stats.mode\n    }\n\n    yield {\n      path: path.basename(target),\n      content: fs.createReadStream(absolutePath),\n      mtime,\n      mode\n    }\n\n    return\n  }\n\n  const dirName = path.basename(absolutePath)\n\n  let pattern = '*'\n\n  if (options.recursive) {\n    pattern = '**/*'\n  }\n\n  for await (const content of globSource(target, pattern, {\n    hidden: options.hidden,\n    preserveMode: options.preserveMode,\n    preserveMtime: options.preserveMtime,\n    mode: options.mode,\n    mtime: options.mtime\n  })) {\n    yield {\n      ...content,\n      path: `${dirName}${content.path}`\n    }\n  }\n}\n\n/**\n * @typedef {object} Argv\n * @property {import('../types').Context} Argv.ctx\n * @property {boolean} Argv.trickle\n * @property {number} Argv.shardSplitThreshold\n * @property {import('multiformats/cid').Version} Argv.cidVersion\n * @property {boolean} Argv.rawLeaves\n * @property {boolean} Argv.onlyHash\n * @property {string} Argv.hash\n * @property {boolean} Argv.wrapWithDirectory\n * @property {boolean} Argv.pin\n * @property {string} Argv.chunker\n * @property {boolean} Argv.preload\n * @property {number} Argv.fileImportConcurrency\n * @property {number} Argv.blockWriteConcurrency\n * @property {number} Argv.timeout\n * @property {boolean} Argv.quieter\n * @property {boolean} Argv.quiet\n * @property {boolean} Argv.silent\n * @property {boolean} Argv.progress\n * @property {string[]} Argv.file\n * @property {number} Argv.mtime\n * @property {number} Argv.mtimeNsecs\n * @property {boolean} Argv.recursive\n * @property {boolean} Argv.hidden\n * @property {boolean} Argv.preserveMode\n * @property {boolean} Argv.preserveMtime\n * @property {number} Argv.mode\n * @property {string} Argv.cidBase\n * @property {boolean} Argv.enableShardingExperiment\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'add [file...]',\n\n  describe: 'Add a file to IPFS using the UnixFS data format',\n\n  builder: {\n    progress: {\n      alias: 'p',\n      boolean: true,\n      default: true,\n      describe: 'Stream progress data'\n    },\n    recursive: {\n      alias: 'r',\n      boolean: true,\n      default: false\n    },\n    trickle: {\n      alias: 't',\n      boolean: true,\n      default: false,\n      describe: 'Use the trickle DAG builder'\n    },\n    'wrap-with-directory': {\n      alias: 'w',\n      boolean: true,\n      default: false,\n      describe: 'Add a wrapping node'\n    },\n    'only-hash': {\n      alias: 'n',\n      boolean: true,\n      default: false,\n      describe: 'Only chunk and hash, do not write'\n    },\n    'block-write-concurrency': {\n      number: true,\n      default: 10,\n      describe: 'After a file has been chunked, this controls how many chunks to hash and add to the block store concurrently'\n    },\n    chunker: {\n      default: 'size-262144',\n      describe: 'Chunking algorithm to use, formatted like [size-{size}, rabin, rabin-{avg}, rabin-{min}-{avg}-{max}]'\n    },\n    'file-import-concurrency': {\n      number: true,\n      default: 50,\n      describe: 'How many files to import at once'\n    },\n    'enable-sharding-experiment': {\n      boolean: true,\n      default: false\n    },\n    'shard-split-threshold': {\n      number: true,\n      default: 1000\n    },\n    'raw-leaves': {\n      boolean: true,\n      describe: 'Use raw blocks for leaf nodes. (experimental)'\n    },\n    'cid-version': {\n      number: true,\n      describe: 'CID version. Defaults to 0 unless an option that depends on CIDv1 is passed. (experimental)',\n      default: 0\n    },\n    'cid-base': {\n      describe: 'Number base to display CIDs in',\n      string: true,\n      default: 'base58btc'\n    },\n    hash: {\n      string: true,\n      describe: 'Hash function to use. Will set CID version to 1 if used. (experimental)',\n      default: 'sha2-256'\n    },\n    quiet: {\n      alias: 'q',\n      boolean: true,\n      default: false,\n      describe: 'Write minimal output'\n    },\n    quieter: {\n      alias: 'Q',\n      boolean: true,\n      default: false,\n      describe: 'Write only final hash'\n    },\n    silent: {\n      boolean: true,\n      default: false,\n      describe: 'Write no output'\n    },\n    pin: {\n      boolean: true,\n      default: true,\n      describe: 'Pin this object when adding'\n    },\n    preload: {\n      boolean: true,\n      default: true,\n      describe: 'Preload this object when adding'\n    },\n    hidden: {\n      alias: 'H',\n      boolean: true,\n      default: false,\n      describe: 'Include files that are hidden. Only takes effect on recursive add.'\n    },\n    'preserve-mode': {\n      boolean: true,\n      default: false,\n      describe: 'Apply permissions to created UnixFS entries'\n    },\n    'preserve-mtime': {\n      boolean: true,\n      default: false,\n      describe: 'Apply modification time to created UnixFS entries'\n    },\n    mode: {\n      string: true,\n      describe: 'File mode to apply to created UnixFS entries'\n    },\n    mtime: {\n      number: true,\n      coerce: coerceMtime,\n      describe: 'Modification time in seconds before or since the Unix Epoch to apply to created UnixFS entries'\n    },\n    'mtime-nsecs': {\n      number: true,\n      coerce: coerceMtimeNsecs,\n      describe: 'Modification time fraction in nanoseconds'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({\n    ctx: { ipfs, print, isDaemon, getStdin },\n    trickle,\n    shardSplitThreshold,\n    cidVersion,\n    rawLeaves,\n    onlyHash,\n    hash,\n    wrapWithDirectory,\n    pin,\n    chunker,\n    preload,\n    fileImportConcurrency,\n    blockWriteConcurrency,\n    timeout,\n    quieter,\n    quiet,\n    silent,\n    progress,\n    file,\n    mtime,\n    mtimeNsecs,\n    recursive,\n    hidden,\n    preserveMode,\n    preserveMtime,\n    mode,\n    cidBase,\n    enableShardingExperiment\n  }) {\n    const options = {\n      trickle,\n      shardSplitThreshold,\n      cidVersion,\n      rawLeaves,\n      onlyHash,\n      hashAlg: hash,\n      wrapWithDirectory,\n      pin,\n      chunker,\n      preload,\n      fileImportConcurrency,\n      blockWriteConcurrency,\n      /**\n       * @type {import('ipfs-core-types/src/root').AddProgressFn}\n       */\n      progress: (bytes, name) => {},\n      timeout\n    }\n\n    if (enableShardingExperiment && isDaemon) {\n      throw new Error('Error: Enabling the sharding experiment should be done on the daemon')\n    }\n\n    /** @type {{update: Function, interrupt: Function, terminate: Function} | undefined} */\n    let bar\n    let log = print\n\n    if (quieter || quiet || silent) {\n      progress = false\n    }\n\n    if (progress && file) {\n      const totalBytes = await getTotalBytes(file)\n      bar = createProgressBar(totalBytes, print)\n\n      if (print.isTTY) {\n        // bar.interrupt uses clearLine and cursorTo methods that are only on TTYs\n        log = bar.interrupt.bind(bar)\n      }\n\n      /**\n       * @param {number} byteLength\n       */\n      options.progress = byteLength => {\n        if (bar) {\n          bar.update(byteLength / totalBytes, { progress: byteman(byteLength, 2, 'MB') })\n        }\n      }\n    }\n\n    if (options.rawLeaves == null) {\n      options.rawLeaves = cidVersion > 0\n    }\n\n    /** @type {{ secs: number, nsecs?: number } | undefined} */\n    let date\n\n    if (mtime) {\n      date = { secs: mtime, nsecs: mtimeNsecs }\n    }\n\n    const source = file\n      ? merge(...file.map(file => getSource(file, {\n        hidden,\n        recursive,\n        preserveMode,\n        preserveMtime,\n        mode,\n        mtime: date\n      })))\n      : [{\n          content: getStdin(),\n          mode,\n          mtime: date\n        }] // Pipe to ipfs.add tagging with mode and mtime\n\n    let finalCid\n    const base = await ipfs.bases.getBase(cidBase)\n\n    try {\n      for await (const { cid, path } of ipfs.addAll(source, options)) {\n        if (silent) {\n          continue\n        }\n\n        if (quieter) {\n          finalCid = cid\n          continue\n        }\n\n        const pathStr = stripControlCharacters(path)\n        const cidStr = cid.toString(base.encoder)\n        let message = cidStr\n\n        if (!quiet) {\n          // print the hash twice if we are piping from stdin\n          message = `added ${cidStr} ${file ? pathStr || '' : cidStr}`.trim()\n        }\n\n        log(message)\n      }\n    } catch (/** @type {any} */ err) {\n      // Tweak the error message and add more relevant info for the CLI\n      if (err.code === 'ERR_DIR_NON_RECURSIVE') {\n        err.message = `'${err.path}' is a directory, use the '-r' flag to specify directories`\n      }\n\n      throw err\n    } finally {\n      if (bar) {\n        bar.terminate()\n      }\n    }\n\n    if (quieter && finalCid) {\n      log(finalCid.toString(base.encoder))\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/bitswap/index.js",
    "content": "import bitswapStat from './stat.js'\nimport bitswapUnwant from './unwant.js'\nimport bitswapWantlist from './wantlist.js'\n\n/** @type {import('yargs').CommandModule[]} */\nexport const commands = [\n  bitswapStat,\n  bitswapUnwant,\n  bitswapWantlist\n]\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/bitswap/stat.js",
    "content": "import prettyBytes from 'pretty-bytes'\nimport parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {boolean} Argv.human\n * @property {string} Argv.cidBase\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'stat',\n\n  describe: 'Show some diagnostic information on the bitswap agent',\n\n  builder: {\n    'cid-base': {\n      describe: 'Number base to display CIDs in. Note: specifying a CID base for v0 CIDs will have no effect',\n      string: true,\n      default: 'base58btc'\n    },\n    human: {\n      boolean: true,\n      default: false\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, cidBase, human, timeout }) {\n    const stats = await ipfs.bitswap.stat({\n      timeout\n    })\n\n    /** @type {Record<string, any>} */\n    const output = {\n      ...stats\n    }\n\n    if (human) {\n      output.blocksReceived = Number(stats.blocksReceived)\n      output.blocksSent = Number(stats.blocksSent)\n      output.dataReceived = prettyBytes(Number(stats.dataReceived)).toUpperCase()\n      output.dataSent = prettyBytes(Number(stats.dataSent)).toUpperCase()\n      output.dupBlksReceived = Number(stats.dupBlksReceived)\n      output.dupDataReceived = prettyBytes(Number(stats.dupDataReceived)).toUpperCase()\n      output.wantlist = `[${stats.wantlist.length} keys]`\n    } else {\n      const base = await ipfs.bases.getBase(cidBase)\n\n      const wantlist = stats.wantlist.map(cid => cid.toString(base.encoder))\n      output.wantlist = `[${wantlist.length} keys]\n            ${wantlist.join('\\n            ')}`\n    }\n\n    print(`bitswap status\n        provides buffer: ${output.provideBufLen}\n        blocks received: ${output.blocksReceived}\n        blocks sent: ${output.blocksSent}\n        data received: ${output.dataReceived}\n        data sent: ${output.dataSent}\n        dup blocks received: ${output.dupBlksReceived}\n        dup data received: ${output.dupDataReceived}\n        wantlist ${output.wantlist}\n        partners [${stats.peers.length}]`)\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/bitswap/unwant.js",
    "content": "import parseDuration from 'parse-duration'\nimport { coerceCID } from '../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {import('multiformats/cid').CID} Argv.key\n * @property {string} Argv.cidBase\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'unwant <key>',\n\n  describe: 'Removes a given block from your wantlist',\n\n  builder: {\n    key: {\n      alias: 'k',\n      describe: 'Key to remove from your wantlist',\n      string: true,\n      coerce: coerceCID\n    },\n    'cid-base': {\n      describe: 'Number base to display CIDs in. Note: specifying a CID base for v0 CIDs will have no effect',\n      string: true,\n      default: 'base58btc'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx, key, cidBase, timeout }) {\n    const { ipfs, print } = ctx\n    const base = await ipfs.bases.getBase(cidBase)\n    await ipfs.bitswap.unwant(key, {\n      timeout\n    })\n    print(`Key ${key.toString(base.encoder)} removed from wantlist`)\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/bitswap/wantlist.js",
    "content": "import parseDuration from 'parse-duration'\nimport { coercePeerId } from '../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {import('@libp2p/interface-peer-id').PeerId} Argv.peer\n * @property {string} Argv.cidBase\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'wantlist [peer]',\n\n  describe: 'Print out all blocks currently on the bitswap wantlist for the local peer',\n\n  builder: {\n    peer: {\n      alias: 'p',\n      describe: 'Specify which peer to show wantlist for',\n      string: true,\n      coerce: coercePeerId\n    },\n    'cid-base': {\n      describe: 'Number base to display CIDs in. Note: specifying a CID base for v0 CIDs will have no effect',\n      string: true,\n      default: 'base58btc'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx, peer, cidBase, timeout }) {\n    const { ipfs, print } = ctx\n    const base = await ipfs.bases.getBase(cidBase)\n\n    /** @type {import('multiformats/cid').CID[]} */\n    let list\n\n    if (peer) {\n      list = await ipfs.bitswap.wantlistForPeer(peer, {\n        timeout\n      })\n    } else {\n      list = await ipfs.bitswap.wantlist({\n        timeout\n      })\n    }\n\n    list.forEach(cid => print(cid.toString(base.encoder)))\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/bitswap.js",
    "content": "import { commands } from './bitswap/index.js'\n\n/** @type {import('yargs').CommandModule} */\nconst command = {\n  command: 'bitswap <command>',\n\n  describe: 'Interact with the bitswap agent',\n\n  builder (yargs) {\n    commands.forEach(command => {\n      yargs.command(command)\n    })\n\n    return yargs\n  },\n\n  handler () {\n\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/block/get.js",
    "content": "import parseDuration from 'parse-duration'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\nimport { coerceCID } from '../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {import('multiformats/cid').CID} Argv.key\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'get <key>',\n\n  describe: 'Get a raw IPFS block',\n\n  builder: {\n    key: {\n      string: true,\n      coerce: coerceCID\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx, key, timeout }) {\n    const { ipfs, print } = ctx\n    const block = await ipfs.block.get(key, {\n      timeout\n    })\n    if (block) {\n      print(uint8ArrayToString(block), false)\n    } else {\n      print('Block was unwanted before it could be remotely retrieved')\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/block/index.js",
    "content": "import blockGet from './get.js'\nimport blockPut from './put.js'\nimport blockRm from './rm.js'\nimport blockStat from './stat.js'\n\n/** @type {import('yargs').CommandModule[]} */\nexport const commands = [\n  blockGet,\n  blockPut,\n  blockRm,\n  blockStat\n]\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/block/put.js",
    "content": "import fs from 'fs'\nimport concat from 'it-concat'\nimport parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string} Argv.block\n * @property {string} Argv.format\n * @property {string} Argv.mhtype\n * @property {number} Argv.mhlen\n * @property {import('multiformats/cid').Version} Argv.version\n * @property {boolean} Argv.pin\n * @property {string} Argv.cidBase\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'put [block]',\n\n  describe: 'Stores input as an IPFS block',\n\n  builder: {\n    format: {\n      alias: 'f',\n      describe: 'cid format for blocks to be created with',\n      default: 'dag-pb'\n    },\n    mhtype: {\n      describe: 'multihash hash function',\n      default: 'sha2-256'\n    },\n    mhlen: {\n      describe: 'multihash hash length',\n      default: undefined\n    },\n    version: {\n      describe: 'cid version',\n      number: true,\n      default: 0\n    },\n    'cid-base': {\n      describe: 'Number base to display CIDs in',\n      string: true,\n      default: 'base58btc'\n    },\n    pin: {\n      describe: 'Pin this block recursively',\n      boolean: true,\n      default: false\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print, getStdin }, block, timeout, format, mhtype, mhlen, version, cidBase, pin }) {\n    let data\n\n    if (block) {\n      data = fs.readFileSync(block)\n    } else {\n      data = (await concat(getStdin(), { type: 'buffer' })).subarray()\n    }\n\n    const cid = await ipfs.block.put(data, {\n      timeout,\n      format,\n      mhtype,\n      version,\n      pin\n    })\n    const base = await ipfs.bases.getBase(cidBase)\n\n    print(cid.toString(base.encoder))\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/block/rm.js",
    "content": "import parseDuration from 'parse-duration'\nimport { coerceCIDs } from '../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {import('multiformats/cid').CID[]} Argv.hash\n * @property {boolean} Argv.force\n * @property {boolean} Argv.quiet\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'rm <hash...>',\n\n  describe: 'Remove IPFS block(s)',\n\n  builder: {\n    hash: {\n      type: 'array',\n      coerce: coerceCIDs\n    },\n    force: {\n      alias: 'f',\n      describe: 'Ignore nonexistent blocks',\n      boolean: true,\n      default: false\n    },\n    quiet: {\n      alias: 'q',\n      describe: 'Write minimal output',\n      boolean: true,\n      default: false\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx, hash, force, quiet, timeout }) {\n    const { ipfs, print } = ctx\n\n    let errored = false\n\n    for await (const result of ipfs.block.rm(hash, {\n      force,\n      quiet,\n      timeout\n    })) {\n      if (result.error) {\n        errored = true\n      }\n\n      if (!quiet) {\n        print(result.error ? result.error.message : `removed ${result.cid}`)\n      }\n    }\n\n    if (errored && !quiet) {\n      throw new Error('some blocks not removed')\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/block/stat.js",
    "content": "import parseDuration from 'parse-duration'\nimport { coerceCID } from '../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {import('multiformats/cid').CID} Argv.key\n * @property {string} Argv.cidBase\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'stat <key>',\n\n  describe: 'Print information of a raw IPFS block',\n\n  builder: {\n    key: {\n      string: true,\n      coerce: coerceCID\n    },\n    'cid-base': {\n      describe: 'Number base to display CIDs in',\n      string: true,\n      default: 'base58btc'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx, key, cidBase, timeout }) {\n    const { ipfs, print } = ctx\n    const stats = await ipfs.block.stat(key, {\n      timeout\n    })\n    const base = await ipfs.bases.getBase(cidBase)\n    print('Key: ' + stats.cid.toString(base.encoder))\n    print('Size: ' + stats.size)\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/block.js",
    "content": "import { commands } from './block/index.js'\n\n/** @type {import('yargs').CommandModule} */\nconst command = {\n  command: 'block <command>',\n\n  describe: 'Manipulate raw IPFS blocks',\n\n  builder (yargs) {\n    commands.forEach(command => {\n      yargs.command(command)\n    })\n\n    return yargs\n  },\n\n  handler () {\n\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/bootstrap/add.js",
    "content": "import parseDuration from 'parse-duration'\nimport { coerceMultiaddr } from '../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {import('@multiformats/multiaddr').Multiaddr} Argv.peer\n * @property {boolean} Argv.default\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'add [<peer>]',\n\n  describe: 'Add peers to the bootstrap list',\n\n  builder: {\n    peer: {\n      string: true,\n      coerce: coerceMultiaddr\n    },\n    default: {\n      describe: 'Add default bootstrap nodes',\n      boolean: true,\n      default: false\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, peer, default: defaultPeers, timeout }) {\n    let list\n\n    if (peer) {\n      list = await ipfs.bootstrap.add(peer, {\n        timeout\n      })\n    } else if (defaultPeers) {\n      list = await ipfs.bootstrap.reset({\n        timeout\n      })\n    } else {\n      throw new Error('Please specify a peer or the --default flag')\n    }\n\n    list.Peers.forEach((peer) => print(peer.toString()))\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/bootstrap/index.js",
    "content": "import bootstrapAdd from './add.js'\nimport bootstrapList from './list.js'\nimport bootstrapRm from './rm.js'\n\n/** @type {import('yargs').CommandModule[]} */\nexport const commands = [\n  bootstrapAdd,\n  bootstrapList,\n  bootstrapRm\n]\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/bootstrap/list.js",
    "content": "import parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'list',\n\n  describe: 'Show peers in the bootstrap list',\n\n  builder: {\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, timeout }) {\n    const list = await ipfs.bootstrap.list({\n      timeout\n    })\n    list.Peers.forEach((node) => print(node.toString()))\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/bootstrap/rm.js",
    "content": "import parseDuration from 'parse-duration'\nimport { coerceMultiaddr } from '../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {import('@multiformats/multiaddr').Multiaddr} Argv.peer\n * @property {boolean} Argv.all\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'rm [<peer>]',\n\n  describe: 'Removes peers from the bootstrap list',\n\n  builder: {\n    peer: {\n      string: true,\n      coerce: coerceMultiaddr\n    },\n    all: {\n      boolean: true,\n      describe: 'Remove all bootstrap peers',\n      default: false\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, all, peer, timeout }) {\n    let list\n\n    if (peer) {\n      list = await ipfs.bootstrap.rm(peer, {\n        timeout\n      })\n    } else if (all) {\n      list = await ipfs.bootstrap.clear({\n        timeout\n      })\n    } else {\n      throw new Error('Please specify a peer or the --all flag')\n    }\n\n    list.Peers.forEach((peer) => print(peer.toString()))\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/bootstrap.js",
    "content": "import { commands } from './bootstrap/index.js'\n\n/** @type {import('yargs').CommandModule} */\nconst command = {\n  command: 'bootstrap <command>',\n\n  describe: 'Show or edit the list of bootstrap peers',\n\n  builder (yargs) {\n    commands.forEach(command => {\n      yargs.command(command)\n    })\n\n    return yargs\n  },\n\n  handler () {\n\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/cat.js",
    "content": "import parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../types').Context} Argv.ctx\n * @property {string} Argv.ipfsPath\n * @property {number} Argv.offset\n * @property {number} Argv.length\n * @property {boolean} Argv.preload\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'cat <ipfsPath>',\n\n  describe: 'Fetch and cat an IPFS path referencing a file',\n\n  builder: {\n    offset: {\n      alias: 'o',\n      number: true,\n      describe: 'Byte offset to begin reading from'\n    },\n    length: {\n      alias: ['n', 'count'],\n      number: true,\n      describe: 'Maximum number of bytes to read'\n    },\n    preload: {\n      boolean: true,\n      default: true,\n      describe: 'Preload this object when adding'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, ipfsPath, offset, length, preload, timeout }) {\n    for await (const buf of ipfs.cat(ipfsPath, { offset, length, preload, timeout })) {\n      print.write(buf)\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/cid/base32.js",
    "content": "import split from 'it-split'\nimport { CID } from 'multiformats/cid'\nimport { base32 } from 'multiformats/bases/base32'\nimport { toString as uint8arrayToString } from 'uint8arrays/to-string'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string[]} [Argv.cids]\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'base32 [cids...]',\n\n  describe: 'Convert CIDs to base 32 CID version 1',\n\n  async handler ({ ctx: { print, getStdin }, cids }) {\n    let input\n\n    if (cids && cids.length) {\n      input = cids\n    } else {\n      input = split(getStdin())\n    }\n\n    for await (const data of input) {\n      const input = (data instanceof Uint8Array ? uint8arrayToString(data) : data).trim()\n\n      if (!input) {\n        continue\n      }\n\n      print(CID.parse(input).toV1().toString(base32.encoder))\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/cid/bases.js",
    "content": "\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {boolean} [Argv.prefix]\n * @property {boolean} [Argv.numeric]\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'bases',\n\n  describe: 'List available multibase encoding names',\n\n  builder: {\n    prefix: {\n      describe: 'Display the single letter encoding codes as well as the encoding name',\n      boolean: true,\n      default: false\n    },\n    numeric: {\n      describe: 'Display the numeric encoding code as well as the encoding name',\n      boolean: true,\n      default: false\n    }\n  },\n\n  handler ({ ctx: { ipfs, print }, prefix, numeric }) {\n    for (const base of ipfs.bases.listBases()) {\n      if (prefix && numeric) {\n        print(`${base.prefix}\\t${base.prefix.charCodeAt(0)}\\t${base.name}`)\n      } else if (prefix) {\n        print(`${base.prefix}\\t${base.name}`)\n      } else if (numeric) {\n        print(`${base.prefix.charCodeAt(0)}\\t${base.name}`)\n      } else {\n        print(base.name)\n      }\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/cid/codecs.js",
    "content": "\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {boolean} [Argv.numeric]\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'codecs',\n\n  describe: 'List available CID codec names',\n\n  builder: {\n    numeric: {\n      describe: 'Display the numeric code as well as the codec name',\n      boolean: true,\n      default: false\n    }\n  },\n\n  handler ({ ctx: { ipfs, print }, numeric }) {\n    for (const codec of ipfs.codecs.listCodecs()) {\n      if (numeric) {\n        print(`${codec.code}\\t${codec.name}`)\n      } else {\n        print(codec.name)\n      }\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/cid/format.js",
    "content": "import split from 'it-split'\nimport { CID } from 'multiformats/cid'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string[]} [Argv.cids]\n * @property {string} [Argv.format]\n * @property {import('multiformats/cid').Version} [Argv.cidVersion]\n * @property {string} [Argv.base]\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'format [cids...]',\n\n  describe: 'Format and convert a CID in various useful ways',\n\n  builder: {\n    format: {\n      describe: `Printf style format string:\n\n%% literal %\n%b multibase name\n%B multibase code\n%v version string\n%V version number\n%c codec name\n%C codec code\n%h multihash name\n%H multihash code\n%L hash digest length\n%m multihash encoded in base %b (with multibase prefix)\n%M multihash encoded in base %b without multibase prefix\n%d hash digest encoded in base %b (with multibase prefix)\n%D hash digest encoded in base %b without multibase prefix\n%s cid string encoded in base %b (1)\n%S cid string encoded in base %b without multibase prefix\n%P cid prefix: %v-%c-%h-%L\n\n(1) For CID version 0 the multibase must be base58btc and no prefix is used. For Cid version 1 the multibase prefix is included.`,\n      alias: 'f',\n      string: true,\n      default: '%s'\n    },\n    'cid-version': {\n      describe: 'CID version to convert to',\n      alias: 'v',\n      number: true\n    },\n    base: {\n      describe: 'Multibase to display output in',\n      alias: 'b',\n      string: true\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print, getStdin }, cids, format, cidVersion, base }) {\n    let input\n\n    if (cids && cids.length) {\n      input = cids\n    } else {\n      input = split(getStdin())\n    }\n\n    let formatStr = format || '%s'\n\n    if (formatStr === 'prefix') {\n      formatStr = '%P'\n    }\n\n    if (typeof formatStr !== 'string' || formatStr.indexOf('%') === -1) {\n      throw new Error(`invalid format string: ${formatStr}`)\n    }\n\n    for await (const data of input) {\n      const str = data.toString().trim()\n\n      if (!str) {\n        continue\n      }\n\n      let cid = CID.parse(str)\n\n      if (cidVersion != null && cid.version !== cidVersion) {\n        if (cidVersion === 0) {\n          cid = cid.toV0()\n        } else if (cidVersion === 1) {\n          cid = cid.toV1()\n        } else {\n          throw new Error(`invalid cid version: ${cidVersion}`)\n        }\n      }\n\n      let cidBase = findBase(str, ipfs)\n\n      if (base) {\n        const foundBase = ipfs.bases.listBases().find(b => b.name === base)\n\n        if (!foundBase) {\n          throw new Error(`invalid base prefix: ${str.substring(0, 1)}`)\n        }\n\n        cidBase = foundBase\n      }\n\n      print(formatStr.replace(/%([a-zA-Z%])/g, replacer(cid, cidBase, ipfs)))\n    }\n  }\n}\n\nexport default command\n\n/**\n * @param {CID} cid\n * @param {import('multiformats/bases/interface').MultibaseCodec<any>} base\n * @param {import('ipfs-core-types').IPFS} ipfs\n * @returns {(match: any, specifier: string) => string}\n */\nfunction replacer (cid, base, ipfs) {\n  /**\n   * @param {*} match\n   * @param {string} specifier\n   */\n  const replace = (match, specifier) => {\n    switch (specifier) {\n      case '%':\n        return '%'\n      case 'b': // base name\n        return base.name\n      case 'B': // base code\n        return base.prefix\n      case 'v': // version string\n        return `cidv${cid.version}`\n      case 'V': // version num\n        return cid.version.toString()\n      case 'c': // codec name\n        return findCodec(cid, ipfs).name\n      case 'C': // codec code\n        return cid.code\n      case 'h': // hash fun name\n        return findHasher(cid, ipfs).name\n      case 'H': // hash fun code\n        return findHasher(cid, ipfs).code\n      case 'L': // hash length\n        return cid.multihash.size.toString()\n      case 'm': // multihash encoded in base %b\n        return base.encoder.encode(cid.multihash.bytes)\n      case 'M': // multihash encoded in base %b without base prefix\n        return base.encoder.encode(cid.multihash.bytes).substring(1)\n      case 'd': // hash digest encoded in base %b\n        return base.encoder.encode(cid.multihash.digest)\n      case 'D': // hash digest encoded in base %b without base prefix\n        return base.encoder.encode(cid.multihash.digest).substring(1)\n      case 's': // cid string encoded in base %b\n        return base.encoder.encode(cid.bytes).slice(cid.version === 0 && base.name === 'base58btc' ? 1 : 0)\n      case 'S': // cid string without base prefix\n        return base.encoder.encode(cid.bytes).slice(1)\n      case 'P': // prefix\n        return prefix(cid, ipfs)\n\n      default:\n        throw new Error(`unrecognized specifier in format string: ${specifier}`)\n    }\n  }\n\n  return replace\n}\n\n/**\n * @param {string} str\n * @param {import('ipfs-core-types').IPFS} ipfs\n */\nfunction findBase (str, ipfs) {\n  if (CID.parse(str).version === 0) {\n    // force a match for base58btc for CIDv0, assuming it's configured\n    str = `z${str}`\n  }\n\n  const prefix = str.substring(0, 1)\n  const base = ipfs.bases.listBases().find(b => b.prefix === prefix)\n\n  if (!base) {\n    throw new Error(`invalid base prefix: ${str.substring(0, 1)}`)\n  }\n\n  return base\n}\n\n/**\n * @param {CID} cid\n * @param {import('ipfs-core-types').IPFS} ipfs\n */\nfunction findCodec (cid, ipfs) {\n  const codec = ipfs.codecs.listCodecs().find(c => c.code === cid.code)\n\n  if (!codec) {\n    throw new Error(`invalid codec: ${cid.code}`)\n  }\n\n  return codec\n}\n\n/**\n * @param {CID} cid\n * @param {import('ipfs-core-types').IPFS} ipfs\n */\nfunction findHasher (cid, ipfs) {\n  const codec = ipfs.hashers.listHashers().find(h => h.code === cid.multihash.code)\n\n  if (!codec) {\n    throw new Error(`invalid codec: ${cid.code}`)\n  }\n\n  return codec\n}\n\n/**\n * @param {CID} cid\n * @param {import('ipfs-core-types').IPFS} ipfs\n */\nfunction prefix (cid, ipfs) {\n  const hasher = findHasher(cid, ipfs)\n  const codec = findCodec(cid, ipfs)\n\n  return `cidv${cid.version}-${codec.name}-${hasher.name}-${cid.multihash.size}`\n}\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/cid/hashes.js",
    "content": "\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {boolean} [Argv.numeric]\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'hashes',\n\n  describe: 'List available multihash hashing algorithm names',\n\n  builder: {\n    numeric: {\n      describe: 'Display the numeric code as well as the hashing algorithm name',\n      boolean: true,\n      default: false\n    }\n  },\n\n  handler ({ ctx: { ipfs, print }, numeric }) {\n    for (const codec of ipfs.hashers.listHashers()) {\n      if (numeric) {\n        print(`${codec.code}\\t${codec.name}`)\n      } else {\n        print(codec.name)\n      }\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/cid/index.js",
    "content": "import cidBase32 from './base32.js'\nimport cidBases from './bases.js'\nimport cidCodecs from './codecs.js'\nimport cidFormat from './format.js'\nimport cidHashes from './hashes.js'\n\n/** @type {import('yargs').CommandModule[]} */\nexport const commands = [\n  cidBase32,\n  cidBases,\n  cidCodecs,\n  cidFormat,\n  cidHashes\n]\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/cid.js",
    "content": "import { commands } from './cid/index.js'\n\n/** @type {import('yargs').CommandModule} */\nconst command = {\n  command: 'cid <command>',\n\n  describe: 'Convert, format and discover properties of CIDs',\n\n  builder (yargs) {\n    commands.forEach(command => {\n      yargs.command(command)\n    })\n\n    return yargs\n  },\n\n  handler () {\n\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/config/edit.js",
    "content": "import path from 'path'\nimport { execa } from 'execa'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'edit',\n\n  describe: 'Opens the config file for editing in $EDITOR',\n\n  async handler ({ ctx: { repoPath } }) {\n    const editor = process.env.EDITOR\n\n    if (!editor) {\n      throw new Error('ENV variable $EDITOR not set')\n    }\n\n    await execa(editor, [path.join(repoPath, 'config')])\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/config/index.js",
    "content": "import configEdit from './edit.js'\nimport configProfile from './profile.js'\nimport configReplace from './replace.js'\nimport configShow from './show.js'\n\n/** @type {import('yargs').CommandModule[]} */\nexport const commands = [\n  configEdit,\n  configProfile,\n  configReplace,\n  configShow\n]\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/config/profile/apply.js",
    "content": "import JSONDiff from 'jsondiffpatch'\nimport parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../../types').Context} Argv.ctx\n * @property {string} Argv.profile\n * @property {boolean} Argv.dryRun\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'apply <profile>',\n\n  describe: 'Apply profile to config',\n\n  builder: {\n    'dry-run': {\n      boolean: true,\n      describe: 'print difference between the current config and the config that would be generated',\n      default: false\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx, profile, dryRun, timeout }) {\n    const { print, ipfs, isDaemon } = ctx\n    const diff = await ipfs.config.profiles.apply(profile, {\n      dryRun,\n      timeout\n    })\n    const delta = JSONDiff.diff(diff.original, diff.updated)\n    const res = delta && JSONDiff.formatters.console.format(delta, diff.original)\n\n    if (res) {\n      print(res)\n\n      if (isDaemon) {\n        print('\\nThe IPFS daemon is running in the background, you may need to restart it for changes to take effect.')\n      }\n    } else {\n      print(`IPFS config already contains the settings from the '${profile}' profile`)\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/config/profile/index.js",
    "content": "import configProfileApply from './apply.js'\nimport configProfileLs from './ls.js'\n\n/** @type {import('yargs').CommandModule[]} */\nexport const commands = [\n  configProfileApply,\n  configProfileLs\n]\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/config/profile/ls.js",
    "content": "import parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../../types').Context} Argv.ctx\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'ls',\n\n  describe: 'List available config profiles',\n\n  builder: {\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, timeout }) {\n    for (const profile of await ipfs.config.profiles.list({\n      timeout\n    })) {\n      print(`${profile.name}:\\n ${profile.description}`)\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/config/profile.js",
    "content": "import { commands } from './profile/index.js'\n\n/**\n * @typedef {import('yargs').Argv<{}>} Argv\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'profile <command>',\n\n  describe: 'Interact with config profiles',\n\n  builder (yargs) {\n    return yargs\n      .command(commands)\n  },\n\n  handler () {\n\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/config/replace.js",
    "content": "import path from 'path'\nimport fs from 'fs'\nimport parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string} Argv.file\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'replace <file>',\n\n  describe: 'Replaces the config with <file>',\n\n  builder: {\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  handler ({ ctx: { ipfs, isDaemon }, file, timeout }) {\n    const filePath = path.resolve(process.cwd(), file)\n\n    const config = isDaemon\n      ? filePath\n      : JSON.parse(fs.readFileSync(filePath, 'utf8'))\n\n    return ipfs.config.replace(config, {\n      timeout\n    })\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/config/show.js",
    "content": "import parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'show',\n\n  describe: 'Outputs the content of the config file',\n\n  builder: {\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, timeout }) {\n    const config = await ipfs.config.getAll({\n      timeout\n    })\n    print(JSON.stringify(config, null, 2))\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/config.js",
    "content": "import { commands } from './config/index.js'\nimport parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../types').Context} Argv.ctx\n * @property {any} Argv.value\n * @property {boolean} Argv.bool\n * @property {boolean} Argv.json\n * @property {string} Argv.key\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'config <key> [value]',\n\n  describe: 'Get and set IPFS config values',\n\n  builder: (yargs) => {\n    commands.forEach(command => {\n      yargs.command(command)\n    })\n\n    yargs\n      .option('bool', {\n        boolean: true,\n        describe: 'Set a boolean value',\n        default: false\n      })\n      .option('json', {\n        boolean: true,\n        describe: 'Parse stringified JSON',\n        default: false\n      })\n      .option('timeout', {\n        string: true,\n        coerce: parseDuration\n      })\n\n    return yargs\n  },\n\n  async handler ({ ctx: { ipfs, print }, value, bool, json, key, timeout }) {\n    if (!value) {\n      // Get the value of a given key\n      value = await ipfs.config.get(key, {\n        timeout\n      })\n\n      if (typeof value === 'object') {\n        print(JSON.stringify(value, null, 2))\n      } else {\n        print(value)\n      }\n    } else {\n      // Set the new value of a given key\n\n      if (bool) {\n        value = (value === 'true')\n      } else if (json) {\n        try {\n          value = JSON.parse(value)\n        } catch (/** @type {any} */ err) {\n          throw new Error('invalid JSON provided')\n        }\n      }\n\n      await ipfs.config.set(key, value, {\n        timeout\n      })\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/daemon.js",
    "content": "import os from 'os'\nimport fs from 'fs'\nimport { multiaddrToUri } from '@multiformats/multiaddr-to-uri'\nimport { ipfsPathHelp } from '../utils.js'\nimport { isTest } from 'ipfs-utils/src/env.js'\nimport { logger } from '@libp2p/logger'\nimport { Daemon } from 'ipfs-daemon'\n\nconst log = logger('ipfs:cli:daemon')\n\n/**\n * @typedef {object} Argv\n * @property {import('../types').Context} Argv.ctx\n * @property {string} [Argv.initConfig]\n * @property {string[]} [Argv.initProfile]\n * @property {boolean} Argv.enableShardingExperiment\n * @property {boolean} Argv.offline\n * @property {boolean} Argv.enableNamesysPubsub\n * @property {boolean} Argv.enablePreload\n * @property {boolean} Argv.silent\n * @property {boolean} Argv.migrate\n * @property {string} Argv.pass\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'daemon',\n\n  describe: 'Start a long-running daemon process',\n\n  builder (yargs) {\n    yargs\n      .epilog(ipfsPathHelp)\n      .option('init-config', {\n        string: true,\n        desc: 'Path to existing configuration file to be loaded during --init.'\n      })\n      .option('init-profile', {\n        string: true,\n        desc: 'Configuration profiles to apply for --init. See ipfs init --help for more',\n        coerce: (value) => {\n          return (value || '').split(',')\n        }\n      })\n      .option('enable-sharding-experiment', {\n        boolean: true,\n        default: false\n      })\n      .option('offline', {\n        boolean: true,\n        desc: 'Run offline. Do not connect to the rest of the network but provide local API',\n        default: false\n      })\n      .option('enable-namesys-pubsub', {\n        boolean: true,\n        default: false\n      })\n      .option('enable-preload', {\n        boolean: true,\n        default: !isTest // preload by default, unless in test env\n      })\n\n    return yargs\n  },\n\n  async handler ({ ctx: { print, repoPath }, initConfig, silent, migrate, offline, pass, enablePreload, enableNamesysPubsub, enableShardingExperiment, initProfile }) {\n    print('Initializing IPFS daemon...')\n    print(`System version: ${os.arch()}/${os.platform()}`)\n    print(`Node.js version: ${process.versions.node}`)\n\n    let config = {}\n    // read and parse config file\n    if (initConfig) {\n      try {\n        const raw = fs.readFileSync(initConfig, { encoding: 'utf8' })\n        config = JSON.parse(raw)\n      } catch (/** @type {any} */ error) {\n        log(error)\n        throw new Error('Default config couldn\\'t be found or content isn\\'t valid JSON.')\n      }\n    }\n\n    const daemon = new Daemon({\n      config,\n      silent: silent,\n      repo: process.env.IPFS_PATH,\n      repoAutoMigrate: migrate,\n      offline: offline,\n      pass: pass,\n      preload: { enabled: enablePreload },\n      EXPERIMENTAL: {\n        ipnsPubsub: enableNamesysPubsub,\n        sharding: enableShardingExperiment\n      },\n      init: initProfile ? { profiles: initProfile } : undefined\n    })\n\n    try {\n      await daemon.start()\n\n      const version = await daemon._ipfs.version()\n\n      print(`js-ipfs version: ${version.version}`)\n\n      if (daemon._httpApi && daemon._httpApi._apiServers) {\n        daemon._httpApi._apiServers.forEach(apiServer => {\n          print(`HTTP API listening on ${apiServer.info.ma}`)\n        })\n      }\n\n      if (daemon._grpcServer && daemon._grpcServer) {\n        print(`gRPC listening on ${daemon._grpcServer.info.ma}`)\n      }\n\n      if (daemon._httpGateway && daemon._httpGateway._gatewayServers) {\n        daemon._httpGateway._gatewayServers.forEach(gatewayServer => {\n          print(`Gateway (read only) listening on ${gatewayServer.info.ma}`)\n        })\n      }\n\n      if (daemon._httpApi && daemon._httpApi._apiServers) {\n        daemon._httpApi._apiServers.forEach(apiServer => {\n          print(`Web UI available at ${multiaddrToUri(apiServer.info.ma)}/webui`)\n        })\n      }\n    } catch (/** @type {any} */ err) {\n      if (err.code === 'ERR_REPO_NOT_INITIALIZED' || err.message.match(/uninitialized/i)) {\n        err.message = 'no initialized ipfs repo found in ' + repoPath + '\\nplease run: jsipfs init'\n      }\n      throw err\n    }\n\n    print('Daemon is ready')\n\n    const cleanup = async () => {\n      print('Received interrupt signal, shutting down...')\n      await daemon.stop()\n      process.exit(0)\n    }\n\n    // listen for graceful termination\n    process.on('SIGTERM', cleanup)\n    process.on('SIGINT', cleanup)\n    process.on('SIGHUP', cleanup)\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/dag/export.js",
    "content": "import parseDuration from 'parse-duration'\nimport { CID } from 'multiformats/cid'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string} Argv.rootcid\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'export <root cid>',\n\n  describe: 'Streams the DAG beginning at the given root CID as a CAR stream on stdout',\n\n  builder: {\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, rootcid, timeout }) {\n    const options = { timeout }\n    const cid = CID.parse(rootcid)\n\n    const exporter = ipfs.dag.export(cid, options)\n    for await (const chunk of exporter) {\n      print.write(chunk)\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/dag/get.js",
    "content": "import parseDuration from 'parse-duration'\nimport { toCidAndPath } from 'ipfs-core-utils/to-cid-and-path'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\nimport * as dagPB from '@ipld/dag-pb'\nimport * as dagCBOR from '@ipld/dag-cbor'\nimport * as dagJSON from '@ipld/dag-json'\nimport * as raw from 'multiformats/codecs/raw'\n\n/**\n * @template T\n * @typedef {import('multiformats/codecs/interface').BlockCodec<number, T>} BlockCodec\n */\n\nconst codecs = [dagCBOR, dagJSON, dagPB, raw].reduce((/** @type {Record<string, BlockCodec<any>>} */ m, codec) => {\n  m[codec.name] = codec\n  return m\n}, /** @type {Record<string, BlockCodec<any>>} */ {})\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string} Argv.cidpath\n * @property {'dag-json' | 'dag-cbor' | 'dag-pb' | 'raw'} Argv.outputCodec\n * @property {'base16' | 'base64' | 'base58btc'} Argv.dataEnc\n * @property {boolean} Argv.localResolve\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'get <cid path>',\n\n  describe: 'Get a dag node or value from ipfs',\n\n  builder: {\n    'local-resolve': {\n      boolean: true,\n      default: false\n    },\n    'output-codec': {\n      describe: 'Codec to encode data in before displaying',\n      string: true,\n      choices: ['dag-json', 'dag-cbor', 'dag-pb', 'raw'],\n      default: 'dag-json'\n    },\n    'data-enc': {\n      describe: 'String encoding to display raw node data in if using \"raw\" output-codec',\n      string: true,\n      choices: ['base16', 'base64', 'base58btc']\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, cidpath, dataEnc, outputCodec, localResolve, timeout }) {\n    const options = {\n      localResolve,\n      timeout\n    }\n\n    const {\n      cid, path\n    } = toCidAndPath(cidpath)\n\n    let result\n\n    try {\n      result = await ipfs.dag.get(cid, {\n        ...options,\n        path\n      })\n    } catch (/** @type {any} */ err) {\n      return print(`dag get failed: ${err}`)\n    }\n\n    if (options.localResolve) {\n      print('resolving path within the node only')\n      print(`remainder path: ${result.remainderPath || 'n/a'}\\n`)\n    }\n\n    const node = result.value\n\n    if (outputCodec === 'raw') {\n      if (!(node instanceof Uint8Array)) {\n        print('dag get cannot print a non-bytes node as \"raw\"')\n        return\n      }\n      if (dataEnc) {\n        print(uint8ArrayToString(node, dataEnc), false)\n      } else {\n        print.write(node)\n      }\n    } else {\n      const codec = codecs[outputCodec]\n      if (!codec) {\n        print(`unsupported codec \"${outputCodec}\"`)\n        return\n      }\n      const output = codec.encode(node)\n      print(output, false)\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/dag/import.js",
    "content": "import fs from 'fs'\nimport parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string[]} Argv.path\n * @property {boolean} Argv.pinRoots\n * @property {number} Argv.timeout\n * @property {string} Argv.cidBase\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'import [path...]',\n\n  describe: 'Import the contents of one or more CARs from files or stdin',\n\n  builder: {\n    'pin-roots': {\n      boolean: true,\n      default: true,\n      describe: 'Pin optional roots listed in the CAR headers after importing.'\n    },\n    'cid-base': {\n      describe: 'Number base to display CIDs in',\n      string: true,\n      default: 'base58btc'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print, getStdin }, path, pinRoots, timeout, cidBase }) {\n    const handleResult = async (/** @type {import('ipfs-core-types/src/dag').ImportResult} */ { root }) => {\n      const base = await ipfs.bases.getBase(cidBase)\n      print(`pinned root\\t${root.cid.toString(base.encoder)}\\t${root.pinErrorMsg || 'success'}`)\n    }\n\n    const options = { timeout, pinRoots }\n\n    if (path) { // files\n      for await (const result of ipfs.dag.import(fromFiles(print, path), options)) {\n        await handleResult(result)\n      }\n    } else { // stdin\n      print('importing CAR from stdin...')\n      for await (const result of ipfs.dag.import([getStdin()], options)) {\n        await handleResult(result)\n      }\n    }\n  }\n}\n\nexport default command\n\n/**\n * @param {import('../../types').Context[\"print\"]} print\n * @param {string[]} paths\n */\nfunction * fromFiles (print, paths) {\n  for (const path of paths) {\n    print(`importing from ${path}...`)\n    yield fs.createReadStream(path)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/dag/index.js",
    "content": "import dagExport from './export.js'\nimport dagGet from './get.js'\nimport dagImport from './import.js'\nimport dagPut from './put.js'\nimport dagResolve from './resolve.js'\n\n/** @type {import('yargs').CommandModule[]} */\nexport const commands = [\n  dagExport,\n  dagGet,\n  dagImport,\n  dagPut,\n  dagResolve\n]\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/dag/put.js",
    "content": "import * as dagCBOR from '@ipld/dag-cbor'\nimport * as dagPB from '@ipld/dag-pb'\nimport * as dagJSON from '@ipld/dag-json'\nimport * as raw from 'multiformats/codecs/raw'\nimport concat from 'it-concat'\nimport parseDuration from 'parse-duration'\n\n/**\n * @template T\n * @typedef {import('multiformats/codecs/interface').BlockCodec<number, T>} BlockCodec\n */\n\n/**\n * @type {Record<string, BlockCodec<any>>}\n */\nconst codecs = [dagCBOR, dagJSON, dagPB, raw].reduce((/** @type {Record<string, BlockCodec<any>>} */ m, codec) => {\n  m[codec.name] = codec\n  return m\n}, /** @type {Record<string, BlockCodec<any>>} */ {})\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string} Argv.data\n * @property {'dag-cbor' | 'dag-json' | 'dag-pb' | 'raw'} Argv.inputCodec\n * @property {'dag-cbor' | 'dag-json' | 'dag-pb' | 'raw'} Argv.storeCodec\n * @property {import('multiformats/cid').Version} Argv.cidVersion\n * @property {boolean} Argv.pin\n * @property {string} Argv.hashAlg\n * @property {string} Argv.cidBase\n * @property {boolean} Argv.preload\n * @property {boolean} Argv.onlyHash\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'put [data]',\n\n  describe: 'accepts input from a file or stdin and parses it into an object of the specified format',\n\n  builder: {\n    data: {\n      string: true\n    },\n    'store-codec': {\n      string: true,\n      default: 'dag-cbor',\n      describe: 'The codec that the stored object will be encoded with',\n      choices: ['dag-cbor', 'dag-json', 'dag-pb', 'raw']\n    },\n    'input-codec': {\n      string: true,\n      default: 'dag-json',\n      describe: 'The codec that the input object is encoded with',\n      choices: ['dag-cbor', 'dag-json', 'dag-pb', 'raw']\n    },\n    pin: {\n      boolean: true,\n      default: true,\n      describe: 'Pin this object when adding'\n    },\n    'hash-alg': {\n      string: true,\n      alias: 'hash',\n      default: 'sha2-256',\n      describe: 'Hash function to use'\n    },\n    'cid-version': {\n      number: true,\n      describe: 'CID version. Defaults to 0 unless an option that depends on CIDv1 is passed',\n      default: 0\n    },\n    'cid-base': {\n      describe: 'Number base to display CIDs in',\n      string: true,\n      default: 'base58btc'\n    },\n    preload: {\n      boolean: true,\n      default: true,\n      describe: 'Preload this object when adding'\n    },\n    'only-hash': {\n      boolean: true,\n      default: false,\n      describe: 'Only hash the content, do not write to the underlying block store'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print, getStdin }, data, inputCodec, storeCodec, pin, hashAlg, cidVersion, cidBase, preload, onlyHash, timeout }) {\n    if (!codecs[inputCodec]) {\n      throw new Error(`Unknown input-codec ${inputCodec}`)\n    }\n\n    if (storeCodec !== 'dag-pb') {\n      cidVersion = 1\n    }\n\n    /** @type {Uint8Array} */\n    let source\n\n    if (!data) {\n      // pipe from stdin\n      source = (await concat(getStdin(), { type: 'buffer' })).subarray()\n    } else {\n      source = Buffer.from(data)\n    }\n\n    const node = codecs[inputCodec].decode(source)\n\n    const cid = await ipfs.dag.put(node, {\n      storeCodec,\n      hashAlg,\n      version: cidVersion,\n      onlyHash,\n      preload,\n      pin,\n      timeout\n    })\n    const base = await ipfs.bases.getBase(cidBase)\n\n    print(cid.toString(base.encoder))\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/dag/resolve.js",
    "content": "import parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string} Argv.ref\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'resolve <ref>',\n\n  describe: 'fetches a dag node from ipfs, prints its address and remaining path',\n\n  builder: {\n    ref: {\n      string: true\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, ref, timeout }) {\n    const options = {\n      timeout\n    }\n\n    try {\n      let {\n        cid: lastCid\n      } = await ipfs.dag.resolve(ref, options)\n\n      if (!lastCid) {\n        if (ref.startsWith('/ipfs/')) {\n          ref = ref.substring(6)\n        }\n\n        // @ts-expect-error we will toString this so it doesn't matter\n        lastCid = ref.split('/').shift()\n      }\n\n      print(lastCid.toString())\n    } catch (/** @type {any} */ err) {\n      return print(`dag get resolve: ${err}`)\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/dag.js",
    "content": "import { commands } from './dag/index.js'\n\n/** @type {import('yargs').CommandModule} */\nconst command = {\n  command: 'dag <command>',\n\n  describe: 'Interact with ipld dag objects',\n\n  builder (yargs) {\n    commands.forEach(command => {\n      yargs.command(command)\n    })\n\n    return yargs\n  },\n\n  handler () {\n\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/dht/find-peer.js",
    "content": "import parseDuration from 'parse-duration'\nimport { coercePeerId } from '../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {import('@libp2p/interface-peer-id').PeerId} Argv.peerId\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'findpeer <peerId>',\n\n  describe: 'Find the multiaddresses associated with a Peer ID',\n\n  builder: {\n    peerId: {\n      string: true,\n      coerce: coercePeerId\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, peerId, timeout }) {\n    for await (const event of ipfs.dht.findPeer(peerId, {\n      timeout\n    })) {\n      if (event.name === 'FINAL_PEER') {\n        event.peer.multiaddrs.forEach(addr => print(`${addr}`))\n      }\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/dht/find-providers.js",
    "content": "import parseDuration from 'parse-duration'\nimport { coerceCID } from '../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {import('multiformats/cid').CID} Argv.key\n * @property {number} Argv.numProviders\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'findprovs <key>',\n\n  describe: 'Find peers that can provide a specific value, given a key',\n\n  builder: {\n    key: {\n      string: true,\n      coerce: coerceCID\n    },\n    'num-providers': {\n      alias: 'n',\n      describe: 'The number of providers to find. Default: 20',\n      default: 20,\n      number: true\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, key, numProviders, timeout }) {\n    const providers = new Set()\n\n    for await (const event of ipfs.dht.findProvs(key, {\n      timeout\n    })) {\n      if (event.name === 'PROVIDER') {\n        event.providers.forEach(peerData => {\n          if (providers.has(peerData.id)) {\n            return\n          }\n\n          providers.add(peerData.id)\n          print(peerData.id.toString())\n        })\n\n        if (providers.size >= numProviders) {\n          break\n        }\n      }\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/dht/get.js",
    "content": "import parseDuration from 'parse-duration'\nimport { coerceCID } from '../../utils.js'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {import('multiformats/cid').CID} Argv.key\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'get <key>',\n\n  describe: 'Given a key, query the routing system for its best value',\n\n  builder: {\n    key: {\n      string: true,\n      coerce: coerceCID\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, key, timeout }) {\n    for await (const event of await ipfs.dht.get(key.bytes, {\n      timeout\n    })) {\n      if (event.name === 'VALUE') {\n        print(uint8ArrayToString(event.value, 'base58btc'))\n      }\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/dht/index.js",
    "content": "import dhtFindPeer from './find-peer.js'\nimport dhtFindProviders from './find-providers.js'\nimport dhtGet from './get.js'\nimport dhtProvide from './provide.js'\nimport dhtPut from './put.js'\nimport dhtQuery from './query.js'\n\n/** @type {import('yargs').CommandModule[]} */\nexport const commands = [\n  dhtFindPeer,\n  dhtFindProviders,\n  dhtGet,\n  dhtProvide,\n  dhtPut,\n  dhtQuery\n]\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/dht/provide.js",
    "content": "import parseDuration from 'parse-duration'\nimport { coerceCID } from '../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {import('multiformats/cid').CID} Argv.key\n * @property {boolean} Argv.recursive\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'provide <key>',\n\n  describe: 'Announce to the network that you are providing given values',\n\n  builder: {\n    key: {\n      string: true,\n      coerce: coerceCID\n    },\n    recursive: {\n      alias: 'r',\n      describe: 'Recursively provide entire graph',\n      default: false,\n      boolean: true\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs }, key, recursive, timeout }) {\n    await ipfs.dht.provide(key, {\n      recursive,\n      timeout\n    })\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/dht/put.js",
    "content": "import parseDuration from 'parse-duration'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string} Argv.key\n * @property {string} Argv.value\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'put <key> <value>',\n\n  describe: 'Write a key/value pair to the routing system',\n\n  builder: {\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs }, key, value, timeout }) {\n    await ipfs.dht.put(uint8ArrayFromString(key), uint8ArrayFromString(value), {\n      timeout\n    })\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/dht/query.js",
    "content": "import parseDuration from 'parse-duration'\nimport { coercePeerId } from '../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {import('@libp2p/interface-peer-id').PeerId} Argv.peerId\n * @property {number} Argv.timeout\n * @property {number} Argv.count\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'query <peerId>',\n\n  describe: 'Find the closest Peer IDs to a given Peer ID by querying the DHT',\n\n  builder: {\n    peerId: {\n      string: true,\n      coerce: coercePeerId\n    },\n    count: {\n      number: true,\n      default: 20\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, peerId, timeout, count }) {\n    const seen = new Set()\n\n    for await (const event of ipfs.dht.query(peerId, {\n      timeout\n    })) {\n      if (event.name === 'PEER_RESPONSE') {\n        for (const peerData of event.closer) {\n          const peerId = peerData.id.toString()\n\n          if (seen.has(peerId)) {\n            continue\n          }\n\n          print(peerId)\n          seen.add(peerId)\n\n          if (seen.size === count) {\n            return\n          }\n        }\n      }\n\n      if (event.name === 'FINAL_PEER') {\n        return\n      }\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/dht.js",
    "content": "import { commands } from './dht/index.js'\n\n/** @type {import('yargs').CommandModule} */\nconst command = {\n  command: 'dht <command>',\n\n  describe: 'Issue commands directly through the DHT',\n\n  builder (yargs) {\n    commands.forEach(command => {\n      yargs.command(command)\n    })\n\n    return yargs\n  },\n\n  handler () {\n\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/dns.js",
    "content": "import parseDuration from 'parse-duration'\nimport {\n  stripControlCharacters\n} from '../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../types').Context} Argv.ctx\n * @property {string} Argv.domain\n * @property {boolean} Argv.recursive\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'dns <domain>',\n\n  describe: 'Resolve DNS links',\n\n  builder: {\n    recursive: {\n      boolean: true,\n      default: true,\n      alias: 'r',\n      desc: 'Resolve until the result is not a DNS link'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, domain, recursive, timeout }) {\n    const path = await ipfs.dns(domain, { recursive, timeout })\n    print(stripControlCharacters(path))\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/files/chmod.js",
    "content": "import {\n  asBoolean\n} from '../../utils.js'\nimport parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string} Argv.path\n * @property {number} Argv.mode\n * @property {boolean} Argv.recursive\n * @property {string} Argv.hashAlg\n * @property {boolean} Argv.flush\n * @property {number} Argv.shardSplitThreshold\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'chmod [mode] [path]',\n\n  describe: 'Change file modes',\n\n  builder: {\n    path: {\n      string: true,\n      describe: 'The MFS path to change the mode of'\n    },\n    mode: {\n      string: true,\n      describe: 'The mode to use'\n    },\n    recursive: {\n      alias: 'r',\n      boolean: true,\n      default: false,\n      coerce: asBoolean,\n      describe: 'Whether to change modes recursively'\n    },\n    'hash-alg': {\n      alias: 'h',\n      string: true,\n      default: 'sha2-256',\n      describe: 'Hash function to use. Will set CID version to 1 if used'\n    },\n    flush: {\n      alias: 'f',\n      boolean: true,\n      default: true,\n      coerce: asBoolean,\n      describe: 'Flush the changes to disk immediately'\n    },\n    'shard-split-threshold': {\n      number: true,\n      default: 1000,\n      describe: 'If a directory has more links than this, it will be transformed into a hamt-sharded-directory'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({\n    ctx: { ipfs },\n    path,\n    mode,\n    recursive,\n    hashAlg,\n    flush,\n    shardSplitThreshold,\n    timeout\n  }) {\n    await ipfs.files.chmod(path, mode, {\n      recursive,\n      hashAlg,\n      flush,\n      shardSplitThreshold,\n      timeout\n    })\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/files/cp.js",
    "content": "import {\n  asBoolean\n} from '../../utils.js'\nimport parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string} Argv.source\n * @property {string} Argv.dest\n * @property {boolean} Argv.parents\n * @property {string} Argv.hashAlg\n * @property {boolean} Argv.flush\n * @property {number} Argv.shardSplitThreshold\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'cp <source> <dest>',\n\n  describe: 'Copy files between locations in the mfs',\n\n  builder: {\n    parents: {\n      alias: 'p',\n      boolean: true,\n      default: false,\n      coerce: asBoolean,\n      describe: 'Create any non-existent intermediate directories'\n    },\n    'hash-alg': {\n      alias: 'h',\n      string: true,\n      default: 'sha2-256',\n      describe: 'Hash function to use. Will set CID version to 1 if used'\n    },\n    flush: {\n      alias: 'f',\n      boolean: true,\n      default: true,\n      coerce: asBoolean,\n      describe: 'Flush the changes to disk immediately'\n    },\n    'shard-split-threshold': {\n      number: true,\n      default: 1000,\n      describe: 'If a directory has more links than this, it will be transformed into a hamt-sharded-directory'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({\n    ctx: { ipfs },\n    source,\n    dest,\n    parents,\n    flush,\n    hashAlg,\n    shardSplitThreshold,\n    timeout\n  }) {\n    await ipfs.files.cp(source, dest, {\n      parents,\n      flush,\n      hashAlg,\n      shardSplitThreshold,\n      timeout\n    })\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/files/flush.js",
    "content": "import parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string} Argv.path\n * @property {string} Argv.cidBase\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'flush [path]',\n\n  describe: ' Flush a given path\\'s data to disk',\n\n  builder: {\n    'cid-base': {\n      describe: 'CID base to use',\n      default: 'base58btc'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({\n    ctx: { ipfs, print },\n    path,\n    cidBase,\n    timeout\n  }) {\n    const cid = await ipfs.files.flush(path || '/', {\n      timeout\n    })\n\n    const base = await ipfs.bases.getBase(cidBase)\n\n    print(JSON.stringify({\n      Cid: cid.toString(base.encoder)\n    }))\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/files/index.js",
    "content": "import filesChmod from './chmod.js'\nimport filesCp from './cp.js'\nimport filesFlush from './flush.js'\nimport filesLs from './ls.js'\nimport filesMkdir from './mkdir.js'\nimport filesMv from './mv.js'\nimport filesRead from './read.js'\nimport filesRm from './rm.js'\nimport filesStat from './stat.js'\nimport filesTouch from './touch.js'\nimport filesWrite from './write.js'\n\n/** @type {import('yargs').CommandModule[]} */\nexport const commands = [\n  filesChmod,\n  filesCp,\n  filesFlush,\n  filesLs,\n  filesMkdir,\n  filesMv,\n  filesRead,\n  filesRm,\n  filesStat,\n  filesTouch,\n  filesWrite\n]\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/files/ls.js",
    "content": "import {\n  asBoolean,\n  stripControlCharacters\n} from '../../utils.js'\nimport { formatMode } from 'ipfs-core-utils/files/format-mode'\nimport { formatMtime } from 'ipfs-core-utils/files/format-mtime'\nimport parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string} Argv.path\n * @property {boolean} Argv.long\n * @property {string} Argv.cidBase\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'ls [path]',\n\n  describe: 'List mfs directories',\n\n  builder: {\n    long: {\n      alias: 'l',\n      boolean: true,\n      default: false,\n      coerce: asBoolean,\n      describe: 'Use long listing format.'\n    },\n    'cid-base': {\n      describe: 'CID base to use',\n      default: 'base58btc'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({\n    ctx: { ipfs, print },\n    path,\n    long,\n    cidBase,\n    timeout\n  }) {\n    const base = await ipfs.bases.getBase(cidBase)\n\n    /**\n     * @param {import('ipfs-core-types/src/files').MFSEntry} file\n     */\n    const printListing = file => {\n      const name = stripControlCharacters(file.name)\n\n      if (long) {\n        print(`${file.mode ? formatMode(file.mode, file.type === 'directory') : ''}\\t${file.mtime ? formatMtime(file.mtime) : ''}\\t${name}\\t${file.cid.toString(base.encoder)}\\t${file.size}`)\n      } else {\n        print(name)\n      }\n    }\n\n    for await (const file of ipfs.files.ls(path || '/', {\n      timeout\n    })) {\n      printListing(file)\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/files/mkdir.js",
    "content": "import {\n  asBoolean,\n  asOctal,\n  asMtimeFromSeconds,\n  coerceMtime,\n  coerceMtimeNsecs\n} from '../../utils.js'\nimport parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string} Argv.path\n * @property {boolean} Argv.parents\n * @property {import('multiformats/cid').Version} Argv.cidVersion\n * @property {string} Argv.hashAlg\n * @property {boolean} Argv.flush\n * @property {number} Argv.shardSplitThreshold\n * @property {number} Argv.mode\n * @property {number} Argv.mtime\n * @property {number} Argv.mtimeNsecs\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'mkdir <path>',\n\n  describe: 'Make mfs directories',\n\n  builder: {\n    parents: {\n      alias: 'p',\n      boolean: true,\n      default: false,\n      coerce: asBoolean,\n      describe: 'No error if existing, make parent directories as needed.'\n    },\n    'cid-version': {\n      alias: ['cid-ver'],\n      number: true,\n      default: 0,\n      describe: 'Cid version to use. (experimental).'\n    },\n    'hash-alg': {\n      alias: 'h',\n      string: true,\n      default: 'sha2-256',\n      describe: 'Hash function to use. Will set CID version to 1 if used'\n    },\n    flush: {\n      alias: 'f',\n      boolean: true,\n      default: true,\n      coerce: asBoolean,\n      describe: 'Flush the changes to disk immediately'\n    },\n    'shard-split-threshold': {\n      number: true,\n      default: 1000,\n      describe: 'If a directory has more links than this, it will be transformed into a hamt-sharded-directory'\n    },\n    mode: {\n      number: true,\n      coerce: asOctal,\n      describe: 'Mode to apply to the new directory'\n    },\n    mtime: {\n      number: true,\n      coerce: coerceMtime,\n      describe: 'Modification time in seconds before or since the Unix Epoch to apply to created UnixFS entries'\n    },\n    'mtime-nsecs': {\n      number: true,\n      coerce: coerceMtimeNsecs,\n      describe: 'Modification time fraction in nanoseconds'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({\n    ctx: { ipfs },\n    path,\n    parents,\n    cidVersion,\n    hashAlg,\n    flush,\n    shardSplitThreshold,\n    mode,\n    mtime,\n    mtimeNsecs,\n    timeout\n  }) {\n    await ipfs.files.mkdir(path, {\n      parents,\n      cidVersion,\n      hashAlg,\n      flush,\n      shardSplitThreshold,\n      mode,\n      mtime: asMtimeFromSeconds(mtime, mtimeNsecs),\n      timeout\n    })\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/files/mv.js",
    "content": "import {\n  asBoolean\n} from '../../utils.js'\nimport parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string} Argv.source\n * @property {string} Argv.dest\n * @property {boolean} Argv.parents\n * @property {import('multiformats/cid').Version} Argv.cidVersion\n * @property {string} Argv.hashAlg\n * @property {boolean} Argv.flush\n * @property {number} Argv.shardSplitThreshold\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'mv <source> <dest>',\n\n  describe: 'Move mfs files around',\n\n  builder: {\n    parents: {\n      alias: 'p',\n      boolean: true,\n      default: false,\n      coerce: asBoolean,\n      describe: 'Create any non-existent intermediate directories'\n    },\n    'cid-version': {\n      alias: ['cid-ver'],\n      number: true,\n      default: 0,\n      describe: 'Cid version to use. (experimental).'\n    },\n    'hash-alg': {\n      alias: 'h',\n      string: true,\n      default: 'sha2-256',\n      describe: 'Hash function to use. Will set CID version to 1 if used'\n    },\n    flush: {\n      alias: 'f',\n      boolean: true,\n      default: true,\n      coerce: asBoolean,\n      describe: 'Flush the changes to disk immediately'\n    },\n    'shard-split-threshold': {\n      number: true,\n      default: 1000,\n      describe: 'If a directory has more links than this, it will be transformed into a hamt-sharded-directory'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({\n    ctx: { ipfs },\n    source,\n    dest,\n    parents,\n    cidVersion,\n    hashAlg,\n    flush,\n    shardSplitThreshold,\n    timeout\n  }) {\n    await ipfs.files.mv(source, dest, {\n      parents,\n      cidVersion,\n      hashAlg,\n      flush,\n      shardSplitThreshold,\n      timeout\n    })\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/files/read.js",
    "content": "import parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string} Argv.path\n * @property {number} Argv.offset\n * @property {number} Argv.length\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'read <path>',\n\n  describe: 'Read an mfs file',\n\n  builder: {\n    offset: {\n      alias: 'o',\n      number: true,\n      describe: 'Start writing at this offset'\n    },\n    length: {\n      alias: 'l',\n      number: true,\n      describe: 'Write only this number of bytes'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({\n    ctx: { ipfs, print },\n    path,\n    offset,\n    length,\n    timeout\n  }) {\n    for await (const buffer of ipfs.files.read(path, {\n      offset,\n      length,\n      timeout\n    })) {\n      print(buffer, false)\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/files/rm.js",
    "content": "import {\n  asBoolean\n} from '../../utils.js'\nimport parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string} Argv.path\n * @property {boolean} Argv.recursive\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'rm <path>',\n\n  describe: 'Remove an mfs file or directory',\n\n  builder: {\n    recursive: {\n      alias: 'r',\n      boolean: true,\n      default: false,\n      coerce: asBoolean,\n      describe: 'Remove directories recursively'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({\n    ctx: { ipfs },\n    path,\n    recursive,\n    timeout\n  }) {\n    await ipfs.files.rm(path, {\n      recursive,\n      timeout\n    })\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/files/stat.js",
    "content": "import {\n  asBoolean\n} from '../../utils.js'\nimport { formatMode } from 'ipfs-core-utils/files/format-mode'\nimport { formatMtime } from 'ipfs-core-utils/files/format-mtime'\nimport parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string} Argv.path\n * @property {string} Argv.format\n * @property {boolean} Argv.hash\n * @property {boolean} Argv.size\n * @property {boolean} Argv.withLocal\n * @property {string} Argv.cidBase\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'stat [path]',\n\n  describe: 'Display file/directory status',\n\n  builder: {\n    format: {\n      alias: 'f',\n      string: true,\n      default: `<hash>\nSize: <size>\nCumulativeSize: <cumulsize>\nChildBlocks: <childs>\nType: <type>\nMode: <mode>\nMtime: <mtime>`,\n      describe: 'Print statistics in given format. Allowed tokens: <hash> <size> <cumulsize> <type> <childs> <mode> <mtime>. Conflicts with other format options.'\n    },\n    hash: {\n      alias: 'h',\n      boolean: true,\n      default: false,\n      coerce: asBoolean,\n      describe: 'Print only hash. Implies \\'--format=<hash>\\'. Conflicts with other format options.'\n    },\n    size: {\n      alias: 's',\n      boolean: true,\n      default: false,\n      coerce: asBoolean,\n      describe: 'Print only size. Implies \\'--format=<cumulsize>\\'. Conflicts with other format options.'\n    },\n    'with-local': {\n      alias: 'l',\n      boolean: true,\n      default: false,\n      coerce: asBoolean,\n      describe: 'Compute the amount of the dag that is local, and if possible the total size'\n    },\n    'cid-base': {\n      describe: 'CID base to use',\n      default: 'base58btc'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({\n    ctx: { ipfs, print },\n    path,\n    format,\n    hash,\n    size,\n    withLocal,\n    cidBase,\n    timeout\n  }) {\n    const stats = await ipfs.files.stat(path, {\n      withLocal,\n      timeout\n    })\n    const base = await ipfs.bases.getBase(cidBase)\n\n    if (hash) {\n      return print(stats.cid.toString(base.encoder))\n    }\n\n    if (size) {\n      return print(`${stats.size}`)\n    }\n\n    print(format\n      .replace('<hash>', stats.cid.toString(base.encoder))\n      .replace('<size>', `${stats.size}`)\n      .replace('<cumulsize>', `${stats.cumulativeSize}`)\n      .replace('<childs>', `${stats.blocks}`)\n      .replace('<type>', stats.type)\n      .replace('<mode>', stats.mode ? formatMode(stats.mode, stats.type === 'directory') : '')\n      .replace('<mtime>', stats.mtime ? formatMtime(stats.mtime) : '')\n    )\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/files/touch.js",
    "content": "import {\n  asBoolean,\n  asMtimeFromSeconds,\n  coerceMtime,\n  coerceMtimeNsecs\n} from '../../utils.js'\nimport parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string} Argv.path\n * @property {boolean} Argv.flush\n * @property {import('multiformats/cid').Version} Argv.cidVersion\n * @property {string} Argv.hashAlg\n * @property {number} Argv.shardSplitThreshold\n * @property {number} Argv.mtime\n * @property {number} Argv.mtimeNsecs\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'touch [path]',\n\n  describe: 'change file modification times',\n\n  builder: {\n    mtime: {\n      number: true,\n      alias: 'm',\n      coerce: coerceMtime,\n      describe: 'Modification time in seconds before or since the Unix Epoch to apply to created UnixFS entries'\n    },\n    'mtime-nsecs': {\n      number: true,\n      coerce: coerceMtimeNsecs,\n      describe: 'Modification time fraction in nanoseconds'\n    },\n    flush: {\n      alias: 'f',\n      boolean: true,\n      default: true,\n      coerce: asBoolean,\n      describe: 'Flush the changes to disk immediately'\n    },\n    'cid-version': {\n      alias: ['cid-ver'],\n      number: true,\n      default: 0,\n      describe: 'Cid version to use. (experimental).'\n    },\n    'hash-alg': {\n      alias: 'h',\n      string: true,\n      default: 'sha2-256',\n      describe: 'Hash function to use. Will set CID version to 1 if used'\n    },\n    'shard-split-threshold': {\n      number: true,\n      default: 1000,\n      describe: 'If a directory has more links than this, it will be transformed into a hamt-sharded-directory'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({\n    ctx: { ipfs },\n    path,\n    flush,\n    cidVersion,\n    hashAlg,\n    shardSplitThreshold,\n    mtime,\n    mtimeNsecs,\n    timeout\n  }) {\n    await ipfs.files.touch(path, {\n      mtime: asMtimeFromSeconds(mtime, mtimeNsecs),\n      flush,\n      cidVersion,\n      hashAlg,\n      shardSplitThreshold,\n      timeout\n    })\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/files/write.js",
    "content": "import {\n  asBoolean,\n  asOctal,\n  asMtimeFromSeconds,\n  coerceMtime,\n  coerceMtimeNsecs\n} from '../../utils.js'\nimport parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string} Argv.path\n * @property {number} Argv.offset\n * @property {number} Argv.length\n * @property {boolean} Argv.create\n * @property {boolean} Argv.truncate\n * @property {boolean} Argv.rawLeaves\n * @property {boolean} Argv.reduceSingleLeafToSelf\n * @property {import('multiformats/cid').Version} Argv.cidVersion\n * @property {string} Argv.hashAlg\n * @property {boolean} Argv.parents\n * @property {'trickle' | 'balanced'} Argv.strategy\n * @property {boolean} Argv.flush\n * @property {number} Argv.shardSplitThreshold\n * @property {number} Argv.mode\n * @property {number} Argv.mtime\n * @property {number} Argv.mtimeNsecs\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'write <path>',\n\n  describe: 'Write to mfs files',\n\n  builder: {\n    parents: {\n      alias: 'p',\n      boolean: true,\n      default: false,\n      describe: 'Create any non-existent intermediate directories'\n    },\n    create: {\n      alias: 'e',\n      boolean: true,\n      default: false,\n      coerce: asBoolean,\n      describe: 'Create the file if it does not exist'\n    },\n    offset: {\n      alias: 'o',\n      number: true,\n      describe: 'Start writing at this offset'\n    },\n    length: {\n      alias: 'l',\n      number: true,\n      describe: 'Write only this number of bytes'\n    },\n    truncate: {\n      alias: 't',\n      boolean: true,\n      default: false,\n      coerce: asBoolean,\n      describe: 'Truncate the file after writing'\n    },\n    'raw-leaves': {\n      alias: 'r',\n      boolean: true,\n      default: false,\n      coerce: asBoolean,\n      describe: 'Whether to write leaf nodes as raw UnixFS nodes'\n    },\n    'reduce-single-leaf-to-self': {\n      boolean: true,\n      default: false,\n      coerce: asBoolean,\n      describe: 'If a file can fit in one DAGNode, only use one DAGNode instead of storing the data in a child'\n    },\n    flush: {\n      alias: 'f',\n      boolean: true,\n      default: true,\n      coerce: asBoolean,\n      describe: 'Flush the changes to disk immediately'\n    },\n    strategy: {\n      alias: 's',\n      string: true,\n      default: 'balanced'\n    },\n    'cid-version': {\n      alias: ['cid-ver'],\n      number: true,\n      default: 0,\n      describe: 'Cid version to use'\n    },\n    'hash-alg': {\n      alias: 'h',\n      string: true,\n      default: 'sha2-256'\n    },\n    'shard-split-threshold': {\n      number: true,\n      default: 1000,\n      describe: 'If a directory has more links than this, it will be transformed into a hamt-sharded-directory'\n    },\n    mode: {\n      number: true,\n      coerce: asOctal,\n      describe: 'The mode to use'\n    },\n    mtime: {\n      number: true,\n      coerce: coerceMtime,\n      describe: 'Modification time in seconds before or since the Unix Epoch to apply to created UnixFS entries'\n    },\n    'mtime-nsecs': {\n      number: true,\n      coerce: coerceMtimeNsecs,\n      describe: 'Modification time fraction in nanoseconds'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({\n    ctx: { ipfs, getStdin },\n    path,\n    offset,\n    length,\n    create,\n    truncate,\n    rawLeaves,\n    reduceSingleLeafToSelf,\n    cidVersion,\n    hashAlg,\n    parents,\n    strategy,\n    flush,\n    shardSplitThreshold,\n    mode,\n    mtime,\n    mtimeNsecs,\n    timeout\n  }) {\n    await ipfs.files.write(path, getStdin(), {\n      offset,\n      length,\n      create,\n      truncate,\n      rawLeaves,\n      reduceSingleLeafToSelf,\n      cidVersion,\n      hashAlg,\n      parents,\n      strategy,\n      flush,\n      shardSplitThreshold,\n      mode,\n      mtime: asMtimeFromSeconds(mtime, mtimeNsecs),\n      timeout\n    })\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/files.js",
    "content": "import { commands } from './files/index.js'\n\n/** @type {import('yargs').CommandModule} */\nconst command = {\n  command: 'files <command>',\n\n  describe: 'Operations over mfs files (ls, mkdir, rm, etc)',\n\n  builder (yargs) {\n    commands.forEach(command => {\n      yargs.command(command)\n    })\n\n    return yargs\n  },\n\n  handler ({ ctx: { print } }) {\n    print('Type `jsipfs files --help` for more instructions')\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/get.js",
    "content": "import fs from 'fs'\nimport path from 'path'\n// @ts-expect-error no types\nimport toIterable from 'stream-to-it'\nimport { pipe } from 'it-pipe'\nimport parseDuration from 'parse-duration'\nimport {\n  stripControlCharacters\n} from '../utils.js'\nimport { extract } from 'it-tar'\n\n/**\n * @typedef {object} Argv\n * @property {import('../types').Context} Argv.ctx\n * @property {string} Argv.ipfsPath\n * @property {string} Argv.output\n * @property {boolean} Argv.force\n * @property {number} Argv.timeout\n * @property {boolean} Argv.archive\n * @property {boolean} Argv.compress\n * @property {-1 | 0 | 1 | 2 | 3 | 4 | 5 | 6| 7 | 8| 9} Argv.compressionLevel\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'get <ipfsPath>',\n\n  describe: 'Download IPFS objects',\n\n  builder: {\n    output: {\n      alias: 'o',\n      string: true,\n      default: process.cwd()\n    },\n    force: {\n      alias: 'f',\n      boolean: true,\n      default: false\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    },\n    archive: {\n      alias: 'a',\n      boolean: true,\n      desc: 'Output a TAR archive'\n    },\n    compress: {\n      alias: 'C',\n      boolean: true,\n      desc: 'Compress the output with GZIP compression'\n    },\n    compressionLevel: {\n      alias: ['l', 'compression-level'],\n      number: true,\n      desc: 'The level of compression (-1-9)',\n      default: 6\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, ipfsPath, output, force, timeout, archive, compress, compressionLevel }) {\n    print(`Saving file(s) ${stripControlCharacters(ipfsPath)}`)\n\n    if (output.substring(0, output.length) !== output && !force) {\n      throw new Error(`File prefix invalid, would write to files outside of ${output}, pass --force to override`)\n    }\n\n    if (archive || compress) {\n      if (output === process.cwd()) {\n        output = path.join(output, ipfsPath)\n      }\n\n      await fs.promises.mkdir(path.dirname(output), { recursive: true })\n      await pipe(\n        ipfs.get(ipfsPath, {\n          timeout,\n          archive,\n          compress,\n          compressionLevel\n        }),\n        toIterable.sink(fs.createWriteStream(output))\n      )\n\n      return\n    }\n\n    /**\n     * @type {any[]}\n     */\n    await pipe(\n      ipfs.get(ipfsPath, {\n        timeout,\n        archive,\n        compress,\n        compressionLevel\n      }),\n      extract(),\n      async function extractTarball (source) {\n        for await (const { header, body } of source) {\n          const outputPath = path.join(output, header.name)\n\n          if (outputPath.substring(0, output.length) !== output && !force) {\n            throw new Error(`File prefix invalid, would write to files outside of ${output}, pass --force to override`)\n          }\n\n          if (header.type === 'file') {\n            await fs.promises.mkdir(path.dirname(outputPath), { recursive: true })\n            await pipe(\n              body,\n              toIterable.sink(fs.createWriteStream(outputPath))\n            )\n          } else if (header.type === 'directory') {\n            await fs.promises.mkdir(outputPath, { recursive: true })\n          } else {\n            throw new Error(`Unknown tar entry type ${header.type}`)\n          }\n\n          await fs.promises.chmod(outputPath, header.mode)\n          await fs.promises.utimes(outputPath, header.mtime, header.mtime)\n        }\n      }\n    )\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/id.js",
    "content": "import parseDuration from 'parse-duration'\nimport { coercePeerId } from '../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../types').Context} Argv.ctx\n * @property {string} Argv.format\n * @property {number} Argv.timeout\n * @property {import('@libp2p/interface-peer-id').PeerId} [Argv.peerId]\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'id [peerId]',\n\n  describe: 'Shows IPFS Node ID info',\n\n  builder: {\n    peerid: {\n      string: true,\n      describe: 'Peer.ID of node to look up',\n      coerce: coercePeerId\n    },\n    format: {\n      alias: 'f',\n      string: true,\n      describe: 'Print Node ID info in the given format. Allowed tokens: <id> <aver> <pver> <pubkey> <addrs> <protocols>'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, format, timeout, peerId }) {\n    const id = await ipfs.id({\n      timeout,\n      peerId\n    })\n\n    if (format) {\n      print(format\n        .replace('<id>', id.id.toString())\n        .replace('<aver>', id.agentVersion)\n        .replace('<pver>', id.protocolVersion)\n        .replace('<pubkey>', id.publicKey)\n        .replace('<addrs>', (id.addresses || []).map(addr => addr.toString()).join('\\n'))\n        .replace('<protocols>', (id.protocols || []).map(protocol => protocol.toString()).join('\\n'))\n      )\n\n      return\n    }\n\n    print(JSON.stringify(id, null, 2))\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/index.js",
    "content": "import add from './add.js'\nimport bitswap from './bitswap.js'\nimport block from './block.js'\nimport bootstrap from './bootstrap.js'\nimport cat from './cat.js'\nimport cid from './cid.js'\nimport config from './config.js'\nimport daemon from './daemon.js'\nimport dag from './dag.js'\nimport dht from './dht.js'\nimport dns from './dns.js'\nimport files from './files.js'\nimport get from './get.js'\nimport id from './id.js'\nimport init from './init.js'\nimport key from './key.js'\nimport ls from './ls.js'\nimport name from './name.js'\nimport object from './object.js'\nimport pin from './pin.js'\nimport ping from './ping.js'\nimport pubsub from './pubsub.js'\nimport refsLocal from './refs-local.js'\nimport refs from './refs.js'\nimport repo from './repo.js'\nimport resolve from './resolve.js'\nimport shutdown from './shutdown.js'\nimport stats from './stats.js'\nimport swarm from './swarm.js'\nimport version from './version.js'\n\n/** @type {import('yargs').CommandModule[]} */\nexport const commandList = [\n  add,\n  bitswap,\n  block,\n  bootstrap,\n  cat,\n  cid,\n  config,\n  daemon,\n  dag,\n  dht,\n  dns,\n  files,\n  get,\n  id,\n  init,\n  key,\n  ls,\n  name,\n  object,\n  pin,\n  ping,\n  pubsub,\n  refsLocal,\n  refs,\n  repo,\n  resolve,\n  shutdown,\n  stats,\n  swarm,\n  version\n]\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/init.js",
    "content": "import fs from 'fs'\nimport { logger } from '@libp2p/logger'\nimport { ipfsPathHelp } from '../utils.js'\nimport * as IPFS from 'ipfs-core'\n\nconst log = logger('ipfs:cli:init')\n\n/** @type {Record<string, import('@libp2p/crypto/keys').KeyTypes>} */\nconst keyTypes = {\n  ed25519: 'Ed25519',\n  rsa: 'RSA'\n}\n\n/**\n * @typedef {object} Argv\n * @property {import('../types').Context} Argv.ctx\n * @property {string} Argv.defaultConfig\n * @property {'rsa' | 'ed25519' | 'secp256k1'} Argv.algorithm\n * @property {number} Argv.bits\n * @property {boolean} Argv.emptyRepo\n * @property {string} Argv.privateKey\n * @property {string[]} Argv.profile\n * @property {string} Argv.pass\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'init [defaultConfig]',\n  describe: 'Initialize a local IPFS node. If you are going to run IPFS in a server environment, you may want to initialize it using the \\'server\\' profile. For the list of available profiles run `jsipfs config profile ls`. ' + ipfsPathHelp,\n\n  builder: {\n    defaultConfig: {\n      describe: 'Node config, this should be a path to a file or JSON and will be merged with the default config. See https://github.com/ipfs/js-ipfs#optionsconfig',\n      string: true\n    },\n    algorithm: {\n      string: true,\n      choices: Object.keys(keyTypes),\n      alias: 'a',\n      default: 'ed25519',\n      describe: 'Cryptographic algorithm to use for key generation'\n    },\n    bits: {\n      number: true,\n      alias: 'b',\n      default: '2048',\n      describe: 'Number of bits to use if the generated private key is RSA (defaults to 2048)',\n      coerce: Number\n    },\n    emptyRepo: {\n      alias: 'e',\n      boolean: true,\n      describe: \"Don't add and pin help files to the local storage\"\n    },\n    privateKey: {\n      alias: 'k',\n      string: true,\n      describe: 'Pre-generated private key to use for the repo'\n    },\n    profile: {\n      alias: 'p',\n      string: true,\n      describe: 'Apply profile settings to config. Multiple profiles can be separated by \\',\\'',\n      coerce: (value) => {\n        return (value || '').split(',')\n      }\n    }\n  },\n\n  async handler ({ ctx: { print, repoPath }, defaultConfig, algorithm, bits, privateKey, emptyRepo, profile, pass }) {\n    let config = {}\n    // read and parse config file\n    if (defaultConfig) {\n      try {\n        const raw = fs.readFileSync(defaultConfig, { encoding: 'utf8' })\n        config = JSON.parse(raw)\n      } catch (/** @type {any} */ error) {\n        log(error)\n        throw new Error('Default config couldn\\'t be found or content isn\\'t valid JSON.')\n      }\n    }\n\n    print(`initializing ipfs node at ${repoPath}`)\n\n    try {\n      await IPFS.create({\n        repo: repoPath,\n        init: {\n          algorithm: keyTypes[algorithm],\n          bits: bits,\n          privateKey: privateKey,\n          emptyRepo: emptyRepo,\n          profiles: profile\n        },\n        pass: pass,\n        start: false,\n        config\n      })\n    } catch (/** @type {any} */ err) {\n      if (err.code === 'EACCES') {\n        err.message = 'EACCES: permission denied, stat $IPFS_PATH/version'\n      }\n      throw err\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/key/export.js",
    "content": "import fs from 'fs'\nimport parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string} Argv.name\n * @property {string} Argv.passout\n * @property {string} Argv.output\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'export <name>',\n\n  describe: 'Export the key as a password protected PKCS #8 PEM file',\n\n  builder: {\n    passout: {\n      alias: 'p',\n      describe: 'Password for the PEM',\n      string: true,\n      demandOption: true\n    },\n    output: {\n      alias: 'o',\n      describe: 'Output file',\n      string: true,\n      default: 'stdout'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx, name, passout, output, timeout }) {\n    const { ipfs } = ctx\n    const pem = await ipfs.key.export(name, passout, {\n      timeout\n    })\n    if (output === 'stdout') {\n      process.stdout.write(pem)\n    } else {\n      fs.writeFileSync(output, pem)\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/key/gen.js",
    "content": "import parseDuration from 'parse-duration'\nimport {\n  stripControlCharacters\n} from '../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string} Argv.name\n * @property {string} Argv.type\n * @property {number} Argv.size\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'gen <name>',\n\n  describe: 'Create a new key',\n\n  builder: {\n    type: {\n      alias: 't',\n      describe: 'type of the key to create',\n      choices: ['rsa', 'ed25519'],\n      default: 'ed25519'\n    },\n    size: {\n      alias: 's',\n      describe: 'size of the key to generate',\n      default: 2048,\n      number: true\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, name, type, size, timeout }) {\n    const key = await ipfs.key.gen(name, {\n      type: type.toLowerCase() === 'rsa' ? 'RSA' : 'Ed25519',\n      size,\n      timeout\n    })\n    print(`generated ${key.id} ${stripControlCharacters(key.name)}`)\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/key/import.js",
    "content": "import fs from 'fs'\nimport parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string} Argv.name\n * @property {string} Argv.input\n * @property {string} Argv.passin\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'import <name>',\n\n  describe: 'Import the key from a PKCS #8 PEM file',\n\n  builder: {\n    passin: {\n      alias: 'p',\n      describe: 'Password for the PEM',\n      string: true\n    },\n    input: {\n      alias: 'i',\n      describe: 'Input PEM file',\n      string: true,\n      demandOption: true,\n      coerce: input => fs.readFileSync(input, 'utf8')\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, name, input, passin, timeout }) {\n    const key = await ipfs.key.import(name, input, passin, {\n      timeout\n    })\n    print(`imported ${key.id} ${key.name}`)\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/key/index.js",
    "content": "import keyExport from './export.js'\nimport keyGen from './gen.js'\nimport keyImport from './import.js'\nimport keyList from './list.js'\nimport keyRename from './rename.js'\nimport keyRm from './rm.js'\n\n/** @type {import('yargs').CommandModule[]} */\nexport const commands = [\n  keyExport,\n  keyGen,\n  keyImport,\n  keyList,\n  keyRename,\n  keyRm\n]\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/key/list.js",
    "content": "import parseDuration from 'parse-duration'\nimport {\n  stripControlCharacters\n} from '../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'list',\n\n  describe: 'List all local keys',\n\n  builder: {\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, timeout }) {\n    const keys = await ipfs.key.list({\n      timeout\n    })\n    keys.forEach((ki) => print(`${ki.id} ${stripControlCharacters(ki.name)}`))\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/key/rename.js",
    "content": "import parseDuration from 'parse-duration'\nimport {\n  stripControlCharacters\n} from '../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string} Argv.name\n * @property {string} Argv.newName\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'rename <name> <newName>',\n\n  describe: 'Rename a key',\n\n  builder: {\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, name, newName, timeout }) {\n    const res = await ipfs.key.rename(name, newName, {\n      timeout\n    })\n    print(`renamed to ${res.id} ${stripControlCharacters(res.now)}`)\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/key/rm.js",
    "content": "import parseDuration from 'parse-duration'\nimport {\n  stripControlCharacters\n} from '../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string} Argv.name\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'rm <name>',\n\n  describe: 'Remove a key',\n\n  builder: {\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, name, timeout }) {\n    const key = await ipfs.key.rm(name, {\n      timeout\n    })\n    print(`${key.id} ${stripControlCharacters(key.name)}`)\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/key.js",
    "content": "import { commands } from './key/index.js'\n\n/** @type {import('yargs').CommandModule} */\nconst command = {\n  command: 'key <command>',\n\n  describe: 'Manage your keys',\n\n  builder (yargs) {\n    commands.forEach(command => {\n      yargs.command(command)\n    })\n\n    return yargs\n  },\n\n  handler () {\n\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/ls.js",
    "content": "import { rightpad, stripControlCharacters } from '../utils.js'\nimport { formatMode } from 'ipfs-core-utils/files/format-mode'\nimport { formatMtime } from 'ipfs-core-utils/files/format-mtime'\nimport parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../types').Context} Argv.ctx\n * @property {string} Argv.key\n * @property {boolean} Argv.headers\n * @property {string} Argv.cidBase\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'ls <key>',\n\n  describe: 'List files for the given directory',\n\n  builder: {\n    v: {\n      alias: 'headers',\n      desc: 'Print table headers (Hash, Size, Name)',\n      boolean: true,\n      default: false\n    },\n    'cid-base': {\n      describe: 'Number base to display CIDs in',\n      string: true,\n      default: 'base58btc'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, key, headers, cidBase, timeout }) {\n    // replace multiple slashes\n    key = key.replace(/\\/(\\/+)/g, '/')\n\n    // strip trailing slash\n    if (key.endsWith('/')) {\n      key = key.replace(/(\\/+)$/, '')\n    }\n\n    let pathParts = key.split('/')\n\n    if (key.startsWith('/ipfs/')) {\n      pathParts = pathParts.slice(2)\n    }\n\n    let first = true\n\n    /** @type {number[]} */\n    let maxWidths = []\n    /**\n     * @param  {...string} args\n     */\n    const getMaxWidths = (...args) => {\n      maxWidths = args.map((v, i) => Math.max(maxWidths[i] || 0, v.length))\n      return maxWidths\n    }\n\n    /**\n     * @param {*} mode\n     * @param {*} mtime\n     * @param {*} cid\n     * @param {*} size\n     * @param {*} name\n     * @param {*} depth\n     */\n    const printLink = (mode, mtime, cid, size, name, depth = 0) => {\n      name = stripControlCharacters(name)\n      const widths = getMaxWidths(mode, mtime, cid, size, name)\n      // todo: fix this by resolving https://github.com/ipfs/js-ipfs-unixfs-exporter/issues/24\n      const padding = Math.max(depth - pathParts.length, 0)\n      print(\n        rightpad(mode, widths[0] + 1) +\n          rightpad(mtime, widths[1] + 1) +\n          rightpad(cid, widths[2] + 1) +\n          rightpad(size, widths[3] + 1) +\n          '  '.repeat(padding) + name\n      )\n    }\n\n    const base = await ipfs.bases.getBase(cidBase)\n\n    for await (const link of ipfs.ls(key, { timeout })) {\n      const mode = link.mode != null ? formatMode(link.mode, link.type === 'dir') : ''\n      const mtime = link.mtime != null ? formatMtime(link.mtime) : '-'\n      const cid = link.cid.toString(base.encoder)\n      const size = link.size ? String(link.size) : '-'\n      const name = stripControlCharacters(link.type === 'dir' ? `${link.name || ''}/` : link.name)\n\n      if (first) {\n        first = false\n        if (headers) {\n          // Seed max widths for the first item\n          getMaxWidths(mode, mtime, cid, size, name)\n          printLink('Mode', 'Mtime', 'Hash', 'Size', 'Name')\n        }\n      }\n\n      printLink(mode, mtime, cid, size, name)\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/name/index.js",
    "content": "import namePublish from './publish.js'\nimport namePubsub from './pubsub.js'\nimport nameResolve from './resolve.js'\n\n/** @type {import('yargs').CommandModule[]} */\nexport const commands = [\n  namePublish,\n  namePubsub,\n  nameResolve\n]\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/name/publish.js",
    "content": "import parseDuration from 'parse-duration'\nimport {\n  stripControlCharacters\n} from '../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {import('multiformats/cid').CID} Argv.ipfsPath\n * @property {boolean} Argv.resolve\n * @property {string} Argv.lifetime\n * @property {string} Argv.key\n * @property {string} Argv.ttl\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'publish <ipfsPath>',\n\n  describe: 'Publish IPNS names',\n\n  builder: {\n    ipfsPath: {\n      string: true\n    },\n    resolve: {\n      alias: 'r',\n      describe: 'Resolve given path before publishing. Default: true',\n      default: true,\n      boolean: true\n    },\n    lifetime: {\n      alias: 't',\n      describe: 'Time duration that the record will be valid for. Default: 24h',\n      default: '24h',\n      string: true\n    },\n    key: {\n      alias: 'k',\n      describe: 'Name of the key to be used, as listed by \"ipfs key list -l\". Default: self',\n      default: 'self',\n      string: true\n    },\n    ttl: {\n      describe: 'Time duration this record should be cached for (caution: experimental)',\n      default: '',\n      string: true\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, ipfsPath, resolve, lifetime, key, ttl, timeout }) {\n    const result = await ipfs.name.publish(ipfsPath, {\n      resolve,\n      lifetime,\n      key,\n      ttl,\n      timeout\n    })\n    print(`Published to ${stripControlCharacters(result.name)}: ${result.value}`)\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/name/pubsub/cancel.js",
    "content": "import parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../../types').Context} Argv.ctx\n * @property {string} Argv.name\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'cancel <name>',\n\n  describe: 'Cancel a name subscription',\n\n  builder: {\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, name, timeout }) {\n    const result = await ipfs.name.pubsub.cancel(name, {\n      timeout\n    })\n    print(result.canceled ? 'canceled' : 'no subscription')\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/name/pubsub/index.js",
    "content": "import namePubsubCancel from './cancel.js'\nimport namePubsubState from './state.js'\nimport namePubsubSubs from './subs.js'\n\n/** @type {import('yargs').CommandModule[]} */\nexport const commands = [\n  namePubsubCancel,\n  namePubsubState,\n  namePubsubSubs\n]\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/name/pubsub/state.js",
    "content": "import parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../../types').Context} Argv.ctx\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'state',\n\n  describe: 'Query the state of IPNS pubsub',\n\n  builder: {\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, timeout }) {\n    const result = await ipfs.name.pubsub.state({\n      timeout\n    })\n    print(result.enabled ? 'enabled' : 'disabled')\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/name/pubsub/subs.js",
    "content": "import parseDuration from 'parse-duration'\nimport {\n  stripControlCharacters\n} from '../../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../../types').Context} Argv.ctx\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'subs',\n\n  describe: 'Show current name subscriptions',\n\n  builder: {\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, timeout }) {\n    const result = await ipfs.name.pubsub.subs({\n      timeout\n    })\n    result.forEach(s => print(stripControlCharacters(s)))\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/name/pubsub.js",
    "content": "import { commands } from './pubsub/index.js'\n\n/**\n * @typedef {import('yargs').Argv} Argv\n */\n\n/*\nManage and inspect the state of the IPNS pubsub resolver.\nNote: this command is experimental and subject to change as the system is refined.\n*/\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'pubsub',\n\n  describe: 'IPNS pubsub management',\n\n  builder (yargs) {\n    commands.forEach(command => {\n      yargs.command(command)\n    })\n\n    return yargs\n  },\n\n  handler () {\n\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/name/resolve.js",
    "content": "import parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string} Argv.name\n * @property {boolean} Argv.nocache\n * @property {boolean} Argv.recursive\n * @property {boolean} Argv.stream\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'resolve <name>',\n\n  describe: 'Resolve IPNS names',\n\n  builder: {\n    nocache: {\n      boolean: true,\n      alias: 'n',\n      describe: 'Do not use cached entries. Default: false',\n      default: false\n    },\n    recursive: {\n      boolean: true,\n      alias: 'r',\n      describe: 'Resolve until the result is not an IPNS name. Default: true',\n      default: true\n    },\n    stream: {\n      boolean: true,\n      alias: 's',\n      describe: 'Stream entries as they are found',\n      default: false\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, nocache, recursive, name, stream, timeout }) {\n    let bestValue\n\n    for await (const value of ipfs.name.resolve(name, { nocache, recursive, timeout })) {\n      bestValue = value\n      if (stream) print(value)\n    }\n\n    if (!stream) print(bestValue || '')\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/name.js",
    "content": "import { commands } from './name/index.js'\n\n/*\nIPNS is a PKI namespace, where names are the hashes of public keys, and\nthe private key enables publishing new (signed) values. In both publish\nand resolve, the default name used is the node's own PeerID,\nwhich is the hash of its public key.\n*/\n/** @type {import('yargs').CommandModule} */\nconst command = {\n  command: 'name <command>',\n\n  describe: 'Publish and resolve IPNS names',\n\n  builder (yargs) {\n    commands.forEach(command => {\n      yargs.command(command)\n    })\n\n    return yargs\n  },\n\n  handler () {\n\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/object/data.js",
    "content": "import parseDuration from 'parse-duration'\nimport { coerceCID } from '../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {import('multiformats/cid').CID} Argv.key\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'data <key>',\n\n  describe: 'Outputs the raw bytes in an IPFS object',\n\n  builder: {\n    key: {\n      string: true,\n      coerce: coerceCID\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, key, timeout }) {\n    const data = await ipfs.object.data(key, { timeout })\n    print(data, false)\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/object/get.js",
    "content": "import * as dagPB from '@ipld/dag-pb'\nimport parseDuration from 'parse-duration'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\nimport {\n  stripControlCharacters,\n  coerceCID\n} from '../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {import('multiformats/cid').CID} Argv.key\n * @property {'base64' | 'text' | 'hex'} Argv.dataEncoding\n * @property {string} Argv.cidBase\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'get <key>',\n\n  describe: 'Get and serialize the DAG node named by <key>',\n\n  builder: {\n    key: {\n      string: true,\n      coerce: coerceCID\n    },\n    'data-encoding': {\n      string: true,\n      default: 'base64'\n    },\n    'cid-base': {\n      describe: 'Number base to display CIDs in. Note: specifying a CID base for v0 CIDs will have no effect',\n      string: true,\n      default: 'base58btc'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, key, dataEncoding, cidBase, timeout }) {\n    const node = await ipfs.object.get(key, { timeout })\n\n    /** @type {string | undefined} */\n    let encoding\n\n    if (dataEncoding === 'base64') {\n      encoding = 'base64pad'\n    }\n\n    if (dataEncoding === 'text') {\n      encoding = 'ascii'\n    }\n\n    if (dataEncoding === 'hex') {\n      encoding = 'base16'\n    }\n\n    const buf = dagPB.encode(node)\n    const base = await ipfs.bases.getBase(cidBase)\n\n    const answer = {\n      // @ts-expect-error encoding type is wrong\n      Data: node.Data ? uint8ArrayToString(node.Data, encoding) : '',\n      Hash: key.toString(base.encoder),\n      Size: buf.length,\n      Links: node.Links.map((l) => {\n        return {\n          Name: stripControlCharacters(l.Name),\n          Size: l.Tsize,\n          Hash: l.Hash.toString(base.encoder)\n        }\n      })\n    }\n\n    print(JSON.stringify(answer))\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/object/index.js",
    "content": "import objectData from './data.js'\nimport objectGet from './get.js'\nimport objectLinks from './links.js'\nimport objectNew from './new.js'\nimport objectPatch from './patch.js'\nimport objectPut from './put.js'\nimport objectStat from './stat.js'\n\n/** @type {import('yargs').CommandModule[]} */\nexport const commands = [\n  objectData,\n  objectGet,\n  objectLinks,\n  objectNew,\n  objectPatch,\n  objectPut,\n  objectStat\n]\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/object/links.js",
    "content": "import parseDuration from 'parse-duration'\nimport {\n  stripControlCharacters,\n  coerceCID\n} from '../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {import('multiformats/cid').CID} Argv.key\n * @property {string} Argv.cidBase\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'links <key>',\n\n  describe: 'Outputs the links pointed to by the specified object',\n\n  builder: {\n    key: {\n      string: true,\n      coerce: coerceCID\n    },\n    'cid-base': {\n      describe: 'Number base to display CIDs in. Note: specifying a CID base for v0 CIDs will have no effect',\n      string: true,\n      default: 'base58btc'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, key, cidBase, timeout }) {\n    const links = await ipfs.object.links(key, { timeout })\n    const base = await ipfs.bases.getBase(cidBase)\n\n    links.forEach((link) => {\n      const cidStr = link.Hash.toString(base.encoder)\n      print(`${cidStr} ${link.Tsize} ${stripControlCharacters(link.Name)}`)\n    })\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/object/new.js",
    "content": "import parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {'unixfs-dir'} Argv.template\n * @property {string} Argv.cidBase\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'new [<template>]',\n\n  describe: 'Create new ipfs objects',\n\n  builder: {\n    'cid-base': {\n      describe: 'Number base to display CIDs in. Note: specifying a CID base for v0 CIDs will have no effect',\n      string: true,\n      default: 'base58btc'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, template, cidBase, timeout }) {\n    const cid = await ipfs.object.new({\n      template,\n      timeout\n    })\n    const base = await ipfs.bases.getBase(cidBase)\n    print(cid.toString(base.encoder))\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/object/patch/add-link.js",
    "content": "import * as dagPB from '@ipld/dag-pb'\nimport parseDuration from 'parse-duration'\nimport { coerceCID } from '../../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../../types').Context} Argv.ctx\n * @property {import('multiformats/cid').CID} Argv.root\n * @property {string} Argv.name\n * @property {import('multiformats/cid').CID} Argv.ref\n * @property {string} Argv.cidBase\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'add-link <root> <name> <ref>',\n\n  describe: 'Add a link to a given object',\n\n  builder: {\n    root: {\n      string: true,\n      coerce: coerceCID\n    },\n    ref: {\n      string: true,\n      coerce: coerceCID\n    },\n    'cid-base': {\n      describe: 'Number base to display CIDs in. Note: specifying a CID base for v0 CIDs will have no effect',\n      string: true,\n      default: 'base58btc'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, root, name, ref, cidBase, timeout }) {\n    const nodeA = await ipfs.object.get(ref, { timeout })\n    const block = dagPB.encode(nodeA)\n    const cid = await ipfs.object.patch.addLink(root, {\n      Name: name,\n      Hash: ref,\n      Tsize: block.length\n    }, { timeout })\n    const base = await ipfs.bases.getBase(cidBase)\n    print(cid.toString(base.encoder))\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/object/patch/append-data.js",
    "content": "import concat from 'it-concat'\nimport fs from 'fs'\nimport parseDuration from 'parse-duration'\nimport { coerceCID } from '../../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../../types').Context} Argv.ctx\n * @property {import('multiformats/cid').CID} Argv.root\n * @property {string} Argv.data\n * @property {string} Argv.cidBase\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'append-data <root> [data]',\n\n  describe: 'Append data to the data segment of a dag node',\n\n  builder: {\n    root: {\n      string: true,\n      coerce: coerceCID\n    },\n    'cid-base': {\n      describe: 'Number base to display CIDs in. Note: specifying a CID base for v0 CIDs will have no effect',\n      string: true,\n      default: 'base58btc'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print, getStdin }, root, data, cidBase, timeout }) {\n    let buf\n\n    if (data) {\n      buf = fs.readFileSync(data)\n    } else {\n      buf = (await concat(getStdin(), { type: 'buffer' })).subarray()\n    }\n\n    const cid = await ipfs.object.patch.appendData(root, buf, {\n      timeout\n    })\n    const base = await ipfs.bases.getBase(cidBase)\n\n    print(cid.toString(base.encoder))\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/object/patch/index.js",
    "content": "import objectPatchAddLink from './add-link.js'\nimport objectPatchAppendData from './append-data.js'\nimport objectPatchRmLink from './rm-link.js'\nimport objectPatchSetData from './set-data.js'\n\n/** @type {import('yargs').CommandModule[]} */\nexport const commands = [\n  objectPatchAddLink,\n  objectPatchAppendData,\n  objectPatchRmLink,\n  objectPatchSetData\n]\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/object/patch/rm-link.js",
    "content": "import parseDuration from 'parse-duration'\nimport { coerceCID } from '../../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../../types').Context} Argv.ctx\n * @property {import('multiformats/cid').CID} Argv.root\n * @property {string} Argv.link\n * @property {string} Argv.cidBase\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'rm-link <root> <link>',\n\n  describe: 'Remove a link from an object',\n\n  builder: {\n    root: {\n      string: true,\n      coerce: coerceCID\n    },\n    'cid-base': {\n      describe: 'Number base to display CIDs in. Note: specifying a CID base for v0 CIDs will have no effect',\n      string: true,\n      default: 'base58btc'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, root, link, cidBase, timeout }) {\n    const cid = await ipfs.object.patch.rmLink(root, link, {\n      timeout\n    })\n    const base = await ipfs.bases.getBase(cidBase)\n\n    print(cid.toString(base.encoder))\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/object/patch/set-data.js",
    "content": "import fs from 'fs'\nimport concat from 'it-concat'\nimport parseDuration from 'parse-duration'\nimport { coerceCID } from '../../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../../types').Context} Argv.ctx\n * @property {import('multiformats/cid').CID} Argv.root\n * @property {string} Argv.data\n * @property {string} Argv.cidBase\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'set-data <root> [data]',\n\n  describe: 'Set data field of an ipfs object',\n\n  builder: {\n    root: {\n      string: true,\n      coerce: coerceCID\n    },\n    'cid-base': {\n      describe: 'Number base to display CIDs in. Note: specifying a CID base for v0 CIDs will have no effect',\n      string: true,\n      default: 'base58btc'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print, getStdin }, root, data, cidBase, timeout }) {\n    let buf\n\n    if (data) {\n      buf = fs.readFileSync(data)\n    } else {\n      buf = (await concat(getStdin(), { type: 'buffer' })).subarray()\n    }\n\n    const cid = await ipfs.object.patch.setData(root, buf, {\n      timeout\n    })\n\n    const base = await ipfs.bases.getBase(cidBase)\n\n    print(cid.toString(base.encoder))\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/object/patch.js",
    "content": "import { commands } from './patch/index.js'\n\n/**\n * @typedef {import('yargs').Argv} Argv\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'patch',\n\n  describe: 'Create a new merkledag object based on an existing one',\n\n  builder (yargs) {\n    commands.forEach(command => {\n      yargs.command(command)\n    })\n\n    return yargs\n  },\n\n  handler () {\n\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/object/put.js",
    "content": "import concat from 'it-concat'\nimport * as dagPB from '@ipld/dag-pb'\nimport parseDuration from 'parse-duration'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string} Argv.data\n * @property {'json' | 'protobuf'} Argv.inputEnc\n * @property {string} Argv.cidBase\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'put [data]',\n\n  describe: 'Stores input as a DAG object, outputs its key',\n\n  builder: {\n    'input-enc': {\n      string: true,\n      choices: ['json', 'protobuf'],\n      default: 'json'\n    },\n    'cid-base': {\n      describe: 'Number base to display CIDs in',\n      string: true,\n      default: 'base58btc'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print, getStdin }, data, inputEnc, cidBase, timeout }) {\n    let buf\n\n    if (data) {\n      buf = uint8ArrayFromString(data)\n    } else {\n      buf = (await concat(getStdin(), { type: 'buffer' })).subarray()\n    }\n\n    let node\n\n    if (inputEnc === 'protobuf') {\n      node = dagPB.decode(buf)\n    } else {\n      node = JSON.parse(uint8ArrayToString(buf))\n    }\n\n    const base = await ipfs.bases.getBase(cidBase)\n\n    const cid = await ipfs.object.put(node, { timeout })\n    print(`added ${cid.toString(base.encoder)}`)\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/object/stat.js",
    "content": "import parseDuration from 'parse-duration'\nimport { coerceCID } from '../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {import('multiformats/cid').CID} Argv.key\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'stat <key>',\n\n  describe: 'Get stats for the DAG node named by <key>',\n\n  builder: {\n    key: {\n      string: true,\n      coerce: coerceCID\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, key, timeout }) {\n    const stats = await ipfs.object.stat(key, { timeout })\n\n    Object.entries(stats).forEach(([key, value]) => {\n      if (key === 'Hash') {\n        return // only for js-ipfs-http-client output\n      }\n\n      print(`${key}: ${value}`)\n    })\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/object.js",
    "content": "import { commands } from './object/index.js'\n\n/** @type {import('yargs').CommandModule} */\nconst command = {\n  command: 'object <command>',\n\n  describe: 'Interact with ipfs objects',\n\n  builder (yargs) {\n    commands.forEach(command => {\n      yargs.command(command)\n    })\n\n    return yargs\n  },\n\n  handler () {\n\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/pin/add.js",
    "content": "import parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string[]} Argv.ipfsPath\n * @property {boolean} Argv.recursive\n * @property {string} Argv.cidBase\n * @property {number} Argv.timeout\n * @property {Record<string, any>} Argv.metadata\n * @property {Record<string, any>} Argv.metadataJson\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'add <ipfsPath...>',\n\n  describe: 'Pins object to local storage, preventing it from being garbage collected',\n\n  builder: {\n    recursive: {\n      boolean: true,\n      alias: 'r',\n      default: true,\n      describe: 'Recursively pin the object linked to by the specified object(s).'\n    },\n    'cid-base': {\n      describe: 'Number base to display CIDs in',\n      string: true,\n      default: 'base58btc'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    },\n    metadata: {\n      describe: 'Metadata to add to the pin',\n      string: true,\n      alias: 'm',\n      coerce: (val) => {\n        if (!val) {\n          return\n        }\n\n        /** @type {Record<string, any>} */\n        const output = {}\n\n        val.split(',').forEach((/** @type {string} */ line) => {\n          const parts = line.split('=')\n          output[parts[0]] = parts[1]\n        })\n\n        return output\n      }\n    },\n    'metadata-json': {\n      describe: 'Metadata to add to the pin in JSON format',\n      string: true,\n      coerce: JSON.parse\n    }\n  },\n\n  async handler ({ ctx, ipfsPath, recursive, cidBase, timeout, metadata, metadataJson }) {\n    const { ipfs, print } = ctx\n    const type = recursive ? 'recursive' : 'direct'\n    const base = await ipfs.bases.getBase(cidBase)\n\n    if (metadataJson) {\n      metadata = metadataJson\n    }\n\n    for await (const res of ipfs.pin.addAll(ipfsPath.map(path => ({ path, recursive, metadata })), { timeout })) {\n      print(`pinned ${res.toString(base.encoder)} ${type}ly`)\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/pin/index.js",
    "content": "import pinAdd from './add.js'\nimport pinLs from './ls.js'\nimport pinRm from './rm.js'\n\n/** @type {import('yargs').CommandModule[]} */\nexport const commands = [\n  pinAdd,\n  pinLs,\n  pinRm\n]\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/pin/ls.js",
    "content": "import parseDuration from 'parse-duration'\nimport {\n  makeEntriesPrintable\n} from '../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string[]} Argv.ipfsPath\n * @property {'direct' | 'indirect' | 'recursive' | 'all'} Argv.type\n * @property {boolean} Argv.quiet\n * @property {string} Argv.cidBase\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  // bracket syntax with '...' tells yargs to optionally accept a list\n  command: 'ls [ipfsPath...]',\n\n  describe: 'List objects pinned to local storage',\n\n  builder: {\n    type: {\n      string: true,\n      alias: 't',\n      default: 'all',\n      choices: ['direct', 'indirect', 'recursive', 'all'],\n      describe: 'The type of pinned keys to list.'\n    },\n    quiet: {\n      boolean: true,\n      alias: 'q',\n      default: false,\n      describe: 'Write just hashes of objects.'\n    },\n    'cid-base': {\n      describe: 'Number base to display CIDs in',\n      string: true,\n      default: 'base58btc'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, ipfsPath, type, quiet, cidBase, timeout }) {\n    const base = await ipfs.bases.getBase(cidBase)\n    /**\n     * @param {import('ipfs-core-types/src/pin').LsResult} res\n     */\n    const printPin = res => {\n      let line = res.cid.toString(base.encoder)\n      if (!quiet) {\n        line += ` ${res.type}`\n\n        if (res.metadata) {\n          line += ` ${JSON.stringify(makeEntriesPrintable(res.metadata, base))}`\n        }\n      }\n      print(line)\n    }\n\n    for await (const res of ipfs.pin.ls({\n      paths: ipfsPath,\n      type,\n      timeout\n    })) {\n      printPin(res)\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/pin/rm.js",
    "content": "import parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string[]} Argv.ipfsPath\n * @property {boolean} Argv.recursive\n * @property {string} Argv.cidBase\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'rm <ipfsPath...>',\n\n  describe: 'Unpins the corresponding block making it available for garbage collection',\n\n  builder: {\n    recursive: {\n      boolean: true,\n      alias: 'r',\n      default: true,\n      describe: 'Recursively unpin the objects linked to by the specified object(s).'\n    },\n    'cid-base': {\n      describe: 'Number base to display CIDs in',\n      string: true,\n      default: 'base58btc'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx, ipfsPath, timeout, recursive, cidBase }) {\n    const { ipfs, print } = ctx\n    const base = await ipfs.bases.getBase(cidBase)\n\n    for await (const res of ipfs.pin.rmAll(ipfsPath.map(path => ({ path, recursive })), { timeout })) {\n      print(`unpinned ${res.toString(base.encoder)}`)\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/pin.js",
    "content": "import { commands } from './pin/index.js'\n\n/** @type {import('yargs').CommandModule} */\nconst command = {\n  command: 'pin <command>',\n\n  describe: 'Pin and unpin objects to local storage',\n\n  builder (yargs) {\n    commands.forEach(command => {\n      yargs.command(command)\n    })\n\n    return yargs\n  },\n\n  handler () {\n\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/ping.js",
    "content": "import parseDuration from 'parse-duration'\nimport { coercePeerId } from '../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../types').Context} Argv.ctx\n * @property {number} Argv.count\n * @property {import('@libp2p/interface-peer-id').PeerId} Argv.peerId\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'ping <peerId>',\n\n  describe: 'Measure the latency of a connection',\n\n  builder: {\n    peerId: {\n      describe: 'Specify which peer to ping',\n      string: true,\n      coerce: coercePeerId\n    },\n    count: {\n      alias: 'n',\n      number: true,\n      default: 10\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, count, peerId, timeout }) {\n    for await (const pong of ipfs.ping(peerId, { count, timeout })) {\n      const { success, time, text } = pong\n      // Check if it's a pong\n      if (success && !text) {\n        print(`Pong received: time=${time} ms`)\n        // Status response\n      } else {\n        print(text)\n      }\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/pubsub/index.js",
    "content": "import pubsubLs from './ls.js'\nimport pubsubPeers from './peers.js'\nimport pubsubPub from './pub.js'\nimport pubsubSub from './sub.js'\n\n/** @type {import('yargs').CommandModule[]} */\nexport const commands = [\n  pubsubLs,\n  pubsubPeers,\n  pubsubPub,\n  pubsubSub\n]\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/pubsub/ls.js",
    "content": "import parseDuration from 'parse-duration'\nimport {\n  stripControlCharacters\n} from '../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'ls',\n\n  describe: 'Get your list of subscriptions',\n\n  builder: {\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, timeout }) {\n    const subscriptions = await ipfs.pubsub.ls({\n      timeout\n    })\n    subscriptions.forEach(sub => print(stripControlCharacters(sub)))\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/pubsub/peers.js",
    "content": "import parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string} Argv.topic\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'peers <topic>',\n\n  describe: 'Get all peers subscribed to a topic',\n\n  builder: {\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, topic, timeout }) {\n    const peers = await ipfs.pubsub.peers(topic, {\n      timeout\n    })\n    peers.forEach(peer => print(peer.toString()))\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/pubsub/pub.js",
    "content": "import parseDuration from 'parse-duration'\nimport { coerceUint8Array } from '../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string} Argv.topic\n * @property {Uint8Array} Argv.data\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'pub <topic> <data>',\n\n  describe: 'Publish data to a topic',\n\n  builder: {\n    data: {\n      string: true,\n      coerce: coerceUint8Array\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs }, topic, data, timeout }) {\n    await ipfs.pubsub.publish(topic, data, {\n      timeout\n    })\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/pubsub/sub.js",
    "content": "import parseDuration from 'parse-duration'\n\n/**\n * @typedef {import('@libp2p/interface-pubsub').Message} Message\n */\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {string} Argv.topic\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'sub <topic>',\n\n  describe: 'Subscribe to a topic',\n\n  builder: {\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, topic, timeout }) {\n    /**\n     * @type {import('@libp2p/interfaces/events').EventHandler<Message>}\n     */\n    const handler = msg => print(msg.data.toString())\n    await ipfs.pubsub.subscribe(topic, handler, {\n      timeout\n    })\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/pubsub.js",
    "content": "import { commands } from './pubsub/index.js'\n\n/** @type {import('yargs').CommandModule} */\nconst command = {\n  command: 'pubsub <command>',\n\n  describe: 'pubsub commands',\n\n  builder (yargs) {\n    commands.forEach(command => {\n      yargs.command(command)\n    })\n\n    return yargs\n  },\n\n  handler () {\n\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/refs-local.js",
    "content": "import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport parseDuration from 'parse-duration'\nimport { base32 } from 'multiformats/bases/base32'\n\n/**\n * @typedef {object} Argv\n * @property {import('../types').Context} Argv.ctx\n * @property {boolean} Argv.multihash\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'refs-local',\n\n  describe: 'List all local references. CIDs are reconstructed therefore they might differ from those under which the blocks were originally stored',\n\n  builder: {\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    },\n    multihash: {\n      boolean: true,\n      default: false,\n      desc: 'Shows base32 encoded multihashes instead of reconstructed CIDs'\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, timeout, multihash }) {\n    for await (const { ref, err } of ipfs.refs.local({\n      timeout\n    })) {\n      if (err) {\n        print(err.toString(), true, true)\n      } else {\n        if (multihash) {\n          print(base32.encode(uint8ArrayFromString(ref)).toUpperCase())\n        } else {\n          print(ref)\n        }\n      }\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/refs.js",
    "content": "import parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../types').Context} Argv.ctx\n * @property {string} Argv.key\n * @property {string} Argv.keys\n * @property {boolean} Argv.recursive\n * @property {string} Argv.format\n * @property {boolean} Argv.edges\n * @property {boolean} Argv.unique\n * @property {number} Argv.maxDepth\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'refs <key> [keys..]',\n\n  describe: 'List links (references) from an object',\n\n  builder: {\n    recursive: {\n      alias: 'r',\n      desc: 'Recursively list links of child nodes',\n      boolean: true,\n      default: false\n    },\n    format: {\n      desc: 'Output edges with given format. Available tokens: <src> <dst> <linkname>',\n      string: true,\n      default: '<dst>'\n    },\n    edges: {\n      alias: 'e',\n      desc: 'Output edge format: `<from> -> <to>`',\n      boolean: true,\n      default: false\n    },\n    unique: {\n      alias: 'u',\n      desc: 'Omit duplicate refs from output',\n      boolean: true,\n      default: false\n    },\n    'max-depth': {\n      desc: 'Only for recursive refs, limits fetch and listing to the given depth',\n      number: true\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, key, keys, recursive, format, edges, unique, maxDepth, timeout }) {\n    if (maxDepth === 0) {\n      return\n    }\n\n    const k = [key].concat(keys)\n\n    for await (const { err, ref } of ipfs.refs(k, { recursive, format, edges, unique, maxDepth, timeout })) {\n      if (err) {\n        print(err.toString(), true, true)\n      } else {\n        print(ref)\n      }\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/repo/gc.js",
    "content": "import parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {boolean} Argv.quiet\n * @property {boolean} Argv.streamErrors\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'gc',\n\n  describe: 'Perform a garbage collection sweep on the repo',\n\n  builder: {\n    quiet: {\n      alias: 'q',\n      desc: 'Write minimal output',\n      boolean: true,\n      default: false\n    },\n    'stream-errors': {\n      desc: 'Output individual errors thrown when deleting blocks',\n      boolean: true,\n      default: true\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, quiet, streamErrors, timeout }) {\n    for await (const r of ipfs.repo.gc({\n      timeout\n    })) {\n      if (r.err != null) {\n        streamErrors && print(r.err.message, true, true)\n      } else {\n        print((quiet ? '' : 'removed ') + r.cid)\n      }\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/repo/index.js",
    "content": "import repoGc from './gc.js'\nimport repoStat from './stat.js'\nimport repoVersion from './version.js'\n\n/** @type {import('yargs').CommandModule[]} */\nexport const commands = [\n  repoGc,\n  repoStat,\n  repoVersion\n]\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/repo/stat.js",
    "content": "import prettyBytes from 'pretty-bytes'\nimport parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {boolean} Argv.human\n * @property {boolean} Argv.sizeOnly\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'stat',\n\n  describe: 'Get stats for the currently used repo',\n\n  builder: {\n    human: {\n      boolean: true,\n      alias: 'H',\n      default: false\n    },\n    sizeOnly: {\n      boolean: true,\n      alias: 's',\n      default: false\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, human, sizeOnly, timeout }) {\n    const stats = await ipfs.repo.stat({\n      timeout\n    })\n\n    /** @type {Record<string, any>} */\n    const output = {\n      ...stats\n    }\n\n    if (human) {\n      output.numObjects = Number(stats.numObjects)\n      output.repoSize = prettyBytes(Number(stats.repoSize)).toUpperCase()\n      output.storageMax = prettyBytes(Number(stats.storageMax)).toUpperCase()\n    }\n\n    if (sizeOnly) {\n      print(\n        `RepoSize:   ${output.repoSize}\nStorageMax: ${output.storageMax}`)\n    } else {\n      print(`NumObjects: ${output.numObjects}\nRepoSize:   ${output.repoSize}\nStorageMax: ${output.storageMax}\nRepoPath:   ${output.repoPath}\nVersion:    ${output.version}`)\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/repo/version.js",
    "content": "import parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'version',\n\n  describe: 'Shows IPFS repo version information',\n\n  builder: {\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, timeout }) {\n    const version = await ipfs.repo.version({\n      timeout\n    })\n    print(`${version}`)\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/repo.js",
    "content": "import { commands } from './repo/index.js'\n\n/** @type {import('yargs').CommandModule} */\nconst command = {\n  command: 'repo <command>',\n\n  describe: 'Manipulate the IPFS repo',\n\n  builder (yargs) {\n    commands.forEach(command => {\n      yargs.command(command)\n    })\n\n    return yargs\n  },\n\n  handler () {\n\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/resolve.js",
    "content": "import parseDuration from 'parse-duration'\nimport {\n  stripControlCharacters\n} from '../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../types').Context} Argv.ctx\n * @property {string} Argv.name\n * @property {boolean} Argv.recursive\n * @property {string} Argv.cidBase\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'resolve <name>',\n\n  describe: 'Resolve the value of names to IPFS',\n\n  builder: {\n    recursive: {\n      alias: 'r',\n      boolean: true,\n      default: true\n    },\n    'cid-base': {\n      describe: 'Number base to display CIDs in',\n      string: true,\n      default: 'base58btc'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { print, ipfs }, name, recursive, cidBase, timeout }) {\n    const res = await ipfs.resolve(name, { recursive, cidBase, timeout })\n    print(stripControlCharacters(res))\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/shutdown.js",
    "content": "import parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../types').Context} Argv.ctx\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'shutdown',\n\n  describe: 'Shut down the ipfs daemon',\n\n  builder: {\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  handler ({ ctx: { ipfs }, timeout }) {\n    // @ts-expect-error not part of the core api\n    return ipfs.shutdown({\n      timeout\n    })\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/stats/bitswap.js",
    "content": "import bitswapStat from '../bitswap/stat.js'\n\n// This is an alias for `bitswap stat`.\n/** @type {bitswapStat} */\nconst command = {\n  ...bitswapStat,\n  // The command needs to be renamed, else it would be `stats stat` instead of\n  // `stats bitswap`\n  command: 'bitswap'\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/stats/bw.js",
    "content": "import parseDuration from 'parse-duration'\nimport { coercePeerId } from '../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {import('@libp2p/interface-peer-id').PeerId} Argv.peer\n * @property {string} Argv.proto\n * @property {boolean} Argv.poll\n * @property {number} Argv.interval\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'bw',\n\n  describe: 'Get bandwidth information',\n\n  builder: {\n    peer: {\n      string: true,\n      coerce: coercePeerId\n    },\n    proto: {\n      string: true,\n      default: ''\n    },\n    poll: {\n      boolean: true,\n      default: false\n    },\n    interval: {\n      string: true,\n      default: '1s',\n      coerce: parseDuration\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, print }, peer, proto, poll, interval, timeout }) {\n    for await (const chunk of ipfs.stats.bw({ peer, proto, poll, interval, timeout })) {\n      print(`bandwidth status\n  total in: ${chunk.totalIn}B\n  total out: ${chunk.totalOut}B\n  rate in: ${chunk.rateIn}B/s\n  rate out: ${chunk.rateOut}B/s`)\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/stats/index.js",
    "content": "import statsBitswap from './bitswap.js'\nimport statsBw from './bw.js'\nimport statsRepo from './repo.js'\n\n/** @type {import('yargs').CommandModule[]} */\nexport const commands = [\n  statsBitswap,\n  statsBw,\n  statsRepo\n]\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/stats/repo.js",
    "content": "\n// This is an alias for `repo stat`.\nimport repoStats from '../repo/stat.js'\n\n/** @type {repoStats} */\nconst command = {\n  ...repoStats,\n\n  // The command needs to be renamed, else it would be `stats stat` instead of\n  // `stats repo`\n  command: 'repo'\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/stats.js",
    "content": "import { commands } from './stats/index.js'\n\n/** @type {import('yargs').CommandModule} */\nconst command = {\n  command: 'stats <command>',\n\n  describe: 'Query IPFS statistics',\n\n  builder (yargs) {\n    commands.forEach(command => {\n      yargs.command(command)\n    })\n\n    return yargs\n  },\n\n  handler () {\n\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/swarm/addrs/index.js",
    "content": "import swarmAddrsLocal from './local.js'\n\n/** @type {import('yargs').CommandModule[]} */\nexport const commands = [\n  swarmAddrsLocal\n]\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/swarm/addrs/local.js",
    "content": "import parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../../types').Context} Argv.ctx\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'local',\n\n  describe: 'List local addresses',\n\n  builder: {\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { print, ipfs, isDaemon }, timeout }) {\n    if (!isDaemon) {\n      throw new Error('This command must be run in online mode. Try running \\'ipfs daemon\\' first.')\n    }\n    const res = await ipfs.swarm.localAddrs({\n      timeout\n    })\n    res.forEach(addr => print(addr.toString()))\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/swarm/addrs.js",
    "content": "import { commands } from './addrs/index.js'\nimport parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'addrs',\n\n  describe: '',\n\n  builder (yargs) {\n    commands.forEach(command => {\n      yargs.command(command)\n    })\n\n    yargs\n      .option('timeout', {\n        string: true,\n        coerce: parseDuration\n      })\n\n    return yargs\n  },\n\n  async handler ({ ctx: { ipfs, print }, timeout }) {\n    const res = await ipfs.swarm.addrs({\n      timeout\n    })\n\n    const output = res.map((peer) => {\n      const count = peer.addrs.length\n      const peerAddrs = [`${peer.id} (${count})`]\n\n      peer.addrs.forEach((addr) => {\n        let res\n        try {\n          res = addr.decapsulate('ipfs').toString()\n        } catch (/** @type {any} */ _) {\n          // peer addresses dont need to have /ipfs/ as we know their peerId\n          // and can encapsulate on dial.\n          res = addr.toString()\n        }\n        peerAddrs.push(`\\t${res}`)\n      })\n\n      return peerAddrs.join('\\n')\n    })\n\n    // Return the output for printing\n    print(output.join('\\n'))\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/swarm/connect.js",
    "content": "import parseDuration from 'parse-duration'\nimport {\n  coerceMultiaddr\n} from '../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {import('@multiformats/multiaddr').Multiaddr} Argv.address\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'connect <address>',\n\n  describe: 'Open connection to a given address',\n\n  builder: {\n    address: {\n      string: true,\n      coerce: coerceMultiaddr\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, isDaemon, print }, address, timeout }) {\n    if (!isDaemon) {\n      throw new Error('This command must be run in online mode. Try running \\'ipfs daemon\\' first.')\n    }\n\n    await ipfs.swarm.connect(address, {\n      timeout\n    })\n\n    print(`${address}`)\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/swarm/disconnect.js",
    "content": "import parseDuration from 'parse-duration'\nimport {\n  coerceMultiaddr\n} from '../../utils.js'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {import('@multiformats/multiaddr').Multiaddr} Argv.address\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'disconnect <address>',\n\n  describe: 'Close connection to a given address',\n\n  builder: {\n    address: {\n      string: true,\n      coerce: coerceMultiaddr\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { ipfs, isDaemon, print }, address, timeout }) {\n    if (!isDaemon) {\n      throw new Error('This command must be run in online mode. Try running \\'ipfs daemon\\' first.')\n    }\n\n    await ipfs.swarm.disconnect(address, {\n      timeout\n    })\n\n    print(`${address}`)\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/swarm/index.js",
    "content": "import swarmAddrs from './addrs.js'\nimport swarmConnect from './connect.js'\nimport swarmDisconnect from './disconnect.js'\nimport swarmPeers from './peers.js'\n\n/** @type {import('yargs').CommandModule[]} */\nexport const commands = [\n  swarmAddrs,\n  swarmConnect,\n  swarmDisconnect,\n  swarmPeers\n]\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/swarm/peers.js",
    "content": "import { IPFS } from '@multiformats/mafmt'\nimport { multiaddr } from '@multiformats/multiaddr'\nimport parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../../types').Context} Argv.ctx\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'peers',\n\n  describe: 'List peers with open connections',\n\n  builder: {\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { print, ipfs, isDaemon }, timeout }) {\n    if (!isDaemon) {\n      throw new Error('This command must be run in online mode. Try running \\'ipfs daemon\\' first.')\n    }\n\n    const result = await ipfs.swarm.peers({\n      timeout\n    })\n\n    result.forEach((item) => {\n      let ma = multiaddr(`${item.addr}`)\n\n      if (!IPFS.matches(ma)) {\n        ma = ma.encapsulate(`/ipfs/${item.peer}`)\n      }\n\n      print(`${ma}`)\n    })\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/swarm.js",
    "content": "import { commands } from './swarm/index.js'\n\n/** @type {import('yargs').CommandModule} */\nconst command = {\n  command: 'swarm <command>',\n\n  describe: 'Swarm inspection tool',\n\n  builder (yargs) {\n    commands.forEach(command => {\n      yargs.command(command)\n    })\n\n    return yargs\n  },\n\n  handler () {\n\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/commands/version.js",
    "content": "import os from 'os'\nimport parseDuration from 'parse-duration'\n\n/**\n * @typedef {object} Argv\n * @property {import('../types').Context} Argv.ctx\n * @property {boolean} Argv.all\n * @property {boolean} Argv.commit\n * @property {boolean} Argv.repo\n * @property {boolean} Argv.number\n * @property {number} Argv.timeout\n */\n\n/** @type {import('yargs').CommandModule<Argv, Argv>} */\nconst command = {\n  command: 'version',\n\n  describe: 'Shows IPFS version information',\n\n  builder: {\n    number: {\n      alias: 'n',\n      boolean: true,\n      default: false,\n      describe: 'Print only the version number'\n    },\n    commit: {\n      boolean: true,\n      default: false,\n      describe: 'Include the version\\'s commit hash'\n    },\n    repo: {\n      boolean: true,\n      default: false,\n      describe: 'Print only the repo\\'s version number'\n    },\n    all: {\n      boolean: true,\n      default: false,\n      describe: 'Print everything we have'\n    },\n    timeout: {\n      string: true,\n      coerce: parseDuration\n    }\n  },\n\n  async handler ({ ctx: { print, ipfs }, all, commit, repo, number, timeout }) {\n    const data = await ipfs.version({\n      timeout\n    })\n\n    const withCommit = all || commit\n    const parsedVersion = `${data.version}${withCommit && data.commit ? `-${data.commit}` : ''}`\n\n    if (repo) {\n      // go-ipfs prints only the number, even without the --number flag.\n      // @ts-expect-error version return type is implementation-specific\n      print(data.repo)\n    } else if (number) {\n      print(parsedVersion)\n    } else if (all) {\n      print(`js-ipfs version: ${parsedVersion}`)\n      print(`interface-ipfs-core version: ${data['interface-ipfs-core']}`)\n      print(`ipfs-http-client version: ${data['ipfs-http-client']}`)\n      print(`Repo version: ${data.repo}`)\n      print(`System version: ${os.arch()}/${os.platform()}`)\n      print(`Node.js version: ${process.version}`)\n\n      if (data.commit) {\n        print(`Commit: ${data.commit}`)\n      }\n    } else {\n      print(`js-ipfs version: ${parsedVersion}`)\n    }\n  }\n}\n\nexport default command\n"
  },
  {
    "path": "packages/ipfs-cli/src/index.js",
    "content": "import parser from './parser.js'\nimport commandAlias from './command-alias.js'\n\n/**\n * @param {string[]} command\n * @param {import('yargs').MiddlewareFunction} ctxMiddleware\n */\nexport async function cli (command, ctxMiddleware) {\n  // Apply command aliasing (eg `refs local` -> `refs-local`)\n  command = commandAlias(command)\n\n  await parser()\n    .middleware(ctxMiddleware)\n    .parse(command)\n}\n"
  },
  {
    "path": "packages/ipfs-cli/src/parser.js",
    "content": "import yargs from 'yargs'\nimport { ipfsPathHelp, disablePrinting } from './utils.js'\nimport { commandList } from './commands/index.js'\n\nexport default () => {\n  const args = yargs(process.argv.slice(2))\n    .option('silent', {\n      desc: 'Write no output',\n      boolean: true,\n      default: false,\n      coerce: silent => {\n        if (silent) disablePrinting()\n        return silent\n      }\n    })\n    .option('pass', {\n      desc: 'Pass phrase for the keys',\n      string: true\n    })\n    .option('migrate', {\n      desc: 'Enable/disable automatic repo migrations',\n      boolean: true,\n      default: false\n    })\n    .options('api', {\n      desc: 'Remote API multiaddr to use',\n      string: true\n    })\n    .epilog(ipfsPathHelp)\n    .demandCommand(1, 'Please specify a command')\n    .showHelpOnFail(false)\n    .help()\n    .strict()\n    .completion()\n    .fail(false)\n\n  commandList.forEach(command => {\n    args.command(command)\n  })\n\n  return args\n}\n"
  },
  {
    "path": "packages/ipfs-cli/src/types.ts",
    "content": "import type { IPFS } from 'ipfs-core-types'\nimport type { Multiaddr } from '@multiformats/multiaddr'\n\ndeclare module '@hapi/hapi' {\n  interface ServerInfo {\n    ma: Multiaddr\n  }\n}\n\nexport interface Context {\n  ipfs: IPFS\n  print: Print\n  isDaemon: boolean\n  getStdin: () => AsyncIterable<Buffer>\n  repoPath: string\n}\n\nexport interface Print {\n  (msg: string | Uint8Array, includeNewline?: boolean, isError?: boolean): void\n  clearLine: () => void\n  cursorTo: (pos: number) => void\n  write: (data: any) => void\n  error: (msg: string, includeNewline?: boolean) => void\n  isTTY: boolean\n  columns: any\n}\n"
  },
  {
    "path": "packages/ipfs-cli/src/utils.js",
    "content": "import fs from 'fs'\nimport os from 'os'\nimport path from 'path'\nimport { logger } from '@libp2p/logger'\nimport Progress from 'progress'\n// @ts-expect-error no types\nimport byteman from 'byteman'\nimport { create } from 'ipfs-core'\nimport { CID } from 'multiformats/cid'\nimport { multiaddr } from '@multiformats/multiaddr'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { create as httpClient } from 'ipfs-http-client'\nimport { peerIdFromString } from '@libp2p/peer-id'\n\nconst log = logger('ipfs:cli:utils')\n\nexport const getRepoPath = () => {\n  return process.env.IPFS_PATH || path.join(os.homedir(), '/.jsipfs')\n}\n\nexport const isDaemonOn = () => {\n  try {\n    fs.readFileSync(path.join(getRepoPath(), 'api'))\n    log('daemon is on')\n    return true\n  } catch (/** @type {any} */ err) {\n    log('daemon is off')\n    return false\n  }\n}\n\nlet visible = true\nexport const disablePrinting = () => { visible = false }\n\n/**\n * @type {import('./types').Print}\n */\nexport const print = (msg, includeNewline = true, isError = false) => {\n  if (visible) {\n    if (msg === undefined) {\n      msg = ''\n    }\n    msg = msg.toString()\n    msg = includeNewline ? msg + '\\n' : msg\n    const outStream = isError ? process.stderr : process.stdout\n\n    outStream.write(msg)\n  }\n}\n\nprint.clearLine = () => {\n  return process.stdout.clearLine(0)\n}\n\n/**\n * @param {number} pos\n */\nprint.cursorTo = (pos) => {\n  process.stdout.cursorTo(pos)\n}\n\n/**\n * Write data directly to stdout\n *\n * @param {string|Uint8Array} data\n */\nprint.write = (data) => {\n  process.stdout.write(data)\n}\n\n/**\n * Print an error message\n *\n * @param {string} msg\n * @param {boolean} [newline=true]\n */\nprint.error = (msg, newline = true) => {\n  print(msg, newline, true)\n}\n\n// used by ipfs.add to interrupt the progress bar\nprint.isTTY = process.stdout.isTTY\nprint.columns = process.stdout.columns\n\n/**\n * @param {number} totalBytes\n * @param {*} [output]\n */\nexport const createProgressBar = (totalBytes, output) => {\n  const total = byteman(totalBytes, 2, 'MB')\n  const barFormat = `:progress / ${total} [:bar] :percent :etas`\n\n  // 16 MB / 34 MB [===========             ] 48% 5.8s //\n  return new Progress(barFormat, {\n    incomplete: ' ',\n    clear: true,\n    stream: output,\n    total: totalBytes\n  })\n}\n\n/**\n * @param {*} val\n * @param {number} n\n */\nexport const rightpad = (val, n) => {\n  let result = String(val)\n  for (let i = result.length; i < n; ++i) {\n    result += ' '\n  }\n  return result\n}\n\nexport const ipfsPathHelp = 'ipfs uses a repository in the local file system. By default, the repo is located at ~/.jsipfs. To change the repo location, set the $IPFS_PATH environment variable: `export IPFS_PATH=/path/to/ipfsrepo`'\n\n/**\n * @param {{ api?: string, silent?: boolean, migrate?: boolean, pass?: string }} argv\n */\nexport async function getIpfs (argv) {\n  if (!argv.api && !isDaemonOn()) {\n    /** @type {import('ipfs-core-types').IPFS} */\n    const ipfs = await create({\n      silent: argv.silent,\n      repoAutoMigrate: argv.migrate,\n      repo: getRepoPath(),\n      init: { allowNew: false },\n      start: false,\n      pass: argv.pass\n    })\n\n    return {\n      isDaemon: false,\n      ipfs,\n      cleanup: async () => {\n        await ipfs.stop()\n      }\n    }\n  }\n\n  let endpoint = null\n  if (!argv.api) {\n    const apiPath = path.join(getRepoPath(), 'api')\n    endpoint = fs.readFileSync(apiPath).toString()\n  } else {\n    endpoint = argv.api\n  }\n\n  /** @type {import('ipfs-core-types').IPFS} */\n  const ipfs = httpClient({ url: endpoint })\n\n  return {\n    isDaemon: true,\n    ipfs,\n    cleanup: async () => { }\n  }\n}\n\n/**\n * @param {boolean} [value]\n */\nexport const asBoolean = (value) => {\n  if (value === false || value === true) {\n    return value\n  }\n\n  if (value === undefined) {\n    return true\n  }\n\n  return false\n}\n\n/**\n * @param {any} value\n */\nexport const asOctal = (value) => {\n  return parseInt(value, 8)\n}\n\n/**\n * @param {number} [secs]\n * @param {number} [nsecs]\n */\nexport const asMtimeFromSeconds = (secs, nsecs) => {\n  if (secs == null) {\n    return undefined\n  }\n\n  return {\n    secs,\n    nsecs\n  }\n}\n\n/**\n * @param {*} value\n */\nexport const coerceMtime = (value) => {\n  value = parseInt(value)\n\n  if (isNaN(value)) {\n    throw new Error('mtime must be a number')\n  }\n\n  return value\n}\n\n/**\n * @param {*} value\n */\nexport const coerceMtimeNsecs = (value) => {\n  value = parseInt(value)\n\n  if (isNaN(value)) {\n    throw new Error('mtime-nsecs must be a number')\n  }\n\n  if (value < 0 || value > 999999999) {\n    throw new Error('mtime-nsecs must be in the range [0,999999999]')\n  }\n\n  return value\n}\n\n/**\n * @param {*} value\n */\nexport const coerceCID = (value) => {\n  if (!value) {\n    return undefined\n  }\n\n  if (value.startsWith('/ipfs/')) {\n    return CID.parse(value.split('/')[2])\n  }\n\n  return CID.parse(value)\n}\n\n/**\n * @param {string[]} values\n */\nexport const coerceCIDs = (values) => {\n  if (values == null) {\n    return []\n  }\n\n  return values.map(coerceCID).filter(Boolean)\n}\n\n/**\n * @param {string} [value]\n */\nexport const coercePeerId = (value) => {\n  if (!value) {\n    return undefined\n  }\n\n  return peerIdFromString(value)\n}\n\n/**\n * @param {string} value\n */\nexport const coerceMultiaddr = (value) => {\n  if (value == null) {\n    return undefined\n  }\n\n  return multiaddr(value)\n}\n\n/**\n * @param {string[]} values\n */\nexport const coerceMultiaddrs = (values) => {\n  if (values == null) {\n    return undefined\n  }\n\n  return values.map(coerceMultiaddr).filter(Boolean)\n}\n\n/**\n * @param {string} value\n */\nexport const coerceUint8Array = (value) => {\n  if (value == null) {\n    return undefined\n  }\n\n  return uint8ArrayFromString(value)\n}\n\nconst DEL = 127\n\n/**\n * Strip control characters from a string\n *\n * @param {string} [str] - a string to strip control characters from\n */\nexport const stripControlCharacters = (str) => {\n  return (str || '')\n    .split('')\n    .filter((c) => {\n      const charCode = c.charCodeAt(0)\n\n      return charCode > 31 && charCode !== DEL\n    })\n    .join('')\n}\n\n/**\n * Escape control characters in a string\n *\n * @param {string} str - a string to escape control characters in\n */\nexport const escapeControlCharacters = (str) => {\n  /** @type {Record<string, string>} */\n  const escapes = {\n    '00': '\\\\0',\n    '08': '\\\\b',\n    '09': '\\\\t',\n    '0A': '\\\\n',\n    '0B': '\\\\v',\n    '0C': '\\\\f',\n    '0D': '\\\\r'\n  }\n\n  return (str || '')\n    .split('')\n    .map((c) => {\n      const charCode = c.charCodeAt(0)\n\n      if (charCode > 31 && charCode !== DEL) {\n        return c\n      }\n\n      const hex = Number(c).toString(16).padStart(2, '0')\n\n      return escapes[hex] || `\\\\x${hex}`\n    })\n    .join('')\n}\n\n/**\n * Removes control characters from all key/values and stringifies\n * CID properties\n *\n * @param {any} obj - all keys/values in this object will be have control characters stripped\n * @param {import('multiformats/bases/interface').MultibaseCodec<any>} cidBase - any encountered CIDs will be stringified using this base\n * @returns {any}\n */\nexport const makeEntriesPrintable = (obj, cidBase) => {\n  const cid = CID.asCID(obj)\n\n  if (cid) {\n    return { '/': cid.toString(cidBase.encoder) }\n  }\n\n  if (typeof obj === 'string') {\n    return stripControlCharacters(obj)\n  }\n\n  if (typeof obj === 'number' || obj == null || obj === true || obj === false) {\n    return obj\n  }\n\n  if (Array.isArray(obj)) {\n    const output = []\n\n    for (const key of obj) {\n      output.push(makeEntriesPrintable(key, cidBase))\n    }\n\n    return output\n  }\n\n  /** @type {Record<string, any>} */\n  const output = {}\n\n  Object.entries(obj)\n    .forEach(([key, value]) => {\n      const outputKey = stripControlCharacters(key)\n\n      output[outputKey] = makeEntriesPrintable(value, cidBase)\n    })\n\n  return output\n}\n"
  },
  {
    "path": "packages/ipfs-cli/test/add.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { CID } from 'multiformats/cid'\nimport { base58btc } from 'multiformats/bases/base58'\nimport { base64 } from 'multiformats/bases/base64'\nimport first from 'it-first'\nimport { cli } from './utils/cli.js'\nimport sinon from 'sinon'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { matchIterable } from './utils/match-iterable.js'\nimport all from 'it-all'\nimport map from 'it-map'\n\n// TODO: Test against all algorithms Object.keys(mh.names)\n// This subset is known to work with both go-ipfs and js-ipfs as of 2017-09-05\nconst HASH_ALGS = [\n  'sha1',\n  'sha2-256',\n  'sha2-512',\n  'keccak-224',\n  'keccak-256',\n  'keccak-384',\n  'keccak-512'\n]\n\nconst defaultOptions = {\n  trickle: false,\n  shardSplitThreshold: 1000,\n  cidVersion: 0,\n  rawLeaves: false,\n  onlyHash: false,\n  hashAlg: 'sha2-256',\n  wrapWithDirectory: false,\n  pin: true,\n  chunker: 'size-262144',\n  preload: true,\n  fileImportConcurrency: 50,\n  blockWriteConcurrency: 10,\n  progress: sinon.match.func,\n  timeout: undefined\n}\n\ndescribe('add', () => {\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      addAll: sinon.stub(),\n      bases: {\n        getBase: sinon.stub()\n      }\n    }\n  })\n\n  it('should add a file', async () => {\n    const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n    ipfs.addAll.withArgs(matchIterable(), defaultOptions).returns([{\n      cid,\n      path: 'README.md'\n    }])\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const out = await cli('add --progress false README.md', { ipfs })\n    expect(out).to.equal(`added ${cid} README.md\\n`)\n\n    const files = await all(map(ipfs.addAll.getCall(0).args[0], (file) => file.path))\n    expect(files).to.deep.equal([\n      'README.md'\n    ])\n  })\n\n  it('should add a directory', async () => {\n    const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n    ipfs.addAll.withArgs(matchIterable(), defaultOptions).returns([{\n      cid,\n      path: 'bitswap/index.js'\n    }, {\n      cid,\n      path: 'bitswap/unwant.js'\n    }, {\n      cid,\n      path: 'bitswap/wantlist.js'\n    }, {\n      cid,\n      path: 'bitswap/stat.js'\n    }, {\n      cid,\n      path: 'bitswap'\n    }])\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const out = await cli('add --recursive src/commands/bitswap', { ipfs })\n    expect(out).to.include(`added ${cid} bitswap/index.js\\n`)\n    expect(out).to.include(`added ${cid} bitswap/unwant.js\\n`)\n    expect(out).to.include(`added ${cid} bitswap/wantlist.js\\n`)\n    expect(out).to.include(`added ${cid} bitswap/stat.js\\n`)\n    expect(out).to.include(`added ${cid} bitswap\\n`)\n\n    const files = await all(map(ipfs.addAll.getCall(0).args[0], (file) => file.path))\n    expect(files.sort()).to.deep.equal([\n      'bitswap/index.js',\n      'bitswap/unwant.js',\n      'bitswap/wantlist.js',\n      'bitswap/stat.js'\n    ].sort())\n  })\n\n  it('should strip control characters from paths when add a file', async () => {\n    const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n    ipfs.addAll.withArgs(matchIterable(), defaultOptions).returns([{\n      cid,\n      path: 'R\\b\\n\\tEADME.md'\n    }])\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const out = await cli('add --progress false README.md', { ipfs })\n    expect(out).to.equal(`added ${cid} README.md\\n`)\n  })\n\n  it('adds a file path with progress', async () => {\n    const cid = 'QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB'\n\n    ipfs.addAll.withArgs(matchIterable(), defaultOptions).returns([{\n      cid,\n      path: 'README.md'\n    }])\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const out = await cli('add README.md', { ipfs })\n    expect(out).to.equal(`added ${cid} README.md\\n`)\n  })\n\n  it('add multiple', async () => {\n    const cid1 = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n    const cid2 = CID.parse('QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o')\n\n    ipfs.addAll.withArgs(matchIterable(), {\n      ...defaultOptions,\n      progress: sinon.match.func,\n      wrapWithDirectory: true\n    }).returns([{\n      cid: cid1,\n      path: 'README.md'\n    }, {\n      cid: cid2,\n      path: 'package.json'\n    }])\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const out = await cli('add README.md package.json --wrap-with-directory', { ipfs })\n    expect(out).to.include(`added ${cid1} README.md\\n`)\n    expect(out).to.include(`added ${cid2} package.json\\n`)\n  })\n\n  it('add with cid-version=1', async () => {\n    const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB').toV1()\n\n    ipfs.addAll.withArgs(matchIterable(), {\n      ...defaultOptions,\n      cidVersion: 1,\n      rawLeaves: true\n    }).returns([{\n      cid,\n      path: 'README.md'\n    }])\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const out = await cli('add README.md --cid-version=1', { ipfs })\n    expect(out).to.equal(`added ${cid.toString(base58btc)} README.md\\n`)\n  })\n\n  it('add with cid-version=1 and raw-leaves=false', async () => {\n    const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB').toV1()\n\n    ipfs.addAll.withArgs(matchIterable(), {\n      ...defaultOptions,\n      cidVersion: 1,\n      rawLeaves: false\n    }).returns([{\n      cid,\n      path: 'README.md'\n    }])\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const out = await cli('add README.md --cid-version=1 --raw-leaves=false', { ipfs })\n    expect(out).to.equal(`added ${cid.toString(base58btc)} README.md\\n`)\n  })\n\n  it('add with cid-version=1 and raw-leaves=true', async () => {\n    const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB').toV1()\n\n    ipfs.addAll.withArgs(matchIterable(), {\n      ...defaultOptions,\n      cidVersion: 1,\n      rawLeaves: true\n    }).returns([{\n      cid,\n      path: 'README.md'\n    }])\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const out = await cli('add README.md --cid-version=1 --raw-leaves=true', { ipfs })\n    expect(out).to.equal(`added ${cid.toString(base58btc)} README.md\\n`)\n  })\n\n  it('add from pipe', async () => {\n    const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n    ipfs.addAll.withArgs(sinon.match([{\n      content: matchIterable(),\n      mtime: undefined,\n      mode: undefined\n    }]), defaultOptions).returns([{\n      cid,\n      path: 'README.md'\n    }])\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const proc = cli('add', {\n      ipfs,\n      getStdin: function * () {\n        yield uint8ArrayFromString('hello\\n')\n      }\n    })\n\n    const out = await proc\n    expect(out).to.equal(`added ${cid} ${cid}\\n`)\n  })\n\n  it('add from pipe with mtime=100', async () => {\n    const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n    ipfs.addAll.withArgs(sinon.match([{\n      content: matchIterable(),\n      mtime: { secs: 100, nsecs: undefined },\n      mode: undefined\n    }]), defaultOptions).returns([{\n      cid,\n      path: 'README.md'\n    }])\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const proc = cli('add --mtime=100', {\n      ipfs,\n      getStdin: function * () {\n        yield uint8ArrayFromString('hello\\n')\n      }\n    })\n\n    const out = await proc\n    expect(out).to.equal(`added ${cid} ${cid}\\n`)\n  })\n\n  it('add --quiet', async () => {\n    const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n    ipfs.addAll.withArgs(matchIterable(), defaultOptions).returns([{\n      cid,\n      path: 'README.md'\n    }])\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const out = await cli('add --quiet README.md', { ipfs })\n    expect(out).to.equal(`${cid}\\n`)\n  })\n\n  it('add --quiet (short option)', async () => {\n    const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n    ipfs.addAll.withArgs(matchIterable(), defaultOptions).returns([{\n      cid,\n      path: 'README.md'\n    }])\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const out = await cli('add -q README.md', { ipfs })\n    expect(out).to.equal(`${cid}\\n`)\n  })\n\n  it('add --quieter', async () => {\n    const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n    ipfs.addAll.withArgs(matchIterable(), defaultOptions).returns([{\n      cid,\n      path: 'README.md'\n    }])\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const out = await cli('add --quieter README.md', { ipfs })\n    expect(out).to.equal(`${cid}\\n`)\n  })\n\n  it('add --quieter (short option)', async () => {\n    const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n    ipfs.addAll.withArgs(matchIterable(), defaultOptions).returns([{\n      cid,\n      path: 'README.md'\n    }])\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const out = await cli('add -Q README.md', { ipfs })\n    expect(out).to.equal(`${cid}\\n`)\n  })\n\n  it('add --silent', async () => {\n    const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n    ipfs.addAll.withArgs(matchIterable(), defaultOptions).returns([{\n      cid,\n      path: 'README.md'\n    }])\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const out = await cli('add --silent README.md', { ipfs })\n    expect(out).to.be.empty()\n  })\n\n  it('add --only-hash outputs correct hash', async () => {\n    const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n    ipfs.addAll.withArgs(matchIterable(), {\n      ...defaultOptions,\n      onlyHash: true\n    }).returns([{\n      cid,\n      path: 'README.md'\n    }])\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const out = await cli('add --only-hash README.md', { ipfs })\n    expect(out).to.equal(`added ${cid} README.md\\n`)\n  })\n\n  it('add does not pin with --pin=false', async () => {\n    const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n    ipfs.addAll.withArgs(matchIterable(), {\n      ...defaultOptions,\n      pin: false\n    }).returns([{\n      cid,\n      path: 'README.md'\n    }])\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const out = await cli('add --pin false README.md', { ipfs })\n    expect(out).to.equal(`added ${cid} README.md\\n`)\n  })\n\n  it('add with mtime', async () => {\n    const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n    ipfs.addAll.withArgs(matchIterable(), defaultOptions).returns([{\n      cid,\n      path: 'README.md'\n    }])\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const out = await cli('add --mtime 5 README.md', { ipfs })\n    expect(out).to.equal(`added ${cid} README.md\\n`)\n\n    const source = ipfs.addAll.getCall(0).args[0]\n    const input = await first(source)\n    expect(input).to.have.nested.property('mtime.secs', 5)\n  })\n\n  it('add with mtime-nsecs', async () => {\n    const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n    ipfs.addAll.withArgs(matchIterable(), defaultOptions).returns([{\n      cid,\n      path: 'README.md'\n    }])\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const out = await cli('add --mtime 5 --mtime-nsecs 100 README.md', { ipfs })\n    expect(out).to.equal(`added ${cid} README.md\\n`)\n\n    const source = ipfs.addAll.getCall(0).args[0]\n    const input = await first(source)\n    expect(input).to.have.nested.property('mtime.secs', 5)\n    expect(input).to.have.nested.property('mtime.nsecs', 100)\n  })\n\n  it('add with mode', async () => {\n    const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n    ipfs.addAll.withArgs(matchIterable(), defaultOptions).returns([{\n      cid,\n      path: 'README.md'\n    }])\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const out = await cli('add --mode 0655 README.md', { ipfs })\n    expect(out).to.equal(`added ${cid} README.md\\n`)\n\n    const source = ipfs.addAll.getCall(0).args[0]\n    const input = await first(source)\n    expect(input).to.have.property('mode', '0655')\n  })\n\n  HASH_ALGS.forEach((name) => {\n    it(`add with hash=${name} and raw-leaves=false`, async () => {\n      const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n      ipfs.addAll.withArgs(matchIterable(), {\n        ...defaultOptions,\n        hashAlg: name,\n        rawLeaves: false\n      }).returns([{\n        cid,\n        path: 'README.md'\n      }])\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const out = await cli(`add README.md --hash=${name} --raw-leaves=false`, { ipfs })\n      expect(out).to.equal(`added ${cid} README.md\\n`)\n    })\n  })\n\n  it('should add and print CID encoded in specified base', async () => {\n    const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB').toV1()\n\n    ipfs.addAll.withArgs(matchIterable(), {\n      ...defaultOptions,\n      rawLeaves: true,\n      cidVersion: 1\n    }).returns([{\n      cid,\n      path: 'README.md'\n    }])\n    ipfs.bases.getBase.withArgs('base64').returns(base64)\n\n    const out = await cli('add --cid-base=base64 --cid-version=1 README.md', { ipfs })\n    expect(out).to.equal(`added ${cid.toString(base64)} README.md\\n`)\n  })\n\n  it('should add with a timeout', async () => {\n    const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n    ipfs.addAll.withArgs(matchIterable(), {\n      ...defaultOptions,\n      timeout: 1000\n    }).returns([{\n      cid,\n      path: 'README.md'\n    }])\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const out = await cli('add  README.md --timeout=1s', { ipfs })\n    expect(out).to.equal(`added ${cid} README.md\\n`)\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/bitswap.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { CID } from 'multiformats/cid'\nimport { base58btc } from 'multiformats/bases/base58'\nimport { base64 } from 'multiformats/bases/base64'\nimport { cli } from './utils/cli.js'\nimport sinon from 'sinon'\nimport { peerIdFromString } from '@libp2p/peer-id'\n\ndescribe('bitswap', () => {\n  const peerId = peerIdFromString('QmUBdnXXPyoDFXj3Hj39dNJ5VkN3QFRskXxcGaYFBB8CNA')\n  const key0 = CID.parse('QmUBdnXXPyoDFXj3Hj39dNJ5VkN3QFRskXxcGaYFBB8CNR')\n  const key1 = CID.parse('zb2rhafnd6kEUujnoMkozHnWXY7XpWttyVDWKXfChqA42VTDU')\n\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      bitswap: {\n        wantlist: sinon.stub(),\n        wantlistForPeer: sinon.stub(),\n        stat: sinon.stub(),\n        unwant: sinon.stub()\n      },\n      bases: {\n        getBase: sinon.stub()\n      }\n    }\n  })\n\n  describe('wantlist', () => {\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('should return the wantlist', async () => {\n      ipfs.bitswap.wantlist.withArgs(defaultOptions).resolves([\n        key0,\n        key1\n      ])\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const out = await cli('bitswap wantlist', { ipfs })\n      expect(out).to.include(key0.toString(base58btc))\n      expect(out).to.include(key1.toString(base58btc))\n    })\n\n    it('should get wantlist with CIDs encoded in specified base', async () => {\n      ipfs.bitswap.wantlist.withArgs({\n        ...defaultOptions\n      }).resolves([\n        key0.toV1(),\n        key1.toV1()\n      ])\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n\n      const out = await cli('bitswap wantlist --cid-base=base64', { ipfs })\n      expect(out).to.include(key0.toV1().toString(base64) + '\\n')\n      expect(out).to.include(key1.toV1().toString(base64) + '\\n')\n    })\n\n    it('wantlist peerid', async () => {\n      ipfs.bitswap.wantlistForPeer.withArgs(peerId, defaultOptions).resolves([])\n\n      const out = await cli(`bitswap wantlist ${peerId.toString()}`, { ipfs })\n      expect(out).to.be.empty()\n    })\n\n    it('wantlist with a timeout', async () => {\n      ipfs.bitswap.wantlist.withArgs({\n        ...defaultOptions,\n        timeout: 1000\n      }).resolves([])\n\n      const out = await cli('bitswap wantlist --timeout=1s', { ipfs })\n      expect(out).to.be.empty()\n    })\n\n    it('wantlist for peer with a timeout', async () => {\n      ipfs.bitswap.wantlistForPeer.withArgs(peerId, {\n        ...defaultOptions,\n        timeout: 1000\n      }).resolves([])\n\n      const out = await cli(`bitswap wantlist ${peerId.toString()} --timeout=1s`, { ipfs })\n      expect(out).to.be.empty()\n    })\n  })\n\n  describe('stat', () => {\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('should return bitswap stats', async () => {\n      ipfs.bitswap.stat.withArgs(defaultOptions).resolves({\n        provideBufLen: BigInt(10),\n        blocksReceived: BigInt(10),\n        blocksSent: BigInt(10),\n        dataReceived: BigInt(10),\n        dupBlksReceived: BigInt(10),\n        dupDataReceived: BigInt(10),\n        dataSent: BigInt(10),\n        wantlist: [\n          key0,\n          key1\n        ],\n        peers: []\n      })\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const out = await cli('bitswap stat', { ipfs })\n\n      expect(out).to.include('bitswap status')\n      expect(out).to.match(/provides buffer:\\s\\d+$/m)\n      expect(out).to.match(/blocks received:\\s\\d+$/m)\n      expect(out).to.match(/blocks sent:\\s\\d+$/m)\n      expect(out).to.match(/data received:\\s\\d+$/m)\n      expect(out).to.match(/data sent:\\s\\d+$/m)\n      expect(out).to.match(/dup blocks received:\\s\\d+$/m)\n      expect(out).to.match(/dup data received:\\s\\d+$/m)\n      expect(out).to.match(/wantlist\\s\\[\\d+\\skeys\\]$/m)\n      expect(out).to.include(key0.toString(base58btc))\n      expect(out).to.include(key1.toString(base58btc))\n      expect(out).to.match(/partners\\s\\[\\d+\\]$/m)\n    })\n\n    it('stat --human', async () => {\n      ipfs.bitswap.stat.withArgs(defaultOptions).resolves({\n        provideBufLen: BigInt(10),\n        blocksReceived: BigInt(10),\n        blocksSent: BigInt(10),\n        dataReceived: BigInt(10),\n        dupBlksReceived: BigInt(10),\n        dupDataReceived: BigInt(10),\n        dataSent: BigInt(10),\n        wantlist: [\n          key0,\n          key1\n        ],\n        peers: []\n      })\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const out = await cli('bitswap stat --human', { ipfs })\n\n      expect(out).to.include('bitswap status')\n      expect(out).to.match(/provides buffer:\\s\\d+$/m)\n      expect(out).to.match(/blocks received:\\s\\d+$/m)\n      expect(out).to.match(/blocks sent:\\s\\d+$/m)\n      expect(out).to.match(/data received:\\s+[\\d.]+\\s[PTGMK]?B$/m)\n      expect(out).to.match(/data sent:\\s+[\\d.]+\\s[PTGMK]?B$/m)\n      expect(out).to.match(/dup blocks received:\\s\\d+$/m)\n      expect(out).to.match(/dup data received:\\s+[\\d.]+\\s[PTGMK]?B$/m)\n      expect(out).to.match(/wantlist\\s\\[\\d+\\skeys\\]$/m)\n      expect(out).to.match(/partners\\s\\[\\d+\\]$/m)\n    })\n\n    it('should get stats with wantlist CIDs encoded in specified base', async () => {\n      ipfs.bitswap.stat.withArgs(defaultOptions).resolves({\n        provideBufLen: BigInt(10),\n        blocksReceived: BigInt(10),\n        blocksSent: BigInt(10),\n        dataReceived: BigInt(10),\n        dupBlksReceived: BigInt(10),\n        dupDataReceived: BigInt(10),\n        dataSent: BigInt(10),\n        wantlist: [\n          key0.toV1(),\n          key1.toV1()\n        ],\n        peers: []\n      })\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n\n      const out = await cli('bitswap stat --cid-base=base64', { ipfs })\n      expect(out).to.include(key1.toV1().toString(base64))\n    })\n\n    it('should return bitswap stats with a timeout', async () => {\n      ipfs.bitswap.stat.withArgs({\n        ...defaultOptions,\n        timeout: 1000\n      }).resolves({\n        provideBufLen: BigInt(10),\n        blocksReceived: BigInt(10),\n        blocksSent: BigInt(10),\n        dataReceived: BigInt(10),\n        dupBlksReceived: BigInt(10),\n        dupDataReceived: BigInt(10),\n        dataSent: BigInt(10),\n        wantlist: [\n          key0,\n          key1\n        ],\n        peers: []\n      })\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const out = await cli('bitswap stat --timeout=1s', { ipfs })\n\n      expect(out).to.include('bitswap status')\n      expect(out).to.match(/provides buffer:\\s\\d+$/m)\n      expect(out).to.match(/blocks received:\\s\\d+$/m)\n      expect(out).to.match(/blocks sent:\\s\\d+$/m)\n      expect(out).to.match(/data received:\\s\\d+$/m)\n      expect(out).to.match(/data sent:\\s\\d+$/m)\n      expect(out).to.match(/dup blocks received:\\s\\d+$/m)\n      expect(out).to.match(/dup data received:\\s\\d+$/m)\n      expect(out).to.match(/wantlist\\s\\[\\d+\\skeys\\]$/m)\n      expect(out).to.include(key0.toString(base58btc))\n      expect(out).to.include(key1.toString(base58btc))\n      expect(out).to.match(/partners\\s\\[\\d+\\]$/m)\n    })\n  })\n\n  describe('unwant', () => {\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('should unwant a block', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const out = await cli('bitswap unwant ' + key0, { ipfs })\n      expect(out).to.eql(`Key ${key0} removed from wantlist\\n`)\n      expect(ipfs.bitswap.unwant.called).to.be.true()\n    })\n\n    it('should unwant a block with a timeout', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const out = await cli(`bitswap unwant ${key0} --timeout=1s`, { ipfs })\n      expect(out).to.eql(`Key ${key0} removed from wantlist\\n`)\n      expect(ipfs.bitswap.unwant.called).to.be.true()\n      expect(ipfs.bitswap.unwant.getCall(0).args).to.deep.equal([key0, {\n        ...defaultOptions,\n        timeout: 1000\n      }])\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/block.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { CID } from 'multiformats/cid'\nimport { base58btc } from 'multiformats/bases/base58'\nimport { base64 } from 'multiformats/bases/base64'\nimport { cli, fail } from './utils/cli.js'\nimport sinon from 'sinon'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\n\ndescribe('block', () => {\n  const cid = CID.parse('QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kp')\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      block: {\n        get: sinon.stub(),\n        put: sinon.stub(),\n        rm: sinon.stub(),\n        stat: sinon.stub()\n      },\n      bases: {\n        getBase: sinon.stub()\n      }\n    }\n  })\n\n  describe('put', () => {\n    const defaultOptions = {\n      format: 'dag-pb',\n      mhtype: 'sha2-256',\n      version: 0,\n      pin: false,\n      timeout: undefined\n    }\n\n    it('should put a file', async () => {\n      ipfs.block.put.withArgs(sinon.match.any, defaultOptions).resolves(cid)\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const out = await cli('block put README.md', { ipfs })\n      expect(out).to.eql(`${cid}\\n`)\n    })\n\n    it('put with flags, format and mhtype', async () => {\n      ipfs.block.put.withArgs(sinon.match.any, {\n        ...defaultOptions,\n        format: 'eth-block',\n        mhtype: 'keccak-256'\n      }).resolves(cid)\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const out = await cli('block put --format eth-block --mhtype keccak-256 README.md', { ipfs })\n      expect(out).to.eql(`${cid}\\n`)\n    })\n\n    it('should put and print CID encoded in specified base', async () => {\n      ipfs.block.put.withArgs(sinon.match.any, defaultOptions).resolves(cid.toV1())\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n\n      const out = await cli('block put README.md --cid-base=base64', { ipfs })\n      expect(out).to.eql(`${cid.toV1().toString(base64)}\\n`)\n    })\n\n    it('should put and pin the block', async () => {\n      ipfs.block.put.withArgs(sinon.match.any, {\n        ...defaultOptions,\n        pin: true\n      }).resolves(cid)\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const out = await cli('block put README.md --pin', { ipfs })\n      expect(out).to.eql(`${cid}\\n`)\n    })\n\n    it('put with a timeout', async () => {\n      ipfs.block.put.withArgs(sinon.match.any, {\n        ...defaultOptions,\n        timeout: 1000\n      }).resolves(cid)\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const out = await cli('block put --timeout=1s README.md', { ipfs })\n      expect(out).to.eql(`${cid}\\n`)\n    })\n  })\n\n  describe('get', () => {\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('should get a block', async () => {\n      ipfs.block.get.withArgs(cid, defaultOptions).resolves(\n        uint8ArrayFromString('hello world\\n')\n      )\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const out = await cli(`block get ${cid}`, { ipfs })\n      expect(out).to.eql('hello world\\n')\n    })\n\n    it('get prints an error when no block is returned', async () => {\n      const out = await cli(`block get ${cid}`, { ipfs })\n      expect(out).to.eql('Block was unwanted before it could be remotely retrieved\\n')\n    })\n\n    it('should get a block with a timeout', async () => {\n      ipfs.block.get.withArgs(cid, {\n        ...defaultOptions,\n        timeout: 1000\n      }).resolves(\n        uint8ArrayFromString('hello world\\n')\n      )\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const out = await cli(`block get ${cid} --timeout=1s`, { ipfs })\n      expect(out).to.eql('hello world\\n')\n    })\n  })\n\n  describe('stat', () => {\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('should stat a block', async () => {\n      ipfs.block.stat.withArgs(cid, defaultOptions).resolves({\n        cid,\n        size: 12\n      })\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const out = await cli(`block stat ${cid}`, { ipfs })\n      expect(out).to.eql([\n        `Key: ${cid}`,\n        'Size: 12'\n      ].join('\\n') + '\\n')\n    })\n\n    it('should stat and print CID encoded in specified base', async () => {\n      const cid = CID.parse('QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kp').toV1()\n      ipfs.block.stat.withArgs(cid, defaultOptions).resolves({\n        cid,\n        size: 12\n      })\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n\n      const out = await cli(`block stat ${cid} --cid-base=base64`, { ipfs })\n      expect(out).to.eql([\n        'Key: mAXASIKlIkE8vD0ebj4GXaUswGEsNLtHBzSoewPuF0pmhkqRH',\n        'Size: 12'\n      ].join('\\n') + '\\n')\n    })\n\n    it('should stat a block with a timeout', async () => {\n      ipfs.block.stat.withArgs(cid, {\n        ...defaultOptions,\n        timeout: 1000\n      }).resolves({\n        cid,\n        size: 12\n      })\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const out = await cli(`block stat ${cid} --timeout=1s`, { ipfs })\n      expect(out).to.eql([\n        `Key: ${cid}`,\n        'Size: 12'\n      ].join('\\n') + '\\n')\n    })\n  })\n\n  describe('rm', () => {\n    const defaultOptions = {\n      force: false,\n      quiet: false,\n      timeout: undefined\n    }\n\n    it('should remove a block', async () => {\n      const cid = CID.parse('QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kp')\n      ipfs.block.rm.withArgs([cid], defaultOptions).returns([{\n        cid,\n        error: false\n      }])\n\n      const out = await cli('block rm QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kp', { ipfs })\n      expect(out).to.eql('removed QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kp\\n')\n    })\n\n    it('rm prints error when removing fails', async () => {\n      const err = new Error('Yikes!')\n      const cid = CID.parse('QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kp')\n      ipfs.block.rm.withArgs([cid], defaultOptions).returns([{\n        cid,\n        error: err\n      }])\n\n      const out = await fail('block rm QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kp', { ipfs })\n      expect(out).to.include(err.message)\n    })\n\n    it('rm quietly', async () => {\n      const cid = CID.parse('QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kp')\n      ipfs.block.rm.withArgs([cid], {\n        ...defaultOptions,\n        quiet: true\n      }).returns([{\n        cid,\n        error: true\n      }])\n\n      const out = await cli('block rm --quiet QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kp', { ipfs })\n      expect(out).to.be.empty()\n    })\n\n    it('rm force', async () => {\n      const cid = CID.parse('QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kh')\n      ipfs.block.rm.withArgs([cid], {\n        ...defaultOptions,\n        force: true\n      }).returns([{\n        cid,\n        error: false\n      }])\n\n      const out = await cli('block rm --force QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kh', { ipfs })\n      expect(out).to.eql('removed QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kh\\n')\n    })\n\n    it('fails to remove non-existent block', async () => {\n      const cid = CID.parse('QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kh')\n      ipfs.block.rm.withArgs([cid]).returns([{\n        cid,\n        error: new Error('block not found')\n      }])\n\n      const out = await fail('block rm QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kh', { ipfs })\n      expect(out).to.include('block not found')\n      expect(out).to.include('some blocks not removed')\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/bootstrap.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { cli } from './utils/cli.js'\nimport sinon from 'sinon'\nimport { multiaddr } from '@multiformats/multiaddr'\n\ndescribe('bootstrap', () => {\n  const peer = multiaddr('/ip4/111.111.111.111/tcp/1001/p2p/QmcyFFKfLDGJKwufn2GeitxvhricsBQyNKTkrD14psikoD')\n  let ipfs\n\n  before(() => {\n    ipfs = {\n      bootstrap: {\n        add: sinon.stub(),\n        list: sinon.stub(),\n        rm: sinon.stub(),\n        clear: sinon.stub(),\n        reset: sinon.stub()\n      }\n    }\n  })\n\n  const defaultList = [\n    '/ip4/104.236.176.52/tcp/4001/p2p/QmSoLnSGccFuZQJzRadHn95W2CrSFmZuTdDWP8HXaHca9z',\n    '/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ',\n    '/ip4/104.236.179.241/tcp/4001/p2p/QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM',\n    '/ip4/162.243.248.213/tcp/4001/p2p/QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm',\n    '/ip4/128.199.219.111/tcp/4001/p2p/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu',\n    '/ip4/104.236.76.40/tcp/4001/p2p/QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64',\n    '/ip4/178.62.158.247/tcp/4001/p2p/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd',\n    '/ip4/178.62.61.185/tcp/4001/p2p/QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3',\n    '/ip4/104.236.151.122/tcp/4001/p2p/QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx',\n    '/ip6/2604:a880:1:20::1f9:9001/tcp/4001/p2p/QmSoLnSGccFuZQJzRadHn95W2CrSFmZuTdDWP8HXaHca9z',\n    '/ip6/2604:a880:1:20::203:d001/tcp/4001/p2p/QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM',\n    '/ip6/2604:a880:0:1010::23:d001/tcp/4001/p2p/QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm',\n    '/ip6/2400:6180:0:d0::151:6001/tcp/4001/p2p/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu',\n    '/ip6/2604:a880:800:10::4a:5001/tcp/4001/p2p/QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64',\n    '/ip6/2a03:b0c0:0:1010::23:1001/tcp/4001/p2p/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd',\n    '/ip6/2a03:b0c0:1:d0::e7:1/tcp/4001/p2p/QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3',\n    '/ip6/2604:a880:1:20::1d9:6001/tcp/4001/p2p/QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx',\n    '/dns4/node0.preload.ipfs.io/tcp/443/wss/p2p/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic',\n    '/dns4/node1.preload.ipfs.io/tcp/443/wss/p2p/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6',\n    '/dns4/node2.preload.ipfs.io/tcp/443/wss/p2p/QmV7gnbW5VTcJ3oyM2Xk1rdFBJ3kTkvxc87UFGsun29STS',\n    '/dns4/node3.preload.ipfs.io/tcp/443/wss/p2p/QmY7JB6MQXhxHvq7dBDh4HpbH29v4yE9JRadAVpndvzySN'\n  ]\n\n  describe('add', () => {\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('add default', async () => {\n      ipfs.bootstrap.reset.withArgs(defaultOptions).returns({\n        Peers: defaultList\n      })\n\n      const out = await cli('bootstrap add --default', { ipfs })\n      expect(out).to.equal(defaultList.join('\\n') + '\\n')\n    })\n\n    it('add another bootstrap node', async () => {\n      ipfs.bootstrap.add.withArgs(peer, defaultOptions).returns({\n        Peers: defaultList.concat(peer)\n      })\n\n      const out = await cli(`bootstrap add ${peer}`, { ipfs })\n      expect(out).to.include(`${peer}\\n`)\n    })\n\n    it('add another bootstrap node with a timeout', async () => {\n      ipfs.bootstrap.add.withArgs(peer, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns({\n        Peers: defaultList.concat(peer)\n      })\n\n      const out = await cli(`bootstrap add ${peer} --timeout=1s`, { ipfs })\n      expect(out).to.include(`${peer}\\n`)\n    })\n  })\n\n  describe('list', () => {\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('lists the bootstrap nodes', async () => {\n      ipfs.bootstrap.list.withArgs(defaultOptions).returns({\n        Peers: defaultList\n      })\n\n      const out = await cli('bootstrap list', { ipfs })\n      expect(out).to.equal(defaultList.join('\\n') + '\\n')\n    })\n\n    it('lists the bootstrap nodes with a timeout', async () => {\n      ipfs.bootstrap.list.withArgs({\n        ...defaultOptions,\n        timeout: 1000\n      }).returns({\n        Peers: defaultList\n      })\n\n      const out = await cli('bootstrap list --timeout=1s', { ipfs })\n      expect(out).to.equal(defaultList.join('\\n') + '\\n')\n    })\n  })\n\n  describe('rm', () => {\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('should remove a bootstrap node', async () => {\n      ipfs.bootstrap.rm.withArgs(peer, defaultOptions).returns({\n        Peers: [\n          peer\n        ]\n      })\n\n      const out = await cli(`bootstrap rm ${peer}`, { ipfs })\n      expect(out).to.include(`${peer}\\n`)\n    })\n\n    it('should remove all bootstrap nodes', async () => {\n      ipfs.bootstrap.clear.withArgs(defaultOptions).returns({\n        Peers: defaultList\n      })\n\n      const outRm = await cli('bootstrap rm --all', { ipfs })\n      expect(outRm).to.equal(defaultList.join('\\n') + '\\n')\n    })\n\n    it('should remove a bootstrap node with a timeout', async () => {\n      ipfs.bootstrap.rm.withArgs(peer, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns({\n        Peers: [\n          peer\n        ]\n      })\n\n      const out = await cli(`bootstrap rm ${peer} --timeout=1s`, { ipfs })\n      expect(out).to.include(`${peer}\\n`)\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/cat.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { CID } from 'multiformats/cid'\nimport { cli, fail } from './utils/cli.js'\nimport sinon from 'sinon'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\n\nconst defaultOptions = {\n  offset: undefined,\n  length: undefined,\n  timeout: undefined,\n  preload: true\n}\n\ndescribe('cat', () => {\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      cat: sinon.stub()\n    }\n  })\n\n  it('should cat a file', async () => {\n    const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n    const buf = uint8ArrayFromString('hello world')\n\n    ipfs.cat.withArgs(cid.toString(), defaultOptions).returns([buf])\n\n    const out = await cli(`cat ${cid}`, { ipfs, raw: true })\n    expect(out).to.deep.equal(buf)\n  })\n\n  it('cat part of a file using `count`', async () => {\n    const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n    const buf = uint8ArrayFromString('hello world')\n\n    ipfs.cat.withArgs(cid.toString(), {\n      ...defaultOptions,\n      offset: 21,\n      length: 5\n    }).returns([buf])\n\n    const out = await cli('cat QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB --offset 21 --count 5', { ipfs, raw: true })\n    expect(out).to.deep.equal(buf)\n  })\n\n  it('cat part of a file using `length`', async () => {\n    const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n    const buf = uint8ArrayFromString('hello world')\n\n    ipfs.cat.withArgs(cid.toString(), {\n      ...defaultOptions,\n      offset: 21,\n      length: 5\n    }).returns([buf])\n\n    const out = await cli('cat QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB --offset 21 --length 5', { ipfs, raw: true })\n    expect(out).to.deep.equal(buf)\n  })\n\n  it('cat non-existent file', async () => {\n    const err = new Error('wat')\n    ipfs.cat.returns(async function * () { // eslint-disable-line require-await,require-yield\n      throw err\n    }())\n\n    const out = await fail('cat QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB/dummy', { ipfs })\n    expect(out).to.equal(`${err.message}\\n`)\n  })\n\n  it('should cat a file with a timeout', async () => {\n    const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n    const buf = uint8ArrayFromString('hello world')\n\n    ipfs.cat.withArgs(cid.toString(), {\n      ...defaultOptions,\n      timeout: 1000\n    }).returns([buf])\n\n    const out = await cli(`cat ${cid} --timeout=1s`, { ipfs, raw: true })\n    expect(out).to.deep.equal(buf)\n  })\n\n  it('should cat a file without preloading', async () => {\n    const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n    const buf = uint8ArrayFromString('hello world')\n\n    ipfs.cat.withArgs(cid.toString(), {\n      ...defaultOptions,\n      preload: false\n    }).returns([buf])\n\n    const out = await cli(`cat ${cid} --preload=false`, { ipfs, raw: true })\n    expect(out).to.deep.equal(buf)\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/cid.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { cli } from './utils/cli.js'\nimport sinon from 'sinon'\nimport { base32 } from 'multiformats/bases/base32'\nimport { base58btc } from 'multiformats/bases/base58'\nimport * as raw from 'multiformats/codecs/raw'\nimport { sha256 } from 'multiformats/hashes/sha2'\nimport * as dagPB from '@ipld/dag-pb'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\n\ndescribe('cid', () => {\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      bases: {\n        listBases: sinon.stub(),\n        getBase: sinon.stub()\n      },\n      codecs: {\n        listCodecs: sinon.stub(),\n        getCodec: sinon.stub()\n      },\n      hashers: {\n        listHashers: sinon.stub(),\n        getHasher: sinon.stub()\n      }\n    }\n  })\n\n  describe('base32', () => {\n    it('should convert a cid to base32', async () => {\n      const out = await cli('cid base32 QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', { ipfs })\n      expect(out.trim()).to.equal('bafybeiczsscdsbs7ffqz55asqdf3smv6klcw3gofszvwlyarci47bgf354')\n    })\n\n    it('should convert a cid to base32 from stdin', async () => {\n      const out = await cli('cid base32', {\n        ipfs,\n        getStdin: function * () {\n          yield uint8ArrayFromString('QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn\\n')\n        }\n      })\n      expect(out.trim()).to.equal('bafybeiczsscdsbs7ffqz55asqdf3smv6klcw3gofszvwlyarci47bgf354')\n    })\n  })\n\n  describe('bases', () => {\n    it('should list bases', async () => {\n      ipfs.bases.listBases.returns([base32])\n\n      const out = await cli('cid bases', { ipfs })\n      expect(out.trim()).to.equal('base32')\n    })\n\n    it('should list bases with prefixes', async () => {\n      ipfs.bases.listBases.returns([base32])\n\n      const out = await cli('cid bases --prefix', { ipfs })\n      expect(out.trim()).to.equal('b\\tbase32')\n    })\n\n    it('should list bases with numeric code', async () => {\n      ipfs.bases.listBases.returns([base32])\n\n      const out = await cli('cid bases --numeric', { ipfs })\n      expect(out.trim()).to.equal('98\\tbase32')\n    })\n\n    it('should list bases with numeric code and prefix', async () => {\n      ipfs.bases.listBases.returns([base32])\n\n      const out = await cli('cid bases --numeric --prefix', { ipfs })\n      expect(out.trim()).to.equal('b\\t98\\tbase32')\n    })\n  })\n\n  describe('codecs', () => {\n    it('should list codecs', async () => {\n      ipfs.codecs.listCodecs.returns([raw])\n\n      const out = await cli('cid codecs', { ipfs })\n      expect(out.trim()).to.equal('raw')\n    })\n\n    it('should list codecs with numeric code', async () => {\n      ipfs.codecs.listCodecs.returns([raw])\n\n      const out = await cli('cid codecs --numeric', { ipfs })\n      expect(out.trim()).to.equal('85\\traw')\n    })\n  })\n\n  describe('format', () => {\n    it('should format cid', async () => {\n      ipfs.bases.listBases.returns([base32, base58btc])\n      ipfs.codecs.listCodecs.returns([raw])\n      ipfs.hashers.listHashers.returns([sha256])\n\n      const out = await cli('cid format QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', { ipfs })\n      expect(out.trim()).to.equal('QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn')\n    })\n\n    it('should format base name', async () => {\n      ipfs.bases.listBases.returns([base32, base58btc])\n      ipfs.codecs.listCodecs.returns([raw])\n      ipfs.hashers.listHashers.returns([sha256])\n\n      const out = await cli('cid format -f \"%b\" QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', { ipfs })\n      expect(out.trim()).to.equal('base58btc')\n    })\n\n    it('should format base prefix', async () => {\n      ipfs.bases.listBases.returns([base32, base58btc])\n      ipfs.codecs.listCodecs.returns([raw])\n      ipfs.hashers.listHashers.returns([sha256])\n\n      const out = await cli('cid format -f \"%B\" QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', { ipfs })\n      expect(out.trim()).to.equal('z')\n    })\n\n    it('should format version string', async () => {\n      ipfs.bases.listBases.returns([base32, base58btc])\n      ipfs.codecs.listCodecs.returns([raw])\n      ipfs.hashers.listHashers.returns([sha256])\n\n      const out = await cli('cid format -f \"%v\" QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', { ipfs })\n      expect(out.trim()).to.equal('cidv0')\n    })\n\n    it('should format version number', async () => {\n      ipfs.bases.listBases.returns([base32, base58btc])\n      ipfs.codecs.listCodecs.returns([raw])\n      ipfs.hashers.listHashers.returns([sha256])\n\n      const out = await cli('cid format -f \"%V\" QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', { ipfs })\n      expect(out.trim()).to.equal('0')\n    })\n\n    it('should format codec name', async () => {\n      ipfs.bases.listBases.returns([base32, base58btc])\n      ipfs.codecs.listCodecs.returns([dagPB])\n      ipfs.hashers.listHashers.returns([sha256])\n\n      const out = await cli('cid format -f \"%c\" QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', { ipfs })\n      expect(out.trim()).to.equal('dag-pb')\n    })\n\n    it('should format codec code', async () => {\n      ipfs.bases.listBases.returns([base32, base58btc])\n      ipfs.codecs.listCodecs.returns([raw])\n      ipfs.hashers.listHashers.returns([sha256])\n\n      const out = await cli('cid format -f \"%C\" QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', { ipfs })\n      expect(out.trim()).to.equal('112')\n    })\n\n    it('should format multihash name', async () => {\n      ipfs.bases.listBases.returns([base32, base58btc])\n      ipfs.codecs.listCodecs.returns([raw])\n      ipfs.hashers.listHashers.returns([sha256])\n\n      const out = await cli('cid format -f \"%h\" QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', { ipfs })\n      expect(out.trim()).to.equal('sha2-256')\n    })\n\n    it('should format multihash name', async () => {\n      ipfs.bases.listBases.returns([base32, base58btc])\n      ipfs.codecs.listCodecs.returns([raw])\n      ipfs.hashers.listHashers.returns([sha256])\n\n      const out = await cli('cid format -f \"%H\" QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', { ipfs })\n      expect(out.trim()).to.equal('18')\n    })\n\n    it('should format multihash digest length', async () => {\n      ipfs.bases.listBases.returns([base32, base58btc])\n      ipfs.codecs.listCodecs.returns([raw])\n      ipfs.hashers.listHashers.returns([sha256])\n\n      const out = await cli('cid format -f \"%L\" QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', { ipfs })\n      expect(out.trim()).to.equal('32')\n    })\n\n    it('should format multihash encoded in default base', async () => {\n      ipfs.bases.listBases.returns([base32, base58btc])\n      ipfs.codecs.listCodecs.returns([raw])\n      ipfs.hashers.listHashers.returns([sha256])\n\n      const out = await cli('cid format -f \"%m\" QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', { ipfs })\n      expect(out.trim()).to.equal('zQmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn')\n    })\n\n    it('should format multihash encoded in base %b', async () => {\n      ipfs.bases.listBases.returns([base32, base58btc])\n      ipfs.codecs.listCodecs.returns([raw])\n      ipfs.hashers.listHashers.returns([sha256])\n\n      const out = await cli('cid format -f \"%m\" -b base32 QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', { ipfs })\n      expect(out.trim()).to.equal('bciqftfeehedf6klbt32bfaglxezl4uwfnwm4lftlmxqbcerz6cmlx3y')\n    })\n\n    // go-ipfs always converts to v1?\n    it.skip('should format multihash encoded in default base without multihash prefix', async () => {\n      ipfs.bases.listBases.returns([base32, base58btc])\n      ipfs.codecs.listCodecs.returns([raw])\n      ipfs.hashers.listHashers.returns([sha256])\n\n      const out = await cli('cid format -f \"%M\" QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', { ipfs })\n      expect(out.trim()).to.equal('bciqftfeehedf6klbt32bfaglxezl4uwfnwm4lftlmxqbcerz6cmlx3y')\n    })\n\n    it('should format multihash encoded in base %b without multihash prefix', async () => {\n      ipfs.bases.listBases.returns([base32, base58btc])\n      ipfs.codecs.listCodecs.returns([raw])\n      ipfs.hashers.listHashers.returns([sha256])\n\n      const out = await cli('cid format -f \"%M\" -b base32 QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', { ipfs })\n      expect(out.trim()).to.equal('ciqftfeehedf6klbt32bfaglxezl4uwfnwm4lftlmxqbcerz6cmlx3y')\n    })\n\n    it('should format hash digest encoded in base %b with multihash prefix', async () => {\n      ipfs.bases.listBases.returns([base32, base58btc])\n      ipfs.codecs.listCodecs.returns([raw])\n      ipfs.hashers.listHashers.returns([sha256])\n\n      const out = await cli('cid format -f \"%d\" QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', { ipfs })\n      expect(out.trim()).to.equal('z72gdmFAgRzYHkJzKiL8MgMMRW3BTSCGyDHroPxJbxMJn')\n    })\n\n    it('should format hash digest encoded in base %b without multihash prefix', async () => {\n      ipfs.bases.listBases.returns([base32, base58btc])\n      ipfs.codecs.listCodecs.returns([raw])\n      ipfs.hashers.listHashers.returns([sha256])\n\n      const out = await cli('cid format -f \"%D\" QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', { ipfs })\n      expect(out.trim()).to.equal('72gdmFAgRzYHkJzKiL8MgMMRW3BTSCGyDHroPxJbxMJn')\n    })\n\n    it('should format cid in default base', async () => {\n      ipfs.bases.listBases.returns([base32, base58btc])\n      ipfs.codecs.listCodecs.returns([raw])\n      ipfs.hashers.listHashers.returns([sha256])\n\n      const out = await cli('cid format -f \"%s\" QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', { ipfs })\n      expect(out.trim()).to.equal('QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn')\n    })\n\n    it('should format cid in specified base', async () => {\n      ipfs.bases.listBases.returns([base32, base58btc])\n      ipfs.codecs.listCodecs.returns([raw])\n      ipfs.hashers.listHashers.returns([sha256])\n\n      const out = await cli('cid format -f \"%s\" -b base32 QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', { ipfs })\n      expect(out.trim()).to.equal('bciqftfeehedf6klbt32bfaglxezl4uwfnwm4lftlmxqbcerz6cmlx3y')\n    })\n\n    it('should format cid in default base without multibase prefix', async () => {\n      ipfs.bases.listBases.returns([base32, base58btc])\n      ipfs.codecs.listCodecs.returns([raw])\n      ipfs.hashers.listHashers.returns([sha256])\n\n      const out = await cli('cid format -f \"%S\" QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', { ipfs })\n      expect(out.trim()).to.equal('QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn')\n    })\n\n    it('should format cid in specified base without multibase prefix', async () => {\n      ipfs.bases.listBases.returns([base32, base58btc])\n      ipfs.codecs.listCodecs.returns([raw])\n      ipfs.hashers.listHashers.returns([sha256])\n\n      const out = await cli('cid format -f \"%S\" -b base32 QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', { ipfs })\n      expect(out.trim()).to.equal('ciqftfeehedf6klbt32bfaglxezl4uwfnwm4lftlmxqbcerz6cmlx3y')\n    })\n\n    it('should format cid prefix', async () => {\n      ipfs.bases.listBases.returns([base32, base58btc])\n      ipfs.codecs.listCodecs.returns([dagPB])\n      ipfs.hashers.listHashers.returns([sha256])\n\n      const out = await cli('cid format -f \"%P\" QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', { ipfs })\n      expect(out.trim()).to.equal('cidv0-dag-pb-sha2-256-32')\n    })\n  })\n\n  describe('hashes', () => {\n    it('should list hashers', async () => {\n      ipfs.hashers.listHashers.returns([sha256])\n\n      const out = await cli('cid hashes', { ipfs })\n      expect(out.trim()).to.equal('sha2-256')\n    })\n\n    it('should list hashers with numeric code', async () => {\n      ipfs.hashers.listHashers.returns([sha256])\n\n      const out = await cli('cid hashes --numeric', { ipfs })\n      expect(out.trim()).to.equal('18\\tsha2-256')\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/config.spec.js",
    "content": "/* eslint-env mocha */\n/* eslint max-nested-callbacks: [\"error\", 5] */\n\nimport { expect } from 'aegir/chai'\nimport { cli, fail } from './utils/cli.js'\nimport sinon from 'sinon'\nimport { profiles } from 'ipfs-core/config/profiles'\nimport { readFile } from 'fs/promises'\nimport path from 'path'\n\ndescribe('config', () => {\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      config: {\n        set: sinon.stub(),\n        getAll: sinon.stub(),\n        get: sinon.stub(),\n        replace: sinon.stub(),\n        profiles: {\n          apply: sinon.stub(),\n          list: sinon.stub()\n        }\n      }\n    }\n  })\n\n  describe('set', () => {\n    it('set a config key with a string value', async () => {\n      await cli('config foo bar', { ipfs })\n      expect(ipfs.config.set.calledWith('foo', 'bar')).to.be.true()\n    })\n\n    it('set a config key with true', async () => {\n      await cli('config foo true --bool', { ipfs })\n      expect(ipfs.config.set.calledWith('foo', true)).to.be.true()\n    })\n\n    it('set a config key with false', async () => {\n      await cli('config foo false --bool', { ipfs })\n      expect(ipfs.config.set.calledWith('foo', false)).to.be.true()\n    })\n\n    it('set a config key with null', async () => {\n      await cli('config foo null --json', { ipfs })\n      expect(ipfs.config.set.calledWith('foo', null)).to.be.true()\n    })\n\n    it('set a config key with json', async () => {\n      await cli('config foo {\"bar\":0} --json', { ipfs })\n      expect(ipfs.config.set.calledWith('foo', { bar: 0 })).to.be.true()\n    })\n\n    it('set a config key with invalid json', async () => {\n      await fail('config foo {\"bar:0\"} --json', { ipfs })\n    })\n\n    it('set a config key with a timeout', async () => {\n      await cli('config foo bar --timeout=1s', { ipfs })\n      expect(ipfs.config.set.calledWith('foo', 'bar', {\n        timeout: 1000\n      })).to.be.true()\n    })\n  })\n\n  describe('get', () => {\n    it('get a config key value', async () => {\n      ipfs.config.get.withArgs('Identity.PeerID').returns('hello')\n\n      const out = await cli('config Identity.PeerID', { ipfs })\n      expect(out).to.equal('hello\\n')\n    })\n\n    it('call config with no arguments', async () => {\n      const out = await fail('config', { ipfs })\n      expect(out).to.include('Not enough non-option arguments: got 0, need at least 1')\n    })\n\n    it('get a config key value with a timeout', async () => {\n      ipfs.config.get.withArgs('Identity.PeerID', {\n        timeout: 1000\n      }).returns('hello')\n\n      const out = await cli('config Identity.PeerID --timeout=1s', { ipfs })\n      expect(out).to.equal('hello\\n')\n    })\n  })\n\n  describe('show', function () {\n    it('returns the full config', async () => {\n      ipfs.config.getAll.withArgs({\n        timeout: undefined\n      }).returns({ foo: 'bar' })\n      const out = await cli('config show', { ipfs })\n      expect(JSON.parse(out)).to.be.eql({ foo: 'bar' })\n    })\n\n    it('returns the full config with a timeout', async () => {\n      ipfs.config.getAll.withArgs({\n        timeout: 1000\n      }).returns({ foo: 'bar' })\n      const out = await cli('config show --timeout=1s', { ipfs })\n      expect(JSON.parse(out)).to.be.eql({ foo: 'bar' })\n    })\n  })\n\n  describe('replace', () => {\n    it('replace config with file', async () => {\n      const filePath = './package.json'\n      const expectedConfig = JSON.parse(await readFile(path.resolve(process.cwd(), filePath)))\n\n      await cli(`config replace ${filePath}`, { ipfs })\n\n      expect(ipfs.config.replace.calledWith(expectedConfig, {\n        timeout: undefined\n      })).to.be.true()\n    })\n\n    it('replace config with file in daemon mode', async () => {\n      const filePath = './package.json'\n      const fullPath = path.resolve(process.cwd(), filePath)\n\n      await cli(`config replace ${filePath}`, { ipfs, isDaemon: true })\n\n      expect(ipfs.config.replace.calledWith(fullPath, {\n        timeout: undefined\n      })).to.be.true()\n    })\n\n    it('replace config with file and timeout', async () => {\n      const filePath = './package.json'\n      const expectedConfig = JSON.parse(await readFile(path.resolve(process.cwd(), filePath)))\n\n      await cli(`config replace ${filePath} --timeout=1s`, { ipfs })\n\n      expect(ipfs.config.replace.calledWith(expectedConfig, {\n        timeout: 1000\n      })).to.be.true()\n    })\n  })\n\n  describe('profile', () => {\n    describe('apply', () => {\n      const defaultOptions = {\n        dryRun: false,\n        timeout: undefined\n      }\n\n      Object.keys(profiles).forEach(profile => {\n        it(`applies profile '${profile}'`, async () => {\n          ipfs.config.profiles.apply.withArgs(profile, defaultOptions).returns({\n            original: {\n              foo: 'bar'\n            },\n            updated: {\n              foo: 'baz'\n            }\n          })\n\n          await cli(`config profile apply ${profile}`, { ipfs })\n\n          expect(ipfs.config.profiles.apply.calledWith(profile, defaultOptions)).to.be.true()\n        })\n      })\n\n      it('--dry-run is passed to core', async () => {\n        ipfs.config.profiles.apply.withArgs('server', {\n          ...defaultOptions,\n          dryRun: true\n        }).returns({\n          original: {\n            foo: 'bar'\n          },\n          updated: {\n            foo: 'baz'\n          }\n        })\n\n        await cli('config profile apply server --dry-run=true', { ipfs })\n\n        expect(ipfs.config.profiles.apply.calledWith('server', {\n          ...defaultOptions,\n          dryRun: true\n        })).to.be.true()\n      })\n\n      it('--timeout is passed to core', async () => {\n        ipfs.config.profiles.apply.withArgs('server', {\n          ...defaultOptions,\n          timeout: 1000\n        }).returns({\n          original: {\n            foo: 'bar'\n          },\n          updated: {\n            foo: 'baz'\n          }\n        })\n\n        await cli('config profile apply server --timeout=1s', { ipfs })\n\n        expect(ipfs.config.profiles.apply.calledWith('server', {\n          ...defaultOptions,\n          timeout: 1000\n        })).to.be.true()\n      })\n    })\n\n    describe('list', () => {\n      it('lists available config profiles', async () => {\n        ipfs.config.profiles.list.withArgs({\n          timeout: undefined\n        }).returns(\n          Object.keys(profiles).map(profile => {\n            return {\n              name: profiles[profile].name,\n              description: profiles[profile].description\n            }\n          })\n        )\n        const out = await cli('config profile ls', { ipfs })\n\n        Object.keys(profiles => profile => {\n          expect(out).includes(profiles[profile].name)\n          expect(out).includes(profiles[profile].description)\n        })\n      })\n\n      it('lists available config profiles with a timeout', async () => {\n        ipfs.config.profiles.list.withArgs({\n          timeout: 1000\n        }).returns(\n          Object.keys(profiles).map(profile => {\n            return {\n              name: profiles[profile].name,\n              description: profiles[profile].description\n            }\n          })\n        )\n        const out = await cli('config profile ls --timeout=1s', { ipfs })\n\n        Object.keys(profiles => profile => {\n          expect(out).includes(profiles[profile].name)\n          expect(out).includes(profiles[profile].description)\n        })\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/daemon.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport os from 'os'\nimport path from 'path'\nimport { nanoid } from 'nanoid'\nimport fs from 'fs'\nimport { clean } from './utils/clean.js'\nimport { ipfsExec } from './utils/ipfs-exec.js'\nimport { isWindows } from './utils/platforms.js'\nimport tempWrite from 'temp-write'\n\n/**\n * @param {*} daemon\n * @param {*} [options]\n */\nconst daemonReady = async (daemon, options) => {\n  options = options || {}\n\n  let stdout = ''\n  let isReady = false\n\n  const readyPromise = new Promise((resolve, reject) => {\n    daemon.stdout.on('data', async data => {\n      stdout += data\n\n      if (stdout.includes('Daemon is ready') && !isReady) {\n        isReady = true\n\n        if (options.onReady) {\n          try {\n            await options.onReady(stdout)\n          } catch (/** @type {any} */ err) {\n            return reject(err)\n          }\n        }\n\n        resolve()\n      }\n    })\n\n    daemon.stderr.on('data', (data) => {\n      if (process && process.env && process.env.DEBUG) {\n        // causes data to be written out to stderr\n        return\n      }\n\n      if (!data.toString().includes('ExperimentalWarning')) {\n        reject(new Error('Daemon didn\\'t start ' + data))\n      }\n    })\n  })\n\n  try {\n    await readyPromise\n    daemon.kill(options.killSignal)\n    await daemon\n    return stdout\n  } catch (/** @type {any} */ err) {\n    // Windows does not support sending signals, but Node.js offers some\n    // emulation. Sending SIGINT, SIGTERM, and SIGKILL cause the unconditional\n    // termination of the target process.\n    // https://nodejs.org/dist/latest/docs/api/process.html#process_signal_events\n    // i.e. The process will exit with non-zero code (normally our signal\n    // handlers cleanly exit)\n    if (isWindows && isReady) {\n      return stdout\n    }\n    throw err\n  }\n}\nconst checkLock = (repo) => {\n  // skip on windows\n  // https://github.com/ipfs/js-ipfsd-ctl/pull/155#issuecomment-326983530\n  if (!isWindows) {\n    if (fs.existsSync(path.join(repo, 'repo.lock'))) {\n      throw new Error('repo.lock not removed')\n    }\n    if (fs.existsSync(path.join(repo, 'api'))) {\n      throw new Error('api file not removed')\n    }\n  }\n}\n\nasync function testSignal (ipfs, killSignal) {\n  await ipfs('init')\n  await ipfs(['config', 'Addresses', JSON.stringify({\n    API: '/ip4/127.0.0.1/tcp/0',\n    Gateway: '/ip4/127.0.0.1/tcp/0'\n  }), '--json'].join(' '))\n\n  const daemon = ipfs('daemon')\n  return daemonReady(daemon, { killSignal })\n}\n\ndescribe.skip('daemon', () => {\n  /** @type {string} */\n  let repoPath\n  /** @type {ReturnType<ipfsExec>} */\n  let ipfs\n\n  beforeEach(() => {\n    repoPath = path.join(os.tmpdir(), 'ipfs-test-not-found-' + nanoid())\n    ipfs = ipfsExec(repoPath)\n  })\n\n  afterEach(() => clean(repoPath))\n\n  it('should not crash if Addresses.Swarm is empty', async function () {\n    if (isWindows) return this.skip()\n    this.timeout(100 * 1000)\n\n    await ipfs('init')\n    await ipfs(['config', 'Addresses', JSON.stringify({\n      Swarm: [],\n      API: '/ip4/127.0.0.1/tcp/0',\n      Gateway: '/ip4/127.0.0.1/tcp/0'\n    }), '--json'].join(' '))\n\n    const daemon = ipfs('daemon')\n    await daemonReady(daemon)\n  })\n\n  it('should allow bind to multiple addresses for API and Gateway', async function () {\n    this.timeout(100 * 1000)\n\n    const apiAddrs = [\n      '/ip4/127.0.0.1/tcp/0',\n      '/dns4/localhost/tcp/0'\n    ]\n\n    const gatewayAddrs = [\n      '/ip4/127.0.0.1/tcp/0',\n      '/dns4/localhost/tcp/0'\n    ]\n\n    await ipfs('init')\n    await ipfs(`config Addresses.API ${JSON.stringify(apiAddrs)} --json`)\n    await ipfs(`config Addresses.Gateway ${JSON.stringify(gatewayAddrs)} --json`)\n\n    const daemon = ipfs('daemon')\n    const stdout = await daemonReady(daemon)\n\n    apiAddrs.forEach(addr => expect(stdout).to.include(`API listening on ${addr.slice(0, -2)}`))\n    gatewayAddrs.forEach(addr => expect(stdout).to.include(`Gateway (read only) listening on ${addr.slice(0, -2)}`))\n  })\n\n  it('should allow no bind addresses for API and Gateway', async function () {\n    this.timeout(100 * 1000)\n\n    await ipfs('init')\n    await ipfs('config Addresses.API [] --json')\n    await ipfs('config Addresses.Gateway [] --json')\n\n    const daemon = ipfs('daemon')\n    const stdout = await daemonReady(daemon)\n\n    expect(stdout).to.not.include(/(API|Gateway \\(read only\\)) listening on/g)\n  })\n\n  it('should handle SIGINT gracefully', async function () {\n    if (isWindows) return this.skip()\n    this.timeout(100 * 1000)\n\n    await testSignal(ipfs, 'SIGINT')\n\n    checkLock(repoPath)\n  })\n\n  it('should handle SIGTERM gracefully', async function () {\n    if (isWindows) return this.skip()\n    this.timeout(100 * 1000)\n\n    await testSignal(ipfs, 'SIGTERM')\n\n    checkLock(repoPath)\n  })\n\n  it('should handle SIGHUP gracefully', async function () {\n    if (isWindows) return this.skip()\n    this.timeout(100 * 1000)\n\n    await testSignal(ipfs, 'SIGHUP')\n\n    checkLock(repoPath)\n  })\n\n  it('should be silent', async function () {\n    if (process && process.env && process.env.DEBUG) return this.skip()\n\n    this.timeout(100 * 1000)\n    await ipfs('init')\n\n    const daemon = ipfs('daemon --silent')\n\n    setTimeout(() => {\n      daemon.kill('SIGKILL')\n    }, 5 * 1000)\n\n    await expect(daemon)\n      .to.eventually.be.rejected()\n      .and.to.include({\n        killed: true,\n        stdout: ''\n      })\n  })\n\n  it('should present ipfs path help when option help is received', async function () {\n    this.timeout(100 * 1000)\n\n    const result = await ipfs('daemon --help')\n\n    expect(result).to.include('export IPFS_PATH=/path/to/ipfsrepo')\n  })\n\n  it('should print version info', async function () {\n    this.timeout(100 * 1000)\n    await ipfs('init')\n\n    const daemon = ipfs('daemon')\n    const stdout = await daemonReady(daemon)\n\n    expect(stdout).to.include('js-ipfs version:')\n    expect(stdout).to.include(`System version: ${os.arch()}/${os.platform()}`)\n    expect(stdout).to.include(`Node.js version: ${process.versions.node}`)\n  })\n\n  it('should init by default', async function () {\n    this.timeout(100 * 1000)\n\n    expect(fs.existsSync(repoPath)).to.be.false()\n\n    const daemon = ipfs('daemon')\n    await daemonReady(daemon)\n\n    expect(fs.existsSync(repoPath)).to.be.true()\n  })\n\n  it('should init with custom config', async function () {\n    this.timeout(100 * 1000)\n    const configPath = tempWrite.sync('{\"Addresses\": {\"API\": \"/ip4/127.0.0.1/tcp/9999\"}}', 'config.json')\n    const daemon = ipfs(`daemon --init-config ${configPath}`)\n\n    await daemonReady(daemon, {\n      async onReady () {\n        const out = await ipfs('config \\'Addresses.API\\'')\n        expect(out).to.be.eq('/ip4/127.0.0.1/tcp/9999\\n')\n      }\n    })\n  })\n\n  it('should init with profiles', async function () {\n    this.timeout(100 * 1000)\n    const daemon = ipfs('daemon --init-profile test')\n\n    await daemonReady(daemon, {\n      async onReady () {\n        const out = await ipfs('config Bootstrap')\n        expect(out).to.be.eq('[]\\n')\n      }\n    })\n  })\n\n  it('should print help when command is unknown', async function () {\n    this.timeout(100 * 1000)\n\n    const err = await ipfs.fail('derp')\n\n    expect(err).to.have.property('stderr').that.includes('Commands:')\n    expect(err).to.have.property('stderr').that.includes('Unknown argument: derp')\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/dag.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { cli } from './utils/cli.js'\nimport * as dagCBOR from '@ipld/dag-cbor'\nimport * as dagPB from '@ipld/dag-pb'\nimport sinon from 'sinon'\nimport { CID } from 'multiformats/cid'\nimport * as raw from 'multiformats/codecs/raw'\nimport { base58btc } from 'multiformats/bases/base58'\nimport { base64 } from 'multiformats/bases/base64'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\nimport { matchIterable } from './utils/match-iterable.js'\n\ndescribe('dag', () => {\n  const dagPbCid = CID.parse('Qmaj2NmcyAXT8dFmZRRytE12wpcaHADzbChKToMEjBsj5Z')\n  const rawCid = CID.createV1(raw.code, dagPbCid.multihash)\n  const dagCborCid = CID.createV1(dagCBOR.code, dagPbCid.multihash)\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      dag: {\n        get: sinon.stub(),\n        resolve: sinon.stub(),\n        put: sinon.stub(),\n        import: sinon.stub(),\n        export: sinon.stub()\n      },\n      bases: {\n        getBase: sinon.stub()\n      }\n    }\n  })\n\n  describe('get', () => {\n    const defaultOptions = {\n      localResolve: false,\n      timeout: undefined,\n      path: undefined\n    }\n\n    it('should get a raw node', async () => {\n      const result = {\n        value: uint8ArrayFromString('hello world')\n      }\n\n      ipfs.dag.get.withArgs(rawCid, defaultOptions).returns(result)\n\n      const out = await cli(`dag get ${rawCid} --output-codec raw --data-enc base16`, { ipfs })\n\n      expect(out).to.equal(uint8ArrayToString(result.value, 'base16'))\n    })\n\n    it('should get a dag-pb node', async () => {\n      const result = {\n        value: {\n          Data: Buffer.from([0, 1, 3]),\n          Links: [{\n            Hash: dagCborCid,\n            Name: 'foo',\n            Tsize: 10\n          }]\n        }\n      }\n\n      ipfs.dag.get.withArgs(dagPbCid, defaultOptions).returns(result)\n\n      const out = await cli(`dag get ${dagPbCid}`, { ipfs })\n\n      expect(out).to.equal(`{\"Data\":{\"/\":{\"bytes\":\"AAED\"}},\"Links\":[{\"Hash\":{\"/\":\"${dagCborCid.toString()}\"},\"Name\":\"foo\",\"Tsize\":10}]}`)\n    })\n\n    it('should get a dag-pb node as dag-pb', async () => {\n      const result = {\n        value: {\n          Data: Buffer.from([0, 1, 3]),\n          Links: [{\n            Hash: dagCborCid,\n            Name: 'foo',\n            Tsize: 10\n          }]\n        }\n      }\n\n      ipfs.dag.get.withArgs(dagPbCid, defaultOptions).returns(result)\n\n      const out = await cli(`dag get ${dagPbCid} --output-codec dag-pb`, { ipfs, raw: true })\n\n      expect(out).to.deep.equal(Buffer.from('122d0a2401711220b80784f97f67ad80d52575d643044ffb37b20f8d4db32ae59e47b1ac68df20e01203666f6f180a0a03000103', 'hex'))\n    })\n\n    it('should get a dag-pb node as dag-cbor', async () => {\n      const result = {\n        value: {\n          Data: Buffer.from([0, 1, 3]),\n          Links: [{\n            Hash: dagCborCid,\n            Name: 'foo',\n            Tsize: 10\n          }]\n        }\n      }\n\n      ipfs.dag.get.withArgs(dagPbCid, defaultOptions).returns(result)\n\n      const out = await cli(`dag get ${dagPbCid} --output-codec dag-cbor`, { ipfs, raw: true })\n\n      expect(out).to.deep.equal(Buffer.from('a2644461746143000103654c696e6b7381a36448617368d82a58250001711220b80784f97f67ad80d52575d643044ffb37b20f8d4db32ae59e47b1ac68df20e0644e616d6563666f6f655473697a650a', 'hex'))\n    })\n\n    it('should fail to get a non bytes node with \"raw\"', async () => {\n      const result = {\n        value: {\n          Data: Buffer.from([0, 1, 3]),\n          Links: [{\n            Hash: dagCborCid,\n            Name: 'foo',\n            Tsize: 10\n          }]\n        }\n      }\n\n      ipfs.dag.get.withArgs(dagPbCid, defaultOptions).returns(result)\n\n      const out = await cli(`dag get ${dagPbCid} --output-codec raw --data-enc base16`, { ipfs })\n\n      expect(out).to.equal('dag get cannot print a non-bytes node as \"raw\"\\n')\n    })\n\n    it('should get a bytes node of a non-bytes block with \"raw\"', async () => {\n      // in this instance we're pretending to path into a 'Data' property of a dag-pb block\n      const result = {\n        value: Buffer.from([0, 1, 3])\n      }\n\n      ipfs.dag.get.withArgs(dagPbCid, { ...defaultOptions, path: '/Data' }).returns(result)\n\n      const out = await cli(`dag get ${dagPbCid}/Data --output-codec raw --data-enc base16`, { ipfs })\n\n      expect(out).to.equal('000103')\n    })\n\n    it('should get raw bytes without data encoding', async () => {\n      // in this instance we're pretending to path into a 'Data' property of a dag-pb block\n      const result = {\n        value: Buffer.from([0, 1, 3])\n      }\n\n      ipfs.dag.get.withArgs(rawCid, defaultOptions).returns(result)\n\n      const out = await cli(`dag get ${rawCid} --output-codec raw`, { ipfs })\n\n      expect(out).to.equal(Buffer.from([0, 1, 3]).toString())\n    })\n\n    it('should get a dag-cbor node', async () => {\n      const result = {\n        value: {\n          foo: 'bar'\n        }\n      }\n\n      ipfs.dag.get.withArgs(dagCborCid, defaultOptions).returns(result)\n\n      const out = await cli(`dag get ${dagCborCid}`, { ipfs })\n\n      expect(out).to.equal('{\"foo\":\"bar\"}')\n    })\n\n    it('should get a dag-cbor node as dag-cbor', async () => {\n      const result = {\n        value: {\n          foo: 'bar'\n        }\n      }\n\n      ipfs.dag.get.withArgs(dagCborCid, defaultOptions).returns(result)\n\n      const out = await cli(`dag get ${dagCborCid} --output-codec dag-cbor`, { ipfs, raw: true })\n\n      expect(out).to.deep.equal(Buffer.from('a163666f6f63626172', 'hex'))\n    })\n\n    it('should get a dag-cbor node with a nested CID', async () => {\n      const result = {\n        value: {\n          foo: 'bar',\n          baz: dagPbCid\n        }\n      }\n\n      ipfs.dag.get.withArgs(dagCborCid, defaultOptions).returns(result)\n\n      const out = await cli(`dag get ${dagCborCid}`, { ipfs })\n\n      expect(out).to.equal(`{\"baz\":{\"/\":\"${dagPbCid}\"},\"foo\":\"bar\"}`)\n    })\n\n    it('should get a node with a deep path', async () => {\n      const path = '/parentHash'\n      const result = {\n        value: uint8ArrayFromString('hello world')\n      }\n\n      ipfs.dag.get.withArgs(rawCid, {\n        ...defaultOptions,\n        path\n      }).returns(result)\n\n      const out = await cli(`dag get ${rawCid}${path} --output-codec raw --data-enc base16`, { ipfs })\n\n      expect(out).to.be.eql(uint8ArrayToString(result.value, 'base16'))\n    })\n\n    it('should get a node with a deep path and an ipfs prefix', async () => {\n      const path = '/parentHash'\n      const result = {\n        value: uint8ArrayFromString('hello world')\n      }\n\n      ipfs.dag.get.withArgs(rawCid, {\n        ...defaultOptions,\n        path\n      }).returns(result)\n\n      const out = await cli(`dag get /ipfs/${rawCid}${path} --output-codec raw --data-enc base16`, { ipfs })\n\n      expect(out).to.be.eql(uint8ArrayToString(result.value, 'base16'))\n    })\n\n    it('should get a node with local resolve', async () => {\n      const result = {\n        value: uint8ArrayFromString('hello world')\n      }\n\n      ipfs.dag.get.withArgs(rawCid, {\n        ...defaultOptions,\n        localResolve: true\n      }).returns(result)\n\n      const out = await cli(`dag get ${rawCid} --local-resolve --output-codec raw --data-enc base16`, { ipfs })\n\n      expect(out).to.include('resolving path within the node only\\n')\n      expect(out).to.include('remainder path: n/a\\n')\n      expect(out).to.include(uint8ArrayToString(result.value, 'base16'))\n    })\n\n    it('should get a node with a timeout', async () => {\n      const result = {\n        value: uint8ArrayFromString('hello world')\n      }\n\n      ipfs.dag.get.withArgs(rawCid, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns(result)\n\n      const out = await cli(`dag get ${rawCid} --timeout=1s --output-codec raw --data-enc base16`, { ipfs })\n\n      expect(out).to.be.eql(uint8ArrayToString(result.value, 'base16'))\n    })\n\n    it('should strip control characters from dag-pb nodes', async () => {\n      const result = {\n        value: {\n          Links: [{\n            Hash: dagPbCid,\n            Name: 'foo\\b\\n\\t.txt',\n            Tsize: 9000\n          }]\n        }\n      }\n\n      ipfs.dag.get.withArgs(dagPbCid, defaultOptions).returns(result)\n\n      const out = await cli(`dag get ${dagPbCid}`, { ipfs })\n\n      expect(out).to.equal(`{\"Links\":[{\"Hash\":{\"/\":\"${dagPbCid.toString(base58btc)}\"},\"Name\":\"foo\\\\b\\\\n\\\\t.txt\",\"Tsize\":9000}]}`)\n    })\n\n    it('should not strip control characters from dag-cbor nodes', async () => {\n      const result = {\n        value: {\n          'lo\\nl': 'ok\\t'\n        }\n      }\n\n      ipfs.dag.get.withArgs(dagCborCid, defaultOptions).returns(result)\n\n      const out = await cli(`dag get ${dagCborCid}`, { ipfs })\n\n      expect(out).to.equal('{\"lo\\\\nl\":\"ok\\\\t\"}')\n    })\n\n    it('should not strip control characters from dag-cbor string nodes', async () => {\n      const result = {\n        value: 'lo\\nl'\n      }\n\n      ipfs.dag.get.withArgs(dagCborCid, defaultOptions).returns(result)\n\n      const out = await cli(`dag get ${dagCborCid}`, { ipfs })\n\n      expect(out).to.equal('\"lo\\\\nl\"')\n    })\n\n    it('should not strip control characters from dag-cbor array nodes', async () => {\n      const result = {\n        value: ['lo\\nl']\n      }\n\n      ipfs.dag.get.withArgs(dagCborCid, defaultOptions).returns(result)\n\n      const out = await cli(`dag get ${dagCborCid}`, { ipfs })\n\n      expect(out).to.equal('[\"lo\\\\nl\"]')\n    })\n\n    it('should not strip control characters from dag-cbor nested array nodes', async () => {\n      const result = {\n        value: {\n          'lo\\nl': ['ok\\t']\n        }\n      }\n\n      ipfs.dag.get.withArgs(dagCborCid, defaultOptions).returns(result)\n\n      const out = await cli(`dag get ${dagCborCid}`, { ipfs })\n\n      expect(out).to.equal('{\"lo\\\\nl\":[\"ok\\\\t\"]}')\n    })\n  })\n\n  describe('resolve', () => {\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('resolves a cid ref', async () => {\n      ipfs.dag.resolve.withArgs(dagPbCid.toString(), defaultOptions).returns([{\n        value: dagPbCid\n      }])\n\n      const out = await cli(`dag resolve ${dagPbCid}`, { ipfs })\n      expect(out).to.equal(`${dagPbCid}\\n`)\n    })\n\n    it('resolves an ipfs path', async () => {\n      ipfs.dag.resolve.withArgs(`/ipfs/${dagPbCid}`, defaultOptions).returns([{\n        value: dagPbCid\n      }])\n\n      const out = await cli(`dag resolve /ipfs/${dagPbCid}`, { ipfs })\n      expect(out).to.equal(`${dagPbCid}\\n`)\n    })\n\n    it('resolves a cid ref with a timeout', async () => {\n      ipfs.dag.resolve.withArgs(dagPbCid.toString(), {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns([{\n        value: dagPbCid\n      }])\n\n      const out = await cli(`dag resolve ${dagPbCid} --timeout=1s`, { ipfs })\n      expect(out).to.equal(`${dagPbCid}\\n`)\n    })\n  })\n\n  describe('put', () => {\n    const defaultOptions = {\n      storeCodec: 'dag-cbor',\n      hashAlg: 'sha2-256',\n      version: 1,\n      onlyHash: false,\n      preload: true,\n      pin: true,\n      timeout: undefined\n    }\n\n    it('puts json string', async () => {\n      ipfs.dag.put.withArgs({}, defaultOptions).resolves(dagCborCid)\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const out = await cli('dag put \"{}\"', { ipfs })\n      expect(out).to.equal(`${dagCborCid.toString(base58btc)}\\n`)\n    })\n\n    it('puts piped json string', async () => {\n      ipfs.dag.put.withArgs({}, defaultOptions).resolves(dagCborCid)\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const out = await cli('dag put', {\n        getStdin: function * () {\n          yield Buffer.from('{}')\n        },\n        ipfs\n      })\n      expect(out).to.equal(`${dagCborCid.toString(base58btc)}\\n`)\n    })\n\n    it('puts piped cbor node', async () => {\n      ipfs.dag.put.withArgs({}, defaultOptions).resolves(dagCborCid)\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const out = await cli('dag put --input-codec dag-cbor', {\n        getStdin: function * () {\n          yield dagCBOR.encode({})\n        },\n        ipfs\n      })\n      expect(out).to.equal(`${dagCborCid.toString(base58btc)}\\n`)\n    })\n\n    it('puts piped raw node', async () => {\n      ipfs.dag.put.withArgs(new Uint8Array(10), {\n        ...defaultOptions,\n        storeCodec: 'raw'\n      }).resolves(rawCid)\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const out = await cli('dag put --input-codec raw --store-codec raw', {\n        getStdin: function * () {\n          yield Buffer.alloc(10)\n        },\n        ipfs\n      })\n      expect(out).to.equal(`${rawCid.toString(base58btc)}\\n`)\n    })\n\n    it('puts piped dag-pb node', async () => {\n      ipfs.dag.put.withArgs(dagPB.decode(dagPB.encode({ Links: [] })), {\n        ...defaultOptions,\n        storeCodec: 'dag-pb',\n        version: 0\n      }).resolves(dagPbCid)\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const out = await cli('dag put --input-codec dag-pb --store-codec dag-pb', {\n        getStdin: function * () {\n          yield dagPB.encode({ Links: [] })\n        },\n        ipfs\n      })\n      expect(out).to.equal(`${dagPbCid.toString(base58btc)}\\n`)\n    })\n\n    it('puts dag-pb node as dag-json', async () => {\n      ipfs.dag.put.withArgs({ Links: [] }, {\n        ...defaultOptions,\n        storeCodec: 'dag-pb',\n        version: 0\n      }).resolves(dagPbCid)\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const out = await cli('dag put --store-codec dag-pb --input-codec dag-json \\'{\"Links\":[]}\\'', {\n        ipfs\n      })\n      expect(out).to.equal(`${dagPbCid.toString(base58btc)}\\n`)\n    })\n\n    it('puts piped dag-pb node with cid-v1', async () => {\n      ipfs.dag.put.withArgs(dagPB.decode(dagPB.encode({ Links: [] })), {\n        ...defaultOptions,\n        storeCodec: 'dag-pb',\n        version: 1\n      }).resolves(dagPbCid)\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const out = await cli('dag put --input-codec dag-pb --store-codec dag-pb --cid-version=1', {\n        getStdin: function * () {\n          yield dagPB.encode({ Links: [] })\n        },\n        ipfs\n      })\n      expect(out).to.equal(`${dagPbCid.toString(base58btc)}\\n`)\n    })\n\n    it('puts json string with esoteric hashing algorithm', async () => {\n      ipfs.dag.put.withArgs({}, {\n        ...defaultOptions,\n        hashAlg: 'blake2s-40'\n      }).resolves(dagCborCid)\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const out = await cli('dag put --hash-alg blake2s-40 \"{}\"', { ipfs })\n      expect(out).to.equal(`${dagCborCid.toString(base58btc)}\\n`)\n    })\n\n    it('puts json string with cid base', async () => {\n      ipfs.dag.put.withArgs({}, defaultOptions).resolves(dagCborCid)\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n\n      const out = await cli('dag put --cid-base base64 \"{}\"', { ipfs })\n      expect(out).to.equal(`${dagCborCid.toV1().toString(base64)}\\n`)\n    })\n\n    it('pins node after putting', async () => {\n      ipfs.dag.put.withArgs({ hello: 'world' }, {\n        ...defaultOptions,\n        pin: true\n      }).resolves(dagCborCid)\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const out = await cli('dag put --pin \\'{\"hello\":\"world\"}\\'', { ipfs })\n\n      expect(out).to.equal(`${dagCborCid.toString(base58btc)}\\n`)\n    })\n\n    it('puts json string with a timeout', async () => {\n      ipfs.dag.put.withArgs({}, {\n        ...defaultOptions,\n        timeout: 1000\n      }).resolves(dagCborCid)\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const out = await cli('dag put \"{}\" --timeout=1s', { ipfs })\n      expect(out).to.equal(`${dagCborCid.toString(base58btc)}\\n`)\n    })\n  })\n\n  describe('import', () => {\n    const defaultOptions = {\n      pinRoots: true,\n      timeout: undefined\n    }\n\n    it('imports car from stdin', async () => {\n      const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n      ipfs.dag.import.withArgs([matchIterable()], {\n        ...defaultOptions\n      }).returns([{ root: { cid, pinErrorMsg: '' } }])\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const proc = cli('dag import', {\n        ipfs,\n        getStdin: function * () {\n          yield uint8ArrayFromString('hello\\n')\n        }\n      })\n\n      const out = await proc\n      expect(out).to.equal(`importing CAR from stdin...\\npinned root\\t${cid}\\tsuccess\\n`)\n    })\n\n    it('imports car from path', async () => {\n      const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n      ipfs.dag.import.withArgs(matchIterable(), {\n        ...defaultOptions\n      }).returns([{ root: { cid, pinErrorMsg: '' } }])\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const proc = cli('dag import README.md', {\n        ipfs\n      })\n\n      const out = await proc\n      expect(out).to.equal(`pinned root\\t${cid}\\tsuccess\\n`)\n    })\n\n    it('imports car from path and fails to pin', async () => {\n      const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n      ipfs.dag.import.withArgs(matchIterable(), {\n        ...defaultOptions\n      }).returns([{ root: { cid, pinErrorMsg: 'oh noes' } }])\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const proc = cli('dag import README.md', {\n        ipfs\n      })\n\n      const out = await proc\n      expect(out).to.equal(`pinned root\\t${cid}\\toh noes\\n`)\n    })\n\n    it('imports car from path with no pin arg', async () => {\n      const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n      ipfs.dag.import.withArgs(matchIterable(), {\n        ...defaultOptions,\n        pinRoots: false\n      }).returns([{ root: { cid, pinErrorMsg: '' } }])\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const proc = cli('dag import README.md --pin-roots=false', {\n        ipfs\n      })\n\n      const out = await proc\n      expect(out).to.equal(`pinned root\\t${cid}\\tsuccess\\n`)\n    })\n\n    it('imports car from path with different base', async () => {\n      const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n      ipfs.dag.import.withArgs(matchIterable(), {\n        ...defaultOptions\n      }).returns([{ root: { cid, pinErrorMsg: '' } }])\n      ipfs.bases.getBase.withArgs('derp').returns(base58btc)\n\n      const proc = cli('dag import README.md --cid-base=derp', {\n        ipfs\n      })\n\n      const out = await proc\n      expect(out).to.equal(`pinned root\\t${cid}\\tsuccess\\n`)\n    })\n\n    it('imports car from path with timeout', async () => {\n      const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n      ipfs.dag.import.withArgs(matchIterable(), {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns([{ root: { cid, pinErrorMsg: '' } }])\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const proc = cli('dag import README.md --timeout=1s', {\n        ipfs\n      })\n\n      const out = await proc\n      expect(out).to.equal(`pinned root\\t${cid}\\tsuccess\\n`)\n    })\n  })\n\n  describe('export', () => {\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('exports car', async () => {\n      const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n      ipfs.dag.export.withArgs(cid, {\n        ...defaultOptions\n      }).returns(['some bytes'])\n\n      const proc = cli(`dag export ${cid}`, {\n        ipfs\n      })\n\n      const out = await proc\n      expect(out).to.equal('some bytes')\n    })\n\n    it('exports car with timeout', async () => {\n      const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n      ipfs.dag.export.withArgs(cid, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns(['some bytes'])\n\n      const proc = cli(`dag export ${cid} --timeout=1s`, {\n        ipfs\n      })\n\n      const out = await proc\n      expect(out).to.equal('some bytes')\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/dht.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { cli } from './utils/cli.js'\nimport sinon from 'sinon'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\nimport { CID } from 'multiformats/cid'\nimport { peerIdFromString } from '@libp2p/peer-id'\nimport { matchPeerId } from './utils/match-peer-id.js'\n\ndescribe('dht', () => {\n  let ipfs\n\n  beforeEach(function () {\n    ipfs = {\n      dht: {\n        put: sinon.stub(),\n        get: sinon.stub(),\n        provide: sinon.stub(),\n        findProvs: sinon.stub(),\n        findPeer: sinon.stub(),\n        query: sinon.stub()\n      }\n    }\n  })\n\n  describe('put', () => {\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('should be able to put a value to the dht', async () => {\n      const key = 'testkey'\n      const value = 'testvalue'\n\n      await cli(`dht put ${key} ${value}`, {\n        ipfs\n      })\n      expect(ipfs.dht.put.calledWith(uint8ArrayFromString(key), uint8ArrayFromString(value), defaultOptions)).to.be.true()\n    })\n\n    it('should be able to put a value to the dht with a timeout', async () => {\n      const key = 'testkey'\n      const value = 'testvalue'\n\n      await cli(`dht put ${key} ${value} --timeout=1s`, {\n        ipfs\n      })\n      expect(ipfs.dht.put.calledWith(uint8ArrayFromString(key), uint8ArrayFromString(value), {\n        ...defaultOptions,\n        timeout: 1000\n      })).to.be.true()\n    })\n  })\n\n  describe('get', () => {\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('should be able to get a value from the dht', async () => {\n      const key = CID.parse('QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kp')\n      const value = uint8ArrayFromString('testvalue')\n\n      ipfs.dht.get.withArgs(key.bytes, defaultOptions).returns([{\n        name: 'VALUE',\n        value\n      }])\n\n      const out = await cli(`dht get ${key}`, {\n        ipfs\n      })\n      expect(out).to.equal(`${uint8ArrayToString(value, 'base58btc')}\\n`)\n    })\n\n    it('should be able to get a value from the dht with a timeout', async () => {\n      const key = CID.parse('QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kp')\n      const value = uint8ArrayFromString('testvalue')\n\n      ipfs.dht.get.withArgs(key.bytes, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns([{\n        name: 'VALUE',\n        value\n      }])\n\n      const out = await cli(`dht get ${key} --timeout=1s`, {\n        ipfs\n      })\n      expect(out).to.equal(`${uint8ArrayToString(value, 'base58btc')}\\n`)\n    })\n  })\n\n  describe('provide', () => {\n    const defaultOptions = {\n      recursive: false,\n      timeout: undefined\n    }\n\n    it('should be able to provide data', async () => {\n      const key = CID.parse('QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kp')\n\n      await cli(`dht provide ${key}`, {\n        ipfs\n      })\n      expect(ipfs.dht.provide.calledWith(key, defaultOptions)).to.be.true()\n    })\n\n    it('should be able to provide data recursively', async () => {\n      const key = CID.parse('QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kp')\n\n      await cli(`dht provide ${key} --recursive`, {\n        ipfs\n      })\n      expect(ipfs.dht.provide.calledWith(key, {\n        ...defaultOptions,\n        recursive: true\n      })).to.be.true()\n    })\n\n    it('should be able to provide data recursively (short option)', async () => {\n      const key = CID.parse('QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kp')\n\n      await cli(`dht provide ${key} -r`, {\n        ipfs\n      })\n      expect(ipfs.dht.provide.calledWith(key, {\n        ...defaultOptions,\n        recursive: true\n      })).to.be.true()\n    })\n\n    it('should be able to provide data with a timeout', async () => {\n      const key = CID.parse('QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kp')\n\n      await cli(`dht provide ${key} --timeout=1s`, {\n        ipfs\n      })\n      expect(ipfs.dht.provide.calledWith(key, {\n        ...defaultOptions,\n        timeout: 1000\n      })).to.be.true()\n    })\n  })\n\n  describe('findprovs', () => {\n    const defaultOptions = {\n      timeout: undefined\n    }\n    const key = CID.parse('QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kp')\n    const prov = {\n      id: 'prov-id'\n    }\n\n    it('should be able to find providers for data', async () => {\n      ipfs.dht.findProvs.withArgs(key, defaultOptions).returns([{\n        name: 'PROVIDER',\n        providers: [\n          prov\n        ]\n      }])\n\n      const out = await cli(`dht findprovs ${key}`, { ipfs })\n      expect(out).to.equal(`${prov.id}\\n`)\n    })\n\n    it('should be able to find smaller number of providers for data', async () => {\n      ipfs.dht.findProvs.withArgs(key, {\n        ...defaultOptions\n      }).returns([{\n        name: 'PROVIDER',\n        providers: [\n          prov\n        ]\n      }])\n\n      const out = await cli(`dht findprovs ${key} --num-providers 5`, { ipfs })\n      expect(out).to.equal(`${prov.id}\\n`)\n    })\n\n    it('should be able to find providers for data with a timeout', async () => {\n      ipfs.dht.findProvs.withArgs(key, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns([{\n        name: 'PROVIDER',\n        providers: [\n          prov\n        ]\n      }])\n\n      const out = await cli(`dht findprovs ${key} --timeout=1s`, { ipfs })\n      expect(out).to.equal(`${prov.id}\\n`)\n    })\n  })\n\n  describe('findpeer', () => {\n    const defaultOptions = {\n      timeout: undefined\n    }\n    const peerId = peerIdFromString('QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kp')\n    const peer = {\n      multiaddrs: [\n        'addr'\n      ]\n    }\n\n    it('should find a peer', async () => {\n      ipfs.dht.findPeer.withArgs(peerId, defaultOptions).returns([{\n        name: 'FINAL_PEER',\n        peer\n      }])\n\n      const out = await cli(`dht findpeer ${peerId.toString()}`, { ipfs })\n\n      expect(out).to.equal(`${peer.multiaddrs[0]}\\n`)\n    })\n\n    it('should find a peer with a timeout', async () => {\n      ipfs.dht.findPeer.withArgs(matchPeerId(peerId), {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns([{\n        name: 'FINAL_PEER',\n        peer\n      }])\n\n      const out = await cli(`dht findpeer ${peerId} --timeout=1s`, { ipfs })\n      expect(out).to.equal(`${peer.multiaddrs[0]}\\n`)\n    })\n  })\n\n  describe('query', () => {\n    const defaultOptions = {\n      timeout: undefined\n    }\n    const peerId = peerIdFromString('QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kp')\n    const peer = {\n      id: peerId\n    }\n\n    it('should query the DHT', async () => {\n      ipfs.dht.query.withArgs(matchPeerId(peerId), defaultOptions).returns([{\n        name: 'PEER_RESPONSE',\n        closer: [\n          peer\n        ]\n      }])\n\n      const out = await cli(`dht query ${peerId.toString()}`, { ipfs })\n      expect(out).to.equal(`${peer.id}\\n`)\n    })\n\n    it('should query the DHT with a timeout', async () => {\n      ipfs.dht.query.withArgs(matchPeerId(peerId), {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns([{\n        name: 'PEER_RESPONSE',\n        closer: [\n          peer\n        ]\n      }])\n\n      const out = await cli(`dht query ${peerId} --timeout=1s`, { ipfs })\n      expect(out).to.equal(`${peer.id}\\n`)\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/dns.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { cli } from './utils/cli.js'\nimport sinon from 'sinon'\n\nconst defaultOptions = {\n  recursive: true,\n  timeout: undefined\n}\n\ndescribe('dns', () => {\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      dns: sinon.stub()\n    }\n  })\n\n  it('resolves ipfs.io dns', async () => {\n    const domain = 'ipfs.io'\n    const path = 'path'\n\n    ipfs.dns.withArgs(domain, defaultOptions).returns(path)\n\n    const out = await cli('dns ipfs.io', {\n      ipfs\n    })\n    expect(out).to.equal(`${path}\\n`)\n  })\n\n  it('resolves ipfs.io dns non-recursively', async () => {\n    const domain = 'ipfs.io'\n    const path = 'path'\n\n    ipfs.dns.withArgs(domain, {\n      ...defaultOptions,\n      recursive: false\n    }).returns(path)\n\n    const out = await cli('dns ipfs.io --recursive=false', {\n      ipfs\n    })\n    expect(out).to.equal(`${path}\\n`)\n  })\n\n  it('resolves ipfs.io dns recursively (short option)', async () => {\n    const domain = 'ipfs.io'\n    const path = 'path'\n\n    ipfs.dns.withArgs(domain, {\n      ...defaultOptions,\n      recursive: false\n    }).returns(path)\n\n    const out = await cli('dns ipfs.io -r false', {\n      ipfs\n    })\n    expect(out).to.equal(`${path}\\n`)\n  })\n\n  it('resolves ipfs.io dns with a timeout', async () => {\n    const domain = 'ipfs.io'\n    const path = 'path'\n\n    ipfs.dns.withArgs(domain, {\n      ...defaultOptions,\n      timeout: 1000\n    }).returns(path)\n\n    const out = await cli('dns ipfs.io --timeout=1s', {\n      ipfs\n    })\n    expect(out).to.equal(`${path}\\n`)\n  })\n\n  it('strips control characters from response', async () => {\n    const domain = 'ipfs.io'\n    const path = 'path'\n    const junkPath = `${path}\\n\\b\\t`\n\n    ipfs.dns.withArgs(domain, defaultOptions).returns(junkPath)\n\n    const out = await cli('dns ipfs.io', {\n      ipfs\n    })\n    expect(out).to.equal(`${path}\\n`)\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/files/chmod.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport sinon from 'sinon'\nimport { cli } from '../utils/cli.js'\n\nconst defaultOptions = {\n  recursive: false,\n  hashAlg: 'sha2-256',\n  flush: true,\n  shardSplitThreshold: 1000,\n  timeout: undefined\n}\n\ndescribe('chmod', () => {\n  const path = '/foo'\n  const mode = '0777'\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      files: {\n        chmod: sinon.stub()\n      }\n    }\n  })\n\n  it('should update the mode for a file', async () => {\n    await cli(`files chmod ${mode} ${path}`, { ipfs })\n\n    expect(ipfs.files.chmod.callCount).to.equal(1)\n    expect(ipfs.files.chmod.getCall(0).args).to.deep.equal([\n      path,\n      mode,\n      defaultOptions\n    ])\n  })\n\n  it('should update the mode for a file with a string', async () => {\n    await cli(`files chmod +x ${path}`, { ipfs })\n\n    expect(ipfs.files.chmod.callCount).to.equal(1)\n    expect(ipfs.files.chmod.getCall(0).args).to.deep.equal([\n      path,\n      '+x',\n      defaultOptions\n    ])\n  })\n\n  it('should update the mode recursively', async () => {\n    await cli(`files chmod ${mode} --recursive ${path}`, { ipfs })\n\n    expect(ipfs.files.chmod.callCount).to.equal(1)\n    expect(ipfs.files.chmod.getCall(0).args).to.deep.equal([\n      path,\n      mode, {\n        ...defaultOptions,\n        recursive: true\n      }\n    ])\n  })\n\n  it('should update the mode recursively (short option)', async () => {\n    await cli(`files chmod ${mode} -r ${path}`, { ipfs })\n\n    expect(ipfs.files.chmod.callCount).to.equal(1)\n    expect(ipfs.files.chmod.getCall(0).args).to.deep.equal([\n      path,\n      mode, {\n        ...defaultOptions,\n        recursive: true\n      }\n    ])\n  })\n\n  it('should update the mode without flushing', async () => {\n    await cli(`files chmod ${mode} --flush false ${path}`, { ipfs })\n\n    expect(ipfs.files.chmod.callCount).to.equal(1)\n    expect(ipfs.files.chmod.getCall(0).args).to.deep.equal([\n      path,\n      mode, {\n        ...defaultOptions,\n        flush: false\n      }\n    ])\n  })\n\n  it('should update the mode without flushing (short option)', async () => {\n    await cli(`files chmod ${mode} -f false ${path}`, { ipfs })\n\n    expect(ipfs.files.chmod.callCount).to.equal(1)\n    expect(ipfs.files.chmod.getCall(0).args).to.deep.equal([\n      path,\n      mode, {\n        ...defaultOptions,\n        flush: false\n      }\n    ])\n  })\n\n  it('should update the mode a with different hash algorithm', async () => {\n    await cli(`files chmod ${mode} --hash-alg sha3-256 ${path}`, { ipfs })\n\n    expect(ipfs.files.chmod.callCount).to.equal(1)\n    expect(ipfs.files.chmod.getCall(0).args).to.deep.equal([\n      path,\n      mode, {\n        ...defaultOptions,\n        hashAlg: 'sha3-256'\n      }\n    ])\n  })\n\n  it('should update the mode a with different hash algorithm (short option)', async () => {\n    await cli(`files chmod ${mode} -h sha3-256 ${path}`, { ipfs })\n\n    expect(ipfs.files.chmod.callCount).to.equal(1)\n    expect(ipfs.files.chmod.getCall(0).args).to.deep.equal([\n      path,\n      mode, {\n        ...defaultOptions,\n        hashAlg: 'sha3-256'\n      }\n    ])\n  })\n\n  it('should update the mode with a shard split threshold', async () => {\n    await cli('files chmod 0777 --shard-split-threshold 10 /foo', { ipfs })\n\n    expect(ipfs.files.chmod.callCount).to.equal(1)\n    expect(ipfs.files.chmod.getCall(0).args).to.deep.equal([\n      path,\n      mode, {\n        ...defaultOptions,\n        shardSplitThreshold: 10\n      }\n    ])\n  })\n\n  it('should update the mode with a timeout', async () => {\n    await cli(`files chmod ${mode} ${path} --timeout=1s`, { ipfs })\n\n    expect(ipfs.files.chmod.callCount).to.equal(1)\n    expect(ipfs.files.chmod.getCall(0).args).to.deep.equal([\n      path,\n      mode, {\n        ...defaultOptions,\n        timeout: 1000\n      }\n    ])\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/files/cp.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport sinon from 'sinon'\nimport { cli } from '../utils/cli.js'\n\nconst defaultOptions = {\n  parents: false,\n  hashAlg: 'sha2-256',\n  flush: true,\n  shardSplitThreshold: 1000,\n  timeout: undefined\n}\n\ndescribe('cp', () => {\n  const source = 'source'\n  const dest = 'dest'\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      files: {\n        cp: sinon.stub()\n      }\n    }\n  })\n\n  it('should copy files', async () => {\n    await cli(`files cp ${source} ${dest}`, { ipfs })\n\n    expect(ipfs.files.cp.callCount).to.equal(1)\n    expect(ipfs.files.cp.getCall(0).args).to.deep.equal([\n      source,\n      dest,\n      defaultOptions\n    ])\n  })\n\n  it('should copy files and create intermediate directories', async () => {\n    await cli(`files cp --parents ${source} ${dest}`, { ipfs })\n\n    expect(ipfs.files.cp.callCount).to.equal(1)\n    expect(ipfs.files.cp.getCall(0).args).to.deep.equal([\n      source,\n      dest, {\n        ...defaultOptions,\n        parents: true\n      }\n    ])\n  })\n\n  it('should copy files and create intermediate directories (short option)', async () => {\n    await cli(`files cp --parents ${source} ${dest}`, { ipfs })\n\n    expect(ipfs.files.cp.callCount).to.equal(1)\n    expect(ipfs.files.cp.getCall(0).args).to.deep.equal([\n      source,\n      dest, {\n        ...defaultOptions,\n        parents: true\n      }\n    ])\n  })\n\n  it('should copy files with a different hash algorithm', async () => {\n    await cli(`files cp --hash-alg sha3-256 ${source} ${dest}`, { ipfs })\n\n    expect(ipfs.files.cp.callCount).to.equal(1)\n    expect(ipfs.files.cp.getCall(0).args).to.deep.equal([\n      source,\n      dest, {\n        ...defaultOptions,\n        hashAlg: 'sha3-256'\n      }\n    ])\n  })\n\n  it('should copy files with a different hash algorithm (short option)', async () => {\n    await cli(`files cp -h sha3-256 ${source} ${dest}`, { ipfs })\n\n    expect(ipfs.files.cp.callCount).to.equal(1)\n    expect(ipfs.files.cp.getCall(0).args).to.deep.equal([\n      source,\n      dest, {\n        ...defaultOptions,\n        hashAlg: 'sha3-256'\n      }\n    ])\n  })\n\n  it('should copy files with a different shard split threshold', async () => {\n    await cli(`files cp --shard-split-threshold 10 ${source} ${dest}`, { ipfs })\n\n    expect(ipfs.files.cp.callCount).to.equal(1)\n    expect(ipfs.files.cp.getCall(0).args).to.deep.equal([\n      source,\n      dest, {\n        ...defaultOptions,\n        shardSplitThreshold: 10\n      }\n    ])\n  })\n\n  it('should copy files with a timeout', async () => {\n    await cli(`files cp ${source} ${dest} --timeout=1s`, { ipfs })\n\n    expect(ipfs.files.cp.callCount).to.equal(1)\n    expect(ipfs.files.cp.getCall(0).args).to.deep.equal([\n      source,\n      dest, {\n        ...defaultOptions,\n        timeout: 1000\n      }\n    ])\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/files/flush.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport sinon from 'sinon'\nimport { CID } from 'multiformats/cid'\nimport { base58btc } from 'multiformats/bases/base58'\nimport { base64 } from 'multiformats/bases/base64'\nimport { cli } from '../utils/cli.js'\nconst cid = CID.parse('QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn')\n\nconst defaultOptions = {\n  timeout: undefined\n}\n\ndescribe('flush', () => {\n  const path = '/foo'\n  let ipfs\n  let print\n  let output\n\n  beforeEach(() => {\n    ipfs = {\n      files: {\n        flush: sinon.stub().resolves(cid)\n      },\n      bases: {\n        getBase: sinon.stub()\n      }\n    }\n    print = (msg = '', newline = true) => {\n      output += newline ? msg + '\\n' : msg\n    }\n  })\n\n  it('should flush a path', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    await cli(`files flush ${path}`, { ipfs, print })\n\n    expect(ipfs.files.flush.callCount).to.equal(1)\n    expect(ipfs.files.flush.getCall(0).args).to.deep.equal([\n      path,\n      defaultOptions\n    ])\n    expect(output).to.include(cid.toString())\n  })\n\n  it('should flush without a path', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    await cli('files flush', { ipfs, print })\n\n    expect(ipfs.files.flush.callCount).to.equal(1)\n    expect(ipfs.files.flush.getCall(0).args).to.deep.equal([\n      '/',\n      defaultOptions\n    ])\n    expect(output).to.include(cid.toString())\n  })\n\n  it('should flush with a different CID base', async () => {\n    ipfs.files.flush.returns(cid.toV1())\n    ipfs.bases.getBase.withArgs('base64').returns(base64)\n\n    await cli('files flush --cid-base base64', { ipfs, print })\n\n    expect(ipfs.files.flush.callCount).to.equal(1)\n    expect(ipfs.files.flush.getCall(0).args).to.deep.equal([\n      '/',\n      defaultOptions\n    ])\n    expect(output).to.include(cid.toV1().toString(base64))\n  })\n\n  it('should flush a path with a timeout', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    await cli(`files flush ${path} --timeout=1s`, { ipfs, print })\n\n    expect(ipfs.files.flush.callCount).to.equal(1)\n    expect(ipfs.files.flush.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        timeout: 1000\n      }\n    ])\n    expect(output).to.include(cid.toString())\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/files/ls.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport sinon from 'sinon'\nimport { isNode } from 'ipfs-utils/src/env.js'\nimport { CID } from 'multiformats/cid'\nimport { base58btc } from 'multiformats/bases/base58'\nimport { cli } from '../utils/cli.js'\nconst fileCid = CID.parse('bafybeigyov3nzxrqjismjpq7ghkkjorcmozy5rgaikvyieakoqpxfc3rvu')\n\nconst defaultOptions = {\n  timeout: undefined\n}\n\ndescribe('ls', () => {\n  if (!isNode) {\n    return\n  }\n\n  let ipfs\n  let print\n  let output\n\n  beforeEach(() => {\n    output = ''\n    ipfs = {\n      files: {\n        ls: sinon.stub().returns([])\n      },\n      bases: {\n        getBase: sinon.stub()\n      }\n    }\n    print = (msg = '', newline = true) => {\n      output += newline ? msg + '\\n' : msg\n    }\n  })\n\n  it('should list a path', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const path = '/foo'\n\n    await cli(`files ls ${path}`, { ipfs, print })\n\n    expect(ipfs.files.ls.callCount).to.equal(1)\n    expect(ipfs.files.ls.getCall(0).args).to.deep.equal([\n      path,\n      defaultOptions\n    ])\n  })\n\n  it('should list without a path', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    await cli('files ls', { ipfs, print })\n\n    expect(ipfs.files.ls.callCount).to.equal(1)\n    expect(ipfs.files.ls.getCall(0).args).to.deep.equal([\n      '/',\n      defaultOptions\n    ])\n  })\n\n  it('should list a path with details', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const files = [{\n      cid: fileCid,\n      name: 'file-name',\n      size: 'file-size',\n      mode: 0o755,\n      mtime: {\n        secs: Date.now() / 1000,\n        nsecs: 0\n      }\n    }]\n\n    ipfs.files.ls = sinon.stub().withArgs('/foo', defaultOptions).returns(files)\n\n    await cli('files ls --long /foo', { ipfs, print })\n\n    expect(ipfs.files.ls.callCount).to.equal(1)\n    expect(output).to.include(files[0].cid.toString(base58btc))\n    expect(output).to.include(files[0].name)\n    expect(output).to.include(files[0].size)\n  })\n\n  it('should list a path with details (short option)', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const files = [{\n      cid: fileCid,\n      name: 'file-name',\n      size: 'file-size',\n      mode: 0o755,\n      mtime: {\n        secs: Date.now() / 1000,\n        nsecs: 0\n      }\n    }]\n\n    ipfs.files.ls = sinon.stub().withArgs('/foo', defaultOptions).returns(files)\n\n    await cli('files ls -l /foo', { ipfs, print })\n\n    expect(ipfs.files.ls.callCount).to.equal(1)\n    expect(output).to.include(files[0].cid.toString(base58btc))\n    expect(output).to.include(files[0].name)\n    expect(output).to.include(files[0].size)\n  })\n\n  it('should list a path with a timeout', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const path = '/foo'\n\n    await cli(`files ls ${path} --timeout=1s`, { ipfs, print })\n\n    expect(ipfs.files.ls.callCount).to.equal(1)\n    expect(ipfs.files.ls.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        timeout: 1000\n      }\n    ])\n  })\n\n  it('should strip control characters from path names', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const files = [{\n      cid: fileCid,\n      name: 'file\\n\\t\\b-name',\n      size: 'file-size',\n      mode: 0o755,\n      mtime: {\n        secs: Date.now() / 1000,\n        nsecs: 0\n      }\n    }]\n\n    ipfs.files.ls = sinon.stub().withArgs('/foo', defaultOptions).returns(files)\n\n    await cli('files ls --long /foo', { ipfs, print })\n\n    expect(ipfs.files.ls.callCount).to.equal(1)\n    expect(output).to.include(files[0].cid.toString(base58btc))\n    expect(output).to.include('file-name')\n    expect(output).to.include(files[0].size)\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/files/mkdir.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport sinon from 'sinon'\nimport { isNode } from 'ipfs-utils/src/env.js'\nimport { cli } from '../utils/cli.js'\n\nconst defaultOptions = {\n  parents: false,\n  cidVersion: 0,\n  hashAlg: 'sha2-256',\n  flush: true,\n  shardSplitThreshold: 1000,\n  mode: undefined,\n  mtime: undefined,\n  timeout: undefined\n}\n\ndescribe('mkdir', () => {\n  if (!isNode) {\n    return\n  }\n\n  const path = '/foo'\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      files: {\n        mkdir: sinon.stub()\n      }\n    }\n  })\n\n  it('should make a directory', async () => {\n    await cli(`files mkdir ${path}`, { ipfs })\n\n    expect(ipfs.files.mkdir.callCount).to.equal(1)\n    expect(ipfs.files.mkdir.getCall(0).args).to.deep.equal([\n      path,\n      defaultOptions\n    ])\n  })\n\n  it('should make a directory with parents', async () => {\n    await cli(`files mkdir --parents ${path}`, { ipfs })\n\n    expect(ipfs.files.mkdir.callCount).to.equal(1)\n    expect(ipfs.files.mkdir.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        parents: true\n      }\n    ])\n  })\n\n  it('should make a directory with parents (short option)', async () => {\n    await cli(`files mkdir -p ${path}`, { ipfs })\n\n    expect(ipfs.files.mkdir.callCount).to.equal(1)\n    expect(ipfs.files.mkdir.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        parents: true\n      }\n    ])\n  })\n\n  it('should make a directory with a different cid version', async () => {\n    await cli(`files mkdir --cid-version 5 ${path}`, { ipfs })\n\n    expect(ipfs.files.mkdir.callCount).to.equal(1)\n    expect(ipfs.files.mkdir.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        cidVersion: 5\n      }\n    ])\n  })\n\n  it('should make a directory with a different cid version (shortish option)', async () => {\n    await cli(`files mkdir --cid-ver 5 ${path}`, { ipfs })\n\n    expect(ipfs.files.mkdir.callCount).to.equal(1)\n    expect(ipfs.files.mkdir.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        cidVersion: 5\n      }\n    ])\n  })\n\n  it('should make a directory with a different hash algorithm', async () => {\n    await cli(`files mkdir --hash-alg sha3-256 ${path}`, { ipfs })\n\n    expect(ipfs.files.mkdir.callCount).to.equal(1)\n    expect(ipfs.files.mkdir.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        hashAlg: 'sha3-256'\n      }\n    ])\n  })\n\n  it('should make a directory with a different hash algorithm (short option)', async () => {\n    await cli(`files mkdir -h sha3-256 ${path}`, { ipfs })\n\n    expect(ipfs.files.mkdir.callCount).to.equal(1)\n    expect(ipfs.files.mkdir.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        hashAlg: 'sha3-256'\n      }\n    ])\n  })\n\n  it('should make a directory without flushing', async () => {\n    await cli(`files mkdir --flush false ${path}`, { ipfs })\n\n    expect(ipfs.files.mkdir.callCount).to.equal(1)\n    expect(ipfs.files.mkdir.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        flush: false\n      }\n    ])\n  })\n\n  it('should make a directory without flushing (short option)', async () => {\n    await cli(`files mkdir -f false ${path}`, { ipfs })\n\n    expect(ipfs.files.mkdir.callCount).to.equal(1)\n    expect(ipfs.files.mkdir.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        flush: false\n      }\n    ])\n  })\n\n  it('should make a directory a different shard split threshold', async () => {\n    await cli(`files mkdir --shard-split-threshold 10 ${path}`, { ipfs })\n\n    expect(ipfs.files.mkdir.callCount).to.equal(1)\n    expect(ipfs.files.mkdir.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        shardSplitThreshold: 10\n      }\n    ])\n  })\n\n  it('should make a directory a different mode', async () => {\n    await cli(`files mkdir --mode 0111 ${path}`, { ipfs })\n\n    expect(ipfs.files.mkdir.callCount).to.equal(1)\n    expect(ipfs.files.mkdir.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        mode: parseInt('0111', 8)\n      }\n    ])\n  })\n\n  it('should make a directory a different mtime', async () => {\n    await cli(`files mkdir --mtime 5 ${path}`, { ipfs })\n\n    expect(ipfs.files.mkdir.callCount).to.equal(1)\n    expect(ipfs.files.mkdir.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        mtime: {\n          secs: 5,\n          nsecs: undefined\n        }\n      }\n    ])\n  })\n\n  it('should make a directory a different mtime and mtime nsecs', async () => {\n    await cli(`files mkdir --mtime 5 --mtime-nsecs 10 ${path}`, { ipfs })\n\n    expect(ipfs.files.mkdir.callCount).to.equal(1)\n    expect(ipfs.files.mkdir.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        mtime: {\n          secs: 5,\n          nsecs: 10\n        }\n      }\n    ])\n  })\n\n  it('should make a directory with a timeout', async () => {\n    await cli(`files mkdir ${path} --timeout=1s`, { ipfs })\n\n    expect(ipfs.files.mkdir.callCount).to.equal(1)\n    expect(ipfs.files.mkdir.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        timeout: 1000\n      }\n    ])\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/files/mv.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport sinon from 'sinon'\nimport { isNode } from 'ipfs-utils/src/env.js'\nimport { cli } from '../utils/cli.js'\n\nconst defaultOptions = {\n  parents: false,\n  cidVersion: 0,\n  hashAlg: 'sha2-256',\n  flush: true,\n  shardSplitThreshold: 1000,\n  timeout: undefined\n}\n\ndescribe('mv', () => {\n  if (!isNode) {\n    return\n  }\n\n  const source = '/src'\n  const dest = '/dest'\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      files: {\n        mv: sinon.stub()\n      }\n    }\n  })\n\n  it('should move an entry', async () => {\n    await cli(`files mv ${source} ${dest}`, { ipfs })\n\n    expect(ipfs.files.mv.callCount).to.equal(1)\n    expect(ipfs.files.mv.getCall(0).args).to.deep.equal([\n      source,\n      dest,\n      defaultOptions\n    ])\n  })\n\n  it('should move an entry and create parents', async () => {\n    await cli(`files mv --parents ${source} ${dest}`, { ipfs })\n\n    expect(ipfs.files.mv.callCount).to.equal(1)\n    expect(ipfs.files.mv.getCall(0).args).to.deep.equal([\n      source,\n      dest, {\n        ...defaultOptions,\n        parents: true\n      }\n    ])\n  })\n\n  it('should move an entry and create parents (short option)', async () => {\n    await cli(`files mv -p ${source} ${dest}`, { ipfs })\n\n    expect(ipfs.files.mv.callCount).to.equal(1)\n    expect(ipfs.files.mv.getCall(0).args).to.deep.equal([\n      source,\n      dest, {\n        ...defaultOptions,\n        parents: true\n      }\n    ])\n  })\n\n  it('should make a directory with a different cid version', async () => {\n    await cli(`files mv --cid-version 5 ${source} ${dest}`, { ipfs })\n\n    expect(ipfs.files.mv.callCount).to.equal(1)\n    expect(ipfs.files.mv.getCall(0).args).to.deep.equal([\n      source,\n      dest, {\n        ...defaultOptions,\n        cidVersion: 5\n      }\n    ])\n  })\n\n  it('should make a directory with a different cid version (shortish option)', async () => {\n    await cli(`files mv --cid-ver 5 ${source} ${dest}`, { ipfs })\n\n    expect(ipfs.files.mv.callCount).to.equal(1)\n    expect(ipfs.files.mv.getCall(0).args).to.deep.equal([\n      source,\n      dest, {\n        ...defaultOptions,\n        cidVersion: 5\n      }\n    ])\n  })\n\n  it('should make a directory with a different hash algorithm', async () => {\n    await cli(`files mv --hash-alg sha3-256 ${source} ${dest}`, { ipfs })\n\n    expect(ipfs.files.mv.callCount).to.equal(1)\n    expect(ipfs.files.mv.getCall(0).args).to.deep.equal([\n      source,\n      dest, {\n        ...defaultOptions,\n        hashAlg: 'sha3-256'\n      }\n    ])\n  })\n\n  it('should make a directory with a different hash algorithm (short option)', async () => {\n    await cli(`files mv -h sha3-256 ${source} ${dest}`, { ipfs })\n\n    expect(ipfs.files.mv.callCount).to.equal(1)\n    expect(ipfs.files.mv.getCall(0).args).to.deep.equal([\n      source,\n      dest, {\n        ...defaultOptions,\n        hashAlg: 'sha3-256'\n      }\n    ])\n  })\n\n  it('should make a directory without flushing', async () => {\n    await cli(`files mv --flush false ${source} ${dest}`, { ipfs })\n\n    expect(ipfs.files.mv.callCount).to.equal(1)\n    expect(ipfs.files.mv.getCall(0).args).to.deep.equal([\n      source,\n      dest, {\n        ...defaultOptions,\n        flush: false\n      }\n    ])\n  })\n\n  it('should make a directory without flushing (short option)', async () => {\n    await cli(`files mv -f false ${source} ${dest}`, { ipfs })\n\n    expect(ipfs.files.mv.callCount).to.equal(1)\n    expect(ipfs.files.mv.getCall(0).args).to.deep.equal([\n      source,\n      dest, {\n        ...defaultOptions,\n        flush: false\n      }\n    ])\n  })\n\n  it('should make a directory a different shard split threshold', async () => {\n    await cli(`files mv --shard-split-threshold 10 ${source} ${dest}`, { ipfs })\n\n    expect(ipfs.files.mv.callCount).to.equal(1)\n    expect(ipfs.files.mv.getCall(0).args).to.deep.equal([\n      source,\n      dest, {\n        ...defaultOptions,\n        shardSplitThreshold: 10\n      }\n    ])\n  })\n\n  it('should move an entry with a timeout', async () => {\n    await cli(`files mv ${source} ${dest} --timeout=1s`, { ipfs })\n\n    expect(ipfs.files.mv.callCount).to.equal(1)\n    expect(ipfs.files.mv.getCall(0).args).to.deep.equal([\n      source,\n      dest, {\n        ...defaultOptions,\n        timeout: 1000\n      }\n    ])\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/files/read.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport sinon from 'sinon'\nimport { isNode } from 'ipfs-utils/src/env.js'\nimport { cli } from '../utils/cli.js'\n\nconst defaultOptions = {\n  offset: undefined,\n  length: undefined,\n  timeout: undefined\n}\n\ndescribe('read', () => {\n  if (!isNode) {\n    return\n  }\n\n  const path = '/foo'\n  let ipfs\n  let print\n  let output\n\n  beforeEach(() => {\n    output = ''\n    ipfs = {\n      files: {\n        read: sinon.stub().returns(['hello world'])\n      }\n    }\n    print = (msg = '', newline = true) => {\n      output += newline ? msg + '\\n' : msg\n    }\n  })\n\n  it('should read a path', async () => {\n    await cli(`files read ${path}`, { ipfs, print })\n\n    expect(ipfs.files.read.callCount).to.equal(1)\n    expect(ipfs.files.read.getCall(0).args).to.deep.equal([\n      path,\n      defaultOptions\n    ])\n    expect(output).to.equal('hello world')\n  })\n\n  it('should read a path with an offset', async () => {\n    const offset = 5\n\n    await cli(`files read --offset ${offset} ${path}`, { ipfs, print })\n\n    expect(ipfs.files.read.callCount).to.equal(1)\n    expect(ipfs.files.read.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        offset\n      }\n    ])\n    expect(output).to.equal('hello world')\n  })\n\n  it('should read a path with an offset (short option)', async () => {\n    const offset = 5\n\n    await cli(`files read -o ${offset} ${path}`, { ipfs, print })\n\n    expect(ipfs.files.read.callCount).to.equal(1)\n    expect(ipfs.files.read.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        offset\n      }\n    ])\n    expect(output).to.equal('hello world')\n  })\n\n  it('should read a path with a length', async () => {\n    const length = 5\n\n    await cli(`files read --length ${length} ${path}`, { ipfs, print })\n\n    expect(ipfs.files.read.callCount).to.equal(1)\n    expect(ipfs.files.read.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        length\n      }\n    ])\n    expect(output).to.equal('hello world')\n  })\n\n  it('should read a path with a length (short option)', async () => {\n    const length = 5\n\n    await cli(`files read -l ${length} ${path}`, { ipfs, print })\n\n    expect(ipfs.files.read.callCount).to.equal(1)\n    expect(ipfs.files.read.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        length\n      }\n    ])\n    expect(output).to.equal('hello world')\n  })\n\n  it('should read a path with a timeout', async () => {\n    await cli(`files read ${path} --timeout=1s`, { ipfs, print })\n\n    expect(ipfs.files.read.callCount).to.equal(1)\n    expect(ipfs.files.read.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        timeout: 1000\n      }\n    ])\n    expect(output).to.equal('hello world')\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/files/rm.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport sinon from 'sinon'\nimport { isNode } from 'ipfs-utils/src/env.js'\nimport { cli } from '../utils/cli.js'\n\nconst defaultOptions = {\n  recursive: false,\n  timeout: undefined\n}\n\ndescribe('rm', () => {\n  if (!isNode) {\n    return\n  }\n\n  const path = '/foo'\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      files: {\n        rm: sinon.stub().resolves()\n      }\n    }\n  })\n\n  it('should remove a path', async () => {\n    await cli(`files rm ${path}`, { ipfs })\n\n    expect(ipfs.files.rm.callCount).to.equal(1)\n    expect(ipfs.files.rm.getCall(0).args).to.deep.equal([\n      path,\n      defaultOptions\n    ])\n  })\n\n  it('should remove a path recursively', async () => {\n    await cli(`files rm --recursive ${path}`, { ipfs })\n\n    expect(ipfs.files.rm.callCount).to.equal(1)\n    expect(ipfs.files.rm.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        recursive: true\n      }\n    ])\n  })\n\n  it('should remove a path recursively (short option)', async () => {\n    await cli(`files rm -r ${path}`, { ipfs })\n\n    expect(ipfs.files.rm.callCount).to.equal(1)\n    expect(ipfs.files.rm.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        recursive: true\n      }\n    ])\n  })\n\n  it('should remove a path with a timeout', async () => {\n    await cli(`files rm ${path} --timeout=1s`, { ipfs })\n\n    expect(ipfs.files.rm.callCount).to.equal(1)\n    expect(ipfs.files.rm.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        timeout: 1000\n      }\n    ])\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/files/stat.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport sinon from 'sinon'\nimport { isNode } from 'ipfs-utils/src/env.js'\nimport { CID } from 'multiformats/cid'\nimport { base58btc } from 'multiformats/bases/base58'\nimport { cli } from '../utils/cli.js'\nconst fileCid = CID.parse('bafybeigyov3nzxrqjismjpq7ghkkjorcmozy5rgaikvyieakoqpxfc3rvu')\n\nconst defaultOptions = {\n  withLocal: false,\n  timeout: undefined\n}\n\ndescribe('stat', () => {\n  if (!isNode) {\n    return\n  }\n\n  const path = '/foo'\n  let ipfs\n  let print\n  let output\n\n  beforeEach(() => {\n    output = ''\n    ipfs = {\n      files: {\n        stat: sinon.stub().resolves({\n          cid: fileCid,\n          size: 'stats-size',\n          cumulativeSize: 'stats-cumulativeSize',\n          blocks: 'stats-blocks',\n          type: 'stats-type',\n          mode: 'stats-mode',\n          mtime: 'stats-mtime'\n        })\n      },\n      bases: {\n        getBase: sinon.stub()\n      }\n    }\n    print = (msg = '', newline = true) => {\n      output += newline ? msg + '\\n' : msg\n    }\n  })\n\n  it('should stat a path', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    await cli(`files stat ${path}`, { ipfs, print })\n\n    expect(ipfs.files.stat.callCount).to.equal(1)\n    expect(ipfs.files.stat.getCall(0).args).to.deep.equal([\n      path,\n      defaultOptions\n    ])\n    expect(output).to.include('CumulativeSize')\n  })\n\n  it('should stat a path with local', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    await cli(`files stat --with-local ${path}`, { ipfs, print })\n\n    expect(ipfs.files.stat.callCount).to.equal(1)\n    expect(ipfs.files.stat.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        withLocal: true\n      }\n    ])\n    expect(output).to.include('CumulativeSize')\n  })\n\n  it('should stat a path with local (short option)', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    await cli(`files stat -l ${path}`, { ipfs, print })\n\n    expect(ipfs.files.stat.callCount).to.equal(1)\n    expect(ipfs.files.stat.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        withLocal: true\n      }\n    ])\n    expect(output).to.include('CumulativeSize')\n  })\n\n  it('should stat a path and only show hashes', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    await cli(`files stat --hash ${path}`, { ipfs, print })\n\n    expect(ipfs.files.stat.callCount).to.equal(1)\n    expect(ipfs.files.stat.getCall(0).args).to.deep.equal([\n      path,\n      defaultOptions\n    ])\n    expect(output).to.equal(`${fileCid.toString(base58btc)}\\n`)\n  })\n\n  it('should stat a path and only show hashes (short option)', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    await cli(`files stat -h ${path}`, { ipfs, print })\n\n    expect(ipfs.files.stat.callCount).to.equal(1)\n    expect(ipfs.files.stat.getCall(0).args).to.deep.equal([\n      path,\n      defaultOptions\n    ])\n    expect(output).to.equal(`${fileCid.toString(base58btc)}\\n`)\n  })\n\n  it('should stat a path and only show sizes', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    await cli(`files stat --size ${path}`, { ipfs, print })\n\n    expect(ipfs.files.stat.callCount).to.equal(1)\n    expect(ipfs.files.stat.getCall(0).args).to.deep.equal([\n      path,\n      defaultOptions\n    ])\n    expect(output).to.equal('stats-size\\n')\n  })\n\n  it('should stat a path and only show sizes (short option)', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    await cli(`files stat -s ${path}`, { ipfs, print })\n\n    expect(ipfs.files.stat.callCount).to.equal(1)\n    expect(ipfs.files.stat.getCall(0).args).to.deep.equal([\n      path,\n      defaultOptions\n    ])\n    expect(output).to.equal('stats-size\\n')\n  })\n\n  it('should stat a path with format option', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    await cli(`files stat --format '<mode> <type>' ${path}`, { ipfs, print })\n\n    expect(ipfs.files.stat.callCount).to.equal(1)\n    expect(ipfs.files.stat.getCall(0).args).to.deep.equal([\n      path,\n      defaultOptions\n    ])\n    expect(output).to.equal('---------- stats-type\\n')\n  })\n\n  it('should stat a path with a timeout', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    await cli(`files stat ${path} --timeout=1s`, { ipfs, print })\n\n    expect(ipfs.files.stat.callCount).to.equal(1)\n    expect(ipfs.files.stat.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        timeout: 1000\n      }\n    ])\n    expect(output).to.include('CumulativeSize')\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/files/touch.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport sinon from 'sinon'\nimport { isNode } from 'ipfs-utils/src/env.js'\nimport { cli } from '../utils/cli.js'\n\nconst defaultOptions = {\n  mtime: null,\n  cidVersion: 0,\n  hashAlg: 'sha2-256',\n  flush: true,\n  shardSplitThreshold: 1000,\n  timeout: undefined\n}\n\ndescribe('touch', () => {\n  if (!isNode) {\n    return\n  }\n\n  const path = '/foo'\n  const mtime = {\n    secs: 1000,\n    nsecs: undefined\n  }\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      files: {\n        touch: sinon.stub()\n      }\n    }\n  })\n\n  it('should update the mtime for a file', async () => {\n    await cli(`files touch -m ${mtime.secs} ${path}`, { ipfs })\n\n    expect(ipfs.files.touch.callCount).to.equal(1)\n    expect(ipfs.files.touch.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        mtime\n      }\n    ])\n  })\n\n  it('should update the mtime without flushing', async () => {\n    await cli(`files touch -m ${mtime.secs} --flush false ${path}`, { ipfs })\n\n    expect(ipfs.files.touch.callCount).to.equal(1)\n    expect(ipfs.files.touch.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        mtime,\n        flush: false\n      }\n    ])\n  })\n\n  it('should update the mtime without flushing (short option)', async () => {\n    await cli(`files touch -m ${mtime.secs} -f false ${path}`, { ipfs })\n\n    expect(ipfs.files.touch.callCount).to.equal(1)\n    expect(ipfs.files.touch.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        mtime,\n        flush: false\n      }\n    ])\n  })\n\n  it('should update the mtime with a different hash algorithm', async () => {\n    await cli(`files touch -m ${mtime.secs} --hash-alg sha3-256 ${path}`, { ipfs })\n\n    expect(ipfs.files.touch.callCount).to.equal(1)\n    expect(ipfs.files.touch.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        mtime,\n        hashAlg: 'sha3-256'\n      }\n    ])\n  })\n\n  it('should update the mtime with a different hash algorithm (short option)', async () => {\n    await cli(`files touch -m ${mtime.secs} -h sha3-256 ${path}`, { ipfs })\n\n    expect(ipfs.files.touch.callCount).to.equal(1)\n    expect(ipfs.files.touch.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        mtime,\n        hashAlg: 'sha3-256'\n      }\n    ])\n  })\n\n  it('should update the mtime with a shard split threshold', async () => {\n    await cli(`files touch -m ${mtime.secs} --shard-split-threshold 10 ${path}`, { ipfs })\n\n    expect(ipfs.files.touch.callCount).to.equal(1)\n    expect(ipfs.files.touch.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        mtime,\n        shardSplitThreshold: 10\n      }\n    ])\n  })\n\n  it('should update the mtime and nsecs', async () => {\n    await cli(`files touch -m 5 --mtime-nsecs 10 ${path}`, { ipfs })\n\n    expect(ipfs.files.touch.callCount).to.equal(1)\n    expect(ipfs.files.touch.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        mtime: {\n          secs: 5,\n          nsecs: 10\n        }\n      }\n    ])\n  })\n\n  it('should update the mtime for a file with a timeout', async () => {\n    await cli(`files touch -m ${mtime.secs} ${path} --timeout=1s`, { ipfs })\n\n    expect(ipfs.files.touch.callCount).to.equal(1)\n    expect(ipfs.files.touch.getCall(0).args).to.deep.equal([\n      path, {\n        ...defaultOptions,\n        mtime,\n        timeout: 1000\n      }\n    ])\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/files/write.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport sinon from 'sinon'\nimport { isNode } from 'ipfs-utils/src/env.js'\nimport { cli } from '../utils/cli.js'\n\nconst defaultOptions = {\n  offset: undefined,\n  length: undefined,\n  create: false,\n  truncate: false,\n  rawLeaves: false,\n  reduceSingleLeafToSelf: false,\n  cidVersion: 0,\n  hashAlg: 'sha2-256',\n  parents: false,\n  strategy: 'balanced',\n  flush: true,\n  shardSplitThreshold: 1000,\n  mode: undefined,\n  mtime: undefined,\n  timeout: undefined\n}\n\ndescribe('write', () => {\n  if (!isNode) {\n    return\n  }\n\n  const stdin = 'stdin'\n  const getStdin = () => stdin\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      files: {\n        write: sinon.stub()\n      }\n    }\n  })\n\n  it('should write to a file', async () => {\n    const path = '/foo'\n\n    await cli(`files write ${path}`, { ipfs, getStdin })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.getCall(0).args).to.deep.equal([\n      path,\n      stdin,\n      defaultOptions\n    ])\n  })\n\n  it('should write to a file and create parents', async () => {\n    const path = '/foo'\n\n    await cli(`files write --parents ${path}`, { ipfs, getStdin })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.getCall(0).args).to.deep.equal([\n      path,\n      stdin, {\n        ...defaultOptions,\n        parents: true\n      }\n    ])\n  })\n\n  it('should write to a file and create parents (short option)', async () => {\n    const path = '/foo'\n\n    await cli(`files write -p ${path}`, { ipfs, getStdin })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.getCall(0).args).to.deep.equal([\n      path,\n      stdin, {\n        ...defaultOptions,\n        parents: true\n      }\n    ])\n  })\n\n  it('should write to a file and create it', async () => {\n    const path = '/foo'\n\n    await cli(`files write --create ${path}`, { ipfs, getStdin })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.getCall(0).args).to.deep.equal([\n      path,\n      stdin, {\n        ...defaultOptions,\n        create: true\n      }\n    ])\n  })\n\n  it('should write to a file and create it (short option)', async () => {\n    const path = '/foo'\n\n    await cli(`files write -e ${path}`, { ipfs, getStdin })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.getCall(0).args).to.deep.equal([\n      path,\n      stdin, {\n        ...defaultOptions,\n        create: true\n      }\n    ])\n  })\n\n  it('should write to a file with an offset', async () => {\n    const path = '/foo'\n\n    await cli(`files write --offset 10 ${path}`, { ipfs, getStdin })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.getCall(0).args).to.deep.equal([\n      path,\n      stdin, {\n        ...defaultOptions,\n        offset: 10\n      }\n    ])\n  })\n\n  it('should write to a file with an offset (short option)', async () => {\n    const path = '/foo'\n\n    await cli(`files write -o 10 ${path}`, { ipfs, getStdin })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.getCall(0).args).to.deep.equal([\n      path,\n      stdin, {\n        ...defaultOptions,\n        offset: 10\n      }\n    ])\n  })\n\n  it('should write to a file with a length', async () => {\n    const path = '/foo'\n\n    await cli(`files write --length 10 ${path}`, { ipfs, getStdin })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.getCall(0).args).to.deep.equal([\n      path,\n      stdin, {\n        ...defaultOptions,\n        length: 10\n      }\n    ])\n  })\n\n  it('should write to a file with a length (short option)', async () => {\n    const path = '/foo'\n\n    await cli(`files write -l 10 ${path}`, { ipfs, getStdin })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.getCall(0).args).to.deep.equal([\n      path,\n      stdin, {\n        ...defaultOptions,\n        length: 10\n      }\n    ])\n  })\n\n  it('should write to a file and truncate it', async () => {\n    const path = '/foo'\n\n    await cli(`files write --truncate ${path}`, { ipfs, getStdin })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.getCall(0).args).to.deep.equal([\n      path,\n      stdin, {\n        ...defaultOptions,\n        truncate: true\n      }\n    ])\n  })\n\n  it('should write to a file and truncate it (short option)', async () => {\n    const path = '/foo'\n\n    await cli(`files write -t ${path}`, { ipfs, getStdin })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.getCall(0).args).to.deep.equal([\n      path,\n      stdin, {\n        ...defaultOptions,\n        truncate: true\n      }\n    ])\n  })\n\n  it('should write to a file with raw leaves', async () => {\n    const path = '/foo'\n\n    await cli(`files write --raw-leaves ${path}`, { ipfs, getStdin })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.getCall(0).args).to.deep.equal([\n      path,\n      stdin, {\n        ...defaultOptions,\n        rawLeaves: true\n      }\n    ])\n  })\n\n  it('should write to a file with raw leaves (short option)', async () => {\n    const path = '/foo'\n\n    await cli(`files write -r ${path}`, { ipfs, getStdin })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.getCall(0).args).to.deep.equal([\n      path,\n      stdin, {\n        ...defaultOptions,\n        rawLeaves: true\n      }\n    ])\n  })\n\n  it('should write to a file and reduce a single leaf to one node', async () => {\n    const path = '/foo'\n\n    await cli(`files write --reduce-single-leaf-to-self ${path}`, { ipfs, getStdin })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.getCall(0).args).to.deep.equal([\n      path,\n      stdin, {\n        ...defaultOptions,\n        reduceSingleLeafToSelf: true\n      }\n    ])\n  })\n\n  it('should write to a file without flushing', async () => {\n    const path = '/foo'\n\n    await cli(`files write --flush false ${path}`, { ipfs, getStdin })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.getCall(0).args).to.deep.equal([\n      path,\n      stdin, {\n        ...defaultOptions,\n        flush: false\n      }\n    ])\n  })\n\n  it('should write to a file without flushing (short option)', async () => {\n    const path = '/foo'\n\n    await cli(`files write -f false ${path}`, { ipfs, getStdin })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.getCall(0).args).to.deep.equal([\n      path,\n      stdin, {\n        ...defaultOptions,\n        flush: false\n      }\n    ])\n  })\n\n  it('should write to a file with a specified strategy', async () => {\n    const path = '/foo'\n\n    await cli(`files write --strategy trickle ${path}`, { ipfs, getStdin })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.getCall(0).args).to.deep.equal([\n      path,\n      stdin, {\n        ...defaultOptions,\n        strategy: 'trickle'\n      }\n    ])\n  })\n\n  it('should write to a file with a specified strategy (short option)', async () => {\n    const path = '/foo'\n\n    await cli(`files write -s trickle ${path}`, { ipfs, getStdin })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.getCall(0).args).to.deep.equal([\n      path,\n      stdin, {\n        ...defaultOptions,\n        strategy: 'trickle'\n      }\n    ])\n  })\n\n  it('should write to a file with a specified cid version', async () => {\n    const path = '/foo'\n\n    await cli(`files write --cid-version 5 ${path}`, { ipfs, getStdin })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.getCall(0).args).to.deep.equal([\n      path,\n      stdin, {\n        ...defaultOptions,\n        cidVersion: 5\n      }\n    ])\n  })\n\n  it('should write to a file with a specified cid version (shortish option)', async () => {\n    const path = '/foo'\n\n    await cli(`files write --cid-ver 5 ${path}`, { ipfs, getStdin })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.getCall(0).args).to.deep.equal([\n      path,\n      stdin, {\n        ...defaultOptions,\n        cidVersion: 5\n      }\n    ])\n  })\n\n  it('should write to a file with a specified hash algorithm', async () => {\n    const path = '/foo'\n\n    await cli(`files write --hash-alg sha3-256 ${path}`, { ipfs, getStdin })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.getCall(0).args).to.deep.equal([\n      path,\n      stdin, {\n        ...defaultOptions,\n        hashAlg: 'sha3-256'\n      }\n    ])\n  })\n\n  it('should write to a file with a specified hash algorithm (short option)', async () => {\n    const path = '/foo'\n\n    await cli(`files write -h sha3-256 ${path}`, { ipfs, getStdin })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.getCall(0).args).to.deep.equal([\n      path,\n      stdin, {\n        ...defaultOptions,\n        hashAlg: 'sha3-256'\n      }\n    ])\n  })\n\n  it('should write to a file with a specified shard split threshold', async () => {\n    const path = '/foo'\n\n    await cli(`files write --shard-split-threshold 10 ${path}`, { ipfs, getStdin })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.getCall(0).args).to.deep.equal([\n      path,\n      stdin, {\n        ...defaultOptions,\n        shardSplitThreshold: 10\n      }\n    ])\n  })\n\n  it('should write to a file with a specified mode', async () => {\n    const path = '/foo'\n\n    await cli(`files write --mode 0557 ${path}`, { ipfs, getStdin })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.getCall(0).args).to.deep.equal([\n      path,\n      stdin, {\n        ...defaultOptions,\n        mode: parseInt('0557', 8)\n      }\n    ])\n  })\n\n  it('should write to a file with a specified mtime', async () => {\n    const path = '/foo'\n\n    await cli(`files write --mtime 11 ${path}`, { ipfs, getStdin })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.getCall(0).args).to.deep.equal([\n      path,\n      stdin, {\n        ...defaultOptions,\n        mtime: {\n          secs: 11,\n          nsecs: undefined\n        }\n      }\n    ])\n  })\n\n  it('should write to a file with a specified mtime and mtime nsecs', async () => {\n    const path = '/foo'\n\n    await cli(`files write --mtime 11 --mtime-nsecs 10 ${path}`, { ipfs, getStdin })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.getCall(0).args).to.deep.equal([\n      path,\n      stdin, {\n        ...defaultOptions,\n        mtime: {\n          secs: 11,\n          nsecs: 10\n        }\n      }\n    ])\n  })\n\n  it('should write to a file with a timeout', async () => {\n    const path = '/foo'\n\n    await cli(`files write ${path} --timeout=1s`, { ipfs, getStdin })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.getCall(0).args).to.deep.equal([\n      path,\n      stdin, {\n        ...defaultOptions,\n        timeout: 1000\n      }\n    ])\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/general.spec.js",
    "content": "/* eslint-env mocha */\n\nimport os from 'os'\nimport { promises as fs } from 'fs'\nimport path, { dirname } from 'path'\nimport { nanoid } from 'nanoid'\nimport { expect } from 'aegir/chai'\nimport { promisify } from 'util'\nimport { fail } from './utils/cli.js'\nimport sinon from 'sinon'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\nimport { repoVersion } from 'ipfs-repo/constants'\nimport ncpCb from 'ncp'\nimport { ipfsExec } from './utils/ipfs-exec.js'\nimport { clean } from './utils/clean.js'\nimport { isWindows } from './utils/platforms.js'\nimport { fileURLToPath } from 'url'\n\nconst ncp = promisify(ncpCb)\n\n// @ts-expect-error need to set module to es2020 to use import.meta.url, which we do,\n// but then the \"--module\" setting doesn't get used by the \"--build\" setting\n// which we use to build types from jsdoc\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n\ndescribe.skip('general cli options', () => {\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      version: sinon.stub()\n    }\n  })\n\n  it('should handle unknown arguments correctly', async () => {\n    const out = await fail('random --again', { ipfs })\n    expect(out).to.include('Unknown arguments: again, random')\n  })\n})\n\ndescribe.skip('--migrate', () => {\n  let ipfs, repoPath\n\n  async function setRepoVersion (version) {\n    await fs.writeFile(path.join(repoPath, 'version'), version.toString())\n  }\n\n  async function getRepoVersion () {\n    return parseInt(await fs.readFile(path.join(repoPath, 'version'), 'utf8'))\n  }\n\n  beforeEach(async () => {\n    repoPath = path.join(os.tmpdir(), `ipfs-${nanoid()}`)\n    const v7RepoPath = path.join(__dirname, '../fixtures/v7-repo')\n    await ncp(v7RepoPath, repoPath)\n    ipfs = ipfsExec(repoPath)\n  })\n\n  afterEach(() => clean(repoPath))\n\n  it('should not migrate for daemon command when --migrate flag not set', async () => {\n    // There are no migrations prior to 7 so it's safe to set version to 5 since\n    // the repo is the same. We set to 5 because version 6 & 7 are considered\n    // the same in repo.version.check.\n    await setRepoVersion(5)\n    const err = await ipfs.fail('daemon')\n    expect(err.stderr).to.include('Pass --migrate for automatic migration')\n    const version = await getRepoVersion()\n    expect(version).to.equal(5) // Should not have migrated\n  })\n\n  it('should not migrate for other commands when --migrate flag not set', async () => {\n    // There are no migrations prior to 7 so it's safe to set version to 5 since\n    // the repo is the same. We set to 5 because version 6 & 7 are considered\n    // the same in repo.version.check.\n    await setRepoVersion(5)\n    const err = await ipfs.fail('files ls')\n    expect(err.stderr).to.include('Pass --migrate for automatic migration')\n    const version = await getRepoVersion()\n    expect(version).to.equal(5) // Should not have migrated\n  })\n\n  it('should migrate for daemon command when --migrate flag set', async () => {\n    // There are no migrations prior to 7 so it's safe to set version to 5 since\n    // the repo is the same. We set to 5 because version 6 & 7 are considered\n    // the same in repo.version.check.\n    await setRepoVersion(5)\n\n    const daemon = ipfs('daemon --migrate')\n    let stdout = ''\n    let killed = false\n\n    daemon.stdout.on('data', data => {\n      stdout += uint8ArrayToString(data)\n\n      if (stdout.includes('Daemon is ready') && !killed) {\n        killed = true\n        daemon.kill()\n      }\n    })\n\n    if (isWindows) {\n      await expect(daemon)\n        .to.eventually.be.rejected()\n        .and.to.include({ killed: true })\n        .and.to.have.a.property('stdout').that.includes('Daemon is ready')\n    } else {\n      await expect(daemon)\n        .to.eventually.include('Daemon is ready')\n        .and.to.include('Received interrupt signal, shutting down...')\n    }\n\n    const version = await getRepoVersion()\n    expect(version).to.equal(repoVersion) // Should have migrated to latest\n  })\n\n  it('should migrate for other commands when --migrate flag set', async () => {\n    // There are no migrations prior to 7 so it's safe to set version to 5 since\n    // the repo is the same. We set to 5 because version 6 & 7 are considered\n    // the same in repo.version.check.\n    await setRepoVersion(5)\n    await ipfs('files ls --migrate')\n    const version = await getRepoVersion()\n    expect(version).to.equal(repoVersion) // Should have migrated to latest\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/get.spec.js",
    "content": "/* eslint-env mocha */\n\nimport fs from 'fs'\nimport { expect } from 'aegir/chai'\nimport path from 'path'\nimport { CID } from 'multiformats/cid'\nimport { cli } from './utils/cli.js'\nimport sinon from 'sinon'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { pack } from 'it-tar'\nimport { pipe } from 'it-pipe'\nimport toBuffer from 'it-to-buffer'\nimport { clean } from './utils/clean.js'\nimport Pako from 'pako'\n\nconst defaultOptions = {\n  timeout: undefined,\n  archive: undefined,\n  compress: undefined,\n  compressionLevel: 6\n}\n\n/**\n * @param {import('it-tar').TarImportCandidate[]} files\n */\nasync function * tarballed (files) {\n  yield * pipe(\n    files,\n    pack()\n  )\n}\n\n/**\n * @param {AsyncIterable<Uint8Array>} bytes\n * @param {number} level\n */\nasync function * gzipped (bytes, level = 6) {\n  yield * pipe(\n    bytes,\n    async function * (source) {\n      const buf = await toBuffer(source)\n\n      yield Pako.gzip(buf, {\n        level\n      })\n    }\n  )\n}\n\ndescribe('get', () => {\n  const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n  const buf = uint8ArrayFromString('hello world')\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      get: sinon.stub()\n    }\n  })\n\n  it('should get file', async () => {\n    ipfs.get.withArgs(cid.toString(), defaultOptions).returns(\n      tarballed([{\n        header: {\n          name: `${cid}`,\n          type: 'file',\n          size: buf.length\n        },\n        body: (async function * () {\n          yield buf\n        }())\n      }])\n    )\n\n    const outPath = path.join(process.cwd(), cid.toString())\n    await clean(outPath)\n\n    const out = await cli(`get ${cid}`, { ipfs })\n    expect(out)\n      .to.equal(`Saving file(s) ${cid}\\n`)\n\n    expect(fs.readFileSync(outPath)).to.equalBytes(buf)\n\n    await clean(outPath)\n  })\n\n  it('get file with output option', async () => {\n    ipfs.get.withArgs(cid.toString(), defaultOptions).returns(\n      tarballed([{\n        header: {\n          name: `${cid}`,\n          type: 'file',\n          size: buf.length\n        },\n        body: (async function * () {\n          yield buf\n        }())\n      }])\n    )\n\n    const outPath = path.join(process.cwd(), 'derp')\n    await clean(outPath)\n\n    const out = await cli(`get ${cid} --output ${outPath}`, { ipfs })\n    expect(out)\n      .to.equal(`Saving file(s) ${cid}\\n`)\n\n    expect(fs.readFileSync(path.join(outPath, cid.toString()))).to.equalBytes(buf)\n\n    await clean(outPath)\n  })\n\n  it('should get gzipped file', async () => {\n    ipfs.get.withArgs(cid.toString(), {\n      ...defaultOptions,\n      compress: true\n    }).returns(\n      gzipped(\n        async function * () {\n          yield buf\n        }()\n      )\n    )\n\n    const outPath = path.join(process.cwd(), cid.toString())\n    await clean(outPath)\n\n    const out = await cli(`get ${cid} --compress`, { ipfs })\n    expect(out)\n      .to.equal(`Saving file(s) ${cid}\\n`)\n\n    expect(Pako.inflate(fs.readFileSync(outPath))).to.equalBytes(buf)\n\n    await clean(outPath)\n  })\n\n  it('should get gzipped file with short compress option', async () => {\n    ipfs.get.withArgs(cid.toString(), {\n      ...defaultOptions,\n      compress: true\n    }).returns(\n      gzipped(\n        async function * () {\n          yield buf\n        }()\n      )\n    )\n\n    const outPath = path.join(process.cwd(), cid.toString())\n    await clean(outPath)\n\n    const out = await cli(`get ${cid} -C`, { ipfs })\n    expect(out)\n      .to.equal(`Saving file(s) ${cid}\\n`)\n\n    expect(Pako.inflate(fs.readFileSync(outPath))).to.equalBytes(buf)\n\n    await clean(outPath)\n  })\n\n  it('should get gzipped file with compression level', async () => {\n    const compressionLevel = 9\n\n    ipfs.get.withArgs(cid.toString(), {\n      ...defaultOptions,\n      compress: true,\n      compressionLevel\n    }).returns(\n      gzipped(\n        (async function * () {\n          yield buf\n        }()),\n        compressionLevel\n      )\n    )\n\n    const outPath = path.join(process.cwd(), cid.toString())\n    await clean(outPath)\n\n    const out = await cli(`get ${cid} --compress --compression-level ${compressionLevel}`, { ipfs })\n    expect(out)\n      .to.equal(`Saving file(s) ${cid}\\n`)\n\n    expect(Pako.inflate(fs.readFileSync(outPath))).to.equalBytes(buf)\n\n    await clean(outPath)\n  })\n\n  it('should get gzipped file with short compression level', async () => {\n    const compressionLevel = 9\n\n    ipfs.get.withArgs(cid.toString(), {\n      ...defaultOptions,\n      compress: true,\n      compressionLevel\n    }).returns(\n      gzipped(\n        (async function * () {\n          yield buf\n        }()),\n        compressionLevel\n      )\n    )\n\n    const outPath = path.join(process.cwd(), cid.toString())\n    await clean(outPath)\n\n    const out = await cli(`get ${cid} --compress -l ${compressionLevel}`, { ipfs })\n    expect(out)\n      .to.equal(`Saving file(s) ${cid}\\n`)\n\n    expect(Pako.inflate(fs.readFileSync(outPath))).to.equalBytes(buf)\n\n    await clean(outPath)\n  })\n\n  it('get gzipped directory', async () => {\n    ipfs.get.withArgs(cid.toString(), {\n      ...defaultOptions,\n      compress: true,\n      archive: true\n    }).returns(\n      gzipped(\n        tarballed([{\n          header: {\n            name: `${cid}`,\n            type: 'directory',\n            size: 0\n          }\n        }, {\n          header: {\n            name: `${cid}/foo.txt`,\n            type: 'file',\n            size: buf.length\n          },\n          body: (async function * () {\n            yield buf\n          }())\n        }])\n      )\n    )\n\n    const outPath = path.join(process.cwd(), cid.toString())\n    await clean(outPath)\n\n    const out = await cli(`get ${cid} --archive true --compress true`, { ipfs })\n    expect(out).to.eql(\n      `Saving file(s) ${cid}\\n`\n    )\n\n    expect(fs.statSync(outPath).isFile()).to.be.true()\n    expect(fs.readFileSync(outPath).slice(0, 2)).to.equalBytes([0x1F, 0x8B]) // gzip magic bytes\n\n    await clean(outPath)\n  })\n\n  it('get file with short output option', async () => {\n    ipfs.get.withArgs(cid.toString(), defaultOptions).returns(\n      tarballed([{\n        header: {\n          name: `${cid}`,\n          type: 'file',\n          size: buf.length\n        },\n        body: (async function * () {\n          yield buf\n        }())\n      }])\n    )\n\n    const outPath = path.join(process.cwd(), 'herp')\n    await clean(outPath)\n\n    const out = await cli(`get ${cid} -o ${outPath}`, { ipfs })\n    expect(out)\n      .to.equal(`Saving file(s) ${cid}\\n`)\n\n    expect(fs.readFileSync(path.join(outPath, cid.toString()))).to.equalBytes(buf)\n\n    await clean(outPath)\n  })\n\n  it('get directory', async () => {\n    ipfs.get.withArgs(cid.toString(), defaultOptions).returns(\n      tarballed([{\n        header: {\n          name: `${cid}`,\n          type: 'directory',\n          size: 0\n        }\n      }])\n    )\n\n    const outPath = path.join(process.cwd(), cid.toString())\n    await clean(outPath)\n\n    const out = await cli(`get ${cid}`, { ipfs })\n    expect(out)\n      .to.equal(`Saving file(s) ${cid}\\n`)\n\n    expect(fs.statSync(outPath).isDirectory()).to.be.true()\n\n    await clean(outPath)\n  })\n\n  it('get recursively', async () => {\n    ipfs.get.withArgs(cid.toString(), defaultOptions).returns(\n      tarballed([{\n        header: {\n          name: `${cid}`,\n          type: 'directory',\n          size: 0\n        }\n      }, {\n        header: {\n          name: `${cid}/foo.txt`,\n          type: 'file',\n          size: buf.length\n        },\n        body: (async function * () {\n          yield buf\n        }())\n      }])\n    )\n\n    const outPath = path.join(process.cwd(), cid.toString())\n    await clean(outPath)\n\n    const out = await cli(`get ${cid}`, { ipfs })\n    expect(out).to.eql(\n      `Saving file(s) ${cid}\\n`\n    )\n\n    expect(fs.statSync(outPath).isDirectory()).to.be.true()\n    expect(fs.statSync(path.join(outPath, 'foo.txt')).isFile()).to.be.true()\n    expect(fs.readFileSync(path.join(outPath, 'foo.txt'))).to.equalBytes(buf)\n\n    await clean(outPath)\n  })\n\n  it('should get file with a timeout', async () => {\n    ipfs.get.withArgs(cid.toString(), {\n      ...defaultOptions,\n      timeout: 1000\n    }).returns(\n      tarballed([{\n        header: {\n          name: `${cid}`,\n          type: 'file',\n          size: buf.length\n        },\n        body: (async function * () {\n          yield buf\n        }())\n      }])\n    )\n\n    const outPath = path.join(process.cwd(), cid.toString())\n    await clean(outPath)\n\n    const out = await cli(`get ${cid} --timeout=1s`, { ipfs })\n    expect(out)\n      .to.equal(`Saving file(s) ${cid}\\n`)\n\n    expect(fs.readFileSync(outPath)).to.equalBytes(buf)\n\n    await clean(outPath)\n  })\n\n  it('should not get file with path traversal characters that result in leaving the output directory', async () => {\n    ipfs.get.withArgs(cid.toString(), defaultOptions).returns(\n      tarballed([{\n        header: {\n          name: '../foo.txt',\n          type: 'file',\n          size: buf.length\n        },\n        body: (async function * () {\n          yield buf\n        }())\n      }])\n    )\n\n    const outPath = path.join(process.cwd(), 'derp')\n\n    await expect(cli(`get ${cid} --output ${outPath}`, { ipfs })).to.eventually.be.rejectedWith(/File prefix invalid/)\n  })\n\n  it('should get file with path traversal characters that result in leaving the output directory when forced', async () => {\n    ipfs.get.withArgs(cid.toString(), defaultOptions).returns(\n      tarballed([{\n        header: {\n          name: '../foo.txt',\n          type: 'file',\n          size: buf.length\n        },\n        body: (async function * () {\n          yield buf\n        }())\n      }])\n    )\n\n    const dir = path.join(process.cwd(), 'derp')\n    const outPath = path.join(process.cwd(), 'derp', 'herp')\n    await clean(dir)\n\n    const out = await cli(`get ${cid} --output ${outPath} --force`, { ipfs })\n    expect(out)\n      .to.equal(`Saving file(s) ${cid}\\n`)\n\n    expect(fs.readFileSync(path.join(dir, 'foo.txt'))).to.equalBytes(buf)\n\n    await clean(dir)\n  })\n\n  it('should strip control characters when getting a file', async function () {\n    if (process.platform === 'win32') {\n      // windows cannot write files with control characters in the path\n      return this.skip()\n    }\n\n    const ipfsPath = `${cid}/foo/bar`\n    const junkPath = `${cid}/foo\\b/bar`\n\n    ipfs.get.withArgs(junkPath, defaultOptions).returns(\n      tarballed([{\n        header: {\n          name: junkPath,\n          type: 'file',\n          size: buf.length\n        },\n        body: (async function * () {\n          yield buf\n        }())\n      }])\n    )\n\n    const outPath = `${process.cwd()}/${junkPath}`\n    await clean(outPath)\n\n    const out = await cli(`get ${junkPath}`, { ipfs })\n    expect(out)\n      .to.equal(`Saving file(s) ${ipfsPath}\\n`)\n\n    expect(fs.readFileSync(outPath)).to.equalBytes(buf)\n\n    await clean(outPath)\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/id.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { cli } from './utils/cli.js'\nimport sinon from 'sinon'\nimport { peerIdFromString } from '@libp2p/peer-id'\n\nconst defaultOptions = {\n  timeout: undefined,\n  peerId: undefined\n}\n\ndescribe('id', () => {\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      id: sinon.stub()\n    }\n  })\n\n  it('should output json string', async () => {\n    ipfs.id.withArgs(defaultOptions).resolves({\n      id: 'id',\n      publicKey: 'publicKey'\n    })\n\n    const out = await cli('id', { ipfs })\n    const res = JSON.parse(out)\n    expect(res).to.have.property('id', 'id')\n    expect(res).to.have.property('publicKey', 'publicKey')\n  })\n\n  it('should output formatted string', async () => {\n    ipfs.id.withArgs(defaultOptions).resolves({\n      id: 'id',\n      publicKey: 'publicKey'\n    })\n\n    const out = await cli('id --format \"<id> <pubkey>\"', { ipfs })\n    expect(out).to.equal('id publicKey\\n')\n  })\n\n  it('should output string with timeout', async () => {\n    ipfs.id.withArgs({\n      ...defaultOptions,\n      timeout: 1000\n    }).resolves({\n      id: 'id',\n      publicKey: 'publicKey'\n    })\n\n    const out = await cli('id --timeout=1s', { ipfs })\n    const res = JSON.parse(out)\n    expect(res).to.have.property('id', 'id')\n    expect(res).to.have.property('publicKey', 'publicKey')\n  })\n\n  it('get the id of another peer', async () => {\n    const peerId = peerIdFromString('QmSnuWmxptJZdLJpKRarxBMS2Ju2oANVrgbr2xWbie9b2D')\n\n    ipfs.id.withArgs({\n      ...defaultOptions,\n      peerId: peerId.toString()\n    }).resolves({\n      id: 'id',\n      publicKey: 'publicKey'\n    })\n\n    const out = await cli(`id ${peerId}`, { ipfs })\n    const res = JSON.parse(out)\n    expect(res).to.have.property('id', 'id')\n    expect(res).to.have.property('publicKey', 'publicKey')\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/init.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport path from 'path'\nimport fs from 'fs'\nimport { nanoid } from 'nanoid'\nimport os from 'os'\nimport { unmarshalPrivateKey, supportedKeys } from '@libp2p/crypto/keys'\nimport { clean } from './utils/clean.js'\nimport { ipfsExec } from './utils/ipfs-exec.js'\nimport tempWrite from 'temp-write'\nimport { peerIdFromKeys } from '@libp2p/peer-id'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\n\ndescribe('init', function () {\n  let repoPath\n  let ipfs\n\n  const repoExistsSync = (p) => fs.existsSync(path.join(repoPath, p))\n\n  const repoDirSync = (p) => {\n    return fs.readdirSync(path.join(repoPath, p)).filter((f) => {\n      return !f.startsWith('.')\n    })\n  }\n\n  const repoConfSync = (p) => {\n    return JSON.parse(fs.readFileSync(path.join(repoPath, 'config')))\n  }\n\n  beforeEach(() => {\n    repoPath = os.tmpdir() + '/ipfs-' + nanoid()\n    ipfs = ipfsExec(repoPath)\n  })\n\n  afterEach(() => clean(repoPath))\n\n  it('basic', async function () {\n    const out = await ipfs('init')\n    expect(repoDirSync('blocks')).to.have.length.above(2)\n    expect(repoExistsSync('config')).to.equal(true)\n    expect(repoExistsSync('version')).to.equal(true)\n\n    // Test that the following was written when init-ing the repo\n    // jsipfs cat /ipfs/QmfGBRT6BbWJd7yUc2uYdaUZJBbnEFvTqehPFoSMQ6wgdr/readme\n    const command = out.substring(out.indexOf('cat'), out.length - 2 /* omit the newline char */)\n    const out2 = await ipfs(command)\n    expect(out2).to.include('Hello and Welcome to IPFS!')\n  })\n\n  it('algorithm', async function () {\n    await ipfs('init --algorithm ed25519')\n    const buf = uint8ArrayFromString(repoConfSync().Identity.PrivKey, 'base64pad')\n    const key = await unmarshalPrivateKey(buf)\n    const peerId = await peerIdFromKeys(key.public.bytes, key.bytes)\n    const privateKey = await unmarshalPrivateKey(peerId.privateKey)\n    expect(privateKey).is.instanceOf(supportedKeys.ed25519.Ed25519PrivateKey)\n  })\n\n  it('bits', async function () {\n    await ipfs('init --bits 1024')\n    expect(repoDirSync('blocks')).to.have.length.above(2)\n    expect(repoExistsSync('config')).to.equal(true)\n    expect(repoExistsSync('version')).to.equal(true)\n  })\n\n  it('empty', async function () {\n    await ipfs('init --bits 1024 --empty-repo true')\n    expect(repoDirSync('blocks')).to.have.length(2)\n    expect(repoExistsSync('config')).to.equal(true)\n    expect(repoExistsSync('version')).to.equal(true)\n  })\n\n  it('profile', async function () {\n    await ipfs('init --profile lowpower')\n    expect(repoConfSync().Swarm.ConnMgr.LowWater).to.equal(20)\n  })\n\n  it('profile multiple', async function () {\n    await ipfs('init --profile server,lowpower')\n    expect(repoConfSync().Discovery.MDNS.Enabled).to.equal(false)\n    expect(repoConfSync().Swarm.ConnMgr.LowWater).to.equal(20)\n  })\n\n  it('profile non-existent', async function () {\n    await expect(ipfs('init --profile doesnt-exist'))\n      .to.eventually.be.rejected()\n      .and.to.have.property('stderr').that.includes('Could not find profile')\n  })\n\n  it('should present ipfs path help when option help is received', async function () {\n    const res = await ipfs('init --help')\n    expect(res).to.have.string('export IPFS_PATH=')\n  })\n\n  it('default config argument', async () => {\n    const configPath = tempWrite.sync('{\"Addresses\": {\"API\": \"/ip4/127.0.0.1/tcp/9999\"}}', 'config.json')\n    await ipfs(`init ${configPath}`)\n    const configRaw = fs.readFileSync(path.join(repoPath, 'config')).toString()\n    const config = JSON.parse(configRaw)\n    expect(config.Addresses.API).to.be.eq('/ip4/127.0.0.1/tcp/9999')\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/key.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { cli } from './utils/cli.js'\nimport sinon from 'sinon'\n\ndescribe('key', () => {\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      key: {\n        export: sinon.stub(),\n        gen: sinon.stub(),\n        import: sinon.stub(),\n        list: sinon.stub(),\n        rename: sinon.stub(),\n        rm: sinon.stub()\n      }\n    }\n  })\n\n  describe('gen', () => {\n    const name = 'key-name'\n    const id = 'key-id'\n    const defaultOptions = {\n      type: 'Ed25519',\n      size: 2048,\n      timeout: undefined\n    }\n\n    it('should generate a key', async () => {\n      ipfs.key.gen.withArgs(name, defaultOptions).resolves({\n        id,\n        name\n      })\n\n      const out = await cli(`key gen ${name}`, { ipfs })\n      expect(out).to.equal(`generated ${id} ${name}\\n`)\n    })\n\n    it('gen with args', async () => {\n      ipfs.key.gen.withArgs(name, {\n        ...defaultOptions,\n        type: 'RSA',\n        size: 7\n      }).resolves({\n        id,\n        name\n      })\n\n      const out = await cli(`key gen ${name} --type rsa --size 7`, { ipfs })\n      expect(out).to.equal(`generated ${id} ${name}\\n`)\n    })\n\n    it('gen with short args', async () => {\n      ipfs.key.gen.withArgs(name, {\n        ...defaultOptions,\n        type: 'RSA',\n        size: 5\n      }).resolves({\n        id,\n        name\n      })\n\n      const out = await cli(`key gen ${name} -t rsa -s 5`, { ipfs })\n      expect(out).to.equal(`generated ${id} ${name}\\n`)\n    })\n\n    it('gen with a timeout', async () => {\n      ipfs.key.gen.withArgs(name, {\n        ...defaultOptions,\n        timeout: 1000\n      }).resolves({\n        id,\n        name\n      })\n\n      const out = await cli(`key gen ${name} --timeout=1s`, { ipfs })\n      expect(out).to.equal(`generated ${id} ${name}\\n`)\n    })\n\n    it('gen strips control characters', async () => {\n      const junkName = `${name}\\b\\b\\b`\n\n      ipfs.key.gen.withArgs(junkName, defaultOptions).resolves({\n        id,\n        name: junkName\n      })\n\n      const out = await cli(`key gen ${junkName}`, { ipfs })\n      expect(out).to.equal(`generated ${id} ${name}\\n`)\n    })\n  })\n\n  describe('list', () => {\n    const name = 'key-name'\n    const id = 'key-id'\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('should list keys', async () => {\n      ipfs.key.list.withArgs(defaultOptions).resolves([{\n        id,\n        name\n      }])\n      const out = await cli('key list', { ipfs })\n      expect(out).to.equal(`${id} ${name}\\n`)\n    })\n\n    it('should list keys with a timeout', async () => {\n      ipfs.key.list.withArgs({\n        ...defaultOptions,\n        timeout: 1000\n      }).resolves([{\n        id,\n        name\n      }])\n      const out = await cli('key list --timeout=1s', { ipfs })\n      expect(out).to.equal(`${id} ${name}\\n`)\n    })\n\n    it('list strips control characters', async () => {\n      const junkName = `${name}\\b\\b\\b`\n\n      ipfs.key.list.withArgs(defaultOptions).resolves([{\n        id,\n        name: junkName\n      }])\n\n      const out = await cli('key list', { ipfs })\n      expect(out).to.equal(`${id} ${name}\\n`)\n    })\n  })\n\n  describe('rename', () => {\n    const name = 'key-name'\n    const newName = 'new-key-name'\n    const id = 'key-id'\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('should rename a key', async () => {\n      ipfs.key.rename.withArgs(name, newName, defaultOptions).resolves({\n        id,\n        now: newName\n      })\n      const out = await cli(`key rename ${name} ${newName}`, { ipfs })\n      expect(out).to.equal(`renamed to ${id} ${newName}\\n`)\n    })\n\n    it('should rename a key with a timeout', async () => {\n      ipfs.key.rename.withArgs(name, newName, {\n        ...defaultOptions,\n        timeout: 1000\n      }).resolves({\n        id,\n        now: newName\n      })\n      const out = await cli(`key rename ${name} ${newName} --timeout=1s`, { ipfs })\n      expect(out).to.equal(`renamed to ${id} ${newName}\\n`)\n    })\n\n    it('rename strips control characters', async () => {\n      const junkName = `${name}\\b\\b\\b`\n\n      ipfs.key.rename.withArgs(name, junkName, defaultOptions).resolves({\n        id,\n        now: junkName\n      })\n\n      const out = await cli(`key rename ${name} ${junkName}`, { ipfs })\n      expect(out).to.equal(`renamed to ${id} ${name}\\n`)\n    })\n  })\n\n  describe('rm', () => {\n    const name = 'key-name'\n    const id = 'key-id'\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('should remove a key', async () => {\n      ipfs.key.rm.withArgs(name, defaultOptions).resolves({\n        id,\n        name\n      })\n\n      const out = await cli(`key rm ${name}`, { ipfs })\n      expect(out).to.equal(`${id} ${name}\\n`)\n    })\n\n    it('should remove a key with a timeout', async () => {\n      ipfs.key.rm.withArgs(name, {\n        ...defaultOptions,\n        timeout: 1000\n      }).resolves({\n        id,\n        name\n      })\n\n      const out = await cli(`key rm ${name} --timeout=1s`, { ipfs })\n      expect(out).to.equal(`${id} ${name}\\n`)\n    })\n\n    it('remove strips control characters', async () => {\n      const junkName = `${name}\\b\\b\\b`\n\n      ipfs.key.rm.withArgs(junkName, defaultOptions).resolves({\n        id,\n        name: junkName\n      })\n\n      const out = await cli(`key rm ${junkName}`, { ipfs })\n      expect(out).to.equal(`${id} ${name}\\n`)\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/ls.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { cli } from './utils/cli.js'\nimport sinon from 'sinon'\nimport { CID } from 'multiformats/cid'\nimport { base58btc } from 'multiformats/bases/base58'\nimport { base64 } from 'multiformats/bases/base64'\n\nconst defaultOptions = {\n  timeout: undefined\n}\n\ndescribe('ls', () => {\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      ls: sinon.stub(),\n      bases: {\n        getBase: sinon.stub()\n      }\n    }\n\n    ipfs.ls.withArgs('Qmaj2NmcyAXT8dFmZRRytE12wpcaHADzbChKToMEjBsj5Z', defaultOptions).returns([{\n      mode: 0o755,\n      mtime: null,\n      cid: CID.parse('QmamKEPmEH9RUsqRQsfNf5evZQDQPYL9KXg1ADeT7mkHkT'),\n      type: 'dir',\n      name: 'blocks'\n    }, {\n      mode: 0o644,\n      mtime: null,\n      cid: CID.parse('QmPkWYfSLCEBLZu7BZt4kigGDMe3cpogMbeVf97gN2xJDN'),\n      type: 'file',\n      name: 'config',\n      size: 3928\n    }])\n\n    ipfs.ls.withArgs('Qmaj2NmcyAXT8dFmZRRytE12wpcaHADzbChKToMEjBsj5Z', {\n      ...defaultOptions,\n      timeout: 1000\n    }).returns([{\n      mode: 0o755,\n      mtime: null,\n      cid: CID.parse('QmamKEPmEH9RUsqRQsfNf5evZQDQPYL9KXg1ADeT7mkHkT'),\n      type: 'dir',\n      name: 'blocks'\n    }, {\n      mode: 0o644,\n      mtime: null,\n      cid: CID.parse('QmPkWYfSLCEBLZu7BZt4kigGDMe3cpogMbeVf97gN2xJDN'),\n      type: 'file',\n      name: 'config',\n      size: 3928\n    }])\n\n    ipfs.ls.withArgs('/ipfs/Qmaj2NmcyAXT8dFmZRRytE12wpcaHADzbChKToMEjBsj5Z', defaultOptions).returns([{\n      mode: 0o755,\n      mtime: null,\n      cid: CID.parse('QmamKEPmEH9RUsqRQsfNf5evZQDQPYL9KXg1ADeT7mkHkT'),\n      type: 'dir',\n      name: 'blocks'\n    }, {\n      mode: 0o644,\n      mtime: null,\n      cid: CID.parse('QmPkWYfSLCEBLZu7BZt4kigGDMe3cpogMbeVf97gN2xJDN'),\n      type: 'file',\n      name: 'config',\n      size: 3928\n    }])\n\n    ipfs.ls.withArgs('Qmaj2NmcyAXT8dFmZRRytE12wpcaHADzbChKToMEjBsj5Z/blocks', defaultOptions).returns([{\n      mode: 0o644,\n      mtime: null,\n      cid: CID.parse('QmQ8ag7ysVyCMzJGFjxrUStwWtniQ69c7G9aezbmsKeNYD'),\n      type: 'file',\n      name: 'CIQLBK52T5EHVHZY5URTG5JS3JCUJDQM2DRB5RVF33DCUUOFJNGVDUI.data',\n      size: 10849\n    }, {\n      mode: 0o644,\n      mtime: null,\n      cid: CID.parse('QmaSjzSSRanYzRGPXQY6m5SWfSkkfcnzNkurJEQc4chPJx'),\n      type: 'file',\n      name: 'CIQLBS5HG4PRCRQ7O4EBXFD5QN6MTI5YBYMCVQJDXPKCOVR6RMLHZFQ.data',\n      size: 10807\n    }])\n\n    ipfs.ls.withArgs('bafyreicyer3d34cutdzlsbe2nqu5ye62mesuhwkcnl2ypdwpccrsecfmjq', defaultOptions).returns([{\n      mode: 0o755,\n      mtime: null,\n      cid: CID.parse('bafyreicyer3d34cutdzlsbe2nqu5ye62mesuhwkcnl2ypdwpccrsecfmjq'),\n      type: 'dir',\n      name: 'blocks',\n      depth: 0\n    }])\n  })\n\n  it('prints added files', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const out = await cli('ls Qmaj2NmcyAXT8dFmZRRytE12wpcaHADzbChKToMEjBsj5Z', { ipfs })\n    expect(out).to.eql(\n      'drwxr-xr-x - QmamKEPmEH9RUsqRQsfNf5evZQDQPYL9KXg1ADeT7mkHkT - blocks/\\n' +\n      '-rw-r--r-- - QmPkWYfSLCEBLZu7BZt4kigGDMe3cpogMbeVf97gN2xJDN 3928 config\\n'\n    )\n  })\n\n  it('prints added files with /ipfs/ prefix', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const out = await cli('ls /ipfs/Qmaj2NmcyAXT8dFmZRRytE12wpcaHADzbChKToMEjBsj5Z', { ipfs })\n    expect(out).to.eql(\n      'drwxr-xr-x - QmamKEPmEH9RUsqRQsfNf5evZQDQPYL9KXg1ADeT7mkHkT - blocks/\\n' +\n      '-rw-r--r-- - QmPkWYfSLCEBLZu7BZt4kigGDMe3cpogMbeVf97gN2xJDN 3928 config\\n'\n    )\n  })\n\n  it('supports a trailing slash', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const out = await cli('ls Qmaj2NmcyAXT8dFmZRRytE12wpcaHADzbChKToMEjBsj5Z/', { ipfs })\n    expect(out).to.eql(\n      'drwxr-xr-x - QmamKEPmEH9RUsqRQsfNf5evZQDQPYL9KXg1ADeT7mkHkT - blocks/\\n' +\n      '-rw-r--r-- - QmPkWYfSLCEBLZu7BZt4kigGDMe3cpogMbeVf97gN2xJDN 3928 config\\n'\n    )\n  })\n\n  it('supports multiple trailing slashes', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const out = await cli('ls Qmaj2NmcyAXT8dFmZRRytE12wpcaHADzbChKToMEjBsj5Z///', { ipfs })\n    expect(out).to.eql(\n      'drwxr-xr-x - QmamKEPmEH9RUsqRQsfNf5evZQDQPYL9KXg1ADeT7mkHkT - blocks/\\n' +\n      '-rw-r--r-- - QmPkWYfSLCEBLZu7BZt4kigGDMe3cpogMbeVf97gN2xJDN 3928 config\\n'\n    )\n  })\n\n  it('supports multiple intermediate slashes', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const out = await cli('ls Qmaj2NmcyAXT8dFmZRRytE12wpcaHADzbChKToMEjBsj5Z///blocks', { ipfs })\n    expect(out).to.eql(\n      '-rw-r--r-- - QmQ8ag7ysVyCMzJGFjxrUStwWtniQ69c7G9aezbmsKeNYD 10849 CIQLBK52T5EHVHZY5URTG5JS3JCUJDQM2DRB5RVF33DCUUOFJNGVDUI.data\\n' +\n      '-rw-r--r-- - QmaSjzSSRanYzRGPXQY6m5SWfSkkfcnzNkurJEQc4chPJx 10807 CIQLBS5HG4PRCRQ7O4EBXFD5QN6MTI5YBYMCVQJDXPKCOVR6RMLHZFQ.data\\n'\n    )\n  })\n\n  it('adds a header, -v', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const out = await cli('ls Qmaj2NmcyAXT8dFmZRRytE12wpcaHADzbChKToMEjBsj5Z -v', { ipfs })\n    expect(out).to.eql(\n      'Mode       Mtime Hash                                           Size Name\\n' +\n      'drwxr-xr-x -     QmamKEPmEH9RUsqRQsfNf5evZQDQPYL9KXg1ADeT7mkHkT -    blocks/\\n' +\n      '-rw-r--r-- -     QmPkWYfSLCEBLZu7BZt4kigGDMe3cpogMbeVf97gN2xJDN 3928 config\\n'\n    )\n  })\n\n  it('should ls and print CIDs encoded in specified base', async () => {\n    ipfs.bases.getBase.withArgs('base64').returns(base64)\n\n    const out = await cli('ls bafyreicyer3d34cutdzlsbe2nqu5ye62mesuhwkcnl2ypdwpccrsecfmjq --cid-base=base64', { ipfs })\n    expect(out).to.eql(\n      'drwxr-xr-x - mAXESIFgkdj3wVJjyuQSabCncE9phJUPZQmr1h47PEKMiCKxM - blocks/\\n'\n    )\n  })\n\n  it('prints added files with a timeout', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    const out = await cli('ls Qmaj2NmcyAXT8dFmZRRytE12wpcaHADzbChKToMEjBsj5Z --timeout=1s', { ipfs })\n    expect(out).to.eql(\n      'drwxr-xr-x - QmamKEPmEH9RUsqRQsfNf5evZQDQPYL9KXg1ADeT7mkHkT - blocks/\\n' +\n      '-rw-r--r-- - QmPkWYfSLCEBLZu7BZt4kigGDMe3cpogMbeVf97gN2xJDN 3928 config\\n'\n    )\n  })\n\n  it('removes control characters from paths', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    ipfs.ls.withArgs('Qmaj2NmcyAXT8dFmZRRytE12wpcaHADzbChKToMEjBsj5Z', defaultOptions).returns([{\n      mode: 0o755,\n      mtime: null,\n      cid: CID.parse('QmamKEPmEH9RUsqRQsfNf5evZQDQPYL9KXg1ADeT7mkHkT'),\n      type: 'dir',\n      name: 'bl\\nock\\bs',\n      depth: 0\n    }, {\n      mode: 0o644,\n      mtime: null,\n      cid: CID.parse('QmPkWYfSLCEBLZu7BZt4kigGDMe3cpogMbeVf97gN2xJDN'),\n      type: 'file',\n      name: 'co\\r\\tnfig',\n      size: 3928,\n      depth: 0\n    }])\n\n    const out = await cli('ls Qmaj2NmcyAXT8dFmZRRytE12wpcaHADzbChKToMEjBsj5Z', { ipfs })\n    expect(out).to.eql(\n      'drwxr-xr-x - QmamKEPmEH9RUsqRQsfNf5evZQDQPYL9KXg1ADeT7mkHkT - blocks/\\n' +\n      '-rw-r--r-- - QmPkWYfSLCEBLZu7BZt4kigGDMe3cpogMbeVf97gN2xJDN 3928 config\\n'\n    )\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/name-pubsub.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { cli } from './utils/cli.js'\nimport sinon from 'sinon'\n\ndescribe('name pubsub', () => {\n  let ipfs\n\n  // Spawn daemons\n  beforeEach(() => {\n    ipfs = {\n      name: {\n        pubsub: {\n          state: sinon.stub(),\n          cancel: sinon.stub(),\n          subs: sinon.stub()\n        },\n        resolve: sinon.stub()\n      }\n    }\n  })\n\n  describe('state', () => {\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('should get enabled state of pubsub', async () => {\n      ipfs.name.pubsub.state.withArgs(defaultOptions).resolves({\n        enabled: true\n      })\n\n      const res = await cli('name pubsub state', { ipfs })\n      expect(res).to.have.string('enabled') // enabled\n    })\n\n    it('should get enabled state of pubsub with a timeout', async () => {\n      ipfs.name.pubsub.state.withArgs({\n        ...defaultOptions,\n        timeout: 1000\n      }).resolves({\n        enabled: true\n      })\n\n      const res = await cli('name pubsub state --timeout=1s', { ipfs })\n      expect(res).to.have.string('enabled') // enabled\n    })\n  })\n\n  describe('cancel', () => {\n    const subName = 'sub-name'\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('should be able to cancel subscriptions', async () => {\n      ipfs.name.pubsub.cancel.withArgs(subName, defaultOptions).resolves({\n        canceled: true\n      })\n\n      const out = await cli(`name pubsub cancel ${subName}`, { ipfs })\n\n      expect(out).to.equal('canceled\\n')\n    })\n\n    it('should not cancel subscriptions that do not exist', async () => {\n      ipfs.name.pubsub.cancel.withArgs(subName, defaultOptions).resolves({\n        canceled: false\n      })\n\n      const out = await cli(`name pubsub cancel ${subName}`, { ipfs })\n\n      expect(out).to.equal('no subscription\\n')\n    })\n\n    it('should be able to cancel subscriptions with a timeout', async () => {\n      ipfs.name.pubsub.cancel.withArgs(subName, {\n        ...defaultOptions,\n        timeout: 1000\n      }).resolves({\n        canceled: true\n      })\n\n      const out = await cli(`name pubsub cancel ${subName} --timeout=1s`, { ipfs })\n\n      expect(out).to.equal('canceled\\n')\n    })\n  })\n\n  describe('list', () => {\n    const subName = 'sub-name'\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('should list subscriptions', async () => {\n      ipfs.name.pubsub.subs.withArgs(defaultOptions).resolves([\n        subName\n      ])\n\n      const out = await cli('name pubsub subs', { ipfs })\n\n      expect(out).to.equal(`${subName}\\n`)\n    })\n\n    it('should list subscriptions with a timeout', async () => {\n      ipfs.name.pubsub.subs.withArgs({\n        ...defaultOptions,\n        timeout: 1000\n      }).resolves([\n        subName\n      ])\n\n      const out = await cli('name pubsub subs --timeout=1s', { ipfs })\n\n      expect(out).to.equal(`${subName}\\n`)\n    })\n\n    it('should strip control characters when listing subscriptions', async () => {\n      const junkSubname = `${subName}\\n\\b`\n\n      ipfs.name.pubsub.subs.withArgs(defaultOptions).resolves([\n        junkSubname\n      ])\n\n      const out = await cli('name pubsub subs', { ipfs })\n\n      expect(out).to.equal(`${subName}\\n`)\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/name.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { cli } from './utils/cli.js'\nimport sinon from 'sinon'\n\ndescribe('name', () => {\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      name: {\n        publish: sinon.stub(),\n        resolve: sinon.stub()\n      }\n    }\n  })\n\n  describe('resolve', () => {\n    const defaultOptions = {\n      nocache: false,\n      recursive: true,\n      timeout: undefined\n    }\n\n    it('should resolve a name', async () => {\n      const name = 'herp'\n      const value = 'derp'\n\n      ipfs.name.resolve.withArgs(name, defaultOptions).returns([\n        value\n      ])\n\n      const out = await cli(`name resolve ${name}`, { ipfs })\n      expect(out).to.equal(`${value}\\n`)\n    })\n\n    it('resolve with no cache', async () => {\n      const name = 'name'\n      const value = 'value'\n\n      ipfs.name.resolve.withArgs(name, {\n        ...defaultOptions,\n        nocache: true\n      }).returns([\n        value\n      ])\n\n      const out = await cli(`name resolve --nocache ${name}`, { ipfs })\n      expect(out).to.equal(`${value}\\n`)\n    })\n\n    it('resolve with no cache (short option)', async () => {\n      const name = 'name'\n      const value = 'value'\n\n      ipfs.name.resolve.withArgs(name, {\n        ...defaultOptions,\n        nocache: true\n      }).returns([\n        value\n      ])\n\n      const out = await cli(`name resolve -n ${name}`, { ipfs })\n      expect(out).to.equal(`${value}\\n`)\n    })\n\n    it('resolve with no recursion', async () => {\n      const name = 'name'\n      const value = 'value'\n\n      ipfs.name.resolve.withArgs(name, {\n        ...defaultOptions,\n        recursive: false\n      }).returns([\n        value\n      ])\n\n      const out = await cli(`name resolve --recursive false ${name}`, { ipfs })\n      expect(out).to.equal(`${value}\\n`)\n    })\n\n    it('resolve with no recursion (short option)', async () => {\n      const name = 'name'\n      const value = 'value'\n\n      ipfs.name.resolve.withArgs(name, {\n        ...defaultOptions,\n        recursive: false\n      }).returns([\n        value\n      ])\n\n      const out = await cli(`name resolve -r false ${name}`, { ipfs })\n      expect(out).to.equal(`${value}\\n`)\n    })\n\n    it('resolve with streaming', async () => {\n      const name = 'name'\n      const value1 = 'value1'\n      const value2 = 'value2'\n\n      ipfs.name.resolve.withArgs(name, defaultOptions).returns([\n        value1,\n        value2\n      ])\n\n      const out = await cli(`name resolve --stream ${name}`, { ipfs })\n      expect(out).to.equal(`${value1}\\n${value2}\\n`)\n    })\n\n    it('resolve with streaming (short option)', async () => {\n      const name = 'name'\n      const value1 = 'value1'\n      const value2 = 'value2'\n\n      ipfs.name.resolve.withArgs(name, defaultOptions).returns([\n        value1,\n        value2\n      ])\n\n      const out = await cli(`name resolve -s ${name}`, { ipfs })\n      expect(out).to.equal(`${value1}\\n${value2}\\n`)\n    })\n\n    it('resolve multiple results without streaming only prints the last result', async () => {\n      const name = 'name'\n      const value1 = 'value1'\n      const value2 = 'value2'\n\n      ipfs.name.resolve.withArgs(name, defaultOptions).returns([\n        value1,\n        value2\n      ])\n\n      const out = await cli(`name resolve ${name}`, { ipfs })\n      expect(out).to.equal(`${value2}\\n`)\n    })\n\n    it('should resolve a name with a timeout', async () => {\n      const name = 'name'\n      const value = 'value'\n\n      ipfs.name.resolve.withArgs(name, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns([\n        value\n      ])\n\n      const out = await cli(`name resolve ${name} --timeout=1s`, { ipfs })\n      expect(out).to.equal(`${value}\\n`)\n    })\n  })\n\n  describe('publish', () => {\n    const name = 'name'\n    const value = 'value'\n    const defaultOptions = {\n      resolve: true,\n      lifetime: '24h',\n      key: 'self',\n      ttl: '',\n      timeout: undefined\n    }\n\n    it('should publish a name', async () => {\n      ipfs.name.publish.withArgs(name, defaultOptions).resolves({\n        name,\n        value\n      })\n\n      const out = await cli(`name publish ${name}`, { ipfs })\n      expect(out).to.equal(`Published to ${name}: ${value}\\n`)\n    })\n\n    it('publish with resolve', async () => {\n      ipfs.name.publish.withArgs(name, {\n        ...defaultOptions,\n        resolve: false\n      }).resolves({\n        name,\n        value\n      })\n\n      const out = await cli(`name publish --resolve=false ${name}`, { ipfs })\n      expect(out).to.equal(`Published to ${name}: ${value}\\n`)\n    })\n\n    it('publish with resolve (short option)', async () => {\n      ipfs.name.publish.withArgs(name, {\n        ...defaultOptions,\n        resolve: false\n      }).resolves({\n        name,\n        value\n      })\n\n      const out = await cli(`name publish -r false ${name}`, { ipfs })\n      expect(out).to.equal(`Published to ${name}: ${value}\\n`)\n    })\n\n    it('publish with lifetime', async () => {\n      ipfs.name.publish.withArgs(name, {\n        ...defaultOptions,\n        lifetime: '1h'\n      }).resolves({\n        name,\n        value\n      })\n\n      const out = await cli(`name publish --lifetime=1h ${name}`, { ipfs })\n      expect(out).to.equal(`Published to ${name}: ${value}\\n`)\n    })\n\n    it('publish with lifetime (short option)', async () => {\n      ipfs.name.publish.withArgs(name, {\n        ...defaultOptions,\n        lifetime: '1h'\n      }).resolves({\n        name,\n        value\n      })\n\n      const out = await cli(`name publish -t 1h ${name}`, { ipfs })\n      expect(out).to.equal(`Published to ${name}: ${value}\\n`)\n    })\n\n    it('publish with key', async () => {\n      ipfs.name.publish.withArgs(name, {\n        ...defaultOptions,\n        key: 'derp'\n      }).resolves({\n        name,\n        value\n      })\n\n      const out = await cli(`name publish --key=derp ${name}`, { ipfs })\n      expect(out).to.equal(`Published to ${name}: ${value}\\n`)\n    })\n\n    it('publish with key (short option)', async () => {\n      ipfs.name.publish.withArgs(name, {\n        ...defaultOptions,\n        key: 'derp'\n      }).resolves({\n        name,\n        value\n      })\n\n      const out = await cli(`name publish -k derp ${name}`, { ipfs })\n      expect(out).to.equal(`Published to ${name}: ${value}\\n`)\n    })\n\n    it('publish with ttl', async () => {\n      ipfs.name.publish.withArgs(name, {\n        ...defaultOptions,\n        ttl: 'derp'\n      }).resolves({\n        name,\n        value\n      })\n\n      const out = await cli(`name publish --ttl=derp ${name}`, { ipfs })\n      expect(out).to.equal(`Published to ${name}: ${value}\\n`)\n    })\n\n    it('should publish a name with a timeout', async () => {\n      ipfs.name.publish.withArgs(name, {\n        ...defaultOptions,\n        timeout: 1000\n      }).resolves({\n        name,\n        value\n      })\n\n      const out = await cli(`name publish ${name} --timeout=1s`, { ipfs })\n      expect(out).to.equal(`Published to ${name}: ${value}\\n`)\n    })\n\n    it('should strip control characters when publishing names', async () => {\n      const name = 'name'\n      const junkName = `${name}\\b`\n      const value = 'data'\n\n      ipfs.name.publish.withArgs(junkName, defaultOptions)\n        .resolves({\n          name: junkName,\n          value\n        })\n\n      const out = await cli(`name publish ${junkName}`, { ipfs })\n      expect(out).to.equal(`Published to ${name}: ${value}\\n`)\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/object.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport fs from 'fs'\nimport { cli } from './utils/cli.js'\nimport sinon from 'sinon'\nimport { CID } from 'multiformats/cid'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { base58btc } from 'multiformats/bases/base58'\nimport { base64 } from 'multiformats/bases/base64'\nimport * as dagPB from '@ipld/dag-pb'\n\ndescribe('object', () => {\n  const cid = CID.parse('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n')\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      object: {\n        new: sinon.stub(),\n        get: sinon.stub(),\n        put: sinon.stub(),\n        stat: sinon.stub(),\n        data: sinon.stub(),\n        links: sinon.stub(),\n        patch: {\n          appendData: sinon.stub(),\n          addLink: sinon.stub(),\n          setData: sinon.stub(),\n          rmLink: sinon.stub()\n        }\n      },\n      bases: {\n        getBase: sinon.stub()\n      }\n    }\n  })\n\n  describe('new', () => {\n    const defaultOptions = {\n      template: undefined,\n      timeout: undefined\n    }\n\n    it('should create a new object', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.object.new.withArgs(defaultOptions).resolves(cid)\n\n      const out = await cli('object new', { ipfs })\n      expect(out).to.equal(`${cid}\\n`)\n    })\n\n    it('new unixfs-dir', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.object.new.withArgs({\n        ...defaultOptions,\n        template: 'unixfs-dir'\n      }).resolves(cid)\n\n      const out = await cli('object new unixfs-dir', { ipfs })\n      expect(out).to.equal(`${cid}\\n`)\n    })\n\n    it('new with a timeout', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.object.new.withArgs({\n        ...defaultOptions,\n        timeout: 1000\n      }).resolves(cid)\n\n      const out = await cli('object new --timeout=1s', { ipfs })\n      expect(out).to.equal(`${cid}\\n`)\n    })\n\n    it('should new and print CID encoded in specified base', async () => {\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n      ipfs.object.new.withArgs(defaultOptions).resolves(cid.toV1())\n\n      const out = await cli('object new --cid-base=base64', { ipfs })\n      expect(out).to.equal(\n        `${cid.toV1().toString(base64)}\\n`\n      )\n    })\n  })\n\n  describe('get', () => {\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('should get an object', async () => {\n      const node = {\n        Links: []\n      }\n\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.object.get.withArgs(cid, defaultOptions).resolves(node)\n\n      const out = await cli(`object get ${cid}`, { ipfs })\n      const result = JSON.parse(out)\n      expect(result.Links).to.deep.equal([])\n      expect(result.Data).to.equal('')\n    })\n\n    it('should get an object and strip control characters from link names', async () => {\n      const node = {\n        Links: [{\n          Name: 'derp\\n\\b',\n          Tsize: 10,\n          Hash: cid\n        }]\n      }\n\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.object.get.withArgs(cid, defaultOptions).resolves(node)\n\n      const out = await cli(`object get ${cid}`, { ipfs })\n      const result = JSON.parse(out)\n      expect(result.Links).to.deep.equal([{\n        Name: 'derp',\n        Size: 10,\n        Hash: cid.toString()\n      }])\n      expect(result.Data).to.equal('')\n    })\n\n    it('get with data', async () => {\n      const node = {\n        Data: uint8ArrayFromString('aGVsbG8gd29ybGQK', 'base64'),\n        Links: []\n      }\n\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.object.get.withArgs(cid, defaultOptions).resolves(node)\n\n      const out = await cli(`object get ${cid}`, { ipfs })\n      const result = JSON.parse(out)\n      expect(result.Links).to.deep.equal([])\n      expect(result.Data).to.equal('aGVsbG8gd29ybGQK')\n    })\n\n    it('get while overriding data-encoding', async () => {\n      const node = {\n        Data: uint8ArrayFromString('hello world'),\n        Links: []\n      }\n\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.object.get.withArgs(cid, defaultOptions).resolves(node)\n\n      const out = await cli(`object get --data-encoding=utf8 ${cid}`, { ipfs })\n      const result = JSON.parse(out)\n      expect(result.Links).to.deep.equal([])\n      expect(result.Data).to.equal('hello world')\n    })\n\n    it('should get and print CIDs encoded in specified base', async () => {\n      const node = {\n        Links: [{\n          Name: '',\n          Tsize: 0,\n          Hash: cid.toV1()\n        }]\n      }\n\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n      ipfs.object.get.withArgs(cid.toV1(), defaultOptions).resolves(node)\n\n      const out = await cli(`object get --cid-base=base64 ${cid.toV1()}`, { ipfs })\n      const result = JSON.parse(out)\n\n      expect(result.Hash).to.equal(cid.toV1().toString(base64))\n      result.Links.forEach(l => {\n        expect(l.Hash).to.equal(cid.toV1().toString(base64))\n      })\n    })\n\n    it('should get an object with a timeout', async () => {\n      const node = {\n        Links: []\n      }\n\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.object.get.withArgs(cid, {\n        ...defaultOptions,\n        timeout: 1000\n      }).resolves(node)\n\n      const out = await cli(`object get ${cid} --timeout=1s`, { ipfs })\n      const result = JSON.parse(out)\n      expect(result.Links).to.deep.equal([])\n      expect(result.Data).to.equal('')\n    })\n  })\n\n  describe('put', () => {\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('should put an object', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.object.put.withArgs({}, defaultOptions).resolves(cid)\n\n      const out = await cli('object put {}', { ipfs })\n\n      expect(out).to.equal(\n        `added ${cid}\\n`\n      )\n    })\n\n    it('put from pipe', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.object.put.withArgs({}, defaultOptions).resolves(cid)\n\n      const out = await cli('object put', {\n        ipfs,\n        getStdin: function * () {\n          yield uint8ArrayFromString('{}')\n        }\n      })\n\n      expect(out).to.equal(\n        `added ${cid}\\n`\n      )\n    })\n\n    it('put protobuf from pipe', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.object.put.withArgs({ Links: [] }, defaultOptions).resolves(cid)\n\n      const out = await cli('object put --input-enc protobuf', {\n        ipfs,\n        getStdin: function * () {\n          yield dagPB.encode({ Links: [] })\n        }\n      })\n\n      expect(out).to.equal(\n        `added ${cid}\\n`\n      )\n    })\n\n    it('should put and print CID encoded in specified base', async () => {\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n      ipfs.object.put.withArgs({}, defaultOptions).resolves(cid.toV1())\n\n      const out = await cli('object put {} --cid-base=base64', { ipfs })\n\n      expect(out).to.equal(\n        `added ${cid.toV1().toString(base64)}\\n`\n      )\n    })\n\n    it('should put an object with a timeout', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.object.put.withArgs({}, {\n        ...defaultOptions,\n        timeout: 1000\n      }).resolves(cid)\n\n      const out = await cli('object put {} --timeout=1s', { ipfs })\n\n      expect(out).to.equal(\n        `added ${cid}\\n`\n      )\n    })\n  })\n\n  describe('stat', () => {\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('should stat an object', async () => {\n      ipfs.object.stat.withArgs(cid, defaultOptions).resolves({\n        Hash: cid,\n        NumLinks: 1,\n        BlockSize: 60,\n        LinksSize: 53,\n        DataSize: 7,\n        CumulativeSize: 68\n      })\n\n      const out = await cli(`object stat ${cid}`, { ipfs })\n      expect(out).to.deep.equal([\n        'NumLinks: 1',\n        'BlockSize: 60',\n        'LinksSize: 53',\n        'DataSize: 7',\n        'CumulativeSize: 68'\n      ].join('\\n') + '\\n')\n    })\n\n    it('should stat an object with a timeout', async () => {\n      ipfs.object.stat.withArgs(cid, {\n        ...defaultOptions,\n        timeout: 1000\n      }).resolves({\n        Hash: cid,\n        NumLinks: 1,\n        BlockSize: 60,\n        LinksSize: 53,\n        DataSize: 7,\n        CumulativeSize: 68\n      })\n\n      const out = await cli(`object stat ${cid} --timeout=1s`, { ipfs })\n      expect(out).to.deep.equal([\n        'NumLinks: 1',\n        'BlockSize: 60',\n        'LinksSize: 53',\n        'DataSize: 7',\n        'CumulativeSize: 68'\n      ].join('\\n') + '\\n')\n    })\n  })\n\n  describe('data', () => {\n    const data = 'another'\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('should return data from an object', async () => {\n      ipfs.object.data.withArgs(cid, defaultOptions).resolves(data)\n\n      const out = await cli(`object data ${cid}`, { ipfs })\n      expect(out).to.equal(data)\n    })\n\n    it('should return data from an object with a timeout', async () => {\n      ipfs.object.data.withArgs(cid, {\n        ...defaultOptions,\n        timeout: 1000\n      }).resolves(data)\n\n      const out = await cli(`object data ${cid} --timeout=1s`, { ipfs })\n      expect(out).to.equal(data)\n    })\n  })\n\n  describe('links', () => {\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('should return links from an object', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.object.links.withArgs(cid, defaultOptions).resolves([{\n        Name: 'some link',\n        Tsize: 8,\n        Hash: CID.parse('QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V')\n      }])\n\n      const out = await cli(`object links ${cid}`, { ipfs })\n      expect(out).to.equal(\n        'QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V 8 some link\\n'\n      )\n    })\n\n    it('should get links and print CIDs encoded in specified base', async () => {\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n      const cid = CID.parse('QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm').toV1()\n      const linkCid = CID.parse('QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V').toV1()\n\n      ipfs.object.links.withArgs(cid, defaultOptions).resolves([{\n        Name: 'some link',\n        Tsize: 8,\n        Hash: linkCid\n      }])\n\n      const out = await cli(`object links ${cid} --cid-base=base64`, { ipfs })\n\n      out.trim().split('\\n').forEach(line => {\n        const cid = line.split(' ')[0]\n        expect(cid).to.equal(linkCid.toString(base64))\n      })\n    })\n\n    it('should return links from an object with a timeout', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.object.links.withArgs(cid, {\n        ...defaultOptions,\n        timeout: 1000\n      }).resolves([{\n        Name: 'some link',\n        Tsize: 8,\n        Hash: CID.parse('QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V')\n      }])\n\n      const out = await cli(`object links ${cid} --timeout=1s`, { ipfs })\n      expect(out).to.equal(\n        'QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V 8 some link\\n'\n      )\n    })\n\n    it('should get an object and strip control characters from link names', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.object.links.withArgs(cid, defaultOptions).resolves([{\n        Name: 'derp\\t\\n\\b',\n        Tsize: 8,\n        Hash: CID.parse('QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V')\n      }])\n\n      const out = await cli(`object links ${cid}`, { ipfs })\n      expect(out).to.equal(\n        'QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V 8 derp\\n'\n      )\n    })\n  })\n\n  describe('patch', () => {\n    describe('append-data', () => {\n      const defaultOptions = {\n        timeout: undefined\n      }\n\n      it('should append data', async () => {\n        ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n        const filePath = 'README.md'\n        const buf = fs.readFileSync(filePath)\n\n        ipfs.object.patch.appendData.withArgs(cid, buf, defaultOptions).resolves(\n          cid\n        )\n\n        const out = await cli(`object patch append-data ${cid} ${filePath}`, { ipfs })\n        expect(out).to.equal(`${cid}\\n`)\n      })\n\n      it('append data from pipe', async () => {\n        ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n        const buf = uint8ArrayFromString('hello world')\n\n        ipfs.object.patch.appendData.withArgs(cid, buf, defaultOptions).resolves(\n          cid\n        )\n\n        const out = await cli(`object patch append-data ${cid}`, {\n          ipfs,\n          getStdin: function * () {\n            yield buf\n          }\n        })\n        expect(out).to.equal(`${cid}\\n`)\n      })\n\n      it('should append data and print CID encoded in specified base', async () => {\n        ipfs.bases.getBase.withArgs('base64').returns(base64)\n        const filePath = 'README.md'\n        const buf = fs.readFileSync(filePath)\n\n        ipfs.object.patch.appendData.withArgs(cid, buf, defaultOptions).resolves(\n          cid.toV1()\n        )\n\n        const out = await cli(`object patch append-data ${cid} ${filePath} --cid-base=base64`, { ipfs })\n        expect(out).to.equal(`${cid.toV1().toString(base64)}\\n`)\n      })\n\n      it('should append data with a timeout', async () => {\n        ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n        const filePath = 'README.md'\n        const buf = fs.readFileSync(filePath)\n\n        ipfs.object.patch.appendData.withArgs(cid, buf, {\n          ...defaultOptions,\n          timeout: 1000\n        }).resolves(\n          cid\n        )\n\n        const out = await cli(`object patch append-data ${cid} ${filePath} --timeout=1s`, { ipfs })\n        expect(out).to.equal(`${cid}\\n`)\n      })\n    })\n\n    describe('set-data', () => {\n      const defaultOptions = {\n        timeout: undefined\n      }\n\n      it('should set data on an object', async () => {\n        ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n        const filePath = 'README.md'\n        const buf = fs.readFileSync(filePath)\n\n        ipfs.object.patch.setData.withArgs(cid, buf, defaultOptions).resolves(\n          cid\n        )\n\n        const out = await cli(`object patch set-data ${cid} ${filePath}`, { ipfs })\n        expect(out).to.equal(`${cid}\\n`)\n      })\n\n      it('set-data from pipe', async () => {\n        ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n        const buf = uint8ArrayFromString('hello world')\n\n        ipfs.object.patch.setData.withArgs(cid, buf, defaultOptions).resolves(\n          cid\n        )\n\n        const out = await cli(`object patch set-data ${cid}`, {\n          ipfs,\n          getStdin: function * () {\n            yield buf\n          }\n        })\n        expect(out).to.equal(`${cid}\\n`)\n      })\n\n      it('should set-data and print CID encoded in specified base', async () => {\n        ipfs.bases.getBase.withArgs('base64').returns(base64)\n        const filePath = 'README.md'\n        const buf = fs.readFileSync(filePath)\n\n        ipfs.object.patch.setData.withArgs(cid.toV1(), buf, defaultOptions).resolves(\n          cid.toV1()\n        )\n\n        const out = await cli(`object patch set-data ${cid.toV1()} ${filePath} --cid-base=base64`, { ipfs })\n        expect(out).to.equal(`${cid.toV1().toString(base64)}\\n`)\n      })\n\n      it('should set data on an object with a timeout', async () => {\n        ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n        const filePath = 'README.md'\n        const buf = fs.readFileSync(filePath)\n\n        ipfs.object.patch.setData.withArgs(cid, buf, {\n          ...defaultOptions,\n          timeout: 1000\n        }).resolves(\n          cid\n        )\n\n        const out = await cli(`object patch set-data ${cid} ${filePath} --timeout=1s`, { ipfs })\n        expect(out).to.equal(`${cid}\\n`)\n      })\n    })\n\n    describe('add-link', () => {\n      const defaultOptions = {\n        timeout: undefined\n      }\n\n      it('should add a link to an object', async () => {\n        ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n        const linkCid = CID.parse('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n')\n        const updatedCid = CID.parse('QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm')\n\n        ipfs.object.get.withArgs(linkCid, defaultOptions).resolves({\n          Links: []\n        })\n        ipfs.object.patch.addLink.withArgs(cid, {\n          Name: 'foo',\n          Tsize: 0,\n          Hash: linkCid\n        }, defaultOptions).resolves(\n          updatedCid\n        )\n\n        const out = await cli(`object patch add-link ${cid} foo ${linkCid}`, { ipfs })\n        expect(out).to.equal(\n          `${updatedCid}\\n`\n        )\n      })\n\n      it('should add-link and print CID encoded in specified base', async () => {\n        ipfs.bases.getBase.withArgs('base64').returns(base64)\n        const linkCid = CID.parse('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n').toV1()\n        const updatedCid = CID.parse('QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm').toV1()\n\n        ipfs.object.get.withArgs(linkCid, defaultOptions).resolves({\n          Links: []\n        })\n        ipfs.object.patch.addLink.withArgs(cid.toV1(), {\n          Name: 'foo',\n          Tsize: 0,\n          Hash: linkCid\n        }, defaultOptions).resolves(\n          updatedCid\n        )\n\n        const out = await cli(`object patch add-link ${cid.toV1()} foo ${linkCid} --cid-base=base64`, { ipfs })\n        expect(out).to.equal(\n          `${updatedCid.toString(base64)}\\n`\n        )\n      })\n\n      it('should add a link to an object with a timeout', async () => {\n        ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n        const linkCid = CID.parse('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n')\n        const updatedCid = CID.parse('QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm')\n\n        ipfs.object.get.withArgs(linkCid, {\n          ...defaultOptions,\n          timeout: 1000\n        }).resolves({\n          Links: []\n        })\n        ipfs.object.patch.addLink.withArgs(cid, {\n          Name: 'foo',\n          Tsize: 0,\n          Hash: linkCid\n        }, {\n          ...defaultOptions,\n          timeout: 1000\n        }).resolves(\n          updatedCid\n        )\n\n        const out = await cli(`object patch add-link ${cid} foo ${linkCid} --timeout=1s`, { ipfs })\n        expect(out).to.equal(\n          `${updatedCid}\\n`\n        )\n      })\n    })\n\n    describe('rm-link', () => {\n      const defaultOptions = {\n        timeout: undefined\n      }\n\n      it('should remove a link from an object', async () => {\n        ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n        const cid = CID.parse('QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm')\n        const updatedCid = CID.parse('QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm')\n        const linkName = 'foo'\n\n        ipfs.object.patch.rmLink.withArgs(cid, linkName, defaultOptions).resolves(\n          updatedCid\n        )\n\n        const out = await cli(`object patch rm-link ${cid} ${linkName}`, { ipfs })\n        expect(out).to.equal(\n          `${updatedCid}\\n`\n        )\n      })\n\n      it('should rm-link and print CID encoded in specified base', async () => {\n        ipfs.bases.getBase.withArgs('base64').returns(base64)\n        const cid = CID.parse('QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm').toV1()\n        const updatedCid = CID.parse('QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm').toV1()\n        const linkName = 'foo'\n\n        ipfs.object.patch.rmLink.withArgs(cid, linkName, defaultOptions).resolves(\n          updatedCid\n        )\n\n        const out = await cli(`object patch rm-link ${cid} ${linkName} --cid-base=base64`, { ipfs })\n        expect(out).to.equal(\n          `${updatedCid.toString(base64)}\\n`\n        )\n      })\n\n      it('should remove a link from an object with a timeout', async () => {\n        ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n        const cid = CID.parse('QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm')\n        const updatedCid = CID.parse('QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm')\n        const linkName = 'foo'\n\n        ipfs.object.patch.rmLink.withArgs(cid, linkName, {\n          ...defaultOptions,\n          timeout: 1000\n        }).resolves(\n          updatedCid\n        )\n\n        const out = await cli(`object patch rm-link ${cid} ${linkName} --timeout=1s`, { ipfs })\n        expect(out).to.equal(\n          `${updatedCid}\\n`\n        )\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/pin.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { CID } from 'multiformats/cid'\nimport { base58btc } from 'multiformats/bases/base58'\nimport { base64 } from 'multiformats/bases/base64'\nimport { cli } from './utils/cli.js'\nimport sinon from 'sinon'\n\nconst pins = {\n  root: CID.parse('QmTAMavb995EHErSrKo7mB8dYkpaSJxu6ys1a6XJyB2sys'),\n  solarWiki: CID.parse('QmTMbkDfvHwq3Aup6Nxqn3KKw9YnoKzcZvuArAfQ9GF3QG'),\n  mercuryDir: CID.parse('QmbJCNKXJqVK8CzbjpNFz2YekHwh3CSHpBA86uqYg3sJ8q'),\n  mercuryWiki: CID.parse('QmVgSHAdMxFAuMP2JiMAYkB8pCWP1tcB9djqvq8GKAFiHi')\n}\n\ndescribe('pin', () => {\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      pin: {\n        rmAll: sinon.stub(),\n        addAll: sinon.stub(),\n        ls: sinon.stub(),\n        query: sinon.stub()\n      },\n      bases: {\n        getBase: sinon.stub()\n      }\n    }\n  })\n\n  describe('rm', function () {\n    const defaultPinOptions = {\n      recursive: true\n    }\n\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('recursively (default)', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.pin.rmAll.withArgs([{\n        ...defaultPinOptions,\n        path: pins.root.toString()\n      }], defaultOptions).returns([\n        pins.root\n      ])\n\n      const out = await cli(`pin rm ${pins.root}`, { ipfs })\n      expect(out).to.equal(`unpinned ${pins.root}\\n`)\n    })\n\n    it('non recursively', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.pin.rmAll.withArgs([{\n        ...defaultPinOptions,\n        path: pins.root.toString(),\n        recursive: false\n      }], defaultOptions).returns([\n        pins.root\n      ])\n\n      const out = await cli(`pin rm --recursive false ${pins.root}`, { ipfs })\n      expect(out).to.equal(`unpinned ${pins.root}\\n`)\n    })\n\n    it('non recursively (short option)', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.pin.rmAll.withArgs([{\n        ...defaultPinOptions,\n        path: pins.root.toString(),\n        recursive: false\n      }], defaultOptions).returns([\n        pins.root\n      ])\n\n      const out = await cli(`pin rm -r false ${pins.root}`, { ipfs })\n      expect(out).to.equal(`unpinned ${pins.root}\\n`)\n    })\n\n    it('should rm and print CIDs encoded in specified base', async () => {\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n      ipfs.pin.rmAll.withArgs([{\n        ...defaultPinOptions,\n        path: pins.root.toString()\n      }], defaultOptions).returns([\n        pins.root.toV1()\n      ])\n\n      const out = await cli(`pin rm ${pins.root} --cid-base=base64`, { ipfs })\n      const b64CidStr = pins.root.toV1().toString(base64)\n      expect(out).to.eql(`unpinned ${b64CidStr}\\n`)\n    })\n\n    it('with timeout', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.pin.rmAll.withArgs([{\n        ...defaultPinOptions,\n        path: pins.root.toString()\n      }], {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns([\n        pins.root\n      ])\n\n      const out = await cli(`pin rm ${pins.root} --timeout=1s`, { ipfs })\n      expect(out).to.equal(`unpinned ${pins.root}\\n`)\n    })\n  })\n\n  describe('add', function () {\n    const defaultPinOptions = {\n      recursive: true,\n      metadata: undefined\n    }\n\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('recursively (default)', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.pin.addAll.withArgs([{\n        ...defaultPinOptions,\n        path: pins.root.toString()\n      }], defaultOptions).returns([\n        pins.root\n      ])\n\n      const out = await cli(`pin add ${pins.root}`, { ipfs })\n      expect(out).to.equal(`pinned ${pins.root} recursively\\n`)\n    })\n\n    it('non recursively', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.pin.addAll.withArgs([{\n        ...defaultPinOptions,\n        path: pins.root.toString(),\n        recursive: false\n      }], defaultOptions).returns([\n        pins.root\n      ])\n\n      const out = await cli(`pin add --recursive false ${pins.root}`, { ipfs })\n      expect(out).to.equal(`pinned ${pins.root} directly\\n`)\n    })\n\n    it('non recursively (short option)', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.pin.addAll.withArgs([{\n        ...defaultPinOptions,\n        path: pins.root.toString(),\n        recursive: false\n      }], defaultOptions).returns([\n        pins.root\n      ])\n\n      const out = await cli(`pin add -r false ${pins.root}`, { ipfs })\n      expect(out).to.equal(`pinned ${pins.root} directly\\n`)\n    })\n\n    it('with metadata', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.pin.addAll.withArgs([{\n        ...defaultPinOptions,\n        path: pins.root.toString(),\n        metadata: {\n          key: 'value'\n        }\n      }], defaultOptions).returns([\n        pins.root\n      ])\n\n      const out = await cli(`pin add --metadata key=value ${pins.root}`, { ipfs })\n      expect(out).to.equal(`pinned ${pins.root} recursively\\n`)\n    })\n\n    it('with a metadata (short option)', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.pin.addAll.withArgs([{\n        ...defaultPinOptions,\n        path: pins.root.toString(),\n        metadata: {\n          key: 'value'\n        }\n      }], defaultOptions).returns([\n        pins.root\n      ])\n\n      const out = await cli(`pin add -m key=value ${pins.root}`, { ipfs })\n      expect(out).to.equal(`pinned ${pins.root} recursively\\n`)\n    })\n\n    it('with json metadata', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.pin.addAll.withArgs([{\n        ...defaultPinOptions,\n        path: pins.root.toString(),\n        metadata: {\n          key: 'value'\n        }\n      }], defaultOptions).returns([\n        pins.root\n      ])\n\n      const out = await cli(`pin add --metadata-json '{\"key\":\"value\"}' ${pins.root}`, { ipfs })\n      expect(out).to.equal(`pinned ${pins.root} recursively\\n`)\n    })\n\n    it('should add and print CIDs encoded in specified base', async () => {\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n      ipfs.pin.addAll.withArgs([{\n        ...defaultPinOptions,\n        path: pins.root.toString()\n      }], defaultOptions).returns([\n        pins.root.toV1()\n      ])\n\n      const out = await cli(`pin add ${pins.root} --cid-base=base64`, { ipfs })\n      const b64CidStr = pins.root.toV1().toString(base64)\n      expect(out).to.eql(`pinned ${b64CidStr} recursively\\n`)\n    })\n\n    it('recursively with timeout', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.pin.addAll.withArgs([{\n        ...defaultPinOptions,\n        path: pins.root.toString()\n      }], {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns([\n        pins.root\n      ])\n\n      const out = await cli(`pin add ${pins.root} --timeout=1s`, { ipfs })\n      expect(out).to.equal(`pinned ${pins.root} recursively\\n`)\n    })\n  })\n\n  describe('ls', function () {\n    const defaultOptions = {\n      type: 'all',\n      timeout: undefined,\n      paths: undefined\n    }\n\n    it('lists all pins when no hash is passed', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.pin.ls.withArgs(defaultOptions).returns([{\n        cid: pins.root,\n        type: 'recursive'\n      }])\n\n      const out = await cli('pin ls', { ipfs })\n      expect(out).to.equal(`${pins.root} recursive\\n`)\n    })\n\n    it('handles multiple hashes', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.pin.ls.withArgs({\n        ...defaultOptions,\n        paths: [pins.root.toString(), pins.solarWiki.toString()]\n      }).returns([{\n        cid: pins.root,\n        type: 'recursive'\n      }, {\n        cid: pins.solarWiki,\n        type: 'direct'\n      }])\n\n      const out = await cli(`pin ls ${pins.root} ${pins.solarWiki}`, { ipfs })\n      expect(out).to.equal(`${pins.root} recursive\\n${pins.solarWiki} direct\\n`)\n    })\n\n    it('can print quietly', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.pin.ls.withArgs(defaultOptions).returns([{\n        cid: pins.root.toString(),\n        type: 'recursive'\n      }])\n\n      const out = await cli('pin ls --quiet', { ipfs })\n      expect(out).to.equal(`${pins.root}\\n`)\n    })\n\n    it('can print quietly (short option)', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.pin.ls.withArgs(defaultOptions).returns([{\n        cid: pins.root.toString(),\n        type: 'recursive'\n      }])\n\n      const out = await cli('pin ls -q', { ipfs })\n      expect(out).to.equal(`${pins.root}\\n`)\n    })\n\n    it('should ls and print CIDs encoded in specified base', async () => {\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n      ipfs.pin.ls.withArgs(defaultOptions).returns([{\n        cid: pins.root.toV1(),\n        type: 'recursive'\n      }])\n\n      const out = await cli('pin ls --cid-base=base64', { ipfs })\n      expect(out).to.equal(`${pins.root.toV1().toString(base64)} recursive\\n`)\n    })\n\n    it('lists all pins with a timeout', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.pin.ls.withArgs({\n        ...defaultOptions,\n        timeout: 1000\n      }).returns([{\n        cid: pins.root,\n        type: 'recursive'\n      }])\n\n      const out = await cli('pin ls --timeout=1s', { ipfs })\n      expect(out).to.equal(`${pins.root} recursive\\n`)\n    })\n\n    it('strips control characters from metadata', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.pin.ls.withArgs(defaultOptions).returns([{\n        cid: pins.root,\n        type: 'recursive',\n        metadata: {\n          'herp\\n\\t': 'de\\brp'\n        }\n      }])\n\n      const out = await cli('pin ls', { ipfs })\n      expect(out).to.equal(`${pins.root} recursive {\"herp\":\"derp\"}\\n`)\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/ping.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { cli } from './utils/cli.js'\nimport sinon from 'sinon'\nimport { peerIdFromString } from '@libp2p/peer-id'\nimport { matchPeerId } from './utils/match-peer-id.js'\n\nconst defaultOptions = {\n  count: 10,\n  timeout: undefined\n}\n\ndescribe('ping', function () {\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      ping: sinon.stub()\n    }\n  })\n\n  it('ping host', async () => {\n    const peerId = peerIdFromString('QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kp')\n    const time = 10\n\n    // https://github.com/libp2p/js-peer-id/issues/141\n    ipfs.ping.withArgs(matchPeerId(peerId), defaultOptions).returns([{\n      success: true,\n      time\n    }])\n\n    const out = await cli(`ping ${peerId}`, { ipfs })\n    expect(out).to.equal(`Pong received: time=${time} ms\\n`)\n  })\n\n  it('ping host with -n option', async () => {\n    const peerId = peerIdFromString('QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kp')\n    const time = 10\n\n    // https://github.com/libp2p/js-peer-id/issues/141\n    ipfs.ping.withArgs(matchPeerId(peerId), {\n      ...defaultOptions,\n      count: 1\n    }).returns([{\n      success: true,\n      time\n    }])\n\n    const out = await cli(`ping -n 1 ${peerId}`, { ipfs })\n    expect(out).to.equal(`Pong received: time=${time} ms\\n`)\n  })\n\n  it('ping host with --count option', async () => {\n    const peerId = peerIdFromString('QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kp')\n    const time = 10\n\n    // https://github.com/libp2p/js-peer-id/issues/141\n    ipfs.ping.withArgs(matchPeerId(peerId), {\n      ...defaultOptions,\n      count: 1\n    }).returns([{\n      success: true,\n      time\n    }])\n\n    const out = await cli(`ping --count 1 ${peerId}`, { ipfs })\n    expect(out).to.equal(`Pong received: time=${time} ms\\n`)\n  })\n\n  it('ping host with timeout', async () => {\n    const peerId = peerIdFromString('QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kp')\n    const time = 10\n\n    // https://github.com/libp2p/js-peer-id/issues/141\n    ipfs.ping.withArgs(matchPeerId(peerId), {\n      ...defaultOptions,\n      timeout: 1000\n    }).returns([{\n      success: true,\n      time\n    }])\n\n    const out = await cli(`ping --timeout=1s ${peerId}`, { ipfs })\n    expect(out).to.equal(`Pong received: time=${time} ms\\n`)\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/progress-bar.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { createProgressBar } from '../src/utils.js'\n\ndescribe('progress bar', () => {\n  it('created with the correct properties', () => {\n    const total = 1000\n\n    const bar = createProgressBar(total)\n    expect(bar.total).to.eql(total)\n    expect(typeof bar.tick).to.eql('function')\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/pubsub.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { cli } from './utils/cli.js'\nimport sinon from 'sinon'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\n\ndescribe('pubsub', () => {\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      pubsub: {\n        ls: sinon.stub(),\n        peers: sinon.stub(),\n        publish: sinon.stub(),\n        subscribe: sinon.stub()\n      }\n    }\n  })\n\n  describe('ls', () => {\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('should list subscriptions', async () => {\n      const subName = 'sub-name'\n\n      ipfs.pubsub.ls.withArgs(defaultOptions).resolves([\n        subName\n      ])\n\n      const out = await cli('pubsub ls', { ipfs })\n      expect(out).to.equal(`${subName}\\n`)\n    })\n\n    it('should list subscriptions with timeout', async () => {\n      const subName = 'sub-name'\n\n      ipfs.pubsub.ls.withArgs({\n        ...defaultOptions,\n        timeout: 1000\n      }).resolves([\n        subName\n      ])\n\n      const out = await cli('pubsub ls --timeout=1s', { ipfs })\n      expect(out).to.equal(`${subName}\\n`)\n    })\n\n    it('should strip control characters from sub names', async () => {\n      const subName = 'sub-name'\n      const junkSubName = 'sub-nam\\b\\te\\n'\n\n      ipfs.pubsub.ls.withArgs(defaultOptions).resolves([\n        junkSubName\n      ])\n\n      const out = await cli('pubsub ls', { ipfs })\n      expect(out).to.equal(`${subName}\\n`)\n    })\n  })\n\n  describe('peers', () => {\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('should list topic peers', async () => {\n      const subName = 'sub-name'\n      const peer = 'peer-id'\n\n      ipfs.pubsub.peers.withArgs(subName, defaultOptions).resolves([\n        peer\n      ])\n\n      const out = await cli(`pubsub peers ${subName}`, { ipfs })\n      expect(out).to.equal(`${peer}\\n`)\n    })\n\n    it('should list topic peers with a timeout', async () => {\n      const subName = 'sub-name'\n      const peer = 'peer-id'\n\n      ipfs.pubsub.peers.withArgs(subName, {\n        ...defaultOptions,\n        timeout: 1000\n      }).resolves([\n        peer\n      ])\n\n      const out = await cli(`pubsub peers ${subName} --timeout=1s`, { ipfs })\n      expect(out).to.equal(`${peer}\\n`)\n    })\n  })\n\n  describe('pub', () => {\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('should publish message', async () => {\n      const subName = 'sub-name-1'\n      const data = 'data\\r\\nfirst\\nZażółć gęślą jaźń😇'\n\n      await cli(`pubsub pub ${subName} \"${data}\"`, { ipfs })\n\n      expect(ipfs.pubsub.publish.calledWith(subName, uint8ArrayFromString(data), defaultOptions)).to.be.true()\n    })\n\n    it('should publish message with timeout', async () => {\n      const subName = 'sub-name-2'\n      const data = 'data\\r\\nsecond\\nZażółć gęślą jaźń😇'\n\n      await cli(`pubsub pub ${subName} \"${data}\" --timeout=1s`, { ipfs })\n\n      expect(ipfs.pubsub.publish.calledWith(subName, uint8ArrayFromString(data), {\n        ...defaultOptions,\n        timeout: 1000\n      })).to.be.true()\n    })\n  })\n\n  describe('sub', () => {\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('should subscribe', async () => {\n      const subName = 'sub\\nname'\n\n      await cli(`pubsub sub \"${subName}\"`, { ipfs })\n\n      expect(ipfs.pubsub.subscribe.calledWith(subName, sinon.match.func, defaultOptions)).to.be.true()\n    })\n\n    it('should subscribe with a timeout', async () => {\n      const subName = 'sub-name'\n\n      await cli(`pubsub sub \"${subName}\" --timeout=1s`, { ipfs })\n\n      expect(ipfs.pubsub.subscribe.calledWith(subName, sinon.match.func, {\n        ...defaultOptions,\n        timeout: 1000\n      })).to.be.true()\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/refs-local.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { cli } from './utils/cli.js'\nimport sinon from 'sinon'\nimport { base32 } from 'multiformats/bases/base32'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\n\nconst defaultOptions = {\n  timeout: undefined\n}\n\ndescribe('refs local', () => {\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      refs: {\n        local: sinon.stub()\n      }\n    }\n  })\n\n  it('prints CID of all blocks', async () => {\n    const ref = 'ref'\n    const err = 'err'\n\n    ipfs.refs.local.withArgs(defaultOptions).returns([{\n      ref\n    }, {\n      err\n    }])\n\n    const out = await cli('refs local', { ipfs })\n    const lines = out.split('\\n')\n\n    expect(lines.includes(ref)).to.be.true()\n    expect(lines.includes(err)).to.be.true()\n  })\n\n  it('prints multihash of all blocks', async () => {\n    const ref = 'ref'\n    const err = 'err'\n\n    ipfs.refs.local.withArgs(defaultOptions).returns([{\n      ref\n    }, {\n      err\n    }])\n\n    const out = await cli('refs local --multihash', { ipfs })\n    const lines = out.split('\\n')\n\n    expect(lines.includes(base32.encode(uint8ArrayFromString(ref)).toUpperCase())).to.be.true()\n    expect(lines.includes(err)).to.be.true()\n  })\n\n  it('prints CID of all blocks with timeout', async () => {\n    const ref = 'ref'\n    const err = 'err'\n\n    ipfs.refs.local.withArgs({\n      ...defaultOptions,\n      timeout: 1000\n    }).returns([{\n      ref\n    }, {\n      err\n    }])\n\n    const out = await cli('refs local --timeout=1s', { ipfs })\n    const lines = out.split('\\n')\n\n    expect(lines.includes(ref)).to.be.true()\n    expect(lines.includes(err)).to.be.true()\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/refs.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { CID } from 'multiformats/cid'\nimport { cli } from './utils/cli.js'\nimport sinon from 'sinon'\n\nconst defaultOptions = {\n  recursive: false,\n  format: '<dst>',\n  edges: false,\n  unique: false,\n  maxDepth: undefined,\n  timeout: undefined\n}\n\n// Note: There are more comprehensive tests in interface-js-ipfs-core\ndescribe('refs', () => {\n  let ipfs\n  const cid = CID.parse('Qmaj2NmcyAXT8dFmZRRytE12wpcaHADzbChKToMEjBsj5Z')\n  const err = 'err'\n  const ref = 'ref'\n\n  beforeEach(() => {\n    ipfs = {\n      refs: sinon.stub()\n    }\n  })\n\n  it('prints refs', async () => {\n    ipfs.refs.withArgs([cid.toString()], defaultOptions).returns([{\n      err\n    }, {\n      ref\n    }])\n\n    const out = await cli(`refs ${cid}`, { ipfs })\n    expect(out).to.eql(\n      `${err}\\n` +\n      `${ref}\\n`\n    )\n  })\n\n  it('prints refs with recursion', async () => {\n    ipfs.refs.withArgs([cid.toString()], {\n      ...defaultOptions,\n      recursive: true\n    }).returns([{\n      err\n    }, {\n      ref\n    }])\n\n    const out = await cli(`refs --recursive ${cid}`, { ipfs })\n    expect(out).to.eql(\n      `${err}\\n` +\n      `${ref}\\n`\n    )\n  })\n\n  it('prints refs with recursion (short option)', async () => {\n    ipfs.refs.withArgs([cid.toString()], {\n      ...defaultOptions,\n      recursive: true\n    }).returns([{\n      err\n    }, {\n      ref\n    }])\n\n    const out = await cli(`refs -r ${cid}`, { ipfs })\n    expect(out).to.eql(\n      `${err}\\n` +\n      `${ref}\\n`\n    )\n  })\n\n  it('prints refs with format', async () => {\n    ipfs.refs.withArgs([cid.toString()], {\n      ...defaultOptions,\n      format: '<src> <dst>'\n    }).returns([{\n      err\n    }, {\n      ref\n    }])\n\n    const out = await cli(`refs --format '<src> <dst>' ${cid}`, { ipfs })\n    expect(out).to.eql(\n      `${err}\\n` +\n      `${ref}\\n`\n    )\n  })\n\n  it('prints refs with unique', async () => {\n    ipfs.refs.withArgs([cid.toString()], {\n      ...defaultOptions,\n      unique: true\n    }).returns([{\n      err\n    }, {\n      ref\n    }])\n\n    const out = await cli(`refs --unique ${cid}`, { ipfs })\n    expect(out).to.eql(\n      `${err}\\n` +\n      `${ref}\\n`\n    )\n  })\n\n  it('prints refs with unique (short option)', async () => {\n    ipfs.refs.withArgs([cid.toString()], {\n      ...defaultOptions,\n      unique: true\n    }).returns([{\n      err\n    }, {\n      ref\n    }])\n\n    const out = await cli(`refs -u ${cid}`, { ipfs })\n    expect(out).to.eql(\n      `${err}\\n` +\n      `${ref}\\n`\n    )\n  })\n\n  it('prints refs with max-depth', async () => {\n    ipfs.refs.withArgs([cid.toString()], {\n      ...defaultOptions,\n      maxDepth: 4\n    }).returns([{\n      err\n    }, {\n      ref\n    }])\n\n    const out = await cli(`refs --max-depth 4 ${cid}`, { ipfs })\n    expect(out).to.eql(\n      `${err}\\n` +\n      `${ref}\\n`\n    )\n  })\n\n  it('prints refs with timeout', async () => {\n    ipfs.refs.withArgs([cid.toString()], {\n      ...defaultOptions,\n      timeout: 1000\n    }).returns([{\n      err\n    }, {\n      ref\n    }])\n\n    const out = await cli(`refs --timeout=1s ${cid}`, { ipfs })\n    expect(out).to.eql(\n      `${err}\\n` +\n      `${ref}\\n`\n    )\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/repo.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { cli } from './utils/cli.js'\nimport sinon from 'sinon'\nimport { CID } from 'multiformats/cid'\n\ndescribe('repo', () => {\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      repo: {\n        stat: sinon.stub(),\n        version: sinon.stub(),\n        gc: sinon.stub()\n      }\n    }\n  })\n\n  describe('stat', () => {\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('get repo stats', async () => {\n      ipfs.repo.stat.withArgs(defaultOptions).resolves({\n        numObjects: BigInt(10),\n        repoSize: BigInt(10),\n        storageMax: BigInt(10),\n        repoPath: '/foo',\n        version: 5\n      })\n\n      const stats = await cli('repo stat', { ipfs })\n      expect(stats).to.match(/^NumObjects:\\s+\\d+$/m)\n      expect(stats).to.match(/^RepoSize:\\s+\\d+$/m)\n      expect(stats).to.match(/^StorageMax:\\s+\\d+$/m)\n      expect(stats).to.match(/^RepoPath:\\s.+$/m)\n      expect(stats).to.match(/^Version:\\s+\\d+$/m)\n    })\n\n    it('get repo stats with just size', async () => {\n      ipfs.repo.stat.withArgs(defaultOptions).resolves({\n        numObjects: BigInt(10),\n        repoSize: BigInt(10),\n        storageMax: BigInt(10),\n        repoPath: '/foo',\n        version: 5\n      })\n\n      const stats = await cli('repo stat -s', { ipfs })\n      expect(stats).to.not.match(/^NumObjects:$/m)\n      expect(stats).to.match(/^RepoSize:\\s+\\d+$/m)\n      expect(stats).to.match(/^StorageMax:\\s+\\d+$/m)\n      expect(stats).to.not.match(/^RepoPath:$/m)\n      expect(stats).to.not.match(/^Version:$/m)\n    })\n\n    it('get human readable repo stats', async () => {\n      ipfs.repo.stat.withArgs(defaultOptions).resolves({\n        numObjects: BigInt(10),\n        repoSize: BigInt(10),\n        storageMax: BigInt(10),\n        repoPath: '/foo',\n        version: 5\n      })\n\n      const stats = await cli('repo stat --human', { ipfs })\n      expect(stats).to.match(/^NumObjects:\\s+\\d+$/m)\n      expect(stats).to.match(/^RepoSize:\\s+[\\d.]+\\s[PTGMK]?B$/gm)\n      expect(stats).to.match(/^StorageMax:\\s+[\\d.]+\\s[PTGMK]?B$/gm)\n      expect(stats).to.match(/^RepoPath:\\s.+$/m)\n      expect(stats).to.match(/^Version:\\s+\\d+$/m)\n    })\n\n    it('get repo with timeout', async () => {\n      ipfs.repo.stat.withArgs({\n        ...defaultOptions,\n        timeout: 1000\n      }).resolves({\n        numObjects: BigInt(10),\n        repoSize: BigInt(10),\n        storageMax: BigInt(10),\n        repoPath: '/foo',\n        version: 5\n      })\n\n      const stats = await cli('repo stat --timeout=1s', { ipfs })\n      expect(stats).to.match(/^NumObjects:\\s+\\d+$/m)\n      expect(stats).to.match(/^RepoSize:\\s+\\d+$/m)\n      expect(stats).to.match(/^StorageMax:\\s+\\d+$/m)\n      expect(stats).to.match(/^RepoPath:\\s.+$/m)\n      expect(stats).to.match(/^Version:\\s+\\d+$/m)\n    })\n  })\n\n  describe('version', () => {\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('get the repo version', async () => {\n      const repoVersion = 5\n\n      ipfs.repo.version.withArgs(defaultOptions).resolves(repoVersion)\n\n      const out = await cli('repo version', { ipfs })\n      expect(out).to.eql(`${repoVersion}\\n`)\n    })\n\n    it('get the repo version with timeout', async () => {\n      const repoVersion = 5\n\n      ipfs.repo.version.withArgs({\n        ...defaultOptions,\n        timeout: 1000\n      }).resolves(repoVersion)\n\n      const out = await cli('repo version --timeout=1s', { ipfs })\n      expect(out).to.eql(`${repoVersion}\\n`)\n    })\n  })\n\n  describe('gc', () => {\n    const cid = CID.parse('Qmd286K6pohQcTKYqnS1YhWrCiS4gz7Xi34sdwMe9USZ7u')\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('gc with no flags prints errors and outputs hashes', async () => {\n      ipfs.repo.gc.withArgs(defaultOptions).returns([{\n        err: new Error('err')\n      }, {\n        cid\n      }])\n\n      const out = await cli('repo gc', { ipfs })\n      expect(out).to.equal(`err\\nremoved ${cid.toString()}\\n`)\n    })\n\n    it('gc with --quiet prints hashes only', async () => {\n      ipfs.repo.gc.withArgs(defaultOptions).returns([{\n        err: new Error('err')\n      }, {\n        cid\n      }])\n\n      const out = await cli('repo gc --quiet', { ipfs })\n      expect(out).to.equal(`err\\n${cid.toString()}\\n`)\n    })\n\n    it('gc with -q prints hashes only', async () => {\n      ipfs.repo.gc.withArgs(defaultOptions).returns([{\n        err: new Error('err')\n      }, {\n        cid\n      }])\n\n      const out = await cli('repo gc -q', { ipfs })\n      expect(out).to.equal(`err\\n${cid.toString()}\\n`)\n    })\n\n    it('gc with --stream-errors=false does not print errors', async () => {\n      ipfs.repo.gc.withArgs(defaultOptions).returns([{\n        err: new Error('err')\n      }, {\n        cid\n      }])\n\n      const out = await cli('repo gc --stream-errors=false', { ipfs })\n      expect(out).to.equal(`removed ${cid.toString()}\\n`)\n    })\n\n    it('should run gc with timeout', async () => {\n      ipfs.repo.gc.withArgs({\n        ...defaultOptions,\n        timeout: 1000\n      }).returns([{\n        err: new Error('err')\n      }, {\n        cid\n      }])\n\n      const out = await cli('repo gc --timeout=1s', { ipfs })\n      expect(out).to.equal(`err\\nremoved ${cid.toString()}\\n`)\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/resolve.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { CID } from 'multiformats/cid'\nimport { cli } from './utils/cli.js'\nimport sinon from 'sinon'\n\nconst defaultOptions = {\n  recursive: true,\n  cidBase: 'base58btc',\n  timeout: undefined\n}\n\ndescribe('resolve', () => {\n  let ipfs\n  const cid = CID.parse('Qmaj2NmcyAXT8dFmZRRytE12wpcaHADzbChKToMEjBsj5Z')\n\n  beforeEach(() => {\n    ipfs = {\n      resolve: sinon.stub()\n    }\n  })\n\n  it('resolves a CID', async () => {\n    const resolved = `/ipfs/${cid}`\n\n    ipfs.resolve.withArgs(cid.toString(), defaultOptions).resolves(resolved)\n\n    const out = await cli(`resolve ${cid}`, { ipfs })\n    expect(out).to.equal(resolved + '\\n')\n  })\n\n  it('resolves a CID recursively by default', async () => {\n    const resolved = `/ipfs/${cid}`\n\n    ipfs.resolve.withArgs(cid.toString(), defaultOptions).resolves(resolved)\n\n    const out = await cli(`resolve ${cid} --recursive`, { ipfs })\n    expect(out).to.equal(resolved + '\\n')\n  })\n\n  it('allows non-recursive lookups with flag', async () => {\n    const resolved = `/ipfs/${cid}`\n\n    ipfs.resolve.withArgs(cid.toString(), {\n      ...defaultOptions,\n      recursive: false\n    }).resolves(resolved)\n\n    const out = await cli(`resolve ${cid} --recursive=false`, { ipfs })\n    expect(out).to.equal(resolved + '\\n')\n  })\n\n  it('resolves a CID with a timeout', async () => {\n    const resolved = `/ipfs/${cid}`\n\n    ipfs.resolve.withArgs(cid.toString(), {\n      ...defaultOptions,\n      timeout: 1000\n    }).resolves(resolved)\n\n    const out = await cli(`resolve ${cid} --timeout 1s`, { ipfs })\n    expect(out).to.equal(resolved + '\\n')\n  })\n\n  it('strips control characters when resolving a CID', async () => {\n    const resolved = `/ipfs/${cid}/derp/\\bherp`\n\n    ipfs.resolve.withArgs(cid.toString(), defaultOptions).resolves(resolved)\n\n    const out = await cli(`resolve ${cid}`, { ipfs })\n    expect(out).to.equal(`/ipfs/${cid}/derp/herp\\n`)\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/swarm.spec.js",
    "content": "/* eslint max-nested-callbacks: [\"error\", 8] */\n/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { cli, fail } from './utils/cli.js'\nimport sinon from 'sinon'\nimport { multiaddr } from '@multiformats/multiaddr'\nimport { peerIdFromString } from '@libp2p/peer-id'\n\ndescribe('swarm', () => {\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      swarm: {\n        connect: sinon.stub(),\n        peers: sinon.stub(),\n        addrs: sinon.stub(),\n        localAddrs: sinon.stub(),\n        disconnect: sinon.stub()\n      }\n    }\n  })\n\n  describe('connect', () => {\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('connect online', async () => {\n      const ma = multiaddr('/ip4/123.123.123.123/tcp/482')\n\n      ipfs.swarm.connect.withArgs(ma, defaultOptions).resolves()\n\n      const out = await cli(`swarm connect ${ma}`, { ipfs, isDaemon: true })\n      expect(out).to.equal(`${ma}\\n`)\n    })\n\n    it('connect offline', async () => {\n      const ma = multiaddr('/ip4/123.123.123.123/tcp/482')\n\n      const out = await fail(`swarm connect ${ma}`, { ipfs, isDaemon: false })\n      expect(out).to.include('This command must be run in online mode')\n\n      expect(ipfs.swarm.connect.called).to.be.false()\n    })\n\n    it('connect with timeout', async () => {\n      const ma = multiaddr('/ip4/123.123.123.123/tcp/482')\n\n      ipfs.swarm.connect.withArgs(ma, {\n        ...defaultOptions,\n        timeout: 1000\n      }).resolves()\n\n      const out = await cli(`swarm connect ${ma} --timeout=1s`, { ipfs, isDaemon: true })\n      expect(out).to.equal(`${ma}\\n`)\n    })\n  })\n\n  describe('peers', () => {\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('peers online', async () => {\n      ipfs.swarm.peers.withArgs(defaultOptions).resolves([{\n        peer: 'Qmaj2NmcyAXT8dFmZRRytE12wpcaHADzbChKToMEjBsj5Z',\n        addr: '/ip4/192.0.0.1/tcp/5001'\n      }, {\n        addr: '/ip4/192.0.0.2/tcp/5002/p2p/Qmaj2NmcyAXT8dFmZRRytE12wpcaHADzbChKToMEjBsj5a'\n      }])\n\n      const out = await cli('swarm peers', { ipfs, isDaemon: true })\n      expect(out).to.equal('/ip4/192.0.0.1/tcp/5001/p2p/Qmaj2NmcyAXT8dFmZRRytE12wpcaHADzbChKToMEjBsj5Z\\n/ip4/192.0.0.2/tcp/5002/p2p/Qmaj2NmcyAXT8dFmZRRytE12wpcaHADzbChKToMEjBsj5a\\n')\n    })\n\n    it('peers offline', async () => {\n      const out = await fail('swarm peers', { ipfs, isDaemon: false })\n      expect(out).to.include('This command must be run in online mode')\n\n      expect(ipfs.swarm.peers.called).to.be.false()\n    })\n\n    it('peers with timeout', async () => {\n      ipfs.swarm.peers.withArgs({\n        ...defaultOptions,\n        timeout: 1000\n      }).resolves([{\n        peer: 'Qmaj2NmcyAXT8dFmZRRytE12wpcaHADzbChKToMEjBsj5Z',\n        addr: '/ip4/192.0.0.1/tcp/5001'\n      }, {\n        addr: '/ip4/192.0.0.2/tcp/5002/p2p/Qmaj2NmcyAXT8dFmZRRytE12wpcaHADzbChKToMEjBsj5a'\n      }])\n\n      const out = await cli('swarm peers --timeout=1s', { ipfs, isDaemon: true })\n      expect(out).to.equal('/ip4/192.0.0.1/tcp/5001/p2p/Qmaj2NmcyAXT8dFmZRRytE12wpcaHADzbChKToMEjBsj5Z\\n/ip4/192.0.0.2/tcp/5002/p2p/Qmaj2NmcyAXT8dFmZRRytE12wpcaHADzbChKToMEjBsj5a\\n')\n    })\n  })\n\n  describe('addrs', () => {\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('addrs', async () => {\n      const peer = peerIdFromString('Qmaj2NmcyAXT8dFmZRRytE12wpcaHADzbChKToMEjBsj5Z')\n      const addr = `/ip4/192.0.0.2/tcp/5002/p2p/${peer}`\n\n      ipfs.swarm.addrs.withArgs(defaultOptions).resolves([{\n        id: peer,\n        addrs: [\n          multiaddr(addr)\n        ]\n      }])\n\n      const out = await cli('swarm addrs', { ipfs })\n      expect(out).to.equal(`${peer} (1)\\n\\t${addr}\\n`)\n    })\n\n    it('addrs with timeout', async () => {\n      const peer = peerIdFromString('Qmaj2NmcyAXT8dFmZRRytE12wpcaHADzbChKToMEjBsj5Z')\n      const addr = `/ip4/192.0.0.2/tcp/5002/p2p/${peer}`\n\n      ipfs.swarm.addrs.withArgs({\n        ...defaultOptions,\n        timeout: 1000\n      }).resolves([{\n        id: peer,\n        addrs: [\n          multiaddr(addr)\n        ]\n      }])\n\n      const out = await cli('swarm addrs --timeout=1s', { ipfs })\n      expect(out).to.equal(`${peer} (1)\\n\\t${addr}\\n`)\n    })\n\n    describe('local', () => {\n      it('addrs local', async () => {\n        const addr = 'addr'\n\n        ipfs.swarm.localAddrs.withArgs(defaultOptions).resolves([\n          addr\n        ])\n\n        const out = await cli('swarm addrs local', { ipfs, isDaemon: true })\n        expect(out).to.equal(`${addr}\\n`)\n      })\n\n      it('addrs local offline', async () => {\n        const out = await fail('swarm addrs local', { ipfs, isDaemon: false })\n        expect(out).to.include('This command must be run in online mode')\n      })\n\n      it('addrs local with timeout', async () => {\n        const addr = 'addr'\n\n        ipfs.swarm.localAddrs.withArgs({\n          ...defaultOptions,\n          timeout: 1000\n        }).resolves([\n          addr\n        ])\n\n        const out = await cli('swarm addrs local --timeout=1s', { ipfs, isDaemon: true })\n        expect(out).to.equal(`${addr}\\n`)\n      })\n    })\n  })\n\n  describe('disconnect', () => {\n    const defaultOptions = {\n      timeout: undefined\n    }\n\n    it('disconnect online', async () => {\n      const ma = multiaddr('/ip4/123.123.123.123/tcp/482')\n      ipfs.swarm.disconnect.withArgs(ma, defaultOptions).resolves()\n      const out = await cli(`swarm disconnect ${ma}`, { ipfs, isDaemon: true })\n      expect(out).to.equal(`${ma}\\n`)\n    })\n\n    it('disconnect offline', async () => {\n      const ma = multiaddr('/ip4/123.123.123.123/tcp/482')\n      const out = await fail(`swarm disconnect ${ma}`, { ipfs, isDaemon: false })\n      expect(out).to.include('This command must be run in online mode')\n    })\n\n    it('disconnect with timeout', async () => {\n      const ma = multiaddr('/ip4/123.123.123.123/tcp/482')\n      ipfs.swarm.disconnect.withArgs(ma, {\n        ...defaultOptions,\n        timeout: 1000\n      }).resolves()\n      const out = await cli(`swarm disconnect ${ma} --timeout=1s`, { ipfs, isDaemon: true })\n      expect(out).to.equal(`${ma}\\n`)\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/test/utils/clean.js",
    "content": "\nimport rimraf from 'rimraf'\nimport fs from 'fs/promises'\nimport { promisify } from 'util'\n\n/**\n * @param {string} dir\n */\nexport async function clean (dir) {\n  try {\n    await fs.access(dir)\n  } catch (/** @type {any} */ err) {\n    // Does not exist so all good\n    return\n  }\n\n  return promisify(rimraf)(dir)\n}\n"
  },
  {
    "path": "packages/ipfs-cli/test/utils/cli.js",
    "content": "\nimport { parseArgsStringToArgv } from 'string-argv'\nimport { cli as exec } from '../../src/index.js'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\n\nconst output = () => {\n  const output = []\n\n  const print = (line, includeNewline = true) => {\n    if (includeNewline) {\n      line = `${line}\\n`\n    }\n\n    output.push(Buffer.from(line))\n  }\n  print.clearLine = () => {\n    output.pop()\n  }\n  print.cursorTo = () => {}\n  print.write = (data) => {\n    output.push(Buffer.from(data))\n  }\n  print.error = print\n  print.getOutput = () => {\n    return Buffer.concat(output)\n  }\n  // used by ipfs.add to interrupt the progress bar\n  print.isTTY = true\n\n  return print\n}\n\nexport async function cli (command, ctx = {}) {\n  const print = output()\n\n  command = parseArgsStringToArgv(command)\n\n  await exec(command, (args) => {\n    args.ctx = {\n      print,\n      ...ctx\n    }\n  })\n\n  const out = print.getOutput()\n\n  if (ctx.raw) {\n    return out\n  }\n\n  return uint8ArrayToString(out)\n}\n\nexport async function fail (command, ctx = {}) {\n  const print = output()\n\n  command = parseArgsStringToArgv(command)\n\n  try {\n    await exec(command, (args) => {\n      args.ctx = {\n        print,\n        ...ctx\n      }\n    })\n\n    throw new Error('Command did not error')\n  } catch (/** @type {any} */ err) {\n    if (err.message === 'Command did not error') {\n      throw err\n    }\n\n    print(err.message)\n  }\n\n  const out = print.getOutput()\n\n  if (ctx.raw) {\n    return out\n  }\n\n  return uint8ArrayToString(out)\n}\n"
  },
  {
    "path": "packages/ipfs-cli/test/utils/ipfs-exec.js",
    "content": "import { execaCommand } from 'execa'\nimport path, { dirname } from 'path'\nimport { fileURLToPath } from 'url'\n\n// @ts-expect-error need to set module to es2020 to use import.meta.url, which we do,\n// but then the \"--module\" setting doesn't get used by the \"--build\" setting\n// which we use to build types from jsdoc\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n\n// This is our new test utility to easily check and execute ipfs cli commands.\n//\n// The top level export is a function that can be passed a `repoPath`\n// and optional `opts` to customize the execution of the commands.\n// This function returns the actual executer, which consists of\n// `ipfs('get <hash>')` and `ipfs.fail('get <hash>')`\n// The first one executes and asserts that the command ran successfully\n// and returns a promise which is resolved to `stdout` of the command.\n// The `.fail` variation asserts that the command exited with `Code > 0`\n// and returns a promise that resolves to `stderr`.\nexport function ipfsExec (repoPath, opts) {\n  const env = { ...process.env }\n  env.IPFS_PATH = repoPath\n\n  const config = Object.assign({}, {\n    stripFinalNewline: false,\n    env: env,\n    timeout: 60 * 1000,\n    all: true\n  }, opts)\n  const exec = (args, options) => {\n    const opts = Object.assign({}, config, options)\n\n    return execaCommand(`${path.resolve(__dirname, '../../../ipfs/src/cli.js')} ${args}`, opts)\n  }\n  const execRaw = (args, options) => {\n    const opts = Object.assign({}, config, options, {\n      encoding: null\n    })\n\n    return execaCommand(`${path.resolve(__dirname, '../../ipfs/src/cli.js')} ${args}`, opts)\n  }\n\n  const execute = (exec, args, options) => {\n    options = options || {}\n\n    const cp = exec(args, options)\n    const res = cp.then((res) => {\n      // We can't escape the os.tmpdir warning due to:\n      // https://github.com/shelljs/shelljs/blob/master/src/tempdir.js#L43\n      // expect(res.stderr).to.be.eql('')\n      return res.stdout\n    }, err => {\n      if (!options.disableErrorLog) {\n        console.error(err.stderr) // eslint-disable-line no-console\n      }\n      throw err\n    })\n\n    res.cancel = cp.cancel.bind(cp)\n    res.kill = cp.kill.bind(cp)\n    res.stdin = cp.stdin\n    res.stdout = cp.stdout\n    res.stderr = cp.stderr\n    res.all = cp.all\n\n    return res\n  }\n\n  function ipfs (command, options) {\n    return execute(exec, command, options)\n  }\n\n  // Will return buffers instead of strings\n  ipfs.raw = function (command, options) {\n    return execute(execRaw, command, options)\n  }\n\n  /**\n   * Expect the command passed as @param arguments to fail.\n   *\n   * @param {string} command - String command to run, e.g. `'pin ls'`\n   * @param {object} [options] - Options to pass to `execa`\n   * @returns {Promise<Error | undefined>} Resolves if the command passed as @param command fails,\n   * rejects if it was successful.\n   */\n  ipfs.fail = function ipfsFail (command, options) {\n    return ipfs(command, { disableErrorLog: true, ...(options || {}) })\n      .then(() => {\n        throw new Error(`jsipfs expected to fail during command: jsipfs ${command}`)\n      }, (/** @type {Error} */ err) => {\n        return err\n      })\n  }\n\n  return ipfs\n}\n"
  },
  {
    "path": "packages/ipfs-cli/test/utils/match-iterable.js",
    "content": "\nimport sinon from 'sinon'\n\nexport function matchIterable () {\n  return sinon.match((thing) => Boolean(thing[Symbol.asyncIterator]) || Boolean(thing[Symbol.iterator]))\n}\n"
  },
  {
    "path": "packages/ipfs-cli/test/utils/match-peer-id.js",
    "content": "import sinon from 'sinon'\n\n/**\n * @param {import('@libp2p/interface-peer-id').PeerId} peerId\n * @returns {import('sinon').SinonMatcher}\n */\nexport function matchPeerId (peerId) {\n  return sinon.match((value) => {\n    return peerId.toString() === value.toString()\n  })\n}\n"
  },
  {
    "path": "packages/ipfs-cli/test/utils/platforms.js",
    "content": "\nimport os from 'os'\n\nconst current = os.platform()\n\nexport const isWindows = current === 'win32'\nexport const isMacOS = current === 'darwin'\nexport const isLinux = current === 'linux'\n"
  },
  {
    "path": "packages/ipfs-cli/test/version.spec.js",
    "content": "/* eslint max-nested-callbacks: [\"error\", 5] */\n/* eslint-env mocha */\n\nimport os from 'os'\nimport { expect } from 'aegir/chai'\nimport { cli } from './utils/cli.js'\nimport sinon from 'sinon'\n\nconst defaultOptions = {\n  timeout: undefined\n}\n\ndescribe('version', () => {\n  let ipfs\n\n  before(() => {\n    ipfs = {\n      version: sinon.stub()\n    }\n  })\n\n  it('get the version', async () => {\n    ipfs.version.withArgs(defaultOptions).resolves({\n      version: 5\n    })\n    const out = await cli('version', { ipfs })\n    expect(out).to.equal('js-ipfs version: 5\\n')\n  })\n\n  it('handles --number', async () => {\n    ipfs.version.withArgs(defaultOptions).resolves({\n      version: 5\n    })\n    const out = await cli('version --number', { ipfs })\n    expect(out).to.equal('5\\n')\n  })\n\n  it('handles --number (short option)', async () => {\n    ipfs.version.withArgs(defaultOptions).resolves({\n      version: 5\n    })\n    const out = await cli('version -n', { ipfs })\n    expect(out).to.equal('5\\n')\n  })\n\n  it('handles --commit', async () => {\n    ipfs.version.withArgs(defaultOptions).resolves({\n      version: 5,\n      commit: '123'\n    })\n    const out = await cli('version --commit', { ipfs })\n    expect(out).to.equal('js-ipfs version: 5-123\\n')\n  })\n\n  it('handles --repo', async () => {\n    ipfs.version.withArgs(defaultOptions).resolves({\n      repo: 6\n    })\n\n    const out = await cli('version --repo', { ipfs })\n    expect(out).to.equal('6\\n')\n  })\n\n  it('prints js-ipfs version with --all', async () => {\n    ipfs.version.withArgs(defaultOptions).resolves({\n      version: 5\n    })\n\n    const out = await cli('version --all', { ipfs })\n    expect(out).to.include('js-ipfs version: 5')\n  })\n\n  it('prints repo version with --all', async () => {\n    ipfs.version.withArgs(defaultOptions).resolves({\n      repo: 6\n    })\n\n    const out = await cli('version --all', { ipfs })\n    expect(out).to.include('Repo version: 6')\n  })\n\n  it('prints arch/platform with --all', async () => {\n    const out = await cli('version --all', { ipfs })\n    expect(out).to.include(`System version: ${os.arch()}/${os.platform()}`)\n  })\n\n  it('prints Node.js version with --all', async () => {\n    const out = await cli('version --all', { ipfs })\n    expect(out).to.include(`Node.js version: ${process.version}`)\n  })\n\n  it('prints version with timeout', async () => {\n    ipfs.version.withArgs({\n      ...defaultOptions,\n      timeout: 1000\n    }).resolves({\n      version: 5\n    })\n    const out = await cli('version --timeout=1s', { ipfs })\n    expect(out).to.equal('js-ipfs version: 5\\n')\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-cli/tsconfig.json",
    "content": "{\n  \"extends\": \"aegir/src/config/tsconfig.aegir.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"emitDeclarationOnly\": true\n  },\n  \"include\": [\n    \"src\",\n    \"package.json\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../ipfs-core\"\n    },\n    {\n      \"path\": \"../ipfs-core-types\"\n    },\n    {\n      \"path\": \"../ipfs-core-utils\"\n    },\n    {\n      \"path\": \"../ipfs-daemon\"\n    },\n    {\n      \"path\": \"../ipfs-http-client\"\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/ipfs-client/.aegir.js",
    "content": "\n/** @type {import('aegir').PartialOptions} */\nexport default {\n  build: {\n    bundlesizeMax: '98kB'\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-client/CHANGELOG.md",
    "content": "# Change Log\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n### [0.10.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-client-v0.10.0...ipfs-client-v0.10.1) (2023-05-25)\n\n\n### Bug Fixes\n\n* add deprecation notice to readmes ([#4362](https://www.github.com/ipfs/js-ipfs/issues/4362)) ([7b79c1b](https://www.github.com/ipfs/js-ipfs/commit/7b79c1b8df5c818dc124b346ea28330455732d5c))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-grpc-client bumped from ^0.13.0 to ^0.13.1\n    * ipfs-http-client bumped from ^60.0.0 to ^60.0.1\n\n## [0.10.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-client-v0.9.2...ipfs-client-v0.10.0) (2023-01-11)\n\n\n### ⚠ BREAKING CHANGES\n\n* update multiformats to v11.x.x and related depenendcies (#4277)\n\n### Bug Fixes\n\n* update multiformats to v11.x.x and related depenendcies ([#4277](https://www.github.com/ipfs/js-ipfs/issues/4277)) ([521c84a](https://www.github.com/ipfs/js-ipfs/commit/521c84a958b04d61702577a5adce28519c1b2a3b))\n* use aegir to publish RCs ([#4284](https://www.github.com/ipfs/js-ipfs/issues/4284)) ([6d90cbf](https://www.github.com/ipfs/js-ipfs/commit/6d90cbf321a7dbf4b1084ba20f0c514dc08d8d0a))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-grpc-client bumped from ^0.12.0 to ^0.13.0\n    * ipfs-http-client bumped from ^59.0.0 to ^60.0.0\n\n### [0.9.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-client-v0.9.1...ipfs-client-v0.9.2) (2022-10-24)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-grpc-client bumped from ^0.11.1 to ^0.12.0\n    * ipfs-http-client bumped from ^58.0.1 to ^59.0.0\n\n### [0.9.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-client-v0.9.0...ipfs-client-v0.9.1) (2022-09-21)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-grpc-client bumped from ^0.11.0 to ^0.11.1\n    * ipfs-http-client bumped from ^58.0.0 to ^58.0.1\n\n## [0.9.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-client-v0.8.3...ipfs-client-v0.9.0) (2022-09-06)\n\n\n### ⚠ BREAKING CHANGES\n\n* update to libp2p@0.38.x (#4151)\n\n### deps\n\n* update to libp2p@0.38.x ([#4151](https://www.github.com/ipfs/js-ipfs/issues/4151)) ([39dbf70](https://www.github.com/ipfs/js-ipfs/commit/39dbf708ec31b263115e44f420651fa4e056a89e))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-grpc-client bumped from ^0.10.0 to ^0.11.0\n    * ipfs-http-client bumped from ^57.0.0 to ^58.0.0\n\n### [0.8.3](https://www.github.com/ipfs/js-ipfs/compare/ipfs-client-v0.8.2...ipfs-client-v0.8.3) (2022-06-24)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-grpc-client bumped from ^0.10.1 to ^0.10.2\n    * ipfs-http-client bumped from ^57.0.2 to ^57.0.3\n\n### [0.8.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-client-v0.8.1...ipfs-client-v0.8.2) (2022-06-22)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-grpc-client bumped from ^0.10.0 to ^0.10.1\n    * ipfs-http-client bumped from ^57.0.1 to ^57.0.2\n\n### [0.8.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-client-v0.8.0...ipfs-client-v0.8.1) (2022-06-01)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-http-client bumped from ^57.0.0 to ^57.0.1\n\n## [0.8.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-client-v0.7.9...ipfs-client-v0.8.0) (2022-05-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* This module is now ESM only and there return types of some methods have changed\n\n### Features\n\n* update to libp2p 0.37.x ([#4092](https://www.github.com/ipfs/js-ipfs/issues/4092)) ([74aee8b](https://www.github.com/ipfs/js-ipfs/commit/74aee8b3d78f233c3199a3e9a6c0ac628a31a433))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-grpc-client bumped from ^0.9.4 to ^0.10.0\n    * ipfs-http-client bumped from ^56.0.3 to ^57.0.0\n\n### [0.7.9](https://www.github.com/ipfs/js-ipfs/compare/ipfs-client-v0.7.8...ipfs-client-v0.7.9) (2022-04-20)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-grpc-client bumped from ^0.9.3 to ^0.9.4\n    * ipfs-http-client bumped from ^56.0.2 to ^56.0.3\n\n### [0.7.8](https://www.github.com/ipfs/js-ipfs/compare/ipfs-client-v0.7.7...ipfs-client-v0.7.8) (2022-03-01)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-grpc-client bumped from ^0.9.2 to ^0.9.3\n    * ipfs-http-client bumped from ^56.0.1 to ^56.0.2\n\n### [0.7.7](https://www.github.com/ipfs/js-ipfs/compare/ipfs-client-v0.7.6...ipfs-client-v0.7.7) (2022-02-06)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-grpc-client bumped from ^0.9.1 to ^0.9.2\n    * ipfs-http-client bumped from ^56.0.0 to ^56.0.1\n\n### [0.7.6](https://www.github.com/ipfs/js-ipfs/compare/ipfs-client-v0.7.5...ipfs-client-v0.7.6) (2022-01-27)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-grpc-client bumped from ^0.9.0 to ^0.9.1\n    * ipfs-http-client bumped from ^55.0.0 to ^56.0.0\n\n### [0.7.5](https://github.com/ipfs/js-ipfs/compare/ipfs-client@0.7.4...ipfs-client@0.7.5) (2021-12-15)\n\n**Note:** Version bump only for package ipfs-client\n\n\n\n\n\n### [0.7.4](https://github.com/ipfs/js-ipfs/compare/ipfs-client@0.7.3...ipfs-client@0.7.4) (2021-11-24)\n\n**Note:** Version bump only for package ipfs-client\n\n\n\n\n\n### [0.7.3](https://github.com/ipfs/js-ipfs/compare/ipfs-client@0.7.2...ipfs-client@0.7.3) (2021-11-19)\n\n**Note:** Version bump only for package ipfs-client\n\n\n\n\n\n### [0.7.2](https://github.com/ipfs/js-ipfs/compare/ipfs-client@0.7.1...ipfs-client@0.7.2) (2021-11-12)\n\n**Note:** Version bump only for package ipfs-client\n\n\n\n\n\n### [0.7.1](https://github.com/ipfs/js-ipfs/compare/ipfs-client@0.7.0...ipfs-client@0.7.1) (2021-09-28)\n\n**Note:** Version bump only for package ipfs-client\n\n\n\n\n\n## [0.7.0](https://github.com/ipfs/js-ipfs/compare/ipfs-client@0.6.6...ipfs-client@0.7.0) (2021-09-24)\n\n\n### Features\n\n* switch to esm ([#3879](https://github.com/ipfs/js-ipfs/issues/3879)) ([9a40109](https://github.com/ipfs/js-ipfs/commit/9a40109632e5b4837eb77a2f57dbc77fbf1fe099))\n\n\n### BREAKING CHANGES\n\n* There are no default exports and everything is now dual published as ESM/CJS\n\n\n\n\n\n### [0.6.6](https://github.com/ipfs/js-ipfs/compare/ipfs-client@0.6.5...ipfs-client@0.6.6) (2021-09-17)\n\n**Note:** Version bump only for package ipfs-client\n\n\n\n\n\n### [0.6.5](https://github.com/ipfs/js-ipfs/compare/ipfs-client@0.6.4...ipfs-client@0.6.5) (2021-09-17)\n\n**Note:** Version bump only for package ipfs-client\n\n\n\n\n\n### [0.6.4](https://github.com/ipfs/js-ipfs/compare/ipfs-client@0.6.3...ipfs-client@0.6.4) (2021-09-02)\n\n\n### Bug Fixes\n\n* remove use of instanceof for CID class ([#3847](https://github.com/ipfs/js-ipfs/issues/3847)) ([ebbb12d](https://github.com/ipfs/js-ipfs/commit/ebbb12db523c53ce8e4ddae5266cd9acb3504431))\n\n\n\n\n\n### [0.6.3](https://github.com/ipfs/js-ipfs/compare/ipfs-client@0.6.2...ipfs-client@0.6.3) (2021-08-25)\n\n**Note:** Version bump only for package ipfs-client\n\n\n\n\n\n### [0.6.2](https://github.com/ipfs/js-ipfs/compare/ipfs-client@0.6.1...ipfs-client@0.6.2) (2021-08-17)\n\n**Note:** Version bump only for package ipfs-client\n\n\n\n\n\n### [0.6.1](https://github.com/ipfs/js-ipfs/compare/ipfs-client@0.6.0...ipfs-client@0.6.1) (2021-08-17)\n\n**Note:** Version bump only for package ipfs-client\n\n\n\n\n\n## [0.6.0](https://github.com/ipfs/js-ipfs/compare/ipfs-client@0.5.1...ipfs-client@0.6.0) (2021-08-11)\n\n\n### Features\n\n* make ipfs.get output tarballs ([#3785](https://github.com/ipfs/js-ipfs/issues/3785)) ([1ad6001](https://github.com/ipfs/js-ipfs/commit/1ad60018d39d5b46c484756631e30e1989fd8eba))\n\n\n### BREAKING CHANGES\n\n* the output type of `ipfs.get` has changed and the `recursive` option has been removed from `ipfs.ls` since it was not supported everywhere\n\n\n\n\n\n### [0.5.1](https://github.com/ipfs/js-ipfs/compare/ipfs-client@0.5.0...ipfs-client@0.5.1) (2021-07-30)\n\n**Note:** Version bump only for package ipfs-client\n\n\n\n\n\n## [0.5.0](https://github.com/ipfs/js-ipfs/compare/ipfs-client@0.4.3...ipfs-client@0.5.0) (2021-07-27)\n\n\n### Features\n\n* upgrade to the new multiformats ([#3556](https://github.com/ipfs/js-ipfs/issues/3556)) ([d13d15f](https://github.com/ipfs/js-ipfs/commit/d13d15f022a87d04a35f0f7822142f9cb898479c))\n\n\n### BREAKING CHANGES\n\n* ipld-formats no longer supported, use multiformat BlockCodecs instead\n\nCo-authored-by: Rod Vagg <rod@vagg.org>\nCo-authored-by: achingbrain <alex@achingbrain.net>\n\n\n\n\n\n### [0.4.3](https://github.com/ipfs/js-ipfs/compare/ipfs-client@0.4.2...ipfs-client@0.4.3) (2021-06-18)\n\n**Note:** Version bump only for package ipfs-client\n\n\n\n\n\n### [0.4.2](https://github.com/ipfs/js-ipfs/compare/ipfs-client@0.4.1...ipfs-client@0.4.2) (2021-06-05)\n\n**Note:** Version bump only for package ipfs-client\n\n\n\n\n\n### [0.4.1](https://github.com/ipfs/js-ipfs/compare/ipfs-client@0.4.0...ipfs-client@0.4.1) (2021-05-26)\n\n**Note:** Version bump only for package ipfs-client\n\n\n\n\n\n## [0.4.0](https://github.com/ipfs/js-ipfs/compare/ipfs-client@0.3.4...ipfs-client@0.4.0) (2021-05-10)\n\n\n### chore\n\n* upgrade deps with new typedefs ([#3550](https://github.com/ipfs/js-ipfs/issues/3550)) ([a418a52](https://github.com/ipfs/js-ipfs/commit/a418a521574c878d7aabd0ad2fd8d516908a3756))\n\n\n### BREAKING CHANGES\n\n* all core api methods now have types, some method signatures have changed, named exports are now used by the http, grpc and ipfs client modules\n\n\n\n\n\n### [0.3.4](https://github.com/ipfs/js-ipfs/compare/ipfs-client@0.3.3...ipfs-client@0.3.4) (2021-03-10)\n\n**Note:** Version bump only for package ipfs-client\n\n\n\n\n\n### [0.3.3](https://github.com/ipfs/js-ipfs/compare/ipfs-client@0.3.2...ipfs-client@0.3.3) (2021-03-09)\n\n\n### Bug Fixes\n\n* update to new aegir ([#3528](https://github.com/ipfs/js-ipfs/issues/3528)) ([49f7880](https://github.com/ipfs/js-ipfs/commit/49f78807d7e26483bd926b45cc7e0f797d77e41b))\n\n\n\n\n\n### [0.3.2](https://github.com/ipfs/js-ipfs/compare/ipfs-client@0.3.1...ipfs-client@0.3.2) (2021-02-08)\n\n**Note:** Version bump only for package ipfs-client\n\n\n\n\n\n### [0.3.1](https://github.com/ipfs/js-ipfs/compare/ipfs-client@0.3.0...ipfs-client@0.3.1) (2021-02-02)\n\n**Note:** Version bump only for package ipfs-client\n\n\n\n\n\n## [0.3.0](https://github.com/ipfs/js-ipfs/compare/ipfs-client@0.2.2...ipfs-client@0.3.0) (2021-02-01)\n\n\n### chore\n\n* update deps ([#3514](https://github.com/ipfs/js-ipfs/issues/3514)) ([061d77c](https://github.com/ipfs/js-ipfs/commit/061d77cc03f40af5a3bc3590481e1e5836e7f0d8))\n\n\n### BREAKING CHANGES\n\n* ipfs-repo upgrade requires repo migration to v10\n\n\n\n\n\n### [0.2.2](https://github.com/ipfs/js-ipfs/compare/ipfs-client@0.2.1...ipfs-client@0.2.2) (2021-01-22)\n\n\n### Bug Fixes\n\n* issue with isolateModules flag ([#3495](https://github.com/ipfs/js-ipfs/issues/3495)) ([839e190](https://github.com/ipfs/js-ipfs/commit/839e1908f3c050b45af176883a7e450fb339bef0)), closes [#3494](https://github.com/ipfs/js-ipfs/issues/3494) [#3498](https://github.com/ipfs/js-ipfs/issues/3498) [/github.com/ipfs-shipyard/ipfs-webui/pull/1655#issuecomment-763846124](https://github.com//github.com/ipfs-shipyard/ipfs-webui/pull/1655/issues/issuecomment-763846124)\n\n\n\n\n\n### [0.2.1](https://github.com/ipfs/js-ipfs/compare/ipfs-client@0.2.0...ipfs-client@0.2.1) (2021-01-20)\n\n**Note:** Version bump only for package ipfs-client\n\n\n\n\n\n# 0.2.0 (2021-01-15)\n\n\n### Features\n\n* add grpc server and client ([#3403](https://github.com/ipfs/js-ipfs/issues/3403)) ([a9027e0](https://github.com/ipfs/js-ipfs/commit/a9027e0ec0cea9a4f34b4f2f52e09abb35237384)), closes [#2519](https://github.com/ipfs/js-ipfs/issues/2519) [#2838](https://github.com/ipfs/js-ipfs/issues/2838) [#2943](https://github.com/ipfs/js-ipfs/issues/2943) [#2854](https://github.com/ipfs/js-ipfs/issues/2854) [#2864](https://github.com/ipfs/js-ipfs/issues/2864)\n* allow passing a http.Agent to the grpc client ([#3477](https://github.com/ipfs/js-ipfs/issues/3477)) ([c5f0bc5](https://github.com/ipfs/js-ipfs/commit/c5f0bc5eeee15369b7d02901035b04184a8608d2)), closes [#3474](https://github.com/ipfs/js-ipfs/issues/3474)"
  },
  {
    "path": "packages/ipfs-client/LICENSE",
    "content": "This project is dual licensed under MIT and Apache-2.0.\n\nMIT: https://www.opensource.org/licenses/mit\nApache-2.0: https://www.apache.org/licenses/license-2.0\n"
  },
  {
    "path": "packages/ipfs-client/LICENSE-APACHE",
    "content": "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\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless 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.\n"
  },
  {
    "path": "packages/ipfs-client/LICENSE-MIT",
    "content": "The MIT License (MIT)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "packages/ipfs-client/README.md",
    "content": "> # ⛔️ DEPRECATED: [js-IPFS](https://github.com/ipfs/js-ipfs) has been superseded by [Helia](https://github.com/ipfs/helia)\n>\n> 📚 [Learn more about this deprecation](https://github.com/ipfs/js-ipfs/issues/4336) or [how to migrate](https://github.com/ipfs/helia/wiki/Migrating-from-js-IPFS)\n>\n> ⚠️ If you continue using this repo, please note that security fixes will not be provided\n\n# ipfs-client <!-- omit in toc -->\n\n[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech)\n[![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech)\n[![codecov](https://img.shields.io/codecov/c/github/ipfs/js-ipfs.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipfs)\n[![CI](https://img.shields.io/github/actions/workflow/status/ipfs/js-ipfs/test.yml?branch=master\\&style=flat-square)](https://github.com/ipfs/js-ipfs/actions/workflows/test.yml?query=branch%3Amaster)\n\n> A client library to talk to local IPFS daemons\n\n## Table of contents <!-- omit in toc -->\n\n- [Install](#install)\n- [API](#api)\n  - [`create([options])`](#createoptions)\n  - [Parameters](#parameters)\n  - [Options](#options)\n  - [Returns](#returns)\n  - [Example](#example)\n- [License](#license)\n- [Contribute](#contribute)\n\n## Install\n\n```console\n$ npm i ipfs-client\n```\n\nThis module combines the [ipfs-grpc-client][] and [ipfs-http-client][] modules to give you a client that is capable of bidirectional streaming in the browser as well as node.\n\n## API\n\nThe client object created by the `createClient` function supports the [IPFS Core API](https://github.com/ipfs/js-ipfs/tree/master/docs/core-api), see the docs for more.\n\n### `create([options])`\n\n### Parameters\n\nNone\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name  | Type                                                                 | Default     | Description                                                       |\n| ----- | -------------------------------------------------------------------- | ----------- | ----------------------------------------------------------------- |\n| grpc  | `Multiaddr` or `string` or `URL`                                     | `undefined` | The address of a [ipfs-grpc-server][] to connect to               |\n| http  | `Multiaddr` or `string` or `URL`                                     | `undefined` | The address of a [ipfs-http-server][] to connect to               |\n| agent | [http.Agent](https://nodejs.org/api/http.html#http_class_http_agent) | `undefined` | A http.Agent used to control HTTP client behaviour (node.js only) |\n\n### Returns\n\n| Type     | Description               |\n| -------- | ------------------------- |\n| `object` | An instance of the client |\n\n### Example\n\n```js\nimport { create } from 'ipfs-client'\n\nconst client = create({\n  grpc: '/ipv4/127.0.0.1/tcp/5003/ws',\n  http: '/ipv4/127.0.0.1/tcp/5002/http'\n})\n\nconst id = await client.id()\n```\n\n## License\n\nLicensed under either of\n\n- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / <http://www.apache.org/licenses/LICENSE-2.0>)\n- MIT ([LICENSE-MIT](LICENSE-MIT) / <http://opensource.org/licenses/MIT>)\n\n## Contribute\n\nContributions welcome! Please check out [the issues](https://github.com/ipfs/js-ipfs/issues).\n\nAlso 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.\n\nPlease 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).\n\nUnless 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.\n\n[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md)\n\n[ipfs]: https://www.npmjs.com/package/ipfs\n\n[ipfs-grpc-client]: https://www.npmjs.com/package/ipfs-grpc-client\n\n[ipfs-http-client]: https://www.npmjs.com/package/ipfs-http-client\n\n[ipfs-grpc-server]: https://www.npmjs.com/package/ipfs-grpc-server\n\n[ipfs-http-server]: https://www.npmjs.com/package/ipfs-http-server\n"
  },
  {
    "path": "packages/ipfs-client/package.json",
    "content": "{\n  \"name\": \"ipfs-client\",\n  \"version\": \"0.10.1\",\n  \"description\": \"A client library to talk to local IPFS daemons\",\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"homepage\": \"https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-client#readme\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ipfs/js-ipfs.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ipfs/js-ipfs/issues\"\n  },\n  \"keywords\": [\n    \"ipfs\"\n  ],\n  \"engines\": {\n    \"node\": \">=16.0.0\",\n    \"npm\": \">=7.0.0\"\n  },\n  \"type\": \"module\",\n  \"types\": \"./dist/src/index.d.ts\",\n  \"typesVersions\": {\n    \"*\": {\n      \"*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ],\n      \"src/*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ]\n    }\n  },\n  \"files\": [\n    \"src\",\n    \"dist\",\n    \"!dist/test\",\n    \"!**/*.tsbuildinfo\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/src/index.d.ts\",\n      \"import\": \"./src/index.js\"\n    }\n  },\n  \"eslintConfig\": {\n    \"extends\": \"ipfs\",\n    \"parserOptions\": {\n      \"sourceType\": \"module\"\n    }\n  },\n  \"scripts\": {\n    \"build\": \"aegir build\",\n    \"lint\": \"aegir lint\",\n    \"clean\": \"aegir clean\",\n    \"dep-check\": \"aegir dep-check -i aegir\"\n  },\n  \"dependencies\": {\n    \"ipfs-grpc-client\": \"^0.13.1\",\n    \"ipfs-http-client\": \"^60.0.1\",\n    \"merge-options\": \"^3.0.4\"\n  },\n  \"devDependencies\": {\n    \"aegir\": \"^37.11.0\"\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-client/src/index.js",
    "content": "import { create as httpClient } from 'ipfs-http-client'\nimport { create as grpcClient } from 'ipfs-grpc-client'\nimport mergeOpts from 'merge-options'\n\nconst mergeOptions = mergeOpts.bind({ ignoreUndefined: true })\n\n/**\n * @typedef {import('ipfs-http-client').Options} HTTPOptions\n * @typedef {import('ipfs-grpc-client').Options} GRPCOptions\n * @typedef {string|URL|import('@multiformats/multiaddr').Multiaddr} Address\n * @typedef {{http?: Address, grpc?: Address} & Partial<HTTPOptions & GRPCOptions>} Options\n *\n * @param {Options} [opts]\n */\nexport function create (opts = {}) {\n  const clients = []\n\n  if (opts.http) {\n    clients.push(httpClient({\n      ...opts,\n      url: opts.http\n    }))\n  }\n\n  if (opts.grpc) {\n    clients.push(grpcClient({\n      ...opts,\n      url: opts.grpc\n    }))\n  }\n\n  // override http methods with grpc if address is supplied\n  const out = mergeOptions({}, ...clients)\n\n  return out\n}\n"
  },
  {
    "path": "packages/ipfs-client/tsconfig.json",
    "content": "{\n  \"extends\": \"aegir/src/config/tsconfig.aegir.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"emitDeclarationOnly\": true\n  },\n  \"include\": [\n    \"src\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../ipfs-grpc-client\"\n    },\n    {\n      \"path\": \"../ipfs-http-client\"\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/ipfs-core/.aegir.js",
    "content": "import { createServer } from 'ipfsd-ctl'\nimport path from 'path'\nimport { fileURLToPath } from 'url'\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url))\n\n/** @type {import('aegir').Options[\"build\"][\"config\"]} */\nconst esbuild = {\n  inject: [path.join(__dirname, '../../scripts/node-globals.js')]\n}\n\n/** @type {import('aegir').PartialOptions} */\nexport default {\n  test: {\n    browser: {\n      config: {\n        assets: '..',\n        buildConfig: esbuild\n      }\n    },\n    async before (options) {\n      const MockPreloadNode = await import('./test/utils/mock-preload-node.js')\n      const preloadNode = MockPreloadNode.createNode()\n      await preloadNode.start()\n      if (options.runner !== 'node') {\n        const ipfsdServer = await createServer({\n          host: '127.0.0.1',\n          port: 57483\n        }, {\n          type: 'js',\n          ipfsModule: await import('./src/index.js'),\n          ipfsHttpModule: await import('ipfs-http-client'),\n          ipfsBin: path.resolve('../ipfs/src/cli.js'),\n          ipfsOptions: {\n            libp2p: {\n              dialer: {\n                dialTimeout: 60e3 // increase timeout because travis is slow\n              }\n            }\n          }\n        }, {\n          go: {\n            ipfsBin: (await import('go-ipfs')).default.path()\n          }\n        }).start()\n        return {\n          ipfsdServer,\n          preloadNode\n        }\n      }\n\n      return {\n        preloadNode\n      }\n    },\n    async after (options, before) {\n      await before.preloadNode.stop()\n      if (options.runner !== 'node') {\n        await before.ipfsdServer.stop()\n      }\n    }\n  },\n  build: {\n    bundlesizeMax: '477KB',\n    config: esbuild\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/CHANGELOG.md",
    "content": "# Change Log\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n### [0.18.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-v0.18.0...ipfs-core-v0.18.1) (2023-05-25)\n\n\n### Bug Fixes\n\n* add deprecation notice to readmes ([#4362](https://www.github.com/ipfs/js-ipfs/issues/4362)) ([7b79c1b](https://www.github.com/ipfs/js-ipfs/commit/7b79c1b8df5c818dc124b346ea28330455732d5c))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-config bumped from ^0.7.0 to ^0.7.1\n    * ipfs-core-types bumped from ^0.14.0 to ^0.14.1\n    * ipfs-core-utils bumped from ^0.18.0 to ^0.18.1\n    * ipfs-http-client bumped from ^60.0.0 to ^60.0.1\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.158.0 to ^0.158.1\n\n## [0.18.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-v0.17.0...ipfs-core-v0.18.0) (2023-01-11)\n\n\n### ⚠ BREAKING CHANGES\n\n* update multiformats to v11.x.x and related depenendcies (#4277)\n\n### Bug Fixes\n\n* allow reading rawLeaves in MFS ([#4282](https://www.github.com/ipfs/js-ipfs/issues/4282)) ([0cfcaf6](https://www.github.com/ipfs/js-ipfs/commit/0cfcaf65998bdc2af0cc29ac48229bb3bc35c5b8))\n* mfs blob import for files larger than 262144b ([#4251](https://www.github.com/ipfs/js-ipfs/issues/4251)) ([6be5906](https://www.github.com/ipfs/js-ipfs/commit/6be59068cc99c517526bfa123ad475ae05fcbaef)), closes [#3601](https://www.github.com/ipfs/js-ipfs/issues/3601) [#3576](https://www.github.com/ipfs/js-ipfs/issues/3576)\n* update multiformats to v11.x.x and related depenendcies ([#4277](https://www.github.com/ipfs/js-ipfs/issues/4277)) ([521c84a](https://www.github.com/ipfs/js-ipfs/commit/521c84a958b04d61702577a5adce28519c1b2a3b))\n* use aegir to publish RCs ([#4284](https://www.github.com/ipfs/js-ipfs/issues/4284)) ([6d90cbf](https://www.github.com/ipfs/js-ipfs/commit/6d90cbf321a7dbf4b1084ba20f0c514dc08d8d0a))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-config bumped from ^0.6.0 to ^0.7.0\n    * ipfs-core-types bumped from ^0.13.0 to ^0.14.0\n    * ipfs-core-utils bumped from ^0.17.0 to ^0.18.0\n    * ipfs-http-client bumped from ^59.0.0 to ^60.0.0\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.157.0 to ^0.158.0\n\n## [0.17.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-v0.16.1...ipfs-core-v0.17.0) (2022-10-24)\n\n\n### ⚠ BREAKING CHANGES\n\n* ipfs is now bundled with libp2p@0.40.x which has different config\n* require IPNS V2 signatures (#4207)\n\n### Features\n\n* upgrade libp2p to 0.40.x ([#4237](https://www.github.com/ipfs/js-ipfs/issues/4237)) ([0cee4a4](https://www.github.com/ipfs/js-ipfs/commit/0cee4a4c55767022584dcbade0b0b9b43326f9c9))\n\n\n### Bug Fixes\n\n* replace slice with subarray for increased performance ([#4210](https://www.github.com/ipfs/js-ipfs/issues/4210)) ([dfc43d4](https://www.github.com/ipfs/js-ipfs/commit/dfc43d4e9be67fdf25553677f469379d966ff806))\n* require IPNS V2 signatures ([#4207](https://www.github.com/ipfs/js-ipfs/issues/4207)) ([d1b0a8a](https://www.github.com/ipfs/js-ipfs/commit/d1b0a8a71073b4ece0dbda5a5405d76dd8d5b358))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-config bumped from ^0.5.1 to ^0.6.0\n    * ipfs-core-types bumped from ^0.12.1 to ^0.13.0\n    * ipfs-core-utils bumped from ^0.16.1 to ^0.17.0\n    * ipfs-http-client bumped from ^58.0.1 to ^59.0.0\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.156.1 to ^0.157.0\n\n### [0.16.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-v0.16.0...ipfs-core-v0.16.1) (2022-09-21)\n\n\n### Bug Fixes\n\n* update @multiformats/multiadd to 11.0.0 ([2a830bf](https://www.github.com/ipfs/js-ipfs/commit/2a830bf58a5929fcce51dede871c99f62192fbda))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-config bumped from ^0.5.0 to ^0.5.1\n    * ipfs-core-types bumped from ^0.12.0 to ^0.12.1\n    * ipfs-core-utils bumped from ^0.16.0 to ^0.16.1\n    * ipfs-http-client bumped from ^58.0.0 to ^58.0.1\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.156.0 to ^0.156.1\n\n## [0.16.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-v0.15.4...ipfs-core-v0.16.0) (2022-09-06)\n\n\n### ⚠ BREAKING CHANGES\n\n* update to libp2p@0.38.x (#4151)\n\n### deps\n\n* update to libp2p@0.38.x ([#4151](https://www.github.com/ipfs/js-ipfs/issues/4151)) ([39dbf70](https://www.github.com/ipfs/js-ipfs/commit/39dbf708ec31b263115e44f420651fa4e056a89e))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-config bumped from ^0.4.0 to ^0.5.0\n    * ipfs-core-types bumped from ^0.11.0 to ^0.12.0\n    * ipfs-core-utils bumped from ^0.15.0 to ^0.16.0\n    * ipfs-http-client bumped from ^57.0.0 to ^58.0.0\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.155.0 to ^0.156.0\n\n### [0.15.4](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-v0.15.3...ipfs-core-v0.15.4) (2022-06-24)\n\n\n### Bug Fixes\n\n* make pubsub message types consistent ([#4145](https://www.github.com/ipfs/js-ipfs/issues/4145)) ([00bd3dd](https://www.github.com/ipfs/js-ipfs/commit/00bd3dd0bca7fc705e5e87272972f586d1f161e8))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-http-client bumped from ^57.0.2 to ^57.0.3\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.155.1 to ^0.155.2\n\n### [0.15.3](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-v0.15.2...ipfs-core-v0.15.3) (2022-06-22)\n\n\n### Bug Fixes\n\n* use default ws filters instead of connecting to everything ([#4142](https://www.github.com/ipfs/js-ipfs/issues/4142)) ([7be50bd](https://www.github.com/ipfs/js-ipfs/commit/7be50bd157b984d4607545bb78d22cd33de933fa)), closes [#4141](https://www.github.com/ipfs/js-ipfs/issues/4141)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-config bumped from ^0.4.0 to ^0.4.1\n    * ipfs-core-types bumped from ^0.11.0 to ^0.11.1\n    * ipfs-core-utils bumped from ^0.15.0 to ^0.15.1\n    * ipfs-http-client bumped from ^57.0.1 to ^57.0.2\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.155.0 to ^0.155.1\n\n### [0.15.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-v0.15.1...ipfs-core-v0.15.2) (2022-06-13)\n\n\n### Bug Fixes\n\n* onMessage assignment should be undefined not null ([#4131](https://www.github.com/ipfs/js-ipfs/issues/4131)) ([129ac77](https://www.github.com/ipfs/js-ipfs/commit/129ac775f1934f8f8e51006c12c6f19d8543954e))\n\n### [0.15.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-v0.15.0...ipfs-core-v0.15.1) (2022-06-01)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-http-client bumped from ^57.0.0 to ^57.0.1\n\n## [0.15.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-v0.14.3...ipfs-core-v0.15.0) (2022-05-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* This module is now ESM only and there return types of some methods have changed\n\n### Features\n\n* update to libp2p 0.37.x ([#4092](https://www.github.com/ipfs/js-ipfs/issues/4092)) ([74aee8b](https://www.github.com/ipfs/js-ipfs/commit/74aee8b3d78f233c3199a3e9a6c0ac628a31a433))\n\n\n### Bug Fixes\n\n* update to latest libp2p interfaces ([#4111](https://www.github.com/ipfs/js-ipfs/issues/4111)) ([4e93dd5](https://www.github.com/ipfs/js-ipfs/commit/4e93dd5d4f4be397c2b1cd8ae5d17e593493e6a9))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-config bumped from ^0.3.2 to ^0.4.0\n    * ipfs-core-types bumped from ^0.10.3 to ^0.11.0\n    * ipfs-core-utils bumped from ^0.14.3 to ^0.15.0\n    * ipfs-http-client bumped from ^56.0.3 to ^57.0.0\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.154.3 to ^0.155.0\n\n### [0.14.3](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-v0.14.2...ipfs-core-v0.14.3) (2022-04-20)\n\n\n### Bug Fixes\n\n* exclude fs from bundle ([#4076](https://www.github.com/ipfs/js-ipfs/issues/4076)) ([6c3cb73](https://www.github.com/ipfs/js-ipfs/commit/6c3cb73db7b46211c88431273f61f04463a4f80d))\n* **rmlink:** fix rmlink to match docs ([#4073](https://www.github.com/ipfs/js-ipfs/issues/4073)) ([1a73160](https://www.github.com/ipfs/js-ipfs/commit/1a73160e5dbe8623458cdf1194158c2f45ff64a6))\n* update car dependency for CARv2 read support ([#4085](https://www.github.com/ipfs/js-ipfs/issues/4085)) ([c367840](https://www.github.com/ipfs/js-ipfs/commit/c367840062e3fc555e696e4fc621651ed1929213))\n* upgrade dep of ipfs-utils ^9.0.2->^9.0.6 ([#4086](https://www.github.com/ipfs/js-ipfs/issues/4086)) ([8f7ce23](https://www.github.com/ipfs/js-ipfs/commit/8f7ce23c18be12bdc52b98bfccbd0a5a2a9c9f7e)), closes [#4080](https://www.github.com/ipfs/js-ipfs/issues/4080)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-config bumped from ^0.3.2 to ^0.3.3\n    * ipfs-core-types bumped from ^0.10.2 to ^0.10.3\n    * ipfs-core-utils bumped from ^0.14.2 to ^0.14.3\n    * ipfs-http-client bumped from ^56.0.2 to ^56.0.3\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.154.2 to ^0.154.3\n\n### [0.14.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-v0.14.1...ipfs-core-v0.14.2) (2022-03-01)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-config bumped from ^0.3.1 to ^0.3.2\n    * ipfs-core-types bumped from ^0.10.1 to ^0.10.2\n    * ipfs-core-utils bumped from ^0.14.1 to ^0.14.2\n    * ipfs-http-client bumped from ^56.0.1 to ^56.0.2\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.154.1 to ^0.154.2\n\n### [0.14.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-v0.14.0...ipfs-core-v0.14.1) (2022-02-06)\n\n\n### Bug Fixes\n\n* **dag:** replace custom dag walk with multiformats/traversal ([#3950](https://www.github.com/ipfs/js-ipfs/issues/3950)) ([596b1f4](https://www.github.com/ipfs/js-ipfs/commit/596b1f48a014083b1736e4ad7e746c652d2583b1))\n* override hashing algorithm when importing files ([#4042](https://www.github.com/ipfs/js-ipfs/issues/4042)) ([709831f](https://www.github.com/ipfs/js-ipfs/commit/709831f61a822d28a6b8e4d6ddc2b659a836079f)), closes [#3952](https://www.github.com/ipfs/js-ipfs/issues/3952)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-config bumped from ^0.3.0 to ^0.3.1\n    * ipfs-core-types bumped from ^0.10.0 to ^0.10.1\n    * ipfs-core-utils bumped from ^0.14.0 to ^0.14.1\n    * ipfs-http-client bumped from ^56.0.0 to ^56.0.1\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.154.0 to ^0.154.1\n\n## [0.14.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-v0.13.0...ipfs-core-v0.14.0) (2022-01-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* peerstore methods are now all async, the repo is migrated to v12\n* node 15+ is required\n\n### Features\n\n* add support for dag-jose codec ([#4028](https://www.github.com/ipfs/js-ipfs/issues/4028)) ([fbe1492](https://www.github.com/ipfs/js-ipfs/commit/fbe1492395ad98e620a872208530a3f8f61535a9))\n* libp2p async peerstore ([#4018](https://www.github.com/ipfs/js-ipfs/issues/4018)) ([a6b201a](https://www.github.com/ipfs/js-ipfs/commit/a6b201af2c3697430ab0ebe002dd573d185f1ac0))\n\n\n### Bug Fixes\n\n* remove abort-controller deps ([#4015](https://www.github.com/ipfs/js-ipfs/issues/4015)) ([902e887](https://www.github.com/ipfs/js-ipfs/commit/902e887e1acac87f607324fa7cb5ad4b14aefcf3))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-config bumped from ^0.2.0 to ^0.3.0\n    * ipfs-core-types bumped from ^0.9.0 to ^0.10.0\n    * ipfs-core-utils bumped from ^0.13.0 to ^0.14.0\n    * ipfs-http-client bumped from ^55.0.0 to ^56.0.0\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.153.0 to ^0.154.0\n\n\n## [0.13.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.12.2...ipfs-core@0.13.0) (2021-12-15)\n\n\n### Bug Fixes\n\n* always close writer so iterator throws on error ([#3980](https://github.com/ipfs/js-ipfs/issues/3980)) ([d147494](https://github.com/ipfs/js-ipfs/commit/d147494f362d38244bbeafbd6e7d76789c7c5020))\n* **pubsub:** multibase in pubsub http rpc ([#3922](https://github.com/ipfs/js-ipfs/issues/3922)) ([6eeaca4](https://github.com/ipfs/js-ipfs/commit/6eeaca452c36fa13be42d704575c577e4ca938f1))\n* return nested value from dag.get ([#3966](https://github.com/ipfs/js-ipfs/issues/3966)) ([45ac973](https://github.com/ipfs/js-ipfs/commit/45ac9730d6484e8324acfbc3579fce052b8452d7)), closes [#3957](https://github.com/ipfs/js-ipfs/issues/3957)\n* use peer store for id ([#3973](https://github.com/ipfs/js-ipfs/issues/3973)) ([adde8c1](https://github.com/ipfs/js-ipfs/commit/adde8c13ba433b81e76033c418607be389fb3d31))\n\n\n### chore\n\n* Bump @ipld/dag-cbor to v7 ([#3977](https://github.com/ipfs/js-ipfs/issues/3977)) ([73476f5](https://github.com/ipfs/js-ipfs/commit/73476f55e39ecfb01eb2b4880637aad658f51bc2))\n\n\n### Features\n\n* dht client ([#3947](https://github.com/ipfs/js-ipfs/issues/3947)) ([62d8ecb](https://github.com/ipfs/js-ipfs/commit/62d8ecbc723e693a2544e69172d99c576d187c23))\n* improve collected metrics ([#3978](https://github.com/ipfs/js-ipfs/issues/3978)) ([33f1034](https://github.com/ipfs/js-ipfs/commit/33f1034a6fc257f1a87de7bb38d876925f61cb5f))\n* update DAG API to match go-ipfs@0.10 changes ([#3917](https://github.com/ipfs/js-ipfs/issues/3917)) ([38c01be](https://github.com/ipfs/js-ipfs/commit/38c01be03b4fd5f401cd9b698cfdb4237d835b01))\n\n\n### BREAKING CHANGES\n\n* **pubsub:** We had to make breaking changes to `pubsub` commands sent over HTTP RPC  to fix data corruption caused by topic names and payload bytes that included `\\n`. More details in https://github.com/ipfs/go-ipfs/issues/7939 and https://github.com/ipfs/go-ipfs/pull/8183\n* On decode of CBOR blocks, `undefined` values will be coerced to `null`\n* `ipfs.dag.put` no longer accepts a `format` arg, it is now `storeCodec` and `inputCodec`.  `'json'` has become `'dag-json'`, `'cbor'` has become `'dag-cbor'` and so on\n* The DHT API has been refactored to return async iterators of query events\n\n\n## [0.12.2](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.12.1...ipfs-core@0.12.2) (2021-11-24)\n\n**Note:** Version bump only for package ipfs-core\n\n\n\n\n\n## [0.12.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.12.0...ipfs-core@0.12.1) (2021-11-19)\n\n\n### Bug Fixes\n\n* pass hasher loader to bitswap ([#3944](https://github.com/ipfs/js-ipfs/issues/3944)) ([f419553](https://github.com/ipfs/js-ipfs/commit/f419553b9dccc0a1172f399c41b766a754a3ac56))\n\n\n\n\n\n## [0.12.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.11.1...ipfs-core@0.12.0) (2021-11-12)\n\n\n### Bug Fixes\n\n* do not accept single items for ipfs.add ([#3900](https://github.com/ipfs/js-ipfs/issues/3900)) ([04e3cf3](https://github.com/ipfs/js-ipfs/commit/04e3cf3f46b585c4644cba70516f375e95361f52))\n* do not lose files when writing files into subshards that contain other subshards ([#3936](https://github.com/ipfs/js-ipfs/issues/3936)) ([8a3ed19](https://github.com/ipfs/js-ipfs/commit/8a3ed19575beaafe5dfd3bce310a548950c148d0)), closes [#3921](https://github.com/ipfs/js-ipfs/issues/3921)\n\n\n### BREAKING CHANGES\n\n* errors will now be thrown if multiple items are passed to `ipfs.add` or single items to `ipfs.addAll` (n.b. you can still pass a list of a single item to `ipfs.addAll`)\n\n\n\n\n\n## [0.11.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.11.0...ipfs-core@0.11.1) (2021-09-28)\n\n**Note:** Version bump only for package ipfs-core\n\n\n\n\n\n## [0.11.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.10.8...ipfs-core@0.11.0) (2021-09-24)\n\n\n### Features\n\n* pull in new globSource ([#3889](https://github.com/ipfs/js-ipfs/issues/3889)) ([be4a542](https://github.com/ipfs/js-ipfs/commit/be4a5428ebc4b05a2edd9a91bf9df6416c1a8c2b))\n* switch to esm ([#3879](https://github.com/ipfs/js-ipfs/issues/3879)) ([9a40109](https://github.com/ipfs/js-ipfs/commit/9a40109632e5b4837eb77a2f57dbc77fbf1fe099))\n\n\n### BREAKING CHANGES\n\n* the globSource api has changed from `globSource(dir, opts)` to `globSource(dir, pattern, opts)`\n* There are no default exports and everything is now dual published as ESM/CJS\n\n\n\n\n\n## [0.10.8](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.10.7...ipfs-core@0.10.8) (2021-09-17)\n\n\n### Bug Fixes\n\n* use Key.asKey instead of instanceOf ([#3877](https://github.com/ipfs/js-ipfs/issues/3877)) ([e3acf9b](https://github.com/ipfs/js-ipfs/commit/e3acf9b67765c166c53f923a9e00430cdf46935b)), closes [#3852](https://github.com/ipfs/js-ipfs/issues/3852)\n\n\n\n\n\n## [0.10.7](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.10.6...ipfs-core@0.10.7) (2021-09-17)\n\n**Note:** Version bump only for package ipfs-core\n\n\n\n\n\n## [0.10.6](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.10.5...ipfs-core@0.10.6) (2021-09-08)\n\n**Note:** Version bump only for package ipfs-core\n\n\n\n\n\n## [0.10.5](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.10.4...ipfs-core@0.10.5) (2021-09-02)\n\n\n### Bug Fixes\n\n* declare types in .ts files ([#3840](https://github.com/ipfs/js-ipfs/issues/3840)) ([eba5fe6](https://github.com/ipfs/js-ipfs/commit/eba5fe6832858107b3e1ae02c99de674622f12b4))\n* remove client-side timeout from http rpc calls ([#3178](https://github.com/ipfs/js-ipfs/issues/3178)) ([f11220e](https://github.com/ipfs/js-ipfs/commit/f11220e00a12afed5ebbbd8b4c5134595aea735d)), closes [#3161](https://github.com/ipfs/js-ipfs/issues/3161)\n* remove use of instanceof for CID class ([#3847](https://github.com/ipfs/js-ipfs/issues/3847)) ([ebbb12d](https://github.com/ipfs/js-ipfs/commit/ebbb12db523c53ce8e4ddae5266cd9acb3504431))\n\n\n\n\n\n## [0.10.4](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.10.3...ipfs-core@0.10.4) (2021-08-25)\n\n**Note:** Version bump only for package ipfs-core\n\n\n\n\n\n## [0.10.3](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.10.1...ipfs-core@0.10.3) (2021-08-17)\n\n**Note:** Version bump only for package ipfs-core\n\n\n\n\n\n## [0.10.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.10.0...ipfs-core@0.10.1) (2021-08-17)\n\n\n### Bug Fixes\n\n* pass correct types to libp2p dht methods ([#3806](https://github.com/ipfs/js-ipfs/issues/3806)) ([5c8da9a](https://github.com/ipfs/js-ipfs/commit/5c8da9a703b8b043ebc670c6c5dcc7df4f687df7)), closes [#3502](https://github.com/ipfs/js-ipfs/issues/3502)\n* pin nanoid version ([#3807](https://github.com/ipfs/js-ipfs/issues/3807)) ([474523a](https://github.com/ipfs/js-ipfs/commit/474523ab8702729f697843d433a7a08baf2d101f))\n* use correct datastores ([#3820](https://github.com/ipfs/js-ipfs/issues/3820)) ([479e09e](https://github.com/ipfs/js-ipfs/commit/479e09e73c936c5770aa83e4d097b62c3987cf6f))\n\n\n\n\n\n## [0.10.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.9.1...ipfs-core@0.10.0) (2021-08-11)\n\n\n### Bug Fixes\n\n* do not write blocks we already have ([#3801](https://github.com/ipfs/js-ipfs/issues/3801)) ([4f532a5](https://github.com/ipfs/js-ipfs/commit/4f532a541dfd9a1ed73ba8eb68ae86e311caeeb3))\n* return rate in/out as number ([#3798](https://github.com/ipfs/js-ipfs/issues/3798)) ([2f3df7a](https://github.com/ipfs/js-ipfs/commit/2f3df7a70fe94d6bdf20947854dc9d0b88cb759a)), closes [#3782](https://github.com/ipfs/js-ipfs/issues/3782)\n\n\n### Features\n\n* ed25519 keys by default ([#3693](https://github.com/ipfs/js-ipfs/issues/3693)) ([33fa734](https://github.com/ipfs/js-ipfs/commit/33fa7341c3baaf0926d887c071cc6fbce5ac49a8))\n* make ipfs.get output tarballs ([#3785](https://github.com/ipfs/js-ipfs/issues/3785)) ([1ad6001](https://github.com/ipfs/js-ipfs/commit/1ad60018d39d5b46c484756631e30e1989fd8eba))\n\n\n### BREAKING CHANGES\n\n* rateIn/rateOut are returned as numbers\n* the output type of `ipfs.get` has changed and the `recursive` option has been removed from `ipfs.ls` since it was not supported everywhere\n\n\n\n\n\n### [0.9.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.9.0...ipfs-core@0.9.1) (2021-07-30)\n\n\n### Bug Fixes\n\n* restore default level-js options ([#3779](https://github.com/ipfs/js-ipfs/issues/3779)) ([8380d71](https://github.com/ipfs/js-ipfs/commit/8380d7160e7205bed9cc4aecfc46882bc97d42c3))\n* typo in 'multiformats' type defs ([#3778](https://github.com/ipfs/js-ipfs/issues/3778)) ([1bf35f8](https://github.com/ipfs/js-ipfs/commit/1bf35f8a1622dea1e88bfbd701205df4f96998b1))\n\n\n\n\n\n## [0.9.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.8.0...ipfs-core@0.9.0) (2021-07-27)\n\n\n### Bug Fixes\n\n* make \"ipfs resolve\" cli command recursive by default ([#3707](https://github.com/ipfs/js-ipfs/issues/3707)) ([399ce36](https://github.com/ipfs/js-ipfs/commit/399ce367a1dbc531b52fe228ee4212008c9a1091)), closes [#3692](https://github.com/ipfs/js-ipfs/issues/3692)\n* root datastore extension ([#3768](https://github.com/ipfs/js-ipfs/issues/3768)) ([62311f8](https://github.com/ipfs/js-ipfs/commit/62311f8ffa90ff5d88a23e2da9fabb0841f1b0f5))\n* round bandwidth stats ([#3735](https://github.com/ipfs/js-ipfs/issues/3735)) ([58fb802](https://github.com/ipfs/js-ipfs/commit/58fb802a05f7ea44ef595f118130952176f7190d)), closes [#3726](https://github.com/ipfs/js-ipfs/issues/3726)\n\n\n### Features\n\n* implement dag import/export ([#3728](https://github.com/ipfs/js-ipfs/issues/3728)) ([700765b](https://github.com/ipfs/js-ipfs/commit/700765be2634fa5d2d71d8b87cf68c9cd328d2c4)), closes [#2953](https://github.com/ipfs/js-ipfs/issues/2953) [#2745](https://github.com/ipfs/js-ipfs/issues/2745)\n* upgrade to the new multiformats ([#3556](https://github.com/ipfs/js-ipfs/issues/3556)) ([d13d15f](https://github.com/ipfs/js-ipfs/commit/d13d15f022a87d04a35f0f7822142f9cb898479c))\n\n\n### BREAKING CHANGES\n\n* resolve is now recursive by default\n\nCo-authored-by: Alex Potsides <alex@achingbrain.net>\n* ipld-formats no longer supported, use multiformat BlockCodecs instead\n\nCo-authored-by: Rod Vagg <rod@vagg.org>\nCo-authored-by: achingbrain <alex@achingbrain.net>\n\n\n\n\n\n## [0.8.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.7.1...ipfs-core@0.8.0) (2021-06-18)\n\n\n### Bug Fixes\n\n* repo auto-migration regression ([#3718](https://github.com/ipfs/js-ipfs/issues/3718)) ([b5470d4](https://github.com/ipfs/js-ipfs/commit/b5470d40ea455069f3f3bd7ab3fb42d7c08926b4)), closes [#3712](https://github.com/ipfs/js-ipfs/issues/3712)\n\n\n### Features\n\n* support v2 ipns signatures ([#3708](https://github.com/ipfs/js-ipfs/issues/3708)) ([ade01d1](https://github.com/ipfs/js-ipfs/commit/ade01d138bb185fda902c0a3f7fa14d5bfd48a5e))\n\n\n\n\n\n### [0.7.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.7.0...ipfs-core@0.7.1) (2021-06-05)\n\n\n### Bug Fixes\n\n* stalling subscription on (node) http-client when daemon is stopped ([#3468](https://github.com/ipfs/js-ipfs/issues/3468)) ([0266abf](https://github.com/ipfs/js-ipfs/commit/0266abf0c4b817636172f78c6e91eb4dd5aad451)), closes [#3465](https://github.com/ipfs/js-ipfs/issues/3465)\n\n\n\n\n\n## [0.7.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.6.1...ipfs-core@0.7.0) (2021-05-26)\n\n\n### Bug Fixes\n\n* remove optional chaining from code that will be transpiled ([#3698](https://github.com/ipfs/js-ipfs/issues/3698)) ([96b3909](https://github.com/ipfs/js-ipfs/commit/96b39099efb051b7a76f0afc2ff9429997c73971))\n\n\n### Features\n\n* allow passing the id of a network peer to ipfs.id ([#3386](https://github.com/ipfs/js-ipfs/issues/3386)) ([00fd709](https://github.com/ipfs/js-ipfs/commit/00fd709a7b71e7cf354ea452ebce460dd7375d34))\n\n\n\n\n\n### [0.6.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.6.0...ipfs-core@0.6.1) (2021-05-11)\n\n\n### Bug Fixes\n\n* ipfs get with raw blocks ([#3683](https://github.com/ipfs/js-ipfs/issues/3683)) ([28235b0](https://github.com/ipfs/js-ipfs/commit/28235b02558c513e1119dfd3d12b622d67546eca)), closes [#3682](https://github.com/ipfs/js-ipfs/issues/3682)\n\n\n\n\n\n## [0.6.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.5.4...ipfs-core@0.6.0) (2021-05-10)\n\n\n### Bug Fixes\n\n* do not republish self key twice ([#3634](https://github.com/ipfs/js-ipfs/issues/3634)) ([8545a76](https://github.com/ipfs/js-ipfs/commit/8545a763daa38aefa71cca514016ba400363830a))\n* fix types ([#3662](https://github.com/ipfs/js-ipfs/issues/3662)) ([0fe8892](https://github.com/ipfs/js-ipfs/commit/0fe8892361180dab53ed3c3b006479b32a792d44))\n* mark ipld options as partial ([#3669](https://github.com/ipfs/js-ipfs/issues/3669)) ([f98af8e](https://github.com/ipfs/js-ipfs/commit/f98af8ed24784929898bb5d33a64dc442c77074d))\n* only accept cid for ipfs.dag.get ([#3675](https://github.com/ipfs/js-ipfs/issues/3675)) ([bb8f8bc](https://github.com/ipfs/js-ipfs/commit/bb8f8bc501ffc1ee0f064ba61ec0bca4015bf6ad)), closes [#3637](https://github.com/ipfs/js-ipfs/issues/3637)\n* update ipfs repo ([#3671](https://github.com/ipfs/js-ipfs/issues/3671)) ([9029ee5](https://github.com/ipfs/js-ipfs/commit/9029ee591fa74ea65c9600f2d249897e933416fa))\n* update types after feedback from ceramic ([#3657](https://github.com/ipfs/js-ipfs/issues/3657)) ([0ddbb1b](https://github.com/ipfs/js-ipfs/commit/0ddbb1b1deb4e40dac3e365d7f98a5f174c2ce8f)), closes [#3640](https://github.com/ipfs/js-ipfs/issues/3640)\n\n\n### chore\n\n* upgrade deps with new typedefs ([#3550](https://github.com/ipfs/js-ipfs/issues/3550)) ([a418a52](https://github.com/ipfs/js-ipfs/commit/a418a521574c878d7aabd0ad2fd8d516908a3756))\n\n\n### Features\n\n* support identity hash in block.get + dag.get ([#3616](https://github.com/ipfs/js-ipfs/issues/3616)) ([28ad9ad](https://github.com/ipfs/js-ipfs/commit/28ad9ad6e50abb89a366ecd6b5301e848f0e9962))\n\n\n### BREAKING CHANGES\n\n* all core api methods now have types, some method signatures have changed, named exports are now used by the http, grpc and ipfs client modules\n\n\n\n\n\n### [0.5.4](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.5.3...ipfs-core@0.5.4) (2021-03-10)\n\n**Note:** Version bump only for package ipfs-core\n\n\n\n\n\n### [0.5.3](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.5.2...ipfs-core@0.5.3) (2021-03-09)\n\n\n### Bug Fixes\n\n* update to new aegir ([#3528](https://github.com/ipfs/js-ipfs/issues/3528)) ([49f7880](https://github.com/ipfs/js-ipfs/commit/49f78807d7e26483bd926b45cc7e0f797d77e41b))\n\n\n\n\n\n### [0.5.2](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.5.1...ipfs-core@0.5.2) (2021-02-08)\n\n\n### Bug Fixes\n\n* ts types after multihashing-async release ([#3529](https://github.com/ipfs/js-ipfs/issues/3529)) ([95b891f](https://github.com/ipfs/js-ipfs/commit/95b891f10e0661f508e8641a1c5d41ea9194c630)), closes [#3527](https://github.com/ipfs/js-ipfs/issues/3527)\n\n\n\n\n\n### [0.5.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.5.0...ipfs-core@0.5.1) (2021-02-02)\n\n**Note:** Version bump only for package ipfs-core\n\n\n\n\n\n## [0.5.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.4.2...ipfs-core@0.5.0) (2021-02-01)\n\n\n### Bug Fixes\n\n* updates webpack example to use v5 ([#3512](https://github.com/ipfs/js-ipfs/issues/3512)) ([c7110db](https://github.com/ipfs/js-ipfs/commit/c7110db71b5c0f0f9f415f31f91b5b228341e13e)), closes [#3511](https://github.com/ipfs/js-ipfs/issues/3511)\n\n\n### chore\n\n* update deps ([#3514](https://github.com/ipfs/js-ipfs/issues/3514)) ([061d77c](https://github.com/ipfs/js-ipfs/commit/061d77cc03f40af5a3bc3590481e1e5836e7f0d8))\n\n\n### Features\n\n* enable upnp nat hole punching ([#3426](https://github.com/ipfs/js-ipfs/issues/3426)) ([65dc161](https://github.com/ipfs/js-ipfs/commit/65dc161feebe154b4a2d1472940dc9e70fbb817f))\n* support  remote pinning services in ipfs-http-client ([#3293](https://github.com/ipfs/js-ipfs/issues/3293)) ([ba240fd](https://github.com/ipfs/js-ipfs/commit/ba240fdf93edc88028315483240d7822a7ca88ed))\n\n\n### BREAKING CHANGES\n\n* ipfs-repo upgrade requires repo migration to v10\n\n\n\n\n\n### [0.4.2](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.4.1...ipfs-core@0.4.2) (2021-01-22)\n\n**Note:** Version bump only for package ipfs-core\n\n\n\n\n\n### [0.4.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.4.0...ipfs-core@0.4.1) (2021-01-20)\n\n**Note:** Version bump only for package ipfs-core\n\n\n\n\n\n## [0.4.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.3.1...ipfs-core@0.4.0) (2021-01-15)\n\n\n### chore\n\n* update libp2p to 0.30 ([#3427](https://github.com/ipfs/js-ipfs/issues/3427)) ([a39e6fb](https://github.com/ipfs/js-ipfs/commit/a39e6fb372bf9e7782462b6a4b7530a3f8c9b3f1))\n\n\n### Features\n\n* add grpc server and client ([#3403](https://github.com/ipfs/js-ipfs/issues/3403)) ([a9027e0](https://github.com/ipfs/js-ipfs/commit/a9027e0ec0cea9a4f34b4f2f52e09abb35237384)), closes [#2519](https://github.com/ipfs/js-ipfs/issues/2519) [#2838](https://github.com/ipfs/js-ipfs/issues/2838) [#2943](https://github.com/ipfs/js-ipfs/issues/2943) [#2854](https://github.com/ipfs/js-ipfs/issues/2854) [#2864](https://github.com/ipfs/js-ipfs/issues/2864)\n* allow passing a http.Agent to the grpc client ([#3477](https://github.com/ipfs/js-ipfs/issues/3477)) ([c5f0bc5](https://github.com/ipfs/js-ipfs/commit/c5f0bc5eeee15369b7d02901035b04184a8608d2)), closes [#3474](https://github.com/ipfs/js-ipfs/issues/3474)\n\n\n### BREAKING CHANGES\n\n* The websocket transport will only dial DNS+WSS addresses - see https://github.com/libp2p/js-libp2p-websockets/releases/tag/v0.15.0\n\nCo-authored-by: Hugo Dias <hugomrdias@gmail.com>\n\n\n\n\n\n### [0.3.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.3.0...ipfs-core@0.3.1) (2020-12-16)\n\n\n### Bug Fixes\n\n* export IPFS type ([#3447](https://github.com/ipfs/js-ipfs/issues/3447)) ([cacbfc6](https://github.com/ipfs/js-ipfs/commit/cacbfc6e87eabee0e2a6df2056ac5cc993690a0d)), closes [#3439](https://github.com/ipfs/js-ipfs/issues/3439)\n* fix ipfs.ls() for a single file object ([#3440](https://github.com/ipfs/js-ipfs/issues/3440)) ([f243dd1](https://github.com/ipfs/js-ipfs/commit/f243dd1c37fcb9786d77d129cd9b238457d18a15))\n* regressions introduced by new releases of CID & multicodec ([#3442](https://github.com/ipfs/js-ipfs/issues/3442)) ([b5152d8](https://github.com/ipfs/js-ipfs/commit/b5152d8cc93ecc8d39fc353ea66d7eaf1661e3c0)), closes [/github.com/multiformats/js-cid/commit/0e11f035c9230e7f6d79c159ace9b80de88cb5eb#diff-25a6634263c1b1f6fc4697a04e2b9904ea4b042a89af59dc93ec1f5d44848a26](https://github.com//github.com/multiformats/js-cid/commit/0e11f035c9230e7f6d79c159ace9b80de88cb5eb/issues/diff-25a6634263c1b1f6fc4697a04e2b9904ea4b042a89af59dc93ec1f5d44848a26)\n\n\n\n\n\n## [0.3.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.2.1...ipfs-core@0.3.0) (2020-11-25)\n\n\n### Features\n\n* announce addresses via config ([#3409](https://github.com/ipfs/js-ipfs/issues/3409)) ([1529da9](https://github.com/ipfs/js-ipfs/commit/1529da9bb2f31eeb525584e67a3e0548b4445721))\n\n\n\n\n\n### [0.2.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.2.0...ipfs-core@0.2.1) (2020-11-16)\n\n\n### Bug Fixes\n\n* ensure correct progress is reported ([#3384](https://github.com/ipfs/js-ipfs/issues/3384)) ([633d870](https://github.com/ipfs/js-ipfs/commit/633d8704f74534542f54536bc6960528214339a2))\n* report ipfs.add progress over http ([#3310](https://github.com/ipfs/js-ipfs/issues/3310)) ([39cad4b](https://github.com/ipfs/js-ipfs/commit/39cad4b76b950ea6a76477fd01f8631b8bd9aa1e))\n\n\n\n\n\n## [0.2.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core@0.1.0...ipfs-core@0.2.0) (2020-11-09)\n\n\n### Bug Fixes\n\n* cache preloaded CIDs ([#3363](https://github.com/ipfs/js-ipfs/issues/3363)) ([b5ea76a](https://github.com/ipfs/js-ipfs/commit/b5ea76ad29082fb40e9fc72ef6223039f1ea3be4)), closes [#3307](https://github.com/ipfs/js-ipfs/issues/3307)\n* typedef resolution & add examples that use types ([#3359](https://github.com/ipfs/js-ipfs/issues/3359)) ([dc2795a](https://github.com/ipfs/js-ipfs/commit/dc2795a4f3b515683d09967ce611bf87d5e67f86)), closes [#3356](https://github.com/ipfs/js-ipfs/issues/3356) [#3358](https://github.com/ipfs/js-ipfs/issues/3358)\n\n\n### Features\n\n* pass file name to add/addAll progress handler ([#3372](https://github.com/ipfs/js-ipfs/issues/3372)) ([69681a7](https://github.com/ipfs/js-ipfs/commit/69681a7d7a8434c11f6f10e370e324f5a3d31042)), closes [ipfs/js-ipfs-unixfs#87](https://github.com/ipfs/js-ipfs-unixfs/issues/87)\n* remove all esoteric ipld formats ([#3360](https://github.com/ipfs/js-ipfs/issues/3360)) ([a542882](https://github.com/ipfs/js-ipfs/commit/a5428820a5b157fbb298b8eb49978e08157beca3)), closes [#3347](https://github.com/ipfs/js-ipfs/issues/3347)\n\n\n### BREAKING CHANGES\n\n* only dag-pb, dag-cbor and raw formats are supported out of the box, any others will need to be configured during node startup.\n\n\n\n\n\n# 0.1.0 (2020-10-28)\n\n\n### Bug Fixes\n\n* files ls should return string ([#3352](https://github.com/ipfs/js-ipfs/issues/3352)) ([16ecc74](https://github.com/ipfs/js-ipfs/commit/16ecc7485dfbb1f0c827c5f804974bb804f3dafd)), closes [#3345](https://github.com/ipfs/js-ipfs/issues/3345) [#2939](https://github.com/ipfs/js-ipfs/issues/2939) [#3330](https://github.com/ipfs/js-ipfs/issues/3330) [#2948](https://github.com/ipfs/js-ipfs/issues/2948)\n* remove buffer export from ipfs-core ([#3348](https://github.com/ipfs/js-ipfs/issues/3348)) ([5cc6dfe](https://github.com/ipfs/js-ipfs/commit/5cc6dfebf96ad9509e7ded175291789e32402eec)), closes [#3312](https://github.com/ipfs/js-ipfs/issues/3312)\n* use fetch in electron renderer and electron-fetch in main ([#3251](https://github.com/ipfs/js-ipfs/issues/3251)) ([639d71f](https://github.com/ipfs/js-ipfs/commit/639d71f7ac8f66d9633e753a2a6be927e14a5af0))\n\n\n### Features\n\n* enable custom formats for dag put and get ([#3347](https://github.com/ipfs/js-ipfs/issues/3347)) ([3250ff4](https://github.com/ipfs/js-ipfs/commit/3250ff453a1d3275cc4ab746f59f9f70abd5cc5f))\n* remove support for SECIO ([#3295](https://github.com/ipfs/js-ipfs/issues/3295)) ([5f5ef7e](https://github.com/ipfs/js-ipfs/commit/5f5ef7ee6cc6dc634cc6adbede0602492490a85d))\n* type check & generate defs from jsdoc ([#3281](https://github.com/ipfs/js-ipfs/issues/3281)) ([bbcaf34](https://github.com/ipfs/js-ipfs/commit/bbcaf34111251b142273a5675f4754ff68bd9fa0))\n\n\n### BREAKING CHANGES\n\n* types returned by `ipfs.files.ls` are now strings, in line with the docs but different to previous behaviour\n\nCo-authored-by: Geoffrey Cohler <g.cohler@computer.org>\n* `Buffer` is no longer exported from core\n* this removes support for SECIO making Noise the only security transport.\n\nCloses https://github.com/ipfs/js-ipfs/issues/3210\n\nCo-authored-by: achingbrain <alex@achingbrain.net>"
  },
  {
    "path": "packages/ipfs-core/CODE_OF_CONDUCT.md",
    "content": "# Contributor Code of Conduct\n\nThe `js-ipfs` project follows the [`IPFS Community Code of Conduct`](https://github.com/ipfs/community/blob/master/code-of-conduct.md)\n"
  },
  {
    "path": "packages/ipfs-core/CONTRIBUTING.md",
    "content": "# Contributing guidelines\n\nIPFS as a project, including js-ipfs and all of its modules, follows the [standard IPFS Community contributing guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md).\n\nWe also adhere to the [IPFS JavaScript Community contributing guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING_JS.md) which provide additional information of how to collaborate and contribute in the JavaScript implementation of IPFS.\n\nWe appreciate your time and attention for going over these. Please open an issue on [ipfs/community](https://github.com/ipfs/community) if you have any question.\n\nThank you.\n"
  },
  {
    "path": "packages/ipfs-core/COPYRIGHT",
    "content": "This project is transitioning from an MIT-only license to a dual MIT/Apache-2.0 license.\nUnless otherwise noted, all code contributed prior to 2019-11-21 and not contributed by\na user listed in [this signoff issue](https://github.com/ipfs/js-ipfs/issues/2624) is\nlicensed under MIT-only. All new contributions (and past contributions since 2019-11-21)\nare licensed under a dual MIT/Apache-2.0 license.\n"
  },
  {
    "path": "packages/ipfs-core/LICENSE",
    "content": "This project is dual licensed under MIT and Apache-2.0.\n\nMIT: https://www.opensource.org/licenses/mit\nApache-2.0: https://www.apache.org/licenses/license-2.0\n"
  },
  {
    "path": "packages/ipfs-core/LICENSE-APACHE",
    "content": "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\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless 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.\n"
  },
  {
    "path": "packages/ipfs-core/LICENSE-MIT",
    "content": "The MIT License (MIT)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "packages/ipfs-core/README.md",
    "content": "> # ⛔️ DEPRECATED: [js-IPFS](https://github.com/ipfs/js-ipfs) has been superseded by [Helia](https://github.com/ipfs/helia)\n>\n> 📚 [Learn more about this deprecation](https://github.com/ipfs/js-ipfs/issues/4336) or [how to migrate](https://github.com/ipfs/helia/wiki/Migrating-from-js-IPFS)\n>\n> ⚠️ If you continue using this repo, please note that security fixes will not be provided\n\n# ipfs-core <!-- omit in toc -->\n\n[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech)\n[![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech)\n[![codecov](https://img.shields.io/codecov/c/github/ipfs/js-ipfs.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipfs)\n[![CI](https://img.shields.io/github/actions/workflow/status/ipfs/js-ipfs/test.yml?branch=master\\&style=flat-square)](https://github.com/ipfs/js-ipfs/actions/workflows/test.yml?query=branch%3Amaster)\n\n> JavaScript implementation of the IPFS specification\n\n## Table of contents <!-- omit in toc -->\n\n- - [Install](#install)\n    - [Browser `<script>` tag](#browser-script-tag)\n- [ipfs-core <!-- omit in toc -->](#ipfs-core----omit-in-toc---)\n  - [Getting Started](#getting-started)\n  - [Next Steps](#next-steps)\n    - [Browser CDN](#browser-cdn)\n    - [Browser bundle](#browser-bundle)\n  - [Want to hack on IPFS?](#want-to-hack-on-ipfs)\n  - [License](#license)\n  - [Contribute](#contribute)\n\n## Install\n\n```console\n$ npm i ipfs-core\n```\n\n### Browser `<script>` tag\n\nLoading this module through a script tag will make it's exports available as `IpfsCore` in the global namespace.\n\n```html\n<script src=\"https://unpkg.com/ipfs-core/dist/index.min.js\"></script>\n```\n\n<p align=\"center\">\n<a href=\"https://js.ipfs.io\" title=\"JS IPFS\">\n  <img src=\"https://ipfs.io/ipfs/Qme6KJdKcp85TYbLxuLV7oQzMiLremD7HMoXLZEmgo6Rnh/js-ipfs-sticker.png\" alt=\"IPFS in JavaScript logo\" width=\"244\" />\n</a>\n</p>\n\n<h3 align=\"center\">The JavaScript implementation of the IPFS protocol</h3>\n\n<p align=\"center\">\n<a href=\"https://github.com/ipfs/js-ipfs/tree/master/packages/interface-ipfs-core\"><img src=\"https://img.shields.io/badge/interface--ipfs--core-API%20Docs-blue.svg\"></a>\n<a href=\"https://travis-ci.com/ipfs/js-ipfs?branch=master\"><img src=\"https://badgen.net/travis/ipfs/js-ipfs?branch=master\" /></a>\n<a href=\"https://codecov.io/gh/ipfs/js-ipfs\"><img src=\"https://badgen.net/codecov/c/github/ipfs/js-ipfs\" /></a>\n<a href=\"https://bundlephobia.com/result?p=ipfs\"><img src=\"https://badgen.net/bundlephobia/minzip/ipfs\"></a>\n<a href=\"https://david-dm.org/ipfs/js-ipfs?path=packages/ipfs-core\"><img src=\"https://david-dm.org/ipfs/js-ipfs.svg?style=flat&path=packages/ipfs-core\" /></a>\n<a href=\"https://github.com/feross/standard\"><img src=\"https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat\"></a>\n<a href=\"\"><img src=\"https://img.shields.io/badge/npm-%3E%3D6.0.0-orange.svg?style=flat\" /></a>\n<a href=\"\"><img src=\"https://img.shields.io/badge/Node.js-%3E%3D10.0.0-orange.svg?style=flat\" /></a>\n<a href=\"https://www.npmjs.com/package/ipfs\"><img src=\"https://img.shields.io/npm/dm/ipfs.svg\" /></a>\n<a href=\"https://www.jsdelivr.com/package/npm/ipfs\"><img src=\"https://data.jsdelivr.com/v1/package/npm/ipfs/badge\"/></a>\n<br>\n</p>\n\n# ipfs-core <!-- omit in toc -->\n\n`ipfs-core` is the implementation of the IPFS Core API written in JavaScript without depending on other languages/implementations. It contains all you need to integrate IPFS into your application.\n\nIf you want to run IPFS as a standalone daemon process, see the [ipfs](https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs) module.\n\n## Getting Started\n\nThe `ipfs-core` package contains all the features of `ipfs` but in a lighter package without the CLI or HTTP servers:\n\n```console\n$ npm install ipfs-core\n```\n\nThen start a node in your app:\n\n```javascript\nimport * as IPFS from 'ipfs-core'\n\nconst ipfs = await IPFS.create()\nconst { cid } = await ipfs.add('Hello world')\nconsole.info(cid)\n// QmXXY5ZxbtuYj6DnfApLiGstzPN7fvSyigrRee3hDWPCaf\n```\n\n## Next Steps\n\n- Look into the [js-ipfs-examples](https://github.com/ipfs-examples/js-ipfs-examples) to learn how to spawn an IPFS node in Node.js and in the Browser\n- Read the [Core API docs](https://github.com/ipfs/js-ipfs/tree/master/docs/core-api) to see what you can do with an IPFS node\n- Head over to <https://proto.school> to take interactive tutorials that cover core IPFS APIs\n- Check out <https://docs.ipfs.io> for tips, how-tos and more\n- See <https://blog.ipfs.io> for news and more\n- Need help? Please ask 'How do I?' questions on <https://discuss.ipfs.io>\n\n### Browser CDN\n\nYou can load IPFS right in your browser by adding the following to your page using the super fast [jsdelivr](https://www.jsdelivr.com) CDN:\n\n```html\n<!-- loading the minified version using jsDelivr -->\n<script src=\"https://cdn.jsdelivr.net/npm/ipfs-core/dist/index.min.js\"></script>\n\n<!-- loading the human-readable (not minified) version jsDelivr -->\n<script src=\"https://cdn.jsdelivr.net/npm/ipfs-core/dist/index.min.js\"></script>\n```\n\nInserting one of the above lines will make an `IpfsCore` object available in the global namespace:\n\n```html\n<script>\nasync function main () {\nconst node = await window.IpfsCore.create()\n// Ready to use!\n// See https://github.com/ipfs/js-ipfs#core-api\n}\nmain()\n</script>\n```\n\n### Browser bundle\n\nLearn how to bundle IPFS into your application with webpack, parceljs and browserify in the [examples](https://github.com/ipfs/js-ipfs/tree/master/examples).\n\n## Want to hack on IPFS?\n\n[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md)\n\nThe IPFS implementation in JavaScript needs your help!  There are a few things you can do right now to help out:\n\nRead the [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md) and [JavaScript Contributing Guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING_JS.md).\n\n- **Check out existing issues** The [issue list](https://github.com/ipfs/js-ipfs/issues) has many that are marked as ['help wanted'](https://github.com/ipfs/js-ipfs/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22help+wanted%22) or ['difficulty:easy'](https://github.com/ipfs/js-ipfs/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Adifficulty%3Aeasy) which make great starting points for development, many of which can be tackled with no prior IPFS knowledge\n- **Perform code reviews** More eyes will help\n  a. speed the project along\n  b. ensure quality, and\n  c. reduce possible future bugs.\n- **Add tests**. There can never be enough tests.\n\n## License\n\nLicensed under either of\n\n- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / <http://www.apache.org/licenses/LICENSE-2.0>)\n- MIT ([LICENSE-MIT](LICENSE-MIT) / <http://opensource.org/licenses/MIT>)\n\n## Contribute\n\nContributions welcome! Please check out [the issues](https://github.com/ipfs/js-ipfs/issues).\n\nAlso 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.\n\nPlease 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).\n\nUnless 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.\n\n[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md)\n"
  },
  {
    "path": "packages/ipfs-core/package.json",
    "content": "{\n  \"name\": \"ipfs-core\",\n  \"version\": \"0.18.1\",\n  \"description\": \"JavaScript implementation of the IPFS specification\",\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"homepage\": \"https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-core#readme\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ipfs/js-ipfs.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ipfs/js-ipfs/issues\"\n  },\n  \"keywords\": [\n    \"IPFS\"\n  ],\n  \"engines\": {\n    \"node\": \">=16.0.0\",\n    \"npm\": \">=7.0.0\"\n  },\n  \"type\": \"module\",\n  \"types\": \"./dist/src/index.d.ts\",\n  \"typesVersions\": {\n    \"*\": {\n      \"*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ],\n      \"src/*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ]\n    }\n  },\n  \"files\": [\n    \"src\",\n    \"dist\",\n    \"!dist/test\",\n    \"!**/*.tsbuildinfo\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/src/index.d.ts\",\n      \"import\": \"./src/index.js\"\n    },\n    \"./config/profiles\": {\n      \"types\": \"./src/components/config/profiles.d.ts\",\n      \"import\": \"./src/components/config/profiles.js\"\n    }\n  },\n  \"eslintConfig\": {\n    \"extends\": \"ipfs\",\n    \"parserOptions\": {\n      \"sourceType\": \"module\"\n    }\n  },\n  \"scripts\": {\n    \"build\": \"aegir build\",\n    \"prepublishOnly\": \"node scripts/update-version.js\",\n    \"lint\": \"aegir lint\",\n    \"test\": \"aegir test\",\n    \"test:node\": \"aegir test -t node --cov\",\n    \"test:chrome\": \"aegir test -t browser --cov\",\n    \"test:chrome-webworker\": \"aegir test -t webworker\",\n    \"test:firefox\": \"aegir test -t browser -- --browser firefox\",\n    \"test:firefox-webworker\": \"aegir test -t webworker -- --browser firefox\",\n    \"test:electron-main\": \"aegir test -t electron-main\",\n    \"test:bootstrapers\": \"IPFS_TEST=bootstrapers aegir test -t browser -f test/bootstrappers.js\",\n    \"clean\": \"aegir clean\",\n    \"dep-check\": \"aegir dep-check -i interface-ipfs-core -i ipfs-core-types --i interface-blockstore -i @libp2p/interface-dht -i @libp2p/interface-keys -i @libp2p/interface-transport -i @libp2p/interfaces\"\n  },\n  \"dependencies\": {\n    \"@chainsafe/libp2p-noise\": \"^11.0.0\",\n    \"@ipld/car\": \"^5.0.0\",\n    \"@ipld/dag-cbor\": \"^9.0.0\",\n    \"@ipld/dag-json\": \"^10.0.0\",\n    \"@ipld/dag-pb\": \"^4.0.0\",\n    \"@libp2p/bootstrap\": \"^6.0.0\",\n    \"@libp2p/crypto\": \"^1.0.7\",\n    \"@libp2p/delegated-content-routing\": \"^4.0.0\",\n    \"@libp2p/delegated-peer-routing\": \"^4.0.0\",\n    \"@libp2p/interface-dht\": \"^2.0.0\",\n    \"@libp2p/interface-keys\": \"^1.0.6\",\n    \"@libp2p/interface-peer-id\": \"^2.0.0\",\n    \"@libp2p/interface-transport\": \"^2.1.0\",\n    \"@libp2p/interfaces\": \"^3.2.0\",\n    \"@libp2p/kad-dht\": \"^7.0.0\",\n    \"@libp2p/logger\": \"^2.0.5\",\n    \"@libp2p/mplex\": \"^7.1.1\",\n    \"@libp2p/peer-id\": \"^2.0.0\",\n    \"@libp2p/peer-id-factory\": \"^2.0.0\",\n    \"@libp2p/record\": \"^3.0.0\",\n    \"@libp2p/websockets\": \"^5.0.0\",\n    \"@multiformats/mafmt\": \"^11.0.2\",\n    \"@multiformats/multiaddr\": \"^11.1.5\",\n    \"@multiformats/multiaddr-to-uri\": \"^9.0.1\",\n    \"@multiformats/murmur3\": \"^2.0.0\",\n    \"any-signal\": \"^3.0.0\",\n    \"array-shuffle\": \"^3.0.0\",\n    \"blockstore-core\": \"^3.0.0\",\n    \"browser-readablestream-to-it\": \"^2.0.0\",\n    \"dag-jose\": \"^4.0.0\",\n    \"datastore-core\": \"^8.0.1\",\n    \"datastore-pubsub\": \"^7.0.0\",\n    \"dlv\": \"^1.1.3\",\n    \"err-code\": \"^3.0.1\",\n    \"hamt-sharding\": \"^3.0.0\",\n    \"hashlru\": \"^2.3.0\",\n    \"interface-blockstore\": \"^4.0.0\",\n    \"interface-datastore\": \"^7.0.0\",\n    \"ipfs-bitswap\": \"^15.0.0\",\n    \"ipfs-core-config\": \"^0.7.1\",\n    \"ipfs-core-types\": \"^0.14.1\",\n    \"ipfs-core-utils\": \"^0.18.1\",\n    \"ipfs-http-client\": \"^60.0.1\",\n    \"ipfs-repo\": \"^17.0.0\",\n    \"ipfs-unixfs\": \"^9.0.0\",\n    \"ipfs-unixfs-exporter\": \"^10.0.0\",\n    \"ipfs-unixfs-importer\": \"^12.0.0\",\n    \"ipfs-utils\": \"^9.0.13\",\n    \"ipns\": \"^5.0.1\",\n    \"is-domain-name\": \"^1.0.1\",\n    \"is-ipfs\": \"^8.0.0\",\n    \"it-drain\": \"^2.0.0\",\n    \"it-filter\": \"^2.0.0\",\n    \"it-first\": \"^2.0.0\",\n    \"it-last\": \"^2.0.0\",\n    \"it-map\": \"^2.0.0\",\n    \"it-merge\": \"^2.0.0\",\n    \"it-parallel\": \"^3.0.0\",\n    \"it-peekable\": \"^2.0.0\",\n    \"it-pipe\": \"^2.0.3\",\n    \"it-pushable\": \"^3.0.0\",\n    \"it-tar\": \"^6.0.0\",\n    \"it-to-buffer\": \"^3.0.0\",\n    \"just-safe-set\": \"^4.0.2\",\n    \"libp2p\": \"^0.42.0\",\n    \"merge-options\": \"^3.0.4\",\n    \"mortice\": \"^3.0.0\",\n    \"multiformats\": \"^11.0.0\",\n    \"pako\": \"^2.0.4\",\n    \"parse-duration\": \"^1.0.0\",\n    \"timeout-abort-controller\": \"^3.0.0\",\n    \"uint8arrays\": \"^4.0.2\"\n  },\n  \"devDependencies\": {\n    \"@chainsafe/libp2p-gossipsub\": \"^6.0.0\",\n    \"@types/dlv\": \"^1.1.2\",\n    \"@types/pako\": \"^2.0.0\",\n    \"@types/rimraf\": \"^3.0.1\",\n    \"aegir\": \"^37.11.0\",\n    \"blockstore-datastore-adapter\": \"^5.0.0\",\n    \"delay\": \"^5.0.0\",\n    \"go-ipfs\": \"^0.12.0\",\n    \"interface-blockstore-tests\": \"^4.0.0\",\n    \"interface-ipfs-core\": \"^0.158.1\",\n    \"ipfsd-ctl\": \"^13.0.0\",\n    \"iso-url\": \"^1.0.0\",\n    \"it-all\": \"^2.0.0\",\n    \"nanoid\": \"^4.0.0\",\n    \"p-defer\": \"^4.0.0\",\n    \"rimraf\": \"^3.0.2\",\n    \"sinon\": \"^15.0.1\"\n  },\n  \"gitHead\": \"\"\n}\n"
  },
  {
    "path": "packages/ipfs-core/scripts/update-version.js",
    "content": "import { readFile, writeFile } from 'fs/promises'\n\nconst pkg = JSON.parse(\n  await readFile(\n    new URL('../package.json', import.meta.url)\n  )\n)\n\nawait writeFile(\n  new URL('../src/version.js', import.meta.url),\n  `\nexport const ipfsCore = '${pkg.version}'\nexport const commit = '${pkg.gitHead || ''}'\nexport const interfaceIpfsCore = '${pkg.devDependencies['interface-ipfs-core']}'\n`\n)\n"
  },
  {
    "path": "packages/ipfs-core/src/block-storage.js",
    "content": "import { BaseBlockstore } from 'blockstore-core'\nimport merge from 'it-merge'\nimport { pushable } from 'it-pushable'\nimport filter from 'it-filter'\n\n/**\n * @typedef {import('interface-blockstore').Blockstore} Blockstore\n * @typedef {import('interface-blockstore').Query} Query\n * @typedef {import('interface-blockstore').KeyQuery} KeyQuery\n * @typedef {import('multiformats/cid').CID} CID\n * @typedef {import('ipfs-bitswap').IPFSBitswap} Bitswap\n * @typedef {import('ipfs-core-types/src/utils').AbortOptions} AbortOptions\n * @typedef {import('ipfs-core-types/src/block').RmOptions} RmOptions\n */\n\n/**\n * BlockStorage is a hybrid block datastore. It stores data in a local\n * datastore and may retrieve data from a remote Exchange.\n * It uses an internal `datastore.Datastore` instance to store values.\n *\n * @implements {Blockstore}\n */\nexport class BlockStorage extends BaseBlockstore {\n  /**\n   * Create a new BlockStorage\n   *\n   * @param {Blockstore} blockstore\n   * @param {Bitswap} bitswap\n   */\n  constructor (blockstore, bitswap) {\n    super()\n\n    this.child = blockstore\n    this.bitswap = bitswap\n  }\n\n  open () {\n    return this.child.open()\n  }\n\n  close () {\n    return this.child.close()\n  }\n\n  unwrap () {\n    return this.child\n  }\n\n  /**\n   * Put a block to the underlying datastore\n   *\n   * @param {CID} cid\n   * @param {Uint8Array} block\n   * @param {AbortOptions} [options]\n   */\n  async put (cid, block, options = {}) {\n    if (await this.has(cid)) {\n      return\n    }\n\n    if (this.bitswap.isStarted()) {\n      await this.bitswap.put(cid, block, options)\n    } else {\n      await this.child.put(cid, block, options)\n    }\n  }\n\n  /**\n   * Put a multiple blocks to the underlying datastore\n   *\n   * @param {AsyncIterable<{ key: CID, value: Uint8Array }> | Iterable<{ key: CID, value: Uint8Array }>} blocks\n   * @param {AbortOptions} [options]\n   */\n  async * putMany (blocks, options = {}) {\n    const missingBlocks = filter(blocks, async ({ key }) => { return !(await this.has(key)) })\n\n    if (this.bitswap.isStarted()) {\n      yield * this.bitswap.putMany(missingBlocks, options)\n    } else {\n      yield * this.child.putMany(missingBlocks, options)\n    }\n  }\n\n  /**\n   * Get a block by cid\n   *\n   * @param {CID} cid\n   * @param {AbortOptions} [options]\n   */\n  async get (cid, options = {}) {\n    if (!(await this.has(cid)) && this.bitswap.isStarted()) {\n      return this.bitswap.get(cid, options)\n    } else {\n      return this.child.get(cid, options)\n    }\n  }\n\n  /**\n   * Get multiple blocks back from an array of cids\n   *\n   * @param {AsyncIterable<CID> | Iterable<CID>} cids\n   * @param {AbortOptions} [options]\n   */\n  async * getMany (cids, options = {}) {\n    const getFromBitswap = pushable({ objectMode: true })\n    const getFromChild = pushable({ objectMode: true })\n\n    Promise.resolve().then(async () => {\n      for await (const cid of cids) {\n        if (!(await this.has(cid)) && this.bitswap.isStarted()) {\n          getFromBitswap.push(cid)\n        } else {\n          getFromChild.push(cid)\n        }\n      }\n\n      getFromBitswap.end()\n      getFromChild.end()\n    })\n\n    yield * merge(\n      this.bitswap.getMany(getFromBitswap, options),\n      this.child.getMany(getFromChild, options)\n    )\n  }\n\n  /**\n   * Delete a block from the blockstore\n   *\n   * @param {CID} cid\n   * @param {RmOptions} [options]\n   */\n  async delete (cid, options) {\n    await this.child.delete(cid, options)\n  }\n\n  /**\n   * Delete multiple blocks from the blockstore\n   *\n   * @param {AsyncIterable<CID> | Iterable<CID>} cids\n   * @param {RmOptions} [options]\n   */\n  async * deleteMany (cids, options) {\n    yield * this.child.deleteMany(cids, options)\n  }\n\n  /**\n   * @param {CID} cid\n   * @param {AbortOptions} options\n   */\n  async has (cid, options = {}) {\n    return this.child.has(cid, options)\n  }\n\n  /**\n   * @param {Query} q\n   * @param {AbortOptions} options\n   */\n  async * query (q, options = {}) {\n    yield * this.child.query(q, options)\n  }\n\n  /**\n   * @param {KeyQuery} q\n   * @param {AbortOptions} options\n   */\n  async * queryKeys (q, options = {}) {\n    yield * this.child.queryKeys(q, options)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/add-all/index.js",
    "content": "import { importer } from 'ipfs-unixfs-importer'\nimport { normaliseInput } from 'ipfs-core-utils/files/normalise-input-multiple'\nimport { parseChunkerString } from './utils.js'\nimport { pipe } from 'it-pipe'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport mergeOpts from 'merge-options'\nconst mergeOptions = mergeOpts.bind({ ignoreUndefined: true })\n\n/**\n * @typedef {import('multiformats/cid').CID} CID\n * @typedef {import('ipfs-unixfs-importer').ImportResult} ImportResult\n * @typedef {import('multiformats/hashes/interface').MultihashHasher} MultihashHasher\n * @typedef {import('ipfs-core-utils/multihashes').Multihashes} Multihashes\n */\n\n/**\n * @template T\n *\n * @typedef {import('it-stream-types').Source<T>} Source<T>\n */\n\n/**\n * @typedef {object} Context\n * @property {import('ipfs-repo').IPFSRepo} repo\n * @property {import('../../types').Preload} preload\n * @property {Multihashes} hashers\n * @property {import('ipfs-core-types/src/root').ShardingOptions} [options]\n * @param {Context} context\n */\nexport function createAddAll ({ repo, preload, hashers, options }) {\n  const isShardingEnabled = options && options.sharding\n\n  /**\n   * @type {import('ipfs-core-types/src/root').API<{}>[\"addAll\"]}\n   */\n  async function * addAll (source, options = {}) {\n    const opts = mergeOptions({\n      shardSplitThreshold: isShardingEnabled ? 1000 : Infinity,\n      strategy: 'balanced'\n    }, options, {\n      ...parseChunkerString(options.chunker)\n    })\n\n    // CID v0 is for multihashes encoded with sha2-256\n    if (opts.hashAlg && opts.hashAlg !== 'sha2-256' && opts.cidVersion !== 1) {\n      opts.cidVersion = 1\n    }\n\n    if (opts.trickle) {\n      opts.strategy = 'trickle'\n    }\n\n    if (opts.strategy === 'trickle') {\n      opts.leafType = 'raw'\n      opts.reduceSingleLeafToSelf = false\n    }\n\n    if (opts.cidVersion > 0 && opts.rawLeaves === undefined) {\n      // if the cid version is 1 or above, use raw leaves as this is\n      // what go does.\n      opts.rawLeaves = true\n    }\n\n    if (opts.hashAlg !== undefined && opts.rawLeaves === undefined) {\n      // if a non-default hash alg has been specified, use raw leaves as this is\n      // what go does.\n      opts.rawLeaves = true\n    }\n\n    delete opts.trickle\n\n    /** @type {Record<string, number>} */\n    const totals = {}\n\n    if (opts.progress) {\n      const prog = opts.progress\n\n      /**\n       * @param {number} bytes\n       * @param {string} path\n       */\n      opts.progress = (bytes, path) => {\n        if (!totals[path]) {\n          totals[path] = 0\n        }\n\n        totals[path] += bytes\n\n        prog(totals[path], path)\n      }\n    }\n\n    /** @type {MultihashHasher | undefined} */\n    let hasher\n\n    if (opts.hashAlg != null) {\n      hasher = await hashers.getHasher(opts.hashAlg)\n    }\n\n    const iterator = pipe(\n      normaliseInput(source),\n      /**\n       * @param {Source<import('ipfs-unixfs-importer').ImportCandidate>} source\n       */\n      source => importer(source, repo.blocks, {\n        ...opts,\n        hasher,\n        pin: false\n      }),\n      transformFile(opts),\n      preloadFile(preload, opts),\n      pinFile(repo, opts)\n    )\n\n    const releaseLock = await repo.gcLock.readLock()\n\n    try {\n      for await (const added of iterator) {\n        const path = added.path ?? added.cid.toString()\n\n        // do not keep file totals around forever\n        delete totals[path]\n\n        yield {\n          ...added,\n          path\n        }\n      }\n    } finally {\n      releaseLock()\n    }\n  }\n\n  return withTimeoutOption(addAll)\n}\n\n/**\n * @param {import('ipfs-core-types/src/root').AddAllOptions} opts\n */\nfunction transformFile (opts) {\n  /**\n   * @param {Source<ImportResult>} source\n   */\n  async function * transformFile (source) {\n    for await (const file of source) {\n      let cid = file.cid\n\n      if (opts.cidVersion === 1) {\n        cid = cid.toV1()\n      }\n\n      let path = file.path ? file.path : cid.toString()\n\n      if (opts.wrapWithDirectory && !file.path) {\n        path = ''\n      }\n\n      yield {\n        path,\n        cid: cid,\n        size: file.size,\n        mode: file.unixfs && file.unixfs.mode,\n        mtime: file.unixfs && file.unixfs.mtime\n      }\n    }\n  }\n\n  return transformFile\n}\n\n/**\n * @param {(cid: CID) => void} preload\n * @param {import('ipfs-core-types/src/root').AddAllOptions} opts\n */\nfunction preloadFile (preload, opts) {\n  /**\n   * @param {Source<ImportResult>} source\n   */\n  async function * maybePreloadFile (source) {\n    for await (const file of source) {\n      const isRootFile = !file.path || opts.wrapWithDirectory\n        ? file.path === ''\n        : !file.path.includes('/')\n\n      const shouldPreload = isRootFile && !opts.onlyHash && opts.preload !== false\n\n      if (shouldPreload) {\n        preload(file.cid)\n      }\n\n      yield file\n    }\n  }\n\n  return maybePreloadFile\n}\n\n/**\n * @param {import('ipfs-repo').IPFSRepo} repo\n * @param {import('ipfs-core-types/src/root').AddAllOptions} opts\n */\nfunction pinFile (repo, opts) {\n  /**\n   * @param {Source<ImportResult>} source\n   */\n  async function * maybePinFile (source) {\n    for await (const file of source) {\n      // Pin a file if it is the root dir of a recursive add or the single file\n      // of a direct add.\n      const isRootDir = !(file.path && file.path.includes('/'))\n      const shouldPin = (opts.pin == null ? true : opts.pin) && isRootDir && !opts.onlyHash\n\n      if (shouldPin) {\n        await repo.pins.pinRecursively(file.cid)\n      }\n\n      yield file\n    }\n  }\n\n  return maybePinFile\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/add-all/utils.js",
    "content": "/**\n * @typedef {object} FixedChunkerOptions\n * @property {'fixed'} chunker\n * @property {number} [maxChunkSize]\n *\n * @typedef {object} RabinChunkerOptions\n * @property {'rabin'} chunker\n * @property {number} avgChunkSize\n * @property {number} [minChunkSize]\n * @property {number} [maxChunkSize]\n *\n * @typedef {FixedChunkerOptions|RabinChunkerOptions} ChunkerOptions\n *\n * Parses chunker string into options used by DAGBuilder in ipfs-unixfs-engine\n *\n *\n * @param  {string} [chunker] - Chunker algorithm supported formats:\n * \"size-{size}\"\n * \"rabin\"\n * \"rabin-{avg}\"\n * \"rabin-{min}-{avg}-{max}\"\n *\n * @returns {ChunkerOptions}   Chunker options for DAGBuilder\n */\nexport const parseChunkerString = (chunker) => {\n  if (!chunker) {\n    return {\n      chunker: 'fixed'\n    }\n  } else if (chunker.startsWith('size-')) {\n    const sizeStr = chunker.split('-')[1]\n    const size = parseInt(sizeStr)\n    if (isNaN(size)) {\n      throw new Error('Chunker parameter size must be an integer')\n    }\n    return {\n      chunker: 'fixed',\n      maxChunkSize: size\n    }\n  } else if (chunker.startsWith('rabin')) {\n    return {\n      chunker: 'rabin',\n      ...parseRabinString(chunker)\n    }\n  } else {\n    throw new Error(`Unrecognized chunker option: ${chunker}`)\n  }\n}\n\n/**\n * @typedef {object} RabinChunkerSettings\n * @property {number} avgChunkSize\n * @property {number} [minChunkSize]\n * @property {number} [maxChunkSize]\n *\n * Parses rabin chunker string\n *\n * @param  {string}   chunker - Chunker algorithm supported formats:\n * \"rabin\"\n * \"rabin-{avg}\"\n * \"rabin-{min}-{avg}-{max}\"\n *\n * @returns {RabinChunkerSettings}   rabin chunker options\n */\nexport const parseRabinString = (chunker) => {\n  const options = {}\n  const parts = chunker.split('-')\n  switch (parts.length) {\n    case 1:\n      options.avgChunkSize = 262144\n      break\n    case 2:\n      options.avgChunkSize = parseChunkSize(parts[1], 'avg')\n      break\n    case 4:\n      options.minChunkSize = parseChunkSize(parts[1], 'min')\n      options.avgChunkSize = parseChunkSize(parts[2], 'avg')\n      options.maxChunkSize = parseChunkSize(parts[3], 'max')\n      break\n    default:\n      throw new Error('Incorrect chunker format (expected \"rabin\" \"rabin-[avg]\" or \"rabin-[min]-[avg]-[max]\"')\n  }\n\n  return options\n}\n\n/**\n *\n * @param {string} str\n * @param {string} name\n * @returns {number}\n */\nexport const parseChunkSize = (str, name) => {\n  const size = parseInt(str)\n  if (isNaN(size)) {\n    throw new Error(`Chunker parameter ${name} must be an integer`)\n  }\n\n  return size\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/add.js",
    "content": "import last from 'it-last'\nimport { normaliseInput } from 'ipfs-core-utils/files/normalise-input-single'\n\n/**\n * @param {object} context\n * @param {import('ipfs-core-types/src/root').API<{}>[\"addAll\"]} context.addAll\n */\nexport function createAdd ({ addAll }) {\n  /**\n   * @type {import('ipfs-core-types/src/root').API<{}>[\"add\"]}\n   */\n  async function add (entry, options = {}) {\n    // @ts-expect-error TODO: https://github.com/ipfs/js-ipfs/issues/3290\n    const result = await last(addAll(normaliseInput(entry), options))\n    // Note this should never happen as `addAll` should yield at least one item\n    // but to satisfy type checker we perfom this check and for good measure\n    // throw an error in case it does happen.\n    if (result == null) {\n      throw Error('Failed to add a file, if you see this please report a bug')\n    }\n\n    return result\n  }\n\n  return add\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/bitswap/index.js",
    "content": "import { createWantlist } from './wantlist.js'\nimport { createWantlistForPeer } from './wantlist-for-peer.js'\nimport { createUnwant } from './unwant.js'\nimport { createStat } from './stat.js'\n\n/**\n * @typedef {import('../../types').NetworkService} NetworkService\n * @typedef {import('@libp2p/interface-peer-id').PeerId} PeerId\n * @typedef {import('multiformats/cid').CID} CID\n * @typedef {import('ipfs-core-types/src/utils').AbortOptions} AbortOptions\n */\n\nexport class BitswapAPI {\n  /**\n   * @param {object} config\n   * @param {NetworkService} config.network\n   */\n  constructor ({ network }) {\n    this.wantlist = createWantlist({ network })\n    this.wantlistForPeer = createWantlistForPeer({ network })\n    this.unwant = createUnwant({ network })\n    this.stat = createStat({ network })\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/bitswap/stat.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @param {object} config\n * @param {import('../../types').NetworkService} config.network\n */\nexport function createStat ({ network }) {\n  /**\n   * @type {import('ipfs-core-types/src/bitswap').API<{}>[\"stat\"]}\n   */\n  async function stat (options = {}) {\n    /** @type {import('ipfs-bitswap').IPFSBitswap} */\n    const bitswap = (await network.use(options)).bitswap\n    const snapshot = bitswap.stat().snapshot\n\n    return {\n      provideBufLen: parseInt(snapshot.providesBufferLength.toString()),\n      blocksReceived: BigInt(snapshot.blocksReceived.toString()),\n      wantlist: Array.from(bitswap.getWantlist()).map(e => e[1].cid),\n      peers: bitswap.peers(),\n      dupBlksReceived: BigInt(snapshot.dupBlksReceived.toString()),\n      dupDataReceived: BigInt(snapshot.dupDataReceived.toString()),\n      dataReceived: BigInt(snapshot.dataReceived.toString()),\n      blocksSent: BigInt(snapshot.blocksSent.toString()),\n      dataSent: BigInt(snapshot.dataSent.toString())\n    }\n  }\n\n  return withTimeoutOption(stat)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/bitswap/unwant.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @param {object} config\n * @param {import('../../types').NetworkService} config.network\n */\nexport function createUnwant ({ network }) {\n  /**\n   * @type {import('ipfs-core-types/src/bitswap').API<{}>[\"unwant\"]}\n   */\n  async function unwant (cids, options = {}) {\n    const { bitswap } = await network.use(options)\n\n    if (!Array.isArray(cids)) {\n      cids = [cids]\n    }\n\n    return bitswap.unwant(cids)\n  }\n\n  return withTimeoutOption(unwant)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/bitswap/wantlist-for-peer.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @param {object} config\n * @param {import('../../types').NetworkService} config.network\n */\nexport function createWantlistForPeer ({ network }) {\n  /**\n   * @type {import('ipfs-core-types/src/bitswap').API<{}>[\"wantlistForPeer\"]}\n   */\n  async function wantlistForPeer (peerId, options = {}) {\n    const { bitswap } = await network.use(options)\n    const list = bitswap.wantlistForPeer(peerId)\n\n    return Array.from(list).map(e => e[1].cid)\n  }\n\n  return withTimeoutOption(wantlistForPeer)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/bitswap/wantlist.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @param {object} config\n * @param {import('../../types').NetworkService} config.network\n */\nexport function createWantlist ({ network }) {\n  /**\n   * @type {import('ipfs-core-types/src/bitswap').API<{}>[\"wantlist\"]}\n   */\n  async function wantlist (options = {}) {\n    const { bitswap } = await network.use(options)\n    const list = bitswap.getWantlist()\n\n    return Array.from(list).map(e => e[1].cid)\n  }\n\n  return withTimeoutOption(wantlist)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/block/get.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @param {object} config\n * @param {import('../../types').Preload} config.preload\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n */\nexport function createGet ({ preload, repo }) {\n  /**\n   * @type {import('ipfs-core-types/src/block').API<{}>[\"get\"]}\n   */\n  async function get (cid, options = {}) { // eslint-disable-line require-await\n    if (options.preload !== false) {\n      preload(cid)\n    }\n\n    return repo.blocks.get(cid, options)\n  }\n\n  return withTimeoutOption(get)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/block/index.js",
    "content": "import { createGet } from './get.js'\nimport { createPut } from './put.js'\nimport { createRm } from './rm.js'\nimport { createStat } from './stat.js'\n\n/**\n * @typedef {import('../../types').Preload} Preload\n */\n\nexport class BlockAPI {\n  /**\n   * @param {object} config\n   * @param {import('ipfs-core-utils/src/multihashes').Multihashes} config.hashers\n   * @param {import('ipfs-core-utils/src/multicodecs').Multicodecs} config.codecs\n   * @param {Preload} config.preload\n   * @param {import('ipfs-repo').IPFSRepo} config.repo\n   */\n  constructor ({ codecs, hashers, preload, repo }) {\n    this.get = createGet({ preload, repo })\n    this.put = createPut({ codecs, hashers, preload, repo })\n    this.rm = createRm({ repo })\n    this.stat = createStat({ preload, repo })\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/block/put.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @typedef {import('multiformats/cid').Version} CIDVersion\n */\n\n/**\n * @param {object} config\n * @param {import('ipfs-core-utils/multicodecs').Multicodecs} config.codecs\n * @param {import('ipfs-core-utils/multihashes').Multihashes} config.hashers\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n * @param {import('../../types').Preload} config.preload\n *\n */\nexport function createPut ({ codecs, hashers, repo, preload }) {\n  /**\n   * @type {import('ipfs-core-types/src/block').API<{}>[\"put\"]}\n   */\n  async function put (block, options = {}) {\n    const release = options.pin ? await repo.gcLock.readLock() : null\n\n    try {\n      const cidVersion = options.version != null ? options.version : 0\n      const codecName = options.format || (cidVersion === 0 ? 'dag-pb' : 'raw')\n\n      const hasher = await hashers.getHasher(options.mhtype || 'sha2-256')\n      const hash = await hasher.digest(block)\n      const codec = await codecs.getCodec(codecName)\n      const cid = CID.create(cidVersion, codec.code, hash)\n\n      await repo.blocks.put(cid, block, {\n        signal: options.signal\n      })\n\n      if (options.preload !== false) {\n        preload(cid)\n      }\n\n      if (options.pin === true) {\n        await repo.pins.pinRecursively(cid, {\n          signal: options.signal\n        })\n      }\n\n      return cid\n    } finally {\n      if (release) {\n        release()\n      }\n    }\n  }\n\n  return withTimeoutOption(put)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/block/rm.js",
    "content": "import errCode from 'err-code'\nimport parallel from 'it-parallel'\nimport map from 'it-map'\nimport filter from 'it-filter'\nimport { pipe } from 'it-pipe'\nimport { cleanCid } from './utils.js'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\nconst BLOCK_RM_CONCURRENCY = 8\n\n/**\n * @param {object} config\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n */\nexport function createRm ({ repo }) {\n  /**\n   * @type {import('ipfs-core-types/src/block').API<{}>[\"rm\"]}\n   */\n  async function * rm (cids, options = {}) {\n    if (!Array.isArray(cids)) {\n      cids = [cids]\n    }\n\n    // We need to take a write lock here to ensure that adding and removing\n    // blocks are exclusive operations\n    const release = await repo.gcLock.writeLock()\n\n    try {\n      yield * pipe(\n        cids,\n        source => map(source, cid => {\n          return async () => {\n            cid = cleanCid(cid)\n\n            /** @type {import('ipfs-core-types/src/block').RmResult} */\n            const result = { cid }\n\n            try {\n              const has = await repo.blocks.has(cid)\n\n              if (!has) {\n                throw errCode(new Error('block not found'), 'ERR_BLOCK_NOT_FOUND')\n              }\n\n              await repo.blocks.delete(cid)\n            } catch (/** @type {any} */ err) {\n              if (!options.force) {\n                err.message = `cannot remove ${cid}: ${err.message}`\n                result.error = err\n              }\n            }\n\n            return result\n          }\n        }),\n        source => parallel(source, { concurrency: BLOCK_RM_CONCURRENCY }),\n        source => filter(source, () => !options.quiet)\n      )\n    } finally {\n      release()\n    }\n  }\n\n  return withTimeoutOption(rm)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/block/stat.js",
    "content": "import { cleanCid } from './utils.js'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @param {object} config\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n * @param {import('../../types').Preload} config.preload\n */\n\nexport function createStat ({ repo, preload }) {\n  /**\n   * @type {import('ipfs-core-types/src/block').API<{}>[\"stat\"]}\n   */\n  async function stat (cid, options = {}) {\n    cid = cleanCid(cid)\n\n    if (options.preload !== false) {\n      preload(cid)\n    }\n\n    const block = await repo.blocks.get(cid)\n\n    return { cid, size: block.length }\n  }\n\n  return withTimeoutOption(stat)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/block/utils.js",
    "content": "import { CID } from 'multiformats/cid'\n\n/**\n * @param {string|Uint8Array|CID} cid\n */\nexport function cleanCid (cid) {\n  if (cid instanceof Uint8Array) {\n    return CID.decode(cid)\n  }\n\n  return CID.parse(cid.toString())\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/bootstrap/add.js",
    "content": "import { isValidMultiaddr } from './utils.js'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @param {object} config\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n */\nexport function createAdd ({ repo }) {\n  /**\n   * @type {import('ipfs-core-types/src/bootstrap').API<{}>[\"add\"]}\n   */\n  async function add (multiaddr, options = {}) {\n    if (!isValidMultiaddr(multiaddr)) {\n      throw new Error(`${multiaddr} is not a valid Multiaddr`)\n    }\n\n    const config = await repo.config.getAll(options)\n    const boostrappers = config.Bootstrap || []\n    boostrappers.push(multiaddr.toString())\n\n    config.Bootstrap = Array.from(\n      new Set(boostrappers)\n    ).sort((a, b) => a.localeCompare(b))\n\n    await repo.config.replace(config)\n\n    return {\n      Peers: [multiaddr]\n    }\n  }\n\n  return withTimeoutOption(add)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/bootstrap/clear.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport { multiaddr } from '@multiformats/multiaddr'\n\n/**\n * @param {object} config\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n */\nexport function createClear ({ repo }) {\n  /**\n   * @type {import('ipfs-core-types/src/bootstrap').API<{}>[\"clear\"]}\n   */\n  async function clear (options = {}) {\n    const config = await repo.config.getAll(options)\n    const removed = config.Bootstrap || []\n    config.Bootstrap = []\n\n    await repo.config.replace(config)\n\n    return { Peers: removed.map(ma => multiaddr(ma)) }\n  }\n\n  return withTimeoutOption(clear)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/bootstrap/index.js",
    "content": "import { createAdd } from './add.js'\nimport { createClear } from './clear.js'\nimport { createList } from './list.js'\nimport { createReset } from './reset.js'\nimport { createRm } from './rm.js'\nexport class BootstrapAPI {\n  /**\n   * @param {object} config\n   * @param {import('ipfs-repo').IPFSRepo} config.repo\n   */\n  constructor ({ repo }) {\n    this.add = createAdd({ repo })\n    this.list = createList({ repo })\n    this.rm = createRm({ repo })\n    this.clear = createClear({ repo })\n    this.reset = createReset({ repo })\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/bootstrap/list.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport { multiaddr } from '@multiformats/multiaddr'\n\n/**\n * @param {object} config\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n */\nexport function createList ({ repo }) {\n  /**\n   * @type {import('ipfs-core-types/src/bootstrap').API<{}>[\"list\"]}\n   */\n  async function list (options = {}) {\n    /** @type {string[]|null} */\n    const peers = (await repo.config.get('Bootstrap', options))\n    return { Peers: (peers || []).map(ma => multiaddr(ma)) }\n  }\n\n  return withTimeoutOption(list)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/bootstrap/reset.js",
    "content": "import defaultConfig from 'ipfs-core-config/config'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport { multiaddr } from '@multiformats/multiaddr'\n\n/**\n * @param {object} config\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n */\nexport function createReset ({ repo }) {\n  /**\n   * @type {import('ipfs-core-types/src/bootstrap').API<{}>[\"reset\"]}\n   */\n  async function reset (options = {}) {\n    const config = await repo.config.getAll(options)\n    config.Bootstrap = defaultConfig().Bootstrap\n\n    await repo.config.replace(config)\n\n    return {\n      Peers: defaultConfig().Bootstrap.map(ma => multiaddr(ma))\n    }\n  }\n\n  return withTimeoutOption(reset)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/bootstrap/rm.js",
    "content": "import { isValidMultiaddr } from './utils.js'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @param {object} config\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n */\nexport function createRm ({ repo }) {\n  /**\n   * @type {import('ipfs-core-types/src/bootstrap').API<{}>[\"rm\"]}\n   */\n  async function rm (multiaddr, options = {}) {\n    if (!isValidMultiaddr(multiaddr)) {\n      throw new Error(`${multiaddr} is not a valid Multiaddr`)\n    }\n\n    const config = await repo.config.getAll(options)\n    config.Bootstrap = (config.Bootstrap || []).filter(ma => ma.toString() !== multiaddr.toString())\n\n    await repo.config.replace(config)\n\n    return { Peers: [multiaddr] }\n  }\n\n  return withTimeoutOption(rm)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/bootstrap/utils.js",
    "content": "import { IPFS } from '@multiformats/mafmt'\n\n/**\n * @param {any} ma\n */\nexport function isValidMultiaddr (ma) {\n  try {\n    return IPFS.matches(ma)\n  } catch (/** @type {any} */ err) {\n    return false\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/cat.js",
    "content": "import { exporter } from 'ipfs-unixfs-exporter'\nimport { normalizeCidPath } from '../utils.js'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport { CID } from 'multiformats/cid'\n\n/**\n * @typedef {object} Context\n * @property {import('ipfs-repo').IPFSRepo} repo\n * @property {import('../types').Preload} preload\n *\n * @param {Context} context\n */\nexport function createCat ({ repo, preload }) {\n  /**\n   * @type {import('ipfs-core-types/src/root').API<{}>[\"cat\"]}\n   */\n  async function * cat (ipfsPath, options = {}) {\n    ipfsPath = normalizeCidPath(ipfsPath)\n\n    if (options.preload !== false) {\n      const pathComponents = ipfsPath.split('/')\n      preload(CID.parse(pathComponents[0]))\n    }\n\n    const file = await exporter(ipfsPath, repo.blocks, options)\n\n    // File may not have unixfs prop if small & imported with rawLeaves true\n    if (file.type === 'directory') {\n      throw new Error('this dag node is a directory')\n    }\n\n    if (!file.content) {\n      throw new Error('this dag node has no content')\n    }\n\n    yield * file.content(options)\n  }\n\n  return withTimeoutOption(cat)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/config/index.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport { logger } from '@libp2p/logger'\nimport { profiles } from './profiles.js'\n\nconst log = logger('ipfs:core:config')\n\n/**\n * @param {object} config\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n */\nexport function createConfig ({ repo }) {\n  return {\n    getAll: withTimeoutOption(getAll),\n    get: withTimeoutOption(get),\n    set: withTimeoutOption(set),\n    replace: withTimeoutOption(replace),\n    profiles: {\n      apply: withTimeoutOption(applyProfile),\n      list: withTimeoutOption(listProfiles)\n    }\n  }\n\n  /**\n   * @type {import('ipfs-core-types/src/config').API<{}>[\"getAll\"]}\n   */\n  async function getAll (options = {}) { // eslint-disable-line require-await\n    return repo.config.getAll(options)\n  }\n\n  /**\n   * @type {import('ipfs-core-types/src/config').API<{}>[\"get\"]}\n   */\n  async function get (key, options) { // eslint-disable-line require-await\n    if (!key) {\n      return Promise.reject(new Error('key argument is required'))\n    }\n\n    return repo.config.get(key, options)\n  }\n\n  /**\n   * @type {import('ipfs-core-types/src/config').API<{}>[\"set\"]}\n   */\n  async function set (key, value, options) { // eslint-disable-line require-await\n    return repo.config.set(key, value, options)\n  }\n\n  /**\n   * @type {import('ipfs-core-types/src/config').API<{}>[\"replace\"]}\n   */\n  async function replace (value, options) { // eslint-disable-line require-await\n    return repo.config.replace(value, options)\n  }\n\n  /**\n   * @type {import('ipfs-core-types/src/config/profiles').API<{}>[\"apply\"]}\n   */\n  async function applyProfile (profileName, options = { dryRun: false }) {\n    const { dryRun } = options\n\n    const profile = profiles[profileName]\n\n    if (!profile) {\n      throw new Error(`No profile with name '${profileName}' exists`)\n    }\n\n    try {\n      const oldCfg = await repo.config.getAll(options)\n      let newCfg = JSON.parse(JSON.stringify(oldCfg)) // clone\n      newCfg = profile.transform(newCfg)\n\n      if (!dryRun) {\n        await repo.config.replace(newCfg, options)\n      }\n\n      // Scrub private key from output\n      // @ts-expect-error `oldCfg.Identity` maybe undefined\n      delete oldCfg.Identity.PrivKey\n      delete newCfg.Identity.PrivKey\n\n      return { original: oldCfg, updated: newCfg }\n    } catch (/** @type {any} */ err) {\n      log(err)\n\n      throw new Error(`Could not apply profile '${profileName}' to config: ${err.message}`)\n    }\n  }\n}\n\n/**\n * @type {import('ipfs-core-types/src/config/profiles').API<{}>[\"list\"]}\n */\nasync function listProfiles (_options) { // eslint-disable-line require-await\n  return Object.keys(profiles).map(name => ({\n    name,\n    description: profiles[name].description\n  }))\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/config/profiles.js",
    "content": "import set from 'just-safe-set'\nimport getDefaultConfig from 'ipfs-core-config/config'\n\n/**\n * @typedef {import('ipfs-core-types/src/config').Config} Config\n *\n * @typedef {object} Transformer\n * @property {string} description\n * @property {(config: Config) => Config} transform\n */\n\n/**\n * @type {Record<string, Transformer>}\n */\nexport const profiles = {\n  server: {\n    description: 'Recommended for nodes with public IPv4 address (servers, VPSes, etc.), disables host and content discovery and UPnP in local networks.',\n    transform: (config) => {\n      set(config, 'Discovery.MDNS.Enabled', false)\n      set(config, 'Discovery.webRTCStar.Enabled', false)\n      config.Swarm = {\n        ...(config.Swarm || {}),\n        DisableNatPortMap: true\n      }\n\n      return config\n    }\n  },\n  'local-discovery': {\n    description: 'Sets default values to fields affected by `server` profile, enables discovery and UPnP in local networks.',\n    transform: (config) => {\n      set(config, 'Discovery.MDNS.Enabled', true)\n      set(config, 'Discovery.webRTCStar.Enabled', true)\n      set(config, 'Swarm', {\n        ...(config.Swarm || {}),\n        DisableNatPortMap: false\n      })\n\n      return config\n    }\n  },\n  test: {\n    description: 'Reduces external interference, useful for running ipfs in test environments. Note that with these settings node won\\'t be able to talk to the rest of the network without manual bootstrap.',\n    transform: (config) => {\n      const defaultConfig = getDefaultConfig()\n\n      set(config, 'Addresses.API', defaultConfig.Addresses.API ? '/ip4/127.0.0.1/tcp/0' : '')\n      set(config, 'Addresses.Gateway', defaultConfig.Addresses.Gateway ? '/ip4/127.0.0.1/tcp/0' : '')\n      set(config, 'Addresses.Swarm', defaultConfig.Addresses.Swarm.length ? ['/ip4/127.0.0.1/tcp/0'] : [])\n      set(config, 'Addresses.Delegates', [])\n      set(config, 'Bootstrap', [])\n      set(config, 'Discovery.MDNS.Enabled', false)\n      set(config, 'Discovery.webRTCStar.Enabled', false)\n      set(config, 'Swarm', {\n        ...(config.Swarm || {}),\n        DisableNatPortMap: true\n      })\n\n      return config\n    }\n  },\n  'default-networking': {\n    description: 'Restores default network settings. Inverse profile of the `test` profile.',\n    transform: (config) => {\n      const defaultConfig = getDefaultConfig()\n\n      set(config, 'Addresses.API', defaultConfig.Addresses.API)\n      set(config, 'Addresses.Gateway', defaultConfig.Addresses.Gateway)\n      set(config, 'Addresses.Swarm', defaultConfig.Addresses.Swarm)\n      set(config, 'Addresses.Delegates', defaultConfig.Addresses.Delegates)\n      set(config, 'Bootstrap', defaultConfig.Bootstrap)\n      set(config, 'Discovery.MDNS.Enabled', defaultConfig.Discovery.MDNS.Enabled)\n      set(config, 'Discovery.webRTCStar.Enabled', defaultConfig.Discovery.webRTCStar.Enabled)\n      set(config, 'Swarm', {\n        ...(config.Swarm || {}),\n        DisableNatPortMap: false\n      })\n\n      return config\n    }\n  },\n  lowpower: {\n    description: 'Reduces daemon overhead on the system. May affect node functionality,performance of content discovery and data fetching may be degraded. Recommended for low power systems.',\n    transform: (config) => {\n      const Swarm = config.Swarm || {}\n      const ConnMgr = Swarm.ConnMgr || {}\n      ConnMgr.LowWater = 20\n      ConnMgr.HighWater = 40\n\n      Swarm.ConnMgr = ConnMgr\n      config.Swarm = Swarm\n\n      return config\n    }\n  },\n  'default-power': {\n    description: 'Inverse of \"lowpower\" profile.',\n    transform: (config) => {\n      const defaultConfig = getDefaultConfig()\n\n      config.Swarm = defaultConfig.Swarm\n\n      return config\n    }\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/dag/export.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { createUnsafe } from 'multiformats/block'\nimport { CarWriter } from '@ipld/car/writer'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport { logger } from '@libp2p/logger'\nimport * as raw from 'multiformats/codecs/raw'\nimport * as json from 'multiformats/codecs/json'\nimport { walk } from 'multiformats/traversal'\n\nconst log = logger('ipfs:components:dag:import')\n\n// blocks that we're OK with not inspecting for links\n/** @type {number[]} */\nconst NO_LINKS_CODECS = [\n  raw.code, // raw\n  json.code // JSON\n]\n\n/**\n * @typedef {import('../../types').Preload} Preload\n * @typedef {import('ipfs-repo').IPFSRepo} IPFSRepo\n * @typedef {import('@ipld/car/api').BlockWriter} BlockWriter\n * @typedef {import('ipfs-core-types/src/utils').AbortOptions} AbortOptions\n */\n\n/**\n * @template T\n * @template C\n * @template A\n * @template V\n * @typedef {import('multiformats/block').Block<T, C, A, V>} Block\n */\n\n/**\n * @param {object} config\n * @param {IPFSRepo} config.repo\n * @param {Preload} config.preload\n * @param {import('ipfs-core-utils/multicodecs').Multicodecs} config.codecs\n */\nexport function createExport ({ repo, preload, codecs }) {\n  /**\n   * @type {import('ipfs-core-types/src/dag').API<{}>[\"export\"]}\n   */\n  async function * dagExport (root, options = {}) {\n    if (options.preload !== false) {\n      preload(root)\n    }\n\n    const cid = CID.asCID(root)\n    if (!cid) {\n      throw new Error(`Unexpected error converting CID type: ${root}`)\n    }\n\n    log(`Exporting ${cid} as car`)\n    const { writer, out } = await CarWriter.create([cid])\n\n    // we need to write with one async channel and send the CarWriter output\n    // with another to the caller, but if the write causes an error we capture\n    // that and make sure it gets propagated\n    /** @type {Error|null} */\n    let err = null\n    ;(async () => {\n      try {\n        const load = makeLoader(repo, writer, {\n          signal: options.signal,\n          timeout: options.timeout\n        }, codecs)\n        await walk({ cid, load })\n      } catch (/** @type {any} */ e) {\n        err = e\n      } finally {\n        writer.close()\n      }\n    })()\n\n    for await (const chunk of out) {\n      if (err) {\n        break\n      }\n      yield chunk\n    }\n    if (err) {\n      throw err\n    }\n  }\n\n  return withTimeoutOption(dagExport)\n}\n\n/**\n * @param {IPFSRepo} repo\n * @param {BlockWriter} writer\n * @param {AbortOptions} options\n * @param {import('ipfs-core-utils/multicodecs').Multicodecs} codecs\n * @returns {(cid:CID)=>Promise<ReturnType<createUnsafe>|null>}\n */\nfunction makeLoader (repo, writer, options, codecs) {\n  return async (cid) => {\n    const codec = await codecs.getCodec(cid.code)\n\n    if (!codec) {\n      throw new Error(`Can't decode links in block with codec 0x${cid.code.toString(16)} to form complete DAG`)\n    }\n\n    const bytes = await repo.blocks.get(cid, options)\n\n    log(`Adding block ${cid} to car`)\n    await writer.put({ cid, bytes })\n\n    if (NO_LINKS_CODECS.includes(cid.code)) {\n      return null // skip this block, no need to look inside\n    }\n\n    return createUnsafe({ bytes, cid, codec })\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/dag/get.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport first from 'it-first'\nimport last from 'it-last'\nimport { resolve } from '../../utils.js'\nimport errCode from 'err-code'\n\n/**\n * @param {object} config\n * @param {import('ipfs-core-utils/multicodecs').Multicodecs} config.codecs\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n * @param {import('../../types').Preload} config.preload\n */\nexport function createGet ({ codecs, repo, preload }) {\n  /**\n   * @type {import('ipfs-core-types/src/dag').API<{}>[\"get\"]}\n   */\n  const get = async function get (cid, options = {}) {\n    if (options.preload !== false) {\n      preload(cid)\n    }\n\n    if (options.path) {\n      const entry = options.localResolve\n        ? await first(resolve(cid, options.path, codecs, repo, options))\n        : await last(resolve(cid, options.path, codecs, repo, options))\n      /** @type {import('ipfs-core-types/src/dag').GetResult | undefined} - first and last will return undefined when empty */\n      const result = (entry)\n\n      if (!result) {\n        throw errCode(new Error('Not found'), 'ERR_NOT_FOUND')\n      }\n\n      return result\n    }\n\n    const codec = await codecs.getCodec(cid.code)\n    const block = await repo.blocks.get(cid, options)\n    const node = codec.decode(block)\n\n    return {\n      value: node,\n      remainderPath: ''\n    }\n  }\n\n  return withTimeoutOption(get)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/dag/import.js",
    "content": "import { CarBlockIterator } from '@ipld/car/iterator'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport itPeekable from 'it-peekable'\nimport drain from 'it-drain'\nimport map from 'it-map'\nimport { logger } from '@libp2p/logger'\nconst log = logger('ipfs:components:dag:import')\n\n/**\n * @typedef {import('multiformats/cid').CID} CID\n * @typedef {import('ipfs-repo').IPFSRepo} IPFSRepo\n * @typedef {import('ipfs-core-types/src/utils').AbortOptions} AbortOptions\n * @typedef {import('ipfs-core-types/src/dag/').ImportRootStatus} RootStatus\n */\n\n/**\n * @param {object} config\n * @param {IPFSRepo} config.repo\n */\nexport function createImport ({ repo }) {\n  /**\n   * @type {import('ipfs-core-types/src/dag').API<{}>[\"import\"]}\n   */\n  async function * dagImport (sources, options = {}) {\n    const release = await repo.gcLock.readLock()\n\n    try {\n      const abortOptions = { signal: options.signal, timeout: options.timeout }\n      const peekable = itPeekable(sources)\n\n      const { value, done } = await peekable.peek()\n\n      if (done) {\n        return\n      }\n\n      if (value) {\n        // @ts-expect-error\n        peekable.push(value)\n      }\n\n      /**\n       * @type {AsyncIterable<AsyncIterable<Uint8Array>> | Iterable<AsyncIterable<Uint8Array>>}\n       */\n      let cars\n\n      if (value instanceof Uint8Array) {\n        // @ts-expect-error\n        cars = [peekable]\n      } else {\n        // @ts-expect-error\n        cars = peekable\n      }\n\n      for await (const car of cars) {\n        const roots = await importCar(repo, abortOptions, car)\n\n        if (options.pinRoots !== false) { // default=true\n          for (const cid of roots) {\n            let pinErrorMsg = ''\n\n            try { // eslint-disable-line max-depth\n              if (await repo.blocks.has(cid)) { // eslint-disable-line max-depth\n                log(`Pinning root ${cid}`)\n                await repo.pins.pinRecursively(cid)\n              } else {\n                pinErrorMsg = 'blockstore: block not found'\n              }\n            } catch (/** @type {any} */ err) {\n              pinErrorMsg = err.message\n            }\n\n            yield { root: { cid, pinErrorMsg } }\n          }\n        }\n      }\n    } finally {\n      release()\n    }\n  }\n\n  return withTimeoutOption(dagImport)\n}\n\n/**\n * @param {IPFSRepo} repo\n * @param {AbortOptions} options\n * @param {AsyncIterable<Uint8Array>} source\n * @returns {Promise<CID[]>}\n */\nasync function importCar (repo, options, source) {\n  const reader = await CarBlockIterator.fromIterable(source)\n  const roots = await reader.getRoots()\n\n  await drain(\n    repo.blocks.putMany(\n      map(reader, ({ cid: key, bytes: value }) => {\n        log(`Import block ${key}`)\n\n        return { key, value }\n      }),\n      { signal: options.signal }\n    )\n  )\n\n  return roots\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/dag/index.js",
    "content": "import { createExport } from './export.js'\nimport { createGet } from './get.js'\nimport { createImport } from './import.js'\nimport { createPut } from './put.js'\nimport { createResolve } from './resolve.js'\n\nexport class DagAPI {\n  /**\n   * @param {object} config\n   * @param {import('ipfs-core-utils/multihashes').Multihashes} config.hashers\n   * @param {import('ipfs-core-utils/multicodecs').Multicodecs} config.codecs\n   * @param {import('../../types').Preload} config.preload\n   * @param {import('ipfs-repo').IPFSRepo} config.repo\n   */\n  constructor ({ repo, codecs, hashers, preload }) {\n    this.export = createExport({ repo, preload, codecs })\n    this.get = createGet({ codecs, repo, preload })\n    this.import = createImport({ repo })\n    this.resolve = createResolve({ repo, codecs, preload })\n    this.put = createPut({ repo, codecs, hashers, preload })\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/dag/put.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @param {object} config\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n * @param {import('ipfs-core-utils/multicodecs').Multicodecs} config.codecs\n * @param {import('ipfs-core-utils/multihashes').Multihashes} config.hashers\n * @param {import('../../types').Preload} config.preload\n */\nexport function createPut ({ repo, codecs, hashers, preload }) {\n  /**\n   * @type {import('ipfs-core-types/src/dag').API<{}>[\"put\"]}\n   */\n  async function put (dagNode, options = {}) {\n    const release = options.pin ? await repo.gcLock.readLock() : null\n\n    try {\n      const storeCodec = await codecs.getCodec(options.storeCodec || 'dag-cbor')\n      // TODO: doesn't getCodec throw? verify and possibly remove this\n      if (!storeCodec) {\n        throw new Error(`Unknown storeCodec ${options.storeCodec}, please configure additional BlockCodecs for this IPFS instance`)\n      }\n\n      if (options.inputCodec) {\n        if (!(dagNode instanceof Uint8Array)) {\n          throw new Error('Can only inputCodec on raw bytes that can be decoded')\n        }\n        const inputCodec = await codecs.getCodec(options.inputCodec)\n        if (!inputCodec) {\n          throw new Error(`Unknown inputCodec ${options.inputCodec}, please configure additional BlockCodecs for this IPFS instance`)\n        }\n        dagNode = inputCodec.decode(dagNode)\n      }\n\n      const cidVersion = options.version != null ? options.version : 1\n      const hasher = await hashers.getHasher(options.hashAlg || 'sha2-256')\n\n      if (!hasher) {\n        throw new Error(`Unknown hash algorithm ${options.hashAlg}, please configure additional MultihashHashers for this IPFS instance`)\n      }\n\n      const buf = storeCodec.encode(dagNode)\n      const hash = await hasher.digest(buf)\n      const cid = CID.create(cidVersion, storeCodec.code, hash)\n\n      await repo.blocks.put(cid, buf, {\n        signal: options.signal\n      })\n\n      if (options.pin) {\n        await repo.pins.pinRecursively(cid)\n      }\n\n      if (options.preload !== false) {\n        preload(cid)\n      }\n\n      return cid\n    } finally {\n      if (release) {\n        release()\n      }\n    }\n  }\n\n  return withTimeoutOption(put)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/dag/resolve.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport { toCidAndPath } from 'ipfs-core-utils/to-cid-and-path'\nimport { resolvePath } from '../../utils.js'\n\n/**\n * @param {object} config\n * @param {import('ipfs-core-utils/multicodecs').Multicodecs} config.codecs\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n * @param {import('../../types').Preload} config.preload\n */\nexport function createResolve ({ repo, codecs, preload }) {\n  /**\n   * @type {import('ipfs-core-types/src/dag').API<{}>[\"resolve\"]}\n   */\n  async function dagResolve (ipfsPath, options = {}) {\n    const {\n      cid\n    } = toCidAndPath(ipfsPath)\n\n    if (options.preload !== false) {\n      preload(cid)\n    }\n\n    return resolvePath(repo, codecs, ipfsPath, options)\n  }\n\n  return withTimeoutOption(dagResolve)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/dht.js",
    "content": "import errCode from 'err-code'\nimport { NotEnabledError } from '../errors.js'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport { CID } from 'multiformats/cid'\nimport { base58btc } from 'multiformats/bases/base58'\nimport { base36 } from 'multiformats/bases/base36'\nimport { concat as uint8ArrayConcat } from 'uint8arrays/concat'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { peerIdFromString } from '@libp2p/peer-id'\n\n/**\n * @typedef {import('@libp2p/interface-dht').QueryEvent} QueryEvent\n * @typedef {import('./network').Network} Network\n * @typedef {import('@libp2p/interface-peer-id').PeerId} PeerId\n */\n\nconst IPNS_PREFIX = '/ipns/'\n\n/**\n * @param {string} str\n */\nfunction toDHTKey (str) {\n  if (str.startsWith(IPNS_PREFIX)) {\n    str = str.substring(IPNS_PREFIX.length)\n  }\n\n  /** @type {Uint8Array|undefined} */\n  let buf\n\n  if (str[0] === '1' || str[0] === 'Q') {\n    // ed25519 key or hash of rsa key\n    str = `z${str}`\n  }\n\n  if (str[0] === 'z') {\n    buf = base58btc.decode(str)\n  }\n\n  if (str[0] === 'k') {\n    // base36 encoded string\n    buf = base36.decode(str)\n  }\n\n  if (!buf) {\n    throw new Error('Could not parse string')\n  }\n\n  if (buf[0] !== 0x01 && buf[1] !== 0x72) {\n    // prefix key with CIDv1 and libp2p-key codec\n    buf = uint8ArrayConcat([\n      [0x01, 0x72],\n      buf\n    ])\n  }\n\n  if (buf.length !== 40) {\n    throw new Error('Incorrect length ' + buf.length)\n  }\n\n  return uint8ArrayConcat([\n    uint8ArrayFromString(IPNS_PREFIX),\n    buf.subarray(2)\n  ])\n}\n\n/**\n * @param {object} config\n * @param {import('../types').NetworkService} config.network\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n * @param {PeerId} config.peerId\n */\nexport function createDht ({ network, repo, peerId }) {\n  const { get, put, findProvs, findPeer, provide, query } = {\n    /**\n     * @type {import('ipfs-core-types/src/dht').API<{}>[\"get\"]}\n     */\n    async * get (key, options = {}) {\n      const { libp2p } = await use(network, peerId, options)\n\n      const dhtKey = key instanceof Uint8Array ? key : toDHTKey(key)\n\n      if (libp2p.dht == null) {\n        throw errCode(new Error('dht not configured'), 'ERR_DHT_NOT_CONFIGURED')\n      }\n\n      yield * libp2p.dht.get(dhtKey, options)\n    },\n\n    /**\n     * @type {import('ipfs-core-types/src/dht').API<{}>[\"put\"]}\n     */\n    async * put (key, value, options) {\n      const { libp2p } = await use(network, peerId, options)\n\n      const dhtKey = key instanceof Uint8Array ? key : toDHTKey(key)\n\n      if (libp2p.dht == null) {\n        throw errCode(new Error('dht not configured'), 'ERR_DHT_NOT_CONFIGURED')\n      }\n\n      yield * libp2p.dht.put(dhtKey, value, options)\n    },\n\n    /**\n     * @type {import('ipfs-core-types/src/dht').API<{}>[\"findProvs\"]}\n     */\n    async * findProvs (cid, options = {}) {\n      const { libp2p } = await use(network, peerId, options)\n\n      if (libp2p.dht == null) {\n        throw errCode(new Error('dht not configured'), 'ERR_DHT_NOT_CONFIGURED')\n      }\n\n      yield * libp2p.dht.findProviders(cid, {\n        signal: options.signal\n      })\n    },\n\n    /**\n     * @type {import('ipfs-core-types/src/dht').API<{}>[\"findPeer\"]}\n     */\n    async * findPeer (peerIdToFind, options = {}) {\n      const { libp2p } = await use(network, peerId, options)\n\n      if (libp2p.dht == null) {\n        throw errCode(new Error('dht not configured'), 'ERR_DHT_NOT_CONFIGURED')\n      }\n\n      yield * libp2p.dht.findPeer(peerIdToFind, {\n        signal: options.signal\n      })\n    },\n\n    /**\n     * @type {import('ipfs-core-types/src/dht').API<{}>[\"provide\"]}\n     */\n    async * provide (cid, options = { recursive: false }) {\n      const { libp2p } = await use(network, peerId, options)\n\n      // ensure blocks are actually local\n      const hasBlock = await repo.blocks.has(cid)\n\n      if (!hasBlock) {\n        throw errCode(new Error('block(s) not found locally, cannot provide'), 'ERR_BLOCK_NOT_FOUND')\n      }\n\n      if (options.recursive) {\n        // TODO: Implement recursive providing\n        throw errCode(new Error('not implemented yet'), 'ERR_NOT_IMPLEMENTED_YET')\n      }\n\n      if (libp2p.dht == null) {\n        throw errCode(new Error('dht not configured'), 'ERR_DHT_NOT_CONFIGURED')\n      }\n\n      yield * libp2p.dht.provide(cid)\n    },\n\n    /**\n     * @type {import('ipfs-core-types/src/dht').API<{}>[\"query\"]}\n     */\n    async * query (peerIdToQuery, options = {}) {\n      const { libp2p } = await use(network, peerId, options)\n      let bytes\n      const asCid = CID.asCID(peerIdToQuery)\n\n      if (asCid != null) {\n        bytes = asCid.multihash.bytes\n      } else {\n        bytes = peerIdFromString(peerIdToQuery.toString()).toBytes()\n      }\n\n      if (libp2p.dht == null) {\n        throw errCode(new Error('dht not configured'), 'ERR_DHT_NOT_CONFIGURED')\n      }\n\n      yield * libp2p.dht.getClosestPeers(bytes, options)\n    }\n  }\n\n  return {\n    get: withTimeoutOption(get),\n    put: withTimeoutOption(put),\n    findProvs: withTimeoutOption(findProvs),\n    findPeer: withTimeoutOption(findPeer),\n    provide: withTimeoutOption(provide),\n    query: withTimeoutOption(query)\n  }\n}\n\n/**\n * @param {import('../types').NetworkService} network\n * @param {PeerId} peerId\n * @param {import('ipfs-core-types/src/utils').AbortOptions} [options]\n * @returns {Promise<Network>}\n */\nconst use = async (network, peerId, options) => {\n  const net = await network.use(options)\n  if (net.libp2p.dht != null) {\n    return net\n  } else {\n    const fn = async function * () {\n      yield {\n        from: peerId,\n        name: 'QUERY_ERROR',\n        type: 3,\n        error: new NotEnabledError('dht not enabled')\n      }\n    }\n\n    return {\n      libp2p: {\n        dht: {\n          // @ts-expect-error incomplete implementation\n          get: fn,\n          // @ts-expect-error incomplete implementation\n          put: fn,\n          // @ts-expect-error incomplete implementation\n          findProviders: fn,\n          // @ts-expect-error incomplete implementation\n          findPeer: fn,\n          // @ts-expect-error incomplete implementation\n          provide: fn,\n          // @ts-expect-error incomplete implementation\n          getClosestPeers: fn\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/dns.js",
    "content": "// dns-nodejs gets replaced by dns-browser when bundled\nimport { resolveDnslink } from 'ipfs-core-config/dns'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @param {string} domain\n * @returns {string}\n */\nfunction fqdnFixups (domain) {\n  // Allow resolution of .eth names via .eth.link\n  // More context at the go-ipfs counterpart: https://github.com/ipfs/go-ipfs/pull/6448\n  if (domain.endsWith('.eth')) {\n    domain = domain.replace(/.eth$/, '.eth.link')\n  }\n  return domain\n}\n\nexport function createDns () {\n  /**\n   * @type {import('ipfs-core-types/src/root').API<{}>[\"dns\"]}\n   */\n  const resolveDNS = async (domain, options = { recursive: true }) => { // eslint-disable-line require-await\n    if (typeof domain !== 'string') {\n      throw new Error('Invalid arguments, domain must be a string')\n    }\n\n    domain = fqdnFixups(domain)\n\n    return resolveDnslink(domain, options)\n  }\n\n  return withTimeoutOption(resolveDNS)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/files/chmod.js",
    "content": "import mergeOpts from 'merge-options'\nimport { toMfsPath } from './utils/to-mfs-path.js'\nimport { logger } from '@libp2p/logger'\nimport errCode from 'err-code'\nimport { UnixFS } from 'ipfs-unixfs'\nimport { toTrail } from './utils/to-trail.js'\nimport { addLink } from './utils/add-link.js'\nimport { updateTree } from './utils/update-tree.js'\nimport { updateMfsRoot } from './utils/update-mfs-root.js'\nimport * as dagPB from '@ipld/dag-pb'\nimport { CID } from 'multiformats/cid'\nimport { pipe } from 'it-pipe'\nimport { importer } from 'ipfs-unixfs-importer'\nimport { recursive } from 'ipfs-unixfs-exporter'\nimport last from 'it-last'\nimport { createCp } from './cp.js'\nimport { createRm } from './rm.js'\nimport { persist } from './utils/persist.js'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\nconst mergeOptions = mergeOpts.bind({ ignoreUndefined: true })\nconst log = logger('ipfs:mfs:touch')\n\n/**\n * @typedef {import('multiformats/cid').Version} CIDVersion\n * @typedef {import('@ipld/dag-pb').PBNode} PBNode\n * @typedef {import('./').MfsContext} MfsContext\n *\n * @typedef {object} DefaultOptions\n * @property {boolean} flush\n * @property {string} hashAlg\n * @property {CIDVersion} cidVersion\n * @property {number} shardSplitThreshold\n * @property {boolean} recursive\n * @property {AbortSignal} [signal]\n * @property {number} [timeout]\n */\n\n/**\n * @type {DefaultOptions}\n */\nconst defaultOptions = {\n  flush: true,\n  shardSplitThreshold: 1000,\n  hashAlg: 'sha2-256',\n  cidVersion: 0,\n  recursive: false\n}\n\n/**\n * @param {string} mode\n * @param {number} originalMode\n * @param {boolean} isDirectory\n */\nfunction calculateModification (mode, originalMode, isDirectory) {\n  let modification = 0\n\n  if (mode.includes('x') || (mode.includes('X') && (isDirectory || (originalMode & 0o1 || originalMode & 0o10 || originalMode & 0o100)))) {\n    modification += 1\n  }\n\n  if (mode.includes('w')) {\n    modification += 2\n  }\n\n  if (mode.includes('r')) {\n    modification += 4\n  }\n\n  return modification\n}\n\n/**\n * @param {string} references\n * @param {number} modification\n */\nfunction calculateUGO (references, modification) {\n  let ugo = 0\n\n  if (references.includes('u')) {\n    ugo += (modification << 6)\n  }\n\n  if (references.includes('g')) {\n    ugo += (modification << 3)\n  }\n\n  if (references.includes('o')) {\n    ugo += (modification)\n  }\n\n  return ugo\n}\n\n/**\n * @param {string} references\n * @param {string} mode\n * @param {number} modification\n */\nfunction calculateSpecial (references, mode, modification) {\n  if (mode.includes('t')) {\n    modification += parseInt('1000', 8)\n  }\n\n  if (mode.includes('s')) {\n    if (references.includes('u')) {\n      modification += parseInt('4000', 8)\n    }\n\n    if (references.includes('g')) {\n      modification += parseInt('2000', 8)\n    }\n  }\n\n  return modification\n}\n\n/**\n * https://en.wikipedia.org/wiki/Chmod#Symbolic_modes\n *\n * @param {string} input\n * @param {number} originalMode\n * @param {boolean} isDirectory\n */\nfunction parseSymbolicMode (input, originalMode, isDirectory) {\n  if (!originalMode) {\n    originalMode = 0\n  }\n\n  const match = input.match(/^(u?g?o?a?)(-?\\+?=?)?(r?w?x?X?s?t?)$/)\n\n  if (!match) {\n    throw new Error(`Invalid file mode: ${input}`)\n  }\n\n  let [\n    ,\n    references,\n    operator,\n    mode\n  ] = match\n\n  if (references === 'a' || !references) {\n    references = 'ugo'\n  }\n\n  let modification = calculateModification(mode, originalMode, isDirectory)\n  modification = calculateUGO(references, modification)\n  modification = calculateSpecial(references, mode, modification)\n\n  if (operator === '=') {\n    if (references.includes('u')) {\n      // blank u bits\n      originalMode = originalMode & parseInt('7077', 8)\n\n      // or them together\n      originalMode = originalMode | modification\n    }\n\n    if (references.includes('g')) {\n      // blank g bits\n      originalMode = originalMode & parseInt('7707', 8)\n\n      // or them together\n      originalMode = originalMode | modification\n    }\n\n    if (references.includes('o')) {\n      // blank o bits\n      originalMode = originalMode & parseInt('7770', 8)\n\n      // or them together\n      originalMode = originalMode | modification\n    }\n\n    return originalMode\n  }\n\n  if (operator === '+') {\n    return modification | originalMode\n  }\n\n  if (operator === '-') {\n    return modification ^ originalMode\n  }\n\n  return originalMode\n}\n\n/**\n * @param {string | InstanceType<typeof window.String> | number} mode\n * @param {UnixFS} metadata\n * @returns {number}\n */\nfunction calculateMode (mode, metadata) {\n  if (mode instanceof String || typeof mode === 'string') {\n    const strMode = `${mode}`\n\n    if (strMode.match(/^\\d+$/g)) {\n      mode = parseInt(strMode, 8)\n    } else {\n      mode = 0 + strMode.split(',').reduce((curr, acc) => {\n        return parseSymbolicMode(acc, curr, metadata.isDirectory())\n      }, metadata.mode || 0)\n    }\n  }\n\n  return mode\n}\n\n/**\n * @param {MfsContext} context\n */\nexport function createChmod (context) {\n  /**\n   * @type {import('ipfs-core-types/src/files').API<{}>[\"chmod\"]}\n   */\n  async function mfsChmod (path, mode, options = {}) {\n    /** @type {DefaultOptions} */\n    const opts = mergeOptions(defaultOptions, options)\n\n    log(`Fetching stats for ${path}`)\n\n    const {\n      cid,\n      mfsDirectory,\n      name\n    } = await toMfsPath(context, path, opts)\n\n    if (cid.code !== dagPB.code) {\n      throw errCode(new Error(`${path} was not a UnixFS node`), 'ERR_NOT_UNIXFS')\n    }\n\n    if (opts.recursive) {\n      // recursively export from root CID, change perms of each entry then reimport\n      // but do not reimport files, only manipulate dag-pb nodes\n      const root = await pipe(\n        async function * () {\n          for await (const entry of recursive(cid, context.repo.blocks)) {\n            if (entry.type !== 'file' && entry.type !== 'directory') {\n              throw errCode(new Error(`${path} was not a UnixFS node`), 'ERR_NOT_UNIXFS')\n            }\n\n            entry.unixfs.mode = calculateMode(mode, entry.unixfs)\n\n            const node = dagPB.prepare({\n              Data: entry.unixfs.marshal(),\n              Links: entry.node.Links\n            })\n\n            yield {\n              path: entry.path,\n              content: node\n            }\n          }\n        },\n        // @ts-expect-error we account for the incompatible source type with our custom dag builder below\n        (source) => importer(source, context.repo.blocks, {\n          ...opts,\n          pin: false,\n          dagBuilder: async function * (source, block, opts) {\n            for await (const entry of source) {\n              yield async function () {\n                /** @type {PBNode} */\n                // @ts-expect-error - cannot derive type\n                const node = entry.content\n\n                const buf = dagPB.encode(node)\n                const cid = await persist(buf, block, opts)\n\n                if (!node.Data) {\n                  throw errCode(new Error(`${cid} had no data`), 'ERR_INVALID_NODE')\n                }\n\n                const unixfs = UnixFS.unmarshal(node.Data)\n\n                return {\n                  cid,\n                  size: buf.length,\n                  path: entry.path,\n                  unixfs\n                }\n              }\n            }\n          }\n        }),\n        (nodes) => last(nodes)\n      )\n\n      if (!root) {\n        throw errCode(new Error(`Could not chmod ${path}`), 'ERR_COULD_NOT_CHMOD')\n      }\n\n      // remove old path from mfs\n      await createRm(context)(path, opts)\n\n      // add newly created tree to mfs at path\n      await createCp(context)(`/ipfs/${root.cid}`, path, opts)\n\n      return\n    }\n\n    const block = await context.repo.blocks.get(cid)\n    const node = dagPB.decode(block)\n\n    if (!node.Data) {\n      throw errCode(new Error(`${cid} had no data`), 'ERR_INVALID_NODE')\n    }\n\n    const metadata = UnixFS.unmarshal(node.Data)\n    metadata.mode = calculateMode(mode, metadata)\n    const updatedBlock = dagPB.encode({\n      Data: metadata.marshal(),\n      Links: node.Links\n    })\n\n    const hashAlg = opts.hashAlg || defaultOptions.hashAlg\n    const hasher = await context.hashers.getHasher(hashAlg)\n    const hash = await hasher.digest(updatedBlock)\n    const updatedCid = CID.create(opts.cidVersion, dagPB.code, hash)\n\n    if (opts.flush) {\n      await context.repo.blocks.put(updatedCid, updatedBlock)\n    }\n\n    const trail = await toTrail(context, mfsDirectory)\n    const parent = trail[trail.length - 1]\n    const parentCid = CID.decode(parent.cid.bytes)\n    const parentBlock = await context.repo.blocks.get(parentCid)\n    const parentNode = dagPB.decode(parentBlock)\n\n    const result = await addLink(context, {\n      parent: parentNode,\n      name: name,\n      cid: updatedCid,\n      size: updatedBlock.length,\n      flush: opts.flush,\n      // TODO vmx 2021-03-29: decide on the API, whether it should be a `hashAlg` or `hasher`\n      hashAlg,\n      cidVersion: cid.version,\n      shardSplitThreshold: Infinity\n    })\n\n    parent.cid = result.cid\n\n    // update the tree with the new child\n    const newRootCid = await updateTree(context, trail, opts)\n\n    // Update the MFS record with the new CID for the root of the tree\n    await updateMfsRoot(context, newRootCid, opts)\n  }\n\n  return withTimeoutOption(mfsChmod)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/files/cp.js",
    "content": "import { createMkdir } from './mkdir.js'\nimport { createStat } from './stat.js'\nimport { logger } from '@libp2p/logger'\nimport errCode from 'err-code'\nimport { updateTree } from './utils/update-tree.js'\nimport { updateMfsRoot } from './utils/update-mfs-root.js'\nimport { addLink } from './utils/add-link.js'\nimport { toMfsPath } from './utils/to-mfs-path.js'\nimport mergeOpts from 'merge-options'\nimport { toTrail } from './utils/to-trail.js'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\nconst mergeOptions = mergeOpts.bind({ ignoreUndefined: true })\nconst log = logger('ipfs:mfs:cp')\n\n/**\n * @typedef {import('@ipld/dag-pb').PBNode} DAGNode\n * @typedef {import('multiformats/cid').CID} CID\n * @typedef {import('multiformats/cid').Version} CIDVersion\n * @typedef {import('ipfs-unixfs').Mtime} Mtime\n * @typedef {import('./utils/to-mfs-path').MfsPath} MfsPath\n * @typedef {import('./utils/to-trail').MfsTrail} MfsTrail\n * @typedef {import('./').MfsContext} MfsContext\n * @typedef {object} DefaultOptions\n * @property {boolean} parents\n * @property {boolean} flush\n * @property {string} hashAlg\n * @property {CIDVersion} cidVersion\n * @property {number} shardSplitThreshold\n * @property {AbortSignal} [signal]\n * @property {number} [timeout]\n */\n\n/**\n * @type {DefaultOptions}\n */\nconst defaultOptions = {\n  parents: false,\n  flush: true,\n  hashAlg: 'sha2-256',\n  cidVersion: 0,\n  shardSplitThreshold: 1000\n}\n\n/**\n * @param {MfsContext} context\n */\nexport function createCp (context) {\n  /**\n   * @type {import('ipfs-core-types/src/files').API<{}>[\"cp\"]}\n   */\n  async function mfsCp (from, to, opts = {}) {\n    /** @type {DefaultOptions} */\n    const options = mergeOptions(defaultOptions, opts)\n\n    if (!Array.isArray(from)) {\n      from = [from]\n    }\n\n    const sources = await Promise.all(\n      from.map((/** @type {CID | string} */ path) => toMfsPath(context, path, options))\n    )\n    let destination = await toMfsPath(context, to, options)\n\n    if (!sources.length || !destination) {\n      throw errCode(new Error('Please supply at least one source'), 'ERR_INVALID_PARAMS')\n    }\n\n    // make sure all sources exist\n    const missing = sources.find(source => !source.exists)\n\n    if (missing) {\n      throw errCode(new Error(`${missing.path} does not exist`), 'ERR_INVALID_PARAMS')\n    }\n\n    const destinationIsDirectory = isDirectory(destination)\n\n    if (destination.exists) {\n      log('Destination exists')\n\n      if (sources.length === 1 && !destinationIsDirectory) {\n        throw errCode(new Error('directory already has entry by that name'), 'ERR_ALREADY_EXISTS')\n      }\n    } else {\n      log('Destination does not exist')\n\n      if (sources.length > 1) {\n        // copying multiple files to one location, destination will be a directory\n        if (!options.parents) {\n          throw errCode(new Error('destination did not exist, pass -p to create intermediate directories'), 'ERR_INVALID_PARAMS')\n        }\n\n        await createMkdir(context)(destination.path, options)\n        destination = await toMfsPath(context, destination.path, options)\n      } else if (destination.parts.length > 1) {\n        // copying to a folder, create it if necessary\n        const parentFolder = `/${destination.parts.slice(0, -1).join('/')}`\n\n        try {\n          await createStat(context)(parentFolder, options)\n        } catch (/** @type {any} */ err) {\n          if (err.code !== 'ERR_NOT_FOUND') {\n            throw err\n          }\n\n          if (!options.parents) {\n            throw errCode(new Error('destination did not exist, pass -p to create intermediate directories'), 'ERR_INVALID_PARAMS')\n          }\n\n          await createMkdir(context)(parentFolder, options)\n          destination = await toMfsPath(context, destination.path, options)\n        }\n      }\n    }\n\n    const destinationPath = isDirectory(destination) ? destination.mfsPath : destination.mfsDirectory\n    const trail = await toTrail(context, destinationPath)\n\n    if (sources.length === 1) {\n      const source = sources.pop()\n\n      if (!source) {\n        throw errCode(new Error('could not find source'), 'ERR_INVALID_PARAMS')\n      }\n\n      const destinationName = destinationIsDirectory ? source.name : destination.name\n\n      log(`Only one source, copying to destination ${destinationIsDirectory ? 'directory' : 'file'} ${destinationName}`)\n\n      return copyToFile(context, source, destinationName, trail, options)\n    }\n\n    log('Multiple sources, wrapping in a directory')\n    return copyToDirectory(context, sources, destination, trail, options)\n  }\n\n  return withTimeoutOption(mfsCp)\n}\n\n/**\n * @param {*} destination\n */\nconst isDirectory = (destination) => {\n  return destination.unixfs &&\n    destination.unixfs.type &&\n    destination.unixfs.type.includes('directory')\n}\n\n/**\n * @param {MfsContext} context\n * @param {MfsPath} source\n * @param {string} destination\n * @param {MfsTrail[]} destinationTrail\n * @param {DefaultOptions} options\n */\nconst copyToFile = async (context, source, destination, destinationTrail, options) => {\n  let parent = destinationTrail.pop()\n\n  if (!parent) {\n    throw errCode(new Error('destination had no parent'), 'ERR_INVALID_PARAMS')\n  }\n\n  parent = await addSourceToParent(context, source, destination, parent, options)\n\n  // update the tree with the new containing directory\n  destinationTrail.push(parent)\n\n  const newRootCid = await updateTree(context, destinationTrail, options)\n\n  // Update the MFS record with the new CID for the root of the tree\n  await updateMfsRoot(context, newRootCid, options)\n}\n\n/**\n * @param {MfsContext} context\n * @param {MfsPath[]} sources\n * @param {*} destination\n * @param {MfsTrail[]} destinationTrail\n * @param {DefaultOptions} options\n */\nconst copyToDirectory = async (context, sources, destination, destinationTrail, options) => {\n  // copy all the sources to the destination\n  for (let i = 0; i < sources.length; i++) {\n    const source = sources[i]\n\n    destination = await addSourceToParent(context, source, source.name, destination, options)\n  }\n\n  // update the tree with the new containing directory\n  destinationTrail[destinationTrail.length - 1] = destination\n\n  const newRootCid = await updateTree(context, destinationTrail, options)\n\n  // Update the MFS record with the new CID for the root of the tree\n  await updateMfsRoot(context, newRootCid, options)\n}\n\n/**\n * @param {MfsContext} context\n * @param {MfsPath} source\n * @param {string} childName\n * @param {*} parent\n * @param {DefaultOptions} options\n * @returns {Promise<MfsTrail>}\n */\nconst addSourceToParent = async (context, source, childName, parent, options) => {\n  const sourceBlock = await context.repo.blocks.get(source.cid)\n  const {\n    node,\n    cid,\n    size\n  } = await addLink(context, {\n    parentCid: parent.cid,\n    size: sourceBlock.length,\n    cid: source.cid,\n    name: childName,\n    hashAlg: options.hashAlg,\n    cidVersion: options.cidVersion,\n    flush: options.flush,\n    shardSplitThreshold: options.shardSplitThreshold\n  })\n\n  parent.node = node\n  parent.cid = cid\n  parent.size = size\n\n  return parent\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/files/flush.js",
    "content": "import { createStat } from './stat.js'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport mergeOpts from 'merge-options'\n\nconst mergeOptions = mergeOpts.bind({ ignoreUndefined: true })\n\n/**\n * @typedef {import('./').MfsContext} MfsContext\n * @typedef {object} DefaultOptions\n * @property {AbortSignal} [signal]\n * @property {number} [timeout]\n */\n\n/**\n * @type {DefaultOptions}\n */\nconst defaultOptions = {}\n\n/**\n * @param {MfsContext} context\n */\nexport function createFlush (context) {\n  /**\n   * @type {import('ipfs-core-types/src/files').API<{}>[\"flush\"]}\n   */\n  async function mfsFlush (path, options = {}) {\n    /** @type {DefaultOptions} */\n    options = mergeOptions(defaultOptions, options)\n\n    const { cid } = await createStat(context)(path, options)\n\n    return cid\n  }\n\n  return withTimeoutOption(mfsFlush)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/files/index.js",
    "content": "import { createLock } from './utils/create-lock.js'\nimport * as isIpfs from 'is-ipfs'\nimport { createStat } from './stat.js'\nimport { createChmod } from './chmod.js'\nimport { createCp } from './cp.js'\nimport { createFlush } from './flush.js'\nimport { createMkdir } from './mkdir.js'\nimport { createMv } from './mv.js'\nimport { createRm } from './rm.js'\nimport { createTouch } from './touch.js'\nimport { createRead } from './read.js'\nimport { createWrite } from './write.js'\nimport { createLs } from './ls.js'\n\n/**\n * @typedef {import('multiformats/hashes/interface').MultihashHasher} MultihashHasher\n * @typedef {import('ipfs-core-utils/multihashes').Multihashes} Multihashes\n * @typedef {import('ipfs-repo').IPFSRepo} IPFSRepo\n *\n * @typedef {object} MfsContext\n * @property {IPFSRepo} repo\n * @property {Multihashes} hashers\n */\n\n/**\n * These operations are read-locked at the function level and will execute simultaneously\n *\n * @type {Record<string, any>}\n */\nconst readOperations = {\n  stat: createStat\n}\n\n/**\n * These operations are locked at the function level and will execute in series\n *\n * @type {Record<string, any>}\n */\nconst writeOperations = {\n  chmod: createChmod,\n  cp: createCp,\n  flush: createFlush,\n  mkdir: createMkdir,\n  mv: createMv,\n  rm: createRm,\n  touch: createTouch\n}\n\n/**\n * These operations are asynchronous and manage their own locking\n *\n * @type {Record<string, any>}\n */\nconst unwrappedOperations = {\n  write: createWrite,\n  read: createRead,\n  ls: createLs\n}\n\n/**\n * @param {object} arg\n * @param {MfsContext} arg.options\n * @param {*} arg.mfs\n * @param {*} arg.operations\n * @param {*} arg.lock\n */\nconst wrap = ({\n  options, mfs, operations, lock\n}) => {\n  Object.keys(operations).forEach(key => {\n    mfs[key] = lock(operations[key](options))\n  })\n}\n\nconst defaultOptions = {\n  repoOwner: true,\n  repo: null\n}\n\n/**\n * @param {object} options\n * @param {IPFSRepo} options.repo\n * @param {boolean} options.repoOwner\n * @param {Multihashes} options.hashers\n */\nfunction createMfs (options) {\n  const {\n    repoOwner\n  } = Object.assign({}, defaultOptions || {}, options)\n\n  const lock = createLock(repoOwner)\n\n  /**\n   * @param {(fn: (...args: any) => any) => (...args: any) => any} operation\n   */\n  const readLock = (operation) => {\n    return lock.readLock(operation)\n  }\n\n  /**\n   * @param {(fn: (...args: any) => any) => (...args: any) => any} operation\n   */\n  const writeLock = (operation) => {\n    return lock.writeLock(operation)\n  }\n\n  /** @type {Record<string, any>} */\n  const mfs = {}\n\n  wrap({\n    options, mfs, operations: readOperations, lock: readLock\n  })\n  wrap({\n    options, mfs, operations: writeOperations, lock: writeLock\n  })\n\n  Object.keys(unwrappedOperations).forEach(key => {\n    mfs[key] = unwrappedOperations[key](options)\n  })\n\n  return mfs\n}\n\n/**\n * @param {object} context\n * @param {IPFSRepo} context.repo\n * @param {import('../../types').Preload} context.preload\n * @param {import('..').Options} context.options\n * @param {Multihashes} context.hashers\n * @returns {import('ipfs-core-types/src/files').API}\n */\nexport function createFiles ({ repo, preload, hashers, options: constructorOptions }) {\n  const methods = createMfs({\n    repo,\n    repoOwner: constructorOptions.repoOwner !== false,\n    hashers\n  })\n\n  /**\n   * @param {any} fn\n   */\n  const withPreload = fn => {\n    /**\n     * @param  {...any} args\n     */\n    const wrapped = (...args) => {\n      const paths = args.filter(arg => isIpfs.ipfsPath(arg) || isIpfs.cid(arg))\n\n      if (paths.length) {\n        const options = args[args.length - 1]\n        if (options && options.preload !== false) {\n          paths.forEach(path => preload(path))\n        }\n      }\n\n      return fn(...args)\n    }\n\n    return wrapped\n  }\n\n  return {\n    ...methods,\n    chmod: methods.chmod,\n    cp: withPreload(methods.cp),\n    mkdir: methods.mkdir,\n    stat: withPreload(methods.stat),\n    rm: methods.rm,\n    read: withPreload(methods.read),\n    touch: methods.touch,\n    write: methods.write,\n    mv: withPreload(methods.mv),\n    flush: methods.flush,\n    ls: withPreload(async function * (/** @type {...any} */ ...args) {\n      for await (const file of methods.ls(...args)) {\n        yield { ...file, size: file.size || 0 }\n      }\n    })\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/files/ls.js",
    "content": "import { exporter } from 'ipfs-unixfs-exporter'\nimport { toMfsPath } from './utils/to-mfs-path.js'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport map from 'it-map'\n\n/**\n * @typedef {import('./').MfsContext} MfsContext\n * @typedef {import('ipfs-core-types/src/files').MFSEntry} MFSEntry\n */\n\n/**\n * @param {import('ipfs-unixfs-exporter').UnixFSEntry} fsEntry\n */\nconst toOutput = (fsEntry) => {\n  /** @type {MFSEntry} */\n  const output = {\n    cid: fsEntry.cid,\n    name: fsEntry.name,\n    type: fsEntry.type === 'directory' ? 'directory' : 'file',\n    size: fsEntry.size\n  }\n\n  if (fsEntry.type === 'file' || fsEntry.type === 'directory') {\n    output.mode = fsEntry.unixfs.mode\n    output.mtime = fsEntry.unixfs.mtime\n  }\n\n  return output\n}\n\n/**\n * @param {MfsContext} context\n */\nexport function createLs (context) {\n  /**\n   * @type {import('ipfs-core-types/src/files').API<{}>[\"ls\"]}\n   */\n  async function * mfsLs (path, options = {}) {\n    const mfsPath = await toMfsPath(context, path, options)\n    const fsEntry = await exporter(mfsPath.mfsPath, context.repo.blocks)\n\n    // directory, perhaps sharded\n    if (fsEntry.type === 'directory') {\n      yield * map(fsEntry.content(options), toOutput)\n\n      return\n    }\n\n    // single file/node\n    yield toOutput(fsEntry)\n  }\n\n  return withTimeoutOption(mfsLs)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/files/mkdir.js",
    "content": "import errCode from 'err-code'\nimport { logger } from '@libp2p/logger'\nimport { exporter } from 'ipfs-unixfs-exporter'\nimport { createNode } from './utils/create-node.js'\nimport { toPathComponents } from './utils/to-path-components.js'\nimport { updateMfsRoot } from './utils/update-mfs-root.js'\nimport { updateTree } from './utils/update-tree.js'\nimport { addLink } from './utils/add-link.js'\nimport { loadMfsRoot } from './utils/with-mfs-root.js'\nimport mergeOpts from 'merge-options'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\nconst mergeOptions = mergeOpts.bind({ ignoreUndefined: true })\nconst log = logger('ipfs:mfs:mkdir')\n\n/**\n * @typedef {import('@ipld/dag-pb').PBNode} PBNode\n * @typedef {import('multiformats/cid').CID} CID\n * @typedef {import('multiformats/cid').Version} CIDVersion\n * @typedef {import('ipfs-unixfs').MtimeLike} MtimeLike\n * @typedef {import('./').MfsContext} MfsContext\n * @typedef {object} DefaultOptions\n * @property {boolean} parents\n * @property {string} hashAlg\n * @property {CIDVersion} cidVersion\n * @property {number} shardSplitThreshold\n * @property {boolean} flush\n * @property {number} [mode]\n * @property {MtimeLike} [mtime]\n * @property {AbortSignal} [signal]\n * @property {number} [timeout]\n */\n\n/**\n * @type {DefaultOptions}\n */\nconst defaultOptions = {\n  parents: false,\n  hashAlg: 'sha2-256',\n  cidVersion: 0,\n  shardSplitThreshold: 1000,\n  flush: true\n}\n\n/**\n * @param {MfsContext} context\n */\nexport function createMkdir (context) {\n  /**\n   * @type {import('ipfs-core-types/src/files').API<{}>[\"mkdir\"]}\n   */\n  async function mfsMkdir (path, options = {}) {\n    /** @type {DefaultOptions} */\n    const opts = mergeOptions(defaultOptions, options)\n\n    if (!path) {\n      throw new Error('no path given to Mkdir')\n    }\n\n    path = path.trim()\n\n    if (path === '/') {\n      if (opts.parents) {\n        return\n      }\n\n      throw errCode(new Error('cannot create directory \\'/\\': Already exists'), 'ERR_INVALID_PATH')\n    }\n\n    if (path.substring(0, 1) !== '/') {\n      throw errCode(new Error('paths must start with a leading slash'), 'ERR_INVALID_PATH')\n    }\n\n    log(`Creating ${path}`)\n\n    const pathComponents = toPathComponents(path)\n\n    if (pathComponents[0] === 'ipfs') {\n      throw errCode(new Error(\"path cannot have the prefix 'ipfs'\"), 'ERR_INVALID_PATH')\n    }\n\n    const root = await loadMfsRoot(context, opts)\n    let parent\n    const trail = []\n    const emptyDir = await createNode(context, 'directory', opts)\n\n    // make sure the containing folder exists, creating it if necessary\n    for (let i = 0; i <= pathComponents.length; i++) {\n      const subPathComponents = pathComponents.slice(0, i)\n      const subPath = `/ipfs/${root}/${subPathComponents.join('/')}`\n\n      try {\n        parent = await exporter(subPath, context.repo.blocks)\n\n        if (parent.type !== 'file' && parent.type !== 'directory') {\n          throw errCode(new Error(`${path} was not a UnixFS node`), 'ERR_NOT_UNIXFS')\n        }\n\n        if (i === pathComponents.length) {\n          if (opts.parents) {\n            return\n          }\n\n          throw errCode(new Error('file already exists'), 'ERR_ALREADY_EXISTS')\n        }\n\n        trail.push({\n          name: parent.name,\n          cid: parent.cid\n        })\n      } catch (/** @type {any} */ err) {\n        if (err.code === 'ERR_NOT_FOUND') {\n          if (i < pathComponents.length && !opts.parents) {\n            throw errCode(new Error(`Intermediate directory path ${subPath} does not exist, use the -p flag to create it`), 'ERR_NOT_FOUND')\n          }\n\n          // add the intermediate directory\n          await addEmptyDir(context, subPathComponents[subPathComponents.length - 1], emptyDir, trail[trail.length - 1], trail, opts)\n        } else {\n          throw err\n        }\n      }\n    }\n\n    // add an empty dir to the last path component\n    // await addEmptyDir(context, pathComponents[pathComponents.length - 1], emptyDir, parent, trail)\n\n    // update the tree from the leaf to the root\n    const newRootCid = await updateTree(context, trail, opts)\n\n    // Update the MFS record with the new CID for the root of the tree\n    await updateMfsRoot(context, newRootCid, opts)\n  }\n\n  return withTimeoutOption(mfsMkdir)\n}\n\n/**\n * @param {MfsContext} context\n * @param {string} childName\n * @param {{ cid: CID, node?: PBNode }} emptyDir\n * @param {{ cid?: CID, node?: PBNode }} parent\n * @param {{ name: string, cid: CID }[]} trail\n * @param {DefaultOptions} options\n */\nconst addEmptyDir = async (context, childName, emptyDir, parent, trail, options) => {\n  log(`Adding empty dir called ${childName} to ${parent.cid}`)\n\n  const result = await addLink(context, {\n    parent: parent.node,\n    parentCid: parent.cid,\n    // TODO vmx 2021-03-09: Remove the usage of size completely\n    size: 0,\n    cid: emptyDir.cid,\n    name: childName,\n    hashAlg: options.hashAlg,\n    cidVersion: options.cidVersion,\n    flush: options.flush,\n    shardSplitThreshold: options.shardSplitThreshold\n  })\n\n  trail[trail.length - 1].cid = result.cid\n\n  trail.push({\n    name: childName,\n    cid: emptyDir.cid\n  })\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/files/mv.js",
    "content": "import { createCp } from './cp.js'\nimport { createRm } from './rm.js'\nimport mergeOpts from 'merge-options'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\nconst mergeOptions = mergeOpts.bind({ ignoreUndefined: true })\n\n/**\n * @typedef {import('multiformats/cid').Version} CIDVersion\n * @typedef {import('./').MfsContext} MfsContext\n * @typedef {object} DefaultOptions\n * @property {boolean} parents\n * @property {boolean} flush\n * @property {CIDVersion} cidVersion\n * @property {string} hashAlg\n * @property {number} shardSplitThreshold\n * @property {AbortSignal} [signal]\n * @property {number} [timeout]\n */\n\n/**\n * @type {DefaultOptions}\n */\nconst defaultOptions = {\n  parents: false,\n  flush: true,\n  cidVersion: 0,\n  hashAlg: 'sha2-256',\n  shardSplitThreshold: 1000\n}\n\n/**\n * @param {MfsContext} context\n */\nexport function createMv (context) {\n  /**\n   * @type {import('ipfs-core-types/src/files').API<{}>[\"mv\"]}\n   */\n  async function mfsMv (from, to, options = {}) {\n    /** @type {DefaultOptions} */\n    const opts = mergeOptions(defaultOptions, options)\n\n    await createCp(context)(from, to, opts)\n    await createRm(context)(from, {\n      ...opts,\n      recursive: true\n    })\n  }\n\n  return withTimeoutOption(mfsMv)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/files/read.js",
    "content": "import { exporter } from 'ipfs-unixfs-exporter'\nimport mergeOpts from 'merge-options'\nimport { toMfsPath } from './utils/to-mfs-path.js'\nimport errCode from 'err-code'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\nconst mergeOptions = mergeOpts.bind({ ignoreUndefined: true })\n\n/**\n * @typedef {import('./').MfsContext} MfsContext\n * @typedef {object} DefaultOptions\n * @property {number} offset\n * @property {number} length\n * @property {AbortSignal} [signal]\n * @property {number} [timeout]\n */\n\n/**\n * @type {DefaultOptions}\n */\nconst defaultOptions = {\n  offset: 0,\n  length: Infinity\n}\n\n/**\n * @param {MfsContext} context\n */\nexport function createRead (context) {\n  /**\n   * @type {import('ipfs-core-types/src/files').API<{}>[\"read\"]}\n   */\n  function mfsRead (path, options = {}) {\n    /** @type {DefaultOptions} */\n    options = mergeOptions(defaultOptions, options)\n\n    return {\n      [Symbol.asyncIterator]: async function * read () {\n        const mfsPath = await toMfsPath(context, path, options)\n        const result = await exporter(mfsPath.mfsPath, context.repo.blocks)\n\n        if (result.type !== 'file' && result.type !== 'raw') {\n          throw errCode(new Error(`${path} was not a file or raw bytes`), 'ERR_NOT_FILE')\n        }\n\n        if (!result.content) {\n          throw errCode(new Error(`Could not load content stream from ${path}`), 'ERR_NO_CONTENT')\n        }\n\n        for await (const buf of result.content({\n          offset: options.offset,\n          length: options.length\n        })) {\n          yield buf\n        }\n      }\n    }\n  }\n\n  return withTimeoutOption(mfsRead)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/files/rm.js",
    "content": "import errCode from 'err-code'\nimport { updateTree } from './utils/update-tree.js'\nimport { updateMfsRoot } from './utils/update-mfs-root.js'\nimport { removeLink } from './utils/remove-link.js'\nimport { toMfsPath } from './utils/to-mfs-path.js'\nimport { toTrail } from './utils/to-trail.js'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport mergeOpts from 'merge-options'\n\nconst mergeOptions = mergeOpts.bind({ ignoreUndefined: true })\n\n/**\n * @typedef {import('multiformats/cid').Version} CIDVersion\n * @typedef {import('./').MfsContext} MfsContext\n * @typedef {object} DefaultOptions\n * @property {boolean} recursive\n * @property {CIDVersion} cidVersion\n * @property {string} hashAlg\n * @property {boolean} flush\n * @property {number} shardSplitThreshold\n * @property {AbortSignal} [signal]\n * @property {number} [timeout]\n */\n\n/**\n * @type {DefaultOptions}\n */\nconst defaultOptions = {\n  recursive: false,\n  cidVersion: 0,\n  hashAlg: 'sha2-256',\n  flush: true,\n  shardSplitThreshold: 1000\n}\n\n/**\n * @param {MfsContext} context\n */\nexport function createRm (context) {\n  /**\n   * @type {import('ipfs-core-types/src/files').API<{}>[\"rm\"]}\n   */\n  async function mfsRm (paths, opts = {}) {\n    /** @type {DefaultOptions} */\n    const options = mergeOptions(defaultOptions, opts)\n\n    if (!Array.isArray(paths)) {\n      paths = [paths]\n    }\n\n    const sources = await Promise.all(\n      paths.map(path => toMfsPath(context, path, options))\n    )\n\n    if (!sources.length) {\n      throw errCode(new Error('Please supply at least one path to remove'), 'ERR_INVALID_PARAMS')\n    }\n\n    sources.forEach(source => {\n      if (source.path === '/') {\n        throw errCode(new Error('Cannot delete root'), 'ERR_INVALID_PARAMS')\n      }\n    })\n\n    for (const source of sources) {\n      await removePath(context, source.path, options)\n    }\n  }\n\n  return withTimeoutOption(mfsRm)\n}\n\n/**\n * @param {MfsContext} context\n * @param {string} path\n * @param {DefaultOptions} options\n */\nconst removePath = async (context, path, options) => {\n  const mfsPath = await toMfsPath(context, path, options)\n  const trail = await toTrail(context, mfsPath.mfsPath)\n  const child = trail[trail.length - 1]\n  trail.pop()\n  const parent = trail[trail.length - 1]\n\n  if (!parent) {\n    throw errCode(new Error(`${path} does not exist`), 'ERR_NOT_FOUND')\n  }\n\n  if (child.type === 'directory' && !options.recursive) {\n    throw errCode(new Error(`${path} is a directory, use -r to remove directories`), 'ERR_WAS_DIR')\n  }\n\n  const {\n    cid\n  } = await removeLink(context, {\n    parentCid: parent.cid,\n    name: child.name,\n    hashAlg: options.hashAlg,\n    cidVersion: options.cidVersion,\n    flush: options.flush,\n    shardSplitThreshold: options.shardSplitThreshold\n  })\n\n  parent.cid = cid\n\n  // update the tree with the new child\n  const newRootCid = await updateTree(context, trail, options)\n\n  // Update the MFS record with the new CID for the root of the tree\n  await updateMfsRoot(context, newRootCid, options)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/files/stat.js",
    "content": "import mergeOpts from 'merge-options'\nimport { toMfsPath } from './utils/to-mfs-path.js'\nimport { exporter } from 'ipfs-unixfs-exporter'\nimport { logger } from '@libp2p/logger'\nimport errCode from 'err-code'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport * as dagPB from '@ipld/dag-pb'\n\nconst mergeOptions = mergeOpts.bind({ ignoreUndefined: true })\nconst log = logger('ipfs:mfs:stat')\n\n/**\n * @typedef {import('./').MfsContext} MfsContext\n * @typedef {object} DefaultOptions\n * @property {boolean} withLocal\n * @property {AbortSignal} [signal]\n * @property {number} [timeout]\n */\n\n/**\n * @type {DefaultOptions}\n */\nconst defaultOptions = {\n  withLocal: false\n}\n\n/**\n * @typedef {import('ipfs-core-types/src/files').StatResult} StatResult\n */\n\n/**\n * @param {MfsContext} context\n */\nexport function createStat (context) {\n  /**\n   * @type {import('ipfs-core-types/src/files').API<{}>[\"stat\"]}\n   */\n  async function mfsStat (path, options = {}) {\n    /** @type {DefaultOptions} */\n    options = mergeOptions(defaultOptions, options)\n\n    log(`Fetching stats for ${path}`)\n\n    const {\n      type,\n      cid,\n      mfsPath\n    } = await toMfsPath(context, path, options)\n\n    const exportPath = type === 'ipfs' && cid ? cid : mfsPath\n    let file\n\n    try {\n      file = await exporter(exportPath, context.repo.blocks)\n    } catch (/** @type {any} */ err) {\n      if (err.code === 'ERR_NOT_FOUND') {\n        throw errCode(new Error(`${path} does not exist`), 'ERR_NOT_FOUND')\n      }\n\n      throw err\n    }\n\n    if (!statters[file.type]) {\n      throw new Error(`Cannot stat codec ${file.cid.code}`)\n    }\n\n    return statters[file.type](file)\n  }\n\n  return withTimeoutOption(mfsStat)\n}\n\n/** @type {Record<string, (file:any) => StatResult>} */\nconst statters = {\n  /**\n   * @param {import('ipfs-unixfs-exporter').RawNode} file\n   */\n  raw: (file) => {\n    return {\n      cid: file.cid,\n      size: file.node.length,\n      cumulativeSize: file.node.length,\n      blocks: 0,\n      type: 'file', // for go compatibility\n      local: undefined,\n      sizeLocal: undefined,\n      withLocality: false\n    }\n  },\n  /**\n   * @param {import('ipfs-unixfs-exporter').UnixFSFile} file\n   */\n  file: (file) => {\n    /** @type {StatResult} */\n    const stat = {\n      cid: file.cid,\n      type: 'file',\n      size: file.unixfs.fileSize(),\n      cumulativeSize: dagPB.encode(file.node).length + (file.node.Links || []).reduce((acc, curr) => acc + (curr.Tsize || 0), 0),\n      blocks: file.unixfs.blockSizes.length,\n      local: undefined,\n      sizeLocal: undefined,\n      withLocality: false,\n      mode: file.unixfs.mode\n    }\n\n    if (file.unixfs.mtime) {\n      stat.mtime = file.unixfs.mtime\n    }\n\n    return stat\n  },\n  /**\n   * @param {import('ipfs-unixfs-exporter').UnixFSDirectory} file\n   */\n  directory: (file) => {\n    /** @type {StatResult} */\n    const stat = {\n      cid: file.cid,\n      type: 'directory',\n      size: 0,\n      cumulativeSize: dagPB.encode(file.node).length + (file.node.Links || []).reduce((acc, curr) => acc + (curr.Tsize || 0), 0),\n      blocks: file.node.Links.length,\n      local: undefined,\n      sizeLocal: undefined,\n      withLocality: false,\n      mode: file.unixfs.mode\n    }\n\n    if (file.unixfs.mtime) {\n      stat.mtime = file.unixfs.mtime\n    }\n\n    return stat\n  },\n  /**\n   * @param {import('ipfs-unixfs-exporter').ObjectNode} file\n   */\n  object: (file) => {\n    /** @type {StatResult} */\n    return {\n      cid: file.cid,\n      size: file.node.length,\n      cumulativeSize: file.node.length,\n      type: 'file', // for go compatibility\n      blocks: 0,\n      local: undefined,\n      sizeLocal: undefined,\n      withLocality: false\n    }\n  },\n  /**\n   * @param {import('ipfs-unixfs-exporter').IdentityNode} file\n   */\n  identity: (file) => {\n    /** @type {StatResult} */\n    return {\n      cid: file.cid,\n      size: file.node.length,\n      cumulativeSize: file.node.length,\n      blocks: 0,\n      type: 'file', // for go compatibility\n      local: undefined,\n      sizeLocal: undefined,\n      withLocality: false\n    }\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/files/touch.js",
    "content": "import mergeOpts from 'merge-options'\nimport { toMfsPath } from './utils/to-mfs-path.js'\nimport { logger } from '@libp2p/logger'\nimport errCode from 'err-code'\nimport { UnixFS } from 'ipfs-unixfs'\nimport { toTrail } from './utils/to-trail.js'\nimport { addLink } from './utils/add-link.js'\nimport { updateTree } from './utils/update-tree.js'\nimport { updateMfsRoot } from './utils/update-mfs-root.js'\nimport * as dagPB from '@ipld/dag-pb'\nimport { CID } from 'multiformats/cid'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\nconst mergeOptions = mergeOpts.bind({ ignoreUndefined: true })\nconst log = logger('ipfs:mfs:touch')\n\n/**\n * @typedef {import('multiformats/cid').Version} CIDVersion\n * @typedef {import('ipfs-unixfs').MtimeLike} MtimeLike\n * @typedef {import('./').MfsContext} MfsContext\n * @typedef {object} DefaultOptions\n * @property {boolean} flush\n * @property {number} shardSplitThreshold\n * @property {CIDVersion} cidVersion\n * @property {string} hashAlg\n * @property {MtimeLike} [mtime]\n * @property {AbortSignal} [signal]\n * @property {number} [timeout]\n */\n\n/**\n * @type {DefaultOptions}\n */\nconst defaultOptions = {\n  flush: true,\n  shardSplitThreshold: 1000,\n  cidVersion: 0,\n  hashAlg: 'sha2-256'\n}\n\n/**\n * @param {MfsContext} context\n */\nexport function createTouch (context) {\n  /**\n   * @type {import('ipfs-core-types/src/files').API<{}>[\"touch\"]}\n   */\n  async function mfsTouch (path, options = {}) {\n    /** @type {DefaultOptions} */\n    const settings = mergeOptions(defaultOptions, options)\n    settings.mtime = settings.mtime || new Date()\n\n    log(`Touching ${path} mtime: ${settings.mtime}`)\n\n    const {\n      cid,\n      mfsDirectory,\n      name,\n      exists\n    } = await toMfsPath(context, path, settings)\n\n    const hashAlg = options.hashAlg || defaultOptions.hashAlg\n    const hasher = await context.hashers.getHasher(hashAlg)\n\n    let updatedBlock\n    let updatedCid\n\n    let cidVersion = settings.cidVersion\n\n    if (!exists) {\n      const metadata = new UnixFS({\n        type: 'file',\n        mtime: settings.mtime\n      })\n      updatedBlock = dagPB.encode({ Data: metadata.marshal(), Links: [] })\n\n      const hash = await hasher.digest(updatedBlock)\n\n      updatedCid = CID.create(settings.cidVersion, dagPB.code, hash)\n\n      if (settings.flush) {\n        await context.repo.blocks.put(updatedCid, updatedBlock)\n      }\n    } else {\n      if (cid.code !== dagPB.code) {\n        throw errCode(new Error(`${path} was not a UnixFS node`), 'ERR_NOT_UNIXFS')\n      }\n\n      cidVersion = cid.version\n\n      const block = await context.repo.blocks.get(cid)\n      const node = dagPB.decode(block)\n\n      if (!node.Data) {\n        throw errCode(new Error(`${path} had no data`), 'ERR_INVALID_NODE')\n      }\n\n      const metadata = UnixFS.unmarshal(node.Data)\n\n      // @ts-expect-error TODO: restore setting all date types as mtime - it's in the code, just not the signature\n      metadata.mtime = settings.mtime\n\n      updatedBlock = dagPB.encode({\n        Data: metadata.marshal(),\n        Links: node.Links\n      })\n\n      const hash = await hasher.digest(updatedBlock)\n      updatedCid = CID.create(settings.cidVersion, dagPB.code, hash)\n\n      if (settings.flush) {\n        await context.repo.blocks.put(updatedCid, updatedBlock)\n      }\n    }\n\n    const trail = await toTrail(context, mfsDirectory)\n    const parent = trail[trail.length - 1]\n    const parentCid = parent.cid\n    const parentBlock = await context.repo.blocks.get(parentCid)\n    const parentNode = dagPB.decode(parentBlock)\n\n    const result = await addLink(context, {\n      parent: parentNode,\n      name: name,\n      cid: updatedCid,\n      size: updatedBlock.length,\n      flush: settings.flush,\n      shardSplitThreshold: settings.shardSplitThreshold,\n      hashAlg: settings.hashAlg,\n      cidVersion\n    })\n\n    parent.cid = result.cid\n\n    // update the tree with the new child\n    const newRootCid = await updateTree(context, trail, settings)\n\n    // Update the MFS record with the new CID for the root of the tree\n    await updateMfsRoot(context, newRootCid, settings)\n  }\n\n  return withTimeoutOption(mfsTouch)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/files/utils/add-link.js",
    "content": "import * as dagPB from '@ipld/dag-pb'\nimport { CID } from 'multiformats/cid'\nimport { logger } from '@libp2p/logger'\nimport { UnixFS } from 'ipfs-unixfs'\nimport { DirSharded } from './dir-sharded.js'\nimport {\n  updateHamtDirectory,\n  recreateHamtLevel,\n  recreateInitialHamtLevel,\n  createShard,\n  toPrefix,\n  addLinksToHamtBucket\n} from './hamt-utils.js'\nimport errCode from 'err-code'\nimport last from 'it-last'\n\nconst log = logger('ipfs:mfs:core:utils:add-link')\n\n/**\n * @typedef {import('ipfs-unixfs').Mtime} Mtime\n * @typedef {import('multiformats/cid').Version} CIDVersion\n * @typedef {import('hamt-sharding').Bucket<any>} Bucket\n * @typedef {import('../').MfsContext} MfsContext\n * @typedef {import('@ipld/dag-pb').PBNode} PBNode\n * @typedef {import('@ipld/dag-pb').PBLink} PBLink\n */\n\n/**\n * @param {MfsContext} context\n * @param {object} options\n * @param {CID} options.cid\n * @param {string} options.name\n * @param {number} options.size\n * @param {number} options.shardSplitThreshold\n * @param {string} options.hashAlg\n * @param {CIDVersion} options.cidVersion\n * @param {boolean} options.flush\n * @param {CID} [options.parentCid]\n * @param {PBNode} [options.parent]\n */\nexport async function addLink (context, options) {\n  let parent = options.parent\n\n  if (options.parentCid) {\n    const parentCid = CID.asCID(options.parentCid)\n    if (parentCid === null) {\n      throw errCode(new Error('Invalid CID passed to addLink'), 'EINVALIDPARENTCID')\n    }\n\n    if (parentCid.code !== dagPB.code) {\n      throw errCode(new Error('Unsupported codec. Only DAG-PB is supported'), 'EINVALIDPARENTCID')\n    }\n\n    log(`Loading parent node ${parentCid}`)\n    const block = await context.repo.blocks.get(parentCid)\n    parent = dagPB.decode(block)\n  }\n\n  if (!parent) {\n    throw errCode(new Error('No parent node or CID passed to addLink'), 'EINVALIDPARENT')\n  }\n\n  if (!options.cid) {\n    throw errCode(new Error('No child cid passed to addLink'), 'EINVALIDCHILDCID')\n  }\n\n  if (!options.name) {\n    throw errCode(new Error('No child name passed to addLink'), 'EINVALIDCHILDNAME')\n  }\n\n  if (!options.size && options.size !== 0) {\n    throw errCode(new Error('No child size passed to addLink'), 'EINVALIDCHILDSIZE')\n  }\n\n  if (!parent.Data) {\n    throw errCode(new Error('Parent node with no data passed to addLink'), 'ERR_INVALID_PARENT')\n  }\n\n  const meta = UnixFS.unmarshal(parent.Data)\n\n  if (meta.type === 'hamt-sharded-directory') {\n    log('Adding link to sharded directory')\n\n    return addToShardedDirectory(context, {\n      ...options,\n      parent\n    })\n  }\n\n  if (parent.Links.length >= options.shardSplitThreshold) {\n    log('Converting directory to sharded directory')\n\n    return convertToShardedDirectory(context, {\n      ...options,\n      parent,\n      mtime: meta.mtime,\n      mode: meta.mode\n    })\n  }\n\n  log(`Adding ${options.name} (${options.cid}) to regular directory`)\n\n  return addToDirectory(context, {\n    ...options,\n    parent\n  })\n}\n\n/**\n * @param {MfsContext} context\n * @param {object} options\n * @param {CID} options.cid\n * @param {string} options.name\n * @param {number} options.size\n * @param {PBNode} options.parent\n * @param {string} options.hashAlg\n * @param {CIDVersion} options.cidVersion\n * @param {boolean} options.flush\n * @param {Mtime} [options.mtime]\n * @param {number} [options.mode]\n */\nconst convertToShardedDirectory = async (context, options) => {\n  const result = await createShard(context, options.parent.Links.map(link => ({\n    name: (link.Name || ''),\n    size: link.Tsize || 0,\n    cid: link.Hash\n  })).concat({\n    name: options.name,\n    size: options.size,\n    cid: options.cid\n  }), options)\n\n  log(`Converted directory to sharded directory ${result.cid}`)\n\n  return result\n}\n\n/**\n * @param {MfsContext} context\n * @param {object} options\n * @param {CID} options.cid\n * @param {string} options.name\n * @param {number} options.size\n * @param {PBNode} options.parent\n * @param {string} options.hashAlg\n * @param {CIDVersion} options.cidVersion\n * @param {boolean} options.flush\n * @param {Mtime} [options.mtime]\n * @param {number} [options.mode]\n */\nconst addToDirectory = async (context, options) => {\n  // Remove existing link if it exists\n  const parentLinks = options.parent.Links.filter((link) => {\n    return link.Name !== options.name\n  })\n  parentLinks.push({\n    Name: options.name,\n    Tsize: options.size,\n    Hash: options.cid\n  })\n\n  if (!options.parent.Data) {\n    throw errCode(new Error('Parent node with no data passed to addToDirectory'), 'ERR_INVALID_PARENT')\n  }\n\n  const node = UnixFS.unmarshal(options.parent.Data)\n\n  let data\n  if (node.mtime) {\n    // Update mtime if previously set\n    const ms = Date.now()\n    const secs = Math.floor(ms / 1000)\n\n    node.mtime = {\n      secs: secs,\n      nsecs: (ms - (secs * 1000)) * 1000\n    }\n\n    data = node.marshal()\n  } else {\n    data = options.parent.Data\n  }\n  options.parent = dagPB.prepare({\n    Data: data,\n    Links: parentLinks\n  })\n\n  // Persist the new parent PbNode\n  const hasher = await context.hashers.getHasher(options.hashAlg)\n  const buf = dagPB.encode(options.parent)\n  const hash = await hasher.digest(buf)\n  const cid = CID.create(options.cidVersion, dagPB.code, hash)\n\n  if (options.flush) {\n    await context.repo.blocks.put(cid, buf)\n  }\n\n  return {\n    node: options.parent,\n    cid,\n    size: buf.length\n  }\n}\n\n/**\n * @param {MfsContext} context\n * @param {object} options\n * @param {CID} options.cid\n * @param {string} options.name\n * @param {number} options.size\n * @param {PBNode} options.parent\n * @param {string} options.hashAlg\n * @param {CIDVersion} options.cidVersion\n * @param {boolean} options.flush\n */\nconst addToShardedDirectory = async (context, options) => {\n  const {\n    shard, path\n  } = await addFileToShardedDirectory(context, options)\n  const result = await last(shard.flush(context.repo.blocks))\n\n  if (!result) {\n    throw new Error('No result from flushing shard')\n  }\n\n  const block = await context.repo.blocks.get(result.cid)\n  const node = dagPB.decode(block)\n\n  // we have written out the shard, but only one sub-shard will have been written so replace it in the original shard\n  const parentLinks = options.parent.Links.filter((link) => {\n    // TODO vmx 2021-03-31: Check that there cannot be multiple ones matching\n    // Remove the old link\n    return (link.Name || '').substring(0, 2) !== path[0].prefix\n  })\n\n  const newLink = node.Links\n    .find(link => (link.Name || '').substring(0, 2) === path[0].prefix)\n\n  if (!newLink) {\n    throw new Error(`No link found with prefix ${path[0].prefix}`)\n  }\n\n  parentLinks.push(newLink)\n\n  return updateHamtDirectory(context, parentLinks, path[0].bucket, options)\n}\n\n/**\n * @param {MfsContext} context\n * @param {object} options\n * @param {CID} options.cid\n * @param {string} options.name\n * @param {number} options.size\n * @param {PBNode} options.parent\n * @param {string} options.hashAlg\n * @param {CIDVersion} options.cidVersion\n */\nconst addFileToShardedDirectory = async (context, options) => {\n  const file = {\n    name: options.name,\n    cid: options.cid,\n    size: options.size\n  }\n\n  if (!options.parent.Data) {\n    throw errCode(new Error('Parent node with no data passed to addFileToShardedDirectory'), 'ERR_INVALID_PARENT')\n  }\n\n  // start at the root bucket and descend, loading nodes as we go\n  const rootBucket = await recreateInitialHamtLevel(options.parent.Links)\n  const node = UnixFS.unmarshal(options.parent.Data)\n\n  const shard = new DirSharded({\n    root: true,\n    dir: true,\n    parent: undefined,\n    parentKey: undefined,\n    path: '',\n    dirty: true,\n    flat: false,\n    mode: node.mode\n  }, options)\n  shard._bucket = rootBucket\n\n  if (node.mtime) {\n    // update mtime if previously set\n    shard.mtime = {\n      secs: Math.round(Date.now() / 1000)\n    }\n  }\n\n  // load subshards until the bucket & position no longer changes\n  const position = await rootBucket._findNewBucketAndPos(file.name)\n  const path = toBucketPath(position)\n  path[0].node = options.parent\n  let index = 0\n\n  while (index < path.length) {\n    const segment = path[index]\n    index++\n    const node = segment.node\n\n    if (!node) {\n      throw new Error('Segment had no node')\n    }\n\n    const link = node.Links\n      .find(link => (link.Name || '').substring(0, 2) === segment.prefix)\n\n    if (!link) {\n      // prefix is new, file will be added to the current bucket\n      log(`Link ${segment.prefix}${file.name} will be added`)\n      index = path.length\n\n      break\n    }\n\n    if (link.Name === `${segment.prefix}${file.name}`) {\n      // file already existed, file will be added to the current bucket\n      log(`Link ${segment.prefix}${file.name} will be replaced`)\n      index = path.length\n\n      break\n    }\n\n    if ((link.Name || '').length > 2) {\n      // another file had the same prefix, will be replaced with a subshard\n      log(`Link ${link.Name} ${link.Hash} will be replaced with a subshard`)\n      index = path.length\n\n      break\n    }\n\n    // load sub-shard\n    log(`Found subshard ${segment.prefix}`)\n    const block = await context.repo.blocks.get(link.Hash)\n    const subShard = dagPB.decode(block)\n\n    // subshard hasn't been loaded, descend to the next level of the HAMT\n    if (!path[index]) {\n      log(`Loaded new subshard ${segment.prefix}`)\n      await recreateHamtLevel(context, subShard.Links, rootBucket, segment.bucket, parseInt(segment.prefix, 16))\n\n      const position = await rootBucket._findNewBucketAndPos(file.name)\n\n      path.push({\n        bucket: position.bucket,\n        prefix: toPrefix(position.pos),\n        node: subShard\n      })\n\n      break\n    }\n\n    const nextSegment = path[index]\n\n    // add next levels worth of links to bucket\n    await addLinksToHamtBucket(context, subShard.Links, nextSegment.bucket, rootBucket)\n\n    nextSegment.node = subShard\n  }\n\n  // finally add the new file into the shard\n  await shard._bucket.put(file.name, {\n    size: file.size,\n    cid: file.cid\n  })\n\n  return {\n    shard, path\n  }\n}\n\n/**\n * @param {{ pos: number, bucket: Bucket }} position\n * @returns {{ bucket: Bucket, prefix: string, node?: PBNode }[]}\n */\nconst toBucketPath = (position) => {\n  const path = [{\n    bucket: position.bucket,\n    prefix: toPrefix(position.pos)\n  }]\n\n  let bucket = position.bucket._parent\n  let positionInBucket = position.bucket._posAtParent\n\n  while (bucket) {\n    path.push({\n      bucket,\n      prefix: toPrefix(positionInBucket)\n    })\n\n    positionInBucket = bucket._posAtParent\n    bucket = bucket._parent\n  }\n\n  path.reverse()\n\n  return path\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/files/utils/create-lock.js",
    "content": "import mortice from 'mortice'\n\n/**\n * @typedef {object} Lock\n * @property {(fn: (...args: any) => any) => (...args: any) => any} readLock\n * @property {(fn: (...args: any) => any) => (...args: any) => any} writeLock\n */\n\n/** @type {Lock} */\nlet lock\n\n/**\n * @param {boolean} [repoOwner]\n */\nexport function createLock (repoOwner = false) {\n  if (lock) {\n    return lock\n  }\n\n  const mutex = mortice({\n    // ordinarily the main thread would store the read/write lock but\n    // if we are the thread that owns the repo, we can store the lock\n    // on this process even if we are a worker thread\n    singleProcess: repoOwner\n  })\n\n  lock = {\n    readLock: (func) => {\n      return async (...args) => {\n        const releaseLock = await mutex.readLock()\n\n        try {\n          return await func.apply(null, args)\n        } finally {\n          releaseLock()\n        }\n      }\n    },\n\n    writeLock: (func) => {\n      return async (...args) => {\n        const releaseLock = await mutex.writeLock()\n\n        try {\n          return await func.apply(null, args)\n        } finally {\n          releaseLock()\n        }\n      }\n    }\n  }\n\n  return lock\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/files/utils/create-node.js",
    "content": "import { UnixFS } from 'ipfs-unixfs'\nimport * as dagPB from '@ipld/dag-pb'\nimport { CID } from 'multiformats/cid'\n\n/**\n * @typedef {import('ipfs-unixfs').MtimeLike} MtimeLike\n * @typedef {import('multiformats/cid').Version} CIDVersion\n * @typedef {import('../').MfsContext} MfsContext\n */\n\n/**\n * @param {MfsContext} context\n * @param {'file' | 'directory'} type\n * @param {object} options\n * @param {string} options.hashAlg\n * @param {CIDVersion} options.cidVersion\n * @param {boolean} options.flush\n * @param {MtimeLike} [options.mtime]\n * @param {number} [options.mode]\n */\nexport async function createNode (context, type, options) {\n  const metadata = new UnixFS({\n    type,\n    mode: options.mode,\n    mtime: options.mtime\n  })\n\n  // Persist the new parent PBNode\n  const hasher = await context.hashers.getHasher(options.hashAlg)\n  const node = {\n    Data: metadata.marshal(),\n    Links: []\n  }\n  const buf = dagPB.encode(node)\n  const hash = await hasher.digest(buf)\n  const cid = CID.create(options.cidVersion, dagPB.code, hash)\n\n  if (options.flush) {\n    await context.repo.blocks.put(cid, buf)\n  }\n\n  return {\n    cid,\n    node\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/files/utils/dir-sharded.js",
    "content": "import { encode, prepare } from '@ipld/dag-pb'\nimport { UnixFS } from 'ipfs-unixfs'\nimport { persist } from './persist.js'\nimport { createHAMT, Bucket } from 'hamt-sharding'\nimport {\n  hamtHashCode,\n  hamtHashFn,\n  hamtBucketBits\n} from './hamt-constants.js'\n\n/**\n * @typedef {import('ipfs-unixfs-importer').ImporterOptions} ImporterOptions\n * @typedef {import('interface-blockstore').Blockstore} Blockstore\n * @typedef {import('multiformats/cid').CID} CID\n * @typedef {import('ipfs-unixfs').Mtime} Mtime\n *\n * @typedef {object} ImportResult\n * @property {CID} cid\n * @property {import('@ipld/dag-pb').PBNode} node\n * @property {number} size\n *\n * @typedef {object} DirContents\n * @property {CID} [cid]\n * @property {number} [size]\n *\n * @typedef {object} DirOptions\n * @property {Mtime} [mtime]\n * @property {number} [mode]\n * @property {import('multiformats/codecs/interface').BlockCodec<any, any>} [codec]\n * @property {import('multiformats/cid').Version} [cidVersion]\n * @property {boolean} [onlyHash]\n * @property {AbortSignal} [signal]\n */\n\n/**\n * @typedef {object} DirProps\n * @property {boolean} root\n * @property {boolean} dir\n * @property {string} path\n * @property {boolean} dirty\n * @property {boolean} flat\n * @property {Dir} [parent]\n * @property {string} [parentKey]\n * @property {import('ipfs-unixfs').UnixFS} [unixfs]\n * @property {number} [mode]\n * @property {import('ipfs-unixfs').Mtime} [mtime]\n */\nexport class Dir {\n  /**\n   * @param {DirProps} props\n   * @param {DirOptions} options\n   */\n  constructor (props, options) {\n    this.options = options || {}\n    this.root = props.root\n    this.dir = props.dir\n    this.path = props.path\n    this.dirty = props.dirty\n    this.flat = props.flat\n    this.parent = props.parent\n    this.parentKey = props.parentKey\n    this.unixfs = props.unixfs\n    this.mode = props.mode\n    this.mtime = props.mtime\n    /** @type {CID | undefined} */\n    this.cid = undefined\n    /** @type {number | undefined} */\n    this.size = undefined\n  }\n\n  /**\n   * @param {string} name\n   * @param {DirContents} value\n   */\n  async put (name, value) { }\n  /**\n   * @param {string} name\n   * @returns {Promise<DirContents | undefined>}\n   */\n  get (name) {\n    return Promise.resolve(this)\n  }\n\n  /**\n   * @returns {AsyncIterable<{ key: string, child: DirContents}>}\n   */\n  async * eachChildSeries () { }\n  /**\n   * @param {Blockstore} blockstore\n   * @returns {AsyncIterable<ImportResult>}\n   */\n  async * flush (blockstore) { }\n}\n\nexport class DirSharded extends Dir {\n  /**\n   * @param {DirProps} props\n   * @param {DirOptions} options\n   */\n  constructor (props, options) {\n    super(props, options)\n\n    /** @type {Bucket<DirContents>} */\n    this._bucket = createHAMT({\n      hashFn: hamtHashFn,\n      bits: hamtBucketBits\n    })\n  }\n\n  /**\n   * @param {string} name\n   * @param {DirContents} value\n   */\n  async put (name, value) {\n    await this._bucket.put(name, value)\n  }\n\n  /**\n   * @param {string} name\n   */\n  get (name) {\n    return this._bucket.get(name)\n  }\n\n  childCount () {\n    return this._bucket.leafCount()\n  }\n\n  directChildrenCount () {\n    return this._bucket.childrenCount()\n  }\n\n  onlyChild () {\n    return this._bucket.onlyChild()\n  }\n\n  async * eachChildSeries () {\n    for await (const { key, value } of this._bucket.eachLeafSeries()) {\n      yield {\n        key,\n        child: value\n      }\n    }\n  }\n\n  /**\n   * @param {Blockstore} blockstore\n   * @returns {AsyncIterable<ImportResult>}\n   */\n  async * flush (blockstore) {\n    yield * flush(this._bucket, blockstore, this, this.options)\n  }\n}\n\n/**\n * @param {Bucket<?>} bucket\n * @param {Blockstore} blockstore\n * @param {*} shardRoot\n * @param {DirOptions} options\n * @returns {AsyncIterable<ImportResult>}\n */\nasync function * flush (bucket, blockstore, shardRoot, options) {\n  const children = bucket._children\n  const links = []\n  let childrenSize = 0\n\n  for (let i = 0; i < children.length; i++) {\n    const child = children.get(i)\n\n    if (!child) {\n      continue\n    }\n\n    const labelPrefix = i.toString(16).toUpperCase().padStart(2, '0')\n\n    if (child instanceof Bucket) {\n      let shard\n\n      for await (const subShard of await flush(child, blockstore, null, options)) {\n        shard = subShard\n      }\n\n      if (!shard) {\n        throw new Error('Could not flush sharded directory, no subshard found')\n      }\n\n      links.push({\n        Name: labelPrefix,\n        Tsize: shard.size,\n        Hash: shard.cid\n      })\n      childrenSize += shard.size\n    } else if (typeof child.value.flush === 'function') {\n      const dir = child.value\n      let flushedDir\n\n      for await (const entry of dir.flush(blockstore)) {\n        flushedDir = entry\n\n        yield flushedDir\n      }\n\n      const label = labelPrefix + child.key\n      links.push({\n        Name: label,\n        Tsize: flushedDir.size,\n        Hash: flushedDir.cid\n      })\n\n      childrenSize += flushedDir.size\n    } else {\n      const value = child.value\n\n      if (!value.cid) {\n        continue\n      }\n\n      const label = labelPrefix + child.key\n      const size = value.size\n\n      links.push({\n        Name: label,\n        Tsize: size,\n        Hash: value.cid\n      })\n      childrenSize += size\n    }\n  }\n\n  // go-ipfs uses little endian, that's why we have to\n  // reverse the bit field before storing it\n  const data = Uint8Array.from(children.bitField().reverse())\n  const dir = new UnixFS({\n    type: 'hamt-sharded-directory',\n    data,\n    fanout: bucket.tableSize(),\n    hashType: hamtHashCode,\n    mtime: shardRoot && shardRoot.mtime,\n    mode: shardRoot && shardRoot.mode\n  })\n\n  const node = {\n    Data: dir.marshal(),\n    Links: links\n  }\n  const buffer = encode(prepare(node))\n  const cid = await persist(buffer, blockstore, options)\n  const size = buffer.length + childrenSize\n\n  yield {\n    cid,\n    node,\n    size\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/files/utils/hamt-constants.js",
    "content": "import { murmur3128 } from '@multiformats/murmur3'\n\nexport const hamtHashCode = murmur3128.code\nexport const hamtBucketBits = 8\n\n/**\n * @param {Uint8Array} buf\n */\nexport async function hamtHashFn (buf) {\n  return (await murmur3128.encode(buf))\n    // Murmur3 outputs 128 bit but, accidentally, IPFS Go's\n    // implementation only uses the first 64, so we must do the same\n    // for parity..\n    .subarray(0, 8)\n    // Invert buffer because that's how Go impl does it\n    .reverse()\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/files/utils/hamt-utils.js",
    "content": "import * as dagPB from '@ipld/dag-pb'\nimport {\n  Bucket,\n  createHAMT\n} from 'hamt-sharding'\nimport { DirSharded } from './dir-sharded.js'\nimport { logger } from '@libp2p/logger'\nimport { UnixFS } from 'ipfs-unixfs'\nimport last from 'it-last'\nimport { CID } from 'multiformats/cid'\nimport {\n  hamtHashCode,\n  hamtHashFn,\n  hamtBucketBits\n} from './hamt-constants.js'\n\nconst log = logger('ipfs:mfs:core:utils:hamt-utils')\n\n/**\n * @typedef {import('multiformats/cid').Version} CIDVersion\n * @typedef {import('ipfs-unixfs').Mtime} Mtime\n * @typedef {import('../').MfsContext} MfsContext\n * @typedef {import('@ipld/dag-pb').PBNode} PBNode\n * @typedef {import('@ipld/dag-pb').PBLink} PBLink\n */\n\n/**\n * @param {MfsContext} context\n * @param {PBLink[]} links\n * @param {Bucket<any>} bucket\n * @param {object} options\n * @param {PBNode} options.parent\n * @param {CIDVersion} options.cidVersion\n * @param {boolean} options.flush\n * @param {string} options.hashAlg\n */\nexport const updateHamtDirectory = async (context, links, bucket, options) => {\n  if (!options.parent.Data) {\n    throw new Error('Could not update HAMT directory because parent had no data')\n  }\n\n  // update parent with new bit field\n  const data = Uint8Array.from(bucket._children.bitField().reverse())\n  const node = UnixFS.unmarshal(options.parent.Data)\n  const dir = new UnixFS({\n    type: 'hamt-sharded-directory',\n    data,\n    fanout: bucket.tableSize(),\n    hashType: hamtHashCode,\n    mode: node.mode,\n    mtime: node.mtime\n  })\n\n  const hasher = await context.hashers.getHasher(options.hashAlg)\n  const parent = {\n    Data: dir.marshal(),\n    Links: links.sort((a, b) => (a.Name || '').localeCompare(b.Name || ''))\n  }\n  const buf = dagPB.encode(parent)\n  const hash = await hasher.digest(buf)\n  const cid = CID.create(options.cidVersion, dagPB.code, hash)\n\n  if (options.flush) {\n    await context.repo.blocks.put(cid, buf)\n  }\n\n  return {\n    node: parent,\n    cid,\n    size: links.reduce((sum, link) => sum + (link.Tsize || 0), buf.length)\n  }\n}\n\n/**\n * @param {MfsContext} context\n * @param {PBLink[]} links\n * @param {Bucket<any>} rootBucket\n * @param {Bucket<any>} parentBucket\n * @param {number} positionAtParent\n */\nexport const recreateHamtLevel = async (context, links, rootBucket, parentBucket, positionAtParent) => {\n  // recreate this level of the HAMT\n  const bucket = new Bucket({\n    hash: rootBucket._options.hash,\n    bits: rootBucket._options.bits\n  }, parentBucket, positionAtParent)\n  parentBucket._putObjectAt(positionAtParent, bucket)\n\n  await addLinksToHamtBucket(context, links, bucket, rootBucket)\n\n  return bucket\n}\n\n/**\n * @param {PBLink[]} links\n */\nexport const recreateInitialHamtLevel = async (links) => {\n  const bucket = createHAMT({\n    hashFn: hamtHashFn,\n    bits: hamtBucketBits\n  })\n\n  // populate sub bucket but do not recurse as we do not want to pull whole shard in\n  await Promise.all(\n    links.map(async link => {\n      const linkName = (link.Name || '')\n\n      if (linkName.length === 2) {\n        const pos = parseInt(linkName, 16)\n\n        const subBucket = new Bucket({\n          hash: bucket._options.hash,\n          bits: bucket._options.bits\n        }, bucket, pos)\n        bucket._putObjectAt(pos, subBucket)\n\n        return Promise.resolve()\n      }\n\n      return bucket.put(linkName.substring(2), {\n        size: link.Tsize,\n        cid: link.Hash\n      })\n    })\n  )\n\n  return bucket\n}\n\n/**\n * @param {MfsContext} context\n * @param {PBLink[]} links\n * @param {Bucket<any>} bucket\n * @param {Bucket<any>} rootBucket\n */\nexport const addLinksToHamtBucket = async (context, links, bucket, rootBucket) => {\n  await Promise.all(\n    links.map(async link => {\n      const linkName = (link.Name || '')\n\n      if (linkName.length === 2) {\n        log('Populating sub bucket', linkName)\n        const pos = parseInt(linkName, 16)\n        const block = await context.repo.blocks.get(link.Hash)\n        const node = dagPB.decode(block)\n\n        const subBucket = new Bucket({\n          hash: rootBucket._options.hash,\n          bits: rootBucket._options.bits\n        }, bucket, pos)\n        bucket._putObjectAt(pos, subBucket)\n\n        await addLinksToHamtBucket(context, node.Links, subBucket, rootBucket)\n\n        return Promise.resolve()\n      }\n\n      return rootBucket.put(linkName.substring(2), {\n        size: link.Tsize,\n        cid: link.Hash\n      })\n    })\n  )\n}\n\n/**\n * @param {number} position\n */\nexport const toPrefix = (position) => {\n  return position\n    .toString(16)\n    .toUpperCase()\n    .padStart(2, '0')\n    .substring(0, 2)\n}\n\n/**\n * @param {MfsContext} context\n * @param {string} fileName\n * @param {PBNode} rootNode\n */\nexport const generatePath = async (context, fileName, rootNode) => {\n  // start at the root bucket and descend, loading nodes as we go\n  const rootBucket = await recreateInitialHamtLevel(rootNode.Links)\n  const position = await rootBucket._findNewBucketAndPos(fileName)\n\n  // the path to the root bucket\n  /** @type {{ bucket: Bucket<any>, prefix: string, node?: PBNode }[]} */\n  const path = [{\n    bucket: position.bucket,\n    prefix: toPrefix(position.pos)\n  }]\n  let currentBucket = position.bucket\n\n  while (currentBucket !== rootBucket) {\n    path.push({\n      bucket: currentBucket,\n      prefix: toPrefix(currentBucket._posAtParent)\n    })\n\n    // @ts-expect-error - only the root bucket's parent will be undefined\n    currentBucket = currentBucket._parent\n  }\n\n  path.reverse()\n  path[0].node = rootNode\n\n  // load PbNode for each path segment\n  for (let i = 0; i < path.length; i++) {\n    const segment = path[i]\n\n    if (!segment.node) {\n      throw new Error('Could not generate HAMT path')\n    }\n\n    // find prefix in links\n    const link = segment.node.Links\n      .filter(link => (link.Name || '').substring(0, 2) === segment.prefix)\n      .pop()\n\n    // entry was not in shard\n    if (!link) {\n      // reached bottom of tree, file will be added to the current bucket\n      log(`Link ${segment.prefix}${fileName} will be added`)\n      // return path\n      continue\n    }\n\n    // found entry\n    if (link.Name === `${segment.prefix}${fileName}`) {\n      log(`Link ${segment.prefix}${fileName} will be replaced`)\n      // file already existed, file will be added to the current bucket\n      // return path\n      continue\n    }\n\n    // found subshard\n    log(`Found subshard ${segment.prefix}`)\n    const block = await context.repo.blocks.get(link.Hash)\n    const node = dagPB.decode(block)\n\n    // subshard hasn't been loaded, descend to the next level of the HAMT\n    if (!path[i + 1]) {\n      log(`Loaded new subshard ${segment.prefix}`)\n\n      await recreateHamtLevel(context, node.Links, rootBucket, segment.bucket, parseInt(segment.prefix, 16))\n      const position = await rootBucket._findNewBucketAndPos(fileName)\n\n      // i--\n      path.push({\n        bucket: position.bucket,\n        prefix: toPrefix(position.pos),\n        node: node\n      })\n\n      continue\n    }\n\n    const nextSegment = path[i + 1]\n\n    // add intermediate links to bucket\n    await addLinksToHamtBucket(context, node.Links, nextSegment.bucket, rootBucket)\n\n    nextSegment.node = node\n  }\n\n  await rootBucket.put(fileName, true)\n\n  path.reverse()\n\n  return {\n    rootBucket,\n    path\n  }\n}\n\n/**\n * @param {MfsContext} context\n * @param {{ name: string, size: number, cid: CID }[]} contents\n * @param {object} [options]\n * @param {Mtime} [options.mtime]\n * @param {number} [options.mode]\n */\nexport const createShard = async (context, contents, options = {}) => {\n  const shard = new DirSharded({\n    root: true,\n    dir: true,\n    parent: undefined,\n    parentKey: undefined,\n    path: '',\n    dirty: true,\n    flat: false,\n    mtime: options.mtime,\n    mode: options.mode\n  }, options)\n\n  for (let i = 0; i < contents.length; i++) {\n    await shard._bucket.put(contents[i].name, {\n      size: contents[i].size,\n      cid: contents[i].cid\n    })\n  }\n\n  const res = await last(shard.flush(context.repo.blocks))\n\n  if (!res) {\n    throw new Error('Flushing shard yielded no result')\n  }\n\n  return res\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/files/utils/persist.js",
    "content": "import { CID } from 'multiformats/cid'\nimport * as dagPB from '@ipld/dag-pb'\nimport { sha256 } from 'multiformats/hashes/sha2'\n\n/**\n * @typedef {object} PersistOptions\n * @property {import('multiformats/codecs/interface').BlockCodec<any, any>} [codec]\n * @property {import('multiformats/hashes/interface').MultihashHasher} [hasher]\n * @property {import('multiformats/cid').Version} [cidVersion]\n * @property {boolean} [onlyHash]\n * @property {AbortSignal} [signal]\n */\n\n/**\n * @param {Uint8Array} buffer\n * @param {import('interface-blockstore').Blockstore} blockstore\n * @param {PersistOptions} options\n */\nexport const persist = async (buffer, blockstore, options) => {\n  if (!options.codec) {\n    options.codec = dagPB\n  }\n\n  if (!options.hasher) {\n    options.hasher = sha256\n  }\n\n  if (options.cidVersion === undefined) {\n    options.cidVersion = 1\n  }\n\n  if (options.codec === dagPB && options.hasher !== sha256) {\n    options.cidVersion = 1\n  }\n\n  const multihash = await options.hasher.digest(buffer)\n  const cid = CID.create(options.cidVersion, options.codec.code, multihash)\n\n  if (!options.onlyHash) {\n    await blockstore.put(cid, buffer, {\n      signal: options.signal\n    })\n  }\n\n  return cid\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/files/utils/remove-link.js",
    "content": "\nimport * as dagPB from '@ipld/dag-pb'\nimport { CID } from 'multiformats/cid'\nimport { logger } from '@libp2p/logger'\nimport { UnixFS } from 'ipfs-unixfs'\nimport {\n  generatePath,\n  updateHamtDirectory\n} from './hamt-utils.js'\nimport errCode from 'err-code'\n\nconst log = logger('ipfs:mfs:core:utils:remove-link')\n\n/**\n * @typedef {import('../').MfsContext} MfsContext\n * @typedef {import('multiformats/cid').Version} CIDVersion\n * @typedef {import('hamt-sharding').Bucket<any>} Bucket\n * @typedef {import('@ipld/dag-pb').PBNode} PBNode\n *\n * @typedef {object} RemoveLinkOptions\n * @property {string} name\n * @property {number} shardSplitThreshold\n * @property {string} hashAlg\n * @property {CIDVersion} cidVersion\n * @property {boolean} flush\n * @property {CID} [parentCid]\n * @property {PBNode} [parent]\n *\n * @typedef {object} RemoveLinkOptionsInternal\n * @property {string} name\n * @property {number} shardSplitThreshold\n * @property {string} hashAlg\n * @property {CIDVersion} cidVersion\n * @property {boolean} flush\n * @property {PBNode} parent\n */\n\n/**\n * @param {MfsContext} context\n * @param {RemoveLinkOptions} options\n */\nexport async function removeLink (context, options) {\n  let parent = options.parent\n\n  if (options.parentCid) {\n    const parentCid = CID.asCID(options.parentCid)\n    if (parentCid === null) {\n      throw errCode(new Error('Invalid CID passed to removeLink'), 'EINVALIDPARENTCID')\n    }\n\n    log(`Loading parent node ${parentCid}`)\n    const block = await context.repo.blocks.get(parentCid)\n    parent = dagPB.decode(block)\n  }\n\n  if (!parent) {\n    throw errCode(new Error('No parent node or CID passed to removeLink'), 'EINVALIDPARENT')\n  }\n\n  if (!options.name) {\n    throw errCode(new Error('No child name passed to removeLink'), 'EINVALIDCHILDNAME')\n  }\n\n  if (!parent.Data) {\n    throw errCode(new Error('Parent node had no data'), 'ERR_INVALID_NODE')\n  }\n\n  const meta = UnixFS.unmarshal(parent.Data)\n\n  if (meta.type === 'hamt-sharded-directory') {\n    log(`Removing ${options.name} from sharded directory`)\n\n    return removeFromShardedDirectory(context, {\n      ...options,\n      parent\n    })\n  }\n\n  log(`Removing link ${options.name} regular directory`)\n\n  return removeFromDirectory(context, {\n    ...options,\n    parent\n  })\n}\n\n/**\n * @param {MfsContext} context\n * @param {RemoveLinkOptionsInternal} options\n */\nconst removeFromDirectory = async (context, options) => {\n  // Remove existing link if it exists\n  options.parent.Links = options.parent.Links.filter((link) => {\n    return link.Name !== options.name\n  })\n\n  const parentBlock = await dagPB.encode(options.parent)\n  const hasher = await context.hashers.getHasher(options.hashAlg)\n  const hash = await hasher.digest(parentBlock)\n  const parentCid = CID.create(options.cidVersion, dagPB.code, hash)\n\n  await context.repo.blocks.put(parentCid, parentBlock)\n\n  log(`Updated regular directory ${parentCid}`)\n\n  return {\n    node: options.parent,\n    cid: parentCid\n  }\n}\n\n/**\n * @param {MfsContext} context\n * @param {RemoveLinkOptionsInternal} options\n */\nconst removeFromShardedDirectory = async (context, options) => {\n  const {\n    rootBucket, path\n  } = await generatePath(context, options.name, options.parent)\n\n  await rootBucket.del(options.name)\n\n  const {\n    node\n  } = await updateShard(context, path, options.name, options)\n\n  return updateHamtDirectory(context, node.Links, rootBucket, options)\n}\n\n/**\n * @param {MfsContext} context\n * @param {{ bucket: Bucket, prefix: string, node?: PBNode }[]} positions\n * @param {string} name\n * @param {RemoveLinkOptionsInternal} options\n * @returns {Promise<{ node: PBNode, cid: CID, size: number }>}\n */\nconst updateShard = async (context, positions, name, options) => {\n  const last = positions.pop()\n\n  if (!last) {\n    throw errCode(new Error('Could not find parent'), 'EINVALIDPARENT')\n  }\n\n  const {\n    bucket,\n    prefix,\n    node\n  } = last\n\n  if (!node) {\n    throw errCode(new Error('Could not find parent'), 'EINVALIDPARENT')\n  }\n\n  const link = node.Links\n    .find(link => (link.Name || '').substring(0, 2) === prefix)\n\n  if (!link) {\n    throw errCode(new Error(`No link found with prefix ${prefix} for file ${name}`), 'ERR_NOT_FOUND')\n  }\n\n  if (link.Name === `${prefix}${name}`) {\n    log(`Removing existing link ${link.Name}`)\n\n    const links = node.Links.filter((nodeLink) => {\n      return nodeLink.Name !== link.Name\n    })\n\n    await bucket.del(name)\n\n    return updateHamtDirectory(context, links, bucket, options)\n  }\n\n  log(`Descending into sub-shard ${link.Name} for ${prefix}${name}`)\n\n  const result = await updateShard(context, positions, name, options)\n\n  let cid = result.cid\n  let size = result.size\n  let newName = prefix\n\n  if (result.node.Links.length === 1) {\n    log(`Removing subshard for ${prefix}`)\n\n    // convert shard back to normal dir\n    const link = result.node.Links[0]\n\n    newName = `${prefix}${(link.Name || '').substring(2)}`\n    cid = link.Hash\n    size = link.Tsize || 0\n  }\n\n  log(`Updating shard ${prefix} with name ${newName}`)\n\n  return updateShardParent(context, bucket, node, prefix, newName, size, cid, options)\n}\n\n/**\n * @param {MfsContext} context\n * @param {Bucket} bucket\n * @param {PBNode} parent\n * @param {string} oldName\n * @param {string} newName\n * @param {number} size\n * @param {CID} cid\n * @param {RemoveLinkOptionsInternal} options\n */\nconst updateShardParent = (context, bucket, parent, oldName, newName, size, cid, options) => {\n  // Remove existing link if it exists\n  const parentLinks = parent.Links.filter((link) => {\n    return link.Name !== oldName\n  })\n  parentLinks.push({\n    Name: newName,\n    Tsize: size,\n    Hash: cid\n  })\n\n  return updateHamtDirectory(context, parentLinks, bucket, options)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/files/utils/to-async-iterator.js",
    "content": "import errCode from 'err-code'\nimport { logger } from '@libp2p/logger'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport browserStreamToIt from 'browser-readablestream-to-it'\n\nconst log = logger('ipfs:mfs:utils:to-async-iterator')\n\n/**\n * @param {*} content\n */\nexport function toAsyncIterator (content) {\n  if (!content) {\n    throw errCode(new Error('paths must start with a leading slash'), 'ERR_INVALID_PATH')\n  }\n\n  if (typeof content === 'string' || content instanceof String) {\n    log('Content was a string')\n\n    content = uint8ArrayFromString(content.toString())\n  }\n\n  if (content.length) {\n    log('Content was array-like')\n\n    return {\n      [Symbol.asyncIterator]: function * bufferContent () {\n        yield content\n      }\n    }\n  }\n\n  if (content[Symbol.asyncIterator]) {\n    log('Content was an async iterator')\n    return content\n  }\n\n  if (content[Symbol.iterator]) {\n    log('Content was an iterator')\n    return content\n  }\n\n  if (global.Blob && content instanceof global.Blob) {\n    // HTML5 Blob objects (including Files)\n    log('Content was an HTML5 Blob')\n    return browserStreamToIt(content.stream())\n  }\n\n  throw errCode(new Error(`Don't know how to convert ${content} into an async iterator`), 'ERR_INVALID_PARAMS')\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/files/utils/to-mfs-path.js",
    "content": "import { loadMfsRoot } from './with-mfs-root.js'\nimport { toPathComponents } from './to-path-components.js'\nimport { exporter } from 'ipfs-unixfs-exporter'\nimport errCode from 'err-code'\nimport { CID } from 'multiformats/cid'\n\nconst IPFS_PREFIX = 'ipfs'\n\n/**\n * @typedef {import('ipfs-unixfs-exporter').UnixFSEntry} UnixFSEntry\n * @typedef {import('ipfs-unixfs-exporter').ExporterOptions} ExporterOptions\n * @typedef {import('../').MfsContext} MfsContext\n *\n * @typedef {object} FilePath\n * @property {'mfs' | 'ipfs'} type\n * @property {'file'} entryType\n * @property {number} depth\n * @property {string} mfsPath\n * @property {string} mfsDirectory\n * @property {string[]} parts\n * @property {string} path\n * @property {string} name\n * @property {CID} cid\n * @property {boolean} exists\n * @property {import('ipfs-unixfs').UnixFS} unixfs\n * @property {(options?: ExporterOptions) => AsyncIterable<Uint8Array>} content\n *\n * @typedef {object} DirectoryPath\n * @property {'mfs' | 'ipfs'} type\n * @property {'directory'} entryType\n * @property {number} depth\n * @property {string} mfsPath\n * @property {string} mfsDirectory\n * @property {string[]} parts\n * @property {string} path\n * @property {string} name\n * @property {CID} cid\n * @property {boolean} exists\n * @property {import('ipfs-unixfs').UnixFS} unixfs\n * @property {(options?: ExporterOptions) => AsyncIterable<UnixFSEntry>} content\n *\n * @typedef {object} ObjectPath\n * @property {'mfs' | 'ipfs'} type\n * @property {'object'} entryType\n * @property {number} depth\n * @property {string} mfsPath\n * @property {string} mfsDirectory\n * @property {string[]} parts\n * @property {string} path\n * @property {string} name\n * @property {CID} cid\n * @property {boolean} exists\n * @property {(options?: ExporterOptions) => AsyncIterable<any>} content\n *\n * @typedef {object} RawPath\n * @property {'mfs' | 'ipfs'} type\n * @property {'raw'} entryType\n * @property {number} depth\n * @property {string} mfsPath\n * @property {string} mfsDirectory\n * @property {string[]} parts\n * @property {string} path\n * @property {string} name\n * @property {CID} cid\n * @property {boolean} exists\n * @property {(options?: ExporterOptions) => AsyncIterable<Uint8Array>} content\n *\n * @typedef {object} IdentityPath\n * @property {'mfs' | 'ipfs'} type\n * @property {'identity'} entryType\n * @property {number} depth\n * @property {string} mfsPath\n * @property {string} mfsDirectory\n * @property {string[]} parts\n * @property {string} path\n * @property {string} name\n * @property {CID} cid\n * @property {boolean} exists\n * @property {(options?: ExporterOptions) => AsyncIterable<Uint8Array>} content\n *\n * @typedef {FilePath | DirectoryPath | ObjectPath | RawPath | IdentityPath} MfsPath\n */\n\n/**\n * @param {MfsContext} context\n * @param {string | CID} path\n * @param {import('ipfs-core-types/src/utils').AbortOptions} [options]\n */\nexport const toMfsPath = async (context, path, options) => {\n  const root = await loadMfsRoot(context, options)\n\n  /** @type {MfsPath} */\n  // @ts-expect-error fields get set later\n  let output = {\n    entryType: 'file'\n  }\n\n  let ipfsPath = ''\n\n  if (CID.asCID(path)) {\n    ipfsPath = `/ipfs/${path}`\n  } else {\n    ipfsPath = path.toString()\n  }\n\n  ipfsPath = ipfsPath.trim()\n  ipfsPath = ipfsPath.replace(/(\\/\\/+)/g, '/')\n\n  if (ipfsPath.endsWith('/') && ipfsPath.length > 1) {\n    ipfsPath = ipfsPath.substring(0, ipfsPath.length - 1)\n  }\n\n  if (!ipfsPath) {\n    throw errCode(new Error('paths must not be empty'), 'ERR_NO_PATH')\n  }\n\n  if (ipfsPath.substring(0, 1) !== '/') {\n    throw errCode(new Error('paths must start with a leading slash'), 'ERR_INVALID_PATH')\n  }\n\n  if (ipfsPath.substring(ipfsPath.length - 1) === '/') {\n    ipfsPath = ipfsPath.substring(0, ipfsPath.length - 1)\n  }\n\n  const pathComponents = toPathComponents(ipfsPath)\n\n  if (pathComponents[0] === IPFS_PREFIX) {\n    // e.g. /ipfs/QMfoo or /ipfs/Qmfoo/sub/path\n    let mfsDirectory\n\n    if (pathComponents.length === 2) {\n      mfsDirectory = `/${pathComponents.join('/')}`\n    } else {\n      mfsDirectory = `/${pathComponents.slice(0, pathComponents.length - 1).join('/')}`\n    }\n\n    // @ts-expect-error fields being set\n    output = {\n      type: 'ipfs',\n      depth: pathComponents.length - 2,\n      entryType: 'file',\n\n      mfsPath: `/${pathComponents.join('/')}`,\n      mfsDirectory,\n      parts: pathComponents,\n      path: `/${pathComponents.join('/')}`,\n      name: pathComponents[pathComponents.length - 1]\n    }\n  } else {\n    const mfsPath = `/${IPFS_PREFIX}/${root}${pathComponents.length ? '/' + pathComponents.join('/') : ''}`\n    const mfsDirectory = `/${IPFS_PREFIX}/${root}/${pathComponents.slice(0, pathComponents.length - 1).join('/')}`\n\n    // @ts-expect-error fields being set\n    output = {\n      type: 'mfs',\n      depth: pathComponents.length,\n      entryType: 'file',\n\n      mfsDirectory,\n      mfsPath,\n      parts: pathComponents,\n      path: `/${pathComponents.join('/')}`,\n      name: pathComponents[pathComponents.length - 1]\n    }\n  }\n\n  const cidPath = output.type === 'mfs' ? output.mfsPath : output.path\n\n  try {\n    const res = await exporter(cidPath, context.repo.blocks, options)\n\n    output.cid = res.cid\n    output.mfsPath = `/ipfs/${res.path}`\n    output.entryType = res.type\n    output.content = res.content\n\n    if ((output.entryType === 'file' || output.entryType === 'directory') && (res.type === 'file' || res.type === 'directory')) {\n      output.unixfs = res.unixfs\n    }\n  } catch (/** @type {any} */ err) {\n    if (err.code !== 'ERR_NOT_FOUND') {\n      throw err\n    }\n  }\n\n  output.exists = Boolean(output.cid)\n\n  return output\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/files/utils/to-path-components.js",
    "content": "\n/**\n * @param {string} [path]\n */\nexport function toPathComponents (path = '') {\n  // split on / unless escaped with \\\n  return (path\n    .trim()\n    .match(/([^\\\\^/]|\\\\\\/)+/g) || [])\n    .filter(Boolean)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/files/utils/to-trail.js",
    "content": "import { walkPath } from 'ipfs-unixfs-exporter'\nimport { logger } from '@libp2p/logger'\n\nconst log = logger('ipfs:mfs:utils:to-trail')\n\n/**\n * @typedef {import('../').MfsContext} MfsContext\n * @typedef {object} MfsTrail\n * @property {string} name\n * @property {import('multiformats/cid').CID} cid\n * @property {number} [size]\n * @property {string} [type]\n *\n * TODO: export supported types from unixfs-exporter and use for `type` above\n */\n\n/**\n * @param {MfsContext} context\n * @param {string} path\n * @returns {Promise<MfsTrail[]>}\n */\nexport async function toTrail (context, path) {\n  log(`Creating trail for path ${path}`)\n\n  const output = []\n\n  for await (const fsEntry of walkPath(path, context.repo.blocks)) {\n    output.push({\n      name: fsEntry.name,\n      cid: fsEntry.cid,\n      size: fsEntry.size,\n      type: fsEntry.type\n    })\n  }\n\n  return output\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/files/utils/update-mfs-root.js",
    "content": "import { logger } from '@libp2p/logger'\nimport {\n  MFS_ROOT_KEY\n} from '../../../utils.js'\nimport errCode from 'err-code'\n\nconst log = logger('ipfs:mfs:utils:update-mfs-root')\n\n/**\n * @typedef {import('../').MfsContext} MfsContext\n */\n\n/**\n * @param {MfsContext} context\n * @param {import('multiformats/cid').CID} cid\n * @param {import('ipfs-core-types/src/utils').AbortOptions} options\n */\nexport async function updateMfsRoot (context, cid, options) {\n  if (options && options.signal && options.signal.aborted) {\n    throw errCode(new Error('Request aborted'), 'ERR_ABORTED', { name: 'Aborted' })\n  }\n\n  log(`New MFS root will be ${cid}`)\n\n  await context.repo.datastore.put(MFS_ROOT_KEY, cid.bytes)\n\n  return cid\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/files/utils/update-tree.js",
    "content": "import { logger } from '@libp2p/logger'\nimport { addLink } from './add-link.js'\nimport {\n  decode\n} from '@ipld/dag-pb'\n\nconst log = logger('ipfs:mfs:utils:update-tree')\n\nconst defaultOptions = {\n  shardSplitThreshold: 1000\n}\n\n/**\n * @typedef {import('multiformats/cid').CID} CID\n * @typedef {import('multiformats/cid').Version} CIDVersion\n * @typedef {import('../').MfsContext} MfsContext\n * @typedef {import('./to-trail').MfsTrail} MfsTrail\n */\n\n/**\n * Loop backwards through the trail, replacing links of all components to update CIDs\n *\n * @param {MfsContext} context\n * @param {MfsTrail[]} trail\n * @param {object} options\n * @param {number} options.shardSplitThreshold\n * @param {string} options.hashAlg\n * @param {CIDVersion} options.cidVersion\n * @param {boolean} options.flush\n */\nexport async function updateTree (context, trail, options) {\n  options = Object.assign({}, defaultOptions, options)\n\n  log('Trail', trail)\n  trail = trail.slice().reverse()\n\n  let index = 0\n  let child\n\n  for await (const block of context.repo.blocks.getMany(trail.map(node => node.cid))) {\n    const node = decode(block)\n    const cid = trail[index].cid\n    const name = trail[index].name\n    index++\n\n    if (!child) {\n      child = {\n        cid,\n        name,\n        size: block.length\n      }\n\n      continue\n    }\n\n    /** @type {{ cid: CID, size: number }} */\n    const result = await addLink(context, {\n      parent: node,\n      name: child.name,\n      cid: child.cid,\n      // TODO vmx 2021-04-05: check what to do with the size\n      size: child.size,\n      flush: options.flush,\n      shardSplitThreshold: options.shardSplitThreshold,\n      hashAlg: options.hashAlg,\n      cidVersion: options.cidVersion\n    })\n\n    // new child for next loop\n    child = {\n      cid: result.cid,\n      name,\n      // TODO vmx 2021-04-05: check what to do with the size\n      size: result.size\n    }\n  }\n\n  // @ts-expect-error - child is possibly undefined\n  const { cid } = child\n  log(`Final CID ${cid}`)\n\n  return cid\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/files/utils/with-mfs-root.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { UnixFS } from 'ipfs-unixfs'\nimport * as dagPB from '@ipld/dag-pb'\nimport { sha256 } from 'multiformats/hashes/sha2'\nimport { logger } from '@libp2p/logger'\nimport errCode from 'err-code'\nimport { MFS_ROOT_KEY } from '../../../utils.js'\n\nconst log = logger('ipfs:mfs:utils:with-mfs-root')\n\n/**\n * @typedef {import('../').MfsContext} MfsContext\n */\n\n/**\n * @param {MfsContext} context\n * @param {import('ipfs-core-types/src/utils').AbortOptions} [options]\n */\nexport async function loadMfsRoot (context, options) {\n  if (options && options.signal && options.signal.aborted) {\n    throw errCode(new Error('Request aborted'), 'ERR_ABORTED', { name: 'Aborted' })\n  }\n\n  // Open the repo if it's been closed\n  await context.repo.datastore.open()\n\n  // Load the MFS root CID\n  let cid\n\n  try {\n    const buf = await context.repo.datastore.get(MFS_ROOT_KEY)\n\n    cid = CID.decode(buf)\n  } catch (/** @type {any} */ err) {\n    if (err.code !== 'ERR_NOT_FOUND') {\n      throw err\n    }\n\n    log('Creating new MFS root')\n    const buf = dagPB.encode({\n      Data: new UnixFS({ type: 'directory' }).marshal(),\n      Links: []\n    })\n    const hash = await sha256.digest(buf)\n    cid = CID.createV0(hash)\n    await context.repo.blocks.put(cid, buf)\n\n    if (options && options.signal && options.signal.aborted) {\n      throw errCode(new Error('Request aborted'), 'ERR_ABORTED', { name: 'Aborted' })\n    }\n\n    await context.repo.datastore.put(MFS_ROOT_KEY, cid.bytes)\n  }\n\n  log(`Loaded MFS root /ipfs/${cid}`)\n\n  return cid\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/files/write.js",
    "content": "import { logger } from '@libp2p/logger'\nimport { importer } from 'ipfs-unixfs-importer'\nimport {\n  decode\n} from '@ipld/dag-pb'\nimport { createStat } from './stat.js'\nimport { createMkdir } from './mkdir.js'\nimport { addLink } from './utils/add-link.js'\nimport mergeOpts from 'merge-options'\nimport { createLock } from './utils/create-lock.js'\nimport { toAsyncIterator } from './utils/to-async-iterator.js'\nimport { toMfsPath } from './utils/to-mfs-path.js'\nimport { toPathComponents } from './utils/to-path-components.js'\nimport { toTrail } from './utils/to-trail.js'\nimport { updateTree } from './utils/update-tree.js'\nimport { updateMfsRoot } from './utils/update-mfs-root.js'\nimport errCode from 'err-code'\nimport {\n  MFS_MAX_CHUNK_SIZE\n} from '../../utils.js'\nimport last from 'it-last'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport {\n  parseMode,\n  parseMtime\n} from 'ipfs-unixfs'\n\nconst mergeOptions = mergeOpts.bind({ ignoreUndefined: true })\nconst log = logger('ipfs:mfs:write')\n\n/**\n * @typedef {import('multiformats/cid').Version} CIDVersion\n * @typedef {import('ipfs-unixfs').MtimeLike} MtimeLike\n * @typedef {import('./').MfsContext} MfsContext\n * @typedef {import('./utils/to-mfs-path').FilePath} FilePath\n * @typedef {import('./utils/to-mfs-path').MfsPath} MfsPath\n * @typedef {import('multiformats/hashes/interface').MultihashHasher} MultihashHasher\n *\n * @typedef {object} DefaultOptions\n * @property {number} offset\n * @property {number} length\n * @property {boolean} create\n * @property {boolean} truncate\n * @property {boolean} rawLeaves\n * @property {boolean} reduceSingleLeafToSelf\n * @property {CIDVersion} cidVersion\n * @property {string} hashAlg\n * @property {boolean} parents\n * @property {import('ipfs-core-types/src/root').AddProgressFn} progress\n * @property {'trickle' | 'balanced'} strategy\n * @property {boolean} flush\n * @property {'raw' | 'file'} leafType\n * @property {number} shardSplitThreshold\n * @property {MtimeLike} [mtime]\n * @property {number} [mode]\n * @property {AbortSignal} [signal]\n * @property {number} [timeout]\n */\n\n/**\n * @type {DefaultOptions}\n */\nconst defaultOptions = {\n  offset: 0, // the offset in the file to begin writing\n  length: Infinity, // how many bytes from the incoming buffer to write\n  create: false, // whether to create the file if it does not exist\n  truncate: false, // whether to truncate the file first\n  rawLeaves: false,\n  reduceSingleLeafToSelf: false,\n  cidVersion: 0,\n  hashAlg: 'sha2-256',\n  parents: false, // whether to create intermediate directories if they do not exist\n  progress: (bytes, path) => {},\n  strategy: 'trickle',\n  flush: true,\n  leafType: 'raw',\n  shardSplitThreshold: 1000\n}\n\n/**\n * @param {MfsContext} context\n */\nexport function createWrite (context) {\n  /**\n   * @type {import('ipfs-core-types/src/files').API<{}>[\"write\"]}\n   */\n  async function mfsWrite (path, content, opts = {}) {\n    /** @type {DefaultOptions} */\n    const options = mergeOptions(defaultOptions, opts)\n\n    /** @type {AsyncIterable<Uint8Array>} */\n    let source\n    /** @type {MfsPath} */\n    let destination\n    /** @type {MfsPath} */\n    let parent\n    log('Reading source, destination and parent')\n    await createLock().readLock(async () => {\n      source = await toAsyncIterator(content)\n      destination = await toMfsPath(context, path, options)\n      parent = await toMfsPath(context, destination.mfsDirectory, options)\n    })()\n    log('Read source, destination and parent')\n    // @ts-expect-error - parent may be undefined\n    if (!options.parents && !parent.exists) {\n      throw errCode(new Error('directory does not exist'), 'ERR_NO_EXIST')\n    }\n\n    // @ts-expect-error\n    if (source == null) {\n      throw errCode(new Error('could not create source'), 'ERR_NO_SOURCE')\n    }\n\n    // @ts-expect-error\n    if (destination == null) {\n      throw errCode(new Error('could not create destination'), 'ERR_NO_DESTINATION')\n    }\n\n    if (!options.create && !destination.exists) {\n      throw errCode(new Error('file does not exist'), 'ERR_NO_EXIST')\n    }\n\n    if (destination.entryType !== 'file') {\n      throw errCode(new Error('not a file'), 'ERR_NOT_A_FILE')\n    }\n\n    return updateOrImport(context, path, source, destination, options)\n  }\n\n  return withTimeoutOption(mfsWrite)\n}\n\n/**\n * @param {MfsContext} context\n * @param {string} path\n * @param {AsyncIterable<Uint8Array>} source\n * @param {FilePath} destination\n * @param {DefaultOptions} options\n */\nconst updateOrImport = async (context, path, source, destination, options) => {\n  const child = await write(context, source, destination, options)\n\n  // The slow bit is done, now add or replace the DAGLink in the containing directory\n  // re-reading the path to the containing folder in case it has changed in the interim\n  await createLock().writeLock(async () => {\n    const pathComponents = toPathComponents(path)\n    const fileName = pathComponents.pop()\n\n    if (fileName == null) {\n      throw errCode(new Error('source does not exist'), 'ERR_NO_EXIST')\n    }\n\n    let parentExists = false\n\n    try {\n      await createStat(context)(`/${pathComponents.join('/')}`, options)\n      parentExists = true\n    } catch (/** @type {any} */ err) {\n      if (err.code !== 'ERR_NOT_FOUND') {\n        throw err\n      }\n    }\n\n    if (!parentExists) {\n      await createMkdir(context)(`/${pathComponents.join('/')}`, options)\n    }\n\n    // get an updated mfs path in case the root changed while we were writing\n    const updatedPath = await toMfsPath(context, path, options)\n    const trail = await toTrail(context, updatedPath.mfsDirectory)\n    const parent = trail[trail.length - 1]\n\n    if (!parent) {\n      throw errCode(new Error('directory does not exist'), 'ERR_NO_EXIST')\n    }\n\n    if (!parent.type || !parent.type.includes('directory')) {\n      throw errCode(new Error(`cannot write to ${parent.name}: Not a directory`), 'ERR_NOT_A_DIRECTORY')\n    }\n\n    const parentBlock = await context.repo.blocks.get(parent.cid)\n    const parentNode = decode(parentBlock)\n\n    const result = await addLink(context, {\n      parent: parentNode,\n      name: fileName,\n      cid: child.cid,\n      size: child.size,\n      flush: options.flush,\n      shardSplitThreshold: options.shardSplitThreshold,\n      hashAlg: options.hashAlg,\n      cidVersion: options.cidVersion\n    })\n\n    parent.cid = result.cid\n\n    // update the tree with the new child\n    const newRootCid = await updateTree(context, trail, options)\n\n    // Update the MFS record with the new CID for the root of the tree\n    await updateMfsRoot(context, newRootCid, options)\n  })()\n}\n\n/**\n * @param {MfsContext} context\n * @param {AsyncIterable<Uint8Array>} source\n * @param {FilePath} destination\n * @param {DefaultOptions} options\n */\nconst write = async (context, source, destination, options) => {\n  if (destination.exists) {\n    log(`Overwriting file ${destination.cid} offset ${options.offset} length ${options.length}`)\n  } else {\n    log(`Writing file offset ${options.offset} length ${options.length}`)\n  }\n\n  /** @type {Array<() => AsyncIterable<Uint8Array>>} */\n  const sources = []\n\n  // pad start of file if necessary\n  if (options.offset > 0) {\n    if (destination.unixfs) {\n      log(`Writing first ${options.offset} bytes of original file`)\n\n      sources.push(\n        () => {\n          return destination.content({\n            offset: 0,\n            length: options.offset\n          })\n        }\n      )\n\n      if (destination.unixfs.fileSize() < options.offset) {\n        const extra = options.offset - destination.unixfs.fileSize()\n\n        log(`Writing zeros for extra ${extra} bytes`)\n        sources.push(\n          asyncZeroes(extra)\n        )\n      }\n    } else {\n      log(`Writing zeros for first ${options.offset} bytes`)\n      sources.push(\n        asyncZeroes(options.offset)\n      )\n    }\n  }\n\n  sources.push(\n    limitAsyncStreamBytes(source, options.length)\n  )\n\n  const content = countBytesStreamed(catAsyncIterators(sources), (bytesWritten) => {\n    if (destination.unixfs && !options.truncate) {\n      // if we've done reading from the new source and we are not going\n      // to truncate the file, add the end of the existing file to the output\n      const fileSize = destination.unixfs.fileSize()\n\n      if (fileSize > bytesWritten) {\n        log(`Writing last ${fileSize - bytesWritten} of ${fileSize} bytes from original file starting at offset ${bytesWritten}`)\n\n        return destination.content({\n          offset: bytesWritten\n        })\n      } else {\n        log('Not writing last bytes from original file')\n      }\n    }\n\n    return {\n      [Symbol.asyncIterator]: async function * () {}\n    }\n  })\n\n  /** @type {number | undefined} */\n  let mode\n\n  if (options.mode !== undefined && options.mode !== null) {\n    mode = parseMode(options.mode)\n  } else if (destination && destination.unixfs) {\n    mode = destination.unixfs.mode\n  }\n\n  /** @type {import('ipfs-unixfs').Mtime | undefined} */\n  let mtime\n\n  if (options.mtime != null) {\n    mtime = parseMtime(options.mtime)\n  } else if (destination && destination.unixfs) {\n    mtime = destination.unixfs.mtime\n  }\n\n  const hasher = await context.hashers.getHasher(options.hashAlg)\n\n  const result = await last(importer([{\n    content: content,\n\n    // persist mode & mtime if set previously\n    mode,\n    mtime\n  }], context.repo.blocks, {\n    progress: options.progress,\n    hasher,\n    cidVersion: options.cidVersion,\n    strategy: options.strategy,\n    rawLeaves: options.rawLeaves,\n    reduceSingleLeafToSelf: options.reduceSingleLeafToSelf,\n    leafType: options.leafType\n  }))\n\n  if (!result) {\n    throw errCode(new Error(`cannot write to ${parent.name}`), 'ERR_COULD_NOT_WRITE')\n  }\n\n  log(`Wrote ${result.cid}`)\n\n  return {\n    cid: result.cid,\n    size: result.size\n  }\n}\n\n/**\n * @param {AsyncIterable<Uint8Array>} stream\n * @param {number} limit\n */\nconst limitAsyncStreamBytes = (stream, limit) => {\n  return async function * _limitAsyncStreamBytes () {\n    let emitted = 0\n\n    for await (const buf of stream) {\n      emitted += buf.length\n\n      if (emitted > limit) {\n        yield buf.subarray(0, limit - emitted)\n\n        return\n      }\n\n      yield buf\n    }\n  }\n}\n\n/**\n * @param {number} count\n * @param {number} chunkSize\n */\nconst asyncZeroes = (count, chunkSize = MFS_MAX_CHUNK_SIZE) => {\n  const buf = new Uint8Array(chunkSize)\n\n  async function * _asyncZeroes () {\n    while (true) {\n      yield buf\n    }\n  }\n\n  return limitAsyncStreamBytes(_asyncZeroes(), count)\n}\n\n/**\n * @param {Array<() => AsyncIterable<Uint8Array>>} sources\n */\nconst catAsyncIterators = async function * (sources) { // eslint-disable-line require-await\n  for (let i = 0; i < sources.length; i++) {\n    yield * sources[i]()\n  }\n}\n\n/**\n * @param {AsyncIterable<Uint8Array>} source\n * @param {(count: number) => AsyncIterable<Uint8Array>} notify\n */\nconst countBytesStreamed = async function * (source, notify) {\n  let wrote = 0\n\n  for await (const buf of source) {\n    wrote += buf.length\n\n    yield buf\n  }\n\n  for await (const buf of notify(wrote)) {\n    wrote += buf.length\n\n    yield buf\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/get.js",
    "content": "import { exporter, recursive } from 'ipfs-unixfs-exporter'\nimport errCode from 'err-code'\nimport { normalizeCidPath } from '../utils.js'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport { CID } from 'multiformats/cid'\nimport { pack } from 'it-tar'\nimport { pipe } from 'it-pipe'\nimport Pako from 'pako'\nimport toBuffer from 'it-to-buffer'\n\n// https://www.gnu.org/software/gzip/manual/gzip.html\nconst DEFAULT_COMPRESSION_LEVEL = 6\n\n/**\n * @typedef {object} Context\n * @property {import('ipfs-repo').IPFSRepo} repo\n * @property {import('../types').Preload} preload\n *\n * @param {Context} context\n */\nexport function createGet ({ repo, preload }) {\n  /**\n   * @type {import('ipfs-core-types/src/root').API<{}>[\"get\"]}\n   */\n  async function * get (ipfsPath, options = {}) {\n    if (options.compressionLevel != null && (options.compressionLevel < -1 || options.compressionLevel > 9)) {\n      throw errCode(new Error('Compression level must be between -1 and 9'), 'ERR_INVALID_PARAMS')\n    }\n\n    if (options.preload !== false) {\n      let pathComponents\n\n      try {\n        pathComponents = normalizeCidPath(ipfsPath).split('/')\n      } catch (/** @type {any} */ err) {\n        throw errCode(err, 'ERR_INVALID_PATH')\n      }\n\n      preload(CID.parse(pathComponents[0]))\n    }\n\n    const ipfsPathOrCid = CID.asCID(ipfsPath) || ipfsPath\n    const file = await exporter(ipfsPathOrCid, repo.blocks, options)\n\n    if (file.type === 'file' || file.type === 'raw') {\n      const args = []\n\n      if (!options.compress || options.archive === true) {\n        args.push([{\n          header: {\n            name: file.path,\n            mode: file.type === 'file' && file.unixfs.mode,\n            mtime: file.type === 'file' && file.unixfs.mtime ? new Date(file.unixfs.mtime.secs * 1000) : undefined,\n            size: file.size,\n            type: 'file'\n          },\n          body: file.content()\n        }],\n        pack()\n        )\n      } else {\n        args.push(\n          file.content\n        )\n      }\n\n      if (options.compress) {\n        args.push(\n          /**\n           * @param {AsyncIterable<Uint8Array>} source\n           */\n          async function * (source) {\n            const buf = await toBuffer(source)\n\n            yield Pako.gzip(buf, {\n              level: options.compressionLevel || DEFAULT_COMPRESSION_LEVEL\n            })\n          }\n        )\n      }\n\n      // @ts-expect-error cannot derive type\n      yield * pipe(...args)\n\n      return\n    }\n\n    if (file.type === 'directory') {\n      /** @type {any[]} */\n      const args = [\n        recursive(ipfsPathOrCid, repo.blocks, options),\n        /**\n         * @param {AsyncIterable<import('ipfs-unixfs-exporter').UnixFSEntry>} source\n         */\n        async function * (source) {\n          for await (const entry of source) {\n            /** @type {import('it-tar').TarImportCandidate} */\n            const output = {\n              header: {\n                name: entry.path,\n                size: entry.size\n              }\n            }\n\n            if (entry.type === 'file') {\n              output.header.type = 'file'\n              output.header.mode = entry.unixfs.mode != null ? entry.unixfs.mode : undefined\n              output.header.mtime = entry.unixfs.mtime ? new Date(entry.unixfs.mtime.secs * 1000) : undefined\n              output.body = entry.content()\n            } else if (entry.type === 'raw') {\n              output.header.type = 'file'\n              output.body = entry.content()\n            } else if (entry.type === 'directory') {\n              output.header.type = 'directory'\n              output.header.mode = entry.unixfs.mode != null ? entry.unixfs.mode : undefined\n              output.header.mtime = entry.unixfs.mtime ? new Date(entry.unixfs.mtime.secs * 1000) : undefined\n            } else {\n              throw errCode(new Error('Not a UnixFS node'), 'ERR_NOT_UNIXFS')\n            }\n\n            yield output\n          }\n        },\n        pack()\n      ]\n\n      if (options.compress) {\n        if (!options.archive) {\n          throw errCode(new Error('file is not regular'), 'ERR_INVALID_PATH')\n        }\n\n        if (options.compress) {\n          args.push(\n            /**\n             * @param {AsyncIterable<Uint8Array>} source\n             */\n            async function * (source) {\n              const buf = await toBuffer(source)\n\n              yield Pako.gzip(buf, {\n                level: options.compressionLevel || DEFAULT_COMPRESSION_LEVEL\n              })\n            }\n          )\n        }\n      }\n\n      // @ts-expect-error cannot derive type\n      yield * pipe(...args)\n\n      return\n    }\n\n    throw errCode(new Error('Not a UnixFS node'), 'ERR_NOT_UNIXFS')\n  }\n\n  return withTimeoutOption(get)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/id.js",
    "content": "import { ipfsCore as pkgversion } from '../version.js'\nimport { multiaddr } from '@multiformats/multiaddr'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\nimport { NotStartedError } from '../errors.js'\nimport errCode from 'err-code'\nimport { logger } from '@libp2p/logger'\n\nconst log = logger('ipfs:components:id')\n\n/**\n * @typedef {import('libp2p').Libp2p} Libp2p\n * @typedef {import('ipfs-core-types/src/utils').AbortOptions} AbortOptions\n * @typedef {import('@libp2p/interface-peer-id').PeerId} PeerId\n */\n\n/**\n * @param {object} config\n * @param {import('@libp2p/interface-peer-id').PeerId} config.peerId\n * @param {import('../types').NetworkService} config.network\n */\nexport function createId ({ peerId, network }) {\n  /**\n   * @type {import('ipfs-core-types/src/root').API<{}>[\"id\"]}\n   */\n  async function id (options = {}) { // eslint-disable-line require-await\n    const net = network.try()\n\n    if (!net) {\n      if (options.peerId) {\n        throw new NotStartedError()\n      }\n\n      if (peerId.publicKey == null) {\n        throw errCode(new Error('Public key missing'), 'ERR_MISSING_PUBLIC_KEY')\n      }\n\n      return {\n        id: peerId,\n        publicKey: uint8ArrayToString(peerId.publicKey, 'base64pad'),\n        addresses: [],\n        agentVersion: `js-ipfs/${pkgversion}`,\n        protocolVersion: '9000',\n        protocols: []\n      }\n    }\n\n    const { libp2p } = net\n    const peerIdToId = options.peerId ? options.peerId : peerId\n    const peer = await findPeer(peerIdToId, libp2p, options)\n    const agentVersion = uint8ArrayToString(peer.metadata.get('AgentVersion') || new Uint8Array())\n    const protocolVersion = uint8ArrayToString(peer.metadata.get('ProtocolVersion') || new Uint8Array())\n    const idStr = peer.id.toString()\n    const publicKeyStr = peer.publicKey ? uint8ArrayToString(peer.publicKey, 'base64pad') : ''\n\n    return {\n      id: peerIdToId,\n      publicKey: publicKeyStr,\n      addresses: (peer.addresses || [])\n        .map(ma => {\n          const str = ma.toString()\n\n          // some relay-style transports add our peer id to the ma for us\n          // so don't double-add\n          if (str.endsWith(`/p2p/${idStr}`)) {\n            return str\n          }\n\n          return `${str}/p2p/${idStr}`\n        })\n        .sort()\n        .map(ma => multiaddr(ma)),\n      agentVersion,\n      protocolVersion,\n      protocols: (peer.protocols || []).sort()\n    }\n  }\n\n  return withTimeoutOption(id)\n}\n\n/**\n * @param {PeerId} peerId\n * @param {Libp2p} libp2p\n * @param {AbortOptions} options\n */\nasync function findPeer (peerId, libp2p, options) {\n  let peer = await libp2p.peerStore.get(peerId)\n\n  if (!peer) {\n    peer = await findPeerOnDht(peerId, libp2p, options)\n  }\n\n  let publicKey = peerId.publicKey ? peerId.publicKey : await libp2p.peerStore.keyBook.get(peerId)\n\n  if (publicKey == null) {\n    try {\n      publicKey = await libp2p.getPublicKey(peerId, options)\n    } catch (err) {\n      log.error('Could not load public key for', peerId.toString(), err)\n    }\n  }\n\n  return {\n    ...peer,\n    publicKey,\n    metadata: peer.metadata || new Map(),\n    addresses: peer.addresses.map(addr => addr.multiaddr)\n  }\n}\n\n/**\n * @param {PeerId} peerId\n * @param {Libp2p} libp2p\n * @param {AbortOptions} options\n */\nasync function findPeerOnDht (peerId, libp2p, options) {\n  if (libp2p.dht == null) {\n    throw errCode(new Error('dht not configured'), 'ERR_DHT_NOT_CONFIGURED')\n  }\n\n  for await (const event of libp2p.dht.findPeer(peerId, options)) {\n    if (event.name === 'FINAL_PEER') {\n      break\n    }\n  }\n\n  const peer = await libp2p.peerStore.get(peerId)\n\n  if (!peer) {\n    throw errCode(new Error('Could not find peer'), 'ERR_NOT_FOUND')\n  }\n\n  return peer\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/index.js",
    "content": "import mergeOpts from 'merge-options'\nimport { isTest } from 'ipfs-utils/src/env.js'\nimport { logger } from '@libp2p/logger'\nimport errCode from 'err-code'\nimport { UnixFS } from 'ipfs-unixfs'\nimport * as dagPB from '@ipld/dag-pb'\nimport * as dagCBOR from '@ipld/dag-cbor'\nimport * as dagJSON from '@ipld/dag-json'\nimport * as dagJOSE from 'dag-jose'\nimport { identity } from 'multiformats/hashes/identity'\nimport { bases, hashes, codecs } from 'multiformats/basics'\nimport { initAssets } from 'ipfs-core-config/init-assets'\nimport { AlreadyInitializedError } from '../errors.js'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { TimeoutController } from 'timeout-abort-controller'\nimport { createStart } from './start.js'\n\nimport { createStop } from './stop.js'\nimport { createDns } from './dns.js'\nimport { createIsOnline } from './is-online.js'\nimport { createResolve } from './resolve.js'\nimport { PinAPI } from './pin/index.js'\nimport { IPNSAPI } from './ipns.js'\nimport { NameAPI } from './name/index.js'\nimport { createRefs } from './refs/index.js'\nimport { createLocal } from './refs/local.js'\nimport { BitswapAPI } from './bitswap/index.js'\nimport { BootstrapAPI } from './bootstrap/index.js'\nimport { BlockAPI } from './block/index.js'\nimport { RootAPI } from './root.js'\nimport { createVersion } from './version.js'\nimport { createId } from './id.js'\nimport { createConfig } from './config/index.js'\nimport { DagAPI } from './dag/index.js'\nimport { createPreloader } from '../preload.js'\nimport { createMfsPreloader } from '../mfs-preload.js'\nimport { createFiles } from './files/index.js'\nimport { KeyAPI } from './key/index.js'\nimport { ObjectAPI } from './object/index.js'\nimport { RepoAPI } from './repo/index.js'\nimport { StatsAPI } from './stats/index.js'\nimport { Storage } from './storage.js'\nimport { Network } from './network.js'\nimport { Service } from '../utils/service.js'\nimport { SwarmAPI } from './swarm/index.js'\nimport { createPing } from './ping.js'\nimport { createDht } from './dht.js'\nimport { createPubsub } from './pubsub.js'\nimport { Multicodecs } from 'ipfs-core-utils/multicodecs'\nimport { Multihashes } from 'ipfs-core-utils/multihashes'\nimport { Multibases } from 'ipfs-core-utils/multibases'\n\nconst mergeOptions = mergeOpts.bind({ ignoreUndefined: true })\nconst log = logger('ipfs')\n\nconst IPNS_INIT_KEYSPACE_TIMEOUT = 30000\n\n/**\n * @typedef {import('../types').Options} Options\n * @typedef {import('../types').Print} Print\n * @typedef {import('./storage')} StorageAPI\n * @typedef {import('multiformats/codecs/interface').BlockCodec<any, any>} BlockCodec\n * @typedef {import('multiformats/hashes/interface').MultihashHasher} MultihashHasher\n * @typedef {import('multiformats/bases/interface').MultibaseCodec<any>} MultibaseCodec\n */\n\nclass IPFS {\n  /**\n   * @param {object} config\n   * @param {Print} config.print\n   * @param {Storage} config.storage\n   * @param {import('ipfs-core-utils/multicodecs').Multicodecs} config.codecs\n   * @param {Options} config.options\n   */\n  constructor ({ print, storage, codecs, options }) {\n    const { peerId, repo, keychain } = storage\n    const network = Service.create(Network)\n\n    const preload = createPreloader(options.preload)\n\n    const dns = createDns()\n    const isOnline = createIsOnline({ network })\n    // @ts-expect-error This type check fails as options.\n    // libp2p can be a function, while IPNS router config expects libp2p config\n    const ipns = new IPNSAPI(options)\n\n    /** @type {MultihashHasher[]} */\n    const multihashHashers = Object.values(hashes);\n\n    (options.ipld && options.ipld.hashers ? options.ipld.hashers : []).forEach(hasher => multihashHashers.push(hasher))\n\n    this.hashers = new Multihashes({\n      hashers: multihashHashers,\n      loadHasher: options.ipld && options.ipld.loadHasher\n    })\n\n    /** @type {MultibaseCodec[]} */\n    const multibaseCodecs = Object.values(bases);\n\n    (options.ipld && options.ipld.bases ? options.ipld.bases : []).forEach(base => multibaseCodecs.push(base))\n\n    this.bases = new Multibases({\n      bases: multibaseCodecs,\n      loadBase: options.ipld && options.ipld.loadBase\n    })\n\n    const pin = new PinAPI({ repo, codecs })\n    const block = new BlockAPI({ codecs, hashers: this.hashers, preload, repo })\n\n    const name = new NameAPI({\n      dns,\n      ipns,\n      repo,\n      codecs,\n      peerId,\n      isOnline,\n      keychain,\n      options\n    })\n\n    const resolve = createResolve({ repo, codecs, bases: this.bases, name })\n\n    const dag = new DagAPI({ repo, codecs, hashers: this.hashers, preload })\n    const refs = Object.assign(createRefs({ repo, codecs, resolve, preload }), {\n      local: createLocal({ repo: storage.repo })\n    })\n    const { add, addAll, cat, get, ls } = new RootAPI({\n      preload,\n      repo,\n      options: options.EXPERIMENTAL,\n      hashers: this.hashers\n    })\n\n    const files = createFiles({\n      repo,\n      preload,\n      hashers: this.hashers,\n      options\n    })\n\n    const mfsPreload = createMfsPreloader({\n      files,\n      preload,\n      options: options.preload\n    })\n\n    this.preload = preload\n    this.name = name\n    this.ipns = ipns\n    this.pin = pin\n    this.resolve = resolve\n    this.block = block\n    this.refs = refs\n\n    this.start = createStart({\n      network,\n      peerId,\n      repo,\n      preload,\n      ipns,\n      mfsPreload,\n      print,\n      keychain,\n      hashers: this.hashers,\n      options\n    })\n\n    this.stop = createStop({\n      network,\n      preload,\n      mfsPreload,\n      ipns,\n      repo\n    })\n\n    this.dht = createDht({ network, repo, peerId })\n    this.pubsub = createPubsub({ network, config: options.config })\n    this.dns = dns\n    this.isOnline = isOnline\n    this.id = createId({ network, peerId })\n    this.version = createVersion({ repo })\n    this.bitswap = new BitswapAPI({ network })\n    this.bootstrap = new BootstrapAPI({ repo })\n    this.config = createConfig({ repo })\n    this.ping = createPing({ network })\n\n    this.add = add\n    this.addAll = addAll\n    this.cat = cat\n    this.get = get\n    this.ls = ls\n\n    this.dag = dag\n    this.files = files\n    this.key = new KeyAPI({ keychain })\n    this.object = new ObjectAPI({ preload, codecs, repo })\n    this.repo = new RepoAPI({ repo, hashers: this.hashers })\n    this.stats = new StatsAPI({ repo, network })\n    this.swarm = new SwarmAPI({ network })\n\n    // For the backwards compatibility\n    Object.defineProperty(this, 'libp2p', {\n      get () {\n        const net = network.try()\n        return net ? net.libp2p : undefined\n      }\n    })\n\n    // unimplemented methods\n    const notImplemented = () => Promise.reject(errCode(new Error('Not implemented'), 'ERR_NOT_IMPLEMENTED'))\n    const notImplementedIter = async function * () { throw errCode(new Error('Not implemented'), 'ERR_NOT_IMPLEMENTED') } // eslint-disable-line require-yield\n    this.commands = notImplemented\n    this.diag = {\n      cmds: notImplemented,\n      net: notImplemented,\n      sys: notImplemented\n    }\n    this.log = {\n      level: notImplemented,\n      ls: notImplemented,\n      tail: notImplementedIter\n    }\n    this.mount = notImplemented\n\n    this.codecs = codecs\n  }\n\n  /**\n   * `IPFS.create` will do the initialization. Keep this around for backwards\n   * compatibility.\n   *\n   * @deprecated\n   */\n  async init () { // eslint-disable-line require-await\n    throw new AlreadyInitializedError()\n  }\n}\n\n/**\n * @param {IPFS} ipfs\n */\nconst addEmptyDir = async (ipfs) => {\n  const buf = dagPB.encode({\n    Data: new UnixFS({ type: 'directory' }).marshal(),\n    Links: []\n  })\n\n  const cid = await ipfs.block.put(buf, {\n    mhtype: 'sha2-256',\n    format: 'dag-pb'\n  })\n\n  await ipfs.pin.add(cid)\n\n  return cid\n}\n\n/**\n * @returns {Options}\n */\nconst getDefaultOptions = () => ({\n  start: true,\n  EXPERIMENTAL: {},\n  preload: {\n    enabled: !isTest, // preload by default, unless in test env\n    addresses: [\n      '/dns4/node0.preload.ipfs.io/https',\n      '/dns4/node1.preload.ipfs.io/https',\n      '/dns4/node2.preload.ipfs.io/https',\n      '/dns4/node3.preload.ipfs.io/https'\n    ]\n  }\n})\n\n/**\n * @param {Options} options\n */\nexport async function create (options = {}) {\n  options = mergeOptions(getDefaultOptions(), options)\n  const initOptions = options.init || {}\n\n  /**\n   * @type {BlockCodec}\n   */\n  const id = {\n    name: identity.name,\n    code: identity.code,\n    encode: (id) => id,\n    decode: (id) => id\n  }\n\n  /** @type {BlockCodec[]} */\n  const blockCodecs = Object.values(codecs);\n\n  [dagPB, dagCBOR, dagJSON, dagJOSE, id].concat((options.ipld && options.ipld.codecs) || []).forEach(codec => blockCodecs.push(codec))\n\n  const multicodecs = new Multicodecs({\n    codecs: blockCodecs,\n    loadCodec: options.ipld && options.ipld.loadCodec\n  })\n\n  // eslint-disable-next-line no-console\n  const print = options.silent ? log : console.log\n\n  log('creating repo')\n  const storage = await Storage.start(print, multicodecs, options)\n\n  log('getting repo config')\n  const config = await storage.repo.config.getAll()\n\n  const ipfs = new IPFS({\n    storage,\n    print,\n    codecs: multicodecs,\n    options: { ...options, config }\n  })\n\n  log('starting preload')\n  await ipfs.preload.start()\n\n  log('starting storage')\n  ipfs.ipns.startOffline(storage)\n\n  if (storage.isNew && !initOptions.emptyRepo) {\n    // add empty unixfs dir object (go-ipfs assumes this exists)\n    const cid = await addEmptyDir(ipfs)\n\n    log('adding default assets')\n    await initAssets({ addAll: ipfs.addAll, print })\n\n    log('initializing IPNS keyspace')\n\n    if (storage.peerId.publicKey == null) {\n      throw errCode(new Error('Public key missing'), 'ERR_MISSING_PUBLIC_KEY')\n    }\n\n    const timeoutController = new TimeoutController(IPNS_INIT_KEYSPACE_TIMEOUT)\n    try {\n      await ipfs.ipns.initializeKeyspace(storage.peerId, uint8ArrayFromString(`/ipfs/${cid}`), {\n        signal: timeoutController.signal\n      })\n    } finally {\n      timeoutController.clear()\n    }\n  }\n\n  if (options.start !== false) {\n    log('starting node')\n    await ipfs.start()\n  }\n\n  return ipfs\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/ipns.js",
    "content": "import { IPNS } from '../ipns/index.js'\nimport { createRouting } from '../ipns/routing/config.js'\nimport { OfflineDatastore } from '../ipns/routing/offline-datastore.js'\nimport { NotInitializedError, AlreadyInitializedError } from '../errors.js'\nimport { logger } from '@libp2p/logger'\n\nconst log = logger('ipfs:components:ipns')\n\n/**\n * @typedef {import('@libp2p/interface-peer-id').PeerId} PeerId\n * @typedef {import('@libp2p/interfaces').AbortOptions} AbortOptions\n *\n * @typedef {object} ExperimentalOptions\n * @property {boolean} [ipnsPubsub]\n *\n * @typedef {object} LibP2POptions\n * @property {DHTConfig} [config]\n *\n * @typedef {object} DHTConfig\n * @property {boolean} [enabled]\n */\n\nexport class IPNSAPI {\n  /**\n   * @param {object} options\n   * @param {string} options.pass\n   * @param {boolean} [options.offline]\n   * @param {LibP2POptions} [options.libp2p]\n   * @param {ExperimentalOptions} [options.EXPERIMENTAL]\n   */\n  constructor (options = { pass: '' }) {\n    this.options = options\n\n    /** @type {IPNS | null} */\n    this.offline = null\n\n    /** @type {IPNS | null} */\n    this.online = null\n  }\n\n  getIPNS () {\n    const ipns = this.online || this.offline\n    if (ipns) {\n      return ipns\n    } else {\n      throw new NotInitializedError()\n    }\n  }\n\n  get routing () {\n    return this.getIPNS().routing\n  }\n\n  /**\n   * Activates IPNS subsystem in an ofline mode. If it was started once already\n   * it will throw an exception.\n   *\n   * This is primarily used for offline ipns modifications, such as the\n   * initializeKeyspace feature.\n   *\n   * @param {object} config\n   * @param {import('ipfs-repo').IPFSRepo} config.repo\n   * @param {import('@libp2p/interface-peer-id').PeerId} config.peerId\n   * @param {import('@libp2p/interface-keychain').KeyChain} config.keychain\n   */\n  startOffline ({ repo, peerId, keychain }) {\n    if (this.offline != null) {\n      throw new AlreadyInitializedError()\n    }\n\n    log('initializing IPNS keyspace (offline)')\n\n    const routing = new OfflineDatastore(repo.datastore)\n    const ipns = new IPNS(routing, repo.datastore, peerId, keychain, this.options)\n\n    this.offline = ipns\n  }\n\n  /**\n   * @param {object} config\n   * @param {import('libp2p').Libp2p} config.libp2p\n   * @param {import('ipfs-repo').IPFSRepo} config.repo\n   * @param {import('@libp2p/interface-peer-id').PeerId} config.peerId\n   * @param {import('@libp2p/interface-keychain').KeyChain} config.keychain\n   */\n  async startOnline ({ libp2p, repo, peerId, keychain }) {\n    if (this.online != null) {\n      throw new AlreadyInitializedError()\n    }\n    const routing = createRouting({ libp2p, repo, peerId, options: this.options })\n\n    // @ts-expect-error routing is a TieredDatastore which wants keys to be Keys, IPNS needs keys to be Uint8Arrays\n    const ipns = new IPNS(routing, repo.datastore, peerId, keychain, this.options)\n    await ipns.republisher.start()\n    this.online = ipns\n  }\n\n  async stop () {\n    const ipns = this.online\n    if (ipns) {\n      await ipns.republisher.stop()\n      this.online = null\n    }\n  }\n\n  /**\n   * @param {PeerId} peerId\n   * @param {Uint8Array} value\n   * @param {number} lifetime\n   * @param {AbortOptions} [options]\n   */\n  publish (peerId, value, lifetime, options) {\n    return this.getIPNS().publish(peerId, value, lifetime, options)\n  }\n\n  /**\n   *\n   * @param {string} name\n   * @param {object} [options]\n   * @param {boolean} [options.nocache]\n   * @param {boolean} [options.recursive]\n   * @param {AbortSignal} [options.signal]\n   */\n  resolve (name, options) {\n    return this.getIPNS().resolve(name, options)\n  }\n\n  /**\n   * @param {PeerId} peerId\n   * @param {Uint8Array} value\n   * @param {AbortOptions} [options]\n   */\n  initializeKeyspace (peerId, value, options) {\n    return this.getIPNS().initializeKeyspace(peerId, value, options)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/is-online.js",
    "content": "\n/**\n * @param {object} config\n * @param {import('../types').NetworkService} config.network\n */\nexport function createIsOnline ({ network }) {\n  /**\n   * @returns {boolean}\n   */\n  return () => {\n    const net = network.try()\n    return net != null && Boolean(net.libp2p.isStarted())\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/key/export.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @param {object} config\n * @param {import('@libp2p/interface-keychain').KeyChain} config.keychain\n */\nexport function createExport ({ keychain }) {\n  /**\n   * @type {import('ipfs-core-types/src/key').API<{}>[\"export\"]}\n   */\n  const exportKey = (name, password) =>\n    keychain.exportKey(name, password)\n\n  return withTimeoutOption(exportKey)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/key/gen.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\nconst DEFAULT_KEY_TYPE = 'Ed25519'\nconst DEFAULT_KEY_SIZE = 2048\n\n/**\n * @param {object} config\n * @param {import('@libp2p/interface-keychain').KeyChain} config.keychain\n */\nexport function createGen ({ keychain }) {\n  /**\n   * @type {import('ipfs-core-types/src/key').API<{}>[\"gen\"]}\n   */\n  const gen = (name, options = { type: DEFAULT_KEY_TYPE, size: DEFAULT_KEY_SIZE }) => {\n    return keychain.createKey(name, options.type || DEFAULT_KEY_TYPE, options.size || DEFAULT_KEY_SIZE)\n  }\n\n  return withTimeoutOption(gen)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/key/import.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @param {object} config\n * @param {import('@libp2p/interface-keychain').KeyChain} config.keychain\n */\nexport function createImport ({ keychain }) {\n  /**\n   * @type {import('ipfs-core-types/src/key').API<{}>[\"import\"]}\n   */\n  const importKey = (name, pem, password) => {\n    return keychain.importKey(name, pem, password)\n  }\n\n  return withTimeoutOption(importKey)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/key/index.js",
    "content": "import { createExport } from './export.js'\nimport { createGen } from './gen.js'\nimport { createImport } from './import.js'\nimport { createInfo } from './info.js'\nimport { createList } from './list.js'\nimport { createRename } from './rename.js'\nimport { createRm } from './rm.js'\n\n/**\n * @typedef {import('@libp2p/interface-keychain').KeyChain} Keychain\n */\n\nexport class KeyAPI {\n  /**\n   * @param {object} config\n   * @param {Keychain} config.keychain\n   */\n  constructor ({ keychain }) {\n    this.gen = createGen({ keychain })\n    this.list = createList({ keychain })\n    this.rm = createRm({ keychain })\n    this.rename = createRename({ keychain })\n    this.export = createExport({ keychain })\n    this.import = createImport({ keychain })\n    this.info = createInfo({ keychain })\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/key/info.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @param {object} config\n * @param {import('@libp2p/interface-keychain').KeyChain} config.keychain\n */\nexport function createInfo ({ keychain }) {\n  /**\n   * @type {import('ipfs-core-types/src/key').API<{}>[\"info\"]}\n   */\n  const info = (name) => keychain.findKeyByName(name)\n\n  return withTimeoutOption(info)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/key/list.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @param {object} config\n * @param {import('@libp2p/interface-keychain').KeyChain} config.keychain\n */\nexport function createList ({ keychain }) {\n  /**\n   * @type {import('ipfs-core-types/src/key').API<{}>[\"list\"]}\n   */\n  const list = () => keychain.listKeys()\n\n  return withTimeoutOption(list)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/key/rename.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @param {object} config\n * @param {import('@libp2p/interface-keychain').KeyChain} config.keychain\n */\nexport function createRename ({ keychain }) {\n  /**\n   * @type {import('ipfs-core-types/src/key').API<{}>[\"rename\"]}\n   */\n  const rename = async (oldName, newName) => {\n    const key = await keychain.renameKey(oldName, newName)\n\n    return {\n      was: oldName,\n      now: key.name,\n      id: key.id,\n      overwrite: false\n    }\n  }\n\n  return withTimeoutOption(rename)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/key/rm.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @param {object} config\n * @param {import('@libp2p/interface-keychain').KeyChain} config.keychain\n */\nexport function createRm ({ keychain }) {\n  /**\n   * @type {import('ipfs-core-types/src/key').API<{}>[\"rm\"]}\n   */\n  const rm = (name) => keychain.removeKey(name)\n\n  return withTimeoutOption(rm)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/libp2p.js",
    "content": "import get from 'dlv'\nimport mergeOpts from 'merge-options'\nimport errCode from 'err-code'\nimport { routers } from 'ipfs-core-config/libp2p-pubsub-routers'\nimport { delegatedPeerRouting } from '@libp2p/delegated-peer-routing'\nimport { delegatedContentRouting } from '@libp2p/delegated-content-routing'\nimport { create as ipfsHttpClient } from 'ipfs-http-client'\nimport { multiaddr } from '@multiformats/multiaddr'\nimport { ipfsCore as pkgversion } from '../version.js'\nimport { libp2pConfig as getEnvLibp2pOptions } from 'ipfs-core-config/libp2p'\nimport { createLibp2p as createNode } from 'libp2p'\nimport { kadDHT } from '@libp2p/kad-dht'\nimport { bootstrap } from '@libp2p/bootstrap'\nimport { ipnsValidator } from 'ipns/validator'\nimport { ipnsSelector } from 'ipns/selector'\nimport { webSockets } from '@libp2p/websockets'\nimport { mplex } from '@libp2p/mplex'\nimport { noise } from '@chainsafe/libp2p-noise'\n\nconst mergeOptions = mergeOpts.bind({ ignoreUndefined: true, concatArrays: true })\n\n/**\n * @typedef {object} DekOptions\n * @property {string} hash\n * @property {string} salt\n * @property {number} iterationCount\n * @property {number} keyLength\n *\n * @typedef {object} KeychainConfig\n * @property {string} [pass]\n * @property {DekOptions} [dek]\n *\n * @typedef {import('ipfs-repo').IPFSRepo} Repo\n * @typedef {import('@libp2p/interface-peer-id').PeerId} PeerId\n * @typedef {import('../types').Options} IPFSOptions\n * @typedef {import('libp2p').Libp2p} LibP2P\n * @typedef {import('libp2p').Libp2pOptions} Libp2pOptions\n * @typedef {import('ipfs-core-types/src/config').Config} IPFSConfig\n * @typedef {import('@multiformats/multiaddr').Multiaddr} Multiaddr\n */\n\n/**\n * @param {object} config\n * @param {Repo} config.repo\n * @param {IPFSOptions|undefined} config.options\n * @param {PeerId} config.peerId\n * @param {Multiaddr[]|undefined} config.multiaddrs\n * @param {KeychainConfig|undefined} config.keychainConfig\n * @param {Partial<IPFSConfig>|undefined} config.config\n */\nexport function createLibp2p ({\n  options = {},\n  peerId,\n  multiaddrs = [],\n  repo,\n  keychainConfig = {},\n  config = {}\n}) {\n  const { datastore } = repo\n\n  const libp2pOptions = getLibp2pOptions({\n    options,\n    config,\n    datastore,\n    keychainConfig,\n    peerId,\n    multiaddrs\n  })\n\n  if (typeof options.libp2p === 'function') {\n    return options.libp2p({ libp2pOptions, options, config, datastore, peerId })\n  }\n\n  // do not start by default\n  libp2pOptions.start = false\n\n  return createNode(libp2pOptions)\n}\n\n/**\n * @param {object} input\n * @param {IPFSOptions} input.options\n * @param {Partial<IPFSConfig>} input.config\n * @param {Repo['datastore']} input.datastore\n * @param {KeychainConfig} input.keychainConfig\n * @param {PeerId} input.peerId\n * @param {Multiaddr[]} input.multiaddrs\n * @returns {Libp2pOptions}\n */\nfunction getLibp2pOptions ({ options, config, datastore, keychainConfig, peerId, multiaddrs }) {\n  const getPubsubRouter = () => {\n    const router = get(config, 'Pubsub.Router') || 'gossipsub'\n\n    const availableRouters = routers()\n\n    if (!availableRouters[router]) {\n      throw errCode(new Error(`Router unavailable. Configure libp2p.modules.pubsub to use the ${router} router.`), 'ERR_NOT_SUPPORTED')\n    }\n\n    return availableRouters[router]\n  }\n\n  /** @type {Libp2pOptions} */\n  const libp2pDefaults = {\n    datastore,\n    peerId: peerId\n  }\n\n  /** @type {Libp2pOptions} */\n  const libp2pOptions = {\n    addresses: {\n      listen: multiaddrs.map(ma => ma.toString()),\n      announce: get(options, 'addresses.announce', get(config, 'Addresses.Announce', [])),\n      noAnnounce: get(options, 'addresses.noAnnounce', get(config, 'Addresses.NoAnnounce', []))\n    },\n    connectionManager: get(options, 'connectionManager', {\n      maxConnections: get(options, 'config.Swarm.ConnMgr.HighWater', get(config, 'Swarm.ConnMgr.HighWater')),\n      minConnections: get(options, 'config.Swarm.ConnMgr.LowWater', get(config, 'Swarm.ConnMgr.LowWater'))\n    }),\n    keychain: keychainConfig,\n    identify: {\n      host: {\n        agentVersion: `js-ipfs/${pkgversion}`\n      }\n    },\n    contentRouters: [],\n    peerRouters: [],\n    peerDiscovery: [],\n    transports: [],\n    streamMuxers: [\n      mplex({\n        maxInboundStreams: 256,\n        maxOutboundStreams: 1024\n      })\n    ],\n    connectionEncryption: [\n      noise()\n    ],\n    relay: {\n      enabled: get(options, 'relay.enabled', get(config, 'relay.enabled', true)),\n      hop: {\n        enabled: get(options, 'relay.hop.enabled', get(config, 'relay.hop.enabled', false)),\n        active: get(options, 'relay.hop.active', get(config, 'relay.hop.active', false))\n      }\n    },\n    nat: {\n      enabled: !get(config, 'Swarm.DisableNatPortMap', false)\n    }\n  }\n\n  if (get(options, 'config.Pubsub.Enabled', get(config, 'Pubsub.Enabled', true))) {\n    libp2pOptions.pubsub = getPubsubRouter()\n  }\n\n  if (get(config, 'Routing.Type', 'dhtclient') !== 'none') {\n    libp2pOptions.dht = kadDHT({\n      clientMode: get(config, 'Routing.Type', 'dht') !== 'dhtserver',\n      kBucketSize: get(options, 'dht.kBucketSize', 20),\n      validators: {\n        ipns: ipnsValidator\n      },\n      selectors: {\n        ipns: ipnsSelector\n      }\n    })\n  }\n\n  const boostrapNodes = get(options, 'config.Bootstrap', get(config, 'Bootstrap', []))\n\n  if (boostrapNodes.length > 0) {\n    libp2pOptions.peerDiscovery?.push(\n      bootstrap({\n        list: boostrapNodes\n      })\n    )\n  }\n\n  /** @type {import('libp2p').Libp2pOptions | undefined} */\n  let constructorOptions = get(options, 'libp2p', undefined)\n\n  if (typeof constructorOptions === 'function') {\n    constructorOptions = undefined\n  }\n\n  // Merge defaults with Node.js/browser/other environments options and configuration\n  /** @type {Libp2pOptions} */\n  const libp2pFinalConfig = mergeOptions(\n    libp2pDefaults,\n    getEnvLibp2pOptions(),\n    libp2pOptions,\n    constructorOptions\n  )\n\n  // Set up Delegate Routing based on the presence of Delegates in the config\n  const delegateHosts = get(options, 'config.Addresses.Delegates',\n    get(config, 'Addresses.Delegates', [])\n  )\n\n  if (delegateHosts.length > 0) {\n    // Pick a random delegate host\n    const delegateString = delegateHosts[Math.floor(Math.random() * delegateHosts.length)]\n    const delegateAddr = multiaddr(delegateString).toOptions()\n    const delegateApiOptions = {\n      host: delegateAddr.host,\n      // port is a string atm, so we need to convert for the check\n      // @ts-expect-error - parseInt(input:string) => number\n      protocol: parseInt(delegateAddr.port) === 443 ? 'https' : 'http',\n      port: delegateAddr.port\n    }\n\n    const delegateHttpClient = ipfsHttpClient(delegateApiOptions)\n\n    libp2pFinalConfig.contentRouters?.push(delegatedContentRouting(delegateHttpClient))\n    libp2pFinalConfig.peerRouters?.push(delegatedPeerRouting(delegateHttpClient))\n  }\n\n  // TODO: fixme\n  if (!get(options, 'config.Discovery.MDNS.Enabled', get(config, 'Discovery.MDNS.Enabled', true))) {\n    libp2pFinalConfig.peerDiscovery = libp2pFinalConfig.peerDiscovery?.filter(d => {\n      try {\n        if (typeof d === 'function') {\n          // @ts-expect-error not components\n          return d({})[Symbol.toStringTag] !== '@libp2p/mdns'\n        }\n      } catch {}\n      return true\n    })\n  }\n\n  if (libp2pFinalConfig.transports == null) {\n    libp2pFinalConfig.transports = []\n  }\n\n  // add WebSocket transport if not overridden by user config\n  if (libp2pFinalConfig.transports.find(t => {\n    try {\n      if (typeof t === 'function') {\n        return t({})[Symbol.toStringTag] === '@libp2p/websockets'\n      }\n    } catch {}\n    return false\n  }) == null) {\n    libp2pFinalConfig.transports.push(webSockets())\n  }\n\n  return libp2pFinalConfig\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/ls.js",
    "content": "import { exporter } from 'ipfs-unixfs-exporter'\nimport errCode from 'err-code'\nimport { normalizeCidPath, mapFile } from '../utils.js'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport { CID } from 'multiformats/cid'\n\n/**\n * @typedef {object} Context\n * @property {import('ipfs-repo').IPFSRepo} repo\n * @property {import('../types').Preload} preload\n *\n * @param {Context} context\n */\nexport function createLs ({ repo, preload }) {\n  /**\n   * @type {import('ipfs-core-types/src/root').API<{}>[\"ls\"]}\n   */\n  async function * ls (ipfsPath, options = {}) {\n    const legacyPath = normalizeCidPath(ipfsPath)\n    const pathComponents = legacyPath.split('/')\n\n    if (options.preload !== false) {\n      preload(CID.parse(pathComponents[0]))\n    }\n\n    const ipfsPathOrCid = CID.asCID(legacyPath) || legacyPath\n    const file = await exporter(ipfsPathOrCid, repo.blocks, options)\n\n    if (file.type === 'file') {\n      yield mapFile(file)\n      return\n    }\n\n    if (file.type === 'directory') {\n      for await (const child of file.content()) {\n        yield mapFile(child)\n      }\n\n      return\n    }\n\n    throw errCode(new Error(`Unknown UnixFS type ${file.type}`), 'ERR_UNKNOWN_UNIXFS_TYPE')\n  }\n\n  return withTimeoutOption(ls)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/name/index.js",
    "content": "import { createPublish } from './publish.js'\nimport { createResolve } from './resolve.js'\nimport { PubSubAPI } from './pubsub/index.js'\n\nexport class NameAPI {\n  /**\n   * @param {object} config\n   * @param {import('../ipns').IPNSAPI} config.ipns\n   * @param {import('@libp2p/interface-peer-id').PeerId} config.peerId\n   * @param {import('../../types').Options} config.options\n   * @param {import('ipfs-repo').IPFSRepo} config.repo\n   * @param {import('ipfs-core-utils/multicodecs').Multicodecs} config.codecs\n   * @param {import('ipfs-core-types/src/root').API<{}>[\"isOnline\"]} config.isOnline\n   * @param {import('@libp2p/interface-keychain').KeyChain} config.keychain\n   * @param {import('ipfs-core-types/src/root').API<{}>[\"dns\"]} config.dns\n   */\n  constructor ({ dns, ipns, repo, codecs, peerId, isOnline, keychain, options }) {\n    this.publish = createPublish({ ipns, repo, codecs, peerId, isOnline, keychain })\n    this.resolve = createResolve({ dns, ipns, isOnline, options })\n    this.pubsub = new PubSubAPI({ ipns, options })\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/name/publish.js",
    "content": "import { logger } from '@libp2p/logger'\nimport parseDuration from 'parse-duration'\nimport { importKey, unmarshalPrivateKey } from '@libp2p/crypto/keys'\nimport errcode from 'err-code'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\nimport { OFFLINE_ERROR, normalizePath } from '../../utils.js'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport { resolvePath } from './utils.js'\nimport { peerIdFromKeys } from '@libp2p/peer-id'\n\nconst log = logger('ipfs:name:publish')\n\n/**\n * IPNS - Inter-Planetary Naming System\n *\n * @param {object} config\n * @param {import('../ipns').IPNSAPI} config.ipns\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n * @param {import('ipfs-core-utils/multicodecs').Multicodecs} config.codecs\n * @param {import('@libp2p/interface-peer-id').PeerId} config.peerId\n * @param {import('ipfs-core-types/src/root').API<{}>[\"isOnline\"]} config.isOnline\n * @param {import('@libp2p/interface-keychain').KeyChain} config.keychain\n */\nexport function createPublish ({ ipns, repo, codecs, peerId, isOnline, keychain }) {\n  /**\n   * @param {string} keyName\n   */\n  const lookupKey = async keyName => {\n    /** @type {import('@libp2p/interface-keys').PrivateKey} */\n    let privateKey\n\n    if (keyName === 'self' && peerId.privateKey != null) {\n      privateKey = await unmarshalPrivateKey(peerId.privateKey)\n    } else {\n      try {\n        // We're exporting and immediately importing the key, so we can just use a throw away password\n        const pem = await keychain.exportKey(keyName, 'temp')\n        privateKey = await importKey(pem, 'temp')\n      } catch (/** @type {any} */ err) {\n        log.error(err)\n        throw errcode(err, 'ERR_CANNOT_GET_KEY')\n      }\n    }\n\n    return peerIdFromKeys(privateKey.public.bytes, privateKey.bytes)\n  }\n\n  /**\n   * @type {import('ipfs-core-types/src/name').API<{}>[\"publish\"]}\n   */\n  async function publish (value, options = {}) {\n    const resolve = !(options.resolve === false)\n    const lifetime = options.lifetime || '24h'\n    const key = options.key || 'self'\n\n    if (!isOnline()) {\n      throw errcode(new Error(OFFLINE_ERROR), 'OFFLINE_ERROR')\n    }\n\n    // TODO: params related logic should be in the core implementation\n    // Normalize path value\n    try {\n      value = normalizePath(value)\n    } catch (/** @type {any} */ err) {\n      log.error(err)\n      throw err\n    }\n\n    let pubLifetime = 0\n    try {\n      pubLifetime = parseDuration(lifetime) || 0\n\n      // Calculate lifetime with nanoseconds precision\n      pubLifetime = parseFloat(pubLifetime.toFixed(6))\n    } catch (/** @type {any} */ err) {\n      log.error(err)\n      throw err\n    }\n\n    // TODO: ttl human for cache\n    const results = await Promise.all([\n      // verify if the path exists, if not, an error will stop the execution\n      lookupKey(key),\n      // if resolving, do a get so we make sure we have the blocks\n      resolve ? resolvePath({ ipns, repo, codecs }, value) : Promise.resolve()\n    ])\n\n    const bytes = uint8ArrayFromString(value)\n\n    // Start publishing process\n    const result = await ipns.publish(results[0], bytes, pubLifetime, options)\n\n    return {\n      name: result.name,\n      value: uint8ArrayToString(result.value)\n    }\n  }\n\n  return withTimeoutOption(publish)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/name/pubsub/cancel.js",
    "content": "import { getPubsubRouting } from './utils.js'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @param {object} config\n * @param {import('../../ipns').IPNSAPI} config.ipns\n * @param {import('../../../types').Options} config.options\n */\nexport function createCancel ({ ipns, options }) {\n  const experimental = options.EXPERIMENTAL\n\n  /**\n   * @type {import('ipfs-core-types/src/name/pubsub').API<{}>[\"cancel\"]}\n   */\n  async function cancel (name, options = {}) { // eslint-disable-line require-await\n    const pubsub = getPubsubRouting(ipns, experimental)\n    return pubsub.cancel(name, options)\n  }\n\n  return withTimeoutOption(cancel)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/name/pubsub/index.js",
    "content": "import { createCancel } from './cancel.js'\nimport { createState } from './state.js'\nimport { createSubs } from './subs.js'\n\nexport class PubSubAPI {\n  /**\n   * @param {object} config\n   * @param {import('../../ipns').IPNSAPI} config.ipns\n   * @param {import('../../../types').Options} config.options\n   */\n  constructor ({ ipns, options }) {\n    this.cancel = createCancel({ ipns, options })\n    this.state = createState({ ipns, options })\n    this.subs = createSubs({ ipns, options })\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/name/pubsub/state.js",
    "content": "import { getPubsubRouting } from './utils.js'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @param {object} config\n * @param {import('../../ipns').IPNSAPI} config.ipns\n * @param {import('../../../types').Options} config.options\n */\nexport function createState ({ ipns, options }) {\n  const experimental = options.EXPERIMENTAL\n\n  /**\n   * @type {import('ipfs-core-types/src/name/pubsub').API<{}>[\"state\"]}\n   */\n  async function state (_options = {}) { // eslint-disable-line require-await\n    try {\n      return { enabled: Boolean(getPubsubRouting(ipns, experimental)) }\n    } catch (/** @type {any} */ err) {\n      return { enabled: false }\n    }\n  }\n\n  return withTimeoutOption(state)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/name/pubsub/subs.js",
    "content": "import { getPubsubRouting } from './utils.js'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @param {object} config\n * @param {import('../../ipns').IPNSAPI} config.ipns\n * @param {import('../../../types').Options} config.options\n */\nexport function createSubs ({ ipns, options }) {\n  const experimental = options.EXPERIMENTAL\n\n  /**\n   * @type {import('ipfs-core-types/src/name/pubsub').API<{}>[\"subs\"]}\n   */\n  async function subs (options = {}) { // eslint-disable-line require-await\n    const pubsub = getPubsubRouting(ipns, experimental)\n    return pubsub.getSubscriptions(options)\n  }\n\n  return withTimeoutOption(subs)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/name/pubsub/utils.js",
    "content": "import { IpnsPubsubDatastore } from '../../../ipns/routing/pubsub-datastore.js'\nimport errcode from 'err-code'\n\n/**\n * @typedef {import('../../../types').ExperimentalOptions} ExperimentalOptions\n * @property {boolean} [ipnsPubsub] - Enable pub-sub on IPNS. (Default: `false`)\n */\n\n/**\n * Get pubsub from IPNS routing\n *\n * @param {import('../../ipns').IPNSAPI} ipns\n * @param {ExperimentalOptions} [options]\n */\nexport function getPubsubRouting (ipns, options) {\n  if (!ipns || !(options && options.ipnsPubsub)) {\n    throw errcode(new Error('IPNS pubsub subsystem is not enabled'), 'ERR_IPNS_PUBSUB_NOT_ENABLED')\n  }\n\n  // Only one store and it is pubsub\n  if (ipns.routing instanceof IpnsPubsubDatastore) {\n    return ipns.routing\n  }\n\n  // Find in tiered\n  const pubsub = (ipns.routing.stores || []).find(s => s instanceof IpnsPubsubDatastore)\n\n  if (!pubsub) {\n    throw errcode(new Error('IPNS pubsub datastore not found'), 'ERR_PUBSUB_DATASTORE_NOT_FOUND')\n  }\n\n  return pubsub\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/name/resolve.js",
    "content": "import { logger } from '@libp2p/logger'\nimport errcode from 'err-code'\nimport mergeOpts from 'merge-options'\nimport { CID } from 'multiformats/cid'\nimport * as Digest from 'multiformats/hashes/digest'\nimport { base36 } from 'multiformats/bases/base36'\nimport { peerIdFromString } from '@libp2p/peer-id'\n// @ts-expect-error no types\nimport isDomain from 'is-domain-name'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\nimport { OFFLINE_ERROR } from '../../utils.js'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nconst mergeOptions = mergeOpts.bind({ ignoreUndefined: true })\n\nconst log = logger('ipfs:name:resolve')\n\n/**\n *\n * @param {string} result\n * @param {string[]} remainder\n * @returns {string}\n */\nconst appendRemainder = (result, remainder) =>\n  remainder.length > 0\n    ? result + '/' + remainder.join('/')\n    : result\n\n/**\n * IPNS - Inter-Planetary Naming System\n *\n * @param {object} config\n * @param {import('ipfs-core-types/src/root').API<{}>[\"dns\"]} config.dns\n * @param {import('../ipns').IPNSAPI} config.ipns\n * @param {import('ipfs-core-types/src/root').API<{}>[\"isOnline\"]} config.isOnline\n * @param {import('../../types').Options} config.options\n */\nexport function createResolve ({ dns, ipns, isOnline, options: { offline } }) {\n  /**\n   * @type {import('ipfs-core-types/src/name').API<{}>[\"resolve\"]}\n   */\n  async function * resolve (name, options = {}) { // eslint-disable-line require-await\n    options = mergeOptions({\n      nocache: false,\n      recursive: true\n    }, options)\n\n    // TODO: params related logic should be in the core implementation\n    if (offline && options && options.nocache) {\n      throw errcode(new Error('cannot specify both offline and nocache'), 'ERR_NOCACHE_AND_OFFLINE')\n    }\n\n    // IPNS resolve needs a online daemon\n    if (!isOnline() && !offline) {\n      throw errcode(new Error(OFFLINE_ERROR), 'OFFLINE_ERROR')\n    }\n\n    let ipnsName = name.toString()\n\n    if (!ipnsName.startsWith('/ipns/')) {\n      ipnsName = `/ipns/${ipnsName}`\n    }\n\n    let [namespace, hash, ...remainder] = ipnsName.slice(1).split('/')\n\n    try {\n      if (hash.substring(0, 1) === '1') {\n        const id = peerIdFromString(hash)\n        const digest = Digest.decode(id.toBytes())\n        const libp2pKey = CID.createV1(0x72, digest)\n        hash = libp2pKey.toString(base36)\n      } else {\n        const cid = CID.parse(hash)\n\n        if (cid.version === 1) {\n          hash = cid.toString(base36)\n        }\n      }\n    } catch (/** @type {any} */ err) {\n      // lets check if we have a domain ex. /ipns/ipfs.io and resolve with dns\n      if (isDomain(hash)) {\n        yield appendRemainder(await dns(hash, options), remainder)\n        return\n      }\n\n      log.error(err)\n      throw errcode(new Error('Invalid IPNS name'), 'ERR_IPNS_INVALID_NAME')\n    }\n\n    // multihash is valid lets resolve with IPNS\n    // TODO: convert ipns.resolve to return an iterator\n    const value = await ipns.resolve(`/${namespace}/${hash}`, options)\n    yield appendRemainder(value instanceof Uint8Array ? uint8ArrayToString(value) : value, remainder)\n  }\n\n  return withTimeoutOption(resolve)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/name/utils.js",
    "content": "import * as isIPFS from 'is-ipfs'\nimport { toCidAndPath } from 'ipfs-core-utils/to-cid-and-path'\nimport drain from 'it-drain'\nimport { resolve } from '../../utils.js'\n\n/**\n * resolves the given path by parsing out protocol-specific entries\n * (e.g. /ipns/<node-key>) and then going through the /ipfs/ entries and returning the final node\n *\n * @param {object} context\n * @param {import('../ipns').IPNSAPI} context.ipns\n * @param {import('ipfs-repo').IPFSRepo} context.repo\n * @param {import('ipfs-core-utils/multicodecs').Multicodecs} context.codecs\n * @param {string} name\n * @param {import('ipfs-core-types/src/utils').AbortOptions} [options]\n */\nexport async function resolvePath ({ ipns, repo, codecs }, name, options) {\n  // ipns path\n  if (isIPFS.ipnsPath(name)) {\n    return ipns.resolve(name)\n  }\n\n  const {\n    cid,\n    path\n  } = toCidAndPath(name)\n\n  // ipfs path\n  await drain(resolve(cid, path || '', codecs, repo, options))\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/network.js",
    "content": "import { createBitswap } from 'ipfs-bitswap'\nimport { createLibp2p } from './libp2p.js'\nimport { multiaddr } from '@multiformats/multiaddr'\nimport errCode from 'err-code'\nimport { BlockStorage } from '../block-storage.js'\n\n/**\n * @typedef {object} Online\n * @property {libp2p} libp2p\n * @property {Bitswap} bitswap\n *\n * @typedef {object} Options\n * @property {PeerId} options.peerId\n * @property {Repo} options.repo\n * @property {Print} options.print\n * @property {IPFSOptions} options.options\n * @property {import('ipfs-core-utils/multihashes').Multihashes} options.hashers\n *\n * @typedef {import('ipfs-core-types/src/config').Config} IPFSConfig\n * @typedef {import('../types').Options} IPFSOptions\n * @typedef {import('ipfs-repo').IPFSRepo} Repo\n * @typedef {import('../types').Print} Print\n * @typedef {import('libp2p').Libp2p} libp2p\n * @typedef {import('ipfs-bitswap').IPFSBitswap} Bitswap\n * @typedef {import('@libp2p/interface-peer-id').PeerId} PeerId\n * @typedef {import('ipfs-core-types/src/utils').AbortOptions} AbortOptions\n * @typedef {import('@multiformats/multiaddr').Multiaddr} Multiaddr\n */\n\nexport class Network {\n  /**\n   * @param {PeerId} peerId\n   * @param {libp2p} libp2p\n   * @param {Bitswap} bitswap\n   * @param {Repo} repo\n   * @param {BlockStorage} blockstore\n   */\n  constructor (peerId, libp2p, bitswap, repo, blockstore) {\n    this.peerId = peerId\n    this.libp2p = libp2p\n    this.bitswap = bitswap\n    this.repo = repo\n    this.blockstore = blockstore\n  }\n\n  /**\n   * @param {Options} options\n   */\n  static async start ({ peerId, repo, print, hashers, options }) {\n    // Need to ensure that repo is open as it could have been closed between\n    // `init` and `start`.\n    if (repo.closed) {\n      await repo.open()\n    }\n\n    /** @type {IPFSConfig} */\n    const config = await repo.config.getAll()\n\n    const libp2p = await createLibp2p({\n      options,\n      repo,\n      peerId,\n      multiaddrs: readAddrs(peerId, config),\n      config,\n      keychainConfig: undefined\n    })\n\n    await libp2p.start()\n\n    for (const ma of libp2p.getMultiaddrs()) {\n      print(`Swarm listening on ${ma.toString()}`)\n    }\n\n    const bitswap = createBitswap(libp2p, repo.blocks, {\n      statsEnabled: true,\n      hashLoader: hashers,\n      maxInboundStreams: 1024,\n      maxOutboundStreams: 1024\n    })\n    await bitswap.start()\n\n    const blockstore = new BlockStorage(repo.blocks, bitswap)\n    repo.blocks = blockstore\n    // @ts-expect-error private field\n    repo.pins.blockstore = blockstore\n\n    return new Network(peerId, libp2p, bitswap, repo, blockstore)\n  }\n\n  /**\n   * @param {Network} network\n   */\n  static async stop (network) {\n    network.repo.blocks = network.blockstore.unwrap()\n    // @ts-expect-error private field\n    network.repo.pins.blockstore = network.blockstore.unwrap()\n\n    await network.bitswap.stop()\n    await network.libp2p.stop()\n  }\n}\n\n/**\n * @param {PeerId} peerId\n * @param {IPFSConfig} config\n */\nconst readAddrs = (peerId, config) => {\n  const peerIdStr = peerId.toString()\n  /** @type {Multiaddr[]} */\n  const addrs = []\n  const swarm = (config.Addresses && config.Addresses.Swarm) || []\n  for (const addr of swarm) {\n    let ma = multiaddr(addr)\n\n    // Temporary error for users migrating using websocket-star multiaddrs for listenning on libp2p\n    // websocket-star support was removed from ipfs and libp2p\n    if (ma.protoCodes().includes(WEBSOCKET_STAR_PROTO_CODE)) {\n      throw errCode(new Error('websocket-star swarm addresses are not supported. See https://github.com/ipfs/js-ipfs/issues/2779'), 'ERR_WEBSOCKET_STAR_SWARM_ADDR_NOT_SUPPORTED')\n    }\n\n    // multiaddrs that go via a signalling server or other intermediary (e.g. stardust,\n    // webrtc-star) can have the intermediary's peer ID in the address, so append our\n    // peer ID to the end of it\n    const maId = ma.getPeerId()\n    if (maId && maId !== peerIdStr) {\n      ma = ma.encapsulate(`/p2p/${peerIdStr}`)\n    }\n\n    addrs.push(ma)\n  }\n\n  return addrs\n}\n\nconst WEBSOCKET_STAR_PROTO_CODE = 479\n"
  },
  {
    "path": "packages/ipfs-core/src/components/object/data.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport { createGet } from './get.js'\n\n/**\n * @param {object} config\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n * @param {import('../../types').Preload} config.preload\n */\nexport function createData ({ repo, preload }) {\n  const get = createGet({ repo, preload })\n\n  /**\n   * @type {import('ipfs-core-types/src/object').API<{}>[\"data\"]}\n   */\n  async function data (multihash, options = {}) {\n    const node = await get(multihash, options)\n    return node.Data || new Uint8Array(0)\n  }\n\n  return withTimeoutOption(data)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/object/get.js",
    "content": "import * as dagPB from '@ipld/dag-pb'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @param {object} config\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n * @param {import('../../types').Preload} config.preload\n */\nexport function createGet ({ repo, preload }) {\n  /**\n   * @type {import('ipfs-core-types/src/object').API<{}>[\"get\"]}\n   */\n  async function get (cid, options = {}) { // eslint-disable-line require-await\n    if (options.preload !== false) {\n      preload(cid)\n    }\n\n    const block = await repo.blocks.get(cid, options)\n\n    return dagPB.decode(block)\n  }\n\n  return withTimeoutOption(get)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/object/index.js",
    "content": "import { createData } from './data.js'\nimport { createGet } from './get.js'\nimport { createLinks } from './links.js'\nimport { createNew } from './new.js'\nimport { createPut } from './put.js'\nimport { createStat } from './stat.js'\nimport { ObjectPatchAPI } from './patch/index.js'\n\n/**\n * @typedef {import('../../types').Preload} Preload\n * @typedef {import('multiformats/cid').CID} CID\n * @typedef {import('ipfs-core-types/src/utils').AbortOptions} AbortOptions\n */\n\nexport class ObjectAPI {\n  /**\n   * @param {object} config\n   * @param {import('ipfs-repo').IPFSRepo} config.repo\n   * @param {import('ipfs-core-utils/multicodecs').Multicodecs} config.codecs\n   * @param {Preload} config.preload\n   */\n  constructor ({ repo, codecs, preload }) {\n    this.data = createData({ repo, preload })\n    this.get = createGet({ repo, preload })\n    this.links = createLinks({ repo, codecs })\n    this.new = createNew({ repo, preload })\n    this.put = createPut({ repo, preload })\n    this.stat = createStat({ repo, preload })\n    this.patch = new ObjectPatchAPI({ repo, preload })\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/object/links.js",
    "content": "import * as dagPB from '@ipld/dag-pb'\nimport * as dagCBOR from '@ipld/dag-cbor'\nimport * as dagJSON from '@ipld/dag-json'\nimport * as raw from 'multiformats/codecs/raw'\nimport { CID } from 'multiformats/cid'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @typedef {import('@ipld/dag-pb').PBLink} DAGLink\n */\n\n/**\n * @param {any} node\n * @param {DAGLink[]} [links]\n * @returns {DAGLink[]}\n */\nfunction findLinks (node, links = []) {\n  for (const key in node) {\n    const val = node[key]\n\n    if (key === '/' && Object.keys(node).length === 1) {\n      try {\n        links.push({\n          Name: '',\n          Tsize: 0,\n          Hash: CID.parse(val)\n        })\n        continue\n      } catch (/** @type {any} */ _) {\n        // not a CID\n      }\n    }\n\n    const cid = CID.asCID(val)\n\n    if (cid) {\n      links.push({\n        Name: '',\n        Tsize: 0,\n        Hash: cid\n      })\n      continue\n    }\n\n    if (Array.isArray(val)) {\n      findLinks(val, links)\n    }\n\n    if (val && typeof val === 'object') {\n      findLinks(val, links)\n    }\n  }\n\n  return links\n}\n\n/**\n * @param {object} config\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n * @param {import('ipfs-core-utils/multicodecs').Multicodecs} config.codecs\n */\nexport function createLinks ({ repo, codecs }) {\n  /**\n   * @type {import('ipfs-core-types/src/object').API<{}>[\"links\"]}\n   */\n  async function links (cid, options = {}) {\n    const codec = await codecs.getCodec(cid.code)\n    const block = await repo.blocks.get(cid, options)\n    const node = codec.decode(block)\n\n    switch (cid.code) {\n      case raw.code:\n        return []\n      case dagPB.code:\n        return node.Links\n      case dagCBOR.code:\n      case dagJSON.code:\n        return findLinks(node)\n      default:\n        throw new Error(`Cannot resolve links from codec ${cid.code}`)\n    }\n  }\n\n  return withTimeoutOption(links)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/object/new.js",
    "content": "import * as dagPB from '@ipld/dag-pb'\nimport { sha256 } from 'multiformats/hashes/sha2'\nimport { UnixFS } from 'ipfs-unixfs'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport { CID } from 'multiformats/cid'\n\n/**\n * @param {object} config\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n * @param {import('../../types').Preload} config.preload\n */\nexport function createNew ({ repo, preload }) {\n  /**\n   * @type {import('ipfs-core-types/src/object').API<{}>[\"new\"]}\n   */\n  async function _new (options = {}) {\n    let data\n\n    if (options.template) {\n      if (options.template === 'unixfs-dir') {\n        data = (new UnixFS({ type: 'directory' })).marshal()\n      } else {\n        throw new Error('unknown template')\n      }\n    }\n\n    const buf = dagPB.encode({\n      Data: data,\n      Links: []\n    })\n    const hash = await sha256.digest(buf)\n    const cid = CID.createV0(hash)\n\n    await repo.blocks.put(cid, buf, {\n      signal: options.signal\n    })\n\n    if (options.preload !== false) {\n      preload(cid)\n    }\n\n    return cid\n  }\n\n  return withTimeoutOption(_new)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/object/patch/add-link.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport { createGet } from '../get.js'\nimport { createPut } from '../put.js'\n\n/**\n * @param {object} config\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n * @param {import('../../../types').Preload} config.preload\n */\nexport function createAddLink ({ repo, preload }) {\n  const get = createGet({ repo, preload })\n  const put = createPut({ repo, preload })\n\n  /**\n   * @type {import('ipfs-core-types/src/object/patch').API<{}>[\"addLink\"]}\n   */\n  async function addLink (cid, link, options = {}) {\n    const node = await get(cid, options)\n\n    return put({\n      ...node,\n      Links: node.Links.concat([link])\n    }, options)\n  }\n\n  return withTimeoutOption(addLink)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/object/patch/append-data.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport { concat as uint8ArrayConcat } from 'uint8arrays/concat'\nimport { createGet } from '../get.js'\nimport { createPut } from '../put.js'\n\n/**\n * @param {object} config\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n * @param {import('../../../types').Preload} config.preload\n */\nexport function createAppendData ({ repo, preload }) {\n  const get = createGet({ repo, preload })\n  const put = createPut({ repo, preload })\n\n  /**\n   * @type {import('ipfs-core-types/src/object/patch').API<{}>[\"appendData\"]}\n   */\n  async function appendData (cid, data, options = {}) {\n    const node = await get(cid, options)\n    const newData = uint8ArrayConcat([node.Data || [], data])\n\n    return put({\n      ...node,\n      Data: newData\n    }, options)\n  }\n\n  return withTimeoutOption(appendData)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/object/patch/index.js",
    "content": "import { createAddLink } from './add-link.js'\nimport { createAppendData } from './append-data.js'\nimport { createRmLink } from './rm-link.js'\nimport { createSetData } from './set-data.js'\n\n/**\n * @typedef {import('../../../types').Preload} Preload\n */\n\nexport class ObjectPatchAPI {\n  /**\n   * @param {object} config\n   * @param {import('ipfs-repo').IPFSRepo} config.repo\n   * @param {Preload} config.preload\n   */\n  constructor ({ repo, preload }) {\n    this.addLink = createAddLink({ repo, preload })\n    this.appendData = createAppendData({ repo, preload })\n    this.rmLink = createRmLink({ repo, preload })\n    this.setData = createSetData({ repo, preload })\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/object/patch/rm-link.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport { createGet } from '../get.js'\nimport { createPut } from '../put.js'\n\n/**\n * @param {object} config\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n * @param {import('../../../types').Preload} config.preload\n */\nexport function createRmLink ({ repo, preload }) {\n  const get = createGet({ repo, preload })\n  const put = createPut({ repo, preload })\n\n  /**\n   * @type {import('ipfs-core-types/src/object/patch').API<{}>[\"rmLink\"]}\n   */\n  async function rmLink (cid, link, options = {}) {\n    const node = await get(cid, options)\n    const name = (typeof link === 'string' ? link : link.Name) || ''\n\n    node.Links = node.Links.filter(l => l.Name !== name)\n\n    return put(node, options)\n  }\n\n  return withTimeoutOption(rmLink)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/object/patch/set-data.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport { createGet } from '../get.js'\nimport { createPut } from '../put.js'\n\n/**\n * @param {object} config\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n * @param {import('../../../types').Preload} config.preload\n */\nexport function createSetData ({ repo, preload }) {\n  const get = createGet({ repo, preload })\n  const put = createPut({ repo, preload })\n\n  /**\n   * @type {import('ipfs-core-types/src/object/patch').API<{}>[\"setData\"]}\n   */\n  async function setData (cid, data, options = {}) {\n    const node = await get(cid, options)\n\n    return put({\n      ...node,\n      Data: data\n    }, options)\n  }\n\n  return withTimeoutOption(setData)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/object/put.js",
    "content": "import * as dagPB from '@ipld/dag-pb'\nimport { CID } from 'multiformats/cid'\nimport { sha256 } from 'multiformats/hashes/sha2'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @param {object} config\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n * @param {import('../../types').Preload} config.preload\n */\nexport function createPut ({ repo, preload }) {\n  /**\n   * @type {import('ipfs-core-types/src/object').API<{}>[\"put\"]}\n   */\n  async function put (obj, options = {}) {\n    const release = await repo.gcLock.readLock()\n\n    try {\n      const buf = dagPB.encode(obj)\n      const hash = await sha256.digest(buf)\n      const cid = CID.createV1(dagPB.code, hash)\n\n      await repo.blocks.put(cid, buf, {\n        signal: options.signal\n      })\n\n      if (options.preload !== false) {\n        preload(cid)\n      }\n\n      if (options.pin) {\n        await repo.pins.pinRecursively(cid, {\n          signal: options.signal\n        })\n      }\n\n      return cid\n    } finally {\n      release()\n    }\n  }\n\n  return withTimeoutOption(put)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/object/stat.js",
    "content": "import * as dagPB from '@ipld/dag-pb'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport { createGet } from './get.js'\n\n/**\n * @param {object} config\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n * @param {import('../../types').Preload} config.preload\n */\nexport function createStat ({ repo, preload }) {\n  const get = createGet({ repo, preload })\n\n  /**\n   * @type {import('ipfs-core-types/src/object').API<{}>[\"stat\"]}\n   */\n  async function stat (cid, options = {}) {\n    const node = await get(cid, options)\n    const serialized = dagPB.encode(node)\n    const blockSize = serialized.length\n    const linkLength = node.Links.reduce((a, l) => a + (l.Tsize || 0), 0)\n\n    return {\n      Hash: cid,\n      NumLinks: node.Links.length,\n      BlockSize: blockSize,\n      LinksSize: blockSize - (node.Data || []).length,\n      DataSize: (node.Data || []).length,\n      CumulativeSize: blockSize + linkLength\n    }\n  }\n\n  return withTimeoutOption(stat)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/pin/add-all.js",
    "content": "/* eslint max-nested-callbacks: [\"error\", 8] */\n\nimport { resolvePath } from '../../utils.js'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport { normaliseInput } from 'ipfs-core-utils/pins/normalise-input'\nimport { PinTypes } from 'ipfs-repo/pin-types'\n\n/**\n * @typedef {import('ipfs-core-utils/src/pins/normalise-input').Source} Source\n * @typedef {import('ipfs-core-utils/src/pins/normalise-input').Pin} PinTarget\n * @typedef {import('ipfs-core-types/src/utils').AbortOptions} AbortOptions\n * @typedef {import('multiformats/cid').CID} CID\n */\n\n/**\n * @template T\n * @typedef {Iterable<T>|AsyncIterable<T>} AwaitIterable\n */\n\n/**\n * @param {object} config\n * @param {import('ipfs-core-utils/multicodecs').Multicodecs} config.codecs\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n */\nexport function createAddAll ({ repo, codecs }) {\n  /**\n   * @type {import('ipfs-core-types/src/pin').API<{}>[\"addAll\"]}\n   */\n  async function * addAll (source, options = {}) {\n    /**\n     * @returns {AsyncIterable<CID>}\n     */\n    const pinAdd = async function * () {\n      for await (const { path, recursive, metadata } of normaliseInput(source)) {\n        const { cid } = await resolvePath(repo, codecs, path)\n\n        // verify that each hash can be pinned\n        const { reason } = await repo.pins.isPinnedWithType(cid, [PinTypes.recursive, PinTypes.direct])\n\n        if (reason === 'recursive' && !recursive) {\n          // only disallow trying to override recursive pins\n          throw new Error(`${cid} already pinned recursively`)\n        }\n\n        if (recursive) {\n          await repo.pins.pinRecursively(cid, { metadata })\n        } else {\n          await repo.pins.pinDirectly(cid, { metadata })\n        }\n\n        yield cid\n      }\n    }\n\n    // When adding a file, we take a lock that gets released after pinning\n    // is complete, so don't take a second lock here\n    const lock = Boolean(options.lock)\n\n    if (!lock) {\n      yield * pinAdd()\n      return\n    }\n\n    const release = await repo.gcLock.readLock()\n\n    try {\n      yield * pinAdd()\n    } finally {\n      release()\n    }\n  }\n\n  return withTimeoutOption(addAll)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/pin/add.js",
    "content": "import last from 'it-last'\nimport { CID } from 'multiformats/cid'\n\n/**\n * @param {object} config\n * @param {ReturnType<typeof import('./add-all').createAddAll>} config.addAll\n */\nexport function createAdd ({ addAll }) {\n  /**\n   * @type {import('ipfs-core-types/src/pin').API<{}>[\"add\"]}\n   */\n  return (path, options = {}) => {\n    let iter\n\n    const cid = CID.asCID(path)\n\n    if (cid) {\n      iter = addAll([{\n        cid,\n        ...options\n      }], options)\n    } else {\n      iter = addAll([{\n        path: path.toString(),\n        ...options\n      }], options)\n    }\n\n    // @ts-expect-error return value of last can be undefined\n    return last(iter)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/pin/index.js",
    "content": "import { createAdd } from './add.js'\nimport { createAddAll } from './add-all.js'\nimport { createLs } from './ls.js'\nimport { createRm } from './rm.js'\nimport { createRmAll } from './rm-all.js'\n\nexport class PinAPI {\n  /**\n   * @param {object} config\n   * @param {import('ipfs-core-utils/multicodecs').Multicodecs} config.codecs\n   * @param {import('ipfs-repo').IPFSRepo} config.repo\n   */\n  constructor ({ codecs, repo }) {\n    const addAll = createAddAll({ codecs, repo })\n    this.addAll = addAll\n    this.add = createAdd({ addAll })\n    const rmAll = createRmAll({ codecs, repo })\n    this.rmAll = rmAll\n    this.rm = createRm({ rmAll })\n    this.ls = createLs({ codecs, repo })\n\n    /** @type {import('ipfs-core-types/src/pin/remote').API} */\n    this.remote = {\n      add: (cid, options = {}) => Promise.reject(new Error('Not implemented')),\n      ls: async function * (query, options = {}) { return Promise.reject(new Error('Not implemented')) }, // eslint-disable-line require-yield\n      rm: (query, options = {}) => Promise.reject(new Error('Not implemented')),\n      rmAll: (query, options = {}) => Promise.reject(new Error('Not implemented')),\n      service: {\n        add: (name, credentials) => Promise.reject(new Error('Not implemented')),\n        rm: (name, options = {}) => Promise.reject(new Error('Not implemented')),\n        // @ts-expect-error return types seem to be broken by a recent ts release. doesn't matter here because\n        // we are just throwing. Will be removed by https://github.com/protocol/web3-dev-team/pull/58\n        ls: (options = {}) => Promise.reject(new Error('Not implemented'))\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/pin/ls.js",
    "content": "/* eslint max-nested-callbacks: [\"error\", 8] */\n\nimport { normaliseInput } from 'ipfs-core-utils/pins/normalise-input'\nimport { resolvePath } from '../../utils.js'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport errCode from 'err-code'\nimport { PinTypes } from 'ipfs-repo/pin-types'\n\n/**\n * @typedef {import('multiformats/cid').CID} CID\n */\n\n/**\n * @param {string} type\n * @param {CID} cid\n * @param {Record<string, any>} [metadata]\n */\nfunction toPin (type, cid, metadata) {\n  /** @type {import('ipfs-core-types/src/pin').LsResult} */\n  const output = {\n    type,\n    cid\n  }\n\n  if (metadata) {\n    output.metadata = metadata\n  }\n\n  return output\n}\n\n/**\n * @param {object} config\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n * @param {import('ipfs-core-utils/multicodecs').Multicodecs} config.codecs\n */\nexport function createLs ({ repo, codecs }) {\n  /**\n   * @type {import('ipfs-core-types/src/pin').API<{}>[\"ls\"]}\n   */\n  async function * ls (options = {}) {\n    /** @type {import('ipfs-core-types/src/pin').PinQueryType} */\n    let type = PinTypes.all\n\n    if (options.type) {\n      type = options.type\n\n      if (!Object.keys(PinTypes).includes(type)) {\n        throw errCode(new Error('Invalid pin type'), 'ERR_INVALID_PIN_TYPE')\n      }\n    }\n\n    if (options.paths) {\n      // check the pinned state of specific hashes\n      let matched = false\n\n      for await (const { path } of normaliseInput(options.paths)) {\n        const { cid } = await resolvePath(repo, codecs, path)\n        const { reason, pinned, parent, metadata } = await repo.pins.isPinnedWithType(cid, type)\n\n        if (!pinned) {\n          throw errCode(new Error(`path '${path}' is not pinned`), 'ERR_NOT_PINNED')\n        }\n\n        switch (reason) {\n          case PinTypes.direct:\n          case PinTypes.recursive:\n            matched = true\n            yield toPin(reason, cid, metadata)\n            break\n          default:\n            matched = true\n            yield toPin(`${PinTypes.indirect} through ${parent}`, cid, metadata)\n        }\n      }\n\n      if (!matched) {\n        throw new Error('No match found')\n      }\n\n      return\n    }\n\n    if (type === PinTypes.recursive || type === PinTypes.all) {\n      for await (const { cid, metadata } of repo.pins.recursiveKeys()) {\n        yield toPin(PinTypes.recursive, cid, metadata)\n      }\n    }\n\n    if (type === PinTypes.indirect || type === PinTypes.all) {\n      for await (const cid of repo.pins.indirectKeys(options)) {\n        yield toPin(PinTypes.indirect, cid)\n      }\n    }\n\n    if (type === PinTypes.direct || type === PinTypes.all) {\n      for await (const { cid, metadata } of repo.pins.directKeys()) {\n        yield toPin(PinTypes.direct, cid, metadata)\n      }\n    }\n  }\n\n  return withTimeoutOption(ls)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/pin/rm-all.js",
    "content": "import { normaliseInput } from 'ipfs-core-utils/pins/normalise-input'\nimport { resolvePath } from '../../utils.js'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport { PinTypes } from 'ipfs-repo/pin-types'\n\n/**\n * @param {object} config\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n * @param {import('ipfs-core-utils/multicodecs').Multicodecs} config.codecs\n */\nexport function createRmAll ({ repo, codecs }) {\n  /**\n   * @type {import('ipfs-core-types/src/pin').API<{}>[\"rmAll\"]}\n   */\n  async function * rmAll (source, _options = {}) {\n    const release = await repo.gcLock.readLock()\n\n    try {\n      // verify that each hash can be unpinned\n      for await (const { path, recursive } of normaliseInput(source)) {\n        const { cid } = await resolvePath(repo, codecs, path)\n        const { pinned, reason } = await repo.pins.isPinnedWithType(cid, PinTypes.all)\n\n        if (!pinned) {\n          throw new Error(`${cid} is not pinned`)\n        }\n\n        switch (reason) {\n          case (PinTypes.recursive):\n            if (!recursive) {\n              throw new Error(`${cid} is pinned recursively`)\n            }\n\n            await repo.pins.unpin(cid)\n\n            yield cid\n\n            break\n          case (PinTypes.direct):\n            await repo.pins.unpin(cid)\n\n            yield cid\n\n            break\n          default:\n            throw new Error(`${cid} is pinned indirectly under ${reason}`)\n        }\n      }\n    } finally {\n      release()\n    }\n  }\n\n  return withTimeoutOption(rmAll)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/pin/rm.js",
    "content": "import last from 'it-last'\n\n/**\n * @param {object} config\n * @param {import('ipfs-core-types/src/pin').API<{}>[\"rmAll\"]} config.rmAll\n */\nexport function createRm ({ rmAll }) {\n  /**\n   * @type {import('ipfs-core-types/src/pin').API<{}>[\"rm\"]}\n   */\n  async function rm (path, options = {}) {\n    // @ts-expect-error return value of last can be undefined\n    const cid = await last(rmAll([{ path, ...options }], options))\n\n    if (!cid) {\n      throw new Error('CID expected')\n    }\n\n    return cid\n  }\n\n  return rm\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/ping.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @typedef {Pong|PingFailure|StatusUpdate} Packet\n * Note that not all ping response objects are \"pongs\".\n * A \"pong\" message can be identified by a truthy success property and an empty\n * text property. Other ping responses are failures or status updates.\n *\n * @typedef {object} Pong\n * @property {true} success\n * @property {number} time\n * @property {''} text\n *\n * @typedef {object} PingFailure\n * @property {false} success\n * @property {number} time\n * @property {string} text\n *\n * @typedef {object} StatusUpdate\n * @property {true} success\n * @property {0} time\n * @property {string} text\n *\n * @typedef {PingSettings & AbortOptions} PingOptions\n *\n * @typedef {object} PingSettings\n * @property {number} [count=10] - The number of ping messages to send\n *\n * @typedef {import('ipfs-core-types/src/utils').AbortOptions} AbortOptions\n */\n\n/** @type {{success:true, time:0, text: ''}} */\nconst basePacket = { success: true, time: 0, text: '' }\n\n/**\n * @param {object} config\n * @param {import('../types').NetworkService} config.network\n */\nexport function createPing ({ network }) {\n  /**\n   * @type {import('ipfs-core-types/src/root').API<{}>[\"ping\"]}\n   */\n  async function * ping (peerId, options = {}) {\n    const { libp2p } = await network.use()\n    options.count = options.count || 10\n\n    const storedPeer = await libp2p.peerStore.get(peerId)\n    let id = storedPeer && storedPeer.id\n\n    if (!id) {\n      yield { ...basePacket, text: `Looking up peer ${peerId}` }\n      const remotePeer = await libp2p.peerRouting.findPeer(peerId)\n\n      id = remotePeer && remotePeer.id\n    }\n\n    if (!id) {\n      throw new Error('Peer was not found')\n    }\n\n    yield { ...basePacket, text: `PING ${id.toString()}` }\n\n    let packetCount = 0\n    let totalTime = 0\n\n    for (let i = 0; i < options.count; i++) {\n      try {\n        const time = await libp2p.ping(id)\n        totalTime += time\n        packetCount++\n        yield { ...basePacket, time }\n      } catch (/** @type {any} */ err) {\n        yield { ...basePacket, success: false, text: err.toString() }\n      }\n    }\n\n    if (packetCount) {\n      const average = totalTime / packetCount\n      yield { ...basePacket, text: `Average latency: ${average}ms` }\n    }\n  }\n\n  return withTimeoutOption(ping)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/pubsub.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport errCode from 'err-code'\nimport { NotEnabledError } from '../errors.js'\nimport get from 'dlv'\n\n/**\n * @typedef {import('@libp2p/interface-pubsub').Message} Message\n * @typedef {import('@libp2p/interfaces/events').EventHandler<CustomEvent<Message>>} EventHandler\n * @typedef {import('@libp2p/interfaces/events').EventHandler<Message>} MessageEventHandler\n */\n\n/**\n * @param {object} config\n * @param {import('../types').NetworkService} config.network\n * @param {import('ipfs-core-types/src/config').Config} [config.config]\n */\nexport function createPubsub ({ network, config }) {\n  const isEnabled = get(config || {}, 'Pubsub.Enabled', true)\n\n  /** @type {Record<string, MessageEventHandler[]>} */\n  const handlers = {}\n  /** @type {EventHandler | undefined} */\n  let onMessage\n\n  return {\n    subscribe: isEnabled ? withTimeoutOption(subscribe) : notEnabled,\n    unsubscribe: isEnabled ? withTimeoutOption(unsubscribe) : notEnabled,\n    publish: isEnabled ? withTimeoutOption(publish) : notEnabled,\n    ls: isEnabled ? withTimeoutOption(ls) : notEnabled,\n    peers: isEnabled ? withTimeoutOption(peers) : notEnabled\n  }\n\n  /**\n   * @type {import('ipfs-core-types/src/pubsub').API<{}>[\"subscribe\"]}\n   */\n  async function subscribe (topic, handler, options = {}) {\n    const { libp2p } = await network.use(options)\n\n    libp2p.pubsub.subscribe(topic)\n\n    // listen for 'message' events if we aren't already\n    if (onMessage == null) {\n      onMessage = (evt) => {\n        const msg = evt.detail\n\n        if (handlers[msg.topic]) {\n          handlers[msg.topic].forEach(handler => {\n            if (typeof handler === 'function') {\n              handler(msg)\n              return\n            }\n\n            if (handler != null && handler.handleEvent != null) {\n              handler.handleEvent(msg)\n            }\n          })\n        }\n      }\n\n      libp2p.pubsub.addEventListener('message', onMessage)\n    }\n\n    // store handler for future invocation\n    if (handler != null) {\n      if (handlers[topic] == null) {\n        handlers[topic] = []\n      }\n\n      handlers[topic].push(handler)\n    }\n  }\n\n  /**\n   * @type {import('ipfs-core-types/src/pubsub').API<{}>[\"unsubscribe\"]}\n   */\n  async function unsubscribe (topic, handler, options = {}) {\n    const { libp2p } = await network.use(options)\n\n    // remove handler from local map\n    if (handler != null && handlers[topic] != null) {\n      handlers[topic] = handlers[topic].filter(h => h !== handler)\n\n      if (handlers[topic].length === 0) {\n        delete handlers[topic]\n      }\n    }\n\n    // remove all handlers\n    if (typeof handler !== 'function') {\n      delete handlers[topic]\n    }\n\n    // no more handlers for this topic, unsubscribe\n    if (handlers[topic] == null) {\n      libp2p.pubsub.unsubscribe(topic)\n    }\n\n    // no more pubsub handlers, remove message listener\n    if (Object.keys(handlers).length === 0) {\n      libp2p.pubsub.removeEventListener('message', onMessage)\n      onMessage = undefined\n    }\n  }\n\n  /**\n   * @type {import('ipfs-core-types/src/pubsub').API<{}>[\"publish\"]}\n   */\n  async function publish (topic, data, options = {}) {\n    const { libp2p } = await network.use(options)\n    if (!data) {\n      throw errCode(new Error('argument \"data\" is required'), 'ERR_ARG_REQUIRED')\n    }\n\n    await libp2p.pubsub.publish(topic, data)\n  }\n\n  /**\n   * @type {import('ipfs-core-types/src/pubsub').API<{}>[\"ls\"]}\n   */\n  async function ls (options = {}) {\n    const { libp2p } = await network.use(options)\n\n    return libp2p.pubsub.getTopics()\n  }\n\n  /**\n   * @type {import('ipfs-core-types/src/pubsub').API<{}>[\"peers\"]}\n   */\n  async function peers (topic, options = {}) {\n    const { libp2p } = await network.use(options)\n\n    return libp2p.pubsub.getSubscribers(topic)\n  }\n}\n\nconst notEnabled = async () => { // eslint-disable-line require-await\n  throw new NotEnabledError('pubsub not enabled')\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/refs/index.js",
    "content": "import * as dagPB from '@ipld/dag-pb'\nimport { notFoundError } from 'datastore-core/errors'\nimport { toCidAndPath } from 'ipfs-core-utils/to-cid-and-path'\nimport { CID } from 'multiformats/cid'\nimport { TimeoutController } from 'timeout-abort-controller'\nimport { anySignal } from 'any-signal'\n\nconst ERR_NOT_FOUND = notFoundError().code\n\nexport const Format = {\n  default: '<dst>',\n  edges: '<src> -> <dst>'\n}\n\n/**\n * @typedef {object} Node\n * @property {string} [name]\n * @property {CID} cid\n *\n * @typedef {object} TraversalResult\n * @property {Node} parent\n * @property {Node} node\n * @property {boolean} isDuplicate\n *\n * @typedef {import('ipfs-core-types/src/utils').AbortOptions} AbortOptions\n */\n\n/**\n * @param {object} config\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n * @param {import('ipfs-core-utils/multicodecs').Multicodecs} config.codecs\n * @param {import('ipfs-core-types/src/root').API<{}>[\"resolve\"]} config.resolve\n * @param {import('../../types').Preload} config.preload\n */\nexport function createRefs ({ repo, codecs, resolve, preload }) {\n  /**\n   * @type {import('ipfs-core-types/src/refs').API<{}>[\"refs\"]}\n   */\n  async function * refs (ipfsPath, options = {}) {\n    if (options.maxDepth === 0) {\n      return\n    }\n\n    if (options.edges && options.format && options.format !== Format.default) {\n      throw new Error('Cannot set edges to true and also specify format')\n    }\n\n    options.format = options.edges ? Format.edges : options.format\n\n    if (typeof options.maxDepth !== 'number') {\n      options.maxDepth = options.recursive ? Infinity : 1\n    }\n\n    if (options.timeout) {\n      const controller = new TimeoutController(options.timeout)\n      const signals = [controller.signal]\n\n      if (options.signal) {\n        signals.push(options.signal)\n      }\n\n      options.signal = anySignal(signals)\n    }\n\n    /** @type {(string|CID)[]} */\n    const rawPaths = Array.isArray(ipfsPath) ? ipfsPath : [ipfsPath]\n\n    const paths = rawPaths.map(p => getFullPath(preload, p, options))\n\n    for (const path of paths) {\n      try {\n        yield * refsStream(resolve, repo, codecs, path, options)\n      } catch (/** @type {any} */ err) {\n        yield {\n          ref: '',\n          err: err.message\n        }\n      }\n    }\n  }\n\n  return refs\n}\n\n/**\n * @param {import('../../types').Preload} preload\n * @param {string | CID} ipfsPath\n * @param {import('ipfs-core-types/src/refs').RefsOptions} options\n */\nfunction getFullPath (preload, ipfsPath, options) {\n  const {\n    cid,\n    path\n  } = toCidAndPath(ipfsPath)\n\n  if (options.preload !== false) {\n    preload(cid)\n  }\n\n  return `/ipfs/${cid}${path || ''}`\n}\n\n/**\n * Get a stream of refs at the given path\n *\n * @param {import('ipfs-core-types/src/root').API<{}>[\"resolve\"]} resolve\n * @param {import('ipfs-repo').IPFSRepo} repo\n * @param {import('ipfs-core-utils/multicodecs').Multicodecs} codecs\n * @param {string} path\n * @param {import('ipfs-core-types/src/refs').RefsOptions} options\n */\nasync function * refsStream (resolve, repo, codecs, path, options) {\n  // Resolve to the target CID of the path\n  const resPath = await resolve(path, options)\n  const {\n    cid\n  } = toCidAndPath(resPath)\n\n  const maxDepth = options.maxDepth != null ? options.maxDepth : Infinity\n  const unique = options.unique || false\n\n  // Traverse the DAG, converting it into a stream\n  for await (const obj of objectStream(repo, codecs, cid, maxDepth, unique, options)) {\n    // Root object will not have a parent\n    if (!obj.parent) {\n      continue\n    }\n\n    // Filter out duplicates (isDuplicate flag is only set if options.unique is set)\n    if (obj.isDuplicate) {\n      continue\n    }\n\n    // Format the links\n    // Clients expect refs to be in the format { ref: <ref> }\n    yield {\n      ref: formatLink(obj.parent.cid, obj.node.cid, obj.node.name, options.format)\n    }\n  }\n}\n\n/**\n * Get formatted link\n *\n * @param {CID} srcCid\n * @param {CID} dstCid\n * @param {string} [linkName]\n * @param {string} [format]\n */\nfunction formatLink (srcCid, dstCid, linkName = '', format = Format.default) {\n  let out = format.replace(/<src>/g, srcCid.toString())\n  out = out.replace(/<dst>/g, dstCid.toString())\n  out = out.replace(/<linkname>/g, linkName)\n  return out\n}\n\n/**\n * Do a depth first search of the DAG, starting from the given root cid\n *\n * @param {import('ipfs-repo').IPFSRepo} repo\n * @param {import('ipfs-core-utils/multicodecs').Multicodecs} codecs\n * @param {CID} rootCid\n * @param {number} maxDepth\n * @param {boolean} uniqueOnly\n * @param {AbortOptions} options\n */\nasync function * objectStream (repo, codecs, rootCid, maxDepth, uniqueOnly, options) { // eslint-disable-line require-await\n  const seen = new Set()\n\n  /**\n   * @param {Node} parent\n   * @param {number} depth\n   * @returns {AsyncGenerator<TraversalResult, void, undefined>}\n   */\n  async function * traverseLevel (parent, depth) {\n    const nextLevelDepth = depth + 1\n\n    // Check the depth\n    if (nextLevelDepth > maxDepth) {\n      return\n    }\n\n    // Get this object's links\n    try {\n      // Look at each link, parent and the new depth\n      for await (const link of getLinks(repo, codecs, parent.cid, options)) {\n        yield {\n          parent: parent,\n          node: link,\n          isDuplicate: uniqueOnly && seen.has(link.cid.toString())\n        }\n\n        if (uniqueOnly) {\n          seen.add(link.cid.toString())\n        }\n\n        yield * traverseLevel(link, nextLevelDepth)\n      }\n    } catch (/** @type {any} */ err) {\n      if (err.code === ERR_NOT_FOUND) {\n        err.message = `Could not find object with CID: ${parent.cid}`\n      }\n\n      throw err\n    }\n  }\n\n  yield * traverseLevel({ cid: rootCid }, 0)\n}\n\n/**\n * Fetch a node and then get all its links\n *\n * @param {import('ipfs-repo').IPFSRepo} repo\n * @param {import('ipfs-core-utils/multicodecs').Multicodecs} codecs\n * @param {CID} cid\n * @param {AbortOptions} options\n * @returns {AsyncGenerator<{ name: string, cid: CID }, void, undefined>}\n */\nasync function * getLinks (repo, codecs, cid, options) {\n  const block = await repo.blocks.get(cid, options)\n  const codec = await codecs.getCodec(cid.code)\n  const value = codec.decode(block)\n  const isDagPb = cid.code === dagPB.code\n  /** @type {Array<string|number>} */\n  const base = []\n\n  for (const [name, cid] of links(value, base)) {\n    // special case for dag-pb - use the name of the link\n    // instead of the path within the object\n    if (isDagPb) {\n      const match = name.match(/^Links\\/(\\d+)\\/Hash$/)\n\n      if (match) {\n        const index = Number(match[1])\n\n        if (index < value.Links.length) {\n          yield {\n            name: value.Links[index].Name,\n            cid\n          }\n\n          continue\n        }\n      }\n    }\n\n    yield {\n      name,\n      cid\n    }\n  }\n}\n\n/**\n * @param {*} source\n * @param {Array<string|number>} base\n * @returns {Iterable<[string, CID]>}\n */\nconst links = function * (source, base) {\n  if (source == null) {\n    return\n  }\n\n  if (source instanceof Uint8Array) {\n    return\n  }\n\n  for (const [key, value] of Object.entries(source)) {\n    const path = [...base, key]\n\n    if (value != null && typeof value === 'object') {\n      if (Array.isArray(value)) {\n        for (const [index, element] of value.entries()) {\n          const elementPath = [...path, index]\n          const cid = CID.asCID(element)\n\n          // eslint-disable-next-line max-depth\n          if (cid) {\n            yield [elementPath.join('/'), cid]\n          } else if (typeof element === 'object') {\n            yield * links(element, elementPath)\n          }\n        }\n      } else {\n        const cid = CID.asCID(value)\n\n        if (cid) {\n          yield [path.join('/'), cid]\n        } else {\n          yield * links(value, path)\n        }\n      }\n    }\n  }\n\n  // ts requires a @returns annotation when a function is recursive,\n  // eslint requires a return when you use a @returns annotation.\n  return []\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/refs/local.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @param {object} config\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n */\nexport function createLocal ({ repo }) {\n  /**\n   * @type {import('ipfs-core-types/src/refs').API<{}>[\"local\"]}\n   */\n  async function * refsLocal (options = {}) {\n    for await (const cid of repo.blocks.queryKeys({}, { signal: options.signal })) {\n      yield { ref: cid.toString() }\n    }\n  }\n\n  return withTimeoutOption(refsLocal)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/repo/gc.js",
    "content": "import { logger } from '@libp2p/logger'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport { loadMfsRoot } from '../files/utils/with-mfs-root.js'\n\nconst log = logger('ipfs:repo:gc')\n\n/**\n * @typedef {import('ipfs-core-types/src/pin').API} PinAPI\n * @typedef {import('ipfs-core-types/src/refs').API} RefsAPI\n * @typedef {import('ipfs-repo').IPFSRepo} IPFSRepo\n * @typedef {import('interface-datastore').Key} Key\n * @typedef {import('multiformats/hashes/interface').MultihashHasher} MultihashHasher\n * @typedef {import('ipfs-core-utils/multihashes').Multihashes} Multihashes\n */\n\n/**\n * Perform mark and sweep garbage collection\n *\n * @param {object} config\n * @param {IPFSRepo} config.repo\n * @param {Multihashes} config.hashers\n */\nexport function createGc ({ repo, hashers }) {\n  /**\n   * @type {import('ipfs-core-types/src/repo').API<{}>[\"gc\"]}\n   */\n  async function * gc (options = {}) {\n    const start = Date.now()\n    let mfsRootCid\n\n    try {\n      mfsRootCid = await loadMfsRoot({\n        repo,\n        hashers\n      }, options)\n\n      // temporarily pin mfs root\n      await repo.pins.pinRecursively(mfsRootCid)\n\n      yield * repo.gc()\n    } finally {\n      // gc complete, unpin mfs root\n      if (mfsRootCid) {\n        await repo.pins.unpin(mfsRootCid)\n      }\n    }\n\n    log(`Complete (${Date.now() - start}ms)`)\n  }\n\n  return withTimeoutOption(gc)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/repo/index.js",
    "content": "import { createGc } from './gc.js'\nimport { createStat } from './stat.js'\nimport { createVersion } from './version.js'\n\n/**\n * @typedef {import('multiformats/hashes/interface').MultihashHasher} MultihashHasher\n * @typedef {import('ipfs-core-utils/multihashes').Multihashes} Multihashes\n */\n\nexport class RepoAPI {\n  /**\n   * @param {object} config\n   * @param {import('ipfs-repo').IPFSRepo} config.repo\n   * @param {Multihashes} config.hashers\n   */\n  constructor ({ repo, hashers }) {\n    this.gc = createGc({ repo, hashers })\n    this.stat = createStat({ repo })\n    this.version = createVersion({ repo })\n\n    /**\n     * @param {string} addr\n     */\n    this.setApiAddr = (addr) => repo.apiAddr.set(addr)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/repo/stat.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @param {object} config\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n */\nexport function createStat ({ repo }) {\n  /**\n   * @type {import('ipfs-core-types/src/repo').API<{}>[\"stat\"]}\n   */\n  async function stat (options = {}) {\n    const stats = await repo.stat()\n\n    return {\n      numObjects: BigInt(stats.numObjects.toString()),\n      repoSize: BigInt(stats.repoSize.toString()),\n      repoPath: stats.repoPath,\n      version: `${stats.version}`,\n      storageMax: BigInt(stats.storageMax.toString())\n    }\n  }\n\n  return withTimeoutOption(stat)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/repo/version.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport { repoVersion } from 'ipfs-repo/constants'\n\n/**\n * @param {object} config\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n */\nexport function createVersion ({ repo }) {\n  /**\n   * @type {import('ipfs-core-types/src/repo').API<{}>[\"version\"]}\n   */\n  async function version (options = {}) {\n    try {\n      // @ts-expect-error - not a public API\n      await repo._checkInitialized(options)\n    } catch (/** @type {any} */ err) {\n      // TODO: (dryajov) This is really hacky, there must be a better way\n      const match = [\n        /Key not found in database \\[\\/version\\]/,\n        /ENOENT/,\n        /repo is not initialized yet/\n      ].some((m) => {\n        return m.test(err.message)\n      })\n      if (match) {\n        // this repo has not been initialized\n        return repoVersion\n      }\n      throw err\n    }\n\n    return repo.version.get()\n  }\n\n  return withTimeoutOption(version)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/resolve.js",
    "content": "import * as isIpfs from 'is-ipfs'\nimport { CID } from 'multiformats/cid'\nimport { peerIdFromString } from '@libp2p/peer-id'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport { resolve as res } from '../utils.js'\n\n/**\n * @param {object} config\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n * @param {import('ipfs-core-utils/multicodecs').Multicodecs} config.codecs\n * @param {import('ipfs-core-utils/src/multibases').Multibases} config.bases\n * @param {import('ipfs-core-types/src/name').API} config.name\n */\nexport function createResolve ({ repo, codecs, bases, name }) {\n  /**\n   * @type {import('ipfs-core-types/src/root').API<{}>[\"resolve\"]}\n   */\n  async function resolve (path, opts = {}) {\n    if (!isIpfs.path(path)) {\n      throw new Error('invalid argument ' + path)\n    }\n\n    if (isIpfs.ipnsPath(path)) {\n      for await (const resolvedPath of name.resolve(path, opts)) {\n        path = resolvedPath\n      }\n    }\n\n    const [, schema, hash, ...rest] = path.split('/') // ['', 'ipfs', 'hash', ...path]\n    const base = opts.cidBase ? await bases.getBase(opts.cidBase) : undefined\n    const bytes = parseBytes(hash)\n\n    // nothing to resolve return the input\n    if (rest.length === 0) {\n      const str = base ? base.encoder.encode(bytes) : hash\n\n      return `/${schema}/${str}`\n    }\n\n    const cid = CID.decode(bytes)\n\n    path = rest.join('/')\n\n    const results = res(cid, path, codecs, repo, opts)\n    let value = cid\n    let remainderPath = path\n\n    for await (const result of results) {\n      if (CID.asCID(result.value)) {\n        value = result.value\n        remainderPath = result.remainderPath\n      }\n    }\n\n    return `/ipfs/${value.toString(base && base.encoder)}${remainderPath ? '/' + remainderPath : ''}`\n  }\n\n  return withTimeoutOption(resolve)\n}\n\n/**\n * Parse the input as a PeerID or a CID or throw an error\n *\n * @param {string} str\n */\nfunction parseBytes (str) {\n  try {\n    return peerIdFromString(str).toBytes()\n  } catch {\n    return CID.parse(str).bytes\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/root.js",
    "content": "import { createAdd } from './add.js'\nimport { createAddAll } from './add-all/index.js'\nimport { createCat } from './cat.js'\nimport { createGet } from './get.js'\nimport { createLs } from './ls.js'\n\n/**\n * @typedef {AddAllContext & CatContext & GetContext & ListContext } Context\n * @typedef {import('./add-all').Context} AddAllContext\n * @typedef {import('./cat').Context} CatContext\n * @typedef {import('./get').Context} GetContext\n * @typedef {import('./ls').Context} ListContext\n */\nexport class RootAPI {\n  /**\n   * @param {Context} context\n   */\n  constructor ({ preload, repo, hashers, options }) {\n    const addAll = createAddAll({\n      preload,\n      repo,\n      options,\n      hashers\n    })\n\n    this.addAll = addAll\n    this.add = createAdd({ addAll })\n    this.cat = createCat({ repo, preload })\n    this.get = createGet({ repo, preload })\n    this.ls = createLs({ repo, preload })\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/start.js",
    "content": "import { Service } from '../utils/service.js'\n\n/**\n * @param {object} config\n * @param {import('../types').NetworkService} config.network\n * @param {import('@libp2p/interface-peer-id').PeerId} config.peerId\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n * @param {import('../types').Print} config.print\n * @param {import('../types').Preload} config.preload\n * @param {import('../types').MfsPreload} config.mfsPreload\n * @param {import('./ipns').IPNSAPI} config.ipns\n * @param {import('@libp2p/interface-keychain').KeyChain} config.keychain\n * @param {import('ipfs-core-utils/multihashes').Multihashes} config.hashers\n * @param {import('../types').Options} config.options\n */\nexport function createStart ({ network, preload, peerId, keychain, repo, ipns, mfsPreload, print, hashers, options }) {\n  /**\n   * @type {import('ipfs-core-types/src/root').API<{}>[\"start\"]}\n   */\n  const start = async () => {\n    const { libp2p } = await Service.start(network, {\n      peerId,\n      repo,\n      print,\n      hashers,\n      options\n    })\n\n    await Promise.all([\n      ipns.startOnline({ keychain, libp2p, peerId, repo }),\n      preload.start(),\n      mfsPreload.start()\n    ])\n  }\n\n  return start\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/stats/bw.js",
    "content": "import parseDuration from 'parse-duration'\nimport errCode from 'err-code'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @typedef {import('ipfs-core-types/src/stats').BWOptions} BWOptions\n * @typedef {import('ipfs-core-types/src/stats').BWResult} BandwidthInfo\n * @typedef {import('libp2p').Libp2p} libp2p\n * @typedef {import('multiformats/cid').CID} CID\n * @typedef {import('ipfs-core-types/src/utils').AbortOptions} AbortOptions\n */\n\n/**\n * @param {libp2p} libp2p\n * @param {BWOptions} opts\n * @returns {BandwidthInfo}\n */\nfunction getBandwidthStats (libp2p, opts) {\n/*\n  let stats\n\n  if (!libp2p.metrics) {\n    stats = undefined\n  } else if (opts.peer) {\n    stats = libp2p.metrics.forPeer(opts.peer)\n  } else if (opts.proto) {\n    stats = libp2p.metrics.forProtocol(opts.proto)\n  } else {\n    stats = libp2p.metrics.getGlobal()\n  }\n*/\n  //  if (!stats) {\n  return {\n    totalIn: BigInt(0),\n    totalOut: BigInt(0),\n    rateIn: 0.0,\n    rateOut: 0.0\n  }\n  //  }\n/*\n  const movingAverages = stats.getMovingAverages()\n  const snapshot = stats.getSnapshot()\n\n  return {\n    totalIn: snapshot.dataReceived,\n    totalOut: snapshot.dataSent,\n    rateIn: movingAverages.dataReceived[60000].movingAverage / 60,\n    rateOut: movingAverages.dataSent[60000].movingAverage / 60\n  }\n*/\n}\n\n/**\n * @param {object} config\n * @param {import('../../types').NetworkService} config.network\n */\nexport function createBw ({ network }) {\n  /**\n   * @type {import('ipfs-core-types/src/stats').API<{}>[\"bw\"]}\n   */\n  const bw = async function * (options = {}) {\n    const { libp2p } = await network.use(options)\n\n    if (!options.poll) {\n      yield getBandwidthStats(libp2p, options)\n      return\n    }\n\n    const interval = options.interval || 1000\n    let ms = -1\n    try {\n      ms = typeof interval === 'string' ? parseDuration(interval) || -1 : interval\n      if (!ms || ms < 0) throw new Error('invalid duration')\n    } catch (/** @type {any} */ err) {\n      throw errCode(err, 'ERR_INVALID_POLL_INTERVAL')\n    }\n\n    let timeoutId\n    try {\n      while (true) {\n        yield getBandwidthStats(libp2p, options)\n        // eslint-disable-next-line no-loop-func\n        await new Promise(resolve => { timeoutId = setTimeout(resolve, ms) })\n      }\n    } finally {\n      clearTimeout(timeoutId)\n    }\n  }\n\n  return withTimeoutOption(bw)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/stats/index.js",
    "content": "import { createBw } from './bw.js'\nimport { createStat as createRepo } from '../repo/stat.js'\nimport { createStat as createBitswap } from '../bitswap/stat.js'\n\nexport class StatsAPI {\n  /**\n   * @param {object} config\n   * @param {import('ipfs-repo').IPFSRepo} config.repo\n   * @param {import('../../types').NetworkService} config.network\n   */\n  constructor ({ repo, network }) {\n    this.repo = createRepo({ repo })\n    this.bw = createBw({ network })\n    this.bitswap = createBitswap({ network })\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/stop.js",
    "content": "import { Service } from '../utils/service.js'\n\n/**\n * @param {object} config\n * @param {import('../types').NetworkService} config.network\n * @param {import('../types').Preload} config.preload\n * @param {import('./ipns').IPNSAPI} config.ipns\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n * @param {import('../types').MfsPreload} config.mfsPreload\n */\nexport function createStop ({ network, preload, ipns, repo, mfsPreload }) {\n  /**\n   * @type {import('ipfs-core-types/src/root').API<{}>[\"stop\"]}\n   */\n  const stop = async () => {\n    await Promise.all([\n      preload.stop(),\n      ipns.stop(),\n      mfsPreload.stop()\n    ])\n\n    await Service.stop(network)\n\n    // must be closed after stopping services as some of them\n    // will write into the datastore\n    await repo.close()\n  }\n\n  return stop\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/storage.js",
    "content": "import { logger } from '@libp2p/logger'\nimport { createRepo } from 'ipfs-core-config/repo'\nimport getDefaultConfig from 'ipfs-core-config/config'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\nimport { peerIdFromKeys } from '@libp2p/peer-id'\nimport { isPeerId } from '@libp2p/interface-peer-id'\nimport mergeOpts from 'merge-options'\nimport { profiles as configProfiles } from './config/profiles.js'\nimport { NotEnabledError, NotInitializedError } from '../errors.js'\nimport { createLibp2p } from './libp2p.js'\nimport { ERR_REPO_NOT_INITIALIZED } from 'ipfs-repo/errors'\nimport { createEd25519PeerId, createRSAPeerId } from '@libp2p/peer-id-factory'\nimport errCode from 'err-code'\nimport { unmarshalPrivateKey } from '@libp2p/crypto/keys'\nimport { Key } from 'interface-datastore/key'\n\nconst mergeOptions = mergeOpts.bind({ ignoreUndefined: true })\nconst log = logger('ipfs:components:peer:storage')\n\n/**\n * @typedef {import('ipfs-repo').IPFSRepo} IPFSRepo\n * @typedef {import('../types').Options} IPFSOptions\n * @typedef {import('../types').InitOptions} InitOptions\n * @typedef {import('../types').Print} Print\n * @typedef {import('ipfs-core-types/src/config').Config} IPFSConfig\n * @typedef {import('@libp2p/crypto/keys').KeyTypes} KeyType\n * @typedef {import('@libp2p/interface-keychain').KeyChain} Keychain\n * @typedef {import('@libp2p/interface-peer-id').PeerId} PeerId\n */\n\nexport class Storage {\n  /**\n   * @private\n   * @param {PeerId} peerId\n   * @param {Keychain} keychain\n   * @param {IPFSRepo} repo\n   * @param {Print} print\n   * @param {boolean} isNew\n   */\n  constructor (peerId, keychain, repo, print, isNew) {\n    this.print = print\n    this.peerId = peerId\n    this.keychain = keychain\n    this.repo = repo\n    this.print = print\n    this.isNew = isNew\n  }\n\n  /**\n   * @param {Print} print\n   * @param {import('ipfs-core-utils/multicodecs').Multicodecs} codecs\n   * @param {IPFSOptions} options\n   */\n  static async start (print, codecs, options) {\n    const { repoAutoMigrate, repo: inputRepo, onMigrationProgress } = options\n\n    const repo = (typeof inputRepo === 'string' || inputRepo == null)\n      ? createRepo(print, codecs, {\n        path: inputRepo,\n        autoMigrate: repoAutoMigrate,\n        onMigrationProgress: onMigrationProgress\n      })\n      : inputRepo\n\n    const { peerId, keychain, isNew } = await loadRepo(print, repo, options)\n\n    // TODO: throw error?\n    // @ts-expect-error On start, keychain will always be available\n    return new Storage(peerId, keychain, repo, print, isNew)\n  }\n}\n\n/**\n * @param {Print} print\n * @param {IPFSRepo} repo\n * @param {IPFSOptions} options\n */\nconst loadRepo = async (print, repo, options) => {\n  if (!repo.closed) {\n    return { ...await configureRepo(repo, options), isNew: false }\n  }\n\n  try {\n    await repo.open()\n\n    return { ...await configureRepo(repo, options), isNew: false }\n  } catch (/** @type {any} */ err) {\n    if (err.code !== ERR_REPO_NOT_INITIALIZED) {\n      throw err\n    }\n\n    if (options.init && options.init.allowNew === false) {\n      throw new NotEnabledError('Initialization of new repos disabled by config, pass `config.init.isNew: true` to enable it')\n    }\n\n    return { ...await initRepo(print, repo, options), isNew: true }\n  }\n}\n\n/**\n * @param {Print} print\n * @param {IPFSRepo} repo\n * @param {IPFSOptions} options\n * @returns {Promise<{peerId: PeerId, keychain?: Keychain}>}\n */\nconst initRepo = async (print, repo, options) => {\n  const initOptions = options.init || {}\n\n  // 1. Verify that repo does not exist yet (if it does and we could not open it we give up)\n  const exists = await repo.exists()\n  log('repo exists?', exists)\n\n  if (exists === true) {\n    throw new Error('repo already exists')\n  }\n\n  // 2. Restore `peerId` from a given `.privateKey` or init new using provided options.\n  const peerId = initOptions.privateKey\n    ? await decodePeerId(initOptions.privateKey)\n    : await initPeerId(print, initOptions)\n\n  const identity = peerIdToIdentity(peerId)\n\n  log('peer identity: %s', identity.PeerID)\n\n  // 3. Init new repo with provided `.config` and restored / initialized `peerId`\n  const config = {\n    ...mergeOptions(applyProfiles(getDefaultConfig(), initOptions.profiles), options.config),\n    Identity: identity\n  }\n  await repo.init(config)\n\n  // 4. Open initialized repo.\n  await repo.open()\n\n  log('repo opened')\n\n  /** @type {import('./libp2p').KeychainConfig} */\n  const keychainConfig = {\n    pass: options.pass\n  }\n\n  try {\n    keychainConfig.dek = await repo.config.get('Keychain.DEK')\n  } catch (/** @type {any} */ err) {\n    if (err.code !== 'ERR_NOT_FOUND') {\n      throw err\n    }\n  }\n\n  // Create libp2p for Keychain creation\n  const libp2p = await createLibp2p({\n    options: undefined,\n    multiaddrs: undefined,\n    peerId,\n    repo,\n    config,\n    keychainConfig\n  })\n\n  if (!(await repo.datastore.has(new Key('/info/self')))) {\n    await libp2p.keychain.importPeer('self', peerId)\n  }\n\n  await repo.config.set('Keychain', {\n    // @ts-expect-error private field\n    DEK: libp2p.keychain.init.dek\n  })\n\n  return { peerId, keychain: libp2p.keychain }\n}\n\n/**\n * Takes `peerId` either represented as a string serialized string or\n * an instance and returns a `PeerId` instance.\n *\n * @param {PeerId|string} peerId\n * @returns {Promise<PeerId>}\n */\nconst decodePeerId = async (peerId) => {\n  log('using user-supplied private-key')\n  if (isPeerId(peerId)) {\n    return peerId\n  }\n\n  const rawPrivateKey = uint8ArrayFromString(peerId, 'base64pad')\n  const key = await unmarshalPrivateKey(rawPrivateKey)\n  return await peerIdFromKeys(key.public.bytes, key.bytes)\n}\n\n/**\n * Initializes new PeerId by generating an underlying keypair.\n *\n * @param {Print} print\n * @param {object} options\n * @param {KeyType} [options.algorithm='Ed25519']\n * @param {number} [options.bits=2048]\n * @returns {Promise<PeerId>}\n */\nconst initPeerId = (print, { algorithm = 'Ed25519', bits = 2048 }) => {\n  // Generate peer identity keypair + transform to desired format + add to config.\n  print('generating %s keypair...', algorithm)\n\n  if (algorithm === 'Ed25519') {\n    return createEd25519PeerId()\n  }\n\n  if (algorithm === 'RSA') {\n    return createRSAPeerId({ bits })\n  }\n\n  throw errCode(new Error('Unknown PeerId algorithm'), 'ERR_UNKNOWN_PEER_ID_ALGORITHM')\n}\n\n/**\n * @param {PeerId} peerId\n */\nconst peerIdToIdentity = (peerId) => {\n  if (peerId.privateKey == null) {\n    throw errCode(new Error('Private key missing'), 'ERR_MISSING_PRIVATE_KEY')\n  }\n\n  return {\n    PeerID: peerId.toString(),\n    /** @type {string} */\n    PrivKey: uint8ArrayToString(peerId.privateKey, 'base64pad')\n  }\n}\n\n/**\n * Applies passed `profiles` and a `config` to an open repo.\n *\n * @param {IPFSRepo} repo\n * @param {IPFSOptions} options\n * @returns {Promise<{peerId: PeerId, keychain?: Keychain}>}\n */\nconst configureRepo = async (repo, options) => {\n  const config = options.config\n  const profiles = (options.init && options.init.profiles) || []\n  const pass = options.pass\n  const original = await repo.config.getAll()\n  const changed = mergeConfigs(applyProfiles(original, profiles), config)\n\n  if (original !== changed) {\n    await repo.config.replace(changed)\n  }\n\n  if (!changed.Identity || !changed.Identity.PrivKey) {\n    throw new NotInitializedError('No private key was found in the config, please intialize the repo')\n  }\n\n  const buf = uint8ArrayFromString(changed.Identity.PrivKey, 'base64pad')\n  const key = await unmarshalPrivateKey(buf)\n  const peerId = await peerIdFromKeys(key.public.bytes, key.bytes)\n  const libp2p = await createLibp2p({\n    options: undefined,\n    multiaddrs: undefined,\n    peerId,\n    repo,\n    config: changed,\n    keychainConfig: {\n      pass,\n      ...changed.Keychain\n    }\n  })\n\n  return { peerId, keychain: libp2p.keychain }\n}\n\n/**\n * @param {IPFSConfig} config\n * @param {Partial<IPFSConfig>} [changes]\n */\nconst mergeConfigs = (config, changes) =>\n  changes ? mergeOptions(config, changes) : config\n\n/**\n * Apply profiles (e.g. ['server', 'lowpower']) to config\n *\n * @param {IPFSConfig} config\n * @param {string[]} [profiles]\n */\nconst applyProfiles = (config, profiles) => {\n  return (profiles || []).reduce((config, name) => {\n    const profile = configProfiles[name]\n    if (!profile) {\n      throw new Error(`Could not find profile with name '${name}'`)\n    }\n    log('applying profile %s', name)\n    return profile.transform(config)\n  }, config)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/swarm/addrs.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @typedef {import('ipfs-core-types/src/swarm').AddrsResult} AddrsResult\n */\n\n/**\n * @param {object} config\n * @param {import('../../types').NetworkService} config.network\n */\nexport function createAddrs ({ network }) {\n  /**\n   * @type {import('ipfs-core-types/src/swarm').API<{}>[\"addrs\"]}\n   */\n  async function addrs (options = {}) { // eslint-disable-line require-await\n    /** @type {AddrsResult[]} */\n    const peers = []\n    const { libp2p } = await network.use(options)\n\n    await libp2p.peerStore.forEach(peer => {\n      peers.push({\n        id: peer.id,\n        addrs: peer.addresses.map((mi) => mi.multiaddr)\n      })\n    })\n\n    return peers\n  }\n\n  return withTimeoutOption(addrs)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/swarm/connect.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @param {object} config\n * @param {import('../../types').NetworkService} config.network\n */\nexport function createConnect ({ network }) {\n  /**\n   * @type {import('ipfs-core-types/src/swarm').API<{}>[\"connect\"]}\n   */\n  async function connect (multiaddrOrPeerId, options = {}) {\n    const { libp2p } = await network.use(options)\n    await libp2p.dial(multiaddrOrPeerId, options)\n  }\n\n  return withTimeoutOption(connect)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/swarm/disconnect.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @param {object} config\n * @param {import('../../types').NetworkService} config.network\n */\nexport function createDisconnect ({ network }) {\n  /**\n   * @type {import('ipfs-core-types/src/swarm').API<{}>[\"disconnect\"]}\n   */\n  async function disconnect (addr, options = {}) {\n    const { libp2p } = await network.use(options)\n    await libp2p.hangUp(addr)\n  }\n\n  return withTimeoutOption(disconnect)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/swarm/index.js",
    "content": "import { createAddrs } from './addrs.js'\nimport { createConnect } from './connect.js'\nimport { createDisconnect } from './disconnect.js'\nimport { createLocalAddrs } from './local-addrs.js'\nimport { createPeers } from './peers.js'\n\nexport class SwarmAPI {\n  /**\n   * @param {object} config\n   * @param {import('../../types').NetworkService} config.network\n   */\n  constructor ({ network }) {\n    this.addrs = createAddrs({ network })\n    this.connect = createConnect({ network })\n    this.disconnect = createDisconnect({ network })\n    this.localAddrs = createLocalAddrs({ network })\n    this.peers = createPeers({ network })\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/swarm/local-addrs.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @param {object} config\n * @param {import('../../types').NetworkService} config.network\n */\nexport function createLocalAddrs ({ network }) {\n  /**\n   * @type {import('ipfs-core-types/src/swarm').API<{}>[\"localAddrs\"]}\n   */\n  async function localAddrs (options = {}) {\n    const { libp2p } = await network.use(options)\n    return libp2p.getMultiaddrs()\n  }\n\n  return withTimeoutOption(localAddrs)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/swarm/peers.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @typedef {import('ipfs-core-types/src/swarm').PeersResult} PeersResult\n */\n\n/**\n * @param {object} config\n * @param {import('../../types').NetworkService} config.network\n */\nexport function createPeers ({ network }) {\n  /**\n   * @type {import('ipfs-core-types/src/swarm').API<{}>[\"peers\"]}\n   */\n  async function peers (options = {}) {\n    const { libp2p } = await network.use(options)\n\n    if (options.verbose) {\n      const peers = []\n      for (const connection of libp2p.getConnections()) {\n        /** @type {PeersResult} */\n        const peer = {\n          addr: connection.remoteAddr,\n          peer: connection.remotePeer\n        }\n\n        if (options.verbose || options.direction) {\n          peer.direction = connection.stat.direction\n        }\n\n        if (options.verbose) {\n          peer.muxer = connection.stat.multiplexer\n          peer.latency = 'n/a'\n          peer.streams = [] // TODO: get this from libp2p\n        }\n\n        peers.push(peer)\n      }\n\n      return peers\n    }\n\n    /** @type {Map<string, PeersResult>} */\n    const peers = new Map()\n\n    for (const connection of libp2p.getConnections()) {\n      /** @type {import('ipfs-core-types/src/swarm').PeersResult} */\n      const peer = {\n        addr: connection.remoteAddr,\n        peer: connection.remotePeer\n      }\n\n      peers.set(connection.remotePeer.toString(), peer)\n    }\n\n    return Array.from(peers.values())\n  }\n\n  return withTimeoutOption(peers)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/components/version.js",
    "content": "import { ipfsCore, interfaceIpfsCore, commit } from '../version.js'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @param {object} config\n * @param {import('ipfs-repo').IPFSRepo} config.repo\n */\nexport function createVersion ({ repo }) {\n  /**\n   * @type {import('ipfs-core-types/src/root').API<{}>[\"version\"]}\n   */\n  async function version (_options = {}) {\n    const repoVersion = await repo.version.get()\n\n    return {\n      version: ipfsCore,\n      commit,\n      repo: `${repoVersion}`,\n      'ipfs-core': ipfsCore,\n      'interface-ipfs-core': interfaceIpfsCore\n    }\n  }\n\n  return withTimeoutOption(version)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/errors.js",
    "content": "\nexport class NotInitializedError extends Error {\n  constructor (message = 'not initialized') {\n    super(message)\n    this.name = 'NotInitializedError'\n    this.code = NotInitializedError.code\n  }\n}\nNotInitializedError.code = 'ERR_NOT_INITIALIZED'\n\nexport class AlreadyInitializingError extends Error {\n  constructor (message = 'cannot initialize an initializing node') {\n    super(message)\n    this.name = 'AlreadyInitializingError'\n    this.code = AlreadyInitializedError.code\n  }\n}\nAlreadyInitializingError.code = 'ERR_ALREADY_INITIALIZING'\n\nexport class AlreadyInitializedError extends Error {\n  constructor (message = 'cannot re-initialize an initialized node') {\n    super(message)\n    this.name = 'AlreadyInitializedError'\n    this.code = AlreadyInitializedError.code\n  }\n}\nAlreadyInitializedError.code = 'ERR_ALREADY_INITIALIZED'\n\nexport class NotStartedError extends Error {\n  constructor (message = 'not started') {\n    super(message)\n    this.name = 'NotStartedError'\n    this.code = NotStartedError.code\n  }\n}\nNotStartedError.code = 'ERR_NOT_STARTED'\n\nexport class AlreadyStartingError extends Error {\n  constructor (message = 'cannot start, already startin') {\n    super(message)\n    this.name = 'AlreadyStartingError'\n    this.code = AlreadyStartingError.code\n  }\n}\nAlreadyStartingError.code = 'ERR_ALREADY_STARTING'\n\nexport class AlreadyStartedError extends Error {\n  constructor (message = 'cannot start, already started') {\n    super(message)\n    this.name = 'AlreadyStartedError'\n    this.code = AlreadyStartedError.code\n  }\n}\nAlreadyStartedError.code = 'ERR_ALREADY_STARTED'\n\nexport class NotEnabledError extends Error {\n  constructor (message = 'not enabled') {\n    super(message)\n    this.name = 'NotEnabledError'\n    this.code = NotEnabledError.code\n  }\n}\nNotEnabledError.code = 'ERR_NOT_ENABLED'\n"
  },
  {
    "path": "packages/ipfs-core/src/index.js",
    "content": "import { create as createImport } from './components/index.js'\nimport globSourceImport from 'ipfs-utils/src/files/glob-source.js'\nimport urlSourceImport from 'ipfs-utils/src/files/url-source.js'\n\n/**\n * @typedef {import('ipfs-core-types').IPFS} IPFS\n * @typedef {import('./types').Options} Options\n * @typedef {import('./types').Libp2pFactoryFn} Libp2pFactoryFn\n * @typedef {import('./types').Libp2pFactoryFnArgs} Libp2pFactoryFnArgs\n * @typedef {import('./types').InitOptions} InitOptions\n * @typedef {import('./types').RelayOptions} RelayOptions\n * @typedef {import('./types').PreloadOptions} PreloadOptions\n * @typedef {import('./types').ExperimentalOptions} ExperimentalOptions\n * @typedef {import('./types').Preload} Preload\n * @typedef {import('./types').MfsPreload} MfsPreload\n * @typedef {import('./types').LoadBaseFn} LoadBaseFn\n * @typedef {import('./types').LoadCodecFn} LoadCodecFn\n * @typedef {import('./types').LoadHasherFn} LoadHasherFn\n * @typedef {import('./types').IPLDOptions} IPLDOptions\n */\n\nexport const create = createImport\nexport const globSource = globSourceImport\nexport const urlSource = urlSourceImport\n"
  },
  {
    "path": "packages/ipfs-core/src/ipns/index.js",
    "content": "import errcode from 'err-code'\nimport { logger } from '@libp2p/logger'\nimport { IpnsPublisher } from './publisher.js'\nimport { IpnsRepublisher } from './republisher.js'\nimport { IpnsResolver } from './resolver.js'\nimport { TLRU } from '../utils/tlru.js'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\n\nconst log = logger('ipfs:ipns')\nconst defaultRecordTtl = 60 * 1000\n\n/**\n * @typedef {import('@libp2p/interface-keys').PrivateKey} PrivateKey\n * @typedef {import('@libp2p/interface-peer-id').PeerId} PeerId\n * @typedef {import('@libp2p/interfaces').AbortOptions} AbortOptions\n */\n\nexport class IPNS {\n  /**\n   * @param {import('ipfs-core-types/src/utils').BufferStore} routing\n   * @param {import('interface-datastore').Datastore} datastore\n   * @param {PeerId} peerId\n   * @param {import('@libp2p/interface-keychain').KeyChain} keychain\n   * @param {object} options\n   * @param {string} options.pass\n   * @param {number} [options.initialBroadcastInterval]\n   * @param {number} [options.broadcastInterval]\n   */\n  constructor (routing, datastore, peerId, keychain, options) {\n    this.publisher = new IpnsPublisher(routing, datastore)\n    this.republisher = new IpnsRepublisher(this.publisher, datastore, peerId, keychain, options)\n    this.resolver = new IpnsResolver(routing)\n    this.cache = new TLRU(1000)\n    this.routing = routing\n  }\n\n  /**\n   * Publish\n   *\n   * @param {PeerId} peerId\n   * @param {Uint8Array} value\n   * @param {number} lifetime\n   * @param {AbortOptions} [options]\n   */\n  async publish (peerId, value, lifetime = IpnsPublisher.defaultRecordLifetime, options) {\n    try {\n      await this.publisher.publishWithEOL(peerId, value, lifetime, options)\n\n      log(`IPNS value ${uint8ArrayToString(value, 'base32')} was published correctly`)\n\n      // // Add to cache\n      const id = peerId.toString()\n      // @ts-expect-error - parseFloat expects string\n      const ttEol = parseFloat(lifetime)\n      const ttl = (ttEol < defaultRecordTtl) ? ttEol : defaultRecordTtl\n\n      this.cache.set(id, value, ttl)\n\n      log(`IPNS value ${uint8ArrayToString(value, 'base32')} was cached correctly`)\n\n      return {\n        name: id,\n        value: value\n      }\n    } catch (/** @type {any} */ err) {\n      log.error(err)\n\n      throw err\n    }\n  }\n\n  /**\n   * Resolve\n   *\n   * @param {string} name\n   * @param {object} options\n   * @param {boolean} [options.nocache]\n   * @param {boolean} [options.recursive]\n   * @param {AbortSignal} [options.signal]\n   */\n  async resolve (name, options = {}) {\n    if (typeof name !== 'string') {\n      throw errcode(new Error('name received is not valid'), 'ERR_INVALID_NAME')\n    }\n\n    // If recursive, we should not try to get the cached value\n    if (!options.nocache && !options.recursive) {\n      // Try to get the record from cache\n      const id = name.split('/')[2]\n      const result = this.cache.get(id)\n\n      if (result) {\n        return result\n      }\n    }\n\n    try {\n      const result = await this.resolver.resolve(name, options)\n\n      log(`IPNS record from ${name} was resolved correctly`)\n\n      return result\n    } catch (/** @type {any} */ err) {\n      log.error(err)\n\n      throw err\n    }\n  }\n\n  /**\n   * Initialize keyspace\n   *\n   * Sets the ipns record for the given key to point to an empty directory\n   *\n   * @param {PeerId} peerId\n   * @param {Uint8Array} value\n   * @param {AbortOptions} [options]\n   */\n  async initializeKeyspace (peerId, value, options) { // eslint-disable-line require-await\n    return this.publish(peerId, value, IpnsPublisher.defaultRecordLifetime, options)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/ipns/publisher.js",
    "content": "import { isPeerId } from '@libp2p/interface-peer-id'\nimport { notFoundError } from 'datastore-core/errors'\nimport errcode from 'err-code'\nimport { logger } from '@libp2p/logger'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\nimport { equals as uint8ArrayEquals } from 'uint8arrays/equals'\nimport * as ipns from 'ipns'\n\nconst log = logger('ipfs:ipns:publisher')\n\n/**\n * @typedef {import('@libp2p/interface-keys').PrivateKey} PrivateKey\n * @typedef {import('@libp2p/interface-keys').PublicKey} PublicKey\n * @typedef {import('ipns').IPNSEntry} IPNSEntry\n * @typedef {import('@libp2p/interface-peer-id').PeerId} PeerId\n * @typedef {import('@libp2p/interfaces').AbortOptions} AbortOptions\n */\n\nconst ERR_NOT_FOUND = notFoundError().code\nconst defaultRecordLifetime = 60 * 60 * 1000\n\n// IpnsPublisher is capable of publishing and resolving names to the IPFS routing system.\nexport class IpnsPublisher {\n  /**\n   * @param {import('ipfs-core-types/src/utils').BufferStore} routing\n   * @param {import('interface-datastore').Datastore} datastore\n   */\n  constructor (routing, datastore) {\n    this._routing = routing\n    this._datastore = datastore\n  }\n\n  /**\n   * Publish record with a eol\n   *\n   * @param {PeerId} peerId\n   * @param {Uint8Array} value\n   * @param {number} lifetime\n   * @param {AbortOptions} [options]\n   */\n  async publishWithEOL (peerId, value, lifetime, options) {\n    const record = await this._updateOrCreateRecord(peerId, value, lifetime, options)\n\n    return this._putRecordToRouting(record, peerId, options)\n  }\n\n  /**\n   * Accepts a keypair, as well as a value (ipfsPath), and publishes it out to the routing system\n   *\n   * @param {PeerId} peerId\n   * @param {Uint8Array} value\n   * @param {AbortOptions} options\n   */\n  publish (peerId, value, options) {\n    return this.publishWithEOL(peerId, value, defaultRecordLifetime, options)\n  }\n\n  /**\n   * @param {Uint8Array} record\n   * @param {PeerId} peerId\n   * @param {AbortOptions} [options]\n   */\n  async _putRecordToRouting (record, peerId, options) {\n    if (!(isPeerId(peerId))) {\n      const errMsg = 'peerId received is not valid'\n      log.error(errMsg)\n\n      throw errcode(new Error(errMsg), 'ERR_INVALID_PEER_ID')\n    }\n\n    if (peerId.publicKey == null) {\n      throw errcode(new Error('Public key was missing'), 'ERR_MISSING_PUBLIC_KEY')\n    }\n\n    const routingKey = ipns.peerIdToRoutingKey(peerId)\n\n    await this._publishEntry(routingKey, record, options)\n\n    return record\n  }\n\n  /**\n   * @param {Uint8Array} key\n   * @param {Uint8Array} entry\n   * @param {AbortOptions} [options]\n   */\n  async _publishEntry (key, entry, options) {\n    // Add record to routing (buffer key)\n    try {\n      const res = await this._routing.put(key, entry, options)\n      log(`ipns record for ${uint8ArrayToString(key, 'base32')} was stored in the routing`)\n\n      return res\n    } catch (/** @type {any} */err) {\n      const errMsg = `ipns record for ${uint8ArrayToString(key, 'base32')} could not be stored in the routing - ${err.stack}`\n      log.error(errMsg)\n      log.error(err)\n\n      throw errcode(new Error(errMsg), 'ERR_PUTTING_TO_ROUTING')\n    }\n  }\n\n  /**\n   * Returns the record this node has published corresponding to the given peer ID.\n   *\n   * If `checkRouting` is true and we have no existing record, this method will check the routing system for any existing records.\n   *\n   * @param {PeerId} peerId\n   * @param {object} options\n   * @param {boolean} [options.checkRouting]\n   */\n  async _getPublished (peerId, options = {}) {\n    if (!(isPeerId(peerId))) {\n      const errMsg = 'peerId received is not valid'\n\n      log.error(errMsg)\n\n      throw errcode(new Error(errMsg), 'ERR_INVALID_PEER_ID')\n    }\n\n    const checkRouting = options.checkRouting !== false\n\n    try {\n      const dsVal = await this._datastore.get(ipns.getLocalKey(peerId.toBytes()))\n\n      // unmarshal data\n      return this._unmarshalData(dsVal)\n    } catch (/** @type {any} */ err) {\n      if (err.code !== ERR_NOT_FOUND) {\n        const errMsg = `unexpected error getting the ipns record ${peerId.toString()} from datastore`\n        log.error(errMsg)\n\n        throw errcode(new Error(errMsg), 'ERR_UNEXPECTED_DATASTORE_RESPONSE')\n      }\n\n      if (!checkRouting) {\n        throw errcode(err, 'ERR_NOT_FOUND_AND_CHECK_ROUTING_NOT_ENABLED')\n      }\n\n      // Try to get from routing\n      try {\n        const routingKey = ipns.peerIdToRoutingKey(peerId)\n        const res = await this._routing.get(routingKey)\n\n        // unmarshal data\n        return this._unmarshalData(res)\n      } catch (/** @type {any} */ err) {\n        log.error(err)\n\n        throw err\n      }\n    }\n  }\n\n  /**\n   * @param {Uint8Array} data\n   */\n  _unmarshalData (data) {\n    try {\n      return ipns.unmarshal(data)\n    } catch (/** @type {any} */ err) {\n      throw errcode(err, 'ERR_INVALID_RECORD_DATA')\n    }\n  }\n\n  /**\n   * @param {PeerId} peerId\n   * @param {Uint8Array} value\n   * @param {number} lifetime\n   * @param {AbortOptions} [options]\n   */\n  async _updateOrCreateRecord (peerId, value, lifetime, options) {\n    if (!(isPeerId(peerId))) {\n      const errMsg = 'peerId received is not valid'\n      log.error(errMsg)\n\n      throw errcode(new Error(errMsg), 'ERR_INVALID_PEER_ID')\n    }\n\n    const getPublishedOptions = {\n      checkRouting: true\n    }\n\n    /** @type {IPNSEntry | undefined} */\n    let record\n\n    try {\n      record = await this._getPublished(peerId, getPublishedOptions)\n    } catch (/** @type {any} */ err) {\n      if (err.code !== ERR_NOT_FOUND) {\n        const errMsg = `unexpected error when determining the last published IPNS record for ${peerId.toString()} ${err.stack}`\n        log.error(errMsg)\n\n        throw errcode(new Error(errMsg), 'ERR_DETERMINING_PUBLISHED_RECORD')\n      }\n    }\n\n    // Determinate the record sequence number\n    let seqNumber = 0n\n\n    if (record && record.sequence !== undefined) {\n      // Increment if the published value is different\n      seqNumber = uint8ArrayEquals(record.value, value) ? record.sequence : record.sequence + BigInt(1)\n    }\n\n    /** @type {IPNSEntry} */\n    let entryData\n\n    try {\n      // Create record\n      entryData = await ipns.create(peerId, value, seqNumber, lifetime)\n    } catch (/** @type {any} */ err) {\n      const errMsg = `ipns record for ${value} could not be created`\n\n      log.error(err)\n      throw errcode(new Error(errMsg), 'ERR_CREATING_IPNS_RECORD')\n    }\n\n    // TODO IMPROVEMENT - set ttl (still experimental feature for go)\n\n    try {\n      // Marshal record\n      const data = ipns.marshal(entryData)\n\n      // Store the new record\n      await this._datastore.put(ipns.getLocalKey(peerId.toBytes()), data, options)\n\n      log(`ipns record for ${uint8ArrayToString(value, 'base32')} was stored in the datastore`)\n\n      return data\n    } catch (/** @type {any} */ err) {\n      const errMsg = `ipns record for ${value} could not be stored in the datastore`\n      log.error(errMsg)\n\n      throw errcode(new Error(errMsg), 'ERR_STORING_IN_DATASTORE')\n    }\n  }\n}\n\nIpnsPublisher.defaultRecordLifetime = defaultRecordLifetime\n"
  },
  {
    "path": "packages/ipfs-core/src/ipns/republisher.js",
    "content": "import * as ipns from 'ipns'\nimport { importKey } from '@libp2p/crypto/keys'\nimport { isPeerId } from '@libp2p/interface-peer-id'\nimport errcode from 'err-code'\nimport { logger } from '@libp2p/logger'\nimport { peerIdFromKeys } from '@libp2p/peer-id'\nimport { TimeoutController } from 'timeout-abort-controller'\n\nconst log = logger('ipfs:ipns:republisher')\n\n/**\n * @typedef {import('@libp2p/interface-keys').PrivateKey} PrivateKey\n * @typedef {import('@libp2p/interface-peer-id').PeerId} PeerId\n * @typedef {import('@libp2p/interfaces').AbortOptions} AbortOptions\n */\n\nconst minute = 60 * 1000\nconst hour = 60 * minute\n\nconst defaultBroadcastInterval = 4 * hour\nconst defaultRecordLifetime = 24 * hour\n\nexport class IpnsRepublisher {\n  /**\n   * @param {import('./publisher').IpnsPublisher} publisher\n   * @param {import('interface-datastore').Datastore} datastore\n   * @param {PeerId} peerId\n   * @param {import('@libp2p/interface-keychain').KeyChain} keychain\n   * @param {object} options\n   * @param {string} options.pass\n   * @param {number} [options.initialBroadcastInterval]\n   * @param {number} [options.broadcastInterval]\n   */\n  constructor (publisher, datastore, peerId, keychain, options = { pass: '' }) {\n    this._publisher = publisher\n    this._datastore = datastore\n    this._peerId = peerId\n    this._keychain = keychain\n    this._options = options\n    this._republishHandle = null\n  }\n\n  async start () { // eslint-disable-line require-await\n    if (this._republishHandle) {\n      throw errcode(new Error('republisher is already running'), 'ERR_REPUBLISH_ALREADY_RUNNING')\n    }\n\n    // TODO: this handler should be isolated in another module\n    const republishHandle = {\n      /** @type {null|(() => Promise<void>)} */\n      _task: null,\n      /** @type {null|Promise<void>} */\n      _inflightTask: null,\n      /** @type {null|NodeJS.Timeout} */\n      _timeoutId: null,\n      /**\n       * @param {function(): number} period\n       */\n      runPeriodically: (period) => {\n        republishHandle._timeoutId = setTimeout(async () => {\n          republishHandle._timeoutId = null\n\n          try {\n            // @ts-expect-error - _task could be null\n            republishHandle._inflightTask = republishHandle._task()\n            await republishHandle._inflightTask\n\n            // Schedule next\n            if (republishHandle._task) {\n              republishHandle.runPeriodically(period)\n            }\n          } catch (/** @type {any} */ err) {\n            log.error(err)\n          }\n        }, period())\n      },\n      cancel: async () => {\n        // do not run again\n        if (republishHandle._timeoutId != null) {\n          clearTimeout(republishHandle._timeoutId)\n        }\n        republishHandle._task = null\n\n        // wait for the currently in flight task to complete\n        await republishHandle._inflightTask\n      }\n    }\n\n    const { pass } = this._options\n    let firstRun = true\n\n    republishHandle._task = async () => {\n      const timeoutController = new TimeoutController(30000)\n\n      try {\n        await this._republishEntries(this._peerId, pass, {\n          signal: timeoutController.signal\n        })\n      } finally {\n        timeoutController.clear()\n      }\n    }\n\n    republishHandle.runPeriodically(() => {\n      if (firstRun) {\n        firstRun = false\n        return this._options.initialBroadcastInterval || minute\n      }\n\n      return this._options.broadcastInterval || defaultBroadcastInterval\n    })\n\n    this._republishHandle = republishHandle\n  }\n\n  async stop () {\n    const republishHandle = this._republishHandle\n\n    if (!republishHandle) {\n      throw errcode(new Error('republisher is not running'), 'ERR_REPUBLISH_NOT_RUNNING')\n    }\n\n    this._republishHandle = null\n\n    await republishHandle.cancel()\n  }\n\n  /**\n   * @param {PeerId} peerId\n   * @param {string} pass\n   * @param {AbortOptions} options\n   */\n  async _republishEntries (peerId, pass, options) {\n    // TODO: Should use list of published entries.\n    // We can't currently *do* that because go uses this method for now.\n    try {\n      await this._republishEntry(peerId, options)\n    } catch (/** @type {any} */ err) {\n      const errMsg = 'cannot republish entry for the node\\'s private key'\n\n      log.error(errMsg)\n      return\n    }\n\n    // keychain needs pass to get the cryptographic keys\n    if (pass) {\n      try {\n        const keys = await this._keychain.listKeys()\n\n        for (const key of keys) {\n          if (key.name === 'self') {\n            continue\n          }\n\n          const pem = await this._keychain.exportKey(key.name, pass)\n          const privKey = await importKey(pem, pass)\n          const peerIdKey = await peerIdFromKeys(privKey.public.bytes, privKey.bytes)\n\n          await this._republishEntry(peerIdKey, options)\n        }\n      } catch (/** @type {any} */ err) {\n        log.error(err)\n      }\n    }\n  }\n\n  /**\n   * @param {PeerId} peerId\n   * @param {AbortOptions} options\n   */\n  async _republishEntry (peerId, options) {\n    try {\n      const value = await this._getPreviousValue(peerId)\n      await this._publisher.publishWithEOL(peerId, value, defaultRecordLifetime, options)\n    } catch (/** @type {any} */ err) {\n      if (err.code === 'ERR_NO_ENTRY_FOUND') {\n        return\n      }\n\n      throw err\n    }\n  }\n\n  /**\n   * @param {PeerId} peerId\n   */\n  async _getPreviousValue (peerId) {\n    if (!(isPeerId(peerId))) {\n      throw errcode(new Error('invalid peer ID'), 'ERR_INVALID_PEER_ID')\n    }\n\n    try {\n      const dsVal = await this._datastore.get(ipns.getLocalKey(peerId.toBytes()))\n\n      if (!(dsVal instanceof Uint8Array)) {\n        throw errcode(new Error(\"found ipns record that we couldn't process\"), 'ERR_INVALID_IPNS_RECORD')\n      }\n\n      // unmarshal data\n      try {\n        const record = ipns.unmarshal(dsVal)\n\n        return record.value\n      } catch (/** @type {any} */ err) {\n        log.error(err)\n        throw errcode(new Error('found ipns record that we couldn\\'t convert to a value'), 'ERR_INVALID_IPNS_RECORD')\n      }\n    } catch (/** @type {any} */ err) {\n      // error handling\n      // no need to republish\n      if (err && err.notFound) {\n        throw errcode(new Error(`no previous entry for record with id: ${peerId.toString()}`), 'ERR_NO_ENTRY_FOUND')\n      }\n\n      throw err\n    }\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/ipns/resolver.js",
    "content": "import * as ipns from 'ipns'\nimport { peerIdFromString } from '@libp2p/peer-id'\nimport errcode from 'err-code'\nimport { logger } from '@libp2p/logger'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { concat as uint8ArrayConcat } from 'uint8arrays/concat'\nimport * as Errors from 'datastore-core/errors'\nimport { ipnsValidator } from 'ipns/validator'\n\n/**\n * @typedef {import('@libp2p/interface-peer-id').PeerId} PeerId\n * @typedef {import('@libp2p/interfaces').AbortOptions} AbortOptions\n */\n\nconst log = logger('ipfs:ipns:resolver')\n\nconst ERR_NOT_FOUND = Errors.notFoundError().code\n\nconst defaultMaximumRecursiveDepth = 32\n\nexport class IpnsResolver {\n  /**\n   * @param {import('ipfs-core-types/src/utils').BufferStore} routing\n   */\n  constructor (routing) {\n    this._routing = routing\n  }\n\n  /**\n   * @param {string} name\n   * @param {object} [options]\n   * @param {boolean} [options.recursive]\n   * @param {AbortSignal} [options.signal]\n   */\n  async resolve (name, options = {}) {\n    if (typeof name !== 'string') {\n      throw errcode(new Error('invalid name'), 'ERR_INVALID_NAME')\n    }\n\n    const recursive = options.recursive && options.recursive.toString() === 'true'\n\n    const nameSegments = name.split('/')\n\n    if (nameSegments.length !== 3 || nameSegments[0] !== '') {\n      throw errcode(new Error('invalid name'), 'ERR_INVALID_NAME')\n    }\n\n    const key = nameSegments[2]\n\n    // Define a maximum depth if recursive option enabled\n    let depth = Infinity\n\n    if (recursive) {\n      depth = defaultMaximumRecursiveDepth\n    }\n\n    const res = await this.resolver(key, depth, options)\n\n    log(`${name} was locally resolved correctly`)\n    return res\n  }\n\n  /**\n   * Recursive resolver according to the specified depth\n   *\n   * @param {string} name\n   * @param {number} depth\n   * @param {AbortOptions} options\n   * @returns {Promise<string>}\n   */\n  async resolver (name, depth, options) {\n    // Exceeded recursive maximum depth\n    if (depth === 0) {\n      const errMsg = `could not resolve name (recursion limit of ${defaultMaximumRecursiveDepth} exceeded)`\n      log.error(errMsg)\n\n      throw errcode(new Error(errMsg), 'ERR_RESOLVE_RECURSION_LIMIT')\n    }\n\n    const res = await this._resolveName(name, options)\n    const nameSegments = res.split('/')\n\n    // If obtained a ipfs cid or recursive option is disabled\n    if (nameSegments[1] === 'ipfs' || !depth) {\n      return res\n    }\n\n    // continue recursively until depth equals 0\n    return this.resolver(nameSegments[2], depth - 1, options)\n  }\n\n  /**\n   * Resolve ipns entries from the provided routing\n   *\n   * @param {string} name\n   * @param {AbortOptions} options\n   */\n  async _resolveName (name, options) {\n    const peerId = peerIdFromString(name)\n    const routingKey = ipns.peerIdToRoutingKey(peerId)\n    let record\n\n    try {\n      record = await this._routing.get(routingKey, options)\n    } catch (/** @type {any} */ err) {\n      log.error('could not get record from routing', err)\n\n      if (err.code === ERR_NOT_FOUND) {\n        throw errcode(new Error(`record requested for ${name} was not found in the network`), 'ERR_NO_RECORD_FOUND')\n      }\n\n      throw errcode(new Error(`unexpected error getting the ipns record ${peerId.toString()}`), 'ERR_UNEXPECTED_ERROR_GETTING_RECORD')\n    }\n\n    // We should have the public key by now (inline, or in the entry)\n    return this._validateRecord(peerId, record)\n  }\n\n  /**\n   * Validate a resolved record\n   *\n   * @param {PeerId} peerId\n   * @param {Uint8Array} record\n   */\n  async _validateRecord (peerId, record) {\n    // IPNS entry validation\n    await ipnsValidator(uint8ArrayConcat([\n      uint8ArrayFromString('/ipns/'),\n      peerId.toBytes()\n    ]), record)\n\n    const ipnsEntry = ipns.unmarshal(record)\n\n    return uint8ArrayToString(ipnsEntry.value)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/ipns/routing/config.js",
    "content": "import { TieredDatastore } from 'datastore-core/tiered'\nimport get from 'dlv'\nimport { IpnsPubsubDatastore } from './pubsub-datastore.js'\nimport { OfflineDatastore } from './offline-datastore.js'\nimport { DHTDatastore } from './dht-datastore.js'\n\n/**\n * @typedef {import('interface-datastore').Datastore} Datastore\n */\n\n/**\n * @param {object} arg\n * @param {import('libp2p').Libp2p} arg.libp2p\n * @param {import('ipfs-repo').IPFSRepo} arg.repo\n * @param {import('@libp2p/interface-peer-id').PeerId} arg.peerId\n * @param {object} arg.options\n */\nexport function createRouting ({ libp2p, repo, peerId, options }) {\n  // Setup online routing for IPNS with a tiered routing composed by a DHT and a Pubsub router (if properly enabled)\n  /** @type {any[]} */\n  const ipnsStores = []\n\n  // Add IPNS pubsub if enabled\n  let pubsubDs\n  if (get(options, 'EXPERIMENTAL.ipnsPubsub', false)) {\n    pubsubDs = new IpnsPubsubDatastore(libp2p.pubsub, repo.datastore, peerId)\n    ipnsStores.push(pubsubDs)\n  }\n\n  // Add DHT datastore if enabled\n  if (get(options, 'offline', false) !== true && ['dht', 'dhtclient', 'dhtserver'].includes(get(options, 'config.Routing.Type', 'none'))) {\n    ipnsStores.push(new DHTDatastore(libp2p.dht))\n  }\n\n  // Add an offline datastore if we are offline or no other datastores are configured\n  if (get(options, 'offline', false) || ipnsStores.length === 0) {\n    const offlineDatastore = new OfflineDatastore(repo.datastore)\n    ipnsStores.push(offlineDatastore)\n  }\n\n  // Create ipns routing with a set of datastores\n  return new TieredDatastore(ipnsStores)\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/ipns/routing/dht-datastore.js",
    "content": "import drain from 'it-drain'\nimport { notFoundError } from 'datastore-core/errors'\nimport { logger } from '@libp2p/logger'\n\nconst log = logger('ipfs:ipns:dht-datastore')\n\n/**\n * @typedef {import('@libp2p/interfaces').AbortOptions} AbortOptions\n */\n\nexport class DHTDatastore {\n  /**\n   *\n   * @param {import('@libp2p/interface-dht').DHT} dht\n   */\n  constructor (dht) {\n    this._dht = dht\n  }\n\n  /**\n   * @param {Uint8Array} key - identifier of the value.\n   * @param {Uint8Array} value - value to be stored.\n   * @param {AbortOptions} [options]\n   */\n  async put (key, value, options) {\n    try {\n      await drain(this._dht.put(key, value, options))\n    } catch (/** @type {any} */ err) {\n      log.error(err)\n      throw err\n    }\n  }\n\n  /**\n   * @param {Uint8Array} key - identifier of the value to be obtained.\n   * @param {AbortOptions} [options]\n   */\n  async get (key, options) {\n    for await (const event of this._dht.get(key, options)) {\n      if (event.name === 'VALUE') {\n        return event.value\n      }\n    }\n\n    throw notFoundError()\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/ipns/routing/offline-datastore.js",
    "content": "import { Key } from 'interface-datastore/key'\nimport { Libp2pRecord } from '@libp2p/record'\nimport errcode from 'err-code'\nimport { logger } from '@libp2p/logger'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\n\nconst log = logger('ipfs:ipns:offline-datastore')\n\n/**\n * @typedef {import('@libp2p/interfaces').AbortOptions} AbortOptions\n */\n\n// Offline datastore aims to mimic the same encoding as routing when storing records\n// to the local datastore\nexport class OfflineDatastore {\n  /**\n   * @param {import('interface-datastore').Datastore} datastore\n   */\n  constructor (datastore) {\n    this._datastore = datastore\n    /** @type {any[]} */\n    this.stores = []\n  }\n\n  /**\n   * Put a value to the local datastore indexed by the received key properly encoded.\n   *\n   * @param {Uint8Array} key - identifier of the value.\n   * @param {Uint8Array} value - value to be stored.\n   * @param {AbortOptions} [options]\n   */\n  async put (key, value, options) { // eslint-disable-line require-await\n    if (!(key instanceof Uint8Array)) {\n      throw errcode(new Error('Offline datastore key must be a Uint8Array'), 'ERR_INVALID_KEY')\n    }\n\n    if (!(value instanceof Uint8Array)) {\n      throw errcode(new Error('Offline datastore value must be a Uint8Array'), 'ERR_INVALID_VALUE')\n    }\n\n    let routingKey\n\n    try {\n      routingKey = this._routingKey(key)\n    } catch (/** @type {any} */ err) {\n      log.error(err)\n      throw errcode(new Error('Not possible to generate the routing key'), 'ERR_GENERATING_ROUTING_KEY')\n    }\n\n    // Marshal to libp2p record as the DHT does\n    const record = new Libp2pRecord(key, value, new Date())\n\n    await this._datastore.put(routingKey, record.serialize(), options)\n  }\n\n  /**\n   * Get a value from the local datastore indexed by the received key properly encoded.\n   *\n   * @param {Uint8Array} key - identifier of the value to be obtained.\n   * @param {AbortOptions} [options]\n   */\n  async get (key, options) {\n    if (!(key instanceof Uint8Array)) {\n      throw errcode(new Error('Offline datastore key must be a Uint8Array'), 'ERR_INVALID_KEY')\n    }\n\n    let routingKey\n\n    try {\n      routingKey = this._routingKey(key)\n    } catch (/** @type {any} */ err) {\n      log.error(err)\n      throw errcode(new Error('Not possible to generate the routing key'), 'ERR_GENERATING_ROUTING_KEY')\n    }\n\n    const res = await this._datastore.get(routingKey, options)\n\n    // Unmarshal libp2p record as the DHT does\n    let record\n    try {\n      record = Libp2pRecord.deserialize(res)\n    } catch (/** @type {any} */ err) {\n      log.error(err)\n      throw err\n    }\n\n    return record.value\n  }\n\n  /**\n   * encode key properly - base32(/ipns/{cid})\n   *\n   * @param {Uint8Array} key\n   */\n  _routingKey (key) {\n    return new Key('/dht/record/' + uint8ArrayToString(key, 'base32'), false)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/ipns/routing/pubsub-datastore.js",
    "content": "import { namespaceLength, namespace, peerIdToRoutingKey } from 'ipns'\nimport { ipnsValidator } from 'ipns/validator'\nimport { ipnsSelector } from 'ipns/selector'\nimport { base58btc } from 'multiformats/bases/base58'\nimport { PubSubDatastore } from 'datastore-pubsub'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\nimport errcode from 'err-code'\nimport { logger } from '@libp2p/logger'\nimport { peerIdFromString } from '@libp2p/peer-id'\n\nconst log = logger('ipfs:ipns:pubsub')\n\n/**\n * @typedef {import('@libp2p/interfaces').AbortOptions} AbortOptions\n */\n\n// Pubsub datastore aims to manage the pubsub subscriptions for IPNS\nexport class IpnsPubsubDatastore {\n  /**\n   * @param {import('@libp2p/interface-pubsub').PubSub} pubsub\n   * @param {import('interface-datastore').Datastore} localDatastore\n   * @param {import('@libp2p/interface-peer-id').PeerId} peerId\n   */\n  constructor (pubsub, localDatastore, peerId) {\n    /** @type {Record<string, string>} */\n    this._subscriptions = {}\n\n    // Bind _handleSubscriptionKey function, which is called by PubsubDatastore.\n    this._handleSubscriptionKey = this._handleSubscriptionKey.bind(this)\n\n    this._pubsubDs = new PubSubDatastore(pubsub, localDatastore, peerId, ipnsValidator, ipnsSelector, this._handleSubscriptionKey)\n  }\n\n  /**\n   * Put a value to the pubsub datastore indexed by the received key properly encoded.\n   *\n   * @param {Uint8Array} key - identifier of the value.\n   * @param {Uint8Array} value - value to be stored.\n   * @param {AbortOptions} [options]\n   */\n  async put (key, value, options) {\n    try {\n      await this._pubsubDs.put(key, value, options)\n    } catch (/** @type {any} */ err) {\n      log.error(err)\n      throw err\n    }\n  }\n\n  /**\n   * Get a value from the pubsub datastore indexed by the received key properly encoded.\n   * Also, the identifier topic is subscribed to and the pubsub datastore records will be\n   * updated once new publishes occur.\n   *\n   * @param {Uint8Array} key - identifier of the value to be obtained.\n   * @param {AbortOptions} [options]\n   */\n  async get (key, options) {\n    let res\n    let err\n\n    try {\n      res = await this._pubsubDs.get(key, options)\n    } catch (/** @type {any} */ e) {\n      err = e\n    }\n\n    // Add topic subscribed\n    const ns = key.slice(0, namespaceLength)\n\n    if (uint8ArrayToString(ns) === namespace) {\n      const stringifiedTopic = base58btc.encode(key).substring(1)\n      const id = base58btc.encode(key.slice(namespaceLength)).substring(1)\n\n      this._subscriptions[stringifiedTopic] = id\n\n      log(`subscribed to pubsub topic ${stringifiedTopic}, id ${id}`)\n    }\n\n    // If no data was obtained, after storing the subscription, return the error.\n    if (err) {\n      throw err\n    }\n\n    return res\n  }\n\n  /**\n   * Modify subscription key to have a proper encoding\n   *\n   * @param {Uint8Array | string} key\n   */\n  _handleSubscriptionKey (key) {\n    if (key instanceof Uint8Array) {\n      key = uint8ArrayToString(key, 'base58btc')\n    }\n\n    const subscriber = this._subscriptions[key]\n\n    if (!subscriber) {\n      throw errcode(new Error(`key ${key} does not correspond to a subscription`), 'ERR_INVALID_KEY')\n    }\n\n    try {\n      const k = peerIdToRoutingKey(peerIdFromString(subscriber))\n      return k\n    } catch (/** @type {any} */ err) {\n      log.error(err)\n      throw err\n    }\n  }\n\n  /**\n   * Get pubsub subscriptions related to ipns.\n   */\n  getSubscriptions () {\n    const subscriptions = Object.values(this._subscriptions).filter(Boolean)\n\n    return subscriptions.map((sub) => `${namespace}${sub}`)\n  }\n\n  /**\n   * Cancel pubsub subscriptions related to ipns.\n   *\n   * @param {string} name - ipns path to cancel the pubsub subscription.\n   */\n  async cancel (name) { // eslint-disable-line require-await\n    if (typeof name !== 'string') {\n      throw errcode(new Error('invalid subscription name'), 'ERR_INVALID_SUBSCRIPTION_NAME')\n    }\n\n    // Trim /ipns/ prefix from the name\n    if (name.startsWith(namespace)) {\n      name = name.substring(namespaceLength)\n    }\n\n    const stringifiedTopic = Object.keys(this._subscriptions).find((key) => this._subscriptions[key] === name)\n\n    // Not found topic\n    if (!stringifiedTopic) {\n      return {\n        canceled: false\n      }\n    }\n\n    // Unsubscribe topic\n    const bufTopic = uint8ArrayFromString(stringifiedTopic)\n\n    this._pubsubDs.unsubscribe(bufTopic)\n\n    delete this._subscriptions[stringifiedTopic]\n    log(`unsubscribed pubsub ${stringifiedTopic}: ${name}`)\n\n    return {\n      canceled: true\n    }\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/mfs-preload.js",
    "content": "import { logger } from '@libp2p/logger'\n\nconst log = logger('ipfs:mfs-preload')\n\n/**\n * @typedef {PreloadOptions & MFSPreloadOptions} Options\n * @typedef {object} MFSPreloadOptions\n * @property {number} [interval]\n * @typedef {import('./types').PreloadOptions} PreloadOptions\n */\n\n/**\n * @param {object} config\n * @param {import('./types').Preload} config.preload\n * @param {import('ipfs-core-types/src/files').API} config.files\n * @param {Options} [config.options]\n */\nexport function createMfsPreloader ({ preload, files, options = {} }) {\n  options.interval = options.interval || 30 * 1000\n\n  if (!options.enabled) {\n    log('MFS preload disabled')\n    const noop = async () => {}\n    return { start: noop, stop: noop }\n  }\n\n  let rootCid = ''\n  /** @type {any} */\n  let timeoutId\n\n  const preloadMfs = async () => {\n    try {\n      const stats = await files.stat('/')\n      const nextRootCid = stats.cid.toString()\n\n      if (rootCid !== nextRootCid) {\n        log(`preloading updated MFS root ${rootCid} -> ${stats.cid}`)\n        await preload(stats.cid)\n        rootCid = nextRootCid\n      }\n    } catch (/** @type {any} */ err) {\n      log.error('failed to preload MFS root', err)\n    } finally {\n      timeoutId = setTimeout(preloadMfs, options.interval)\n    }\n  }\n\n  return {\n    /**\n     * @returns {Promise<void>}\n     */\n    async start () {\n      const stats = await files.stat('/')\n      rootCid = stats.cid.toString()\n      log(`monitoring MFS root ${stats.cid}`)\n      timeoutId = setTimeout(preloadMfs, options.interval)\n    },\n    /**\n     * @returns {void}\n     */\n    stop () {\n      clearTimeout(timeoutId)\n    }\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/preload.js",
    "content": "import { multiaddrToUri } from '@multiformats/multiaddr-to-uri'\nimport { logger } from '@libp2p/logger'\nimport shuffle from 'array-shuffle'\nimport { preload } from 'ipfs-core-config/preload'\nimport hashlru from 'hashlru'\n\nconst log = logger('ipfs:preload')\n\n/**\n * @param {import('./types').PreloadOptions} [options]\n */\nexport function createPreloader (options = {}) {\n  options.enabled = Boolean(options.enabled)\n  options.addresses = options.addresses || []\n  options.cache = options.cache || 1000\n\n  if (!options.enabled || !options.addresses.length) {\n    log('preload disabled')\n    const api = () => {}\n    return Object.assign(api, {\n      start: () => {},\n      stop: () => {}\n    })\n  }\n\n  let stopped = true\n  /** @type {AbortController[]} */\n  let requests = []\n  const apiUris = options.addresses.map((str) => multiaddrToUri(str))\n\n  // Avoid preloading the same CID over and over again\n  const cache = hashlru(options.cache)\n\n  /**\n   * @type {import('./types').Preload}\n   */\n  const api = async cid => {\n    try {\n      if (stopped) {\n        throw new Error(`preload ${cid} but preloader is not started`)\n      }\n\n      const path = cid.toString()\n\n      if (cache.has(path)) {\n        // we've preloaded this recently, don't preload it again\n        return\n      }\n\n      // make sure we don't preload this again any time soon\n      cache.set(path, true)\n\n      const fallbackApiUris = shuffle(apiUris)\n      let success = false\n      const now = Date.now()\n\n      for (const uri of fallbackApiUris) {\n        if (stopped) throw new Error(`preload aborted for ${path}`)\n        /** @type {AbortController} */\n        let controller\n\n        try {\n          controller = new AbortController()\n          requests = requests.concat(controller)\n          await preload(`${uri}/api/v0/refs?r=true&arg=${encodeURIComponent(path)}`, { signal: controller.signal })\n          success = true\n        } catch (/** @type {any} */ err) {\n          if (err.type !== 'aborted') log.error(err)\n        } finally {\n          requests = requests.filter(r => r !== controller)\n        }\n\n        if (success) break\n      }\n\n      log(`${success ? '' : 'un'}successfully preloaded ${path} in ${Date.now() - now}ms`)\n    } catch (/** @type {any} */ err) {\n      log.error(err)\n    }\n  }\n\n  /**\n   * @returns {void}\n   */\n  api.start = () => {\n    stopped = false\n  }\n\n  /**\n   * @returns {void}\n   */\n  api.stop = () => {\n    stopped = true\n    log(`aborting ${requests.length} pending preload request(s)`)\n    requests.forEach(r => r.abort())\n    requests = []\n  }\n\n  return api\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/types.ts",
    "content": "import type { KeyType } from '@libp2p/interface-keys'\nimport type { PeerId } from '@libp2p/interface-peer-id'\nimport type { Config as IPFSConfig } from 'ipfs-core-types/src/config'\nimport type { Libp2p, Libp2pOptions } from 'libp2p'\nimport type { IPFSRepo } from 'ipfs-repo'\nimport type { ProgressCallback as MigrationProgressCallback } from 'ipfs-repo-migrations'\nimport type { Network, Options as NetworkOptions } from './components/network'\nimport type { Datastore } from 'interface-datastore'\nimport type { Service } from './utils/service'\nimport type { CID } from 'multiformats/cid'\nimport type { BlockCodec } from 'multiformats/codecs/interface'\nimport type { MultibaseCodec } from 'multiformats/bases/interface'\nimport type { MultihashHasher } from 'multiformats/hashes/interface'\n\nexport interface Options {\n  /**\n   * Initialization options of the IPFS node.\n   * Note that *initializing* a repo is different from creating an instance of\n   * [`ipfs-repo`](https://github.com/ipfs/js-ipfs-repo). The IPFS constructor\n   * sets many special properties when initializing a repo, so you should usually\n   * not try and call `repoInstance.init()` yourself.\n   */\n  init?: InitOptions\n\n  /**\n   * If `false`, do not automatically start the IPFS node. Instead, you’ll need to manually call\n   * [`node.start()`](https://github.com/ipfs/js-ipfs/blob/master/packages/ipfs/docs/MODULE.md#nodestart)\n   * yourself.\n   */\n  start?: boolean\n\n  /**\n   * A passphrase to encrypt/decrypt keys stored in your keychain\n   */\n  pass?: string\n\n  /**\n   * Configure circuit relay (see the [circuit relay tutorial](https://github.com/ipfs-examples/js-ipfs-examples/tree/master/examples/circuit-relaying)\n   * to learn more)\n   */\n  relay?: RelayOptions\n\n  /**\n   * Run ipfs node offline. The node does not connect to the rest of the network\n   * but APIs that do not require network access will still work.\n   */\n  offline?: boolean\n\n  /**\n   * Configure remote preload nodes. The remote will preload content added on this node,\n   * and also attempt to preload objects requested by this node.\n   */\n  preload?: PreloadOptions\n\n  /**\n   * Enable and configure experimental features\n   */\n  EXPERIMENTAL?: ExperimentalOptions\n\n  /**\n   * Modify the default IPFS node config. This object will be *merged* with the default config; it will not replace it.\n   * (Default: [`config-nodejs.js`](https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-core-config/src/config-nodejs.js)\n   * in Node.js, [`config-browser.js`](https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-core-config/src/config-browser.js)\n   * in browsers)\n   */\n  config?: IPFSConfig\n\n  /**\n   * If multiple instances of IPFS are accessing the same repo - e.g. via node cluster or browser UI and webworkers\n   * one instance must be designated the repo owner to hold the lock on shared resources like the datastore.\n   *\n   * Set this property to true on one instance only if this is how your application is set up.\n   */\n  repoOwner?: boolean\n\n  /**\n   * The file path at which to store the IPFS node’s data. Alternatively, you can set up a customized\n   * storage system by providing an Repo implementation. (In browser default is 'ipfs').\n   */\n  repo?: IPFSRepo | string\n\n  /**\n   * Occasionally a repo migration is necessary - pass true here to to this automatically at startup\n   * when a new version of IPFS is being run for the first time and a migration is necessary, otherwise\n   * the node will refuse to start\n   */\n  repoAutoMigrate?: boolean\n\n  /**\n   * Pass a function here to be notified of progress when a repo migration is taking place\n   */\n  onMigrationProgress?: MigrationProgressCallback\n\n  /**\n   * To speed up peers store access, the data associated with this many peers is kept in memory in\n   * a least-recently used cache. The default is 1024. To hold all peers in memory at all times set\n   * this to Infinity.\n   */\n  peerStoreCacheSize?: number\n\n  /**\n   * Modify the default IPLD config. This object\n   * will be *merged* with the default config; it will not replace it. Check IPLD\n   * [docs](https://github.com/ipld/js-ipld#ipld-constructor) for more information\n   * on the available options. (Default: [`ipld.js`]\n   * (https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-core-config/src/ipld-nodejs.js) in Node.js, [`ipld-browser.js`](https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-core-config/src/ipld-browser.js)\n   * (https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-core-config/src/ipld.js)\n   * in browsers)\n   */\n  ipld?: Partial<IPLDOptions>\n\n  /**\n   * The libp2p option allows you to build\n   * your libp2p node by configuration, or via a bundle function. If you are\n   * looking to just modify the below options, using the object format is the\n   * quickest way to get the default features of libp2p. If you need to create a\n   * more customized libp2p node, such as with custom transports or peer/content\n   * routers that need some of the ipfs data on startup, a custom bundle is a\n   * great way to achieve this.\n   * - You can see the bundle in action in the [custom libp2p example](https://github.com/ipfs-examples/js-ipfs-examples/tree/master/examplescustom-libp2p).\n   * - Please see [libp2p/docs/CONFIGURATION.md](https://github.com/libp2p/js-libp2p/blob/master/doc/CONFIGURATION.md)\n   * for the list of options libp2p supports.\n   * - Default: [`libp2p-nodejs.js`](https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-core-config/src/libp2p-nodejs.js)\n   * in Node.js, [`libp2p-browser.js`](https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-core-config/src/libp2p-browser.js) in\n   * browsers.\n   */\n  libp2p?: Partial<Libp2pOptions> | Libp2pFactoryFn\n\n  silent?: boolean\n}\n\nexport interface Libp2pFactoryFnArgs {\n  libp2pOptions: Libp2pOptions\n  options: Options\n  config: IPFSConfig\n  datastore: Datastore\n  peerId: PeerId\n}\n\nexport interface Libp2pFactoryFn {\n  (args: Libp2pFactoryFnArgs): Promise<Libp2p>\n}\n\n/**\n * On first run js-IPFS will initialize a repo which can be customized through this settings\n */\nexport interface InitOptions {\n  /**\n   * Whether to remove built-in assets, like the instructional tour and empty mutable file system, from the repo\n   */\n  emptyRepo?: boolean\n\n  /**\n   * The type of key to use\n   */\n  algorithm?: KeyType\n\n  /**\n   * Number of bits to use in the generated key pair (rsa only)\n   */\n  bits?: number\n\n  /**\n   * A pre-generated private key to use\n   * **NOTE: This overrides `bits`.**\n   */\n  privateKey?: PeerId|string\n\n  /**\n   * Apply profile settings to config\n   */\n  profiles?: string[]\n\n  /**\n   * Set to `false` to disallow initialization if the repo does not already exist\n   */\n  allowNew?: boolean\n}\n\nexport interface RelayOptions {\n  /**\n   * Enable circuit relay dialer and listener. (Default: `true`)\n   */\n  enabled?: boolean\n\n  hop?: {\n    /**\n     * Make this node a relay (other nodes can connect *through* it). (Default: `false`)\n     */\n    enabled?: boolean\n\n    /**\n     * Make this an active relay node. Active relay nodes will attempt to dial a destination peer even if that peer is not yet connected to the relay. (Default: false)\n     */\n    active?: boolean\n  }\n}\n\nexport interface PreloadOptions {\n  /**\n   * Whether to preload anything\n   */\n  enabled?: boolean\n\n  /**\n   * How many CIDs to cache\n   */\n  cache?: number\n\n  /**\n   * Which preload servers to use.  **NOTE:** nodes specified here should also be added to your node's\n   * bootstrap address list at `config.Boostrap`\n   */\n  addresses?: string[]\n}\n\nexport interface ExperimentalOptions {\n  /**\n   * Enable pub-sub on IPNS. (Default: `false`)\n   */\n  ipnsPubsub?: boolean\n\n  /**\n   * Enable directory sharding. Directories that have many child objects will be represented by multiple\n   * DAG nodes instead of just one. It can improve lookup performance when a directory has several\n   * thousand files or more. (Default: `false`)\n   */\n  sharding?: boolean\n}\n\n/**\n * Prints output to the console\n */\nexport interface Print { (...args: any[]): void }\n\nexport interface Preload {\n  (cid: CID): void\n  start: () => void\n  stop: () => void\n}\n\nexport interface MfsPreload {\n  start: () => void\n  stop: () => void\n}\n\nexport type NetworkService = Service<NetworkOptions, Network>\n\nexport interface LoadBaseFn { (codeOrName: number | string): Promise<MultibaseCodec<any>> }\nexport interface LoadCodecFn { (codeOrName: number | string): Promise<BlockCodec<any, any>> }\nexport interface LoadHasherFn { (codeOrName: number | string): Promise<MultihashHasher> }\n\nexport interface IPLDOptions {\n  loadBase: LoadBaseFn\n  loadCodec: LoadCodecFn\n  loadHasher: LoadHasherFn\n  bases: Array<MultibaseCodec<any>>\n  codecs: Array<BlockCodec<any, any>>\n  hashers: MultihashHasher[]\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/utils/service.js",
    "content": "import { NotStartedError, AlreadyStartingError, AlreadyStartedError } from '../errors.js'\nimport { withTimeout } from '../utils.js'\n\n/**\n * @template T\n * @typedef {import('ipfs-core-types/src/utils').Await<T>} Await\n */\n/**\n * @template {(options:any) => any} T\n * @typedef {Parameters<T>[0]} Options\n */\n/**\n * @template {(options:any) => any} T\n * @typedef {ReturnType<T> extends ? Promise<infer U> ? U : ReturnType<T>} State\n */\n/**\n * Represents service state which can be not started in which case\n * it is instance of `Error`. Pending in which case it's promise or\n * ready in which case it is the value itself.\n *\n * @template T\n * @typedef {{ status: 'stopped' }\n * | { status: 'starting', ready: Await<T> }\n * | { status: 'started', value: T }\n * | { status: 'stopping', ready: Await<void> }\n * } ServiceState\n */\n\n/**\n * @typedef {import('ipfs-core-types/src/utils').AbortOptions} AbortOptions\n */\n\n/**\n * @template Options, T\n *\n * Allows you to create a handle to service that can be started or\n * stopped. It enables defining components that need to use service\n * functionality before service is started.\n *\n */\nexport class Service {\n  /**\n   * Takes `activation` function that takes `options` and (async) returns\n   * an implementation.\n   *\n   * @template {(options:any) => Await<any>} T\n   *\n   * @param {object} config\n   * @param {T} config.start\n   * @param {(state:State<T>) => Await<void>} [config.stop]\n   * @returns {Service<Parameters<T>[0], State<T>>}\n   */\n  static create ({ start, stop }) {\n    return new Service(start, stop)\n  }\n\n  /**\n   * Starts the service (by running actiavtion function). Will (async) throw\n   * unless service is stopped.\n   *\n   * @template Options, T\n   * @param {Service<Options, T>} service\n   * @param {Options} options\n   * @returns {Promise<T>}\n   */\n  static async start (service, options) {\n    const { state, activate } = service\n    switch (state.status) {\n      // If service is in 'stopped' state we activate and transition to\n      // to 'pending' state. Once activation is complete transition state to\n      // 'started' state.\n      // Note: This is the only code that does state transitions from\n      // - stopped\n      // - started\n      // Which ensures no race conditions can occur.\n      case 'stopped': {\n        try {\n          const promise = activate(options)\n          service.state = { status: 'starting', ready: promise }\n          // Note: MUST await after state transition above otherwise race\n          // condition may occur.\n          const result = await promise\n          service.state = { status: 'started', value: result }\n          return result\n        // If failed to start, transiton from 'starting' to 'stopped'\n        // state.\n        } catch (/** @type {any} */ error) {\n          service.state = { status: 'stopped' }\n          throw error\n        }\n      }\n      case 'starting': {\n        throw new AlreadyStartingError()\n      }\n      case 'started': {\n        throw new AlreadyStartedError()\n      }\n      // If service is stopping we just wait for that to complete\n      // and try again.\n      case 'stopping': {\n        await state.ready\n        return await Service.start(service, options)\n      }\n      default: {\n        return Service.panic(service)\n      }\n    }\n  }\n\n  /**\n   * Stops the service by executing deactivation. If service is stopped\n   * or is stopping this is noop. If service is starting up when called\n   * it will await for start to complete and then retry stop afterwards.\n   * This may (async) throw if `deactivate` does.\n   *\n   * @template T\n   * @param {Service<any, T>} service\n   * @returns {Promise<void>}\n   */\n  static async stop (service) {\n    const { state, deactivate } = service\n    switch (state.status) {\n      // If stopped there's nothing to do.\n      case 'stopped': {\n        break\n      }\n      // If service is starting we await for it to complete\n      // and try again. That way\n      case 'starting': {\n        // We do not want to error stop if start failed.\n        try { await state.ready } catch (/** @type {any} */ _) {}\n        return await Service.stop(service)\n      }\n      // if service is stopping we just await for it to complete.\n      case 'stopping': {\n        return await state.ready\n      }\n      case 'started': {\n        if (deactivate) {\n          await deactivate(state.value)\n        }\n        service.state = { status: 'stopped' }\n        break\n      }\n      default: {\n        Service.panic(state)\n      }\n    }\n  }\n\n  /**\n   * @template T\n   * @param {Service<any, T>} service\n   * @returns {T|null}\n   */\n  static try ({ state }) {\n    switch (state.status) {\n      case 'started':\n        return state.value\n      default:\n        return null\n    }\n  }\n\n  /**\n   * Unwraps state and returns underlying value. If state is in idle state it\n   * will throw an error. If state is pending it will wait and return the\n   * result or throw on failure. If state is ready returns result.\n   *\n   * @template T\n   * @param {Service<any, T>} service\n   * @param {AbortOptions} [options]\n   * @returns {Promise<T>}\n   */\n  static async use ({ state }, options) {\n    switch (state.status) {\n      case 'started':\n        return state.value\n      case 'starting':\n        return await withTimeout(state.ready, options)\n      default:\n        throw new NotStartedError()\n    }\n  }\n\n  // eslint-disable-next-line jsdoc/require-returns-check\n  /**\n   * @private\n   * @param {Service<any, any>} service\n   * @returns {never}\n   */\n  static panic ({ state }) {\n    const status = JSON.stringify({ status: state.status })\n    throw RangeError(`Service in invalid state ${status}, should never happen if you see this please report a bug`)\n  }\n\n  /**\n   * Takes `activation` function that takes `options` and (async) returns\n   * an implementation.\n   *\n   * @private\n   * @param {(options:Options) => Await<T>} activate\n   * @param {(state:T) => Await<void>} [deactivate]\n   */\n  constructor (activate, deactivate) {\n    this.activate = activate\n    this.deactivate = deactivate\n\n    /**\n     * A state machine for this service.\n     *\n     * @private\n     * @type {ServiceState<T>}\n     */\n    this.state = { status: 'stopped' }\n  }\n\n  /**\n   * Allows you to asynchronously obtain service implementation. If service\n   * is starting it will await for completion. If service is stopped or stopping\n   * this will (async) throw exception. This allows components that need to use\n   * this service convenient API to do it.\n   *\n   * @param {AbortOptions} [options] - Abort options.\n   * @returns {Promise<T>}\n   */\n  async use (options) {\n    return await Service.use(this, options)\n  }\n\n  /**\n   * @returns {T|null}\n   */\n  try () {\n    return Service.try(this)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/utils/tlru.js",
    "content": "import hashlru from 'hashlru'\n\n/**\n * Time Aware Least Recent Used Cache\n *\n * @see https://arxiv.org/pdf/1801.00390\n * @todo move this to ipfs-utils or it's own package\n *\n * @template T\n * @class TLRU\n */\nexport class TLRU {\n  /**\n   * Creates an instance of TLRU.\n   *\n   * @param {number} maxSize\n   */\n  constructor (maxSize) {\n    this.lru = hashlru(maxSize)\n  }\n\n  /**\n   * Get the value from the a key\n   *\n   * @param {string} key\n   * @returns {T|undefined}\n   * @memberof TLoRU\n   */\n  get (key) {\n    const value = this.lru.get(key)\n    if (value) {\n      if ((value.expire) && (value.expire < Date.now())) {\n        this.lru.remove(key)\n        return undefined\n      }\n      return value.value\n    }\n    return undefined\n  }\n\n  /**\n   * Set a key value pair\n   *\n   * @param {string} key\n   * @param {T} value\n   * @param {number} ttl - in miliseconds\n   * @returns {void}\n   */\n  set (key, value, ttl) {\n    this.lru.set(key, { value, expire: Date.now() + ttl })\n  }\n\n  /**\n   * Find if the cache has the key\n   *\n   * @param {string} key\n   * @returns {boolean}\n   */\n  has (key) {\n    const value = this.get(key)\n    if (value) {\n      return true\n    }\n    return false\n  }\n\n  /**\n   * Remove key\n   *\n   * @param {string} key\n   */\n  remove (key) {\n    this.lru.remove(key)\n  }\n\n  /**\n   * Clears the cache\n   *\n   * @memberof TLRU\n   */\n  clear () {\n    this.lru.clear()\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/utils.js",
    "content": "/* eslint-disable no-unreachable */\n\nimport * as isIpfs from 'is-ipfs'\nimport { CID } from 'multiformats/cid'\nimport { Key } from 'interface-datastore/key'\nimport errCode from 'err-code'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport { toCidAndPath } from 'ipfs-core-utils/to-cid-and-path'\nimport * as dagPB from '@ipld/dag-pb'\n\n/**\n * @typedef {import('ipfs-core-types/src/utils').AbortOptions} AbortOptions\n * @typedef {import('@ipld/dag-pb').PBLink} PBLink\n */\n\nconst ERR_BAD_PATH = 'ERR_BAD_PATH'\n\nexport const OFFLINE_ERROR = 'This command must be run in online mode. Try running \\'ipfs daemon\\' first.'\nexport const MFS_ROOT_KEY = new Key('/local/filesroot')\nexport const MFS_MAX_CHUNK_SIZE = 262144\nexport const MFS_MAX_LINKS = 174\n\n/**\n * Returns a well-formed ipfs Path.\n * The returned path will always be prefixed with /ipfs/ or /ipns/.\n *\n * @param  {string | CID} pathStr - An ipfs-path, or ipns-path or a cid\n * @returns {string} - ipfs-path or ipns-path\n * @throws on an invalid @param pathStr\n */\nexport const normalizePath = (pathStr) => {\n  const cid = CID.asCID(pathStr)\n\n  if (cid) {\n    return `/ipfs/${pathStr}`\n  }\n\n  const str = pathStr.toString()\n\n  try {\n    return `/ipfs/${CID.parse(str)}`\n  } catch {}\n\n  if (isIpfs.path(str)) {\n    return str\n  } else {\n    throw errCode(new Error(`invalid path: ${pathStr}`), ERR_BAD_PATH)\n  }\n}\n\n// TODO: do we need both normalizePath and normalizeCidPath?\n// TODO: don't forget ipfs-core-utils/src/to-cid-and-path\n/**\n * @param {Uint8Array|CID|string} path\n */\nexport const normalizeCidPath = (path) => {\n  if (path instanceof Uint8Array) {\n    return CID.decode(path).toString()\n  }\n\n  path = path.toString()\n\n  if (path.indexOf('/ipfs/') === 0) {\n    path = path.substring('/ipfs/'.length)\n  }\n\n  if (path.charAt(path.length - 1) === '/') {\n    path = path.substring(0, path.length - 1)\n  }\n\n  return path\n}\n\n/**\n * Resolve various styles of an ipfs-path to the hash of the target node.\n * Follows links in the path\n *\n * @param {import('ipfs-repo').IPFSRepo} repo\n * @param {import('ipfs-core-utils/multicodecs').Multicodecs} codecs\n * @param {CID | string | Uint8Array} ipfsPath - A CID or IPFS path\n * @param {{ path?: string, signal?: AbortSignal }} [options] - Optional options passed directly to dag.resolve\n * @returns {Promise<{ cid: CID, remainderPath: string}>}\n */\nexport const resolvePath = async function (repo, codecs, ipfsPath, options = {}) {\n  const {\n    cid,\n    path\n  } = toCidAndPath(ipfsPath)\n\n  if (path) {\n    options.path = path\n  }\n\n  let lastCid = cid\n  let lastRemainderPath = options.path || ''\n\n  if (lastRemainderPath.startsWith('/')) {\n    lastRemainderPath = lastRemainderPath.substring(1)\n  }\n\n  if (options.path) {\n    try {\n      for await (const { value, remainderPath } of resolve(cid, options.path, codecs, repo, {\n        signal: options.signal\n      })) {\n        if (!CID.asCID(value)) {\n          break\n        }\n\n        lastRemainderPath = remainderPath\n        lastCid = value\n      }\n    } catch (/** @type {any} */ err) {\n      // TODO: add error codes to IPLD\n      if (err.message.startsWith('Object has no property')) {\n        err.message = `no link named \"${lastRemainderPath.split('/')[0]}\" under ${lastCid}`\n        err.code = 'ERR_NO_LINK'\n      }\n      throw err\n    }\n  }\n\n  return {\n    cid: lastCid,\n    remainderPath: lastRemainderPath || ''\n  }\n}\n\n/**\n * @typedef {import('ipfs-unixfs-exporter').UnixFSEntry} UnixFSEntry\n *\n * @param {UnixFSEntry} file\n */\nexport const mapFile = (file) => {\n  if (file.type !== 'file' && file.type !== 'directory' && file.type !== 'raw') {\n    // file.type === object | identity not supported yet\n    throw new Error(`Unknown node type '${file.type}'`)\n  }\n\n  /** @type {import('ipfs-core-types/src/root').IPFSEntry} */\n  const output = {\n    cid: file.cid,\n    path: file.path,\n    name: file.name,\n    size: file.size,\n    type: 'file'\n  }\n\n  if (file.type === 'directory') {\n    // @ts-expect-error - TS type can't be changed from File to Directory\n    output.type = 'dir'\n  }\n\n  if (file.type === 'file') {\n    output.size = file.unixfs.fileSize()\n  }\n\n  if (file.type === 'file' || file.type === 'directory') {\n    output.mode = file.unixfs.mode\n\n    if (file.unixfs.mtime !== undefined) {\n      output.mtime = file.unixfs.mtime\n    }\n  }\n\n  return output\n}\n\nexport const withTimeout = withTimeoutOption(\n  /**\n   * @template T\n   * @param {Promise<T>|T} promise\n   * @param {AbortOptions} [_options]\n   * @returns {Promise<T>}\n   */\n  async (promise, _options) => await promise\n)\n\n/**\n * Retrieves IPLD Nodes along the `path` that is rooted at `cid`.\n *\n * @param {CID} cid - the CID where the resolving starts\n * @param {string} path - the path that should be resolved\n * @param {import('ipfs-core-utils/src/multicodecs').Multicodecs} codecs\n * @param {import('ipfs-repo').IPFSRepo} repo\n * @param {AbortOptions} [options]\n */\nexport const resolve = async function * (cid, path, codecs, repo, options) {\n  /**\n   * @param {CID} cid\n   */\n  const load = async (cid) => {\n    const codec = await codecs.getCodec(cid.code)\n    const block = await repo.blocks.get(cid, options)\n\n    return codec.decode(block)\n  }\n\n  const parts = path.split('/').filter(Boolean)\n  let value = await load(cid)\n  let lastCid = cid\n\n  // End iteration if there isn't a CID to follow any more\n  while (parts.length) {\n    const key = parts.shift()\n\n    if (!key) {\n      throw errCode(new Error(`Could not resolve path \"${path}\"`), 'ERR_INVALID_PATH')\n    }\n\n    // special case for dag-pb, use the link name as the path segment\n    if (cid.code === dagPB.code && Array.isArray(value.Links)) {\n      const link = value.Links.find((/** @type {PBLink} */ l) => l.Name === key)\n\n      if (link) {\n        yield {\n          value: link.Hash,\n          remainderPath: parts.join('/')\n        }\n\n        value = await load(link.Hash)\n        lastCid = link.Hash\n\n        continue\n      }\n    }\n\n    if (Object.prototype.hasOwnProperty.call(value, key)) {\n      value = value[key]\n\n      yield {\n        value,\n        remainderPath: parts.join('/')\n      }\n    } else {\n      throw errCode(new Error(`no link named \"${key}\" under ${lastCid}`), 'ERR_NO_LINK')\n    }\n\n    if (CID.asCID(value)) {\n      lastCid = value\n      value = await load(value)\n    }\n  }\n\n  yield {\n    value,\n    remainderPath: ''\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/src/version.js",
    "content": "\nexport const ipfsCore = ''\nexport const commit = ''\nexport const interfaceIpfsCore = ''\n"
  },
  {
    "path": "packages/ipfs-core/test/add-all.spec.js",
    "content": "/* eslint max-nested-callbacks: [\"error\", 8] */\n/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport * as utils from '../src/components/add-all/utils.js'\n\ndescribe('add-all/utils', () => {\n  describe('parseChunkerString', () => {\n    it('handles an empty string', () => {\n      const options = utils.parseChunkerString('')\n      expect(options.chunker).to.equal('fixed')\n    })\n\n    it('handles a null chunker string', () => {\n      // @ts-expect-error null is not string | undefined\n      const options = utils.parseChunkerString(null)\n      expect(options.chunker).to.equal('fixed')\n    })\n\n    it('parses a fixed size string', () => {\n      const options = utils.parseChunkerString('size-512')\n      expect(options.chunker).to.equal('fixed')\n      expect(options.maxChunkSize).to.equal(512)\n    })\n\n    it('parses a rabin string without size', () => {\n      const options = utils.parseChunkerString('rabin')\n      expect(options.chunker).to.equal('rabin')\n\n      if (options.chunker === 'rabin') {\n        expect(options.avgChunkSize).to.equal(262144)\n      }\n    })\n\n    it('parses a rabin string with only avg size', () => {\n      const options = utils.parseChunkerString('rabin-512')\n      expect(options.chunker).to.equal('rabin')\n\n      if (options.chunker === 'rabin') {\n        expect(options.avgChunkSize).to.equal(512)\n      }\n    })\n\n    it('parses a rabin string with min, avg, and max', () => {\n      const options = utils.parseChunkerString('rabin-42-92-184')\n      expect(options.chunker).to.equal('rabin')\n\n      if (options.chunker === 'rabin') {\n        expect(options.minChunkSize).to.equal(42)\n        expect(options.avgChunkSize).to.equal(92)\n      }\n\n      expect(options.maxChunkSize).to.equal(184)\n    })\n\n    it('throws an error for unsupported chunker type', () => {\n      const fn = () => utils.parseChunkerString('fake-512')\n      expect(fn).to.throw(Error)\n    })\n\n    it('throws an error for incorrect format string', () => {\n      const fn = () => utils.parseChunkerString('fixed-abc')\n      expect(fn).to.throw(Error)\n    })\n\n    it('throws an error for incorrect rabin format string', () => {\n      const fn = () => utils.parseChunkerString('rabin-1-2-3-4')\n      expect(fn).to.throw(Error)\n    })\n\n    it('throws an error for non integer rabin parameters', () => {\n      const fn = () => utils.parseChunkerString('rabin-abc')\n      expect(fn).to.throw(Error)\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-core/test/block-storage.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { MemoryBlockstore } from 'blockstore-core/memory'\nimport { interfaceBlockstoreTests } from 'interface-blockstore-tests'\nimport { BlockStorage } from '../src/block-storage.js'\n\n/**\n * @typedef {import('ipfs-repo').IPFSRepo} IPFSRepo\n * @typedef {import('interface-blockstore').Blockstore} Blockstore\n */\n\nclass MockBitswap extends MemoryBlockstore {\n  /**\n   * @param {boolean} started\n   */\n  constructor (started) {\n    super()\n\n    this.isStarted = () => started\n  }\n}\n\ndescribe('block-storage', () => {\n  describe('interface-blockstore (bitswap online)', () => {\n    interfaceBlockstoreTests({\n      setup: () => {\n        // bitswap forwards on to the blockstore so just\n        // use the same instance to represent both\n        const blockstore = new MockBitswap(true)\n\n        // @ts-expect-error MockBitswap is missing some properties\n        return new BlockStorage(blockstore, blockstore)\n      },\n      teardown: () => {}\n    })\n  })\n\n  describe('interface-blockstore (bitswap offline)', () => {\n    interfaceBlockstoreTests({\n      setup: () => {\n        // bitswap forwards on to the blockstore so just\n        // use the same instance to represent both\n        const blockstore = new MockBitswap(false)\n\n        // @ts-expect-error MockBitswap is missing some properties\n        return new BlockStorage(blockstore, blockstore)\n      },\n      teardown: () => {}\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-core/test/bootstrappers.js",
    "content": "/* eslint-env mocha */\n\nimport createConfig from 'ipfs-core-config/config'\nimport { waitFor } from './utils/wait-for.js'\nimport createNode from './utils/create-node.js'\n\nconst { Bootstrap: bootstrapList } = createConfig()\n\n/*\n * These tests were graciously made for lgierth, so that he can test the\n * WebSockets Bootstrappers easily <3\n */\ndescribe('Check that a js-ipfs node can indeed contact the bootstrappers', () => {\n  /** @type {import('ipfs-core-types').IPFS} */\n  let ipfs\n  /** @type {() => Promise<void>} */\n  let cleanup\n\n  before(async () => {\n    const res = await createNode({\n      config: {\n        Bootstrap: bootstrapList\n      }\n    })\n    ipfs = res.ipfs\n    cleanup = res.cleanup\n  })\n\n  after(() => cleanup())\n\n  it('a node connects to bootstrappers', async function () {\n    this.timeout(2 * 60 * 1000)\n\n    const test = async () => {\n      const peers = await ipfs.swarm.peers()\n      const peerList = peers.map((peer) => peer.addr.toString())\n\n      if (peerList.length !== bootstrapList.length) {\n        return false\n      }\n\n      return bootstrapList.every(addr => peerList.includes(addr))\n    }\n\n    await waitFor(test, { name: 'connect to all bootstrap nodes', timeout: 60 * 1000 })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-core/test/config.spec.js",
    "content": "\n/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { multiaddr } from '@multiformats/multiaddr'\nimport { isBrowser, isWebWorker } from 'ipfs-utils/src/env.js'\nimport createNode from './utils/create-node.js'\nimport createConfig from 'ipfs-core-config/config'\n\nconst { Bootstrap: bootstrapList } = createConfig()\n\ndescribe('config', function () {\n  this.timeout(10 * 1000)\n\n  /** @type {import('ipfs-core-types').IPFS} */\n  let ipfs\n  /** @type {() => Promise<void>} */\n  let cleanup\n\n  before(async () => {\n    const res = await createNode({\n      config: {\n        Bootstrap: bootstrapList\n      }\n    })\n    ipfs = res.ipfs\n    cleanup = res.cleanup\n  })\n\n  after(() => cleanup())\n\n  it('bootstrap list should contain dialable nodes', async () => {\n    const res = await ipfs.bootstrap.list()\n\n    expect(res.Peers).to.not.be.empty()\n\n    const onlyWssOrResolvableAddr = res.Peers.reduce((acc, curr) => {\n      if (!acc) {\n        return acc\n      }\n\n      const ma = multiaddr(curr)\n      return ma.protos().some(proto => proto.name === 'wss' || proto.resolvable)\n    }, true)\n\n    if (isBrowser || isWebWorker) {\n      expect(onlyWssOrResolvableAddr).to.be.true()\n    } else {\n      expect(onlyWssOrResolvableAddr).to.be.false()\n    }\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-core/test/create-node.spec.js",
    "content": "/* eslint max-nested-callbacks: [\"error\", 8] */\n/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport sinon from 'sinon'\nimport { isNode } from 'ipfs-utils/src/env.js'\nimport tmpDir from 'ipfs-utils/src/temp-dir.js'\nimport { peerIdFromKeys } from '@libp2p/peer-id'\nimport { unmarshalPrivateKey } from '@libp2p/crypto/keys'\nimport * as IPFS from '../src/index.js'\nimport defer from 'p-defer'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { createTempRepo } from './utils/create-repo.js'\nimport { createEd25519PeerId } from '@libp2p/peer-id-factory'\n\ndescribe('create node', function () {\n  /** @type {import('ipfs-repo').IPFSRepo} */\n  let tempRepo\n\n  beforeEach(async () => {\n    tempRepo = await createTempRepo()\n  })\n\n  it('should create a node with a custom repo path', async function () {\n    this.timeout(80 * 1000)\n\n    const node = await IPFS.create({\n      repo: tmpDir(),\n      config: {\n        Addresses: {\n          Swarm: []\n        }\n      },\n      preload: { enabled: false }\n    })\n\n    const config = await node.config.getAll()\n    expect(config.Identity).to.exist()\n\n    await node.stop()\n  })\n\n  it('should create a node with a custom repo', async function () {\n    this.timeout(80 * 1000)\n\n    const node = await IPFS.create({\n      repo: tempRepo,\n      config: {\n        Addresses: {\n          Swarm: []\n        }\n      },\n      preload: { enabled: false }\n    })\n\n    const config = await node.config.getAll()\n    expect(config.Identity).to.exist()\n    await node.stop()\n  })\n\n  it('should create and initialize with algorithm', async () => {\n    const ipfs = await IPFS.create({\n      init: { algorithm: 'Ed25519' },\n      start: false,\n      repo: tempRepo,\n      config: { Addresses: { Swarm: [] } }\n    })\n\n    const id = await ipfs.id()\n    const config = await ipfs.config.getAll()\n    const buf = uint8ArrayFromString(`${config.Identity?.PrivKey}`, 'base64pad')\n    const key = await unmarshalPrivateKey(buf)\n    const peerId = await peerIdFromKeys(key.public.bytes, key.bytes)\n\n    expect(peerId.type).to.equal('Ed25519')\n    expect(id.id.toString()).to.equal(peerId.toString())\n  })\n\n  it('should create and initialize but not start', async () => {\n    const ipfs = await IPFS.create({\n      start: false,\n      repo: tempRepo,\n      config: { Addresses: { Swarm: [] } }\n    })\n\n    expect(ipfs.isOnline()).to.be.false()\n  })\n\n  it('should create but not start', async () => {\n    const ipfs = await IPFS.create({\n      start: false,\n      repo: tempRepo,\n      config: { Addresses: { Swarm: [] } }\n    })\n\n    expect(ipfs.isOnline()).to.be.false()\n  })\n\n  it('should throw on boot error', () => {\n    return expect(IPFS.create({\n      repo: tempRepo,\n      init: {\n        algorithm: 'RSA',\n        bits: 1\n      }, // Too few bits will cause error on boot\n      config: { Addresses: { Swarm: [] } }\n    })).to.eventually.be.rejected()\n  })\n\n  it('should init RSA key with 1024 key bits', async function () {\n    this.timeout(80 * 1000)\n\n    const node = await IPFS.create({\n      repo: tempRepo,\n      init: {\n        algorithm: 'RSA',\n        bits: 1024\n      },\n      config: {\n        Addresses: {\n          Swarm: []\n        }\n      },\n      preload: { enabled: false }\n    })\n\n    const config = await node.config.getAll()\n    expect(config.Identity).to.exist()\n    expect(config.Identity?.PrivKey.length).is.below(1024)\n    await node.stop()\n  })\n\n  it('should be silent', async function () {\n    if (process.env.DEBUG) return this.skip()\n\n    this.timeout(30 * 1000)\n\n    const spy = sinon.spy(console, 'log')\n\n    const ipfs = await IPFS.create({\n      silent: true,\n      repo: tempRepo,\n      config: {\n        Addresses: {\n          Swarm: []\n        }\n      },\n      preload: { enabled: false }\n    })\n\n    // eslint-disable-next-line no-console\n    expect(spy.called).to.be.false()\n    // eslint-disable-next-line no-console\n    spy.restore()\n    await ipfs.stop()\n  })\n\n  it('should allow configuration of swarm and bootstrap addresses', async function () {\n    this.timeout(80 * 1000)\n    if (!isNode) return this.skip()\n\n    const node = await IPFS.create({\n      repo: tempRepo,\n      config: {\n        Addresses: {\n          Swarm: ['/ip4/127.0.0.1/tcp/9977']\n        },\n        Bootstrap: []\n      },\n      preload: { enabled: false }\n    })\n\n    const config = await node.config.getAll()\n    expect(config.Addresses?.Swarm).to.eql(['/ip4/127.0.0.1/tcp/9977'])\n    expect(config.Bootstrap).to.eql([])\n    await node.stop()\n  })\n\n  it('should allow pubsub to be disabled', async function () {\n    this.timeout(80 * 1000)\n    if (!isNode) return this.skip()\n\n    const node = await IPFS.create({\n      repo: tempRepo,\n      config: {\n        Addresses: {\n          Swarm: []\n        },\n        Bootstrap: [],\n        Pubsub: {\n          Enabled: false\n        }\n      }\n    })\n\n    await expect(node.pubsub.peers('topic'))\n      .to.eventually.be.rejected()\n      .with.property('code').that.equals('ERR_NOT_ENABLED')\n\n    await node.stop()\n  })\n\n  it('should start and stop, start and stop', async function () {\n    this.timeout(80 * 1000)\n\n    const node = await IPFS.create({\n      repo: tempRepo,\n      config: {\n        Addresses: {\n          Swarm: []\n        },\n        Bootstrap: []\n      },\n      preload: { enabled: false }\n    })\n\n    await node.stop()\n    await node.start()\n    await node.stop()\n  })\n\n  it('should not share identity with a simultaneously created node', async function () {\n    this.timeout(2 * 60 * 1000)\n\n    /**\n     * @param {import('ipfs-repo').IPFSRepo} repo\n     * @returns\n     */\n    function createNode (repo) {\n      return IPFS.create({\n        repo,\n        init: { emptyRepo: true },\n        config: {\n          Addresses: {\n            Swarm: []\n          },\n          Bootstrap: []\n        },\n        preload: { enabled: false },\n        start: false\n      })\n    }\n\n    const repoA = await createTempRepo()\n    const repoB = await createTempRepo()\n    const [nodeA, nodeB] = await Promise.all([createNode(repoA), createNode(repoB)])\n    const [idA, idB] = await Promise.all([nodeA.id(), nodeB.id()])\n\n    expect(idA.id).to.not.equal(idB.id)\n\n    await Promise.all([nodeA.stop(), nodeB.stop()])\n  })\n\n  it('should not error with empty IPLD config', async function () {\n    this.timeout(80 * 1000)\n\n    const node = await IPFS.create({\n      repo: tempRepo,\n      config: {\n        Addresses: {\n          Swarm: []\n        }\n      },\n      ipld: {},\n      preload: { enabled: false }\n    })\n\n    await node.stop()\n  })\n\n  it('should error when receiving websocket-star swarm addresses', async () => {\n    const node = await IPFS.create({\n      repo: tempRepo,\n      start: false,\n      config: {\n        Addresses: {\n          Swarm: ['/ip4/127.0.0.1/tcp/13579/wss/p2p-websocket-star']\n        },\n        Bootstrap: []\n      },\n      preload: { enabled: false }\n    })\n\n    await expect(node.start()).to.eventually.be.rejected().with.property('code', 'ERR_WEBSOCKET_STAR_SWARM_ADDR_NOT_SUPPORTED')\n  })\n\n  it('should auto-migrate repos by default', async function () {\n    this.timeout(80 * 1000)\n\n    const deferred = defer()\n    const id = await createEd25519PeerId()\n\n    if (id.privateKey == null) {\n      throw new Error('No private key found')\n    }\n\n    // create an old-looking repo\n    const repo = await createTempRepo({\n      version: 1,\n      spec: 1,\n      config: {\n        Identity: {\n          PeerID: id.toString(),\n          PrivKey: uint8ArrayToString(id.privateKey, 'base64pad')\n        }\n      },\n      autoMigrate: true,\n      onMigrationProgress: () => {\n        // migrations are happening\n        deferred.resolve()\n      }\n    })\n\n    const node = await IPFS.create({\n      repo\n    })\n\n    await deferred.promise\n\n    await node.stop()\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-core/test/init.spec.js",
    "content": "/* eslint max-nested-callbacks: [\"error\", 8] */\n/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { isNode } from 'ipfs-utils/src/env.js'\nimport { CID } from 'multiformats/cid'\nimport { peerIdFromKeys } from '@libp2p/peer-id'\nimport createNode from './utils/create-node.js'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays'\nimport { unmarshalPrivateKey } from '@libp2p/crypto/keys'\n\nconst privateKey = 'CAASqAkwggSkAgEAAoIBAQChVmiObYo6pkKrMSd3OzW1cTL+RDmX1rkETYGKWV9TPXMNgElFTYoYHqT9QZomj5RI8iUmHccjzqr4J0mV+E0NpvHHOLlmDZ82lAw2Zx7saUkeQWvC0S9Z0o3aTx2sSubZV53rSomkZgQH4fYTs4RERejV4ltzLFdzQQBwWrBvlagpPHUCxKDUCnE5oIzdbD26ltWViPBWr7TfotzC8Lyi/tceqCpHMUJGMbsVgypnlgpey07MBvs71dVh5LcRen/ztsQO6Yju4D3QgWoyD0SIUdJFvBzEwL9bSiA3QjUc/fkGd7EcdN5bebYOqAi4ZIiAMLp3i4+B8Tzq/acull43AgMBAAECggEBAIDgZE75o4SsEO9tKWht7L5OeXxxBUyMImkUfJkGQUZd/MzZIC5y/Q+9UvBW+gs5gCsw+onTGaM50Iq/32Ej4nE4XURVxIuH8BmJ86N1hlc010qK2cjajqeCsPulXT+m6XbOLYCpnv+q2idt0cL1EH/1FEPeOEztK8ION4qIdw36SoykfTx/RqtkKHtS01AwN82EOPbWk7huyQT5R5MsCZmRJXBFkpNtiL+8619BH2aVlghHO4NouF9wQjdz/ysVuyYg+3rX2cpGjuHDTZ6hVQiJD1lF6D+dua7UPyHYAG2iRQiKZmCjitt9ywzPxiRaYF/aZ02FEMWckZulR09axskCgYEAzjl6ER8WwxYHn4tHse+CrIIF2z5cscdrh7KSwd3Rse9hIIBDJ/0KkvoYd1IcWrS8ywLrRfSLIjEU9u7IN1m+IRVWJ61fXNqOHm9clAu6qNhCN6W2+JfxDkUygTwmsq0v3huO+qkiMQz+a4nAXJe8Utd36ywgPhVGxFa/7x1v1N0CgYEAyEdiYRFf1aQZcO7+B2FH+tkGJsB30VIBhcpG9EukuQUUulLHhScc/KRj+EFAACLdkTqlVI0xVYIWaaCXwoQCWKixjZ5mYPC+bBLgn4IoDS6XTdHtR7Vn3UUvGTKsM0/z4e8/0eSzGNCHoYez9IoBlPNic0sQuST4jzgS2RYnFCMCgYASWSzSLyjwTJp7CIJlg4Dl5l+tBRxsOOkJVssV8q2AnmLO6HqRKUNylkvs+eJJ88DEc0sJm1txvFo4KkCoJBT1jpduyk8szMlOTew3w99kvHEP0G+6KJKrCV8X/okW5q/WnC8ZgEjpglV0rfnugxWfbUpfIzrvKydzuqAzHzRfBQKBgQDANtKSeoxRjEbmfljLWHAure8bbgkQmfXgI7xpZdfXwqqcECpw/pLxXgycDHOSLeQcJ/7Y4RGCEXHVOk2sX+mokW6mjmmPjD4VlyCBtfcef6KzC1EBS3c9g9KqCln+fTOBmY7UsPu6SxiAzK7HeVP/Un8gS+Dm8DalrZlZQ8uJpQKBgF6mL/Xo/XUOiz2jAD18l8Y6s49bA9H2CoLpBGTV1LfY5yTFxRy4R3qnX/IzsKy567sbtkEFKJxplc/RzCQfrgbdj7k26SbKtHR3yERaFGRYq8UeAHeYC1/N19LF5BMQL4y5R4PJ1SFPeJCL/wXiMqs1maTqvKqtc4bbegNdwlxn'\nconst edPrivateKey = 'CAESYFeZamw+9QdwHgSmcvPmfLUpmWTtYpUeycbXcfnkTnDI7OaPmE6V8i+Lw7FNB5CtYuDFKUsOS5h+AogyF/Dft4Ds5o+YTpXyL4vDsU0HkK1i4MUpSw5LmH4CiDIX8N+3gA=='\nconst secpPrivateKey = 'CAISIKCfwZsMEwmzLxGv9duM6j6YQzMx2V46+Yl3laV24Qus'\n\ndescribe('init', function () {\n  if (!isNode) return\n\n  /** @type {import('ipfs-core-types').IPFS} */\n  let ipfs\n  /** @type {import('ipfs-repo').IPFSRepo} */\n  let repo\n  /** @type {() => Promise<void>} */\n  let cleanup\n\n  /**\n   * @param {import('../src/types').Options} [options]\n   */\n  const init = async (options) => {\n    const res = await createNode({\n      ...options,\n      start: false\n    })\n\n    ipfs = res.ipfs\n    repo = res.repo\n    cleanup = res.cleanup\n\n    return ipfs\n  }\n  afterEach(() => cleanup())\n\n  it('should init successfully', async () => {\n    await init()\n\n    const res = await repo.exists()\n    expect(res).to.equal(true)\n\n    const config = await repo.config.getAll()\n\n    expect(config).to.have.property('Identity')\n    expect(config).to.have.nested.property('Keychain.DEK')\n  })\n\n  it('should init successfully with a keychain pass', async () => {\n    await init({\n      pass: 'super-super-secure-1234',\n      init: {\n        algorithm: 'RSA',\n        bits: 512\n      }\n    })\n\n    const res = await repo.exists()\n    expect(res).to.equal(true)\n\n    const config = await repo.config.getAll()\n    expect(config.Keychain).to.exist()\n\n    const { ipfs: ipfs2, repo: repo2 } = await createNode({\n      repo: repo,\n      pass: 'something-else-that-is-long-enough',\n      start: false,\n      init: {\n        algorithm: 'RSA',\n        bits: 512\n      }\n    })\n\n    // same repo, same peer id\n    expect(repo.path).to.equal(repo2.path)\n    expect(await ipfs2.id()).to.deep.equal(await ipfs.id())\n\n    // opened with correct password\n    await expect(ipfs.key.export('self', 'some-other-password')).to.eventually.be.ok()\n\n    // opened with incorrect password\n    await expect(ipfs2.key.export('self', 'some-other-password')).to.eventually.be.rejected()\n  })\n\n  it('should init with a key algorithm (RSA)', async () => {\n    await init({ init: { algorithm: 'RSA' } })\n\n    const config = await repo.config.getAll()\n\n    const buf = uint8ArrayFromString(`${config.Identity?.PrivKey}`, 'base64pad')\n    const key = await unmarshalPrivateKey(buf)\n    const peerId = await peerIdFromKeys(key.public.bytes, key.bytes)\n    expect(peerId.type).equals('RSA')\n  })\n\n  it('should init with a key algorithm (Ed25519)', async () => {\n    await init({ init: { algorithm: 'Ed25519' } })\n\n    const config = await repo.config.getAll()\n    const buf = uint8ArrayFromString(`${config.Identity?.PrivKey}`, 'base64pad')\n    const key = await unmarshalPrivateKey(buf)\n    const peerId = await peerIdFromKeys(key.public.bytes, key.bytes)\n    expect(peerId.type).equals('Ed25519')\n  })\n\n  // not supported any more\n  it.skip('should init with a key algorithm (secp256k1)', async () => {\n    await init({ init: { algorithm: 'secp256k1' } })\n\n    const config = await repo.config.getAll()\n    const buf = uint8ArrayFromString(`${config.Identity?.PrivKey}`, 'base64pad')\n    const key = await unmarshalPrivateKey(buf)\n    const peerId = await peerIdFromKeys(key.public.bytes, key.bytes)\n    expect(peerId.type).equals('secp256k1')\n  })\n\n  it('should set # of bits in key', async function () {\n    this.timeout(120 * 1000)\n\n    await init({\n      init: {\n        algorithm: 'RSA',\n        bits: 1024\n      }\n    })\n\n    const config = await repo.config.getAll()\n    expect(config.Identity?.PrivKey.length).is.above(256)\n  })\n\n  it('should allow a pregenerated RSA key to be used', async () => {\n    await init({ init: { privateKey } })\n\n    const config = await repo.config.getAll()\n    expect(config.Identity?.PeerID).is.equal('QmRsooYQasV5f5r834NSpdUtmejdQcpxXkK6qsozZWEihC')\n  })\n\n  it('should allow a pregenerated Ed25519 key to be used', async () => {\n    await init({ init: { privateKey: edPrivateKey } })\n\n    const config = await repo.config.getAll()\n    expect(config.Identity?.PeerID).is.equal('12D3KooWRm8J3iL796zPFi2EtGGtUJn58AG67gcqzMFHZnnsTzqD')\n  })\n\n  it('should allow a pregenerated secp256k1 key to be used', async () => {\n    await init({ init: { privateKey: secpPrivateKey } })\n\n    const config = await repo.config.getAll()\n    expect(config.Identity?.PeerID).is.equal('16Uiu2HAm5qw8UyXP2RLxQUx5KvtSN8DsTKz8quRGqGNC3SYiaB8E')\n  })\n\n  it('should write init docs', async () => {\n    await init()\n    const multihash = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n    const node = await ipfs.object.get(multihash, { enc: 'base58' })\n    expect(node.Links).to.exist()\n  })\n\n  it('should allow init with an empty repo', async () => {\n    await init({ init: { emptyRepo: true } })\n\n    // Should not have default assets\n    const multihash = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n    await expect(ipfs.object.get(multihash, {})).to.eventually.be.rejected().with.property('code', 'ERR_NOT_FOUND')\n  })\n\n  it('should apply one profile', async () => {\n    await init({ init: { profiles: ['test'] } })\n\n    const config = await repo.config.getAll()\n    expect(config.Bootstrap).to.be.empty()\n  })\n\n  it('should apply multiple profiles', async () => {\n    await init({ init: { profiles: ['test', 'local-discovery'] } })\n\n    const config = await repo.config.getAll()\n    expect(config.Bootstrap).to.be.empty()\n    expect(config.Discovery?.MDNS?.Enabled).to.be.true()\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-core/test/ipld.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport createNode from './utils/create-node.js'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport * as Digest from 'multiformats/hashes/digest'\n\ndescribe('ipld', function () {\n  this.timeout(10 * 1000)\n\n  /** @type {import('ipfs-core-types').IPFS} */\n  let ipfs\n  /** @type {() => Promise<void>} */\n  let cleanup\n\n  before(async () => {\n    /** @type {import('multiformats/codecs/interface').BlockCodec<1337, string>} */\n    const customCodec = {\n      name: 'custom-codec',\n      code: 1337,\n      encode: (str) => uint8ArrayFromString(str),\n      decode: (buf) => uint8ArrayToString(buf)\n    }\n\n    /** @type {import('multiformats/hashes/interface').MultihashHasher} */\n    const customHasher = {\n      digest: (input) => Promise.resolve(Digest.create(1338, input)),\n      name: 'custom-hasher',\n      code: 1338\n    }\n\n    /** @type {import('multiformats/bases/interface').MultibaseCodec<any>} */\n    const customBase = {\n      name: 'custom-base',\n      prefix: '1339',\n      encoder: {\n        name: 'custom-base',\n        prefix: '1339',\n        encode: (input) => uint8ArrayToString(input)\n      },\n      decoder: {\n        decode: (input) => uint8ArrayFromString(input)\n      }\n    }\n\n    const res = await createNode({\n      ipld: {\n        codecs: [\n          customCodec\n        ],\n        hashers: [\n          customHasher\n        ],\n        bases: [\n          customBase\n        ]\n      }\n    })\n    ipfs = res.ipfs\n    cleanup = res.cleanup\n  })\n\n  after(() => cleanup())\n\n  it('should allow codecs to be specified without overwriting others', async () => {\n    const dagCborNode = {\n      hello: 'world'\n    }\n    const cid1 = await ipfs.dag.put(dagCborNode, {\n      storeCodec: 'dag-cbor',\n      hashAlg: 'sha2-256'\n    })\n\n    const dagPbNode = {\n      Data: new Uint8Array(0),\n      Links: []\n    }\n    const cid2 = await ipfs.dag.put(dagPbNode, {\n      storeCodec: 'dag-pb',\n      hashAlg: 'sha2-256'\n    })\n\n    const customNode = 'totally custom'\n    const cid3 = await ipfs.dag.put(customNode, {\n      storeCodec: 'custom-codec',\n      hashAlg: 'sha2-256'\n    })\n\n    await expect(ipfs.dag.get(cid1)).to.eventually.have.property('value').that.deep.equals(dagCborNode)\n    await expect(ipfs.dag.get(cid2)).to.eventually.have.property('value').that.deep.equals(dagPbNode)\n    await expect(ipfs.dag.get(cid3)).to.eventually.have.property('value').that.deep.equals(customNode)\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-core/test/key-exchange.spec.js",
    "content": "/* eslint max-nested-callbacks: [\"error\", 8] */\n/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { nanoid } from 'nanoid'\nimport createNode from './utils/create-node.js'\n\ndescribe('key exchange', function () {\n  this.timeout(20 * 1000)\n\n  /** @type {string} */\n  let selfPem\n  const passwordPem = nanoid()\n\n  /** @type {import('ipfs-core-types').IPFS} */\n  let ipfs\n  /** @type {() => Promise<void>} */\n  let cleanup\n\n  before(async () => {\n    const res = await createNode()\n    ipfs = res.ipfs\n    cleanup = res.cleanup\n  })\n\n  after(() => cleanup())\n\n  it('should export key', async () => {\n    const pem = await ipfs.key.export('self', passwordPem)\n    expect(pem).to.exist()\n    selfPem = pem\n  })\n\n  it('should import key', async () => {\n    const key = await ipfs.key.import('clone', selfPem, passwordPem)\n    expect(key).to.exist()\n    expect(key).to.have.property('name', 'clone')\n    expect(key).to.have.property('id')\n  })\n\n  it('should create ed25519 keys', async () => {\n    const name = 'my-ed-key'\n    const pass = 'password for my ed key'\n    const key = await ipfs.key.gen(name, { type: 'ed25519' })\n    // export it\n    const exportedKey = await ipfs.key.export(name, pass)\n    // delete it\n    await ipfs.key.rm(name)\n    // import it back to the same name\n    const imported = await ipfs.key.import(name, exportedKey, pass)\n    expect(imported.id).to.equal(key.id)\n  })\n\n  it('should create secp256k1 keys', async () => {\n    const name = 'my-secp-key'\n    const pass = 'password for my secp key'\n    const key = await ipfs.key.gen(name, { type: 'secp256k1' })\n    // export it\n    const exportedKey = await ipfs.key.export(name, pass)\n    // delete it\n    await ipfs.key.rm(name)\n    // import it back to the same name\n    const imported = await ipfs.key.import(name, exportedKey, pass)\n    expect(imported.id).to.equal(key.id)\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-core/test/libp2p.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { MemoryDatastore } from 'datastore-core/memory'\nimport { createLibp2p as libp2pComponent } from '../src/components/libp2p.js'\nimport { GossipSub } from '@chainsafe/libp2p-gossipsub'\nimport { createEd25519PeerId } from '@libp2p/peer-id-factory'\n\n/**\n * @type {import('@libp2p/interface-transport').Transport}\n */\n\ndescribe('libp2p customization', function () {\n  // Provide some extra time for ci since we're starting libp2p nodes in each test\n  this.timeout(25 * 1000)\n\n  /**\n   * @type {import('interface-datastore').Datastore}\n   */\n  let datastore\n  /**\n   * @type {import('@libp2p/interface-peer-id').PeerId}\n   */\n  let peerId\n  /**\n   * @type {import('ipfs-core-types/src/config').Config}\n   */\n  let testConfig\n  /**\n   * @type {import('libp2p').Libp2p | null}\n   */\n  let libp2p\n\n  before(async function () {\n    this.timeout(25 * 1000)\n\n    testConfig = {\n      Addresses: {\n        Swarm: ['/ip4/0.0.0.0/tcp/4002'],\n        API: '/ip4/127.0.0.1/tcp/5002',\n        Gateway: '/ip4/127.0.0.1/tcp/9090'\n      },\n      Discovery: {\n        MDNS: {\n          Enabled: false\n        },\n        webRTCStar: {\n          Enabled: false\n        }\n      }\n    }\n    datastore = new MemoryDatastore()\n    peerId = await createEd25519PeerId()\n  })\n\n  afterEach(async () => {\n    if (libp2p) {\n      await libp2p.stop()\n      libp2p = null\n    }\n  })\n\n  describe('config', () => {\n    it('should be able to specify Announce addresses', async () => {\n      const annAddr = '/dns4/test.ipfs.io/tcp/443/wss'\n\n      libp2p = await libp2pComponent({\n        peerId,\n        // @ts-expect-error repo is not complete implementation\n        repo: { datastore },\n        print: console.log, // eslint-disable-line no-console\n        config: {\n          ...testConfig,\n          Addresses: {\n            ...testConfig.Addresses,\n            Announce: [annAddr]\n          }\n        }\n      })\n\n      await libp2p.start()\n\n      expect(libp2p.getMultiaddrs().map(m => m.toString())).to.include(`${annAddr}/p2p/${peerId}`)\n    })\n\n    it('should select gossipsub as pubsub router', async () => {\n      libp2p = await libp2pComponent({\n        peerId,\n        // @ts-expect-error repo is not complete implementation\n        repo: { datastore },\n        print: console.log, // eslint-disable-line no-console\n        config: {\n          ...testConfig,\n          Pubsub: { PubSubRouter: 'gossipsub' }\n        }\n      })\n\n      await libp2p.start()\n\n      expect(libp2p.pubsub).to.be.an.instanceOf(GossipSub)\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-core/test/mfs-preload.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport delay from 'delay'\nimport { sha256 } from 'multiformats/hashes/sha2'\nimport { nanoid } from 'nanoid'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { CID } from 'multiformats/cid'\nimport { waitFor } from './utils/wait-for.js'\nimport { createMfsPreloader } from '../src/mfs-preload.js'\n\nconst fakeCid = async () => {\n  const mh = await sha256.digest(uint8ArrayFromString(nanoid()))\n  return CID.createV0(mh)\n}\n\n/**\n * @param {CID[]} cids\n */\nconst createMockFilesStat = (cids = []) => {\n  let n = 0\n  return () => {\n    return Promise.resolve({ cid: cids[n++] || 'QmHash' })\n  }\n}\n\nconst createMockPreload = () => {\n  /** @type {import('../src/types').Preload & { cids: CID[] }} */\n  const preload = cid => preload.cids.push(cid)\n  preload.start = () => {}\n  preload.stop = () => {}\n  preload.cids = []\n\n  return preload\n}\n\ndescribe('MFS preload', () => {\n  // CIDs returned from our mock files.stat function\n  /** @type {{ initial: CID, same: CID, updated: CID }} */\n  let testCids\n  /** @type {ReturnType<createMockPreload>} */\n  let mockPreload\n  /** @type {import('ipfs-core-types/src/files').API} */\n  let mockFiles\n\n  beforeEach(async () => {\n    mockPreload = createMockPreload()\n\n    testCids = {\n      initial: await fakeCid(),\n      same: await fakeCid(),\n      updated: await fakeCid()\n    }\n\n    // @ts-expect-error not whole file api\n    mockFiles = { stat: createMockFilesStat([testCids.initial, testCids.same, testCids.same, testCids.updated]) }\n  })\n\n  it('should preload MFS root periodically', async function () {\n    this.timeout(80 * 1000)\n\n    // The CIDs we expect to have been preloaded\n    const expectedPreloadCids = [testCids.same, testCids.updated]\n    const preloader = createMfsPreloader({ preload: mockPreload, files: mockFiles, options: { enabled: true, interval: 10 } })\n\n    await preloader.start()\n\n    const test = () => {\n      // Slice off any extra CIDs it processed\n      const cids = mockPreload.cids.slice(0, expectedPreloadCids.length)\n\n      if (cids.length !== expectedPreloadCids.length) {\n        return false\n      }\n\n      return cids.every((cid, i) => cid.toString() === expectedPreloadCids[i].toString())\n    }\n\n    await waitFor(test, { name: 'CIDs to be preloaded' })\n    await preloader.stop()\n  })\n\n  it('should disable preloading MFS', async () => {\n    const preloader = createMfsPreloader({ preload: mockPreload, files: mockFiles, options: { enabled: false, interval: 10 } })\n    await preloader.start()\n    await delay(500)\n    expect(mockPreload.cids).to.be.empty()\n    await preloader.stop()\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-core/test/name.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport sinon from 'sinon'\nimport delay from 'delay'\nimport { createEd25519PeerId } from '@libp2p/peer-id-factory'\nimport errCode from 'err-code'\nimport * as ipns from 'ipns'\nimport { createRouting } from '../src/ipns/routing/config.js'\nimport { IpnsPublisher } from '../src/ipns/publisher.js'\nimport { IpnsRepublisher } from '../src/ipns/republisher.js'\nimport { IpnsResolver } from '../src/ipns/resolver.js'\nimport { OfflineDatastore } from '../src/ipns/routing/offline-datastore.js'\nimport { IpnsPubsubDatastore } from '../src/ipns/routing/pubsub-datastore.js'\nimport { DHTDatastore } from '../src/ipns/routing/dht-datastore.js'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\n\nconst ipfsRef = '/ipfs/QmPFVLPmp9zv5Z5KUqLhe2EivAGccQW2r7M7jhVJGLZoZU'\n\ndescribe('name', function () {\n  describe('republisher', function () {\n    this.timeout(120 * 1000)\n    /** @type {IpnsRepublisher} */\n    let republisher\n\n    afterEach(async () => {\n      if (republisher) {\n        await republisher.stop()\n      }\n    })\n\n    it('should republish entries', async function () {\n      // @ts-expect-error sinon.stub() is not complete publisher implementation\n      republisher = new IpnsRepublisher(sinon.stub(), sinon.stub(), sinon.stub(), sinon.stub(), {\n        initialBroadcastInterval: 200,\n        broadcastInterval: 500\n      })\n      const stub = republisher._republishEntries = sinon.stub()\n\n      await republisher.start()\n\n      expect(stub.calledOnce).to.equal(false)\n\n      // Initial republish should happen after ~200ms\n      await delay(300)\n      expect(stub.calledOnce).to.equal(true)\n\n      // Subsequent republishes should happen after ~700\n      await delay(600)\n      expect(stub.calledTwice).to.equal(true)\n    })\n\n    it('should not republish self key twice', async function () {\n      const mockKeychain = {\n        listKeys: () => Promise.resolve([{ name: 'self' }])\n      }\n      // @ts-expect-error sinon.stub() is not complete publisher implementation\n      republisher = new IpnsRepublisher(sinon.stub(), sinon.stub(), sinon.stub(), mockKeychain, {\n        initialBroadcastInterval: 100,\n        broadcastInterval: 1000,\n        pass: 'pass'\n      })\n      const stub = republisher._republishEntry = sinon.stub()\n\n      await republisher.start()\n\n      expect(stub.calledOnce).to.equal(false)\n\n      // Initial republish should happen after ~100ms\n      await delay(200)\n      expect(stub.calledOnce).to.equal(true)\n    })\n\n    it('should error if run republish again', async () => {\n      // @ts-expect-error sinon.stub() is not complete publisher implementation\n      republisher = new IpnsRepublisher(sinon.stub(), sinon.stub(), sinon.stub(), sinon.stub(), {\n        initialBroadcastInterval: 50,\n        broadcastInterval: 100\n      })\n      republisher._republishEntries = sinon.stub()\n\n      await republisher.start()\n\n      await expect(republisher.start())\n        .to.eventually.be.rejected()\n        .with.property('code').that.equals('ERR_REPUBLISH_ALREADY_RUNNING')\n    })\n  })\n\n  describe('publisher', () => {\n    it('should fail to publish if does not receive private key', () => {\n      // @ts-expect-error constructor needs args\n      const publisher = new IpnsPublisher()\n      // @ts-expect-error invalid argument\n      return expect(publisher.publish(null, ipfsRef))\n        .to.eventually.be.rejected()\n        .with.property('code', 'ERR_INVALID_PEER_ID')\n    })\n\n    it('should fail to publish if an invalid private key is received', () => {\n      // @ts-expect-error constructor needs args\n      const publisher = new IpnsPublisher()\n      // @ts-expect-error invalid argument\n      return expect(publisher.publish({ bytes: 'not that valid' }, ipfsRef))\n        .to.eventually.be.rejected()\n        // .that.eventually.has.property('code', 'ERR_INVALID_PEER_ID') TODO: libp2p-crypto needs to throw err-code\n    })\n\n    it('should fail to publish if _updateOrCreateRecord fails', async () => {\n      // @ts-expect-error constructor needs args\n      const publisher = new IpnsPublisher()\n      const err = new Error('error')\n      const peerId = await createEd25519PeerId()\n\n      sinon.stub(publisher, '_updateOrCreateRecord').rejects(err)\n\n      // @ts-expect-error invalid argument\n      return expect(publisher.publish(peerId, ipfsRef))\n        .to.eventually.be.rejectedWith(err)\n    })\n\n    it('should fail to publish if _putRecordToRouting receives an invalid peer id', () => {\n      // @ts-expect-error constructor needs args\n      const publisher = new IpnsPublisher()\n      // @ts-expect-error invalid argument\n      return expect(publisher._putRecordToRouting(undefined, undefined))\n        .to.eventually.be.rejected()\n        .with.property('code', 'ERR_INVALID_PEER_ID')\n    })\n\n    it('should fail to publish if we receive a unexpected error getting from datastore', async () => {\n      const routing = {}\n      const datastore = {\n        get: sinon.stub().rejects(new Error('boom'))\n      }\n      // @ts-expect-error routing is not complete implementation\n      const publisher = new IpnsPublisher(routing, datastore)\n      const peerId = await createEd25519PeerId()\n\n      // @ts-expect-error invalid argument\n      await expect(publisher.publish(peerId, ipfsRef))\n        .to.eventually.be.rejected()\n        .with.property('code', 'ERR_DETERMINING_PUBLISHED_RECORD')\n    })\n\n    it('should fail to publish if we receive a unexpected error putting to datastore', async () => {\n      const routing = {\n        get: sinon.stub().rejects(errCode(new Error('not found'), 'ERR_NOT_FOUND'))\n      }\n      const datastore = {\n        get: sinon.stub().rejects(errCode(new Error('not found'), 'ERR_NOT_FOUND')),\n        put: sinon.stub().rejects(new Error('error-unexpected'))\n      }\n      // @ts-expect-error routing is not complete implementation\n      const publisher = new IpnsPublisher(routing, datastore)\n      const peerId = await createEd25519PeerId()\n\n      // @ts-expect-error invalid argument\n      await expect(publisher.publish(peerId, ipfsRef))\n        .to.eventually.be.rejected()\n        .with.property('code', 'ERR_STORING_IN_DATASTORE')\n    })\n  })\n\n  describe('resolver', () => {\n    it('should resolve an inlined public key', async () => {\n      const peerId = await createEd25519PeerId()\n      const value = `/ipfs/${peerId.toString()}`\n\n      if (peerId.privateKey == null) {\n        throw new Error('Private key is missing')\n      }\n\n      const record = await ipns.create(peerId, uint8ArrayFromString(value), 1, 10e3)\n\n      const routing = {\n        get: sinon.stub().returns(ipns.marshal(record))\n      }\n      // @ts-expect-error routing is not complete implementation\n      const resolver = new IpnsResolver(routing)\n\n      const resolved = await resolver.resolve(`/ipns/${peerId.toString()}`)\n      expect(resolved).to.equal(value)\n    })\n\n    it('should fail to resolve if the received name is not a string', () => {\n      // @ts-expect-error constructor needs args\n      const resolver = new IpnsResolver()\n      // @ts-expect-error invalid argument\n      return expect(resolver.resolve(false))\n        .to.eventually.be.rejected()\n        .with.property('code', 'ERR_INVALID_NAME')\n    })\n\n    it('should fail to resolve if receives an invalid ipns path', () => {\n      // @ts-expect-error constructor needs args\n      const resolver = new IpnsResolver()\n      return expect(resolver.resolve('ipns/<cid>'))\n        .to.eventually.be.rejected()\n        .with.property('code', 'ERR_INVALID_NAME')\n    })\n\n    it('should fail to resolve if receive error getting from datastore', async () => {\n      const routing = {\n        get: sinon.stub().rejects(new Error('boom'))\n      }\n      // @ts-expect-error routing is not complete implementation\n      const resolver = new IpnsResolver(routing)\n      const peerId = await createEd25519PeerId()\n\n      await expect(resolver.resolve(`/ipns/${peerId.toString()}`))\n        .to.eventually.be.rejected()\n        .with.property('code', 'ERR_UNEXPECTED_ERROR_GETTING_RECORD')\n    })\n\n    it('should fail to resolve if does not find the record', async () => {\n      const routing = {\n        get: sinon.stub().rejects(errCode(new Error('not found'), 'ERR_NOT_FOUND'))\n      }\n      // @ts-expect-error routing is not complete implementation\n      const resolver = new IpnsResolver(routing)\n      const peerId = await createEd25519PeerId()\n\n      await expect(resolver.resolve(`/ipns/${peerId.toString()}`))\n        .to.eventually.be.rejected()\n        .with.property('code', 'ERR_NO_RECORD_FOUND')\n    })\n\n    it('should fail to resolve if does not receive a buffer', async () => {\n      const routing = {\n        get: sinon.stub().resolves('not-a-buffer')\n      }\n      // @ts-expect-error routing is not complete implementation\n      const resolver = new IpnsResolver(routing)\n      const peerId = await createEd25519PeerId()\n\n      await expect(resolver.resolve(`/ipns/${peerId.toString()}`))\n        .to.eventually.be.rejected()\n    })\n  })\n\n  describe('routing config', function () {\n    it('should use only the offline datastore by default', () => {\n      const config = createRouting({\n        // @ts-expect-error sinon.stub() is not complete implementation\n        libp2p: sinon.stub(),\n        // @ts-expect-error sinon.stub() is not complete implementation\n        repo: sinon.stub(),\n        // @ts-expect-error sinon.stub() is not complete implementation\n        peerId: sinon.stub(),\n        options: {}\n      })\n\n      expect(config.stores).to.have.lengthOf(1)\n      expect(config.stores[0]).is.instanceOf(OfflineDatastore)\n    })\n\n    it('should use only the offline datastore if offline', () => {\n      const config = createRouting({\n        // @ts-expect-error sinon.stub() is not complete implementation\n        libp2p: sinon.stub(),\n        // @ts-expect-error sinon.stub() is not complete implementation\n        repo: sinon.stub(),\n        // @ts-expect-error sinon.stub() is not complete implementation\n        peerId: sinon.stub(),\n        options: {\n          offline: true\n        }\n      })\n\n      expect(config.stores).to.have.lengthOf(1)\n      expect(config.stores[0]).is.instanceOf(OfflineDatastore)\n    })\n\n    it('should use the pubsub datastore if enabled', async () => {\n      const peerId = await createEd25519PeerId()\n\n      const config = createRouting({\n        libp2p: {\n          // @ts-expect-error sinon.stub() is not complete implementation\n          pubsub: {\n            addEventListener: sinon.stub()\n          }\n        },\n        // @ts-expect-error sinon.stub() is not complete implementation\n        repo: { datastore: sinon.stub() },\n        peerId,\n        options: {\n          EXPERIMENTAL: {\n            ipnsPubsub: true\n          }\n        }\n      })\n\n      expect(config.stores).to.have.lengthOf(1)\n      expect(config.stores[0]).is.instanceOf(IpnsPubsubDatastore)\n    })\n\n    it('should use the dht if enabled', () => {\n      const dht = sinon.stub()\n\n      const config = createRouting({\n        // @ts-expect-error sinon.stub() is not complete implementation\n        libp2p: { _dht: dht, _config: { dht: { enabled: true } } },\n        // @ts-expect-error sinon.stub() is not complete implementation\n        repo: sinon.stub(),\n        // @ts-expect-error sinon.stub() is not complete implementation\n        peerId: sinon.stub(),\n        options: {\n          config: {\n            Routing: {\n              Type: 'dhtclient'\n            }\n          }\n        }\n      })\n\n      expect(config.stores).to.have.lengthOf(1)\n      expect(config.stores[0]).to.be.an.instanceOf(DHTDatastore)\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-core/test/preload.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { nanoid } from 'nanoid'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { expect } from 'aegir/chai'\nimport all from 'it-all'\nimport { waitForCids, defaultAddr, clearPreloadCids } from './utils/mock-preload-node-utils.js'\nimport createNode from './utils/create-node.js'\nimport * as dagPB from '@ipld/dag-pb'\n\ndescribe('preload', () => {\n  /** @type {import('ipfs-core-types').IPFS} */\n  let ipfs\n  /** @type {() => Promise<void>} */\n  let cleanup\n\n  before(async () => {\n    const res = await createNode({\n      preload: {\n        enabled: true,\n        addresses: [defaultAddr]\n      }\n    })\n    ipfs = res.ipfs\n    cleanup = res.cleanup\n  })\n\n  after(() => cleanup())\n  afterEach(() => clearPreloadCids())\n\n  it('should not preload content multiple times', async function () {\n    this.timeout(50 * 1000)\n    const { cid } = await ipfs.add(uint8ArrayFromString(nanoid()), { preload: false })\n\n    await all(ipfs.cat(cid))\n    await waitForCids(cid)\n\n    // should not preload the second time\n    await clearPreloadCids()\n    await all(ipfs.cat(cid))\n    await expect(waitForCids(cid)).to.eventually.be.rejectedWith('Timed out waiting for CIDs to be preloaded')\n  })\n\n  it('should preload content added with add', async function () {\n    this.timeout(50 * 1000)\n    const res = await ipfs.add(uint8ArrayFromString(nanoid()))\n    await waitForCids(res.cid)\n  })\n\n  it('should preload multiple content added with add', async function () {\n    this.timeout(50 * 1000)\n\n    const res = await all(ipfs.addAll([{\n      content: uint8ArrayFromString(nanoid())\n    }, {\n      content: uint8ArrayFromString(nanoid())\n    }, {\n      content: uint8ArrayFromString(nanoid())\n    }]))\n\n    await waitForCids(res.map(file => file.cid))\n  })\n\n  it('should preload multiple content and intermediate dirs added with add', async function () {\n    this.timeout(50 * 1000)\n\n    const res = await all(ipfs.addAll([{\n      path: 'dir0/dir1/file0',\n      content: uint8ArrayFromString(nanoid())\n    }, {\n      path: 'dir0/dir1/file1',\n      content: uint8ArrayFromString(nanoid())\n    }, {\n      path: 'dir0/file2',\n      content: uint8ArrayFromString(nanoid())\n    }]))\n\n    const rootDir = res.find(file => file.path === 'dir0')\n    expect(rootDir).to.exist()\n\n    if (!rootDir) {\n      throw new Error('rootDir did not exist')\n    }\n\n    await waitForCids(rootDir.cid)\n  })\n\n  it('should preload multiple content and wrapping dir for content added with add and wrapWithDirectory option', async function () {\n    this.timeout(50 * 1000)\n\n    const res = await all(ipfs.addAll([{\n      path: 'dir0/dir1/file0',\n      content: uint8ArrayFromString(nanoid())\n    }, {\n      path: 'dir0/dir1/file1',\n      content: uint8ArrayFromString(nanoid())\n    }, {\n      path: 'dir0/file2',\n      content: uint8ArrayFromString(nanoid())\n    }], { wrapWithDirectory: true }))\n\n    const wrappingDir = res.find(file => file.path === '')\n    expect(wrappingDir).to.exist()\n\n    if (!wrappingDir) {\n      throw new Error('wrappingDir did not exist')\n    }\n\n    await waitForCids(wrappingDir.cid)\n  })\n\n  it('should preload content retrieved with cat', async function () {\n    this.timeout(50 * 1000)\n    const res = await ipfs.add(uint8ArrayFromString(nanoid()), { preload: false })\n    await all(ipfs.cat(res.cid))\n    await waitForCids(res.cid)\n  })\n\n  it('should preload content retrieved with get', async function () {\n    this.timeout(50 * 1000)\n    const res = await ipfs.add(uint8ArrayFromString(nanoid()), { preload: false })\n    await all(ipfs.get(res.cid))\n    await waitForCids(res.cid)\n  })\n\n  it('should preload content retrieved with ls', async function () {\n    this.timeout(50 * 1000)\n\n    const res = await all(ipfs.addAll([{\n      path: 'dir0/dir1/file0',\n      content: uint8ArrayFromString(nanoid())\n    }, {\n      path: 'dir0/dir1/file1',\n      content: uint8ArrayFromString(nanoid())\n    }, {\n      path: 'dir0/file2',\n      content: uint8ArrayFromString(nanoid())\n    }], { wrapWithDirectory: true, preload: false }))\n\n    const wrappingDir = res.find(file => file.path === '')\n    expect(wrappingDir).to.exist()\n\n    if (!wrappingDir) {\n      throw new Error('wrappingDir did not exist')\n    }\n\n    // Adding these files with have preloaded wrappingDir.hash, clear it out\n    await clearPreloadCids()\n\n    await all(ipfs.ls(wrappingDir.cid))\n    await waitForCids(wrappingDir.cid)\n  })\n\n  it('should preload content added with object.new', async function () {\n    this.timeout(50 * 1000)\n    const cid = await ipfs.object.new()\n    await waitForCids(cid)\n  })\n\n  it('should preload content added with object.put', async function () {\n    this.timeout(50 * 1000)\n    const cid = await ipfs.object.put({ Data: uint8ArrayFromString(nanoid()), Links: [] })\n    await waitForCids(cid)\n  })\n\n  it('should preload content added with object.patch.addLink', async function () {\n    this.timeout(50 * 1000)\n\n    const createNode = async () => {\n      const cid = await ipfs.object.put({ Data: uint8ArrayFromString(nanoid()), Links: [] })\n      const node = await ipfs.object.get(cid)\n      return { cid, node }\n    }\n\n    const [parent, link] = await Promise.all([createNode(), createNode()])\n\n    await clearPreloadCids()\n    const cid = await ipfs.object.patch.addLink(parent.cid, {\n      Name: 'link',\n      Hash: link.cid,\n      Tsize: dagPB.encode(link.node).length\n    })\n    await waitForCids(cid)\n  })\n\n  it('should preload content added with object.patch.rmLink', async function () {\n    this.timeout(50 * 1000)\n\n    const linkCid = await ipfs.object.put({ Data: uint8ArrayFromString(nanoid()), Links: [] })\n    const linkNode = await ipfs.object.get(linkCid)\n    const linkBuf = dagPB.encode(linkNode)\n\n    const parentCid = await ipfs.object.put({\n      Data: uint8ArrayFromString(nanoid()),\n      Links: [{\n        Name: 'link',\n        Hash: linkCid,\n        Tsize: linkBuf.length\n      }]\n    })\n\n    await clearPreloadCids()\n    const cid = await ipfs.object.patch.rmLink(parentCid, 'link')\n    await waitForCids(cid)\n  })\n\n  it('should preload content added with object.patch.setData', async function () {\n    this.timeout(50 * 1000)\n    const originalCid = await ipfs.object.put({ Data: uint8ArrayFromString(nanoid()), Links: [] })\n    await clearPreloadCids()\n    const patchedCid = await ipfs.object.patch.setData(originalCid, uint8ArrayFromString(nanoid()))\n    await waitForCids(patchedCid)\n  })\n\n  it('should preload content added with object.patch.appendData', async function () {\n    this.timeout(50 * 1000)\n    const originalCid = await ipfs.object.put({ Data: uint8ArrayFromString(nanoid()), Links: [] })\n    await clearPreloadCids()\n    const patchedCid = await ipfs.object.patch.appendData(originalCid, uint8ArrayFromString(nanoid()))\n    await waitForCids(patchedCid)\n  })\n\n  it('should preload content retrieved with object.get', async function () {\n    this.timeout(50 * 1000)\n    const cid = await ipfs.object.put({ Data: uint8ArrayFromString(nanoid()), Links: [] }, { preload: false })\n    await clearPreloadCids()\n    await ipfs.object.get(cid)\n    await waitForCids(cid)\n  })\n\n  it('should preload content added with block.put', async function () {\n    this.timeout(50 * 1000)\n    const cid = await ipfs.block.put(uint8ArrayFromString(nanoid()))\n    await waitForCids(cid)\n  })\n\n  it('should preload content retrieved with block.get', async function () {\n    this.timeout(50 * 1000)\n    const cid = await ipfs.block.put(uint8ArrayFromString(nanoid()), { preload: false })\n    await clearPreloadCids()\n    await ipfs.block.get(cid)\n    await waitForCids(cid)\n  })\n\n  it('should preload content retrieved with block.stat', async function () {\n    this.timeout(50 * 1000)\n    const cid = await ipfs.block.put(uint8ArrayFromString(nanoid()), { preload: false })\n    await clearPreloadCids()\n    await ipfs.block.stat(cid)\n    await waitForCids(cid)\n  })\n\n  it('should preload content added with dag.put', async function () {\n    this.timeout(50 * 1000)\n    const obj = { test: nanoid() }\n    const cid = await ipfs.dag.put(obj, { storeCodec: 'dag-cbor', hashAlg: 'sha2-256' })\n    await waitForCids(cid)\n  })\n\n  it('should preload content retrieved with dag.get', async function () {\n    this.timeout(50 * 1000)\n    const obj = { test: nanoid() }\n    const opts = { storeCodec: 'dag-cbor', hashAlg: 'sha2-256', preload: false }\n    const cid = await ipfs.dag.put(obj, opts)\n    await clearPreloadCids()\n    await ipfs.dag.get(cid)\n    await waitForCids(cid)\n  })\n\n  it('should preload content retrieved with files.ls', async () => {\n    const res = await ipfs.add({ path: `/t/${nanoid()}`, content: uint8ArrayFromString(nanoid()) }, { preload: false })\n    const dirCid = res.cid\n    await clearPreloadCids()\n    await all(ipfs.files.ls(`/ipfs/${dirCid}`))\n    await waitForCids(`/ipfs/${dirCid}`)\n  })\n\n  it('should preload content retrieved with files.ls by CID', async () => {\n    const res = await ipfs.add({ path: `/t/${nanoid()}`, content: uint8ArrayFromString(nanoid()) }, { preload: false })\n    const dirCid = res.cid\n    await all(ipfs.files.ls(dirCid))\n    await waitForCids(dirCid)\n  })\n\n  it('should preload content retrieved with files.read', async () => {\n    const { cid } = await ipfs.add(uint8ArrayFromString(nanoid()), { preload: false })\n    await clearPreloadCids()\n    await ipfs.files.read(`/ipfs/${cid}`)\n    await waitForCids(`/ipfs/${cid}`)\n  })\n\n  it('should preload content retrieved with files.stat', async () => {\n    const { cid: fileCid } = await ipfs.add(uint8ArrayFromString(nanoid()), { preload: false })\n    await clearPreloadCids()\n    await ipfs.files.stat(`/ipfs/${fileCid}`)\n    await waitForCids(`/ipfs/${fileCid}`)\n  })\n})\n\ndescribe('preload disabled', function () {\n  this.timeout(50 * 1000)\n  /** @type {import('ipfs-core-types').IPFS} */\n  let ipfs\n  /** @type {() => Promise<void>} */\n  let cleanup\n\n  before(async () => {\n    const res = await createNode({\n      preload: {\n        enabled: false,\n        addresses: [defaultAddr]\n      }\n    })\n    ipfs = res.ipfs\n    cleanup = res.cleanup\n  })\n\n  after(() => cleanup())\n\n  it('should not preload if disabled', async () => {\n    const { cid } = await ipfs.add(uint8ArrayFromString(nanoid()))\n\n    return expect(waitForCids(cid))\n      .to.eventually.be.rejected()\n      .and.have.property('code')\n      .that.equals('ERR_TIMEOUT')\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-core/test/pubsub.spec.js",
    "content": "/* eslint max-nested-callbacks: [\"error\", 8] */\n/* eslint-env mocha */\n\nimport { nanoid } from 'nanoid'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { expect } from 'aegir/chai'\nimport createNode from './utils/create-node.js'\n\ndescribe('pubsub disabled', () => {\n  /** @type {import('ipfs-core-types').IPFS} */\n  let ipfs\n  /** @type {() => Promise<void>} */\n  let cleanup\n\n  before(async () => {\n    const res = await createNode({\n      config: {\n        Pubsub: {\n          Enabled: false\n        }\n      }\n    })\n    ipfs = res.ipfs\n    cleanup = res.cleanup\n  })\n\n  after(() => cleanup())\n\n  it('should not allow subscribe if disabled', async () => {\n    const topic = nanoid()\n    const handler = () => { throw new Error('unexpected message') }\n\n    await expect(ipfs.pubsub.subscribe(topic, handler))\n      .to.eventually.be.rejected()\n      .and.to.have.property('code', 'ERR_NOT_ENABLED')\n  })\n\n  it('should not allow unsubscribe if disabled', async () => {\n    const topic = nanoid()\n    const handler = () => { throw new Error('unexpected message') }\n\n    await expect(ipfs.pubsub.unsubscribe(topic, handler))\n      .to.eventually.be.rejected()\n      .and.to.have.property('code', 'ERR_NOT_ENABLED')\n  })\n\n  it('should not allow publish if disabled', async () => {\n    const topic = nanoid()\n    const msg = uint8ArrayFromString(nanoid())\n\n    await expect(ipfs.pubsub.publish(topic, msg))\n      .to.eventually.be.rejected()\n      .and.to.have.property('code', 'ERR_NOT_ENABLED')\n  })\n\n  it('should not allow ls if disabled', async () => {\n    await expect(ipfs.pubsub.ls())\n      .to.eventually.be.rejected()\n      .and.to.have.property('code', 'ERR_NOT_ENABLED')\n  })\n\n  it('should not allow peers if disabled', async () => {\n    const topic = nanoid()\n\n    await expect(ipfs.pubsub.peers(topic))\n      .to.eventually.be.rejected()\n      .and.to.have.property('code', 'ERR_NOT_ENABLED')\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-core/test/utils/clean.js",
    "content": "\nimport rimraf from 'rimraf'\nimport { promises as fs } from 'fs'\nimport { promisify } from 'util'\n\n/**\n * @param {string} dir\n */\nexport async function clean (dir) {\n  try {\n    await fs.access(dir)\n  } catch (/** @type {any} */ err) {\n    // Does not exist so all good\n    return\n  }\n\n  return promisify(rimraf)(dir)\n}\n"
  },
  {
    "path": "packages/ipfs-core/test/utils/codecs.js",
    "content": "/* eslint-env mocha */\n\nimport { Multicodecs } from 'ipfs-core-utils/multicodecs'\nimport * as dagPB from '@ipld/dag-pb'\nimport * as dagCBOR from '@ipld/dag-cbor'\nimport * as dagJSON from '@ipld/dag-json'\nimport * as raw from 'multiformats/codecs/raw'\n\nexport const codecs = new Multicodecs({\n  codecs: [dagPB, dagCBOR, dagJSON, raw],\n  loadCodec: () => Promise.reject(new Error('No extra codecs configured'))\n})\n"
  },
  {
    "path": "packages/ipfs-core/test/utils/create-backend.js",
    "content": "import { MemoryDatastore } from 'datastore-core/memory'\nimport { BlockstoreDatastoreAdapter } from 'blockstore-datastore-adapter'\n\nexport function createBackend (overrides = {}) {\n  return {\n    datastore: new MemoryDatastore(),\n    blocks: new BlockstoreDatastoreAdapter(\n      new MemoryDatastore()\n    ),\n    pins: new MemoryDatastore(),\n    keys: new MemoryDatastore(),\n    root: new MemoryDatastore(),\n    ...overrides\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/test/utils/create-node.js",
    "content": "import mergeOpts from 'merge-options'\nimport { create } from '../../src/index.js'\nimport { createTempRepo } from './create-repo.js'\n\nconst mergeOptions = mergeOpts.bind({ ignoreUndefined: true })\n\n/**\n * @param {import('../../src/types').Options} config\n */\nexport default async (config = {}) => {\n  let repo\n\n  if (config.repo) {\n    if (typeof config.repo === 'string') {\n      repo = await createTempRepo({ path: config.repo })\n    } else {\n      repo = config.repo\n    }\n  } else {\n    repo = await createTempRepo()\n  }\n\n  /** @type {import('ipfs-core-types').IPFS} */\n  const ipfs = await create(mergeOptions({\n    silent: true,\n    repo,\n    config: {\n      Addresses: {\n        Swarm: [],\n        Delegates: []\n      },\n      Bootstrap: []\n    },\n    preload: {\n      enabled: false\n    }\n  }, config))\n\n  return {\n    ipfs,\n    repo,\n    cleanup: async () => {\n      await ipfs.stop()\n    }\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/test/utils/create-repo.js",
    "content": "\nimport { nanoid } from 'nanoid'\nimport { codecs } from './codecs.js'\nimport { createBackend } from './create-backend.js'\nimport { Key } from 'interface-datastore/key'\nimport { createRepo } from 'ipfs-repo'\nimport { MemoryLock } from 'ipfs-repo/locks/memory'\n\n/**\n * @param {object} options\n * @param {string} [options.path]\n * @param {number} [options.version]\n * @param {number} [options.spec]\n * @param {boolean} [options.autoMigrate]\n * @param {(version: number, percentComplete: string, message: string) => void} [options.onMigrationProgress]\n * @param {import('ipfs-core-types/src/config').Config} [options.config]\n */\nexport async function createTempRepo (options = {}) {\n  const path = options.path || 'ipfs-test-' + nanoid()\n\n  const backend = createBackend()\n  const encoder = new TextEncoder()\n\n  if (options.version) {\n    await backend.root.open()\n    await backend.root.put(new Key('/version'), encoder.encode(`${options.version}`))\n    await backend.root.close()\n  }\n\n  if (options.spec) {\n    await backend.root.open()\n    await backend.root.put(new Key('/datastore_spec'), encoder.encode(`${options.spec}`))\n    await backend.root.close()\n  }\n\n  if (options.config) {\n    await backend.root.open()\n    await backend.root.put(new Key('/config'), encoder.encode(JSON.stringify(options.config)))\n    await backend.root.close()\n  }\n\n  return createRepo(path, (codeOrName) => codecs.getCodec(codeOrName), backend, {\n    repoLock: MemoryLock,\n    autoMigrate: options.autoMigrate,\n    onMigrationProgress: options.onMigrationProgress\n  })\n}\n"
  },
  {
    "path": "packages/ipfs-core/test/utils/mock-preload-node-utils.js",
    "content": "/* eslint-env browser */\n\nimport { multiaddrToUri } from '@multiformats/multiaddr-to-uri'\nimport errCode from 'err-code'\nimport HTTP from 'ipfs-utils/src/http.js'\nimport { waitFor } from './wait-for.js'\n\n/**\n * @typedef {import('multiformats/cid').CID} CID\n */\n\nexport const defaultPort = 1138\nexport const defaultAddr = `/dnsaddr/localhost/tcp/${defaultPort}`\n\n/**\n * Get the stored preload CIDs for the server at `addr`\n *\n * @param {string} [addr]\n * @returns {Promise<string[]>}\n */\nexport async function getPreloadCids (addr) {\n  const res = await HTTP.get(`${multiaddrToUri(addr || defaultAddr)}/cids`)\n  return res.json()\n}\n\n/**\n * Clear the stored preload URLs for the server at `addr`\n *\n * @param {string} [addr]\n */\nexport function clearPreloadCids (addr) {\n  return HTTP.delete(`${multiaddrToUri(addr || defaultAddr)}/cids`)\n}\n\n/**\n * Wait for the passed CIDs to appear in the CID list from the preload node\n *\n * @param {CID | CID[] | string | string[]} cids\n * @param {object} [opts]\n * @param {number} [opts.timeout]\n * @param {string} [opts.addr]\n */\nexport async function waitForCids (cids, opts) {\n  const options = opts || {}\n  options.timeout = options.timeout || 1000\n\n  const cidArr = Array.isArray(cids) ? cids : [cids]\n  const cidStrs = cidArr.map(cid => cid.toString()) // Allow passing CID instance\n\n  await waitFor(async () => {\n    const preloadCids = await getPreloadCids(options.addr)\n\n    // See if our cached preloadCids includes all the cids we're looking for.\n    /** @type {{ missing: string[], duplicates: string[] }} */\n    const results = { missing: [], duplicates: [] }\n    const { missing, duplicates } = cidStrs.reduce((results, cid) => {\n      const count = preloadCids.filter(preloadedCid => preloadedCid === cid).length\n      if (count === 0) {\n        results.missing.push(cid)\n      } else if (count > 1) {\n        results.duplicates.push(cid)\n      }\n      return results\n    }, results)\n\n    if (duplicates.length) {\n      throw errCode(new Error(`Multiple occurrences of ${duplicates} found`), 'ERR_DUPLICATE')\n    }\n\n    return missing.length === 0\n  }, {\n    name: 'CIDs to be preloaded',\n    interval: 5,\n    timeout: options.timeout\n  })\n}\n"
  },
  {
    "path": "packages/ipfs-core/test/utils/mock-preload-node.js",
    "content": "/* eslint-env browser */\n\nimport http from 'http'\nimport { URL } from 'iso-url'\n\nexport const defaultPort = 1138\nexport const defaultAddr = `/dnsaddr/localhost/tcp/${defaultPort}`\n\n// Create a mock preload IPFS node with a gateway that'll respond 200 to a\n// request for /api/v0/refs?arg=*. It remembers the preload CIDs it has been\n// called with, and you can ask it for them and also clear them by issuing a\n// GET/DELETE request to /cids.\nexport function createNode () {\n  /** @type {string[]} */\n  let cids = []\n\n  /** @type {ReturnType<http.createServer> & { start: (opts?: any) => Promise<void>, stop: () => Promise<any> }} */\n  // @ts-expect-error start/stop props are added later\n  const server = http.createServer((req, res) => {\n    res.setHeader('Access-Control-Allow-Origin', '*')\n    res.setHeader('Access-Control-Request-Method', '*')\n    res.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET, DELETE')\n    res.setHeader('Access-Control-Allow-Headers', '*')\n\n    if (req.method === 'OPTIONS') {\n      res.writeHead(200)\n      res.end()\n      return\n    }\n\n    if (req.url?.startsWith('/api/v0/refs')) {\n      const arg = new URL(`https://ipfs.io${req.url}`).searchParams.get('arg')\n\n      if (arg) {\n        cids = cids.concat(arg)\n      }\n    } else if (req.method === 'DELETE' && req.url === '/cids') {\n      res.statusCode = 204\n      cids = []\n    } else if (req.method === 'GET' && req.url === '/cids') {\n      res.setHeader('Content-Type', 'application/json')\n      res.write(JSON.stringify(cids))\n    } else {\n      res.statusCode = 500\n    }\n\n    res.end()\n  })\n  server.start = (opts = {}) => new Promise(resolve => server.listen({ port: defaultPort, ...opts }, resolve))\n  server.stop = () => new Promise(resolve => server.close(resolve))\n\n  return server\n}\n"
  },
  {
    "path": "packages/ipfs-core/test/utils/wait-for.js",
    "content": "import delay from 'delay'\nimport errCode from 'err-code'\n\n/**\n * Wait for async function `test` to resolve true or timeout after options.timeout milliseconds\n *\n * @param {() => boolean | Promise<boolean>} test\n * @param {object} options\n * @param {number} [options.timeout]\n * @param {string} [options.name]\n * @param {number} [options.interval]\n */\nexport async function waitFor (test, options) {\n  const opts = Object.assign({ timeout: 5000, interval: 1000, name: 'event' }, options)\n  const start = Date.now()\n\n  while (true) {\n    if (await test()) {\n      return\n    }\n\n    if (Date.now() > start + opts.timeout) {\n      throw errCode(new Error(`Timed out waiting for ${opts.name}`), 'ERR_TIMEOUT')\n    }\n\n    await delay(opts.interval)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core/test/utils.spec.js",
    "content": "/* eslint max-nested-callbacks: [\"error\", 8] */\n/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { resolvePath } from '../src/utils.js'\nimport { createTempRepo } from './utils/create-repo.js'\nimport { importer } from 'ipfs-unixfs-importer'\nimport all from 'it-all'\nimport { codecs } from './utils/codecs.js'\n\ndescribe('utils', () => {\n  /** @type {import('multiformats/cid').CID} */\n  let rootCid\n  /** @type {import('multiformats/cid').CID} */\n  let aboutCid\n  /** @type {string} */\n  let aboutPath\n  /** @type {Uint8Array} */\n  let aboutMultihash\n\n  describe('resolvePath', function () {\n    this.timeout(100 * 1000)\n\n    /** @type {import('ipfs-repo').IPFSRepo} */\n    let repo\n\n    before(async () => {\n      repo = await createTempRepo()\n\n      const res = await all(importer([{\n        path: '/dir/contents.txt',\n        content: Uint8Array.from([0, 1, 2, 3])\n      }], repo.blocks, {\n        wrapWithDirectory: true\n      }))\n\n      rootCid = res[2].cid\n\n      aboutCid = res[0].cid\n      aboutPath = `/ipfs/${aboutCid}`\n      aboutMultihash = aboutCid.multihash.bytes\n    })\n\n    it('handles base58 hash format', async () => {\n      const { cid, remainderPath } = await resolvePath(repo, codecs, rootCid)\n\n      expect(cid.toString()).to.equal(rootCid.toString())\n      expect(remainderPath).to.be.empty()\n    })\n\n    it('handles multihash format', async () => {\n      const { cid, remainderPath } = await resolvePath(repo, codecs, aboutMultihash)\n\n      expect(cid.toString()).to.equal(aboutCid.toString())\n      expect(remainderPath).to.be.empty()\n    })\n\n    it('handles ipfs paths format', async function () {\n      this.timeout(200 * 1000)\n      const { cid, remainderPath } = await resolvePath(repo, codecs, aboutPath)\n\n      expect(cid.toString()).to.equal(aboutCid.toString())\n      expect(remainderPath).to.be.empty()\n    })\n\n    it('should error on invalid hashes', () => {\n      return expect(resolvePath(repo, codecs, '/ipfs/asdlkjahsdfkjahsdfd'))\n        .to.eventually.be.rejected()\n    })\n\n    it('should error when a link doesn\\'t exist', () => {\n      return expect(resolvePath(repo, codecs, `${aboutPath}/fusion`))\n        .to.eventually.be.rejected()\n        .and.have.property('message')\n        .that.include(`no link named \"fusion\" under ${aboutCid}`)\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-core/tsconfig.json",
    "content": "{\n  \"extends\": \"aegir/src/config/tsconfig.aegir.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"emitDeclarationOnly\": true\n  },\n  \"include\": [\n    \"src\",\n    \"test\",\n    \"package.json\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../interface-ipfs-core\"\n    },\n    {\n      \"path\": \"../ipfs-core-config\"\n    },\n    {\n      \"path\": \"../ipfs-core-types\"\n    },\n    {\n      \"path\": \"../ipfs-core-utils\"\n    },\n    {\n      \"path\": \"../ipfs-http-client\"\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/ipfs-core-config/.aegir.js",
    "content": "\n/** @type {import('aegir').PartialOptions} */\nexport default {\n  build: {\n    bundlesizeMax: '540B'\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core-config/CHANGELOG.md",
    "content": "# Change Log\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n### [0.7.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-config-v0.7.0...ipfs-core-config-v0.7.1) (2023-05-25)\n\n\n### Bug Fixes\n\n* add deprecation notice to readmes ([#4362](https://www.github.com/ipfs/js-ipfs/issues/4362)) ([7b79c1b](https://www.github.com/ipfs/js-ipfs/commit/7b79c1b8df5c818dc124b346ea28330455732d5c))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * ipfs-core-utils bumped from ^0.18.0 to ^0.18.1\n\n## [0.7.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-config-v0.6.0...ipfs-core-config-v0.7.0) (2023-01-11)\n\n\n### ⚠ BREAKING CHANGES\n\n* update multiformats to v11.x.x and related depenendcies (#4277)\n\n### Bug Fixes\n\n* disallow publishing pubsub messages to zero peers ([#4286](https://www.github.com/ipfs/js-ipfs/issues/4286)) ([fa578ba](https://www.github.com/ipfs/js-ipfs/commit/fa578bace93e459849a0ffcebbd6f222dc05652d))\n* update multiformats to v11.x.x and related depenendcies ([#4277](https://www.github.com/ipfs/js-ipfs/issues/4277)) ([521c84a](https://www.github.com/ipfs/js-ipfs/commit/521c84a958b04d61702577a5adce28519c1b2a3b))\n* use aegir to publish RCs ([#4284](https://www.github.com/ipfs/js-ipfs/issues/4284)) ([6d90cbf](https://www.github.com/ipfs/js-ipfs/commit/6d90cbf321a7dbf4b1084ba20f0c514dc08d8d0a))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * ipfs-core-utils bumped from ^0.17.0 to ^0.18.0\n\n## [0.6.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-config-v0.5.1...ipfs-core-config-v0.6.0) (2022-10-24)\n\n\n### ⚠ BREAKING CHANGES\n\n* ipfs is now bundled with libp2p@0.40.x which has different config\n\n### Features\n\n* upgrade libp2p to 0.40.x ([#4237](https://www.github.com/ipfs/js-ipfs/issues/4237)) ([0cee4a4](https://www.github.com/ipfs/js-ipfs/commit/0cee4a4c55767022584dcbade0b0b9b43326f9c9))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * ipfs-core-utils bumped from ^0.16.1 to ^0.17.0\n\n### [0.5.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-config-v0.5.0...ipfs-core-config-v0.5.1) (2022-09-21)\n\n\n### Bug Fixes\n\n* update @multiformats/multiadd to 11.0.0 ([2a830bf](https://www.github.com/ipfs/js-ipfs/commit/2a830bf58a5929fcce51dede871c99f62192fbda))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * ipfs-core-utils bumped from ^0.16.0 to ^0.16.1\n\n## [0.5.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-config-v0.4.1...ipfs-core-config-v0.5.0) (2022-09-06)\n\n\n### ⚠ BREAKING CHANGES\n\n* update to libp2p@0.38.x (#4151)\n\n### deps\n\n* update to libp2p@0.38.x ([#4151](https://www.github.com/ipfs/js-ipfs/issues/4151)) ([39dbf70](https://www.github.com/ipfs/js-ipfs/commit/39dbf708ec31b263115e44f420651fa4e056a89e))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * ipfs-core-utils bumped from ^0.15.0 to ^0.16.0\n\n### [0.4.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-config-v0.4.0...ipfs-core-config-v0.4.1) (2022-06-22)\n\n\n### Bug Fixes\n\n* use default ws filters instead of connecting to everything ([#4142](https://www.github.com/ipfs/js-ipfs/issues/4142)) ([7be50bd](https://www.github.com/ipfs/js-ipfs/commit/7be50bd157b984d4607545bb78d22cd33de933fa)), closes [#4141](https://www.github.com/ipfs/js-ipfs/issues/4141)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * ipfs-core-utils bumped from ^0.15.0 to ^0.15.1\n\n## [0.4.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-config-v0.3.3...ipfs-core-config-v0.4.0) (2022-05-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* This module is now ESM only and there return types of some methods have changed\n\n### Features\n\n* update to libp2p 0.37.x ([#4092](https://www.github.com/ipfs/js-ipfs/issues/4092)) ([74aee8b](https://www.github.com/ipfs/js-ipfs/commit/74aee8b3d78f233c3199a3e9a6c0ac628a31a433))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * ipfs-core-utils bumped from ^0.14.3 to ^0.15.0\n\n### [0.3.3](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-config-v0.3.2...ipfs-core-config-v0.3.3) (2022-04-20)\n\n\n### Bug Fixes\n\n* exclude fs from bundle ([#4076](https://www.github.com/ipfs/js-ipfs/issues/4076)) ([6c3cb73](https://www.github.com/ipfs/js-ipfs/commit/6c3cb73db7b46211c88431273f61f04463a4f80d))\n* upgrade dep of ipfs-utils ^9.0.2->^9.0.6 ([#4086](https://www.github.com/ipfs/js-ipfs/issues/4086)) ([8f7ce23](https://www.github.com/ipfs/js-ipfs/commit/8f7ce23c18be12bdc52b98bfccbd0a5a2a9c9f7e)), closes [#4080](https://www.github.com/ipfs/js-ipfs/issues/4080)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * ipfs-core-utils bumped from ^0.14.2 to ^0.14.3\n\n### [0.3.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-config-v0.3.1...ipfs-core-config-v0.3.2) (2022-03-01)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * ipfs-core-utils bumped from ^0.14.1 to ^0.14.2\n\n### [0.3.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-config-v0.3.0...ipfs-core-config-v0.3.1) (2022-02-06)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * ipfs-core-utils bumped from ^0.14.0 to ^0.14.1\n\n## [0.3.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-config-v0.2.0...ipfs-core-config-v0.3.0) (2022-01-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* peerstore methods are now all async, the repo is migrated to v12\n* node 15+ is required\n\n### Features\n\n* libp2p async peerstore ([#4018](https://www.github.com/ipfs/js-ipfs/issues/4018)) ([a6b201a](https://www.github.com/ipfs/js-ipfs/commit/a6b201af2c3697430ab0ebe002dd573d185f1ac0))\n\n\n### Bug Fixes\n\n* remove abort-controller deps ([#4015](https://www.github.com/ipfs/js-ipfs/issues/4015)) ([902e887](https://www.github.com/ipfs/js-ipfs/commit/902e887e1acac87f607324fa7cb5ad4b14aefcf3))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * ipfs-core-utils bumped from ^0.13.0 to ^0.14.0\n\n\n## [0.2.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core-config@0.1.4...ipfs-core-config@0.2.0) (2021-12-15)\n\n\n### Features\n\n* dht client ([#3947](https://github.com/ipfs/js-ipfs/issues/3947)) ([62d8ecb](https://github.com/ipfs/js-ipfs/commit/62d8ecbc723e693a2544e69172d99c576d187c23))\n* improve collected metrics ([#3978](https://github.com/ipfs/js-ipfs/issues/3978)) ([33f1034](https://github.com/ipfs/js-ipfs/commit/33f1034a6fc257f1a87de7bb38d876925f61cb5f))\n\n\n### BREAKING CHANGES\n\n* The DHT API has been refactored to return async iterators of query events\n\n\n### [0.1.4](https://github.com/ipfs/js-ipfs/compare/ipfs-core-config@0.1.3...ipfs-core-config@0.1.4) (2021-11-24)\n\n**Note:** Version bump only for package ipfs-core-config\n\n\n\n\n\n### [0.1.3](https://github.com/ipfs/js-ipfs/compare/ipfs-core-config@0.1.2...ipfs-core-config@0.1.3) (2021-11-19)\n\n**Note:** Version bump only for package ipfs-core-config\n\n\n\n\n\n### [0.1.2](https://github.com/ipfs/js-ipfs/compare/ipfs-core-config@0.1.1...ipfs-core-config@0.1.2) (2021-11-12)\n\n**Note:** Version bump only for package ipfs-core-config\n\n\n\n\n\n### [0.1.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core-config@0.1.0...ipfs-core-config@0.1.1) (2021-09-28)\n\n**Note:** Version bump only for package ipfs-core-config\n\n\n\n\n\n## [0.1.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core-config@0.0.1...ipfs-core-config@0.1.0) (2021-09-24)\n\n\n### Features\n\n* pull in new globSource ([#3889](https://github.com/ipfs/js-ipfs/issues/3889)) ([be4a542](https://github.com/ipfs/js-ipfs/commit/be4a5428ebc4b05a2edd9a91bf9df6416c1a8c2b))\n\n\n### BREAKING CHANGES\n\n* the globSource api has changed from `globSource(dir, opts)` to `globSource(dir, pattern, opts)`\n\n\n\n\n\n## [0.10.5](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.10.4...ipfs-core-utils@0.10.5) (2021-09-17)\n\n**Note:** Version bump only for package ipfs-core-utils\n\n\n\n\n\n## [0.10.4](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.10.3...ipfs-core-utils@0.10.4) (2021-09-17)\n\n**Note:** Version bump only for package ipfs-core-utils\n\n\n\n\n\n## [0.10.3](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.10.2...ipfs-core-utils@0.10.3) (2021-09-02)\n\n\n### Bug Fixes\n\n* declare types in .ts files ([#3840](https://github.com/ipfs/js-ipfs/issues/3840)) ([eba5fe6](https://github.com/ipfs/js-ipfs/commit/eba5fe6832858107b3e1ae02c99de674622f12b4))\n* remove use of instanceof for CID class ([#3847](https://github.com/ipfs/js-ipfs/issues/3847)) ([ebbb12d](https://github.com/ipfs/js-ipfs/commit/ebbb12db523c53ce8e4ddae5266cd9acb3504431))\n\n\n\n\n\n## [0.10.2](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.10.1...ipfs-core-utils@0.10.2) (2021-08-25)\n\n**Note:** Version bump only for package ipfs-core-utils\n\n\n\n\n\n## [0.10.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.10.0...ipfs-core-utils@0.10.1) (2021-08-17)\n\n\n### Bug Fixes\n\n* throw error on missing input to add/addAll ([#3818](https://github.com/ipfs/js-ipfs/issues/3818)) ([1343708](https://github.com/ipfs/js-ipfs/commit/1343708f70d7298b6677555803d68ff282d89439)), closes [#3788](https://github.com/ipfs/js-ipfs/issues/3788)\n\n\n\n\n\n## [0.10.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.9.1...ipfs-core-utils@0.10.0) (2021-08-11)\n\n\n### Features\n\n* make ipfs.get output tarballs ([#3785](https://github.com/ipfs/js-ipfs/issues/3785)) ([1ad6001](https://github.com/ipfs/js-ipfs/commit/1ad60018d39d5b46c484756631e30e1989fd8eba))\n\n\n### BREAKING CHANGES\n\n* the output type of `ipfs.get` has changed and the `recursive` option has been removed from `ipfs.ls` since it was not supported everywhere\n\n\n\n\n\n### [0.9.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.9.0...ipfs-core-utils@0.9.1) (2021-07-30)\n\n**Note:** Version bump only for package ipfs-core-utils\n\n\n\n\n\n## [0.9.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.8.3...ipfs-core-utils@0.9.0) (2021-07-27)\n\n\n### Bug Fixes\n\n* support @web-std/file in normalize input ([#3750](https://github.com/ipfs/js-ipfs/issues/3750)) ([6fd7776](https://github.com/ipfs/js-ipfs/commit/6fd777679d0aa80bbb784d16585456e54b5cf294))\n\n\n### Features\n\n* implement dag import/export ([#3728](https://github.com/ipfs/js-ipfs/issues/3728)) ([700765b](https://github.com/ipfs/js-ipfs/commit/700765be2634fa5d2d71d8b87cf68c9cd328d2c4)), closes [#2953](https://github.com/ipfs/js-ipfs/issues/2953) [#2745](https://github.com/ipfs/js-ipfs/issues/2745)\n* upgrade to the new multiformats ([#3556](https://github.com/ipfs/js-ipfs/issues/3556)) ([d13d15f](https://github.com/ipfs/js-ipfs/commit/d13d15f022a87d04a35f0f7822142f9cb898479c))\n\n\n### BREAKING CHANGES\n\n* ipld-formats no longer supported, use multiformat BlockCodecs instead\n\nCo-authored-by: Rod Vagg <rod@vagg.org>\nCo-authored-by: achingbrain <alex@achingbrain.net>\n\n\n\n\n\n### [0.8.3](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.8.2...ipfs-core-utils@0.8.3) (2021-06-18)\n\n**Note:** Version bump only for package ipfs-core-utils\n\n\n\n\n\n### [0.8.2](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.8.1...ipfs-core-utils@0.8.2) (2021-06-05)\n\n\n### Bug Fixes\n\n* stalling subscription on (node) http-client when daemon is stopped ([#3468](https://github.com/ipfs/js-ipfs/issues/3468)) ([0266abf](https://github.com/ipfs/js-ipfs/commit/0266abf0c4b817636172f78c6e91eb4dd5aad451)), closes [#3465](https://github.com/ipfs/js-ipfs/issues/3465)\n\n\n\n\n\n### [0.8.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.8.0...ipfs-core-utils@0.8.1) (2021-05-26)\n\n**Note:** Version bump only for package ipfs-core-utils\n\n\n\n\n\n## [0.8.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.7.2...ipfs-core-utils@0.8.0) (2021-05-10)\n\n\n### Bug Fixes\n\n* mark ipld options as partial ([#3669](https://github.com/ipfs/js-ipfs/issues/3669)) ([f98af8e](https://github.com/ipfs/js-ipfs/commit/f98af8ed24784929898bb5d33a64dc442c77074d))\n\n\n### chore\n\n* upgrade deps with new typedefs ([#3550](https://github.com/ipfs/js-ipfs/issues/3550)) ([a418a52](https://github.com/ipfs/js-ipfs/commit/a418a521574c878d7aabd0ad2fd8d516908a3756))\n\n\n### BREAKING CHANGES\n\n* all core api methods now have types, some method signatures have changed, named exports are now used by the http, grpc and ipfs client modules\n\n\n\n\n\n### [0.7.2](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.7.1...ipfs-core-utils@0.7.2) (2021-03-09)\n\n\n### Bug Fixes\n\n* update to new aegir ([#3528](https://github.com/ipfs/js-ipfs/issues/3528)) ([49f7880](https://github.com/ipfs/js-ipfs/commit/49f78807d7e26483bd926b45cc7e0f797d77e41b))\n\n\n\n\n\n### [0.7.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.7.0...ipfs-core-utils@0.7.1) (2021-02-08)\n\n**Note:** Version bump only for package ipfs-core-utils\n\n\n\n\n\n## [0.7.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.6.1...ipfs-core-utils@0.7.0) (2021-02-01)\n\n\n### chore\n\n* update deps ([#3514](https://github.com/ipfs/js-ipfs/issues/3514)) ([061d77c](https://github.com/ipfs/js-ipfs/commit/061d77cc03f40af5a3bc3590481e1e5836e7f0d8))\n\n\n### BREAKING CHANGES\n\n* ipfs-repo upgrade requires repo migration to v10\n\n\n\n\n\n### [0.6.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.6.0...ipfs-core-utils@0.6.1) (2021-01-22)\n\n**Note:** Version bump only for package ipfs-core-utils\n\n\n\n\n\n## [0.6.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.5.4...ipfs-core-utils@0.6.0) (2021-01-15)\n\n\n### Features\n\n* allow passing a http.Agent to the grpc client ([#3477](https://github.com/ipfs/js-ipfs/issues/3477)) ([c5f0bc5](https://github.com/ipfs/js-ipfs/commit/c5f0bc5eeee15369b7d02901035b04184a8608d2)), closes [#3474](https://github.com/ipfs/js-ipfs/issues/3474)\n\n\n\n\n\n### [0.5.4](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.5.3...ipfs-core-utils@0.5.4) (2020-12-16)\n\n\n### Bug Fixes\n\n* regressions introduced by new releases of CID & multicodec ([#3442](https://github.com/ipfs/js-ipfs/issues/3442)) ([b5152d8](https://github.com/ipfs/js-ipfs/commit/b5152d8cc93ecc8d39fc353ea66d7eaf1661e3c0)), closes [/github.com/multiformats/js-cid/commit/0e11f035c9230e7f6d79c159ace9b80de88cb5eb#diff-25a6634263c1b1f6fc4697a04e2b9904ea4b042a89af59dc93ec1f5d44848a26](https://github.com//github.com/multiformats/js-cid/commit/0e11f035c9230e7f6d79c159ace9b80de88cb5eb/issues/diff-25a6634263c1b1f6fc4697a04e2b9904ea4b042a89af59dc93ec1f5d44848a26)\n* types for withTimeoutOptions ([#3422](https://github.com/ipfs/js-ipfs/issues/3422)) ([af0b7f3](https://github.com/ipfs/js-ipfs/commit/af0b7f34587bd432860a31d40eabc6aa70aef619)), closes [/github.com/ipfs/js-ipfs/pull/3407/files#diff-722621abc3ed4edc6ab202fdf684f1607c261394b95da6b3ec79748711056f20](https://github.com//github.com/ipfs/js-ipfs/pull/3407/files/issues/diff-722621abc3ed4edc6ab202fdf684f1607c261394b95da6b3ec79748711056f20)\n\n\n\n\n\n### [0.5.3](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.5.2...ipfs-core-utils@0.5.3) (2020-11-25)\n\n**Note:** Version bump only for package ipfs-core-utils\n\n\n\n\n\n### [0.5.2](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.5.1...ipfs-core-utils@0.5.2) (2020-11-16)\n\n\n### Bug Fixes\n\n* report ipfs.add progress over http ([#3310](https://github.com/ipfs/js-ipfs/issues/3310)) ([39cad4b](https://github.com/ipfs/js-ipfs/commit/39cad4b76b950ea6a76477fd01f8631b8bd9aa1e))\n\n\n\n\n\n### [0.5.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.5.0...ipfs-core-utils@0.5.1) (2020-11-09)\n\n\n### Bug Fixes\n\n* typedef resolution & add examples that use types ([#3359](https://github.com/ipfs/js-ipfs/issues/3359)) ([dc2795a](https://github.com/ipfs/js-ipfs/commit/dc2795a4f3b515683d09967ce611bf87d5e67f86)), closes [#3356](https://github.com/ipfs/js-ipfs/issues/3356) [#3358](https://github.com/ipfs/js-ipfs/issues/3358)\n\n\n\n\n\n## [0.5.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.4.0...ipfs-core-utils@0.5.0) (2020-10-28)\n\n\n### Bug Fixes\n\n* use fetch in electron renderer and electron-fetch in main ([#3251](https://github.com/ipfs/js-ipfs/issues/3251)) ([639d71f](https://github.com/ipfs/js-ipfs/commit/639d71f7ac8f66d9633e753a2a6be927e14a5af0))\n\n\n### Features\n\n* type check & generate defs from jsdoc ([#3281](https://github.com/ipfs/js-ipfs/issues/3281)) ([bbcaf34](https://github.com/ipfs/js-ipfs/commit/bbcaf34111251b142273a5675f4754ff68bd9fa0))\n\n\n\n\n\n## [0.4.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.3.2...ipfs-core-utils@0.4.0) (2020-09-03)\n\n\n### Features\n\n* store pins in datastore instead of a DAG ([#2771](https://github.com/ipfs/js-ipfs/issues/2771)) ([64b7fe4](https://github.com/ipfs/js-ipfs/commit/64b7fe41738cbe96d5a9075f0c01156c6f889c40))\n\n\n\n\n\n### [0.3.2](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.3.1...ipfs-core-utils@0.3.2) (2020-08-24)\n\n\n### Bug Fixes\n\n* validate ipns records with inline public keys ([#3224](https://github.com/ipfs/js-ipfs/issues/3224)) ([5cc0e08](https://github.com/ipfs/js-ipfs/commit/5cc0e086b036e7ba40b09768b67b7067adca43c1))\n\n\n\n\n\n### [0.3.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.3.0...ipfs-core-utils@0.3.1) (2020-08-12)\n\n\n### Bug Fixes\n\n* send blobs when running ipfs-http-client in the browser ([#3184](https://github.com/ipfs/js-ipfs/issues/3184)) ([6b24463](https://github.com/ipfs/js-ipfs/commit/6b24463431497bd13b579a730ad7063345729ad9)), closes [#3138](https://github.com/ipfs/js-ipfs/issues/3138)\n\n\n\n\n\n## [0.3.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.2.4...ipfs-core-utils@0.3.0) (2020-07-16)\n\n\n### Bug Fixes\n\n* optional arguments go in the options object ([#3118](https://github.com/ipfs/js-ipfs/issues/3118)) ([8cb8c73](https://github.com/ipfs/js-ipfs/commit/8cb8c73037e44894d756b70f344b3282463206f9))\n* set error code correctly ([#3150](https://github.com/ipfs/js-ipfs/issues/3150)) ([335c13d](https://github.com/ipfs/js-ipfs/commit/335c13d529fc54e4610fc1aa03212126f43c63ec))\n\n\n### Features\n\n* store blocks by multihash instead of CID ([#3124](https://github.com/ipfs/js-ipfs/issues/3124)) ([03b17f5](https://github.com/ipfs/js-ipfs/commit/03b17f5e2d290e84aa0cb541079b79e468e7d1bd))\n\n\n\n\n\n### [0.2.4](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.2.3...ipfs-core-utils@0.2.4) (2020-06-24)\n\n**Note:** Version bump only for package ipfs-core-utils\n\n\n\n\n\n### [0.2.3](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.2.2...ipfs-core-utils@0.2.3) (2020-05-18)\n\n\n### Bug Fixes\n\n* remove node globals ([#2932](https://github.com/ipfs/js-ipfs/issues/2932)) ([d0d2f74](https://github.com/ipfs/js-ipfs/commit/d0d2f74cef4e439c6d2baadba1f1f9f52534fcba))\n\n\n### Features\n\n* cancellable api calls ([#2993](https://github.com/ipfs/js-ipfs/issues/2993)) ([2b24f59](https://github.com/ipfs/js-ipfs/commit/2b24f590041a0df9da87b75ae2344232fe22fe3a)), closes [#3015](https://github.com/ipfs/js-ipfs/issues/3015)\n\n\n\n\n\n### [0.2.2](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.2.1...ipfs-core-utils@0.2.2) (2020-05-05)\n\n**Note:** Version bump only for package ipfs-core-utils\n\n\n\n\n\n### [0.2.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.2.0...ipfs-core-utils@0.2.1) (2020-05-05)\n\n\n### Bug Fixes\n\n* pass headers to request ([#3018](https://github.com/ipfs/js-ipfs/issues/3018)) ([3ba00f8](https://github.com/ipfs/js-ipfs/commit/3ba00f8c6a8a057c5776d539a671a74d9565fb29)), closes [#3017](https://github.com/ipfs/js-ipfs/issues/3017)\n\n\n\n\n\n## [0.2.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.1.1...ipfs-core-utils@0.2.0) (2020-04-16)\n\n\n### Bug Fixes\n\n* make http api only accept POST requests ([#2977](https://github.com/ipfs/js-ipfs/issues/2977)) ([943d4a8](https://github.com/ipfs/js-ipfs/commit/943d4a8cf2d4c4ff5ecd4814c59cb0aae0cfa1fd))\n\n\n### BREAKING CHANGES\n\n* Where we used to accept all and any HTTP methods, now only POST is\naccepted.  The API client will now only send POST requests too.\n\n* test: add tests to make sure we are post-only\n\n* chore: upgrade ipfs-utils\n\n* fix: return 405 instead of 404 for bad methods\n\n* fix: reject browsers that do not send an origin\n\nAlso fixes running interface tests over http in browsers against\njs-ipfs\n\n\n\n\n\n### [0.1.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.0.1...ipfs-core-utils@0.1.1) (2020-04-08)\n\n**Note:** Version bump only for package ipfs-core-utils\n\n\n\n\n\n## 0.0.1 (2020-03-31)\n\n**Note:** Version bump only for package ipfs-core-utils\n\n\n\n\n\n<a name=\"0.7.2\"></a>\n### [0.7.2](https://github.com/ipfs/js-ipfs-utils/compare/v0.7.1...v0.7.2) (2020-02-10)\n\n\n### Bug Fixes\n\n* number is not a valid mtime value ([#24](https://github.com/ipfs/js-ipfs-utils/issues/24)) ([bb2d841](https://github.com/ipfs/js-ipfs-utils/commit/bb2d841)), closes [/github.com/ipfs/js-ipfs-unixfs/blob/master/src/index.js#L104-L106](https://github.com//github.com/ipfs/js-ipfs-unixfs/blob/master/src/index.js/issues/L104-L106)\n\n\n\n<a name=\"0.7.1\"></a>\n### [0.7.1](https://github.com/ipfs/js-ipfs-utils/compare/v0.7.0...v0.7.1) (2020-01-23)\n\n\n### Bug Fixes\n\n* downgrade to ky 15 ([#22](https://github.com/ipfs/js-ipfs-utils/issues/22)) ([5dd7570](https://github.com/ipfs/js-ipfs-utils/commit/5dd7570))\n\n\n\n<a name=\"0.7.0\"></a>\n## [0.7.0](https://github.com/ipfs/js-ipfs-utils/compare/v0.6.0...v0.7.0) (2020-01-23)\n\n\n### Features\n\n* accept browser readable streams as input ([#21](https://github.com/ipfs/js-ipfs-utils/issues/21)) ([0902067](https://github.com/ipfs/js-ipfs-utils/commit/0902067))\n\n\n\n<a name=\"0.6.0\"></a>\n## [0.6.0](https://github.com/ipfs/js-ipfs-utils/compare/v0.5.0...v0.6.0) (2020-01-09)\n\n\n### Bug Fixes\n\n* dependency badge URL ([#16](https://github.com/ipfs/js-ipfs-utils/issues/16)) ([5d93881](https://github.com/ipfs/js-ipfs-utils/commit/5d93881))\n* format mtime as timespec ([#20](https://github.com/ipfs/js-ipfs-utils/issues/20)) ([a68f8b1](https://github.com/ipfs/js-ipfs-utils/commit/a68f8b1))\n\n\n\n<a name=\"0.5.0\"></a>\n## [0.5.0](https://github.com/ipfs/js-ipfs-utils/compare/v0.4.0...v0.5.0) (2019-12-06)\n\n\n### Features\n\n* convert to async iterators ([#15](https://github.com/ipfs/js-ipfs-utils/issues/15)) ([251eff0](https://github.com/ipfs/js-ipfs-utils/commit/251eff0))\n* support unixfs metadata and formatting it ([#14](https://github.com/ipfs/js-ipfs-utils/issues/14)) ([173e4bf](https://github.com/ipfs/js-ipfs-utils/commit/173e4bf))\n\n\n### BREAKING CHANGES\n\n* In order to support metadata on intermediate directories, globSource in this module will now emit directories and files where previously it only emitted files.\n* Support for Node.js streams and Pull Streams has been removed\n\n\n\n<a name=\"0.4.0\"></a>\n## [0.4.0](https://github.com/ipfs/js-ipfs-utils/compare/v0.3.0...v0.4.0) (2019-09-19)\n\n\n### Features\n\n* add isElectronMain env test ([#13](https://github.com/ipfs/js-ipfs-utils/issues/13)) ([9072c90](https://github.com/ipfs/js-ipfs-utils/commit/9072c90))\n\n\n\n<a name=\"0.3.0\"></a>\n## [0.3.0](https://github.com/ipfs/js-ipfs-utils/compare/v0.2.0...v0.3.0) (2019-09-15)\n\n\n### Features\n\n* support old school streams ([#12](https://github.com/ipfs/js-ipfs-utils/issues/12)) ([18cfa86](https://github.com/ipfs/js-ipfs-utils/commit/18cfa86))\n\n\n\n<a name=\"0.2.0\"></a>\n## [0.2.0](https://github.com/ipfs/js-ipfs-utils/compare/v0.1.0...v0.2.0) (2019-09-06)\n\n\n### Features\n\n* env/isTest ([#10](https://github.com/ipfs/js-ipfs-utils/issues/10)) ([481aab1](https://github.com/ipfs/js-ipfs-utils/commit/481aab1))\n\n\n\n<a name=\"0.1.0\"></a>\n## [0.1.0](https://github.com/ipfs/js-ipfs-utils/compare/v0.0.4...v0.1.0) (2019-09-04)\n\n\n### Bug Fixes\n\n* write after end ([#7](https://github.com/ipfs/js-ipfs-utils/issues/7)) ([b30d7a3](https://github.com/ipfs/js-ipfs-utils/commit/b30d7a3))\n\n\n### Features\n\n* add glob-source from js-ipfs to be shared ([#9](https://github.com/ipfs/js-ipfs-utils/issues/9)) ([0a95ef8](https://github.com/ipfs/js-ipfs-utils/commit/0a95ef8))\n* add normalise input function ([#5](https://github.com/ipfs/js-ipfs-utils/issues/5)) ([b22b8de](https://github.com/ipfs/js-ipfs-utils/commit/b22b8de)), closes [#8](https://github.com/ipfs/js-ipfs-utils/issues/8)\n\n\n\n<a name=\"0.0.4\"></a>\n### [0.0.4](https://github.com/ipfs/js-ipfs-utils/compare/v0.0.3...v0.0.4) (2019-07-18)\n\n\n### Features\n\n* add globalThis polyfill ([f0c7c42](https://github.com/ipfs/js-ipfs-utils/commit/f0c7c42))\n\n\n\n<a name=\"0.0.3\"></a>\n### [0.0.3](https://github.com/ipfs/js-ipfs-utils/compare/v0.0.2...v0.0.3) (2019-05-16)\n\n\n\n<a name=\"0.0.2\"></a>\n## 0.0.2 (2019-05-16)\n\n\n### Bug Fixes\n\n* use is-buffer ([bbf5baf](https://github.com/ipfs/js-ipfs-utils/commit/bbf5baf))"
  },
  {
    "path": "packages/ipfs-core-config/LICENSE",
    "content": "This project is dual licensed under MIT and Apache-2.0.\n\nMIT: https://www.opensource.org/licenses/mit\nApache-2.0: https://www.apache.org/licenses/license-2.0\n"
  },
  {
    "path": "packages/ipfs-core-config/LICENSE-APACHE",
    "content": "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\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless 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.\n"
  },
  {
    "path": "packages/ipfs-core-config/LICENSE-MIT",
    "content": "The MIT License (MIT)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "packages/ipfs-core-config/README.md",
    "content": "> # ⛔️ DEPRECATED: [js-IPFS](https://github.com/ipfs/js-ipfs) has been superseded by [Helia](https://github.com/ipfs/helia)\n>\n> 📚 [Learn more about this deprecation](https://github.com/ipfs/js-ipfs/issues/4336) or [how to migrate](https://github.com/ipfs/helia/wiki/Migrating-from-js-IPFS)\n>\n> ⚠️ If you continue using this repo, please note that security fixes will not be provided\n\n# ipfs-core-config <!-- omit in toc -->\n\n[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech)\n[![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech)\n[![codecov](https://img.shields.io/codecov/c/github/ipfs/js-ipfs.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipfs)\n[![CI](https://img.shields.io/github/actions/workflow/status/ipfs/js-ipfs/test.yml?branch=master\\&style=flat-square)](https://github.com/ipfs/js-ipfs/actions/workflows/test.yml?query=branch%3Amaster)\n\n> Package to store node and browser specific config for ipfs-core\n\n## Table of contents <!-- omit in toc -->\n\n- [Install](#install)\n- [License](#license)\n- [Contribute](#contribute)\n\n## Install\n\n```console\n$ npm i ipfs-core-config\n```\n\n## License\n\nLicensed under either of\n\n- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / <http://www.apache.org/licenses/LICENSE-2.0>)\n- MIT ([LICENSE-MIT](LICENSE-MIT) / <http://opensource.org/licenses/MIT>)\n\n## Contribute\n\nContributions welcome! Please check out [the issues](https://github.com/ipfs/js-ipfs/issues).\n\nAlso 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.\n\nPlease 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).\n\nUnless 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.\n\n[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md)\n"
  },
  {
    "path": "packages/ipfs-core-config/package.json",
    "content": "{\n  \"name\": \"ipfs-core-config\",\n  \"version\": \"0.7.1\",\n  \"description\": \"Package to store node and browser specific config for ipfs-core\",\n  \"author\": \"Alex Potsides <alex@achingbrain.net>\",\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"homepage\": \"https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-core-config#readme\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ipfs/js-ipfs.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ipfs/js-ipfs/issues\"\n  },\n  \"engines\": {\n    \"node\": \">=16.0.0\",\n    \"npm\": \">=7.0.0\"\n  },\n  \"type\": \"module\",\n  \"types\": \"./dist/src/index.d.ts\",\n  \"typesVersions\": {\n    \"*\": {\n      \"*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ],\n      \"src/*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ]\n    }\n  },\n  \"files\": [\n    \"src\",\n    \"dist\",\n    \"!dist/test\",\n    \"!**/*.tsbuildinfo\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/src/index.d.ts\",\n      \"import\": \"./src/index.js\"\n    },\n    \"./config\": {\n      \"types\": \"./src/config.d.ts\",\n      \"browser\": \"./src/config.browser.js\",\n      \"import\": \"./src/config.js\"\n    },\n    \"./dns\": {\n      \"types\": \"./src/dns.d.ts\",\n      \"browser\": \"./src/dns.browser.js\",\n      \"import\": \"./src/dns.js\"\n    },\n    \"./init-assets\": {\n      \"types\": \"./src/init-assets.d.ts\",\n      \"browser\": \"./src/init-assets.browser.js\",\n      \"import\": \"./src/init-assets.js\"\n    },\n    \"./libp2p\": {\n      \"types\": \"./src/libp2p.d.ts\",\n      \"browser\": \"./src/libp2p.browser.js\",\n      \"import\": \"./src/libp2p.js\"\n    },\n    \"./libp2p-pubsub-routers\": {\n      \"types\": \"./src/libp2p-pubsub-routers.d.ts\",\n      \"browser\": \"./src/libp2p-pubsub-routers.browser.js\",\n      \"import\": \"./src/libp2p-pubsub-routers.js\"\n    },\n    \"./preload\": {\n      \"types\": \"./src/preload.d.ts\",\n      \"browser\": \"./src/preload.browser.js\",\n      \"import\": \"./src/preload.js\"\n    },\n    \"./repo\": {\n      \"types\": \"./src/repo.d.ts\",\n      \"browser\": \"./src/repo.browser.js\",\n      \"import\": \"./src/repo.js\"\n    }\n  },\n  \"eslintConfig\": {\n    \"extends\": \"ipfs\",\n    \"parserOptions\": {\n      \"sourceType\": \"module\"\n    }\n  },\n  \"scripts\": {\n    \"lint\": \"aegir lint\",\n    \"clean\": \"aegir clean\",\n    \"dep-check\": \"aegir dep-check -i aegir -i ipfs-core-utils\",\n    \"build\": \"aegir build\"\n  },\n  \"dependencies\": {\n    \"@chainsafe/libp2p-gossipsub\": \"^6.0.0\",\n    \"@libp2p/floodsub\": \"^6.0.0\",\n    \"@libp2p/logger\": \"^2.0.5\",\n    \"@libp2p/mdns\": \"^6.0.0\",\n    \"@libp2p/prometheus-metrics\": \"^1.0.1\",\n    \"@libp2p/tcp\": \"^6.0.2\",\n    \"@libp2p/webrtc-star\": \"^6.0.0\",\n    \"blockstore-datastore-adapter\": \"^5.0.0\",\n    \"datastore-core\": \"^8.0.1\",\n    \"datastore-fs\": \"^8.0.0\",\n    \"datastore-level\": \"^9.0.0\",\n    \"err-code\": \"^3.0.1\",\n    \"hashlru\": \"^2.3.0\",\n    \"interface-datastore\": \"^7.0.0\",\n    \"ipfs-repo\": \"^17.0.0\",\n    \"ipfs-utils\": \"^9.0.13\",\n    \"is-ipfs\": \"^8.0.0\",\n    \"it-all\": \"^2.0.0\",\n    \"it-drain\": \"^2.0.0\",\n    \"it-foreach\": \"^1.0.0\",\n    \"p-queue\": \"^7.2.0\",\n    \"uint8arrays\": \"^4.0.2\"\n  },\n  \"devDependencies\": {\n    \"aegir\": \"^37.11.0\",\n    \"ipfs-core-utils\": \"^0.18.1\"\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core-config/src/config.browser.js",
    "content": "\nexport default () => ({\n  Addresses: {\n    Swarm: [\n    ],\n    Announce: [],\n    NoAnnounce: [],\n    API: '',\n    Gateway: '',\n    RPC: '',\n    Delegates: [\n      '/dns4/node0.delegate.ipfs.io/tcp/443/https',\n      '/dns4/node1.delegate.ipfs.io/tcp/443/https',\n      '/dns4/node2.delegate.ipfs.io/tcp/443/https',\n      '/dns4/node3.delegate.ipfs.io/tcp/443/https'\n    ]\n  },\n  Discovery: {\n    MDNS: {\n      Enabled: false,\n      Interval: 10\n    },\n    webRTCStar: {\n      Enabled: true\n    }\n  },\n  Bootstrap: [\n    '/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN',\n    '/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb',\n    '/dnsaddr/bootstrap.libp2p.io/p2p/QmZa1sAxajnQjVM8WjWXoMbmPd7NsWhfKsPkErzpm9wGkp',\n    '/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa',\n    '/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt',\n    '/dns4/node0.preload.ipfs.io/tcp/443/wss/p2p/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic',\n    '/dns4/node1.preload.ipfs.io/tcp/443/wss/p2p/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6',\n    '/dns4/node2.preload.ipfs.io/tcp/443/wss/p2p/QmV7gnbW5VTcJ3oyM2Xk1rdFBJ3kTkvxc87UFGsun29STS',\n    '/dns4/node3.preload.ipfs.io/tcp/443/wss/p2p/QmY7JB6MQXhxHvq7dBDh4HpbH29v4yE9JRadAVpndvzySN'\n  ],\n  Pubsub: {\n    Enabled: true\n  },\n  Swarm: {\n    ConnMgr: {\n      LowWater: 5,\n      HighWater: 20\n    },\n    DisableNatPortMap: true\n  },\n  Routing: {\n    Type: 'dhtclient'\n  }\n})\n"
  },
  {
    "path": "packages/ipfs-core-config/src/config.js",
    "content": "\nexport default () => ({\n  Addresses: {\n    Swarm: [\n      '/ip4/0.0.0.0/tcp/4002',\n      '/ip4/127.0.0.1/tcp/4003/ws'\n    ],\n    Announce: [],\n    NoAnnounce: [],\n    API: '/ip4/127.0.0.1/tcp/5002',\n    Gateway: '/ip4/127.0.0.1/tcp/9090',\n    RPC: '/ip4/127.0.0.1/tcp/5003',\n    Delegates: [\n      '/dns4/node0.delegate.ipfs.io/tcp/443/https',\n      '/dns4/node1.delegate.ipfs.io/tcp/443/https',\n      '/dns4/node2.delegate.ipfs.io/tcp/443/https',\n      '/dns4/node3.delegate.ipfs.io/tcp/443/https'\n    ]\n  },\n  Discovery: {\n    MDNS: {\n      Enabled: true,\n      Interval: 10\n    },\n    webRTCStar: {\n      Enabled: true\n    }\n  },\n  Bootstrap: [\n    '/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ',\n    '/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN',\n    '/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb',\n    '/dnsaddr/bootstrap.libp2p.io/p2p/QmZa1sAxajnQjVM8WjWXoMbmPd7NsWhfKsPkErzpm9wGkp',\n    '/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa',\n    '/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt',\n    '/dns4/node0.preload.ipfs.io/tcp/443/wss/p2p/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic',\n    '/dns4/node1.preload.ipfs.io/tcp/443/wss/p2p/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6',\n    '/dns4/node2.preload.ipfs.io/tcp/443/wss/p2p/QmV7gnbW5VTcJ3oyM2Xk1rdFBJ3kTkvxc87UFGsun29STS',\n    '/dns4/node3.preload.ipfs.io/tcp/443/wss/p2p/QmY7JB6MQXhxHvq7dBDh4HpbH29v4yE9JRadAVpndvzySN'\n  ],\n  Pubsub: {\n    /** @type {'gossipsub'} */\n    Router: ('gossipsub'),\n    Enabled: true\n  },\n  Swarm: {\n    ConnMgr: {\n      LowWater: 50,\n      HighWater: 200\n    },\n    DisableNatPortMap: false\n  },\n  Routing: {\n    Type: 'dhtclient'\n  }\n})\n"
  },
  {
    "path": "packages/ipfs-core-config/src/dns.browser.js",
    "content": "/* eslint-env browser */\n\nimport { TLRU } from './utils/tlru.js'\nimport PQueue from 'p-queue'\nimport HTTP from 'ipfs-utils/src/http.js'\n\n// Avoid sending multiple queries for the same hostname by caching results\nconst cache = new TLRU(1000)\n// TODO: /api/v0/dns does not return TTL yet: https://github.com/ipfs/go-ipfs/issues/5884\n// However we know browsers themselves cache DNS records for at least 1 minute,\n// which acts a provisional default ttl: https://stackoverflow.com/a/36917902/11518426\nconst ttl = 60 * 1000\n\n// @ts-expect-error PQueue@6 is broken\nconst Queue = PQueue.default ? PQueue.default : PQueue\n\n// browsers limit concurrent connections per host,\n// we don't want preload calls to exhaust the limit (~6)\nconst httpQueue = new Queue({ concurrency: 4 })\n\n/**\n * @param {{ Path: string, Message: string }} response\n */\nconst ipfsPath = (response) => {\n  if (response.Path) return response.Path\n  throw new Error(response.Message)\n}\n\n/**\n * @param {string} fqdn\n * @param {object} opts\n */\nexport async function resolveDnslink (fqdn, opts) { // eslint-disable-line require-await\n  /**\n   * @param {string} fqdn\n   * @param {object} opts\n   * @param {boolean} [opts.nocache]\n   */\n  const resolve = async (fqdn, opts = {}) => {\n    // @ts-expect-error - URLSearchParams does not take boolean options, only strings\n    const searchParams = new URLSearchParams(opts)\n    searchParams.set('arg', fqdn)\n\n    // try cache first\n    const query = searchParams.toString()\n    if (!opts.nocache && cache.has(query)) {\n      const response = cache.get(query)\n      return ipfsPath(response)\n    }\n\n    // fallback to delegated DNS resolver\n    const response = await httpQueue.add(async () => {\n      // Delegated HTTP resolver sending DNSLink queries to ipfs.io\n      // TODO: replace hardcoded host with configurable DNS over HTTPS: https://github.com/ipfs/js-ipfs/issues/2212\n      const res = await HTTP.get('https://ipfs.io/api/v0/dns', { searchParams })\n      const query = new URL(res.url).search.slice(1)\n      const json = await res.json()\n      cache.set(query, json, ttl)\n\n      return json\n    })\n    return ipfsPath(response)\n  }\n\n  return resolve(fqdn, opts)\n}\n"
  },
  {
    "path": "packages/ipfs-core-config/src/dns.js",
    "content": "import dns from 'dns'\nimport * as isIPFS from 'is-ipfs'\nimport errcode from 'err-code'\nimport { promisify } from 'util'\n\nconst MAX_RECURSIVE_DEPTH = 32\n\n/**\n * @param {string} domain\n * @param {object} opts\n * @param {boolean} [opts.recursive]\n */\nexport function resolveDnslink (domain, opts) {\n  // recursive is true by default, it's set to false only if explicitly passed as argument in opts\n  const nonRecursive = !opts.recursive\n\n  /** @type {number | undefined} */\n  let depth = MAX_RECURSIVE_DEPTH\n  if (nonRecursive) {\n    depth = undefined\n  }\n\n  return recursiveResolveDnslink(domain, depth)\n}\n\n/**\n * @param {string} domain\n * @param {number} [depth]\n * @returns {Promise<string>}\n */\nasync function recursiveResolveDnslink (domain, depth) {\n  if (depth === 0) {\n    throw errcode(new Error('recursion limit exceeded'), 'ERR_DNSLINK_RECURSION_LIMIT')\n  }\n\n  let dnslinkRecord\n\n  try {\n    dnslinkRecord = await resolve(domain)\n  } catch (/** @type {any} */ err) {\n    // If the code is not ENOTFOUND or ERR_DNSLINK_NOT_FOUND or ENODATA then throw the error\n    if (err.code !== 'ENOTFOUND' && err.code !== 'ERR_DNSLINK_NOT_FOUND' && err.code !== 'ENODATA') {\n      throw err\n    }\n\n    if (domain.startsWith('_dnslink.')) {\n      // The supplied domain contains a _dnslink component\n      // Check the non-_dnslink domain\n      dnslinkRecord = await resolve(domain.replace('_dnslink.', ''))\n    } else {\n      // Check the _dnslink subdomain\n      const _dnslinkDomain = `_dnslink.${domain}`\n      // If this throws then we propagate the error\n      dnslinkRecord = await resolve(_dnslinkDomain)\n    }\n  }\n\n  const result = dnslinkRecord.replace('dnslink=', '')\n  const domainOrCID = result.split('/')[2]\n  const isIPFSCID = isIPFS.cid(domainOrCID)\n\n  if (isIPFSCID || !depth) {\n    return result\n  }\n\n  return recursiveResolveDnslink(domainOrCID, depth - 1)\n}\n\n/**\n * @param {string} domain\n */\nasync function resolve (domain) {\n  const DNSLINK_REGEX = /^dnslink=.+$/\n  const records = await promisify(dns.resolveTxt)(domain)\n  const dnslinkRecords = records.reduce((rs, r) => rs.concat(r), [])\n    .filter(record => DNSLINK_REGEX.test(record))\n\n  // we now have dns text entries as an array of strings\n  // only records passing the DNSLINK_REGEX text are included\n  if (dnslinkRecords.length === 0) {\n    throw errcode(new Error(`No dnslink records found for domain: ${domain}`), 'ERR_DNSLINK_NOT_FOUND')\n  }\n\n  return dnslinkRecords[0]\n}\n"
  },
  {
    "path": "packages/ipfs-core-config/src/index.js",
    "content": ""
  },
  {
    "path": "packages/ipfs-core-config/src/init-assets.browser.js",
    "content": "\nexport function initAssets () {}\n"
  },
  {
    "path": "packages/ipfs-core-config/src/init-assets.js",
    "content": "import all from 'it-all'\nimport assets from './init-files/init-docs/index.js'\n\n/**\n * Add the default assets to the repo.\n *\n * @param {object} arg\n * @param {import('ipfs-core-types/src/root').API<{}>[\"addAll\"]} arg.addAll\n * @param {(msg: string) => void} arg.print\n */\nexport async function initAssets ({ addAll, print }) {\n  const results = await all(addAll(assets, { preload: false }))\n  const dir = results.filter(file => file.path === 'init-docs').pop()\n\n  if (!dir) {\n    return\n  }\n\n  print('to get started, enter:\\n')\n  print(`\\tjsipfs cat /ipfs/${dir.cid}/readme\\n`)\n}\n"
  },
  {
    "path": "packages/ipfs-core-config/src/init-files/init-docs/about.js",
    "content": "export default `                  IPFS -- Inter-Planetary File system\n\nIPFS is a global, versioned, peer-to-peer filesystem. It combines good ideas\nfrom Git, BitTorrent, Kademlia, SFS, and the Web. It is like a single bit-\ntorrent swarm, exchanging git objects. IPFS provides an interface as simple\nas the HTTP web, but with permanence built in. You can also mount the world\nat /ipfs.\n\nIPFS is a protocol:\n- defines a content-addressed file system\n- coordinates content delivery\n- combines Kademlia + BitTorrent + Git\n\nIPFS is a filesystem:\n- has directories and files\n- mountable filesystem (via FUSE)\n\nIPFS is a web:\n- can be used to view documents like the web\n- files accessible via HTTP at 'http://ipfs.io/<path>'\n- browsers or extensions can learn to use 'ipfs://' directly\n- hash-addressed content guarantees authenticity\n\nIPFS is modular:\n- connection layer over any network protocol\n- routing layer\n- uses a routing layer DHT (kademlia/coral)\n- uses a path-based naming service\n- uses bittorrent-inspired block exchange\n\nIPFS uses crypto:\n- cryptographic-hash content addressing\n- block-level deduplication\n- file integrity + versioning\n- filesystem-level encryption + signing support\n\nIPFS is p2p:\n- worldwide peer-to-peer file transfers\n- completely decentralized architecture\n- **no** central point of failure\n\nIPFS is a cdn:\n- add a file to the filesystem locally, and it's now available to the world\n- caching-friendly (content-hash naming)\n- bittorrent-based bandwidth distribution\n\nIPFS has a name service:\n- IPNS, an SFS inspired name system\n- global namespace based on PKI\n- serves to build trust chains\n- compatible with other NSes\n- can map DNS, .onion, .bit, etc to IPNS\n`\n"
  },
  {
    "path": "packages/ipfs-core-config/src/init-files/init-docs/contact.js",
    "content": "export default `Come hang out in our IRC chat room if you have any questions.\n\nContact the ipfs dev team:\n- Bugs: https://github.com/ipfs/go-ipfs/issues\n- Help: irc.freenode.org/#ipfs\n- Email: dev@ipfs.io\n`\n"
  },
  {
    "path": "packages/ipfs-core-config/src/init-files/init-docs/docs/index.js",
    "content": "export default `Index\n`\n"
  },
  {
    "path": "packages/ipfs-core-config/src/init-files/init-docs/help.js",
    "content": "export default `Some helpful resources for finding your way around ipfs:\n\n- quick-start: a quick show of various ipfs features.\n- ipfs commands: a list of all commands\n- ipfs --help: every command describes itself\n- https://github.com/ipfs/go-ipfs -- the src repository\n- #ipfs on irc.freenode.org -- the community irc channel\n`\n"
  },
  {
    "path": "packages/ipfs-core-config/src/init-files/init-docs/index.js",
    "content": "import about from './about.js'\nimport contact from './contact.js'\nimport help from './help.js'\nimport quickStart from './quick-start.js'\nimport readme from './readme.js'\nimport securityNotes from './security-notes.js'\nimport docsIndex from './docs/index.js'\nimport tourIntro from './tour/0.0-intro.js'\nimport { fromString } from 'uint8arrays/from-string'\n\nexport default [{\n  content: fromString(about),\n  path: '/init-docs/about'\n}, {\n  content: fromString(contact),\n  path: '/init-docs/contact'\n}, {\n  content: fromString(help),\n  path: '/init-docs/help'\n}, {\n  content: fromString(quickStart),\n  path: '/init-docs/quick-start'\n}, {\n  content: fromString(readme),\n  path: '/init-docs/readme'\n}, {\n  content: fromString(securityNotes),\n  path: '/init-docs/security-notes'\n}, {\n  content: fromString(docsIndex),\n  path: '/init-docs/docs/index'\n}, {\n  content: fromString(tourIntro),\n  path: '/init-docs/tour/intro'\n}]\n"
  },
  {
    "path": "packages/ipfs-core-config/src/init-files/init-docs/quick-start.js",
    "content": "export default `# 0.1 - Quick Start\n\nThis is a set of short examples with minimal explanation. It is meant as\na \"quick start\". Soon, we'll write a longer tour :-)\n\n\nAdd a file to ipfs:\n\n  echo \"hello world\" >hello\n  ipfs add hello\n\n\nView it:\n\n  ipfs cat <the-hash-you-got-here>\n\n\nTry a directory:\n\n  mkdir foo\n  mkdir foo/bar\n  echo \"baz\" > foo/baz\n  echo \"baz\" > foo/bar/baz\n  ipfs add -r foo\n\n\nView things:\n\n  ipfs ls <the-hash-here>\n  ipfs ls <the-hash-here>/bar\n  ipfs cat <the-hash-here>/baz\n  ipfs cat <the-hash-here>/bar/baz\n  ipfs cat <the-hash-here>/bar\n  ipfs ls <the-hash-here>/baz\n\n\nReferences:\n\n  ipfs refs <the-hash-here>\n  ipfs refs -r <the-hash-here>\n  ipfs refs --help\n\n\nGet:\n\n  ipfs get <the-hash-here> -o foo2\n  diff foo foo2\n\n\nObjects:\n\n  ipfs object get <the-hash-here>\n  ipfs object get <the-hash-here>/foo2\n  ipfs object --help\n\n\nPin + GC:\n\n  ipfs pin add <the-hash-here>\n  ipfs repo gc\n  ipfs ls <the-hash-here>\n  ipfs pin rm <the-hash-here>\n  ipfs repo gc\n\n\nDaemon:\n\n  ipfs daemon  (in another terminal)\n  ipfs id\n\n\nNetwork:\n\n  (must be online)\n  ipfs swarm peers\n  ipfs id\n  ipfs cat <hash-of-remote-object>\n\n\nMount:\n\n  (warning: fuse is finicky!)\n  ipfs mount\n  cd /ipfs/<the-hash-here>\n  ls\n\n\nTool:\n\n  ipfs version\n  ipfs update\n  ipfs commands\n  ipfs config --help\n  open http://localhost:5001/webui\n\n\nBrowse:\n\n  webui:\n\n    http://localhost:5001/webui\n\n  video:\n\n    http://localhost:8080/ipfs/QmVc6zuAneKJzicnJpfrqCH9gSy6bz54JhcypfJYhGUFQu/play#/ipfs/QmTKZgRNwDNZwHtJSjCp6r5FYefzpULfy37JvMt9DwvXse\n\n  images:\n\n    http://localhost:8080/ipfs/QmZpc3HvfjEXvLWGQPWbHk3AjD5j8NEN4gmFN8Jmrd5g83/cs\n\n  markdown renderer app:\n\n    http://localhost:8080/ipfs/QmX7M9CiYXjVeFnkfVGf3y5ixTZ2ACeSGyL1vBJY1HvQPp/mdown\n`\n"
  },
  {
    "path": "packages/ipfs-core-config/src/init-files/init-docs/readme.js",
    "content": "export default `Hello and Welcome to IPFS!\n\n██╗██████╗ ███████╗███████╗\n██║██╔══██╗██╔════╝██╔════╝\n██║██████╔╝█████╗  ███████╗\n██║██╔═══╝ ██╔══╝  ╚════██║\n██║██║     ██║     ███████║\n╚═╝╚═╝     ╚═╝     ╚══════╝\n\nIf you're seeing this, you have successfully installed\nIPFS and are now interfacing with the ipfs merkledag!\n\n -------------------------------------------------------\n| Warning:                                              |\n|   This is alpha software. Use at your own discretion! |\n|   Much is missing or lacking polish. There are bugs.  |\n|   Not yet secure. Read the security notes for more.   |\n -------------------------------------------------------\n\nCheck out some of the other files in this directory:\n\n  ./about\n  ./help\n  ./quick-start     <-- usage examples\n  ./readme          <-- this file\n  ./security-notes\n`\n"
  },
  {
    "path": "packages/ipfs-core-config/src/init-files/init-docs/security-notes.js",
    "content": "export default `                    IPFS Alpha Security Notes\n\nWe try hard to ensure our system is safe and robust, but all software\nhas bugs, especially new software. This distribution is meant to be an\nalpha preview, don't use it for anything mission critical.\n\nPlease note the following:\n\n- This is alpha software and has not been audited. It is our goal\n  to conduct a proper security audit once we close in on a 1.0 release.\n\n- ipfs is a networked program, and may have serious undiscovered\n  vulnerabilities. It is written in Go, and we do not execute any\n  user provided data. But please point any problems out to us in a\n  github issue, or email security@ipfs.io privately.\n\n- security@ipfs.io GPG key:\n  - 4B9665FB 92636D17 7C7A86D3 50AAE8A9 59B13AF3\n  - https://pgp.mit.edu/pks/lookup?op=get&search=0x50AAE8A959B13AF3\n\n- ipfs uses encryption for all communication, but it's NOT PROVEN SECURE\n  YET!  It may be totally broken. For now, the code is included to make\n  sure we benchmark our operations with encryption in mind. In the future,\n  there will be an \"unsafe\" mode for high performance intranet apps.\n  If this is a blocking feature for you, please contact us.\n`\n"
  },
  {
    "path": "packages/ipfs-core-config/src/init-files/init-docs/tour/0.0-intro.js",
    "content": "export default `WIP\n\n# 0.0 - Introduction\n\nWelcome to IPFS! This tour will guide you through a few of the\nfeatures of this tool, and the most common commands. Then, it will\nimmerse you into the world of merkledags and the amazing things\nyou can do with them.\n\n\nThis tour has many parts, and can be taken in different sequences.\nDifferent people learn different ways, so choose your own adventure:\n\n  To start with the concepts, try:\n  - The Merkle DAG\n  - Data Structures on the Merkle DAG\n  - Representing Files with unixfs\n  - add, cat, ls, refs\n  ...\n\n\n  To start with the examples, try:\n  - add, cat, ls, refs\n  - Representing Files with unixfs\n  - Data Structures on the Merkle DAG\n  - The Merkle DAG\n  ...\n\n\n  To start with the network, try:\n  - IPFS Nodes\n  - Running the daemon\n  - The Swarm\n  - The Web\n`\n"
  },
  {
    "path": "packages/ipfs-core-config/src/libp2p-pubsub-routers.browser.js",
    "content": "import { gossipsub } from '@chainsafe/libp2p-gossipsub'\n\n/** @typedef {import('@libp2p/interface-pubsub').PubSub} PubSub */\n\n/** @type {() => Record<string, (components: any) => PubSub>}>} */\nexport const routers = () => ({\n  gossipsub: gossipsub({\n    fallbackToFloodsub: true,\n    emitSelf: true,\n    maxInboundStreams: 64,\n    maxOutboundStreams: 128\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-core-config/src/libp2p-pubsub-routers.js",
    "content": "import { gossipsub } from '@chainsafe/libp2p-gossipsub'\nimport { floodsub } from '@libp2p/floodsub'\n\n/** @typedef {import('@libp2p/interface-pubsub').PubSub} PubSub */\n\n/** @type {() => Record<string, (components: any) => PubSub>}>} */\nexport const routers = () => ({\n  gossipsub: gossipsub({\n    fallbackToFloodsub: true,\n    emitSelf: true,\n    maxInboundStreams: 64,\n    maxOutboundStreams: 128\n  }),\n  floodsub: floodsub({\n    emitSelf: true\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-core-config/src/libp2p.browser.js",
    "content": "import { webRTCStar } from '@libp2p/webrtc-star'\n\nexport function libp2pConfig () {\n  const webRtcStar = webRTCStar()\n\n  /** @type {import('libp2p').Libp2pOptions} */\n  const options = {\n    transports: [\n      webRtcStar.transport\n    ],\n    peerDiscovery: [\n      webRtcStar.discovery\n    ],\n    connectionManager: {\n      maxParallelDials: 150, // 150 total parallel multiaddr dials\n      maxDialsPerPeer: 4, // Allow 4 multiaddrs to be dialed per peer in parallel\n      dialTimeout: 10e3, // 10 second dial timeout per peer dial\n      autoDial: true\n    },\n    nat: {\n      enabled: false\n    }\n  }\n\n  return options\n}\n"
  },
  {
    "path": "packages/ipfs-core-config/src/libp2p.js",
    "content": "import { tcp } from '@libp2p/tcp'\nimport { mdns } from '@libp2p/mdns'\nimport os from 'os'\nimport { prometheusMetrics } from '@libp2p/prometheus-metrics'\n\nexport function libp2pConfig () {\n  /** @type {import('libp2p').Libp2pOptions} */\n  const options = {\n    transports: [\n      tcp()\n    ],\n    peerDiscovery: [\n      mdns()\n    ],\n    connectionManager: {\n      maxParallelDials: 150, // 150 total parallel multiaddr dials\n      maxDialsPerPeer: 4, // Allow 4 multiaddrs to be dialed per peer in parallel\n      dialTimeout: 10e3, // 10 second dial timeout per peer dial\n      autoDial: true\n    },\n    nat: {\n      enabled: true,\n      description: `ipfs@${os.hostname()}`\n    }\n  }\n\n  if (process.env.IPFS_MONITORING != null) {\n    options.metrics = prometheusMetrics()\n  }\n\n  return options\n}\n"
  },
  {
    "path": "packages/ipfs-core-config/src/preload.browser.js",
    "content": "/* eslint-env browser */\n\nimport HTTP from 'ipfs-utils/src/http.js'\nimport { logger } from '@libp2p/logger'\nimport PQueue from 'p-queue'\n\nconst log = logger('ipfs:preload')\n\n// @ts-expect-error PQueue@6 is broken\nconst Queue = PQueue.default ? PQueue.default : PQueue\n\n// browsers limit concurrent connections per host,\n// we don't want preload calls to exhaust the limit (~6)\nconst httpQueue = new Queue({ concurrency: 4 })\n\n/**\n * @param {string} url\n * @param {import('ipfs-core-types/src/utils').AbortOptions} options\n */\nexport function preload (url, options = {}) {\n  log(url)\n\n  return httpQueue.add(async () => {\n    const res = await HTTP.post(url, { signal: options.signal })\n\n    // @ts-expect-error\n    const reader = res.body.getReader()\n\n    try {\n      while (true) {\n        const { done } = await reader.read()\n        if (done) return\n        // Read to completion but do not cache\n      }\n    } finally {\n      reader.releaseLock()\n    }\n  })\n}\n"
  },
  {
    "path": "packages/ipfs-core-config/src/preload.js",
    "content": "import HTTP from 'ipfs-utils/src/http.js'\nimport { logger } from '@libp2p/logger'\nimport drain from 'it-drain'\n\nconst log = logger('ipfs:preload')\n\n/**\n * @param {string} url\n * @param {import('ipfs-core-types/src/utils').AbortOptions} options\n */\nexport async function preload (url, options = {}) {\n  log(url)\n\n  const res = await HTTP.post(url, { signal: options.signal })\n\n  if (res.body) {\n    // Read to completion but do not cache\n    // @ts-expect-error\n    await drain(res.body)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core-config/src/repo.browser.js",
    "content": "import { createRepo as create } from 'ipfs-repo'\nimport { LevelDatastore } from 'datastore-level'\nimport { BlockstoreDatastoreAdapter } from 'blockstore-datastore-adapter'\nimport { MemoryLock } from 'ipfs-repo/locks/memory'\n\n/**\n * @typedef {import('ipfs-repo-migrations').ProgressCallback} MigrationProgressCallback\n */\n\n/**\n * @param {(...args: any[]) => void} print\n * @param {import('ipfs-core-utils/multicodecs').Multicodecs} codecs\n * @param {object} options\n * @param {string} [options.path]\n * @param {boolean} [options.autoMigrate]\n * @param {MigrationProgressCallback} [options.onMigrationProgress]\n * @param {number} [options.peerStoreCacheSize]\n */\nexport function createRepo (print, codecs, options) {\n  const repoPath = options.path || 'ipfs'\n\n  return create(repoPath, (codeOrName) => codecs.getCodec(codeOrName), {\n    root: new LevelDatastore(repoPath, {\n      prefix: '',\n      version: 2\n    }),\n    blocks: new BlockstoreDatastoreAdapter(\n      new LevelDatastore(`${repoPath}/blocks`, {\n        prefix: '',\n        version: 2\n      })\n    ),\n    datastore: new LevelDatastore(`${repoPath}/datastore`, {\n      prefix: '',\n      version: 2\n    }),\n    keys: new LevelDatastore(`${repoPath}/keys`, {\n      prefix: '',\n      version: 2\n    }),\n    pins: new LevelDatastore(`${repoPath}/pins`, {\n      prefix: '',\n      version: 2\n    })\n  }, {\n    autoMigrate: options.autoMigrate,\n    onMigrationProgress: options.onMigrationProgress || print,\n    repoLock: MemoryLock\n  })\n}\n"
  },
  {
    "path": "packages/ipfs-core-config/src/repo.js",
    "content": "import os from 'os'\nimport { createRepo as create } from 'ipfs-repo'\nimport path from 'path'\nimport { FsDatastore } from 'datastore-fs'\nimport { LevelDatastore } from 'datastore-level'\nimport { BlockstoreDatastoreAdapter } from 'blockstore-datastore-adapter'\nimport { ShardingDatastore } from 'datastore-core/sharding'\nimport { NextToLast } from 'datastore-core/shard'\nimport { FSLock } from 'ipfs-repo/locks/fs'\nimport { MountDatastore } from 'datastore-core/mount'\nimport { Key } from 'interface-datastore/key'\nimport { LRUDatastore } from './utils/lru-datastore.js'\n\n/**\n * @typedef {import('ipfs-repo-migrations').ProgressCallback} MigrationProgressCallback\n */\n\n/**\n * @param {(...args: any[]) => void} print\n * @param {import('ipfs-core-utils/multicodecs').Multicodecs} codecs\n * @param {object} options\n * @param {string} [options.path]\n * @param {boolean} [options.autoMigrate]\n * @param {MigrationProgressCallback} [options.onMigrationProgress]\n * @param {number} [options.peerStoreCacheSize]\n */\nexport function createRepo (print, codecs, options = {}) {\n  const repoPath = options.path || path.join(os.homedir(), '.jsipfs')\n  const peerStoreCacheSize = options.peerStoreCacheSize || 1024\n  /**\n   * @type {number}\n   */\n  let lastMigration\n\n  /**\n   * @type {MigrationProgressCallback}\n   */\n  const onMigrationProgress = options.onMigrationProgress || function (version, percentComplete, message) {\n    if (version !== lastMigration) {\n      lastMigration = version\n\n      print(`Migrating repo from v${version - 1} to v${version}`)\n    }\n\n    print(`${percentComplete.toString().padStart(6, ' ')}% ${message}`)\n  }\n\n  const defaultDatastore = new LevelDatastore(`${repoPath}/datastore`)\n\n  return create(repoPath, (codeOrName) => codecs.getCodec(codeOrName), {\n    root: new FsDatastore(repoPath, {\n      extension: ''\n    }),\n    blocks: new BlockstoreDatastoreAdapter(\n      new ShardingDatastore(\n        new FsDatastore(`${repoPath}/blocks`, {\n          extension: '.data'\n        }),\n        new NextToLast(2)\n      )\n    ),\n    datastore: new MountDatastore([{\n      prefix: new Key('/peers'),\n      datastore: new LRUDatastore(peerStoreCacheSize, defaultDatastore)\n    }, {\n      prefix: new Key('/'),\n      datastore: defaultDatastore\n    }]),\n    keys: new FsDatastore(`${repoPath}/keys`),\n    pins: new LevelDatastore(`${repoPath}/pins`)\n  }, {\n    autoMigrate: options.autoMigrate != null ? options.autoMigrate : true,\n    onMigrationProgress: onMigrationProgress,\n    repoLock: FSLock\n  })\n}\n"
  },
  {
    "path": "packages/ipfs-core-config/src/utils/lru-datastore.js",
    "content": "import hashlru from 'hashlru'\nimport { BaseDatastore } from 'datastore-core/base'\nimport each from 'it-foreach'\n\n/**\n * @typedef {import('interface-datastore').Datastore} Datastore\n * @typedef {import('interface-datastore/key').Key} Key\n * @typedef {import('interface-datastore').Options} Options\n * @typedef {import('interface-datastore').Pair} Pair\n * @typedef {import('interface-datastore').Batch} Batch\n * @typedef {import('interface-datastore').Query} Query\n * @typedef {import('interface-datastore').KeyQuery} KeyQuery\n * @typedef {import('interface-store').AwaitIterable<Pair>} AwaitIterablePair\n * @typedef {import('interface-store').AwaitIterable<Key>} AwaitIterableKey\n */\n\n/**\n * A datastore with an internal LRU cache\n */\nexport class LRUDatastore extends BaseDatastore {\n  /**\n   * Creates an instance of TLRU.\n   *\n   * @param {number} maxSize\n   * @param {Datastore} datastore\n   */\n  constructor (maxSize, datastore) {\n    super()\n    this.lru = hashlru(maxSize)\n    this.child = datastore\n  }\n\n  /**\n   * @returns {Promise<void>}\n   */\n  open () {\n    return this.child.open()\n  }\n\n  /**\n   * @returns {Promise<void>}\n   */\n  close () {\n    return this.child.close()\n  }\n\n  /**\n   * @param {Key} key\n   * @param {Uint8Array} val\n   * @param {Options} [options]\n   */\n  put (key, val, options) {\n    this.lru.set(key.toString(), val)\n\n    return this.child.put(key, val, options)\n  }\n\n  /**\n   * @param {Key} key\n   * @param {Options} [options]\n   */\n  async get (key, options) {\n    if (this.lru.has(key.toString())) {\n      return this.lru.get(key.toString())\n    }\n\n    return this.child.get(key, options)\n  }\n\n  /**\n   * @param {Key} key\n   * @param {Options} [options]\n   */\n  async has (key, options) {\n    if (this.lru.has(key.toString())) {\n      return true\n    }\n\n    return this.child.has(key, options)\n  }\n\n  /**\n   * @param {Key} key\n   * @param {Options} [options]\n   */\n  delete (key, options) {\n    this.lru.remove(key.toString())\n\n    return this.child.delete(key, options)\n  }\n\n  /**\n   * @param {AwaitIterablePair} source\n   * @param {Options} [options]\n   */\n  async * putMany (source, options) {\n    yield * this.child.putMany(each(source, (pair) => {\n      this.lru.set(pair.key.toString(), pair.value)\n    }), options)\n  }\n\n  /**\n   * @param {AwaitIterableKey} source\n   * @param {Options} [options]\n   */\n  async * getMany (source, options) {\n    for await (const key of source) {\n      if (this.lru.has(key.toString())) {\n        yield this.lru.get(key.toString())\n      }\n\n      yield this.child.get(key, options)\n    }\n  }\n\n  /**\n   * @param {AwaitIterableKey} source\n   * @param {Options} [options]\n   */\n  async * deleteMany (source, options) {\n    yield * this.child.deleteMany(each(source, (key) => {\n      this.lru.remove(key.toString())\n    }), options)\n  }\n\n  /**\n   * @returns {Batch}\n   */\n  batch () {\n    return this.child.batch()\n  }\n\n  /**\n   * @param {Query} q\n   * @param {Options} [options]\n   */\n  async * query (q, options) {\n    yield * this.child.query(q, options)\n  }\n\n  /**\n   * @param {KeyQuery} q\n   * @param {Options} [options]\n   */\n  async * queryKeys (q, options) {\n    yield * this.child.queryKeys(q, options)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core-config/src/utils/tlru.js",
    "content": "import hashlru from 'hashlru'\n\n/**\n * Time Aware Least Recent Used Cache\n *\n * @see https://arxiv.org/pdf/1801.00390\n * @todo move this to ipfs-utils or it's own package\n *\n * @template T\n * @class TLRU\n */\nexport class TLRU {\n  /**\n   * Creates an instance of TLRU.\n   *\n   * @param {number} maxSize\n   */\n  constructor (maxSize) {\n    this.lru = hashlru(maxSize)\n  }\n\n  /**\n   * Get the value from the a key\n   *\n   * @param {string} key\n   * @returns {T|undefined}\n   * @memberof TLoRU\n   */\n  get (key) {\n    const value = this.lru.get(key)\n    if (value) {\n      if ((value.expire) && (value.expire < Date.now())) {\n        this.lru.remove(key)\n        return undefined\n      }\n      return value.value\n    }\n    return undefined\n  }\n\n  /**\n   * Set a key value pair\n   *\n   * @param {string} key\n   * @param {T} value\n   * @param {number} ttl - in miliseconds\n   * @returns {void}\n   */\n  set (key, value, ttl) {\n    this.lru.set(key, { value, expire: Date.now() + ttl })\n  }\n\n  /**\n   * Find if the cache has the key\n   *\n   * @param {string} key\n   * @returns {boolean}\n   */\n  has (key) {\n    const value = this.get(key)\n    if (value) {\n      return true\n    }\n    return false\n  }\n\n  /**\n   * Remove key\n   *\n   * @param {string} key\n   */\n  remove (key) {\n    this.lru.remove(key)\n  }\n\n  /**\n   * Clears the cache\n   *\n   * @memberof TLRU\n   */\n  clear () {\n    this.lru.clear()\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core-config/tsconfig.json",
    "content": "{\n  \"extends\": \"aegir/src/config/tsconfig.aegir.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"emitDeclarationOnly\": true\n  },\n  \"include\": [\n    \"src\",\n    \"test\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../ipfs-core-utils\"\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/ipfs-core-types/CHANGELOG.md",
    "content": "# Change Log\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n\n### [0.14.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-types-v0.14.0...ipfs-core-types-v0.14.1) (2023-05-25)\n\n\n### Bug Fixes\n\n* add deprecation notice to readmes ([#4362](https://www.github.com/ipfs/js-ipfs/issues/4362)) ([7b79c1b](https://www.github.com/ipfs/js-ipfs/commit/7b79c1b8df5c818dc124b346ea28330455732d5c))\n\n## [0.14.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-types-v0.13.0...ipfs-core-types-v0.14.0) (2023-01-11)\n\n\n### ⚠ BREAKING CHANGES\n\n* update multiformats to v11.x.x and related depenendcies (#4277)\n\n### Bug Fixes\n\n* update multiformats to v11.x.x and related depenendcies ([#4277](https://www.github.com/ipfs/js-ipfs/issues/4277)) ([521c84a](https://www.github.com/ipfs/js-ipfs/commit/521c84a958b04d61702577a5adce28519c1b2a3b))\n* use aegir to publish RCs ([#4284](https://www.github.com/ipfs/js-ipfs/issues/4284)) ([6d90cbf](https://www.github.com/ipfs/js-ipfs/commit/6d90cbf321a7dbf4b1084ba20f0c514dc08d8d0a))\n\n## [0.13.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-types-v0.12.1...ipfs-core-types-v0.13.0) (2022-10-24)\n\n\n### ⚠ BREAKING CHANGES\n\n* ipfs is now bundled with libp2p@0.40.x which has different config\n\n### Features\n\n* upgrade libp2p to 0.40.x ([#4237](https://www.github.com/ipfs/js-ipfs/issues/4237)) ([0cee4a4](https://www.github.com/ipfs/js-ipfs/commit/0cee4a4c55767022584dcbade0b0b9b43326f9c9))\n\n### [0.12.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-types-v0.12.0...ipfs-core-types-v0.12.1) (2022-09-21)\n\n\n### Bug Fixes\n\n* update @multiformats/multiadd to 11.0.0 ([2a830bf](https://www.github.com/ipfs/js-ipfs/commit/2a830bf58a5929fcce51dede871c99f62192fbda))\n\n## [0.12.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-types-v0.11.1...ipfs-core-types-v0.12.0) (2022-09-06)\n\n\n### ⚠ BREAKING CHANGES\n\n* update to libp2p@0.38.x (#4151)\n\n### deps\n\n* update to libp2p@0.38.x ([#4151](https://www.github.com/ipfs/js-ipfs/issues/4151)) ([39dbf70](https://www.github.com/ipfs/js-ipfs/commit/39dbf708ec31b263115e44f420651fa4e056a89e))\n\n### [0.11.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-types-v0.11.0...ipfs-core-types-v0.11.1) (2022-06-22)\n\n\n### Bug Fixes\n\n* use default ws filters instead of connecting to everything ([#4142](https://www.github.com/ipfs/js-ipfs/issues/4142)) ([7be50bd](https://www.github.com/ipfs/js-ipfs/commit/7be50bd157b984d4607545bb78d22cd33de933fa)), closes [#4141](https://www.github.com/ipfs/js-ipfs/issues/4141)\n\n## [0.11.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-types-v0.10.3...ipfs-core-types-v0.11.0) (2022-05-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* This module is now ESM only and there return types of some methods have changed\n\n### Features\n\n* update to libp2p 0.37.x ([#4092](https://www.github.com/ipfs/js-ipfs/issues/4092)) ([74aee8b](https://www.github.com/ipfs/js-ipfs/commit/74aee8b3d78f233c3199a3e9a6c0ac628a31a433))\n\n### [0.10.3](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-types-v0.10.2...ipfs-core-types-v0.10.3) (2022-04-20)\n\n\n### Bug Fixes\n\n* BWOptions.interval accepts number|string ([#4061](https://www.github.com/ipfs/js-ipfs/issues/4061)) ([e90b8f1](https://www.github.com/ipfs/js-ipfs/commit/e90b8f13dedace9fa4163e1ea5a61f8469491542)), closes [#3985](https://www.github.com/ipfs/js-ipfs/issues/3985)\n\n### [0.10.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-types-v0.10.1...ipfs-core-types-v0.10.2) (2022-03-01)\n\n\n### Bug Fixes\n\n* add deps used in ipfs-core-types ([#4058](https://www.github.com/ipfs/js-ipfs/issues/4058)) ([df1bd1b](https://www.github.com/ipfs/js-ipfs/commit/df1bd1bbf151543afb4ce41516248def77e8a225))\n* missing files on publish ([#4056](https://www.github.com/ipfs/js-ipfs/issues/4056)) ([125d42b](https://www.github.com/ipfs/js-ipfs/commit/125d42ba72f905bf95b66489c1b593cbf0a623cb)), closes [#3976](https://www.github.com/ipfs/js-ipfs/issues/3976)\n\n### [0.10.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-types-v0.10.0...ipfs-core-types-v0.10.1) (2022-02-06)\n\n\n### Bug Fixes\n\n* **dag:** replace custom dag walk with multiformats/traversal ([#3950](https://www.github.com/ipfs/js-ipfs/issues/3950)) ([596b1f4](https://www.github.com/ipfs/js-ipfs/commit/596b1f48a014083b1736e4ad7e746c652d2583b1))\n\n## [0.10.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-types-v0.9.0...ipfs-core-types-v0.10.0) (2022-01-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* the `ToJSON` type has been removed\n* peerstore methods are now all async, the repo is migrated to v12\n\n### Features\n\n* libp2p async peerstore ([#4018](https://www.github.com/ipfs/js-ipfs/issues/4018)) ([a6b201a](https://www.github.com/ipfs/js-ipfs/commit/a6b201af2c3697430ab0ebe002dd573d185f1ac0))\n\n\n### Bug Fixes\n\n* remove ToJSON type ([#4031](https://www.github.com/ipfs/js-ipfs/issues/4031)) ([6cb3a87](https://www.github.com/ipfs/js-ipfs/commit/6cb3a87e6010c36f8f484bf65d5c009c5e58994b))\n\n\n## [0.9.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core-types@0.8.4...ipfs-core-types@0.9.0) (2021-12-15)\n\n\n### Features\n\n* dht client ([#3947](https://github.com/ipfs/js-ipfs/issues/3947)) ([62d8ecb](https://github.com/ipfs/js-ipfs/commit/62d8ecbc723e693a2544e69172d99c576d187c23))\n* update DAG API to match go-ipfs@0.10 changes ([#3917](https://github.com/ipfs/js-ipfs/issues/3917)) ([38c01be](https://github.com/ipfs/js-ipfs/commit/38c01be03b4fd5f401cd9b698cfdb4237d835b01))\n\n\n### BREAKING CHANGES\n\n* `ipfs.dag.put` no longer accepts a `format` arg, it is now `storeCodec` and `inputCodec`.  `'json'` has become `'dag-json'`, `'cbor'` has become `'dag-cbor'` and so on\n* The DHT API has been refactored to return async iterators of query events\n\n\n### [0.8.4](https://github.com/ipfs/js-ipfs/compare/ipfs-core-types@0.8.3...ipfs-core-types@0.8.4) (2021-11-24)\n\n**Note:** Version bump only for package ipfs-core-types\n\n\n\n\n\n### [0.8.3](https://github.com/ipfs/js-ipfs/compare/ipfs-core-types@0.8.2...ipfs-core-types@0.8.3) (2021-11-19)\n\n**Note:** Version bump only for package ipfs-core-types\n\n\n\n\n\n### [0.8.2](https://github.com/ipfs/js-ipfs/compare/ipfs-core-types@0.8.1...ipfs-core-types@0.8.2) (2021-11-12)\n\n**Note:** Version bump only for package ipfs-core-types\n\n\n\n\n\n### [0.8.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core-types@0.8.0...ipfs-core-types@0.8.1) (2021-09-28)\n\n**Note:** Version bump only for package ipfs-core-types\n\n\n\n\n\n## [0.8.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core-types@0.7.3...ipfs-core-types@0.8.0) (2021-09-24)\n\n\n### Features\n\n* switch to esm ([#3879](https://github.com/ipfs/js-ipfs/issues/3879)) ([9a40109](https://github.com/ipfs/js-ipfs/commit/9a40109632e5b4837eb77a2f57dbc77fbf1fe099))\n\n\n### BREAKING CHANGES\n\n* There are no default exports and everything is now dual published as ESM/CJS\n\n\n\n\n\n### [0.7.3](https://github.com/ipfs/js-ipfs/compare/ipfs-core-types@0.7.2...ipfs-core-types@0.7.3) (2021-09-17)\n\n\n### Bug Fixes\n\n* use Key.asKey instead of instanceOf ([#3877](https://github.com/ipfs/js-ipfs/issues/3877)) ([e3acf9b](https://github.com/ipfs/js-ipfs/commit/e3acf9b67765c166c53f923a9e00430cdf46935b)), closes [#3852](https://github.com/ipfs/js-ipfs/issues/3852)\n\n\n\n\n\n### [0.7.2](https://github.com/ipfs/js-ipfs/compare/ipfs-core-types@0.7.1...ipfs-core-types@0.7.2) (2021-09-17)\n\n**Note:** Version bump only for package ipfs-core-types\n\n\n\n\n\n### [0.7.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core-types@0.7.0...ipfs-core-types@0.7.1) (2021-09-02)\n\n\n### Bug Fixes\n\n* declare types in .ts files ([#3840](https://github.com/ipfs/js-ipfs/issues/3840)) ([eba5fe6](https://github.com/ipfs/js-ipfs/commit/eba5fe6832858107b3e1ae02c99de674622f12b4))\n* remove use of instanceof for CID class ([#3847](https://github.com/ipfs/js-ipfs/issues/3847)) ([ebbb12d](https://github.com/ipfs/js-ipfs/commit/ebbb12db523c53ce8e4ddae5266cd9acb3504431))\n\n\n\n\n\n## [0.7.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core-types@0.6.1...ipfs-core-types@0.7.0) (2021-08-11)\n\n\n### Bug Fixes\n\n* return rate in/out as number ([#3798](https://github.com/ipfs/js-ipfs/issues/3798)) ([2f3df7a](https://github.com/ipfs/js-ipfs/commit/2f3df7a70fe94d6bdf20947854dc9d0b88cb759a)), closes [#3782](https://github.com/ipfs/js-ipfs/issues/3782)\n* typescript errors ([#3781](https://github.com/ipfs/js-ipfs/issues/3781)) ([79f661e](https://github.com/ipfs/js-ipfs/commit/79f661ef0da859e1fd8ef979df3fb1303d384b8d))\n\n\n### Features\n\n* make ipfs.get output tarballs ([#3785](https://github.com/ipfs/js-ipfs/issues/3785)) ([1ad6001](https://github.com/ipfs/js-ipfs/commit/1ad60018d39d5b46c484756631e30e1989fd8eba))\n\n\n### BREAKING CHANGES\n\n* rateIn/rateOut are returned as numbers\n* the output type of `ipfs.get` has changed and the `recursive` option has been removed from `ipfs.ls` since it was not supported everywhere\n\n\n\n\n\n### [0.6.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core-types@0.6.0...ipfs-core-types@0.6.1) (2021-07-30)\n\n\n### Bug Fixes\n\n* typo in 'multiformats' type defs ([#3778](https://github.com/ipfs/js-ipfs/issues/3778)) ([1bf35f8](https://github.com/ipfs/js-ipfs/commit/1bf35f8a1622dea1e88bfbd701205df4f96998b1))\n\n\n\n\n\n## [0.6.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core-types@0.5.2...ipfs-core-types@0.6.0) (2021-07-27)\n\n\n### Bug Fixes\n\n* export ipfs http client type and use option extension for client ([#3763](https://github.com/ipfs/js-ipfs/issues/3763)) ([31bddd4](https://github.com/ipfs/js-ipfs/commit/31bddd40ab85848cd283ec66001fb7555b4f2d88)), closes [#3749](https://github.com/ipfs/js-ipfs/issues/3749) [#3736](https://github.com/ipfs/js-ipfs/issues/3736)\n* **ipfs-core-types:** wrong extension ([#3753](https://github.com/ipfs/js-ipfs/issues/3753)) ([4bad1c6](https://github.com/ipfs/js-ipfs/commit/4bad1c61f5946e32cf75196cd2c45c5316500b0f))\n\n\n### Features\n\n* implement dag import/export ([#3728](https://github.com/ipfs/js-ipfs/issues/3728)) ([700765b](https://github.com/ipfs/js-ipfs/commit/700765be2634fa5d2d71d8b87cf68c9cd328d2c4)), closes [#2953](https://github.com/ipfs/js-ipfs/issues/2953) [#2745](https://github.com/ipfs/js-ipfs/issues/2745)\n* upgrade to the new multiformats ([#3556](https://github.com/ipfs/js-ipfs/issues/3556)) ([d13d15f](https://github.com/ipfs/js-ipfs/commit/d13d15f022a87d04a35f0f7822142f9cb898479c))\n\n\n### BREAKING CHANGES\n\n* ipld-formats no longer supported, use multiformat BlockCodecs instead\n\nCo-authored-by: Rod Vagg <rod@vagg.org>\nCo-authored-by: achingbrain <alex@achingbrain.net>\n\n\n\n\n\n### [0.5.2](https://github.com/ipfs/js-ipfs/compare/ipfs-core-types@0.5.1...ipfs-core-types@0.5.2) (2021-06-18)\n\n**Note:** Version bump only for package ipfs-core-types\n\n\n\n\n\n### [0.5.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core-types@0.5.0...ipfs-core-types@0.5.1) (2021-06-05)\n\n\n### Bug Fixes\n\n* add onError to pubsub.subscribe types ([#3706](https://github.com/ipfs/js-ipfs/issues/3706)) ([d910aea](https://github.com/ipfs/js-ipfs/commit/d910aead8c8be6798cf838245511331b3f69634c)), closes [#3468](https://github.com/ipfs/js-ipfs/issues/3468)\n\n\n\n\n\n## [0.5.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core-types@0.4.0...ipfs-core-types@0.5.0) (2021-05-26)\n\n\n### Features\n\n* allow passing the id of a network peer to ipfs.id ([#3386](https://github.com/ipfs/js-ipfs/issues/3386)) ([00fd709](https://github.com/ipfs/js-ipfs/commit/00fd709a7b71e7cf354ea452ebce460dd7375d34))\n\n\n\n\n\n## [0.4.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core-types@0.3.1...ipfs-core-types@0.4.0) (2021-05-10)\n\n\n### Bug Fixes\n\n* fix types ([#3662](https://github.com/ipfs/js-ipfs/issues/3662)) ([0fe8892](https://github.com/ipfs/js-ipfs/commit/0fe8892361180dab53ed3c3b006479b32a792d44))\n* loosen input type for swarm.connect and swarm.disconnect ([#3673](https://github.com/ipfs/js-ipfs/issues/3673)) ([46618c7](https://github.com/ipfs/js-ipfs/commit/46618c795bf5363ba3186645640fb81349231db7)), closes [#3638](https://github.com/ipfs/js-ipfs/issues/3638)\n* mark ipld options as partial ([#3669](https://github.com/ipfs/js-ipfs/issues/3669)) ([f98af8e](https://github.com/ipfs/js-ipfs/commit/f98af8ed24784929898bb5d33a64dc442c77074d))\n* update ipfs repo ([#3671](https://github.com/ipfs/js-ipfs/issues/3671)) ([9029ee5](https://github.com/ipfs/js-ipfs/commit/9029ee591fa74ea65c9600f2d249897e933416fa))\n* update types after feedback from ceramic ([#3657](https://github.com/ipfs/js-ipfs/issues/3657)) ([0ddbb1b](https://github.com/ipfs/js-ipfs/commit/0ddbb1b1deb4e40dac3e365d7f98a5f174c2ce8f)), closes [#3640](https://github.com/ipfs/js-ipfs/issues/3640)\n\n\n### chore\n\n* upgrade deps with new typedefs ([#3550](https://github.com/ipfs/js-ipfs/issues/3550)) ([a418a52](https://github.com/ipfs/js-ipfs/commit/a418a521574c878d7aabd0ad2fd8d516908a3756))\n\n\n### BREAKING CHANGES\n\n* all core api methods now have types, some method signatures have changed, named exports are now used by the http, grpc and ipfs client modules\n\n\n\n\n\n### [0.3.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core-types@0.3.0...ipfs-core-types@0.3.1) (2021-03-09)\n\n\n### Bug Fixes\n\n* bitswap related typedefs ([#3580](https://github.com/ipfs/js-ipfs/issues/3580)) ([1af82d1](https://github.com/ipfs/js-ipfs/commit/1af82d1ca4bd447d8c162e1fd8da8b043131969c))\n* update to new aegir ([#3528](https://github.com/ipfs/js-ipfs/issues/3528)) ([49f7880](https://github.com/ipfs/js-ipfs/commit/49f78807d7e26483bd926b45cc7e0f797d77e41b))\n\n\n\n\n\n## [0.3.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core-types@0.2.1...ipfs-core-types@0.3.0) (2021-02-01)\n\n\n### chore\n\n* update deps ([#3514](https://github.com/ipfs/js-ipfs/issues/3514)) ([061d77c](https://github.com/ipfs/js-ipfs/commit/061d77cc03f40af5a3bc3590481e1e5836e7f0d8))\n\n\n### Features\n\n* support  remote pinning services in ipfs-http-client ([#3293](https://github.com/ipfs/js-ipfs/issues/3293)) ([ba240fd](https://github.com/ipfs/js-ipfs/commit/ba240fdf93edc88028315483240d7822a7ca88ed))\n\n\n### BREAKING CHANGES\n\n* ipfs-repo upgrade requires repo migration to v10\n\n\n\n\n\n### [0.2.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core-types@0.2.0...ipfs-core-types@0.2.1) (2021-01-22)\n\n\n### Bug Fixes\n\n* issue with isolateModules flag ([#3495](https://github.com/ipfs/js-ipfs/issues/3495)) ([839e190](https://github.com/ipfs/js-ipfs/commit/839e1908f3c050b45af176883a7e450fb339bef0)), closes [#3494](https://github.com/ipfs/js-ipfs/issues/3494) [#3498](https://github.com/ipfs/js-ipfs/issues/3498) [/github.com/ipfs-shipyard/ipfs-webui/pull/1655#issuecomment-763846124](https://github.com//github.com/ipfs-shipyard/ipfs-webui/pull/1655/issues/issuecomment-763846124)\n\n\n\n\n\n# 0.2.0 (2021-01-15)\n\n\n### Features\n\n* allow passing a http.Agent to the grpc client ([#3477](https://github.com/ipfs/js-ipfs/issues/3477)) ([c5f0bc5](https://github.com/ipfs/js-ipfs/commit/c5f0bc5eeee15369b7d02901035b04184a8608d2)), closes [#3474](https://github.com/ipfs/js-ipfs/issues/3474)\n"
  },
  {
    "path": "packages/ipfs-core-types/COPYRIGHT",
    "content": "This project is transitioning from an MIT-only license to a dual MIT/Apache-2.0 license.\nUnless otherwise noted, all code contributed prior to 2019-11-21 and not contributed by\na user listed in [this signoff issue](https://github.com/ipfs/js-ipfs/issues/2624) is\nlicensed under MIT-only. All new contributions (and past contributions since 2019-11-21)\nare licensed under a dual MIT/Apache-2.0 license.\n"
  },
  {
    "path": "packages/ipfs-core-types/LICENSE",
    "content": "This project is dual licensed under MIT and Apache-2.0.\n\nMIT: https://www.opensource.org/licenses/mit\nApache-2.0: https://www.apache.org/licenses/license-2.0\n"
  },
  {
    "path": "packages/ipfs-core-types/LICENSE-APACHE",
    "content": "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\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless 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.\n"
  },
  {
    "path": "packages/ipfs-core-types/LICENSE-MIT",
    "content": "The MIT License (MIT)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "packages/ipfs-core-types/README.md",
    "content": "> # ⛔️ DEPRECATED: [js-IPFS](https://github.com/ipfs/js-ipfs) has been superseded by [Helia](https://github.com/ipfs/helia)\n>\n> 📚 [Learn more about this deprecation](https://github.com/ipfs/js-ipfs/issues/4336) or [how to migrate](https://github.com/ipfs/helia/wiki/Migrating-from-js-IPFS)\n>\n> ⚠️ If you continue using this repo, please note that security fixes will not be provided\n\n# ipfs-core-types <!-- omit in toc -->\n\n[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech)\n[![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech)\n[![codecov](https://img.shields.io/codecov/c/github/ipfs/js-ipfs.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipfs)\n[![CI](https://img.shields.io/github/actions/workflow/status/ipfs/js-ipfs/test.yml?branch=master\\&style=flat-square)](https://github.com/ipfs/js-ipfs/actions/workflows/test.yml?query=branch%3Amaster)\n\n> IPFS interface definitions used by implementations for API compatibility.\n\n## Table of contents <!-- omit in toc -->\n\n- [Install](#install)\n- [Background](#background)\n- [Usage](#usage)\n  - [In JSDoc syntax](#in-jsdoc-syntax)\n  - [In Typescript](#in-typescript)\n- [Validation](#validation)\n- [License](#license)\n- [Contribute](#contribute)\n\n## Install\n\n```console\n$ npm i ipfs-core-types\n```\n\n## Background\n\nThe primary goal of this module is to define and ensure that IPFS core implementations and their respective client libraries implement the same interface, so that developers can quickly change between a local and a remote node without having to change their applications.\n\nIt offers a set of typescript interface definitions that define the IPFS core API.  Once your implementation implements those APIs you can use the tests found in the [interface-ipfs-core](https://www.npmjs.com/package/interface-ipfs-core) module to validate your implementation.\n\n```console\n$ npm install ipfs-core-types\n```\n\n## Usage\n\nInstall `ipfs-core-types` as one of the dependencies of your project and use it to ensure your implementations API compatibility:\n\n### In [JSDoc syntax](https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html)\n\n```js\n/**\n * @implements {import('ipfs-core-types').IPFS}\n */\nclass MyImpl {\n  // your implementation goes here\n}\n```\n\n### In Typescript\n\n```ts\nimport type { IPFS } from 'ipfs-core-types'\nclass MyImpl implements IPFS {\n  // your implementation goes here\n}\n```\n\n## Validation\n\nIn order to validate API compatibility you can run [typescript](https://www.typescriptlang.org/) over your implementation which will point out all the API compatibilities if there are some.\n\n## License\n\nLicensed under either of\n\n- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / <http://www.apache.org/licenses/LICENSE-2.0>)\n- MIT ([LICENSE-MIT](LICENSE-MIT) / <http://opensource.org/licenses/MIT>)\n\n## Contribute\n\nContributions welcome! Please check out [the issues](https://github.com/ipfs/js-ipfs/issues).\n\nAlso 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.\n\nPlease 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).\n\nUnless 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.\n\n[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md)\n"
  },
  {
    "path": "packages/ipfs-core-types/package.json",
    "content": "{\n  \"name\": \"ipfs-core-types\",\n  \"version\": \"0.14.1\",\n  \"description\": \"IPFS interface definitions used by implementations for API compatibility.\",\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"homepage\": \"https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-core-types#readme\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ipfs/js-ipfs.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ipfs/js-ipfs/issues\"\n  },\n  \"keywords\": [\n    \"API\",\n    \"IPFS\",\n    \"interface\",\n    \"types\"\n  ],\n  \"engines\": {\n    \"node\": \">=16.0.0\",\n    \"npm\": \">=7.0.0\"\n  },\n  \"type\": \"module\",\n  \"types\": \"./dist/src/index.d.ts\",\n  \"files\": [\n    \"src\",\n    \"dist\",\n    \"!dist/test\",\n    \"!**/*.tsbuildinfo\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/src/index.d.ts\",\n      \"import\": \"./dist/src/index.js\"\n    }\n  },\n  \"eslintConfig\": {\n    \"extends\": \"ipfs\",\n    \"parserOptions\": {\n      \"sourceType\": \"module\"\n    }\n  },\n  \"scripts\": {\n    \"clean\": \"aegir clean\",\n    \"lint\": \"aegir lint\",\n    \"build\": \"aegir build\"\n  },\n  \"dependencies\": {\n    \"@ipld/dag-pb\": \"^4.0.0\",\n    \"@libp2p/interface-keychain\": \"^2.0.0\",\n    \"@libp2p/interface-peer-id\": \"^2.0.0\",\n    \"@libp2p/interface-peer-info\": \"^1.0.2\",\n    \"@libp2p/interface-pubsub\": \"^3.0.0\",\n    \"@multiformats/multiaddr\": \"^11.1.5\",\n    \"@types/node\": \"^18.0.0\",\n    \"interface-datastore\": \"^7.0.0\",\n    \"ipfs-unixfs\": \"^9.0.0\",\n    \"multiformats\": \"^11.0.0\"\n  },\n  \"devDependencies\": {\n    \"aegir\": \"^37.11.0\"\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core-types/src/bitswap/index.ts",
    "content": "import type { CID } from 'multiformats/cid'\nimport type { AbortOptions } from '../utils'\nimport type { PeerId } from '@libp2p/interface-peer-id'\n\nexport interface API<OptionExtension = {}> {\n  /**\n   * Returns the wantlist for your node\n   *\n   * @example\n   * ```js\n   * const list = await ipfs.bitswap.wantlist()\n   * console.log(list)\n   * // [ CID('QmHash') ]\n   * ```\n   */\n  wantlist: (options?: AbortOptions & OptionExtension) => Promise<CID[]>\n\n  /**\n   * Returns the wantlist for a connected peer\n   *\n   * @example\n   * ```js\n   * const list = await ipfs.bitswap.wantlistForPeer(peerId)\n   * console.log(list)\n   * // [ CID('QmHash') ]\n   * ```\n   */\n  wantlistForPeer: (peerId: PeerId, options?: AbortOptions & OptionExtension) => Promise<CID[]>\n\n  /**\n   * Removes one or more CIDs from the wantlist\n   *\n   * @example\n   * ```JavaScript\n   * let list = await ipfs.bitswap.wantlist()\n   * console.log(list)\n   * // [ CID('QmHash') ]\n   *\n   * await ipfs.bitswap.unwant(cid)\n   *\n   * list = await ipfs.bitswap.wantlist()\n   * console.log(list)\n   * // []\n   * ```\n   */\n  unwant: (cids: CID | CID[], options?: AbortOptions & OptionExtension) => Promise<void>\n\n  /**\n   * Show diagnostic information on the bitswap agent.\n   * Note: `bitswap.stat` and `stats.bitswap` can be used interchangeably.\n   *\n   * @example\n   * ```js\n   * const stats = await ipfs.bitswap.stat()\n   * console.log(stats)\n   * ```\n   */\n  stat: (options?: AbortOptions & OptionExtension) => Promise<Stats>\n}\n\nexport interface Stats {\n  provideBufLen: number\n  wantlist: CID[]\n  peers: PeerId[]\n  blocksReceived: bigint\n  dataReceived: bigint\n  blocksSent: bigint\n  dataSent: bigint\n  dupBlksReceived: bigint\n  dupDataReceived: bigint\n}\n"
  },
  {
    "path": "packages/ipfs-core-types/src/block/index.ts",
    "content": "import type { AbortOptions, PreloadOptions } from '../utils'\nimport type { CID, Version as CIDVersion } from 'multiformats/cid'\n\nexport interface API<OptionExtension = {}> {\n  /**\n   * Get a raw IPFS block\n   *\n   * @example\n   * ```js\n   * const block = await ipfs.block.get(cid)\n   * console.log(block)\n   * ```\n   */\n  get: (cid: CID, options?: AbortOptions & PreloadOptions & OptionExtension) => Promise<Uint8Array>\n\n  /**\n   * Stores a Uint8Array as a block in the underlying blockstore\n   *\n   * @example\n   * ```js\n   * import * as dagPB from '@ipld/dag-pb'\n   * // Defaults\n   * const encoder = new TextEncoder()\n   * const decoder = new TextDecoder()\n   *\n   * const bytes = encoder.encode('a serialized object')\n   * const cid = await ipfs.block.put(bytes)\n   *\n   * console.log(decoder.decode(block.data))\n   * // Logs:\n   * // a serialized object\n   * console.log(block.cid.toString())\n   * // Logs:\n   * // the CID of the object\n   * ```\n   */\n  put: (block: Uint8Array, options?: PutOptions & OptionExtension) => Promise<CID>\n\n  /**\n   * Remove one or more IPFS block(s) from the underlying block store\n   *\n   * @example\n   * ```js\n   * for await (const result of ipfs.block.rm(cid)) {\n   *   if (result.error) {\n   *     console.error(`Failed to remove block ${result.cid} due to ${result.error.message}`)\n   *   } else {\n   *    console.log(`Removed block ${result.cid}`)\n   *   }\n   * }\n   * ```\n   */\n  rm: (cid: CID | CID[], options?: RmOptions & OptionExtension) => AsyncIterable<RmResult>\n\n  /**\n   * Print information of a raw IPFS block\n   *\n   * @example\n   * ```js\n   * const cid = CID.parse('QmQULBtTjNcMwMr4VMNknnVv3RpytrLSdgpvMcTnfNhrBJ')\n   * const stats = await ipfs.block.stat(cid)\n   * console.log(stats.cid.toString())\n   * // Logs: QmQULBtTjNcMwMr4VMNknnVv3RpytrLSdgpvMcTnfNhrBJ\n   * console.log(stat.size)\n   * // Logs: 3739\n   * ```\n   */\n  stat: (cid: CID, options?: AbortOptions & PreloadOptions & OptionExtension) => Promise<StatResult>\n}\n\nexport interface PutOptions extends AbortOptions, PreloadOptions {\n  /**\n   * The codec to use to create the CID\n   */\n  format?: string\n\n  /**\n   * Multihash hashing algorithm to use. (Defaults to 'sha2-256')\n   */\n  mhtype?: string\n\n  /**\n   * The version to use to create the CID\n   */\n  version?: CIDVersion\n\n  /**\n   * Pin this block when adding. (Defaults to `false`)\n   */\n  pin?: boolean\n}\n\nexport interface RmOptions extends AbortOptions {\n  /**\n   * Ignores non-existent blocks\n   */\n  force?: boolean\n\n  /**\n   * Do not return output if true\n   */\n  quiet?: boolean\n}\n\nexport interface RmResult {\n  /**\n   * The CID of the removed block\n   */\n  cid: CID\n\n  /**\n   * Any error that occurred while trying to remove the block\n   */\n  error?: Error\n}\n\nexport interface StatResult {\n  /**\n   * The CID of the block\n   */\n  cid: CID\n\n  /**\n   * The size of the block\n   */\n  size: number\n}\n"
  },
  {
    "path": "packages/ipfs-core-types/src/bootstrap/index.ts",
    "content": "import type { AbortOptions } from '../utils'\nimport type { Multiaddr } from '@multiformats/multiaddr'\n\nexport interface API<OptionExtension = {}> {\n  /**\n   * Add a peer address to the bootstrap list\n   *\n   * @example\n   * ```js\n   * const validIp4 = '/ip4/104....9z'\n   *\n   * const res = await ipfs.bootstrap.add(validIp4)\n   * console.log(res.Peers)\n   * // Logs:\n   * // ['/ip4/104....9z']\n   * ```\n   */\n  add: (addr: Multiaddr, options?: AbortOptions & OptionExtension) => Promise<{ Peers: Multiaddr[] }>\n\n  /**\n   * Reset the bootstrap list to contain only the default bootstrap nodes\n   *\n   * @example\n   * ```js\n   * const res = await ipfs.bootstrap.list()\n   * console.log(res.Peers)\n   * // Logs:\n   * // [address1, address2, ...]\n   * ```\n   */\n  reset: (options?: AbortOptions & OptionExtension) => Promise<{ Peers: Multiaddr[] }>\n\n  /**\n   * List all peer addresses in the bootstrap list\n   *\n   * @example\n   * ```js\n   * const res = await ipfs.bootstrap.list()\n   * console.log(res.Peers)\n   * // Logs:\n   * // [address1, address2, ...]\n   * ```\n   */\n  list: (options?: AbortOptions & OptionExtension) => Promise<{ Peers: Multiaddr[] }>\n\n  /**\n   * Remove a peer address from the bootstrap list\n   *\n   * @example\n   * ```js\n   * const res = await ipfs.bootstrap.list()\n   * console.log(res.Peers)\n   * // Logs:\n   * // [address1, address2, ...]\n   * ```\n   */\n  rm: (addr: Multiaddr, options?: AbortOptions & OptionExtension) => Promise<{ Peers: Multiaddr[] }>\n\n  /**\n   * Remove all peer addresses from the bootstrap list\n   *\n   * @example\n   * ```js\n   * const res = await ipfs.bootstrap.clear()\n   * console.log(res.Peers)\n   * // Logs:\n   * // [address1, address2, ...]\n   * ```\n   */\n  clear: (options?: AbortOptions & OptionExtension) => Promise<{ Peers: Multiaddr[] }>\n}\n"
  },
  {
    "path": "packages/ipfs-core-types/src/config/index.ts",
    "content": "import type { AbortOptions } from '../utils'\nimport type { API as ProfilesAPI } from './profiles'\n\nexport interface API<OptionExtension = {}> {\n  /**\n   * Returns a value from the currently being used config. If the daemon\n   * is off, it returns the value from the stored config.\n   */\n  get: (key: string, options?: AbortOptions & OptionExtension) => Promise<string | object>\n\n  /**\n   * Returns the full config been used. If the daemon is off, it returns the\n   * stored config\n   */\n  getAll: (options?: AbortOptions & OptionExtension) => Promise<Config>\n\n  /**\n   * Adds or replaces a config value. Note that restarting the node will be\n   * necessary for any change to take effect.\n   */\n  set: (key: string, value: any, options?: AbortOptions & OptionExtension) => Promise<void>\n\n  /**\n   * Replaces the full config. Note that restarting the node will be\n   * necessary for any change to take effect.\n   */\n  replace: (config: Config, options?: AbortOptions & OptionExtension) => Promise<void>\n\n  profiles: ProfilesAPI\n}\n\nexport interface Config {\n  Addresses?: AddressConfig\n  API?: APIConfig\n  Profiles?: string\n  Bootstrap?: string[]\n  Discovery?: DiscoveryConfig\n  Datastore?: DatastoreConfig\n  Identity?: IdentityConfig\n  Keychain?: KeychainConfig\n  Pubsub?: PubsubConfig\n  Swarm?: SwarmConfig\n  Routing?: RoutingConfig\n}\n\n/**\n * Contains information about various listener addresses to be used by this node\n */\nexport interface AddressConfig {\n  API?: string\n  RPC?: string\n  Delegates?: string[]\n  Gateway?: string\n  Swarm?: string[]\n  Announce?: string[]\n  NoAnnounce?: string[]\n}\n\nexport interface APIConfig {\n  HTTPHeaders?: Record<string, string[]>\n}\n\nexport interface DiscoveryConfig {\n  MDNS?: MDNSDiscovery\n  webRTCStar?: WebRTCStarDiscovery\n}\n\nexport interface MDNSDiscovery {\n  Enabled?: boolean\n  Interval?: number\n}\n\nexport interface WebRTCStarDiscovery {\n  Enabled?: boolean\n}\n\nexport interface DatastoreConfig {\n  Spec?: DatastoreSpec\n}\n\nexport interface DatastoreType {\n  type: string\n  path: string\n  sync?: boolean\n  shardFunc?: string\n  compression?: string\n}\n\nexport interface DatastoreMountPoint {\n  mountpoint: string\n  type: string\n  prefix: string\n  child: DatastoreType\n}\n\nexport interface DatastoreSpec {\n  type?: string\n  mounts?: DatastoreMountPoint[]\n}\n\nexport interface IdentityConfig {\n  /**\n   * The unique PKI identity label for this configs peer. Set on init and never\n   * read, its merely here for convenience. IPFS will always generate the peerID\n   * from its keypair at runtime.\n   */\n  PeerID: string\n\n  /**\n   * The base64 encoded protobuf describing (and containing) the nodes private key.\n   */\n  PrivKey: string\n}\n\nexport interface KeychainConfig {\n  DEK?: DEK\n}\n\nexport interface DEK {\n  keyLength?: number\n  iterationCount?: number\n  salt?: string\n  hash?: string\n}\n\nexport interface PubsubConfig {\n  PubSubRouter?: 'gossipsub' | 'floodsub'\n  Enabled?: boolean\n}\n\nexport interface SwarmConfig {\n  ConnMgr?: ConnMgrConfig\n  DisableNatPortMap?: boolean\n}\n\nexport interface ConnMgrConfig {\n  LowWater?: number\n  HighWater?: number\n}\n\nexport interface RoutingConfig {\n  Type?: string\n}\n"
  },
  {
    "path": "packages/ipfs-core-types/src/config/profiles/index.ts",
    "content": "import type { AbortOptions } from '../../utils'\nimport type { Config } from '../'\n\nexport interface API<OptionExtension = {}> {\n  /**\n   * List available config profiles\n   */\n  list: (options?: AbortOptions & OptionExtension) => Promise<Profile[]>\n\n  /**\n   * Apply a profile to the current config.  Note that restarting the node\n   * will be necessary for any change to take effect.\n   */\n  apply: (name: string, options?: ProfilesApplyOptions & OptionExtension) => Promise<ProfilesApplyResult>\n}\n\nexport interface Profile {\n  name: string\n  description: string\n}\n\nexport interface ProfilesApplyOptions extends AbortOptions {\n  dryRun?: boolean\n}\nexport interface ProfilesApplyResult {\n  original: Config\n  updated: Config\n}\n"
  },
  {
    "path": "packages/ipfs-core-types/src/dag/index.ts",
    "content": "import type { AbortOptions, PreloadOptions, IPFSPath } from '../utils'\nimport type { CID, Version as CIDVersion } from 'multiformats/cid'\n\nexport interface API<OptionExtension = {}> {\n  /**\n   * Retrieve an IPLD format node\n   *\n   * @example\n   * ```js\n   * // example obj\n   * const obj = {\n   *   a: 1,\n   *   b: [1, 2, 3],\n   *   c: {\n   *     ca: [5, 6, 7],\n   *     cb: 'foo'\n   *   }\n   * }\n   *\n   * const cid = await ipfs.dag.put(obj, { storeCodec: 'dag-cbor', hashAlg: 'sha2-256' })\n   * console.log(cid.toString())\n   * // zdpuAmtur968yprkhG9N5Zxn6MFVoqAWBbhUAkNLJs2UtkTq5\n   *\n   * async function getAndLog(cid, path) {\n   *   const result = await ipfs.dag.get(cid, { path })\n   *   console.log(result.value)\n   * }\n   *\n   * await getAndLog(cid, '/a')\n   * // Logs:\n   * // 1\n   *\n   * await getAndLog(cid, '/b')\n   * // Logs:\n   * // [1, 2, 3]\n   *\n   * await getAndLog(cid, '/c')\n   * // Logs:\n   * // {\n   * //   ca: [5, 6, 7],\n   * //   cb: 'foo'\n   * // }\n   *\n   * await getAndLog(cid, '/c/ca/1')\n   * // Logs:\n   * // 6\n   * ```\n   */\n  get: (cid: CID, options?: GetOptions & OptionExtension) => Promise<GetResult>\n\n  /**\n   * Store an IPLD format node\n   *\n   * @example\n   * ```js\n   * const obj = { simple: 'object' }\n   * const cid = await ipfs.dag.put(obj, { storeCodec: 'dag-cbor', hashAlg: 'sha2-512' })\n   *\n   * console.log(cid.toString())\n   * // zBwWX9ecx5F4X54WAjmFLErnBT6ByfNxStr5ovowTL7AhaUR98RWvXPS1V3HqV1qs3r5Ec5ocv7eCdbqYQREXNUfYNuKG\n   * ```\n   */\n  put: (node: any, options?: PutOptions & OptionExtension) => Promise<CID>\n\n  /**\n   * Returns the CID and remaining path of the node at the end of the passed IPFS path\n   *\n   * @example\n   * ```JavaScript\n   * // example obj\n   * const obj = {\n   *   a: 1,\n   *   b: [1, 2, 3],\n   *   c: {\n   *     ca: [5, 6, 7],\n   *     cb: 'foo'\n   *   }\n   * }\n   *\n   * const cid = await ipfs.dag.put(obj, { storeCodec: 'dag-cbor', hashAlg: 'sha2-256' })\n   * console.log(cid.toString())\n   * // bafyreicyer3d34cutdzlsbe2nqu5ye62mesuhwkcnl2ypdwpccrsecfmjq\n   *\n   * const result = await ipfs.dag.resolve(`${cid}/c/cb`)\n   * console.log(result)\n   * // Logs:\n   * // {\n   * //   cid: CID(bafyreicyer3d34cutdzlsbe2nqu5ye62mesuhwkcnl2ypdwpccrsecfmjq),\n   * //   remainderPath: 'c/cb'\n   * // }\n   * ```\n   */\n  resolve: (ipfsPath: IPFSPath, options?: ResolveOptions & OptionExtension) => Promise<ResolveResult>\n\n  /**\n   * Exports a CAR for the entire DAG available from the given root CID. The CAR will have a single\n   * root and IPFS will attempt to fetch and bundle all blocks that are linked within the connected\n   * DAG.\n   */\n  export: (root: CID, options?: ExportOptions & OptionExtension) => AsyncIterable<Uint8Array>\n\n  /**\n   * Import all blocks from one or more CARs and optionally recursively pin the roots identified\n   * within the CARs.\n   */\n  import: (sources: Iterable<Uint8Array> | AsyncIterable<Uint8Array> | AsyncIterable<AsyncIterable<Uint8Array>> | Iterable<AsyncIterable<Uint8Array>>, options?: ImportOptions & OptionExtension) => AsyncIterable<ImportResult>\n}\n\nexport interface GetOptions extends AbortOptions, PreloadOptions {\n  /**\n   * An optional path within the DAG to resolve\n   */\n  path?: string\n\n  /**\n   * If set to true, it will avoid resolving through different objects\n   */\n  localResolve?: boolean\n}\n\nexport interface GetResult {\n  /**\n   * The value or node that was fetched during the get operation\n   */\n  value: any\n\n  /**\n   * The remainder of the Path that the node was unable to resolve or what was left in a localResolve scenario\n   */\n  remainderPath?: string\n}\n\nexport interface PutOptions extends AbortOptions, PreloadOptions {\n  /**\n   * The codec that the input object is encoded with if a pre-encoded object is supplied.\n   */\n  inputCodec?: string\n\n  /**\n   * The codec that the stored object will be encoded with (defaults to 'dag-cbor')\n   */\n  storeCodec?: string\n\n  /**\n   * Multihash hashing algorithm to use (defaults to 'sha2-256')\n   */\n  hashAlg?: string\n\n  /**\n   * The version to use to create the CID (default to 1)\n   */\n  version?: CIDVersion\n\n  /**\n   * Pin this block when adding. (Defaults to `false`)\n   */\n  pin?: boolean\n\n  /**\n   * If true no blocks will be written to the underlying blockstore\n   */\n  onlyHash?: boolean\n}\n\nexport interface RmOptions extends AbortOptions {\n  /**\n   * Ignores non-existent blocks\n   */\n  force?: boolean\n}\n\nexport interface TreeOptions extends AbortOptions, PreloadOptions {\n  /**\n   * An optional path within the DAG to resolve\n   */\n  path?: string\n\n  /**\n   * If set to true, it will follow the links and continuously run tree on them, returning all the paths in the graph\n   */\n  recursive?: boolean\n}\n\nexport interface ResolveOptions extends AbortOptions, PreloadOptions {\n  /**\n   * If ipfsPath is a CID, you may pass a path here\n   */\n  path?: string\n}\n\nexport interface ResolveResult {\n  /**\n   * The last CID encountered during the traversal and the path to the end of the IPFS path inside the node referenced by the CID\n   */\n  cid: CID\n\n  /**\n   * The remainder of the Path that the node was unable to resolve\n   */\n  remainderPath?: string\n}\n\nexport interface ExportOptions extends AbortOptions, PreloadOptions {\n}\n\nexport interface ImportOptions extends AbortOptions, PreloadOptions {\n  /**\n   * Recursively pin roots for the imported CARs, defaults to true.\n   */\n  pinRoots?: boolean\n}\n\nexport interface ImportResult {\n  /**\n   * A list of roots and their pin status if `pinRoots` was set.\n   */\n  root: ImportRootStatus\n}\n\nexport interface ImportRootStatus {\n  /**\n   * CID of a root that was recursively pinned.\n   */\n  cid: CID\n\n  /**\n   * The error message if the pin was unsuccessful.\n   */\n  pinErrorMsg?: string\n}\n"
  },
  {
    "path": "packages/ipfs-core-types/src/dht/index.ts",
    "content": "import type { AbortOptions } from '../utils'\nimport type { CID } from 'multiformats/cid'\nimport type { PeerId } from '@libp2p/interface-peer-id'\nimport type { PeerInfo } from '@libp2p/interface-peer-info'\n\nexport interface API<OptionExtension = {}> {\n  /**\n   * Query the DHT for all multiaddresses associated with a `PeerId`.\n   *\n   * @example\n   * ```js\n   * const info = await ipfs.dht.findPeer('QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt')\n   *\n   * console.log(info.id)\n   * // QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt\n   *\n   * info.addrs.forEach(addr => console.log(addr.toString()))\n   * // '/ip4/147.75.94.115/udp/4001/quic'\n   * // '/ip6/2604:1380:3000:1f00::1/udp/4001/quic'\n   * // '/dnsaddr/bootstrap.libp2p.io'\n   * // '/ip6/2604:1380:3000:1f00::1/tcp/4001'\n   * // '/ip4/147.75.94.115/tcp/4001'\n   * ```\n   */\n  findPeer: (peerId: PeerId, options?: AbortOptions & OptionExtension) => AsyncIterable<QueryEvent>\n\n  /**\n   * Find peers in the DHT that can provide a specific value, given a CID.\n   *\n   * @example\n   * ```js\n   * const providers = ipfs.dht.findProvs('QmdPAhQRxrDKqkGPvQzBvjYe3kU8kiEEAd2J6ETEamKAD9')\n   * for await (const provider of providers) {\n   *   console.log(provider.id.toString())\n   * }\n   * ```\n   */\n  findProvs: (cid: CID, options?: AbortOptions & OptionExtension) => AsyncIterable<QueryEvent>\n\n  /**\n   * Given a key, query the DHT for its best value.\n   */\n  get: (key: string | Uint8Array, options?: AbortOptions & OptionExtension) => AsyncIterable<QueryEvent>\n\n  /**\n   * Announce to the network that we are providing given values.\n   */\n  provide: (cid: CID, options?: DHTProvideOptions & OptionExtension) => AsyncIterable<QueryEvent>\n\n  /**\n   * Write a key/value pair to the DHT.\n   *\n   * Given a key of the form /foo/bar and a value of any\n   * form, this will write that value to the DHT with\n   * that key.\n   */\n  put: (key: string | Uint8Array, value: Uint8Array, options?: AbortOptions & OptionExtension) => AsyncIterable<QueryEvent>\n\n  /**\n   * Find the closest peers to a given `PeerId` or `CID`, by querying the DHT.\n   */\n  query: (peerId: PeerId | CID, options?: AbortOptions & OptionExtension) => AsyncIterable<QueryEvent>\n}\n\nexport interface DHTProvideOptions extends AbortOptions {\n  recursive?: boolean\n}\n\nexport enum EventTypes {\n  SENDING_QUERY = 0,\n  PEER_RESPONSE,\n  FINAL_PEER,\n  QUERY_ERROR,\n  PROVIDER,\n  VALUE,\n  ADDING_PEER,\n  DIALING_PEER\n}\n\n/**\n * The types of messages set/received during DHT queries\n */\nexport enum MessageType {\n  PUT_VALUE = 0,\n  GET_VALUE,\n  ADD_PROVIDER,\n  GET_PROVIDERS,\n  FIND_NODE,\n  PING\n}\n\nexport type MessageName = keyof typeof MessageType\n\nexport interface DHTRecord {\n  key: Uint8Array\n  value: Uint8Array\n  timeReceived?: Date\n}\n\nexport interface SendingQueryEvent {\n  type: EventTypes.SENDING_QUERY\n  name: 'SENDING_QUERY'\n}\n\nexport interface PeerResponseEvent {\n  from: PeerId\n  type: EventTypes.PEER_RESPONSE\n  name: 'PEER_RESPONSE'\n  messageType: MessageType\n  messageName: MessageName\n  providers: PeerInfo[]\n  closer: PeerInfo[]\n  record?: DHTRecord\n}\n\nexport interface FinalPeerEvent {\n  peer: PeerInfo\n  type: EventTypes.FINAL_PEER\n  name: 'FINAL_PEER'\n}\n\nexport interface QueryErrorEvent {\n  type: EventTypes.QUERY_ERROR\n  name: 'QUERY_ERROR'\n  error: Error\n}\n\nexport interface ProviderEvent {\n  type: EventTypes.PROVIDER\n  name: 'PROVIDER'\n  providers: PeerInfo[]\n}\n\nexport interface ValueEvent {\n  type: EventTypes.VALUE\n  name: 'VALUE'\n  value: Uint8Array\n}\n\nexport interface AddingPeerEvent {\n  type: EventTypes.ADDING_PEER\n  name: 'ADDING_PEER'\n  peer: PeerId\n}\n\nexport interface DialingPeerEvent {\n  peer: PeerId\n  type: EventTypes.DIALING_PEER\n  name: 'DIALING_PEER'\n}\n\nexport type QueryEvent = SendingQueryEvent | PeerResponseEvent | FinalPeerEvent | QueryErrorEvent | ProviderEvent | ValueEvent | AddingPeerEvent | DialingPeerEvent\n"
  },
  {
    "path": "packages/ipfs-core-types/src/diag/index.ts",
    "content": "import type { AbortOptions } from '../utils'\n\nexport interface API<OptionExtension = {}> {\n  cmds: (options?: AbortOptions & OptionExtension) => Promise<CmdsResult[]>\n  net: (options?: AbortOptions & OptionExtension) => Promise<any>\n  sys: (options?: AbortOptions & OptionExtension) => Promise<any>\n}\n\nexport interface CmdsResult {\n  active: boolean\n  args: string[]\n  endTime: Date\n  id: string\n  options: Record<string, any>\n  startTime: Date\n}\n"
  },
  {
    "path": "packages/ipfs-core-types/src/files/index.ts",
    "content": "import type { AbortOptions, IPFSPath } from '../utils'\nimport type { CID, Version as CIDVersion } from 'multiformats/cid'\nimport type { Mtime, MtimeLike } from 'ipfs-unixfs'\nimport type { AddProgressFn } from '../root'\n\nexport interface API<OptionExtension = {}> {\n  /**\n   * Change mode for files and directories\n   *\n   * @example\n   * ```js\n   * // To give a file -rwxrwxrwx permissions\n   * await ipfs.files.chmod('/path/to/file.txt', parseInt('0777', 8))\n   *\n   * // Alternatively\n   * await ipfs.files.chmod('/path/to/file.txt', '+rwx')\n   *\n   * // You can omit the leading `0` too\n   * await ipfs.files.chmod('/path/to/file.txt', '777')\n   * ```\n   */\n  chmod: (path: string, mode: number | string, options?: ChmodOptions & OptionExtension) => Promise<void>\n\n  /**\n   * Copy files from one location to another\n   *\n   * - If from has multiple values then to must be a directory.\n   * - If from has a single value and to exists and is a directory, from will be copied into to.\n   * - If from has a single value and to exists and is a file, from must be a file and the contents of to will be replaced with the contents of from otherwise an error will be returned.\n   * - If from is an IPFS path, and an MFS path exists with the same name, the IPFS path will be chosen.\n   * - If from is an IPFS path and the content does not exist in your node's repo, only the root node of the source file with be retrieved from the network and linked to from the destination. The remainder of the file will be retrieved on demand.\n   *\n   * @example\n   * ```js\n   * // To copy a file\n   * await ipfs.files.cp('/src-file', '/dst-file')\n   *\n   * // To copy a directory\n   * await ipfs.files.cp('/src-dir', '/dst-dir')\n   *\n   * // To copy multiple files to a directory\n   * await ipfs.files.cp('/src-file1', '/src-file2', '/dst-dir')\n   * ```\n   */\n  cp: (from: IPFSPath | IPFSPath[], to: string, options?: CpOptions & OptionExtension) => Promise<void>\n\n  /**\n   * Make a directory in your MFS\n   */\n  mkdir: (path: string, options?: MkdirOptions & OptionExtension) => Promise<void>\n\n  /**\n   * Get file or directory statistics\n   */\n  stat: (ipfsPath: IPFSPath, options?: StatOptions & OptionExtension) => Promise<StatResult>\n\n  /**\n   * Update the mtime of a file or directory\n   *\n   * @example\n   * ```js\n   * // set the mtime to the current time\n   * await ipfs.files.touch('/path/to/file.txt')\n   *\n   * // set the mtime to a specific time\n   * await ipfs.files.touch('/path/to/file.txt', {\n   *   mtime: new Date('May 23, 2014 14:45:14 -0700')\n   * })\n   * ```\n   */\n  touch: (ipfsPath: string, options?: TouchOptions & OptionExtension) => Promise<void>\n\n  /**\n   * Remove a file or directory\n   *\n   * @example\n   * ```js\n   * // To remove a file\n   * await ipfs.files.rm('/my/beautiful/file.txt')\n   *\n   * // To remove multiple files\n   * await ipfs.files.rm(['/my/beautiful/file.txt', '/my/other/file.txt'])\n   *\n   * // To remove a directory\n   * await ipfs.files.rm('/my/beautiful/directory', { recursive: true })\n   * ```\n   */\n  rm: (ipfsPaths: string | string[], options?: RmOptions & OptionExtension) => Promise<void>\n\n  /**\n   * Read a file\n   *\n   * @example\n   * ```js\n   * const chunks = []\n   *\n   * for await (const chunk of ipfs.files.read('/hello-world')) {\n   *   chunks.push(chunk)\n   * }\n   *\n   * console.log(uint8ArrayConcat(chunks).toString())\n   * // Hello, World!\n   * ```\n   */\n  read: (ipfsPath: IPFSPath, options?: ReadOptions & OptionExtension) => AsyncIterable<Uint8Array>\n\n  /**\n   * Write to an MFS path\n   *\n   * @example\n   * ```js\n   * await ipfs.files.write('/hello-world', new TextEncoder().encode('Hello, world!'))\n   * ```\n   */\n  write: (ipfsPath: string, content: string | Uint8Array | Blob | AsyncIterable<Uint8Array> | Iterable<Uint8Array>, options?: WriteOptions & OptionExtension) => Promise<void>\n\n  /**\n   * Move files from one location to another\n   *\n   * - If from has multiple values then to must be a directory.\n   * - If from has a single value and to exists and is a directory, from will be moved into to.\n   * - If from has a single value and to exists and is a file, from must be a file and the contents of to will be replaced with the contents of from otherwise an error will be returned.\n   * - If from is an IPFS path, and an MFS path exists with the same name, the IPFS path will be chosen.\n   * - If from is an IPFS path and the content does not exist in your node's repo, only the root node of the source file with be retrieved from the network and linked to from the destination. The remainder of the file will be retrieved on demand.\n   * - All values of from will be removed after the operation is complete unless they are an IPFS path.\n   *\n   * @example\n   * ```js\n   * await ipfs.files.mv('/src-file', '/dst-file')\n   *\n   * await ipfs.files.mv('/src-dir', '/dst-dir')\n   *\n   * await ipfs.files.mv('/src-file1', '/src-file2', '/dst-dir')\n   * ```\n   */\n  mv: (from: string | string[], to: string, options?: MvOptions & OptionExtension) => Promise<void>\n\n  /**\n   * Flush a given path's data to the disk\n   *\n   * @example\n   * ```js\n   * const cid = await ipfs.files.flush('/')\n   * ```\n   */\n  flush: (ipfsPath: string, options?: AbortOptions & OptionExtension) => Promise<CID>\n\n  /**\n   * List directories in the local mutable namespace\n   *\n   * @example\n   * ```js\n   * for await (const file of ipfs.files.ls('/screenshots')) {\n   *   console.log(file.name)\n   * }\n   * // 2018-01-22T18:08:46.775Z.png\n   * // 2018-01-22T18:08:49.184Z.png\n   * ```\n   */\n  ls: (ipfsPath: IPFSPath, options?: AbortOptions & OptionExtension) => AsyncIterable<MFSEntry>\n}\n\nexport interface MFSEntry {\n  /**\n   * The object's name\n   */\n  name: string\n\n  /**\n   * The object's type (directory or file)\n   */\n  type: 'directory' | 'file'\n\n  /**\n   * The size of the file in bytes\n   */\n  size: number\n\n  /**\n   * The CID of the object\n   */\n  cid: CID\n\n  /**\n   * The UnixFS mode as a Number\n   */\n  mode?: number\n\n  /**\n   * An object with numeric secs and nsecs properties\n   */\n  mtime?: Mtime\n}\n\nexport interface MFSOptions {\n  /**\n   * If true the changes will be immediately flushed to disk\n   */\n  flush?: boolean\n}\n\nexport interface ChmodOptions extends MFSOptions, AbortOptions {\n  /**\n   * If true mode will be applied to the entire tree under path\n   */\n  recursive?: boolean\n\n  /**\n   * The hash algorithm to use for any updated entries\n   */\n  hashAlg?: string\n\n  /**\n   * The CID version to use for any updated entries\n   */\n  cidVersion?: CIDVersion\n\n  /**\n   * The threshold for splitting any modified folders into HAMT shards\n   */\n  shardSplitThreshold?: number\n}\n\nexport interface CpOptions extends MFSOptions, AbortOptions {\n  /**\n   * The value or node that was fetched during the get operation\n   */\n  parents?: boolean\n\n  /**\n   * The hash algorithm to use for any updated entries\n   */\n  hashAlg?: string\n\n  /**\n   * The CID version to use for any updated entries\n   */\n  cidVersion?: CIDVersion\n\n  /**\n   * The threshold for splitting any modified folders into HAMT shards\n   */\n  shardSplitThreshold?: number\n}\n\nexport interface MkdirOptions extends MFSOptions, AbortOptions {\n  /**\n   * If true, create intermediate directories\n   */\n  parents?: boolean\n\n  /**\n   * An integer that represents the file mode\n   */\n  mode?: number\n\n  /**\n   * A Date object, an object with { secs, nsecs } properties where secs is the number of seconds since (positive) or before (negative) the Unix Epoch began and nsecs is the number of nanoseconds since the last full second, or the output of process.hrtime()\n   */\n  mtime?: MtimeLike\n\n  /**\n   * The hash algorithm to use for any updated entries\n   */\n  hashAlg?: string\n\n  /**\n   * The CID version to use for any updated entries\n   */\n  cidVersion?: CIDVersion\n\n  /**\n   * The threshold for splitting any modified folders into HAMT shards\n   */\n  shardSplitThreshold?: number\n}\n\nexport interface StatOptions extends AbortOptions {\n  /**\n   * If true, return only the CID\n   */\n  hash?: boolean\n\n  /**\n   * If true, return only the size\n   */\n  size?: boolean\n\n  /**\n   * If true, compute the amount of the DAG that is local and if possible the total size\n   */\n  withLocal?: boolean\n}\n\nexport interface StatResult {\n  /**\n   * A CID instance\n   */\n  cid: CID\n\n  /**\n   * The file size in Bytes\n   */\n  size: number\n\n  /**\n   * The size of the DAGNodes making up the file in Bytes\n   */\n  cumulativeSize: number\n\n  /**\n   * Either directory or file\n   */\n  type: 'directory' | 'file'\n\n  /**\n   * If type is directory, this is the number of files in the directory. If it is file it is the number of blocks that make up the file\n   */\n  blocks: number\n\n  /**\n   * Indicates if locality information is present\n   */\n  withLocality: boolean\n\n  /**\n   * Indicates if the queried dag is fully present locally\n   */\n  local?: boolean\n\n  /**\n   * Indicates the cumulative size of the data present locally\n   */\n  sizeLocal?: number\n\n  /**\n   * UnixFS mode if applicable\n   */\n  mode?: number\n\n  /**\n   * UnixFS mtime if applicable\n   */\n  mtime?: Mtime\n}\n\nexport interface TouchOptions extends MFSOptions, AbortOptions {\n  /**\n   * A Date object, an object with { secs, nsecs } properties where secs is the number of seconds since (positive) or before (negative) the Unix Epoch began and nsecs is the number of nanoseconds since the last full second, or the output of process.hrtime()\n   */\n  mtime?: MtimeLike\n\n  /**\n   * The hash algorithm to use for any updated entries\n   */\n  hashAlg?: string\n\n  /**\n   * The CID version to use for any updated entries\n   */\n  cidVersion?: CIDVersion\n\n  /**\n   * The threshold for splitting any modified folders into HAMT shards\n   */\n  shardSplitThreshold?: number\n}\n\nexport interface RmOptions extends MFSOptions, AbortOptions {\n  /**\n   * If true all paths under the specifed path(s) will be removed\n   */\n  recursive?: boolean\n\n  /**\n   * The hash algorithm to use for any updated entries\n   */\n  hashAlg?: string\n\n  /**\n   * The CID version to use for any updated entries\n   */\n  cidVersion?: CIDVersion\n\n  /**\n   * The threshold for splitting any modified folders into HAMT shards\n   */\n  shardSplitThreshold?: number\n}\n\nexport interface ReadOptions extends AbortOptions {\n  /**\n   * An offset to start reading the file from\n   */\n  offset?: number\n\n  /**\n   * An optional max length to read from the file\n   */\n  length?: number\n}\n\nexport interface WriteOptions extends MFSOptions, AbortOptions {\n  /**\n   * An offset within the file to start writing at\n   */\n  offset?: number\n\n  /**\n   * Optionally limit how many bytes are written\n   */\n  length?: number\n\n  /**\n   * Create the MFS path if it does not exist\n   */\n  create?: boolean\n\n  /**\n   * Create intermediate MFS paths if they do not exist\n   */\n  parents?: boolean\n\n  /**\n   * Truncate the file at the MFS path if it would have been larger than the passed content\n   */\n  truncate?: boolean\n\n  /**\n   * If true, DAG leaves will contain raw file data and not be wrapped in a protobuf\n   */\n  rawLeaves?: boolean\n\n  /**\n   * An integer that represents the file mode\n   */\n  mode?: number\n\n  /**\n   * A Date object, an object with { secs, nsecs } properties where secs is the number of seconds since (positive) or before (negative) the Unix Epoch began and nsecs is the number of nanoseconds since the last full second, or the output of process.hrtime()\n   */\n  mtime?: MtimeLike\n\n  /**\n   * The hash algorithm to use for any updated entries\n   */\n  hashAlg?: string\n\n  /**\n   * The CID version to use for any updated entries\n   */\n  cidVersion?: CIDVersion\n\n  /**\n   * The threshold for splitting any modified folders into HAMT shards\n   */\n  shardSplitThreshold?: number\n\n  /**\n   * If writing a file and only a single leaf would be present, store the file data in the root node\n   */\n  reduceSingleLeafToSelf?: boolean\n\n  /**\n   * What sort of DAG structure to create\n   */\n  strategy?: 'balanced' | 'trickle'\n\n  /**\n   * Callback to be notified of write progress\n   */\n  progress?: AddProgressFn\n}\n\nexport interface MvOptions extends MFSOptions, AbortOptions {\n  /**\n   * Create intermediate MFS paths if they do not exist\n   */\n  parents?: boolean\n\n  /**\n   * The hash algorithm to use for any updated entries\n   */\n  hashAlg?: string\n\n  /**\n   * The CID version to use for any updated entries\n   */\n  cidVersion?: CIDVersion\n\n  /**\n   * The threshold for splitting any modified folders into HAMT shards\n   */\n  shardSplitThreshold?: number\n}\n"
  },
  {
    "path": "packages/ipfs-core-types/src/index.ts",
    "content": "import type { API as RootAPI } from './root'\nimport type { API as BitswapAPI } from './bitswap'\nimport type { API as BlockAPI } from './block'\nimport type { API as BootstrapAPI } from './bootstrap'\nimport type { API as ConfigAPI } from './config'\nimport type { API as DAGAPI } from './dag'\nimport type { API as DHTAPI } from './dht'\nimport type { API as DiagAPI } from './diag'\nimport type { API as FilesAPI } from './files'\nimport type { API as KeyAPI } from './key'\nimport type { API as LogAPI } from './log'\nimport type { API as NameAPI } from './name'\nimport type { API as ObjectAPI } from './object'\nimport type { API as PinAPI } from './pin'\nimport type { API as PubsubAPI } from './pubsub'\nimport type { Refs, Local } from './refs'\nimport type { API as RepoAPI } from './repo'\nimport type { API as StatsAPI } from './stats'\nimport type { API as SwarmAPI } from './swarm'\nimport type { AbortOptions, Await, AwaitIterable } from './utils'\nimport type { BlockCodec } from 'multiformats/codecs/interface'\nimport type { MultibaseCodec } from 'multiformats/bases/interface'\nimport type { MultihashHasher } from 'multiformats/hashes/interface'\n\ninterface RefsAPI<OptionExtension = {}> extends Refs<OptionExtension> {\n  local: Local<OptionExtension>\n}\n\nexport interface IPFS<OptionExtension = {}> extends RootAPI<OptionExtension> {\n  bitswap: BitswapAPI<OptionExtension>\n  block: BlockAPI<OptionExtension>\n  bootstrap: BootstrapAPI<OptionExtension>\n  config: ConfigAPI<OptionExtension>\n  dag: DAGAPI<OptionExtension>\n  dht: DHTAPI<OptionExtension>\n  diag: DiagAPI<OptionExtension>\n  files: FilesAPI<OptionExtension>\n  key: KeyAPI<OptionExtension>\n  log: LogAPI<OptionExtension>\n  name: NameAPI<OptionExtension>\n  object: ObjectAPI<OptionExtension>\n  pin: PinAPI<OptionExtension>\n  pubsub: PubsubAPI<OptionExtension>\n  refs: RefsAPI<OptionExtension>\n  repo: RepoAPI<OptionExtension>\n  stats: StatsAPI<OptionExtension>\n  swarm: SwarmAPI<OptionExtension>\n  bases: Bases\n  codecs: Codecs\n  hashers: Hashers\n}\n\ninterface Bases {\n  getBase: (code: string) => Promise<MultibaseCodec<any>>\n  listBases: () => Array<MultibaseCodec<any>>\n}\n\ninterface Codecs {\n  getCodec: (code: number | string) => Promise<BlockCodec<any, any>>\n  listCodecs: () => Array<BlockCodec<any, any>>\n}\n\ninterface Hashers {\n  getHasher: (code: number | string) => Promise<MultihashHasher>\n  listHashers: () => MultihashHasher[]\n}\n\nexport type {\n  AbortOptions,\n  Await,\n  AwaitIterable\n}\n"
  },
  {
    "path": "packages/ipfs-core-types/src/key/index.ts",
    "content": "import type { AbortOptions } from '../utils'\nimport type { KeyType } from '@libp2p/interface-keychain'\n\nexport interface API<OptionExtension = {}> {\n  /**\n   * Generate a new key\n   *\n   * @example\n   * ```js\n   * const key = await ipfs.key.gen('my-key', {\n   *   type: 'rsa',\n   *   size: 2048\n   * })\n   *\n   * console.log(key)\n   * // { id: 'QmYWqAFvLWb2G5A69JGXui2JJXzaHXiUEmQkQgor6kNNcJ',\n   * //  name: 'my-key' }\n   * ```\n   */\n  gen: (name: string, options?: GenOptions & OptionExtension) => Promise<Key>\n\n  /**\n   * List all the keys\n   *\n   * @example\n   * ```js\n   * const keys = await ipfs.key.list()\n   *\n   * console.log(keys)\n   * // [\n   * //   { id: 'QmTe4tuceM2sAmuZiFsJ9tmAopA8au71NabBDdpPYDjxAb',\n   * //     name: 'self' },\n   * //   { id: 'QmWETF5QvzGnP7jKq5sPDiRjSM2fzwzNsna4wSBEzRzK6W',\n   * //     name: 'my-key' }\n   * // ]\n   * ```\n   */\n  list: (options?: AbortOptions & OptionExtension) => Promise<Key[]>\n\n  /**\n   * Remove a key\n   *\n   * @example\n   * ```js\n   * const key = await ipfs.key.rm('my-key')\n   *\n   * console.log(key)\n   * // { id: 'QmWETF5QvzGnP7jKq5sPDiRjSM2fzwzNsna4wSBEzRzK6W',\n   * //   name: 'my-key' }\n   * ```\n   */\n  rm: (name: string, options?: AbortOptions & OptionExtension) => Promise<Key>\n\n  /**\n   * Rename a key\n   *\n   * @example\n   * ```js\n   * const key = await ipfs.key.rename('my-key', 'my-new-key')\n   *\n   * console.log(key)\n   * // { id: 'Qmd4xC46Um6s24MradViGLFtMitvrR4SVexKUgPgFjMNzg',\n   * //   was: 'my-key',\n   * //   now: 'my-new-key',\n   * //   overwrite: false }\n   * ```\n   */\n  rename: (oldName: string, newName: string, options?: AbortOptions & OptionExtension) => Promise<RenameKeyResult>\n\n  /**\n   * Remove a key\n   *\n   * @example\n   * ```js\n   * const pem = await ipfs.key.export('self', 'password')\n   *\n   * console.log(pem)\n   * // -----BEGIN ENCRYPTED PRIVATE KEY-----\n   * // MIIFDTA/BgkqhkiG9w0BBQ0wMjAaBgkqhkiG9w0BBQwwDQQIpdO40RVyBwACAWQw\n   * // ...\n   * // YA==\n   * // -----END ENCRYPTED PRIVATE KEY-----\n   * ```\n   */\n  export: (name: string, password: string, options?: AbortOptions & OptionExtension) => Promise<string>\n\n  /**\n   * Remove a key\n   *\n   * @example\n   * ```js\n   * const key = await ipfs.key.import('clone', pem, 'password')\n   *\n   * console.log(key)\n   * // { id: 'QmQRiays958UM7norGRQUG3tmrLq8pJdmJarwYSk2eLthQ',\n   * //   name: 'clone' }\n   * ```\n   */\n  import: (name: string, pem: string, password: string, options?: AbortOptions & OptionExtension) => Promise<Key>\n\n  /**\n   * Return the id and name of a key\n   *\n   * * @example\n   * ```js\n   * const { id, name } = await ipfs.key.info('key-name')\n   * ```\n   */\n  info: (name: string, options?: AbortOptions & OptionExtension) => Promise<Key>\n}\n\nexport interface GenOptions extends AbortOptions {\n  type: KeyType\n  size?: number\n}\n\nexport interface Key {\n  id: string\n  name: string\n}\n\nexport interface RenameKeyResult {\n  id: string\n  was: string\n  now: string\n  overwrite: boolean\n}\n"
  },
  {
    "path": "packages/ipfs-core-types/src/log/index.ts",
    "content": "import type { AbortOptions } from '../utils'\n\nexport interface API<OptionExtension = {}> {\n  level: (subsystem: string, level: string, options?: AbortOptions & OptionExtension) => Promise<any>\n  ls: (options?: AbortOptions & OptionExtension) => Promise<any>\n  tail: (options?: AbortOptions & OptionExtension) => AsyncIterable<any>\n}\n"
  },
  {
    "path": "packages/ipfs-core-types/src/name/index.ts",
    "content": "import type { CID } from 'multiformats/cid'\nimport type { AbortOptions } from '../utils'\nimport type { API as PubsubAPI } from './pubsub'\nimport type { PeerId } from '@libp2p/interface-peer-id'\n\nexport interface API<OptionExtension = {}> {\n  /**\n   * IPNS is a PKI namespace, where names are the hashes of public keys, and\n   * the private key enables publishing new (signed) values. In both publish\n   * and resolve, the default name used is the node's own PeerID,\n   * which is the hash of its public key.\n   *\n   * @example\n   * ```js\n   * // The address of your files.\n   * const addr = '/ipfs/QmbezGequPwcsWo8UL4wDF6a8hYwM1hmbzYv2mnKkEWaUp'\n   * const res = await ipfs.name.publish(addr)\n   * // You now have a res which contains two fields:\n   * //   - name: the name under which the content was published.\n   * //   - value: the \"real\" address to which Name points.\n   * console.log(`https://gateway.ipfs.io/ipns/${res.name}`)\n   * ```\n   */\n  publish: (value: CID | string, options?: PublishOptions & OptionExtension) => Promise<PublishResult>\n\n  /**\n   * Given a key, query the DHT for its best value.\n   *\n   * @example\n   * ```js\n   * // The IPNS address you want to resolve\n   * const addr = '/ipns/ipfs.io'\n   *\n   * for await (const name of ipfs.name.resolve(addr)) {\n   *   console.log(name)\n   * }\n   * // Logs: /ipfs/QmQrX8hka2BtNHa8N8arAq16TCVx5qHcb46c5yPewRycLm\n   * ```\n   */\n  resolve: (value: PeerId | string, options?: ResolveOptions & OptionExtension) => AsyncIterable<string>\n\n  pubsub: PubsubAPI\n}\n\nexport interface PublishOptions extends AbortOptions {\n  /**\n   * Resolve given path before publishing\n   */\n  resolve?: boolean\n  /**\n   * Time duration of the record\n   */\n  lifetime?: string\n  /**\n   * Time duration this record should be cached\n   */\n  ttl?: string\n  /**\n   * Name of the key to be used\n   */\n  key?: string\n  /**\n   * When offline, save the IPNS record\n   * to the the local datastore without broadcasting to the network instead of\n   * simply failing.\n   *\n   * This option is not yet implemented in js-ipfs. See tracking issue [ipfs/js-ipfs#1997]\n   * (https://github.com/ipfs/js-ipfs/issues/1997).\n   */\n  allowOffline?: boolean\n}\n\nexport interface PublishResult {\n  /**\n   * The published IPNS name\n   */\n  name: string\n\n  /**\n   * The IPNS record\n   */\n  value: string\n}\n\nexport interface ResolveOptions extends AbortOptions {\n  /**\n   * resolve until the result is not an IPNS name\n   */\n  recursive?: boolean\n\n  /**\n   * do not use cached entries\n   */\n  nocache?: boolean\n}\n"
  },
  {
    "path": "packages/ipfs-core-types/src/name/pubsub/index.ts",
    "content": "import type { AbortOptions } from '../../utils'\n\nexport interface API<OptionExtension = {}> {\n  /**\n   * Cancel a name subscription.\n   *\n   * @example\n   * ```js\n   * const name = 'QmQrX8hka2BtNHa8N8arAq16TCVx5qHcb46c5yPewRycLm'\n   * const result = await ipfs.name.pubsub.cancel(name)\n   * console.log(result.canceled)\n   * // Logs: true\n   * ```\n   */\n  cancel: (name: string, options?: AbortOptions & OptionExtension) => Promise<PubsubCancelResult>\n\n  /**\n   * Query the state of IPNS pubsub.\n   *\n   * @returns {Promise<{ enabled: boolean }>}\n   * ```js\n   * const result = await ipfs.name.pubsub.state()\n   * console.log(result.enabled)\n   * // Logs: true\n   * ```\n   */\n  state: (options?: AbortOptions & OptionExtension) => Promise<PubsubStateResult>\n\n  /**\n   * Show current name subscriptions.\n   *\n   * @example\n   * ```js\n   * const result = await ipfs.name.pubsub.subs()\n   * console.log(result)\n   * // Logs: ['/ipns/QmQrX8hka2BtNHa8N8arAq16TCVx5qHcb46c5yPewRycLm']\n   * ```\n   */\n  subs: (options?: AbortOptions & OptionExtension) => Promise<string[]>\n}\n\nexport interface PubsubCancelResult {\n  canceled: boolean\n}\n\nexport interface PubsubStateResult {\n  enabled: boolean\n}\n"
  },
  {
    "path": "packages/ipfs-core-types/src/object/index.ts",
    "content": "import type { CID } from 'multiformats/cid'\nimport type { AbortOptions, PreloadOptions } from '../utils'\nimport type { API as PatchAPI } from './patch'\nimport type { PBNode, PBLink } from '@ipld/dag-pb'\n\nexport interface API<OptionExtension = {}> {\n  new: (options?: NewObjectOptions & OptionExtension) => Promise<CID>\n  put: (obj: PBNode, options?: PutOptions & OptionExtension) => Promise<CID>\n  get: (cid: CID, options?: AbortOptions & PreloadOptions & OptionExtension) => Promise<PBNode>\n  data: (cid: CID, options?: AbortOptions & PreloadOptions & OptionExtension) => Promise<Uint8Array>\n  links: (cid: CID, options?: AbortOptions & PreloadOptions & OptionExtension) => Promise<PBLink[]>\n  stat: (cid: CID, options?: AbortOptions & PreloadOptions & OptionExtension) => Promise<StatResult>\n\n  patch: PatchAPI\n}\n\nexport interface NewObjectOptions extends AbortOptions, PreloadOptions {\n  template?: 'unixfs-dir'\n}\n\nexport interface StatResult {\n  Hash: CID\n  NumLinks: number\n  BlockSize: number\n  LinksSize: number\n  DataSize: number\n  CumulativeSize: number\n}\n\nexport interface PutOptions extends AbortOptions, PreloadOptions {\n  pin?: boolean\n}\n"
  },
  {
    "path": "packages/ipfs-core-types/src/object/patch/index.ts",
    "content": "import type { CID } from 'multiformats/cid'\nimport type { AbortOptions } from '../../utils'\nimport type { PBLink as DAGLink } from '@ipld/dag-pb'\n\nexport interface API<OptionExtension = {}> {\n  addLink: (cid: CID, link: DAGLink, options?: AbortOptions & OptionExtension) => Promise<CID>\n  rmLink: (cid: CID, link: DAGLink | string, options?: AbortOptions & OptionExtension) => Promise<CID>\n  appendData: (cid: CID, data: Uint8Array, options?: AbortOptions & OptionExtension) => Promise<CID>\n  setData: (cid: CID, data: Uint8Array, options?: AbortOptions & OptionExtension) => Promise<CID>\n}\n"
  },
  {
    "path": "packages/ipfs-core-types/src/pin/index.ts",
    "content": "import type { AbortOptions, AwaitIterable } from '../utils'\nimport type { CID } from 'multiformats/cid'\nimport type { API as Remote } from './remote'\n\nexport interface API<OptionExtension = {}> {\n  /**\n   * Adds an IPFS block to the pinset and also stores it to the IPFS\n   * repo. pinset is the set of hashes currently pinned (not gc'able)\n   *\n   * @example\n   * ```js\n   * const cid = CID.parse('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u')\n   * const pinned of ipfs.pin.add(cid))\n   * console.log(pinned)\n   * // Logs:\n   * // CID('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u')\n   * ```\n   */\n  add: (cid: string | CID, options?: AddOptions & OptionExtension) => Promise<CID>\n\n  /**\n   * Adds multiple IPFS blocks to the pinset and also stores it to the IPFS\n   * repo. pinset is the set of hashes currently pinned (not gc'able)\n   *\n   * @example\n   * ```js\n   * const cid = CID.parse('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u')\n   * for await (const cid of ipfs.pin.addAll([cid])) {\n   *   console.log(cid)\n   * }\n   * // Logs:\n   * // CID('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u')\n   * ```\n   */\n  addAll: (source: AwaitIterable<AddInput>, options?: AddAllOptions & OptionExtension) => AsyncIterable<CID>\n\n  /**\n   * List all the objects pinned to local storage\n   *\n   * @example\n   * ```js\n   * for await (const { cid, type } of ipfs.pin.ls()) {\n   *   console.log({ cid, type })\n   * }\n   * // { cid: CID(Qmc5XkteJdb337s7VwFBAGtiaoj2QCEzyxtNRy3iMudc3E), type: 'recursive' }\n   * // { cid: CID(QmZbj5ruYneZb8FuR9wnLqJCpCXMQudhSdWhdhp5U1oPWJ), type: 'indirect' }\n   * // { cid: CID(QmSo73bmN47gBxMNqbdV6rZ4KJiqaArqJ1nu5TvFhqqj1R), type: 'indirect' }\n   *\n   * const paths = [\n   *   CID.parse('Qmc5..'),\n   *   CID.parse('QmZb..'),\n   *   CID.parse('QmSo..')\n   * ]\n   * for await (const { cid, type } of ipfs.pin.ls({ paths })) {\n   *   console.log({ cid, type })\n   * }\n   * // { cid: CID(Qmc5XkteJdb337s7VwFBAGtiaoj2QCEzyxtNRy3iMudc3E), type: 'recursive' }\n   * // { cid: CID(QmZbj5ruYneZb8FuR9wnLqJCpCXMQudhSdWhdhp5U1oPWJ), type: 'indirect' }\n   * // { cid: CID(QmSo73bmN47gBxMNqbdV6rZ4KJiqaArqJ1nu5TvFhqqj1R), type: 'indirect' }\n   * ```\n   */\n  ls: (options?: LsOptions & OptionExtension) => AsyncIterable<LsResult>\n\n  /**\n   * Unpin this block from your repo\n   *\n   * @example\n   * ```js\n   * const cid = CID.parse('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u')\n   * const result = await ipfs.pin.rm(cid)\n   * console.log(result)\n   * // prints the CID that was unpinned\n   * // CID('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u')\n   * ```\n   */\n  rm: (ipfsPath: string | CID, options?: RmOptions & OptionExtension) => Promise<CID>\n\n  /**\n   * Unpin one or more blocks from your repo\n   *\n   * @example\n   * ```js\n   * const source = [\n   *   CID.parse('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u')\n   * ]\n   * for await (const cid of ipfs.pin.rmAll(source)) {\n   *   console.log(cid)\n   * }\n   * // prints the CIDs that were unpinned\n   * // CID('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u')\n   * ```\n   */\n  rmAll: (source: AwaitIterable<RmAllInput>, options?: AbortOptions & OptionExtension) => AsyncIterable<CID>\n\n  remote: Remote<OptionExtension>\n}\n\nexport interface AddOptions extends AbortOptions {\n  /**\n   * If true, pin all blocked linked to from the pinned CID\n   */\n  recursive?: boolean\n\n  /**\n   * Whether to preload all blocks pinned during this operation\n   */\n  preload?: boolean\n\n  /**\n   * Internal option used to control whether to create a repo write lock during a pinning operation\n   */\n  lock?: boolean\n}\n\nexport interface AddAllOptions extends AbortOptions {\n  /**\n   * Whether to preload all blocks pinned during this operation\n   */\n  preload?: boolean\n\n  /**\n   * Internal option used to control whether to create a repo write lock during a pinning operation\n   */\n  lock?: boolean\n}\n\nexport type AddInput = CID | AddInputWithOptions\n\nexport interface AddInputWithOptions {\n  /**\n   * A CID to pin - nb. you must pass either `cid` or `path`, not both\n   */\n  cid?: CID\n\n  /**\n   * An IPFS path to pin - nb. you must pass either `cid` or `path`, not both\n   */\n  path?: string\n\n  /**\n   * If true, pin all blocked linked to from the pinned CID\n   */\n  recursive?: boolean\n\n  /**\n   * A human readable string to store with this pin\n   */\n  comments?: string\n}\n\nexport type PinType = 'recursive' | 'direct' | 'indirect' | 'all'\n\nexport type PinQueryType = 'recursive' | 'direct' | 'indirect' | 'all'\n\nexport interface LsOptions extends AbortOptions {\n  paths?: CID | CID[] | string | string[]\n  type?: PinQueryType\n}\n\nexport interface LsResult {\n  cid: CID\n  type: PinType | string\n  metadata?: Record<string, any>\n}\n\nexport interface RmOptions extends AbortOptions {\n  recursive?: boolean\n}\n\nexport interface RmAllInput {\n  cid?: CID\n  path?: string\n  recursive?: boolean\n}\n"
  },
  {
    "path": "packages/ipfs-core-types/src/pin/remote/index.ts",
    "content": "import type { CID } from 'multiformats/cid'\nimport type { Multiaddr } from '@multiformats/multiaddr'\nimport type { API as Service } from './service'\nimport type { AbortOptions } from '../../utils'\n\nexport interface API<OptionExtension = {}> {\n  /**\n   * API for configuring remote pinning services.\n   */\n  service: Service\n\n  /**\n   * Pin a content with a given CID to a remote pinning service.\n   */\n  add: (cid: CID, options: AddOptions & AbortOptions & OptionExtension) => Promise<Pin>\n\n  /**\n   * Returns a list of matching pins on the remote pinning service.\n   */\n  ls: (query: Query & AbortOptions & OptionExtension) => AsyncIterable<Pin>\n\n  /**\n   * Removes a single pin object matching query allowing it to be garbage\n   * collected (if needed). Will error if multiple pins match provided\n   * query. To remove all matches use `rmAll` instead.\n   */\n  rm: (query: Query & AbortOptions & OptionExtension) => Promise<void>\n\n  /**\n   * Removes all pin object that match given query allowing them to be garbage\n   * collected if needed.\n   */\n  rmAll: (query: Query & AbortOptions & OptionExtension) => Promise<void>\n}\n\nexport interface AddOptions extends RemoteServiceOptions {\n  /**\n   * Optional name for pinned data; can be used for lookups later (max 255\n   * characters)\n   */\n  name?: string\n\n  /**\n   * Optional list of multiaddrs known to provide the data (max 20).\n   */\n  origins?: Multiaddr[]\n\n  /**\n   * If true, will add to the queue on the remote service and return\n   * immediately. If false or omitted will wait until pinned on the\n   * remote service.\n   */\n  background?: boolean\n}\n\n/**\n * Reperesents query for matching pin objects.\n */\nexport interface Query extends RemoteServiceOptions {\n  /**\n   * If provided, will only include pin objects that have a CID from the given\n   * set.\n   */\n  cid?: CID[]\n  /**\n   * If passed, will only include pin objects with names that have this name\n   * (case-sensitive, exact match).\n   */\n  name?: string\n\n  /**\n   * Return pin objects for pins that have one of the specified status values.\n   * If omitted treated as [\"pinned\"]\n   */\n  status?: Status[]\n}\n\nexport interface RemoteServiceOptions {\n  /**\n   * Name of the remote pinning service to use.\n   */\n  service: string\n}\n\nexport interface Pin {\n  status: Status\n  cid: CID\n  name: string\n}\n\nexport type Status =\n  | 'queued'\n  | 'pinning'\n  | 'pinned'\n  | 'failed'\n"
  },
  {
    "path": "packages/ipfs-core-types/src/pin/remote/service/index.ts",
    "content": "import type { AbortOptions } from '../../../utils'\n\nexport interface API<OptionExtension = {}> {\n  /**\n   * Registers remote pinning service with a given name. Errors if service\n   * with the given name is already registered.\n   */\n  add: (name: string, credentials: Credentials & AbortOptions & OptionExtension) => Promise<void>\n\n  /**\n   * Unregisters remote pinning service with a given name. If service with such\n   * name isn't registered this is a noop.\n   */\n  rm: (name: string, options?: AbortOptions & OptionExtension) => Promise<void>\n\n  /**\n   * List registered remote pinning services.\n   */\n  ls: ((options: { stat: true } & AbortOptions & OptionExtension) => Promise<RemotePinServiceWithStat[]>) & ((options?: AbortOptions & OptionExtension) => Promise<RemotePinService[]>)\n}\n\nexport interface Credentials {\n  /**\n   * Service endpoint\n   */\n  endpoint: URL\n  /**\n   * Service key\n   */\n  key: string\n}\n\nexport interface RemotePinService {\n  /**\n   * Service name\n   */\n  service: string\n  /**\n   * Service endpoint URL\n   */\n  endpoint: URL\n}\n\nexport interface RemotePinServiceWithStat extends RemotePinService {\n  /**\n   * Pin count on the remote service. It is fetched from the remote service and\n   * is done only if `pinCount` option is used. Furthermore it may not be\n   * present if service was unreachable.\n   */\n  stat: Stat\n}\n\nexport type Stat = ValidStat | InvalidStat\n\ninterface ValidStat {\n  status: 'valid'\n  pinCount: PinCount\n}\n\ninterface InvalidStat {\n  status: 'invalid'\n  pinCount?: undefined\n}\n\nexport interface PinCount {\n  queued: number\n  pinning: number\n  pinned: number\n  failed: number\n}\n"
  },
  {
    "path": "packages/ipfs-core-types/src/pubsub/index.ts",
    "content": "import type { AbortOptions } from '../utils'\nimport type { PeerId } from '@libp2p/interface-peer-id'\nimport type { Message } from '@libp2p/interface-pubsub'\nimport type { EventHandler } from '@libp2p/interfaces/events'\n\nexport interface API<OptionExtension = {}> {\n  /**\n   * Subscribe to a pubsub topic\n   *\n   * @example\n   * ```js\n   * const topic = 'fruit-of-the-day'\n   * const receiveMsg = (msg) => console.log(msg.data.toString())\n   *\n   * await ipfs.pubsub.subscribe(topic, receiveMsg)\n   * console.log(`subscribed to ${topic}`)\n   * ```\n   */\n  subscribe: (topic: string, handler: EventHandler<Message>, options?: SubscribeOptions & OptionExtension) => Promise<void>\n\n  /**\n   * Unsubscribes from a pubsub topic\n   *\n   * @example\n   * ```js\n   * const topic = 'fruit-of-the-day'\n   * const receiveMsg = (msg) => console.log(msg.toString())\n   *\n   * await ipfs.pubsub.subscribe(topic, receiveMsg)\n   * console.log(`subscribed to ${topic}`)\n   *\n   * await ipfs.pubsub.unsubscribe(topic, receiveMsg)\n   * console.log(`unsubscribed from ${topic}`)\n   *\n   * // Or removing all listeners:\n   *\n   * const topic = 'fruit-of-the-day'\n   * const receiveMsg = (msg) => console.log(msg.toString())\n   * await ipfs.pubsub.subscribe(topic, receiveMsg);\n   * // Will unsubscribe ALL handlers for the given topic\n   * await ipfs.pubsub.unsubscribe(topic);\n   * ```\n   */\n  unsubscribe: (topic: string, handler?: EventHandler<Message>, options?: AbortOptions & OptionExtension) => Promise<void>\n\n  /**\n   * Publish a data message to a pubsub topic\n   *\n   * @example\n   * ```js\n   * const topic = 'fruit-of-the-day'\n   * const msg = new TextEncoder().encode('banana')\n   *\n   * await ipfs.pubsub.publish(topic, msg)\n   * // msg was broadcasted\n   * console.log(`published to ${topic}`)\n   * ```\n   */\n  publish: (topic: string, data: Uint8Array, options?: AbortOptions & OptionExtension) => Promise<void>\n\n  /**\n   * Returns the list of subscriptions the peer is subscribed to\n   */\n  ls: (options?: AbortOptions & OptionExtension) => Promise<string[]>\n\n  /**\n   * Returns the peers that are subscribed to one topic.\n   *\n   * @example\n   * ```js\n   * const topic = 'fruit-of-the-day'\n   *\n   * const peerIds = await ipfs.pubsub.peers(topic)\n   * console.log(peerIds)\n   * ```\n   */\n  peers: (topic: string, options?: AbortOptions & OptionExtension) => Promise<PeerId[]>\n\n  setMaxListeners?: (max: number) => void\n}\n\nexport interface SubscribeOptions extends AbortOptions {\n  /**\n   * A callback to receive an error if one occurs during processing\n   * subscription messages. Only supported by ipfs-http-client.\n   */\n  onError?: (err: Error) => void\n}\n"
  },
  {
    "path": "packages/ipfs-core-types/src/refs/index.ts",
    "content": "import type { AbortOptions, PreloadOptions, IPFSPath } from '../utils'\n\nexport interface API<OptionExtension = {}> {\n  /**\n   * Get links (references) from an object\n   */\n  refs: Refs<OptionExtension>\n\n  /**\n   * List blocks stored in the local block store\n   */\n  local: Local<OptionExtension>\n}\n\nexport interface Refs<OptionExtension = {}> { (ipfsPath: IPFSPath | IPFSPath[], options?: RefsOptions & OptionExtension): AsyncIterable<RefsResult> }\n\nexport interface RefsOptions extends AbortOptions, PreloadOptions {\n  recursive?: boolean\n  unique?: boolean\n  format?: string\n  edges?: boolean\n  maxDepth?: number\n}\n\nexport interface Local<OptionExtension = {}> { (options?: AbortOptions & OptionExtension): AsyncIterable<RefsResult> }\n\nexport interface RefsResult {\n  ref: string\n  err?: Error\n}\n"
  },
  {
    "path": "packages/ipfs-core-types/src/repo/index.ts",
    "content": "import type { AbortOptions } from '../utils'\nimport type { CID } from 'multiformats/cid'\n\nexport interface API<OptionExtension = {}> {\n  /**\n   * Perform garbage collection on the repo\n   *\n   * Any unpinned blocks will be deleted\n   */\n  gc: (options?: GCOptions & OptionExtension) => AsyncIterable<GCResult>\n\n  /**\n   * Return stats about the repo\n   */\n  stat: (options?: AbortOptions & OptionExtension) => Promise<StatResult>\n\n  /**\n   * If the repo has been initialized, report the current version,\n   * otherwise report the version that would be initialized\n   */\n  version: (options?: AbortOptions & OptionExtension) => Promise<number>\n}\n\nexport interface GCOptions extends AbortOptions {\n  quiet?: boolean\n}\n\nexport interface GCError {\n  err: Error\n  cid?: never\n}\n\nexport interface GCSuccess {\n  err?: never\n  cid: CID\n}\n\nexport type GCResult = GCSuccess | GCError\n\nexport interface StatResult {\n  numObjects: bigint\n  repoPath: string\n  repoSize: bigint\n  version: string\n  storageMax: bigint\n}\n"
  },
  {
    "path": "packages/ipfs-core-types/src/root.ts",
    "content": "import type { AbortOptions, PreloadOptions, IPFSPath, ImportCandidateStream, ImportCandidate } from './utils'\nimport type { CID, Version as CIDVersion } from 'multiformats/cid'\nimport type { Mtime } from 'ipfs-unixfs'\nimport type { Multiaddr } from '@multiformats/multiaddr'\nimport type { PeerId } from '@libp2p/interface-peer-id'\n\nexport interface API<OptionExtension = {}> {\n  /**\n   * Import a file or data into IPFS\n   */\n  add: (entry: ImportCandidate, options?: AddOptions & OptionExtension) => Promise<AddResult>\n\n  /**\n   * Import multiple files and data into IPFS\n   */\n  addAll: (source: ImportCandidateStream, options?: AddAllOptions & AbortOptions & OptionExtension) => AsyncIterable<AddResult>\n\n  /**\n   * Returns content of the file addressed by a valid IPFS Path or CID\n   */\n  cat: (ipfsPath: IPFSPath, options?: CatOptions & OptionExtension) => AsyncIterable<Uint8Array>\n\n  /**\n   * Fetch a file or an entire directory tree from IPFS that is addressed by a\n   * valid IPFS Path\n   */\n  get: (ipfsPath: IPFSPath, options?: GetOptions & OptionExtension) => AsyncIterable<Uint8Array>\n\n  /**\n   * Lists a directory from IPFS that is addressed by a valid IPFS Path\n   */\n  ls: (ipfsPath: IPFSPath, options?: ListOptions & OptionExtension) => AsyncIterable<IPFSEntry>\n\n  /**\n   * Returns the identity of the Peer\n   *\n   * @example\n   * ```js\n   * const identity = await ipfs.id()\n   * console.log(identity)\n   * ```\n   */\n  id: (options?: IDOptions & OptionExtension) => Promise<IDResult>\n\n  /**\n   * Returns the implementation version\n   *\n   * @example\n   * ```js\n   * const version = await ipfs.version()\n   * console.log(version)\n   * ```\n   */\n  version: (options?: AbortOptions & OptionExtension) => Promise<VersionResult>\n\n  /**\n   * Resolve DNS links\n   */\n  dns: (domain: string, options?: DNSOptions & OptionExtension) => Promise<string>\n\n  /**\n   * Start the node\n   */\n  start: () => Promise<void>\n\n  /**\n   * Stop the node\n   */\n  stop: (options?: AbortOptions & OptionExtension) => Promise<void>\n\n  /**\n   * Send echo request packets to IPFS hosts.\n   *\n   * @example\n   * ```js\n   * for await (const res of ipfs.ping('Qmhash')) {\n   *   if (res.time) {\n   *     console.log(`Pong received: time=${res.time} ms`)\n   *   } else {\n   *     console.log(res.text)\n   *   }\n   * }\n   * ```\n   */\n  ping: (peerId: PeerId, options?: PingOptions & OptionExtension) => AsyncIterable<PingResult>\n\n  /**\n   * Resolve the value of names to IPFS\n   *\n   * There are a number of mutable name protocols that can link among themselves\n   * and into IPNS. For example IPNS references can (currently) point at an IPFS\n   * object, and DNS links can point at other DNS links, IPNS entries, or IPFS\n   * objects. This command accepts any of these identifiers and resolves them\n   * to the referenced item.\n   *\n   * @example\n   * ```js\n   * // Resolve the value of your identity:\n   * const name = '/ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy'\n   *\n   * const res = await ipfs.resolve(name)\n   * console.log(res)\n   * // Logs: /ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj\n   *\n   * // Resolve the value of another name recursively:\n   * const name = '/ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n'\n   *\n   * // Where:\n   * // /ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n\n   * // ...resolves to:\n   * // /ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy\n   * // ...which in turn resolves to:\n   * // /ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj\n   *\n   * const res = await ipfs.resolve(name, { recursive: true })\n   * console.log(res)\n   * // Logs: /ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj\n   *\n   * // Resolve the value of an IPFS path:\n   * const name = '/ipfs/QmeZy1fGbwgVSrqbfh9fKQrAWgeyRnj7h8fsHS1oy3k99x/beep/boop'\n   * const res = await ipfs.resolve(name)\n   * console.log(res)\n   * // Logs: /ipfs/QmYRMjyvAiHKN9UTi8Bzt1HUspmSRD8T8DwxfSMzLgBon1\n   * ```\n   */\n  resolve: (name: string, options?: ResolveOptions & OptionExtension) => Promise<string>\n\n  /**\n   * Returns a list of available commands\n   */\n  commands: (options?: AbortOptions & OptionExtension) => Promise<string[]>\n\n  mount: (options?: MountOptions & OptionExtension) => Promise<MountResult>\n\n  /**\n   * Returns true if this IPFS node is online - that is, it's listening on network addresses\n   * for incoming connections\n   */\n  isOnline: () => boolean\n}\n\nexport interface IPFSEntry {\n  readonly type: 'dir' | 'file'\n  readonly cid: CID\n  readonly name: string\n  readonly path: string\n  mode?: number\n  mtime?: Mtime\n  size: number\n}\n\nexport interface AddProgressFn { (bytes: number, path?: string): void }\n\nexport interface AddOptions extends AbortOptions {\n  /**\n   * Chunking algorithm used to build ipfs DAGs. (defaults to 'size-262144')\n   */\n  chunker?: string\n  /**\n   * The CID version to use when storing the data\n   */\n  cidVersion?: CIDVersion\n\n  /**\n   * Multihash hashing algorithm to use. (Defaults to 'sha2-256')\n   */\n  hashAlg?: string\n\n  /**\n   * If true, will not add blocks to the blockstore. (Defaults to `false`)\n   */\n  onlyHash?: boolean\n\n  /**\n   * Pin this object when adding. (Defaults to `true`)\n   */\n  pin?: boolean\n\n  /**\n   * A function that will be called with the number of bytes added as a file is\n   * added to ipfs and the path of the file being added.\n   *\n   * **Note** It will not be called for directory entries.\n   */\n  progress?: AddProgressFn\n\n  /**\n   * If true, DAG leaves will contain raw file data and not be wrapped in a\n   * protobuf. (Defaults to `false`)\n   */\n  rawLeaves?: boolean\n\n  /**\n   * If true will use the\n   * [trickle DAG](https://godoc.org/github.com/ipsn/go-ipfs/gxlibs/github.com/ipfs/go-unixfs/importer/trickle)\n   * format for DAG generation. (Defaults to `false`).\n   */\n  trickle?: boolean\n\n  /**\n   * Adds a wrapping node around the content. (Defaults to `false`)\n   */\n  wrapWithDirectory?: boolean\n\n  /**\n   * Whether to preload all blocks created during this operation\n   */\n  preload?: boolean\n\n  /**\n   * How many blocks from a file to write concurrently\n   */\n  blockWriteConcurrency?: number\n}\n\nexport interface AddAllOptions extends AddOptions {\n\n  /**\n   * Allows to create directories with an unlimited number of entries currently\n   * size of unixfs directories is limited by the maximum block size.\n   * ** Note ** that this is an experimental feature. (Defaults to `false`)\n   */\n  enableShardingExperiment?: boolean\n\n  /**\n   * Directories with more than this number of files will be created as HAMT -\n   * sharded directories. (Defaults to 1000)\n   */\n  shardSplitThreshold?: number\n\n  /**\n   * How many files to write concurrently\n   */\n  fileImportConcurrency?: number\n}\n\nexport interface AddResult {\n  cid: CID\n  size: number\n  path: string\n  mode?: number\n  mtime?: Mtime\n}\n\nexport interface ShardingOptions {\n  sharding?: boolean\n}\n\nexport interface CatOptions extends AbortOptions, PreloadOptions {\n  /**\n   * An offset to start reading the file from\n   */\n  offset?: number\n  /**\n   * An optional max length to read from the file\n   */\n  length?: number\n}\n\nexport interface GetOptions extends AbortOptions, PreloadOptions {\n  archive?: boolean\n  compress?: boolean\n  compressionLevel?: -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9\n}\n\nexport interface ListOptions extends AbortOptions, PreloadOptions {\n\n}\n\nexport interface IDOptions extends AbortOptions {\n  peerId?: PeerId\n}\n\nexport interface IDResult {\n  id: PeerId\n  publicKey: string\n  addresses: Multiaddr[]\n  agentVersion: string\n  protocolVersion: string\n  protocols: string[]\n}\n\n/**\n * An object with the version information for the implementation,\n * the commit and the Repo. `js-ipfs` instances will also return\n * the version of `interface-ipfs-core` and `ipfs-http-client`\n * supported by this node\n */\nexport interface VersionResult {\n  version: string\n  commit?: string\n  repo?: string\n  system?: string\n  golang?: string\n  'ipfs-core'?: string\n  'interface-ipfs-core'?: string\n  'ipfs-http-client'?: string\n}\n\nexport interface DNSOptions extends AbortOptions {\n  recursive?: boolean\n}\n\nexport interface PingOptions extends AbortOptions {\n  count?: number\n}\n\nexport interface PingResult {\n  success: boolean\n  time: number\n  text: string\n}\n\nexport interface ResolveOptions extends AbortOptions {\n  recursive?: boolean\n  cidBase?: string\n}\n\nexport interface MountOptions extends AbortOptions {\n  ipfsPath?: string\n  ipnsPath?: string\n}\n\nexport interface MountResult {\n  fuseAllowOther?: boolean\n  ipfs?: string\n  ipns?: string\n}\n"
  },
  {
    "path": "packages/ipfs-core-types/src/stats/index.ts",
    "content": "import type { AbortOptions } from '../utils'\nimport type { API as BitswapAPI } from '../bitswap'\nimport type { API as RepoAPI } from '../repo'\nimport type { PeerId } from '@libp2p/interface-peer-id'\n\nexport interface API<OptionExtension = {}> {\n  bitswap: BitswapAPI<OptionExtension>['stat']\n  repo: RepoAPI<OptionExtension>['stat']\n\n  /**\n   * Return bandwith usage stats\n   */\n  bw: (options?: BWOptions & OptionExtension) => AsyncIterable<BWResult>\n}\n\nexport interface BWOptions extends AbortOptions {\n  /**\n   * Specifies a peer to print bandwidth for\n   */\n  peer?: PeerId\n\n  /**\n   * Specifies a protocol to print bandwidth for\n   */\n  proto?: string\n\n  /**\n   * Is used to yield bandwidth info at an interval\n   */\n  poll?: boolean\n\n  /**\n   * The time interval to wait between updating output, if `poll` is `true`.\n   */\n  interval?: number | string\n}\n\nexport interface BWResult {\n  totalIn: bigint\n  totalOut: bigint\n  rateIn: number\n  rateOut: number\n}\n"
  },
  {
    "path": "packages/ipfs-core-types/src/swarm/index.ts",
    "content": "import type { AbortOptions } from '../utils'\nimport type { Multiaddr } from '@multiformats/multiaddr'\nimport type { PeerId } from '@libp2p/interface-peer-id'\n\nexport interface API<OptionExtension = {}> {\n  /**\n   * List of known addresses of each peer connected\n   */\n  addrs: (options?: AbortOptions & OptionExtension) => Promise<AddrsResult[]>\n\n  /**\n   * Open a connection to a given address or peer id\n   */\n  connect: (multiaddrOrPeerId: Multiaddr | PeerId, options?: AbortOptions & OptionExtension) => Promise<void>\n\n  /**\n   * Close a connection to a given address or peer id\n   */\n  disconnect: (multiaddrOrPeerId: Multiaddr | PeerId, options?: AbortOptions & OptionExtension) => Promise<void>\n\n  /**\n   * Local addresses this node is listening on\n   */\n  localAddrs: (options?: AbortOptions & OptionExtension) => Promise<Multiaddr[]>\n\n  /**\n   * Return a list of connected peers\n   */\n  peers: (options?: PeersOptions & OptionExtension) => Promise<PeersResult[]>\n}\n\nexport interface AddrsResult {\n  id: PeerId\n  addrs: Multiaddr[]\n}\n\nexport interface PeersOptions extends AbortOptions {\n  direction?: boolean\n  streams?: boolean\n  verbose?: boolean\n  latency?: boolean\n}\n\nexport interface PeersResult {\n  addr: Multiaddr\n  peer: PeerId\n  latency?: string\n  muxer?: string\n  streams?: string[]\n  direction?: 'inbound' | 'outbound'\n}\n"
  },
  {
    "path": "packages/ipfs-core-types/src/utils.ts",
    "content": "import type { CID } from 'multiformats/cid'\nimport type { Mtime, MtimeLike } from 'ipfs-unixfs'\n\nexport type Entry<Content extends AsyncIterable<Uint8Array> | Blob> =\n  | FileEntry<Content>\n  | DirectoryEntry\n\nexport interface BaseEntry {\n  path: string\n  mode?: number\n  mtime?: Mtime\n}\nexport interface FileEntry <Content extends AsyncIterable<Uint8Array> | Blob> extends BaseEntry {\n  content?: Content\n}\n\nexport interface DirectoryEntry extends BaseEntry {\n  content?: undefined\n}\n\nexport type ImportCandidateStream =\n| AwaitIterable<ImportCandidate>\n| ReadableStream<ImportCandidate>\n\nexport type ImportCandidate =\n  | ToFile\n  | ToDirectory\n  | ToContent\n\nexport interface ToFile extends ToFileMetadata {\n  path?: string\n  content: ToContent\n}\n\nexport interface ToDirectory extends ToFileMetadata {\n  path: string\n  content?: undefined\n}\n\nexport interface ToFileMetadata {\n  mode?: ToMode\n  mtime?: MtimeLike\n}\n\n/**\n * File content in arbitrary (supported) representation. It is used in input\n * positions and is usually normalized to `Blob` in browser contexts and\n * `AsyncIterable<Uint8Array>` in node.\n */\nexport type ToContent =\n  | string\n  | InstanceType<typeof String>\n  | ArrayBufferView\n  | ArrayBuffer\n  | Blob\n  | AwaitIterable<Uint8Array>\n  | ReadableStream<Uint8Array>\n\nexport type ToMode =\n  | string\n  | number\n\nexport interface BaseFile {\n  cid: CID\n  path: string\n  name: string\n}\n\nexport interface InputFile extends BaseFile {\n  unixfs: undefined\n}\n\nexport interface BrowserImportCandidate {\n  path?: string\n  content?: Blob\n  mtime?: Mtime\n  mode?: number\n}\n\n/**\n * Represents a value that you can await on, which is either value or a promise\n * of one.\n */\nexport type Await<T> =\n  | T\n  | Promise<T>\n\n/**\n * Represents an iterable that can be used in `for await` loops, that is either\n * iterable or an async iterable.\n */\nexport type AwaitIterable<T> =\n  | Iterable<T>\n  | AsyncIterable<T>\n\n/**\n * Common options across all cancellable requests.\n */\nexport interface AbortOptions {\n  /**\n   * Can be provided to a function that starts a long running task, which will\n   * be aborted when signal is triggered.\n   */\n  signal?: AbortSignal\n  /**\n   * Can be provided to a function that starts a long running task, which will\n   * be aborted after provided timeout (in ms).\n   */\n  timeout?: number\n}\n\nexport interface PreloadOptions {\n  preload?: boolean\n}\n\n/**\n * An IPFS path or CID\n */\nexport type IPFSPath = CID | string\n\nexport interface BufferStore {\n  put: (key: Uint8Array, value: Uint8Array, options?: AbortOptions) => Promise<void>\n  get: (key: Uint8Array, options?: AbortOptions) => Promise<Uint8Array>\n  stores: any[]\n}\n"
  },
  {
    "path": "packages/ipfs-core-types/tsconfig.json",
    "content": "{\n  \"extends\": \"aegir/src/config/tsconfig.aegir.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\"\n  },\n  \"include\": [\n    \"src\"\n  ]\n}\n"
  },
  {
    "path": "packages/ipfs-core-utils/.aegir.js",
    "content": "\n/** @type {import('aegir').PartialOptions} */\nexport default {\n  build: {\n    bundlesizeMax: '540B'\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core-utils/CHANGELOG.md",
    "content": "# Change Log\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n\n### [0.18.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-utils-v0.18.0...ipfs-core-utils-v0.18.1) (2023-05-25)\n\n\n### Bug Fixes\n\n* add deprecation notice to readmes ([#4362](https://www.github.com/ipfs/js-ipfs/issues/4362)) ([7b79c1b](https://www.github.com/ipfs/js-ipfs/commit/7b79c1b8df5c818dc124b346ea28330455732d5c))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.14.0 to ^0.14.1\n\n## [0.18.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-utils-v0.17.0...ipfs-core-utils-v0.18.0) (2023-01-11)\n\n\n### ⚠ BREAKING CHANGES\n\n* update multiformats to v11.x.x and related depenendcies (#4277)\n\n### Bug Fixes\n\n* update multiformats to v11.x.x and related depenendcies ([#4277](https://www.github.com/ipfs/js-ipfs/issues/4277)) ([521c84a](https://www.github.com/ipfs/js-ipfs/commit/521c84a958b04d61702577a5adce28519c1b2a3b))\n* use aegir to publish RCs ([#4284](https://www.github.com/ipfs/js-ipfs/issues/4284)) ([6d90cbf](https://www.github.com/ipfs/js-ipfs/commit/6d90cbf321a7dbf4b1084ba20f0c514dc08d8d0a))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.13.0 to ^0.14.0\n\n## [0.17.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-utils-v0.16.1...ipfs-core-utils-v0.17.0) (2022-10-24)\n\n\n### ⚠ BREAKING CHANGES\n\n* ipfs is now bundled with libp2p@0.40.x which has different config\n\n### Features\n\n* upgrade libp2p to 0.40.x ([#4237](https://www.github.com/ipfs/js-ipfs/issues/4237)) ([0cee4a4](https://www.github.com/ipfs/js-ipfs/commit/0cee4a4c55767022584dcbade0b0b9b43326f9c9))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.12.1 to ^0.13.0\n\n### [0.16.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-utils-v0.16.0...ipfs-core-utils-v0.16.1) (2022-09-21)\n\n\n### Bug Fixes\n\n* update @multiformats/multiadd to 11.0.0 ([2a830bf](https://www.github.com/ipfs/js-ipfs/commit/2a830bf58a5929fcce51dede871c99f62192fbda))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.12.0 to ^0.12.1\n\n## [0.16.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-utils-v0.15.1...ipfs-core-utils-v0.16.0) (2022-09-06)\n\n\n### ⚠ BREAKING CHANGES\n\n* update to libp2p@0.38.x (#4151)\n\n### deps\n\n* update to libp2p@0.38.x ([#4151](https://www.github.com/ipfs/js-ipfs/issues/4151)) ([39dbf70](https://www.github.com/ipfs/js-ipfs/commit/39dbf708ec31b263115e44f420651fa4e056a89e))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.11.0 to ^0.12.0\n\n### [0.15.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-utils-v0.15.0...ipfs-core-utils-v0.15.1) (2022-06-22)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.11.0 to ^0.11.1\n\n## [0.15.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-utils-v0.14.3...ipfs-core-utils-v0.15.0) (2022-05-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* This module is now ESM only and there return types of some methods have changed\n\n### Features\n\n* update to libp2p 0.37.x ([#4092](https://www.github.com/ipfs/js-ipfs/issues/4092)) ([74aee8b](https://www.github.com/ipfs/js-ipfs/commit/74aee8b3d78f233c3199a3e9a6c0ac628a31a433))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.3 to ^0.11.0\n\n### [0.14.3](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-utils-v0.14.2...ipfs-core-utils-v0.14.3) (2022-04-20)\n\n\n### Bug Fixes\n\n* exclude fs from bundle ([#4076](https://www.github.com/ipfs/js-ipfs/issues/4076)) ([6c3cb73](https://www.github.com/ipfs/js-ipfs/commit/6c3cb73db7b46211c88431273f61f04463a4f80d))\n* upgrade dep of ipfs-utils ^9.0.2->^9.0.6 ([#4086](https://www.github.com/ipfs/js-ipfs/issues/4086)) ([8f7ce23](https://www.github.com/ipfs/js-ipfs/commit/8f7ce23c18be12bdc52b98bfccbd0a5a2a9c9f7e)), closes [#4080](https://www.github.com/ipfs/js-ipfs/issues/4080)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.2 to ^0.10.3\n\n### [0.14.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-utils-v0.14.1...ipfs-core-utils-v0.14.2) (2022-03-01)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.1 to ^0.10.2\n\n### [0.14.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-utils-v0.14.0...ipfs-core-utils-v0.14.1) (2022-02-06)\n\n\n### Bug Fixes\n\n* **dag:** replace custom dag walk with multiformats/traversal ([#3950](https://www.github.com/ipfs/js-ipfs/issues/3950)) ([596b1f4](https://www.github.com/ipfs/js-ipfs/commit/596b1f48a014083b1736e4ad7e746c652d2583b1))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.0 to ^0.10.1\n\n## [0.14.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-core-utils-v0.13.0...ipfs-core-utils-v0.14.0) (2022-01-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* peerstore methods are now all async, the repo is migrated to v12\n* node 15+ is required\n\n### Features\n\n* libp2p async peerstore ([#4018](https://www.github.com/ipfs/js-ipfs/issues/4018)) ([a6b201a](https://www.github.com/ipfs/js-ipfs/commit/a6b201af2c3697430ab0ebe002dd573d185f1ac0))\n\n\n### Bug Fixes\n\n* remove abort-controller deps ([#4015](https://www.github.com/ipfs/js-ipfs/issues/4015)) ([902e887](https://www.github.com/ipfs/js-ipfs/commit/902e887e1acac87f607324fa7cb5ad4b14aefcf3))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.9.0 to ^0.10.0\n\n## [0.13.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.12.2...ipfs-core-utils@0.13.0) (2021-12-15)\n\n\n### Features\n\n* dht client ([#3947](https://github.com/ipfs/js-ipfs/issues/3947)) ([62d8ecb](https://github.com/ipfs/js-ipfs/commit/62d8ecbc723e693a2544e69172d99c576d187c23))\n\n\n### BREAKING CHANGES\n\n* The DHT API has been refactored to return async iterators of query events\n\n\n## [0.12.2](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.12.1...ipfs-core-utils@0.12.2) (2021-11-24)\n\n\n### Bug Fixes\n\n* typo ([#3946](https://github.com/ipfs/js-ipfs/issues/3946)) ([70c67e2](https://github.com/ipfs/js-ipfs/commit/70c67e27d0b82c7650a2a0d7a646afdcd24e73c2))\n\n\n\n\n\n## [0.12.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.12.0...ipfs-core-utils@0.12.1) (2021-11-19)\n\n**Note:** Version bump only for package ipfs-core-utils\n\n\n\n\n\n## [0.12.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.11.1...ipfs-core-utils@0.12.0) (2021-11-12)\n\n\n### Bug Fixes\n\n* do not accept single items for ipfs.add ([#3900](https://github.com/ipfs/js-ipfs/issues/3900)) ([04e3cf3](https://github.com/ipfs/js-ipfs/commit/04e3cf3f46b585c4644cba70516f375e95361f52))\n\n\n### BREAKING CHANGES\n\n* errors will now be thrown if multiple items are passed to `ipfs.add` or single items to `ipfs.addAll` (n.b. you can still pass a list of a single item to `ipfs.addAll`)\n\n\n\n\n\n## [0.11.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.11.0...ipfs-core-utils@0.11.1) (2021-09-28)\n\n**Note:** Version bump only for package ipfs-core-utils\n\n\n\n\n\n## [0.11.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.10.5...ipfs-core-utils@0.11.0) (2021-09-24)\n\n\n### Bug Fixes\n\n* handle node readable streams properly ([#3890](https://github.com/ipfs/js-ipfs/issues/3890)) ([b0f367d](https://github.com/ipfs/js-ipfs/commit/b0f367d666aceb4ea8bdd532a9d8c3501f8cc78d)), closes [#3882](https://github.com/ipfs/js-ipfs/issues/3882)\n\n\n### Features\n\n* pull in new globSource ([#3889](https://github.com/ipfs/js-ipfs/issues/3889)) ([be4a542](https://github.com/ipfs/js-ipfs/commit/be4a5428ebc4b05a2edd9a91bf9df6416c1a8c2b))\n* switch to esm ([#3879](https://github.com/ipfs/js-ipfs/issues/3879)) ([9a40109](https://github.com/ipfs/js-ipfs/commit/9a40109632e5b4837eb77a2f57dbc77fbf1fe099))\n\n\n### BREAKING CHANGES\n\n* the globSource api has changed from `globSource(dir, opts)` to `globSource(dir, pattern, opts)`\n* There are no default exports and everything is now dual published as ESM/CJS\n\n\n\n\n\n## [0.10.5](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.10.4...ipfs-core-utils@0.10.5) (2021-09-17)\n\n**Note:** Version bump only for package ipfs-core-utils\n\n\n\n\n\n## [0.10.4](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.10.3...ipfs-core-utils@0.10.4) (2021-09-17)\n\n**Note:** Version bump only for package ipfs-core-utils\n\n\n\n\n\n## [0.10.3](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.10.2...ipfs-core-utils@0.10.3) (2021-09-02)\n\n\n### Bug Fixes\n\n* declare types in .ts files ([#3840](https://github.com/ipfs/js-ipfs/issues/3840)) ([eba5fe6](https://github.com/ipfs/js-ipfs/commit/eba5fe6832858107b3e1ae02c99de674622f12b4))\n* remove use of instanceof for CID class ([#3847](https://github.com/ipfs/js-ipfs/issues/3847)) ([ebbb12d](https://github.com/ipfs/js-ipfs/commit/ebbb12db523c53ce8e4ddae5266cd9acb3504431))\n\n\n\n\n\n## [0.10.2](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.10.1...ipfs-core-utils@0.10.2) (2021-08-25)\n\n**Note:** Version bump only for package ipfs-core-utils\n\n\n\n\n\n## [0.10.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.10.0...ipfs-core-utils@0.10.1) (2021-08-17)\n\n\n### Bug Fixes\n\n* throw error on missing input to add/addAll ([#3818](https://github.com/ipfs/js-ipfs/issues/3818)) ([1343708](https://github.com/ipfs/js-ipfs/commit/1343708f70d7298b6677555803d68ff282d89439)), closes [#3788](https://github.com/ipfs/js-ipfs/issues/3788)\n\n\n\n\n\n## [0.10.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.9.1...ipfs-core-utils@0.10.0) (2021-08-11)\n\n\n### Features\n\n* make ipfs.get output tarballs ([#3785](https://github.com/ipfs/js-ipfs/issues/3785)) ([1ad6001](https://github.com/ipfs/js-ipfs/commit/1ad60018d39d5b46c484756631e30e1989fd8eba))\n\n\n### BREAKING CHANGES\n\n* the output type of `ipfs.get` has changed and the `recursive` option has been removed from `ipfs.ls` since it was not supported everywhere\n\n\n\n\n\n### [0.9.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.9.0...ipfs-core-utils@0.9.1) (2021-07-30)\n\n**Note:** Version bump only for package ipfs-core-utils\n\n\n\n\n\n## [0.9.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.8.3...ipfs-core-utils@0.9.0) (2021-07-27)\n\n\n### Bug Fixes\n\n* support @web-std/file in normalize input ([#3750](https://github.com/ipfs/js-ipfs/issues/3750)) ([6fd7776](https://github.com/ipfs/js-ipfs/commit/6fd777679d0aa80bbb784d16585456e54b5cf294))\n\n\n### Features\n\n* implement dag import/export ([#3728](https://github.com/ipfs/js-ipfs/issues/3728)) ([700765b](https://github.com/ipfs/js-ipfs/commit/700765be2634fa5d2d71d8b87cf68c9cd328d2c4)), closes [#2953](https://github.com/ipfs/js-ipfs/issues/2953) [#2745](https://github.com/ipfs/js-ipfs/issues/2745)\n* upgrade to the new multiformats ([#3556](https://github.com/ipfs/js-ipfs/issues/3556)) ([d13d15f](https://github.com/ipfs/js-ipfs/commit/d13d15f022a87d04a35f0f7822142f9cb898479c))\n\n\n### BREAKING CHANGES\n\n* ipld-formats no longer supported, use multiformat BlockCodecs instead\n\nCo-authored-by: Rod Vagg <rod@vagg.org>\nCo-authored-by: achingbrain <alex@achingbrain.net>\n\n\n\n\n\n### [0.8.3](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.8.2...ipfs-core-utils@0.8.3) (2021-06-18)\n\n**Note:** Version bump only for package ipfs-core-utils\n\n\n\n\n\n### [0.8.2](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.8.1...ipfs-core-utils@0.8.2) (2021-06-05)\n\n\n### Bug Fixes\n\n* stalling subscription on (node) http-client when daemon is stopped ([#3468](https://github.com/ipfs/js-ipfs/issues/3468)) ([0266abf](https://github.com/ipfs/js-ipfs/commit/0266abf0c4b817636172f78c6e91eb4dd5aad451)), closes [#3465](https://github.com/ipfs/js-ipfs/issues/3465)\n\n\n\n\n\n### [0.8.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.8.0...ipfs-core-utils@0.8.1) (2021-05-26)\n\n**Note:** Version bump only for package ipfs-core-utils\n\n\n\n\n\n## [0.8.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.7.2...ipfs-core-utils@0.8.0) (2021-05-10)\n\n\n### Bug Fixes\n\n* mark ipld options as partial ([#3669](https://github.com/ipfs/js-ipfs/issues/3669)) ([f98af8e](https://github.com/ipfs/js-ipfs/commit/f98af8ed24784929898bb5d33a64dc442c77074d))\n\n\n### chore\n\n* upgrade deps with new typedefs ([#3550](https://github.com/ipfs/js-ipfs/issues/3550)) ([a418a52](https://github.com/ipfs/js-ipfs/commit/a418a521574c878d7aabd0ad2fd8d516908a3756))\n\n\n### BREAKING CHANGES\n\n* all core api methods now have types, some method signatures have changed, named exports are now used by the http, grpc and ipfs client modules\n\n\n\n\n\n### [0.7.2](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.7.1...ipfs-core-utils@0.7.2) (2021-03-09)\n\n\n### Bug Fixes\n\n* update to new aegir ([#3528](https://github.com/ipfs/js-ipfs/issues/3528)) ([49f7880](https://github.com/ipfs/js-ipfs/commit/49f78807d7e26483bd926b45cc7e0f797d77e41b))\n\n\n\n\n\n### [0.7.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.7.0...ipfs-core-utils@0.7.1) (2021-02-08)\n\n**Note:** Version bump only for package ipfs-core-utils\n\n\n\n\n\n## [0.7.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.6.1...ipfs-core-utils@0.7.0) (2021-02-01)\n\n\n### chore\n\n* update deps ([#3514](https://github.com/ipfs/js-ipfs/issues/3514)) ([061d77c](https://github.com/ipfs/js-ipfs/commit/061d77cc03f40af5a3bc3590481e1e5836e7f0d8))\n\n\n### BREAKING CHANGES\n\n* ipfs-repo upgrade requires repo migration to v10\n\n\n\n\n\n### [0.6.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.6.0...ipfs-core-utils@0.6.1) (2021-01-22)\n\n**Note:** Version bump only for package ipfs-core-utils\n\n\n\n\n\n## [0.6.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.5.4...ipfs-core-utils@0.6.0) (2021-01-15)\n\n\n### Features\n\n* allow passing a http.Agent to the grpc client ([#3477](https://github.com/ipfs/js-ipfs/issues/3477)) ([c5f0bc5](https://github.com/ipfs/js-ipfs/commit/c5f0bc5eeee15369b7d02901035b04184a8608d2)), closes [#3474](https://github.com/ipfs/js-ipfs/issues/3474)\n\n\n\n\n\n### [0.5.4](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.5.3...ipfs-core-utils@0.5.4) (2020-12-16)\n\n\n### Bug Fixes\n\n* regressions introduced by new releases of CID & multicodec ([#3442](https://github.com/ipfs/js-ipfs/issues/3442)) ([b5152d8](https://github.com/ipfs/js-ipfs/commit/b5152d8cc93ecc8d39fc353ea66d7eaf1661e3c0)), closes [/github.com/multiformats/js-cid/commit/0e11f035c9230e7f6d79c159ace9b80de88cb5eb#diff-25a6634263c1b1f6fc4697a04e2b9904ea4b042a89af59dc93ec1f5d44848a26](https://github.com//github.com/multiformats/js-cid/commit/0e11f035c9230e7f6d79c159ace9b80de88cb5eb/issues/diff-25a6634263c1b1f6fc4697a04e2b9904ea4b042a89af59dc93ec1f5d44848a26)\n* types for withTimeoutOptions ([#3422](https://github.com/ipfs/js-ipfs/issues/3422)) ([af0b7f3](https://github.com/ipfs/js-ipfs/commit/af0b7f34587bd432860a31d40eabc6aa70aef619)), closes [/github.com/ipfs/js-ipfs/pull/3407/files#diff-722621abc3ed4edc6ab202fdf684f1607c261394b95da6b3ec79748711056f20](https://github.com//github.com/ipfs/js-ipfs/pull/3407/files/issues/diff-722621abc3ed4edc6ab202fdf684f1607c261394b95da6b3ec79748711056f20)\n\n\n\n\n\n### [0.5.3](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.5.2...ipfs-core-utils@0.5.3) (2020-11-25)\n\n**Note:** Version bump only for package ipfs-core-utils\n\n\n\n\n\n### [0.5.2](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.5.1...ipfs-core-utils@0.5.2) (2020-11-16)\n\n\n### Bug Fixes\n\n* report ipfs.add progress over http ([#3310](https://github.com/ipfs/js-ipfs/issues/3310)) ([39cad4b](https://github.com/ipfs/js-ipfs/commit/39cad4b76b950ea6a76477fd01f8631b8bd9aa1e))\n\n\n\n\n\n### [0.5.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.5.0...ipfs-core-utils@0.5.1) (2020-11-09)\n\n\n### Bug Fixes\n\n* typedef resolution & add examples that use types ([#3359](https://github.com/ipfs/js-ipfs/issues/3359)) ([dc2795a](https://github.com/ipfs/js-ipfs/commit/dc2795a4f3b515683d09967ce611bf87d5e67f86)), closes [#3356](https://github.com/ipfs/js-ipfs/issues/3356) [#3358](https://github.com/ipfs/js-ipfs/issues/3358)\n\n\n\n\n\n## [0.5.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.4.0...ipfs-core-utils@0.5.0) (2020-10-28)\n\n\n### Bug Fixes\n\n* use fetch in electron renderer and electron-fetch in main ([#3251](https://github.com/ipfs/js-ipfs/issues/3251)) ([639d71f](https://github.com/ipfs/js-ipfs/commit/639d71f7ac8f66d9633e753a2a6be927e14a5af0))\n\n\n### Features\n\n* type check & generate defs from jsdoc ([#3281](https://github.com/ipfs/js-ipfs/issues/3281)) ([bbcaf34](https://github.com/ipfs/js-ipfs/commit/bbcaf34111251b142273a5675f4754ff68bd9fa0))\n\n\n\n\n\n## [0.4.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.3.2...ipfs-core-utils@0.4.0) (2020-09-03)\n\n\n### Features\n\n* store pins in datastore instead of a DAG ([#2771](https://github.com/ipfs/js-ipfs/issues/2771)) ([64b7fe4](https://github.com/ipfs/js-ipfs/commit/64b7fe41738cbe96d5a9075f0c01156c6f889c40))\n\n\n\n\n\n### [0.3.2](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.3.1...ipfs-core-utils@0.3.2) (2020-08-24)\n\n\n### Bug Fixes\n\n* validate ipns records with inline public keys ([#3224](https://github.com/ipfs/js-ipfs/issues/3224)) ([5cc0e08](https://github.com/ipfs/js-ipfs/commit/5cc0e086b036e7ba40b09768b67b7067adca43c1))\n\n\n\n\n\n### [0.3.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.3.0...ipfs-core-utils@0.3.1) (2020-08-12)\n\n\n### Bug Fixes\n\n* send blobs when running ipfs-http-client in the browser ([#3184](https://github.com/ipfs/js-ipfs/issues/3184)) ([6b24463](https://github.com/ipfs/js-ipfs/commit/6b24463431497bd13b579a730ad7063345729ad9)), closes [#3138](https://github.com/ipfs/js-ipfs/issues/3138)\n\n\n\n\n\n## [0.3.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.2.4...ipfs-core-utils@0.3.0) (2020-07-16)\n\n\n### Bug Fixes\n\n* optional arguments go in the options object ([#3118](https://github.com/ipfs/js-ipfs/issues/3118)) ([8cb8c73](https://github.com/ipfs/js-ipfs/commit/8cb8c73037e44894d756b70f344b3282463206f9))\n* set error code correctly ([#3150](https://github.com/ipfs/js-ipfs/issues/3150)) ([335c13d](https://github.com/ipfs/js-ipfs/commit/335c13d529fc54e4610fc1aa03212126f43c63ec))\n\n\n### Features\n\n* store blocks by multihash instead of CID ([#3124](https://github.com/ipfs/js-ipfs/issues/3124)) ([03b17f5](https://github.com/ipfs/js-ipfs/commit/03b17f5e2d290e84aa0cb541079b79e468e7d1bd))\n\n\n\n\n\n### [0.2.4](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.2.3...ipfs-core-utils@0.2.4) (2020-06-24)\n\n**Note:** Version bump only for package ipfs-core-utils\n\n\n\n\n\n### [0.2.3](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.2.2...ipfs-core-utils@0.2.3) (2020-05-18)\n\n\n### Bug Fixes\n\n* remove node globals ([#2932](https://github.com/ipfs/js-ipfs/issues/2932)) ([d0d2f74](https://github.com/ipfs/js-ipfs/commit/d0d2f74cef4e439c6d2baadba1f1f9f52534fcba))\n\n\n### Features\n\n* cancellable api calls ([#2993](https://github.com/ipfs/js-ipfs/issues/2993)) ([2b24f59](https://github.com/ipfs/js-ipfs/commit/2b24f590041a0df9da87b75ae2344232fe22fe3a)), closes [#3015](https://github.com/ipfs/js-ipfs/issues/3015)\n\n\n\n\n\n### [0.2.2](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.2.1...ipfs-core-utils@0.2.2) (2020-05-05)\n\n**Note:** Version bump only for package ipfs-core-utils\n\n\n\n\n\n### [0.2.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.2.0...ipfs-core-utils@0.2.1) (2020-05-05)\n\n\n### Bug Fixes\n\n* pass headers to request ([#3018](https://github.com/ipfs/js-ipfs/issues/3018)) ([3ba00f8](https://github.com/ipfs/js-ipfs/commit/3ba00f8c6a8a057c5776d539a671a74d9565fb29)), closes [#3017](https://github.com/ipfs/js-ipfs/issues/3017)\n\n\n\n\n\n## [0.2.0](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.1.1...ipfs-core-utils@0.2.0) (2020-04-16)\n\n\n### Bug Fixes\n\n* make http api only accept POST requests ([#2977](https://github.com/ipfs/js-ipfs/issues/2977)) ([943d4a8](https://github.com/ipfs/js-ipfs/commit/943d4a8cf2d4c4ff5ecd4814c59cb0aae0cfa1fd))\n\n\n### BREAKING CHANGES\n\n* Where we used to accept all and any HTTP methods, now only POST is\naccepted.  The API client will now only send POST requests too.\n\n* test: add tests to make sure we are post-only\n\n* chore: upgrade ipfs-utils\n\n* fix: return 405 instead of 404 for bad methods\n\n* fix: reject browsers that do not send an origin\n\nAlso fixes running interface tests over http in browsers against\njs-ipfs\n\n\n\n\n\n### [0.1.1](https://github.com/ipfs/js-ipfs/compare/ipfs-core-utils@0.0.1...ipfs-core-utils@0.1.1) (2020-04-08)\n\n**Note:** Version bump only for package ipfs-core-utils\n\n\n\n\n\n## 0.0.1 (2020-03-31)\n\n**Note:** Version bump only for package ipfs-core-utils\n\n\n\n\n\n<a name=\"0.7.2\"></a>\n### [0.7.2](https://github.com/ipfs/js-ipfs-utils/compare/v0.7.1...v0.7.2) (2020-02-10)\n\n\n### Bug Fixes\n\n* number is not a valid mtime value ([#24](https://github.com/ipfs/js-ipfs-utils/issues/24)) ([bb2d841](https://github.com/ipfs/js-ipfs-utils/commit/bb2d841)), closes [/github.com/ipfs/js-ipfs-unixfs/blob/master/src/index.js#L104-L106](https://github.com//github.com/ipfs/js-ipfs-unixfs/blob/master/src/index.js/issues/L104-L106)\n\n\n\n<a name=\"0.7.1\"></a>\n### [0.7.1](https://github.com/ipfs/js-ipfs-utils/compare/v0.7.0...v0.7.1) (2020-01-23)\n\n\n### Bug Fixes\n\n* downgrade to ky 15 ([#22](https://github.com/ipfs/js-ipfs-utils/issues/22)) ([5dd7570](https://github.com/ipfs/js-ipfs-utils/commit/5dd7570))\n\n\n\n<a name=\"0.7.0\"></a>\n## [0.7.0](https://github.com/ipfs/js-ipfs-utils/compare/v0.6.0...v0.7.0) (2020-01-23)\n\n\n### Features\n\n* accept browser readable streams as input ([#21](https://github.com/ipfs/js-ipfs-utils/issues/21)) ([0902067](https://github.com/ipfs/js-ipfs-utils/commit/0902067))\n\n\n\n<a name=\"0.6.0\"></a>\n## [0.6.0](https://github.com/ipfs/js-ipfs-utils/compare/v0.5.0...v0.6.0) (2020-01-09)\n\n\n### Bug Fixes\n\n* dependency badge URL ([#16](https://github.com/ipfs/js-ipfs-utils/issues/16)) ([5d93881](https://github.com/ipfs/js-ipfs-utils/commit/5d93881))\n* format mtime as timespec ([#20](https://github.com/ipfs/js-ipfs-utils/issues/20)) ([a68f8b1](https://github.com/ipfs/js-ipfs-utils/commit/a68f8b1))\n\n\n\n<a name=\"0.5.0\"></a>\n## [0.5.0](https://github.com/ipfs/js-ipfs-utils/compare/v0.4.0...v0.5.0) (2019-12-06)\n\n\n### Features\n\n* convert to async iterators ([#15](https://github.com/ipfs/js-ipfs-utils/issues/15)) ([251eff0](https://github.com/ipfs/js-ipfs-utils/commit/251eff0))\n* support unixfs metadata and formatting it ([#14](https://github.com/ipfs/js-ipfs-utils/issues/14)) ([173e4bf](https://github.com/ipfs/js-ipfs-utils/commit/173e4bf))\n\n\n### BREAKING CHANGES\n\n* In order to support metadata on intermediate directories, globSource in this module will now emit directories and files where previously it only emitted files.\n* Support for Node.js streams and Pull Streams has been removed\n\n\n\n<a name=\"0.4.0\"></a>\n## [0.4.0](https://github.com/ipfs/js-ipfs-utils/compare/v0.3.0...v0.4.0) (2019-09-19)\n\n\n### Features\n\n* add isElectronMain env test ([#13](https://github.com/ipfs/js-ipfs-utils/issues/13)) ([9072c90](https://github.com/ipfs/js-ipfs-utils/commit/9072c90))\n\n\n\n<a name=\"0.3.0\"></a>\n## [0.3.0](https://github.com/ipfs/js-ipfs-utils/compare/v0.2.0...v0.3.0) (2019-09-15)\n\n\n### Features\n\n* support old school streams ([#12](https://github.com/ipfs/js-ipfs-utils/issues/12)) ([18cfa86](https://github.com/ipfs/js-ipfs-utils/commit/18cfa86))\n\n\n\n<a name=\"0.2.0\"></a>\n## [0.2.0](https://github.com/ipfs/js-ipfs-utils/compare/v0.1.0...v0.2.0) (2019-09-06)\n\n\n### Features\n\n* env/isTest ([#10](https://github.com/ipfs/js-ipfs-utils/issues/10)) ([481aab1](https://github.com/ipfs/js-ipfs-utils/commit/481aab1))\n\n\n\n<a name=\"0.1.0\"></a>\n## [0.1.0](https://github.com/ipfs/js-ipfs-utils/compare/v0.0.4...v0.1.0) (2019-09-04)\n\n\n### Bug Fixes\n\n* write after end ([#7](https://github.com/ipfs/js-ipfs-utils/issues/7)) ([b30d7a3](https://github.com/ipfs/js-ipfs-utils/commit/b30d7a3))\n\n\n### Features\n\n* add glob-source from js-ipfs to be shared ([#9](https://github.com/ipfs/js-ipfs-utils/issues/9)) ([0a95ef8](https://github.com/ipfs/js-ipfs-utils/commit/0a95ef8))\n* add normalise input function ([#5](https://github.com/ipfs/js-ipfs-utils/issues/5)) ([b22b8de](https://github.com/ipfs/js-ipfs-utils/commit/b22b8de)), closes [#8](https://github.com/ipfs/js-ipfs-utils/issues/8)\n\n\n\n<a name=\"0.0.4\"></a>\n### [0.0.4](https://github.com/ipfs/js-ipfs-utils/compare/v0.0.3...v0.0.4) (2019-07-18)\n\n\n### Features\n\n* add globalThis polyfill ([f0c7c42](https://github.com/ipfs/js-ipfs-utils/commit/f0c7c42))\n\n\n\n<a name=\"0.0.3\"></a>\n### [0.0.3](https://github.com/ipfs/js-ipfs-utils/compare/v0.0.2...v0.0.3) (2019-05-16)\n\n\n\n<a name=\"0.0.2\"></a>\n## 0.0.2 (2019-05-16)\n\n\n### Bug Fixes\n\n* use is-buffer ([bbf5baf](https://github.com/ipfs/js-ipfs-utils/commit/bbf5baf))"
  },
  {
    "path": "packages/ipfs-core-utils/LICENSE",
    "content": "This project is dual licensed under MIT and Apache-2.0.\n\nMIT: https://www.opensource.org/licenses/mit\nApache-2.0: https://www.apache.org/licenses/license-2.0\n"
  },
  {
    "path": "packages/ipfs-core-utils/LICENSE-APACHE",
    "content": "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\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless 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.\n"
  },
  {
    "path": "packages/ipfs-core-utils/LICENSE-MIT",
    "content": "The MIT License (MIT)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "packages/ipfs-core-utils/README.md",
    "content": "> # ⛔️ DEPRECATED: [js-IPFS](https://github.com/ipfs/js-ipfs) has been superseded by [Helia](https://github.com/ipfs/helia)\n>\n> 📚 [Learn more about this deprecation](https://github.com/ipfs/js-ipfs/issues/4336) or [how to migrate](https://github.com/ipfs/helia/wiki/Migrating-from-js-IPFS)\n>\n> ⚠️ If you continue using this repo, please note that security fixes will not be provided\n\n# ipfs-core-utils <!-- omit in toc -->\n\n[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech)\n[![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech)\n[![codecov](https://img.shields.io/codecov/c/github/ipfs/js-ipfs.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipfs)\n[![CI](https://img.shields.io/github/actions/workflow/status/ipfs/js-ipfs/test.yml?branch=master\\&style=flat-square)](https://github.com/ipfs/js-ipfs/actions/workflows/test.yml?query=branch%3Amaster)\n\n> Package to share code between ipfs and ipfs-http-client\n\n## Table of contents <!-- omit in toc -->\n\n- [Install](#install)\n  - [Browser `<script>` tag](#browser-script-tag)\n- [License](#license)\n- [Contribute](#contribute)\n\n## Install\n\n```console\n$ npm i ipfs-core-utils\n```\n\n### Browser `<script>` tag\n\nLoading this module through a script tag will make it's exports available as `IpfsCoreUtils` in the global namespace.\n\n```html\n<script src=\"https://unpkg.com/ipfs-core-utils/dist/index.min.js\"></script>\n```\n\n## License\n\nLicensed under either of\n\n- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / <http://www.apache.org/licenses/LICENSE-2.0>)\n- MIT ([LICENSE-MIT](LICENSE-MIT) / <http://opensource.org/licenses/MIT>)\n\n## Contribute\n\nContributions welcome! Please check out [the issues](https://github.com/ipfs/js-ipfs/issues).\n\nAlso 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.\n\nPlease 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).\n\nUnless 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.\n\n[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md)\n"
  },
  {
    "path": "packages/ipfs-core-utils/package.json",
    "content": "{\n  \"name\": \"ipfs-core-utils\",\n  \"version\": \"0.18.1\",\n  \"description\": \"Package to share code between ipfs and ipfs-http-client\",\n  \"author\": \"Alex Potsides <alex@achingbrain.net>\",\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"homepage\": \"https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-core-utils#readme\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ipfs/js-ipfs.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ipfs/js-ipfs/issues\"\n  },\n  \"engines\": {\n    \"node\": \">=16.0.0\",\n    \"npm\": \">=7.0.0\"\n  },\n  \"type\": \"module\",\n  \"types\": \"./dist/src/index.d.ts\",\n  \"typesVersions\": {\n    \"*\": {\n      \"*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ],\n      \"src/*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ]\n    }\n  },\n  \"files\": [\n    \"src\",\n    \"dist\",\n    \"!dist/test\",\n    \"!**/*.tsbuildinfo\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/src/index.d.ts\",\n      \"import\": \"./src/index.js\"\n    },\n    \"./agent\": {\n      \"types\": \"./src/agent.d.ts\",\n      \"browser\": \"./src/agent.browser.js\",\n      \"import\": \"./src/agent.js\"\n    },\n    \"./errors\": {\n      \"types\": \"./src/errors.d.ts\",\n      \"import\": \"./src/errors.js\"\n    },\n    \"./files/format-mode\": {\n      \"types\": \"./src/files/format-mode.d.ts\",\n      \"import\": \"./src/files/format-mode.js\"\n    },\n    \"./files/format-mtime\": {\n      \"types\": \"./src/files/format-mtime.d.ts\",\n      \"import\": \"./src/files/format-mtime.js\"\n    },\n    \"./files/normalise-content\": {\n      \"types\": \"./src/files/normalise-content.d.ts\",\n      \"import\": \"./src/files/normalise-content.js\"\n    },\n    \"./files/normalise-content.browser\": {\n      \"types\": \"./src/files/normalise-content.browser.d.ts\",\n      \"import\": \"./src/files/normalise-content.browser.js\"\n    },\n    \"./files/normalise-input-multiple\": {\n      \"types\": \"./src/files/normalise-input-multiple.d.ts\",\n      \"import\": \"./src/files/normalise-input-multiple.js\"\n    },\n    \"./files/normalise-input-multiple.browser\": {\n      \"types\": \"./src/files/normalise-input-multiple.browser.d.ts\",\n      \"import\": \"./src/files/normalise-input-multiple.browser.js\"\n    },\n    \"./files/normalise-input-single\": {\n      \"types\": \"./src/files/normalise-input-single.d.ts\",\n      \"import\": \"./src/files/normalise-input-single.js\"\n    },\n    \"./files/normalise-input-single.browser\": {\n      \"types\": \"./src/files/normalise-input-single.browser.d.ts\",\n      \"import\": \"./src/files/normalise-input-single.browser.js\"\n    },\n    \"./multibases\": {\n      \"types\": \"./src/multibases.d.ts\",\n      \"import\": \"./src/multibases.js\"\n    },\n    \"./multicodecs\": {\n      \"types\": \"./src/multicodecs.d.ts\",\n      \"import\": \"./src/multicodecs.js\"\n    },\n    \"./multihashes\": {\n      \"types\": \"./src/multihashes.d.ts\",\n      \"import\": \"./src/multihashes.js\"\n    },\n    \"./multipart-request\": {\n      \"types\": \"./src/multipart-request.d.ts\",\n      \"browser\": \"./src/multipart-request.browser.js\",\n      \"import\": \"./src/multipart-request.js\"\n    },\n    \"./pins/normalise-input\": {\n      \"types\": \"./src/pins/normalise-input.d.ts\",\n      \"import\": \"./src/pins/normalise-input.js\"\n    },\n    \"./to-cid-and-path\": {\n      \"types\": \"./src/to-cid-and-path.d.ts\",\n      \"import\": \"./src/to-cid-and-path.js\"\n    },\n    \"./to-url-string\": {\n      \"types\": \"./src/to-url-string.d.ts\",\n      \"import\": \"./src/to-url-string.js\"\n    },\n    \"./with-timeout-option\": {\n      \"types\": \"./src/with-timeout-option.d.ts\",\n      \"import\": \"./src/with-timeout-option.js\"\n    }\n  },\n  \"eslintConfig\": {\n    \"extends\": \"ipfs\",\n    \"parserOptions\": {\n      \"sourceType\": \"module\"\n    }\n  },\n  \"scripts\": {\n    \"test\": \"aegir test\",\n    \"test:node\": \"aegir test -t node --cov\",\n    \"test:chrome\": \"aegir test -t browser --cov\",\n    \"test:chrome-webworker\": \"aegir test -t webworker\",\n    \"test:firefox\": \"aegir test -t browser -- --browser firefox\",\n    \"test:firefox-webworker\": \"aegir test -t webworker -- --browser firefox\",\n    \"test:electron-main\": \"aegir test -t electron-main\",\n    \"lint\": \"aegir lint\",\n    \"clean\": \"aegir clean\",\n    \"dep-check\": \"aegir dep-check -i ipfs-core-types\",\n    \"build\": \"aegir build\"\n  },\n  \"dependencies\": {\n    \"@libp2p/logger\": \"^2.0.5\",\n    \"@multiformats/multiaddr\": \"^11.1.5\",\n    \"@multiformats/multiaddr-to-uri\": \"^9.0.1\",\n    \"any-signal\": \"^3.0.0\",\n    \"blob-to-it\": \"^2.0.0\",\n    \"browser-readablestream-to-it\": \"^2.0.0\",\n    \"err-code\": \"^3.0.1\",\n    \"ipfs-core-types\": \"^0.14.1\",\n    \"ipfs-unixfs\": \"^9.0.0\",\n    \"ipfs-utils\": \"^9.0.13\",\n    \"it-all\": \"^2.0.0\",\n    \"it-map\": \"^2.0.0\",\n    \"it-peekable\": \"^2.0.0\",\n    \"it-to-stream\": \"^1.0.0\",\n    \"merge-options\": \"^3.0.4\",\n    \"multiformats\": \"^11.0.0\",\n    \"nanoid\": \"^4.0.0\",\n    \"parse-duration\": \"^1.0.0\",\n    \"timeout-abort-controller\": \"^3.0.0\",\n    \"uint8arrays\": \"^4.0.2\"\n  },\n  \"devDependencies\": {\n    \"@web-std/file\": \"^3.0.2\",\n    \"aegir\": \"^37.11.0\"\n  },\n  \"browser\": {\n    \"fs\": false\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core-utils/src/agent.browser.js",
    "content": "\nexport default () => {}\n"
  },
  {
    "path": "packages/ipfs-core-utils/src/agent.js",
    "content": "import http from 'http'\nimport https from 'https'\n\n/**\n * @param {URL} [url]\n */\nexport default (url) => {\n  if (!url) {\n    throw new Error('URL required')\n  }\n\n  return url.protocol.startsWith('https') ? https.Agent : http.Agent\n}\n"
  },
  {
    "path": "packages/ipfs-core-utils/src/errors.js",
    "content": "\nexport class TimeoutError extends Error {\n  constructor (message = 'request timed out') {\n    super(message)\n    this.name = 'TimeoutError'\n    this.code = TimeoutError.code\n  }\n}\n\nTimeoutError.code = 'ERR_TIMEOUT'\n"
  },
  {
    "path": "packages/ipfs-core-utils/src/files/format-mode.js",
    "content": "const S_ISUID = parseInt('4000', 8) //   set UID bit\nconst S_ISGID = parseInt('2000', 8) //   set-group-ID bit (see below)\nconst S_ISVTX = parseInt('1000', 8) //   sticky bit (see below)\n// const S_IRWXU = parseInt('700', 8) //    mask for file owner permissions\nconst S_IRUSR = parseInt('400', 8) //    owner has read permission\nconst S_IWUSR = parseInt('200', 8) //    owner has write permission\nconst S_IXUSR = parseInt('100', 8) //    owner has execute permission\n// const S_IRWXG = parseInt('70', 8) //     mask for group permissions\nconst S_IRGRP = parseInt('40', 8) //     group has read permission\nconst S_IWGRP = parseInt('20', 8) //     group has write permission\nconst S_IXGRP = parseInt('10', 8) //     group has execute permission\n// const S_IRWXO = parseInt('7', 8) //      mask for permissions for others (not in group)\nconst S_IROTH = parseInt('4', 8) //      others have read permission\nconst S_IWOTH = parseInt('2', 8) //      others have write permission\nconst S_IXOTH = parseInt('1', 8) //      others have execute permission\n\n/**\n * @param {number} mode\n * @param {number} perm\n * @param {string} type\n * @param {string[]} output\n */\nfunction checkPermission (mode, perm, type, output) {\n  if ((mode & perm) === perm) {\n    output.push(type)\n  } else {\n    output.push('-')\n  }\n}\n\n/**\n * @param {number} mode\n * @param {boolean} isDirectory\n * @returns {string}\n */\nexport function formatMode (mode, isDirectory) {\n  const output = []\n\n  if (isDirectory) {\n    output.push('d')\n  } else {\n    output.push('-')\n  }\n\n  checkPermission(mode, S_IRUSR, 'r', output)\n  checkPermission(mode, S_IWUSR, 'w', output)\n\n  if ((mode & S_ISUID) === S_ISUID) {\n    output.push('s')\n  } else {\n    checkPermission(mode, S_IXUSR, 'x', output)\n  }\n\n  checkPermission(mode, S_IRGRP, 'r', output)\n  checkPermission(mode, S_IWGRP, 'w', output)\n\n  if ((mode & S_ISGID) === S_ISGID) {\n    output.push('s')\n  } else {\n    checkPermission(mode, S_IXGRP, 'x', output)\n  }\n\n  checkPermission(mode, S_IROTH, 'r', output)\n  checkPermission(mode, S_IWOTH, 'w', output)\n\n  if ((mode & S_ISVTX) === S_ISVTX) {\n    output.push('t')\n  } else {\n    checkPermission(mode, S_IXOTH, 'x', output)\n  }\n\n  return output.join('')\n}\n"
  },
  {
    "path": "packages/ipfs-core-utils/src/files/format-mtime.js",
    "content": "/**\n * @param {import('ipfs-unixfs').Mtime | null} [mtime]\n * @returns {string}\n */\nexport function formatMtime (mtime) {\n  if (mtime == null) {\n    return '-'\n  }\n\n  const date = new Date((mtime.secs * 1000) + Math.round((mtime.nsecs || 0) / 1000))\n\n  return date.toLocaleDateString(Intl.DateTimeFormat().resolvedOptions().locale, {\n    year: 'numeric',\n    month: 'short',\n    day: 'numeric',\n    hour: '2-digit',\n    minute: '2-digit',\n    second: '2-digit',\n    timeZoneName: 'short'\n  })\n}\n"
  },
  {
    "path": "packages/ipfs-core-utils/src/files/normalise-candidate-multiple.js",
    "content": "import errCode from 'err-code'\nimport browserStreamToIt from 'browser-readablestream-to-it'\nimport itPeekable from 'it-peekable'\nimport map from 'it-map'\nimport {\n  isBytes,\n  isBlob,\n  isReadableStream,\n  isFileObject\n} from './utils.js'\nimport {\n  parseMtime,\n  parseMode\n} from 'ipfs-unixfs'\n\n/**\n * @typedef {import('ipfs-core-types/src/utils').ImportCandidate} ImportCandidate\n * @typedef {import('ipfs-core-types/src/utils').ToContent} ToContent\n * @typedef {import('ipfs-unixfs-importer').ImportCandidate} ImporterImportCandidate\n * @typedef {import('ipfs-core-types/src/utils').ImportCandidateStream} ImportCandidateStream\n */\n\n/**\n * @param {ImportCandidateStream} input\n * @param {(content:ToContent) => Promise<AsyncIterable<Uint8Array>>} normaliseContent\n */\n// eslint-disable-next-line complexity\nexport async function * normaliseCandidateMultiple (input, normaliseContent) {\n  // String\n  // Uint8Array|ArrayBuffer|TypedArray\n  // Blob|File\n  // fs.ReadStream\n  // @ts-expect-error _readableState is a property of a node fs.ReadStream\n  if (typeof input === 'string' || input instanceof String || isBytes(input) || isBlob(input) || input._readableState) {\n    throw errCode(new Error('Unexpected input: single item passed - if you are using ipfs.addAll, please use ipfs.add instead'), 'ERR_UNEXPECTED_INPUT')\n  }\n\n  // Browser ReadableStream\n  if (isReadableStream(input)) {\n    input = browserStreamToIt(input)\n  }\n\n  // Iterable<?>\n  if (Symbol.iterator in input || Symbol.asyncIterator in input) {\n    const peekable = itPeekable(input)\n    const { value, done } = await peekable.peek()\n\n    if (done) {\n      // make sure empty iterators result in empty files\n      yield * []\n      return\n    }\n\n    peekable.push(value)\n\n    // (Async)Iterable<Number>\n    // (Async)Iterable<Bytes>\n    if (Number.isInteger(value)) {\n      throw errCode(new Error('Unexpected input: single item passed - if you are using ipfs.addAll, please use ipfs.add instead'), 'ERR_UNEXPECTED_INPUT')\n    }\n\n    // (Async)Iterable<fs.ReadStream>\n    // @ts-expect-error private field\n    if (value._readableState) {\n      // @ts-expect-error Node fs.ReadStreams have a `.path` property so we need to pass it as the content\n      yield * map(peekable, (/** @type {ImportCandidate} */ value) => toFileObject({ content: value }, normaliseContent))\n      return\n    }\n\n    if (isBytes(value)) {\n      // @ts-expect-error peekable is still an iterable of ImportCandidates\n      yield toFileObject({ content: peekable }, normaliseContent)\n      return\n    }\n\n    // (Async)Iterable<(Async)Iterable<?>>\n    // (Async)Iterable<ReadableStream<?>>\n    // ReadableStream<(Async)Iterable<?>>\n    // ReadableStream<ReadableStream<?>>\n    if (isFileObject(value) || value[Symbol.iterator] || value[Symbol.asyncIterator] || isReadableStream(value) || isBlob(value)) {\n      yield * map(peekable, (/** @type {ImportCandidate} */ value) => toFileObject(value, normaliseContent))\n      return\n    }\n  }\n\n  // { path, content: ? }\n  // Note: Detected _after_ (Async)Iterable<?> because Node.js fs.ReadStreams have a\n  // `path` property that passes this check.\n  if (isFileObject(input)) {\n    throw errCode(new Error('Unexpected input: single item passed - if you are using ipfs.addAll, please use ipfs.add instead'), 'ERR_UNEXPECTED_INPUT')\n  }\n\n  throw errCode(new Error('Unexpected input: ' + typeof input), 'ERR_UNEXPECTED_INPUT')\n}\n\n/**\n * @param {ImportCandidate} input\n * @param {(content:ToContent) => Promise<AsyncIterable<Uint8Array>>} normaliseContent\n */\nasync function toFileObject (input, normaliseContent) {\n  // @ts-expect-error - Those properties don't exist on most input types\n  const { path, mode, mtime, content } = input\n\n  /** @type {ImporterImportCandidate} */\n  const file = {\n    path: path || '',\n    mode: parseMode(mode),\n    mtime: parseMtime(mtime)\n  }\n\n  if (content) {\n    file.content = await normaliseContent(content)\n  } else if (!path) { // Not already a file object with path or content prop\n    // @ts-expect-error - input still can be different ToContent\n    file.content = await normaliseContent(input)\n  }\n\n  return file\n}\n"
  },
  {
    "path": "packages/ipfs-core-utils/src/files/normalise-candidate-single.js",
    "content": "import errCode from 'err-code'\nimport browserStreamToIt from 'browser-readablestream-to-it'\nimport itPeekable from 'it-peekable'\nimport {\n  isBytes,\n  isBlob,\n  isReadableStream,\n  isFileObject\n} from './utils.js'\nimport {\n  parseMtime,\n  parseMode\n} from 'ipfs-unixfs'\n\n/**\n * @typedef {import('ipfs-core-types/src/utils').ToContent} ToContent\n * @typedef {import('ipfs-unixfs-importer').ImportCandidate} ImporterImportCandidate\n * @typedef {import('ipfs-core-types/src/utils').ImportCandidate} ImportCandidate\n * @typedef {import('ipfs-core-types/src/utils').ImportCandidateStream} ImportCandidateStream\n */\n\n/**\n * @param {ImportCandidate} input\n * @param {(content:ToContent) => Promise<AsyncIterable<Uint8Array>>} normaliseContent\n */\n// eslint-disable-next-line complexity\nexport async function * normaliseCandidateSingle (input, normaliseContent) {\n  if (input === null || input === undefined) {\n    throw errCode(new Error(`Unexpected input: ${input}`), 'ERR_UNEXPECTED_INPUT')\n  }\n\n  // String\n  if (typeof input === 'string' || input instanceof String) {\n    yield toFileObject(input.toString(), normaliseContent)\n    return\n  }\n\n  // Uint8Array|ArrayBuffer|TypedArray\n  // Blob|File\n  if (isBytes(input) || isBlob(input)) {\n    yield toFileObject(input, normaliseContent)\n    return\n  }\n\n  // Browser ReadableStream\n  if (isReadableStream(input)) {\n    input = browserStreamToIt(input)\n  }\n\n  // Iterable<?>\n  if (Symbol.iterator in input || Symbol.asyncIterator in input) {\n    const peekable = itPeekable(input)\n\n    /** @type {any} value **/\n    const { value, done } = await peekable.peek()\n\n    if (done) {\n      // make sure empty iterators result in empty files\n      yield { content: [] }\n      return\n    }\n\n    peekable.push(value)\n\n    // (Async)Iterable<Number>\n    // (Async)Iterable<Bytes>\n    // (Async)Iterable<String>\n    if (Number.isInteger(value) || isBytes(value) || typeof value === 'string' || value instanceof String) {\n      yield toFileObject(peekable, normaliseContent)\n      return\n    }\n\n    throw errCode(new Error('Unexpected input: multiple items passed - if you are using ipfs.add, please use ipfs.addAll instead'), 'ERR_UNEXPECTED_INPUT')\n  }\n\n  // { path, content: ? }\n  // Note: Detected _after_ (Async)Iterable<?> because Node.js fs.ReadStreams have a\n  // `path` property that passes this check.\n  if (isFileObject(input)) {\n    yield toFileObject(input, normaliseContent)\n    return\n  }\n\n  throw errCode(new Error('Unexpected input: cannot convert \"' + typeof input + '\" into ImportCandidate'), 'ERR_UNEXPECTED_INPUT')\n}\n\n/**\n * @param {ImportCandidate} input\n * @param {(content:ToContent) => Promise<AsyncIterable<Uint8Array>>} normaliseContent\n */\nasync function toFileObject (input, normaliseContent) {\n  // @ts-expect-error - Those properties don't exist on most input types\n  const { path, mode, mtime, content } = input\n\n  /** @type {ImporterImportCandidate} */\n  const file = {\n    path: path || '',\n    mode: parseMode(mode),\n    mtime: parseMtime(mtime)\n  }\n\n  if (content) {\n    file.content = await normaliseContent(content)\n  } else if (!path) { // Not already a file object with path or content prop\n    // @ts-expect-error - input still can be different ToContent\n    file.content = await normaliseContent(input)\n  }\n\n  return file\n}\n"
  },
  {
    "path": "packages/ipfs-core-utils/src/files/normalise-content.browser.js",
    "content": "import errCode from 'err-code'\nimport itPeekable from 'it-peekable'\nimport browserStreamToIt from 'browser-readablestream-to-it'\nimport all from 'it-all'\nimport {\n  isBytes,\n  isBlob,\n  isReadableStream\n} from './utils.js'\n\n/**\n * @param {import('ipfs-core-types/src/utils').ToContent} input\n */\nexport async function normaliseContent (input) {\n  // Bytes\n  if (isBytes(input)) {\n    return new Blob([input])\n  }\n\n  // String\n  if (typeof input === 'string' || input instanceof String) {\n    return new Blob([input.toString()])\n  }\n\n  // Blob | File\n  if (isBlob(input)) {\n    return input\n  }\n\n  // Browser stream\n  if (isReadableStream(input)) {\n    input = browserStreamToIt(input)\n  }\n\n  // (Async)Iterator<?>\n  if (Symbol.iterator in input || Symbol.asyncIterator in input) {\n    /** @type {any} peekable */\n    const peekable = itPeekable(input)\n\n    /** @type {any} value **/\n    const { value, done } = await peekable.peek()\n\n    if (done) {\n      // make sure empty iterators result in empty files\n      return itToBlob(peekable)\n    }\n\n    peekable.push(value)\n\n    // (Async)Iterable<Number>\n    if (Number.isInteger(value)) {\n      return new Blob([Uint8Array.from(await all(peekable))])\n    }\n\n    // (Async)Iterable<Bytes|String>\n    if (isBytes(value) || typeof value === 'string' || value instanceof String) {\n      return itToBlob(peekable)\n    }\n  }\n\n  throw errCode(new Error(`Unexpected input: ${input}`), 'ERR_UNEXPECTED_INPUT')\n}\n\n/**\n * @param {AsyncIterable<BlobPart>|Iterable<BlobPart>} stream\n */\nasync function itToBlob (stream) {\n  const parts = []\n\n  for await (const chunk of stream) {\n    parts.push(chunk)\n  }\n\n  return new Blob(parts)\n}\n"
  },
  {
    "path": "packages/ipfs-core-utils/src/files/normalise-content.js",
    "content": "import errCode from 'err-code'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport browserStreamToIt from 'browser-readablestream-to-it'\nimport blobToIt from 'blob-to-it'\nimport itPeekable from 'it-peekable'\nimport all from 'it-all'\nimport map from 'it-map'\nimport {\n  isBytes,\n  isReadableStream,\n  isBlob\n} from './utils.js'\n\n/**\n * @template T\n * @param {T} thing\n */\nasync function * toAsyncIterable (thing) {\n  yield thing\n}\n\n/**\n * @param {import('ipfs-core-types/src/utils').ToContent} input\n */\nexport async function normaliseContent (input) {\n  // Bytes | String\n  if (isBytes(input)) {\n    return toAsyncIterable(toBytes(input))\n  }\n\n  if (typeof input === 'string' || input instanceof String) {\n    return toAsyncIterable(toBytes(input.toString()))\n  }\n\n  // Blob\n  if (isBlob(input)) {\n    return blobToIt(input)\n  }\n\n  // Browser stream\n  if (isReadableStream(input)) {\n    input = browserStreamToIt(input)\n  }\n\n  // (Async)Iterator<?>\n  if (Symbol.iterator in input || Symbol.asyncIterator in input) {\n    /** @type {any} peekable */\n    const peekable = itPeekable(input)\n\n    /** @type {any} value */\n    const { value, done } = await peekable.peek()\n\n    if (done) {\n      // make sure empty iterators result in empty files\n      return toAsyncIterable(new Uint8Array(0))\n    }\n\n    peekable.push(value)\n\n    // (Async)Iterable<Number>\n    if (Number.isInteger(value)) {\n      return toAsyncIterable(Uint8Array.from(await all(peekable)))\n    }\n\n    // (Async)Iterable<Bytes|String>\n    if (isBytes(value) || typeof value === 'string' || value instanceof String) {\n      return map(peekable, toBytes)\n    }\n  }\n\n  throw errCode(new Error(`Unexpected input: ${input}`), 'ERR_UNEXPECTED_INPUT')\n}\n\n/**\n * @param {ArrayBuffer | ArrayBufferView | string | InstanceType<typeof window.String> | number[]} chunk\n */\nfunction toBytes (chunk) {\n  if (chunk instanceof Uint8Array) {\n    return chunk\n  }\n\n  if (ArrayBuffer.isView(chunk)) {\n    return new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength)\n  }\n\n  if (chunk instanceof ArrayBuffer) {\n    return new Uint8Array(chunk)\n  }\n\n  if (Array.isArray(chunk)) {\n    return Uint8Array.from(chunk)\n  }\n\n  return uint8ArrayFromString(chunk.toString())\n}\n"
  },
  {
    "path": "packages/ipfs-core-utils/src/files/normalise-input-multiple.browser.js",
    "content": "import { normaliseContent } from './normalise-content.browser.js'\nimport { normaliseCandidateMultiple } from './normalise-candidate-multiple.js'\n\n/**\n * @typedef {import('ipfs-core-types/src/utils').ImportCandidateStream} ImportCandidateStream\n * @typedef {import('ipfs-core-types/src/utils').BrowserImportCandidate} BrowserImportCandidate\n */\n\n/**\n * Transforms any of the `ipfs.addAll` input types into\n *\n * ```\n * AsyncIterable<{ path, mode, mtime, content: Blob }>\n * ```\n *\n * See https://github.com/ipfs/js-ipfs/blob/master/docs/core-api/FILES.md#ipfsadddata-options\n *\n * @param {ImportCandidateStream} input\n * @returns {AsyncGenerator<BrowserImportCandidate, void, undefined>}\n */\nexport function normaliseInput (input) {\n  // @ts-expect-error browser normaliseContent returns a Blob not an AsyncIterable<Uint8Array>\n  return normaliseCandidateMultiple(input, normaliseContent, true)\n}\n"
  },
  {
    "path": "packages/ipfs-core-utils/src/files/normalise-input-multiple.js",
    "content": "import { normaliseContent } from './normalise-content.js'\nimport { normaliseCandidateMultiple } from './normalise-candidate-multiple.js'\n\n/**\n * @typedef {import('ipfs-core-types/src/utils').ImportCandidateStream} ImportCandidateStream\n */\n\n/**\n * Transforms any of the `ipfs.addAll` input types into\n *\n * ```\n * AsyncIterable<{ path, mode, mtime, content: AsyncIterable<Uint8Array> }>\n * ```\n *\n * See https://github.com/ipfs/js-ipfs/blob/master/docs/core-api/FILES.md#ipfsadddata-options\n *\n * @param {ImportCandidateStream} input\n */\nexport function normaliseInput (input) {\n  return normaliseCandidateMultiple(input, normaliseContent)\n}\n"
  },
  {
    "path": "packages/ipfs-core-utils/src/files/normalise-input-single.browser.js",
    "content": "import { normaliseContent } from './normalise-content.browser.js'\nimport { normaliseCandidateSingle } from './normalise-candidate-single.js'\n\n/**\n * @typedef {import('ipfs-core-types/src/utils').ImportCandidate} ImportCandidate\n * @typedef {import('ipfs-core-types/src/utils').BrowserImportCandidate} BrowserImportCandidate\n */\n\n/**\n * Transforms any of the `ipfs.add` input types into\n *\n * ```\n * AsyncIterable<{ path, mode, mtime, content: Blob }>\n * ```\n *\n * See https://github.com/ipfs/js-ipfs/blob/master/docs/core-api/FILES.md#ipfsadddata-options\n *\n * @param {ImportCandidate} input\n * @returns {BrowserImportCandidate}\n */\nexport function normaliseInput (input) {\n  // @ts-expect-error browser normaliseContent returns a Blob not an AsyncIterable<Uint8Array>\n  return normaliseCandidateSingle(input, normaliseContent)\n}\n"
  },
  {
    "path": "packages/ipfs-core-utils/src/files/normalise-input-single.js",
    "content": "import { normaliseContent } from './normalise-content.js'\nimport { normaliseCandidateSingle } from './normalise-candidate-single.js'\n\n/**\n * @typedef {import('ipfs-core-types/src/utils').ImportCandidate} ImportCandidate\n */\n\n/**\n * Transforms any of the `ipfs.add` input types into\n *\n * ```\n * AsyncIterable<{ path, mode, mtime, content: AsyncIterable<Uint8Array> }>\n * ```\n *\n * See https://github.com/ipfs/js-ipfs/blob/master/docs/core-api/FILES.md#ipfsadddata-options\n *\n * @param {ImportCandidate} input\n */\nexport function normaliseInput (input) {\n  return normaliseCandidateSingle(input, normaliseContent)\n}\n"
  },
  {
    "path": "packages/ipfs-core-utils/src/files/utils.js",
    "content": "/**\n * @param {any} obj\n * @returns {obj is ArrayBufferView|ArrayBuffer}\n */\nexport function isBytes (obj) {\n  return ArrayBuffer.isView(obj) || obj instanceof ArrayBuffer\n}\n\n/**\n * @param {any} obj\n * @returns {obj is globalThis.Blob}\n */\nexport function isBlob (obj) {\n  return obj.constructor &&\n    (obj.constructor.name === 'Blob' || obj.constructor.name === 'File') &&\n    typeof obj.stream === 'function'\n}\n\n/**\n * An object with a path or content property\n *\n * @param {any} obj\n * @returns {obj is import('ipfs-core-types/src/utils').ImportCandidate}\n */\nexport function isFileObject (obj) {\n  return typeof obj === 'object' && (obj.path || obj.content)\n}\n\n/**\n * @param {any} value\n * @returns {value is ReadableStream}\n */\nexport const isReadableStream = (value) =>\n  value && typeof value.getReader === 'function'\n"
  },
  {
    "path": "packages/ipfs-core-utils/src/index.js",
    "content": "\n"
  },
  {
    "path": "packages/ipfs-core-utils/src/mode-to-string.js",
    "content": "\n/**\n * @param {number | string | undefined} mode\n */\nexport function modeToString (mode) {\n  if (mode == null) {\n    return undefined\n  }\n\n  if (typeof mode === 'string') {\n    return mode\n  }\n\n  return mode.toString(8).padStart(4, '0')\n}\n"
  },
  {
    "path": "packages/ipfs-core-utils/src/multibases.js",
    "content": "/**\n * @typedef {import('multiformats/bases/interface').MultibaseCodec<any>} MultibaseCodec\n * @typedef {import('./types').LoadBaseFn} LoadBaseFn\n * @typedef {import('ipfs-core-types/src/utils').AbortOptions} AbortOptions\n */\n\n/**\n * @type {LoadBaseFn}\n */\nconst LOAD_BASE = (name) => Promise.reject(new Error(`No base found for \"${name}\"`))\n\nexport class Multibases {\n  /**\n   * @param {object} options\n   * @param {LoadBaseFn} [options.loadBase]\n   * @param {MultibaseCodec[]} options.bases\n   */\n  constructor (options) {\n    // Object with current list of active resolvers\n    /** @type {Record<string, MultibaseCodec>}} */\n    this._basesByName = {}\n\n    // Object with current list of active resolvers\n    /** @type {Record<string, MultibaseCodec>}} */\n    this._basesByPrefix = {}\n\n    this._loadBase = options.loadBase || LOAD_BASE\n\n    // Enable all supplied codecs\n    for (const base of options.bases) {\n      this.addBase(base)\n    }\n  }\n\n  /**\n   * Add support for a multibase codec\n   *\n   * @param {MultibaseCodec} base\n   */\n  addBase (base) {\n    if (this._basesByName[base.name] || this._basesByPrefix[base.prefix]) {\n      throw new Error(`Codec already exists for codec \"${base.name}\"`)\n    }\n\n    this._basesByName[base.name] = base\n    this._basesByPrefix[base.prefix] = base\n  }\n\n  /**\n   * Remove support for a multibase codec\n   *\n   * @param {MultibaseCodec} base\n   */\n  removeBase (base) {\n    delete this._basesByName[base.name]\n    delete this._basesByPrefix[base.prefix]\n  }\n\n  /**\n   * @param {string} nameOrPrefix\n   */\n  async getBase (nameOrPrefix) {\n    if (this._basesByName[nameOrPrefix]) {\n      return this._basesByName[nameOrPrefix]\n    }\n\n    if (this._basesByPrefix[nameOrPrefix]) {\n      return this._basesByPrefix[nameOrPrefix]\n    }\n\n    // If not supported, attempt to dynamically load this codec\n    const base = await this._loadBase(nameOrPrefix)\n\n    if (this._basesByName[base.name] == null && this._basesByPrefix[base.prefix] == null) {\n      this.addBase(base)\n    }\n\n    return base\n  }\n\n  listBases () {\n    return Object.values(this._basesByName)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core-utils/src/multicodecs.js",
    "content": "/**\n * @typedef {import('multiformats/codecs/interface').BlockCodec<any, any>} BlockCodec\n * @typedef {import('./types').LoadCodecFn} LoadCodecFn\n * @typedef {import('ipfs-core-types/src/utils').AbortOptions} AbortOptions\n */\n\n/**\n * @type {LoadCodecFn}\n */\nconst LOAD_CODEC = (codeOrName) => Promise.reject(new Error(`No codec found for \"${codeOrName}\"`))\n\nexport class Multicodecs {\n  /**\n   * @param {object} options\n   * @param {LoadCodecFn} [options.loadCodec]\n   * @param {BlockCodec[]} options.codecs\n   */\n  constructor (options) {\n    // Object with current list of active resolvers\n    /** @type {Record<string, BlockCodec>}} */\n    this._codecsByName = {}\n\n    // Object with current list of active resolvers\n    /** @type {Record<number, BlockCodec>}} */\n    this._codecsByCode = {}\n\n    this._loadCodec = options.loadCodec || LOAD_CODEC\n\n    // Enable all supplied codecs\n    for (const codec of options.codecs) {\n      this.addCodec(codec)\n    }\n  }\n\n  /**\n   * Add support for a block codec\n   *\n   * @param {BlockCodec} codec\n   */\n  addCodec (codec) {\n    if (this._codecsByName[codec.name] || this._codecsByCode[codec.code]) {\n      throw new Error(`Resolver already exists for codec \"${codec.name}\"`)\n    }\n\n    this._codecsByName[codec.name] = codec\n    this._codecsByCode[codec.code] = codec\n  }\n\n  /**\n   * Remove support for a block codec\n   *\n   * @param {BlockCodec} codec\n   */\n  removeCodec (codec) {\n    delete this._codecsByName[codec.name]\n    delete this._codecsByCode[codec.code]\n  }\n\n  /**\n   * @param {number | string} code\n   */\n  async getCodec (code) {\n    const table = typeof code === 'string' ? this._codecsByName : this._codecsByCode\n\n    if (table[code]) {\n      return table[code]\n    }\n\n    // If not supported, attempt to dynamically load this codec\n    const codec = await this._loadCodec(code)\n\n    if (table[code] == null) {\n      this.addCodec(codec)\n    }\n\n    return codec\n  }\n\n  listCodecs () {\n    return Object.values(this._codecsByName)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core-utils/src/multihashes.js",
    "content": "/**\n * @typedef {import('multiformats/hashes/interface').MultihashHasher} MultihashHasher\n * @typedef {import('./types').LoadHasherFn} LoadHasherFn\n * @typedef {import('ipfs-core-types/src/utils').AbortOptions} AbortOptions\n */\n\n/**\n * @type {LoadHasherFn}\n */\nconst LOAD_HASHER = (codeOrName) => Promise.reject(new Error(`No hasher found for \"${codeOrName}\"`))\n\nexport class Multihashes {\n  /**\n   * @param {object} options\n   * @param {LoadHasherFn} [options.loadHasher]\n   * @param {MultihashHasher[]} options.hashers\n   */\n  constructor (options) {\n    // Object with current list of active hashers\n    /** @type {Record<string, MultihashHasher>}} */\n    this._hashersByName = {}\n\n    // Object with current list of active hashers\n    /** @type {Record<number, MultihashHasher>}} */\n    this._hashersByCode = {}\n\n    this._loadHasher = options.loadHasher || LOAD_HASHER\n\n    // Enable all supplied hashers\n    for (const hasher of options.hashers) {\n      this.addHasher(hasher)\n    }\n  }\n\n  /**\n   * Add support for a multibase hasher\n   *\n   * @param {MultihashHasher} hasher\n   */\n  addHasher (hasher) {\n    if (this._hashersByName[hasher.name] || this._hashersByCode[hasher.code]) {\n      throw new Error(`Resolver already exists for codec \"${hasher.name}\"`)\n    }\n\n    this._hashersByName[hasher.name] = hasher\n    this._hashersByCode[hasher.code] = hasher\n  }\n\n  /**\n   * Remove support for a multibase hasher\n   *\n   * @param {MultihashHasher} hasher\n   */\n  removeHasher (hasher) {\n    delete this._hashersByName[hasher.name]\n    delete this._hashersByCode[hasher.code]\n  }\n\n  /**\n   * @param {number | string} code\n   */\n  async getHasher (code) {\n    const table = typeof code === 'string' ? this._hashersByName : this._hashersByCode\n\n    if (table[code]) {\n      return table[code]\n    }\n\n    // If not supported, attempt to dynamically load this hasher\n    const hasher = await this._loadHasher(code)\n\n    if (table[code] == null) {\n      this.addHasher(hasher)\n    }\n\n    return hasher\n  }\n\n  listHashers () {\n    return Object.values(this._hashersByName)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core-utils/src/multipart-request.browser.js",
    "content": "\n// Import browser version otherwise electron-renderer will end up with node\n// version and fail.\nimport { normaliseInput } from './files/normalise-input-multiple.browser.js'\nimport { modeToString } from './mode-to-string.js'\n\n/**\n * @typedef {import('ipfs-core-types/src/utils').ImportCandidateStream} ImportCandidateStream\n */\n\n/**\n * @param {ImportCandidateStream} source\n * @param {AbortController} abortController\n * @param {Headers|Record<string, string>} [headers]\n */\nexport async function multipartRequest (source, abortController, headers = {}) {\n  const parts = []\n  const formData = new FormData()\n  let index = 0\n  let total = 0\n\n  for await (const { content, path, mode, mtime } of normaliseInput(source)) {\n    let fileSuffix = ''\n    const type = content ? 'file' : 'dir'\n\n    if (index > 0) {\n      fileSuffix = `-${index}`\n    }\n\n    let fieldName = type + fileSuffix\n    const qs = []\n\n    if (mode !== null && mode !== undefined) {\n      qs.push(`mode=${modeToString(mode)}`)\n    }\n\n    if ((mtime) != null) {\n      const { secs, nsecs } = (mtime)\n\n      qs.push(`mtime=${secs}`)\n\n      if (nsecs != null) {\n        qs.push(`mtime-nsecs=${nsecs}`)\n      }\n    }\n\n    if (qs.length) {\n      fieldName = `${fieldName}?${qs.join('&')}`\n    }\n\n    if (content) {\n      formData.set(fieldName, content, path != null ? encodeURIComponent(path) : undefined)\n      const end = total + content.size\n      parts.push({ name: path, start: total, end })\n      total = end\n    } else if (path != null) {\n      formData.set(fieldName, new File([''], encodeURIComponent(path), { type: 'application/x-directory' }))\n    } else {\n      throw new Error('path or content or both must be set')\n    }\n\n    index++\n  }\n\n  return {\n    total,\n    parts,\n    headers,\n    body: formData\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core-utils/src/multipart-request.js",
    "content": "import { isElectronRenderer } from 'ipfs-utils/src/env.js'\nimport { multipartRequest as multipartRequestNode } from './multipart-request.node.js'\nimport { multipartRequest as multipartRequestBrowser } from './multipart-request.browser.js'\nimport { nanoid } from 'nanoid'\n\n/**\n * @typedef {import('ipfs-core-types/src/utils').ImportCandidateStream} ImportCandidateStream\n */\n\n/**\n * @param {ImportCandidateStream} source\n * @param {AbortController} abortController\n * @param {Headers|Record<string, string>} [headers]\n * @param {string} [boundary]\n */\nexport async function multipartRequest (source, abortController, headers = {}, boundary = `-----------------------------${nanoid()}`) {\n  let req = multipartRequestNode\n\n  // In electron-renderer we use native fetch and should encode body using native\n  // form data.\n  if (isElectronRenderer) {\n    // @ts-expect-error types are different\n    req = multipartRequestBrowser\n  }\n\n  return req(source, abortController, headers, boundary)\n}\n"
  },
  {
    "path": "packages/ipfs-core-utils/src/multipart-request.node.js",
    "content": "import { normaliseInput } from './files/normalise-input-multiple.js'\nimport { nanoid } from 'nanoid'\nimport { modeToString } from './mode-to-string.js'\nimport mergeOpts from 'merge-options'\n// @ts-expect-error no types\nimport toStream from 'it-to-stream'\nimport { logger } from '@libp2p/logger'\nimport itPeekable from 'it-peekable'\n\nconst merge = mergeOpts.bind({ ignoreUndefined: true })\nconst log = logger('ipfs:core-utils:multipart-request')\n\n/**\n * @typedef {import('ipfs-core-types/src/utils').ImportCandidateStream} ImportCandidateStream\n */\n\n/**\n * @param {ImportCandidateStream} source\n * @param {AbortController} abortController\n * @param {Headers|Record<string, string>} [headers]\n * @param {string} [boundary]\n */\nexport async function multipartRequest (source, abortController, headers = {}, boundary = `-----------------------------${nanoid()}`) {\n  /**\n   * @param {ImportCandidateStream} source\n   */\n  async function * streamFiles (source) {\n    try {\n      let index = 0\n\n      // @ts-expect-error\n      for await (const { content, path, mode, mtime } of source) {\n        let fileSuffix = ''\n        const type = content ? 'file' : 'dir'\n\n        if (index > 0) {\n          yield '\\r\\n'\n\n          fileSuffix = `-${index}`\n        }\n\n        let fieldName = type + fileSuffix\n        const qs = []\n\n        if (mode !== null && mode !== undefined) {\n          qs.push(`mode=${modeToString(mode)}`)\n        }\n\n        if (mtime != null) {\n          const { secs, nsecs } = mtime\n\n          qs.push(`mtime=${secs}`)\n\n          if (nsecs != null) {\n            qs.push(`mtime-nsecs=${nsecs}`)\n          }\n        }\n\n        if (qs.length) {\n          fieldName = `${fieldName}?${qs.join('&')}`\n        }\n\n        yield `--${boundary}\\r\\n`\n        yield `Content-Disposition: form-data; name=\"${fieldName}\"; filename=\"${encodeURIComponent(path || '')}\"\\r\\n`\n        yield `Content-Type: ${content ? 'application/octet-stream' : 'application/x-directory'}\\r\\n`\n        yield '\\r\\n'\n\n        if (content) {\n          yield * content\n        }\n\n        index++\n      }\n    } catch (/** @type {any} */ err) {\n      log(err)\n      // workaround for https://github.com/node-fetch/node-fetch/issues/753\n      abortController.abort()\n    } finally {\n      yield `\\r\\n--${boundary}--\\r\\n`\n    }\n  }\n\n  // peek at the first value in order to get the input stream moving\n  // and to validate its contents.\n  // We cannot do this in the `for await..of` in streamFiles due to\n  // https://github.com/node-fetch/node-fetch/issues/753\n  const peekable = itPeekable(normaliseInput(source))\n\n  /** @type {any} value **/\n  const { value, done } = await peekable.peek()\n\n  if (!done) {\n    peekable.push(value)\n  }\n\n  return {\n    parts: null,\n    total: -1,\n    headers: merge(headers, {\n      'Content-Type': `multipart/form-data; boundary=${boundary}`\n    }),\n    // @ts-expect-error normaliseInput returns unixfs importer import candidates\n    body: toStream(streamFiles(peekable))\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core-utils/src/pins/normalise-input.js",
    "content": "import errCode from 'err-code'\nimport { CID } from 'multiformats/cid'\n\n/**\n * @typedef {object} Pinnable\n * @property {string | InstanceType<typeof window.String> | CID} [path]\n * @property {CID} [cid]\n * @property {boolean} [recursive]\n * @property {any} [metadata]\n *\n * @typedef {CID|string|InstanceType<typeof window.String>|Pinnable} ToPin\n * @typedef {ToPin|Iterable<ToPin>|AsyncIterable<ToPin>} Source\n *\n * @typedef {object} Pin\n * @property {string|CID} path\n * @property {boolean} recursive\n * @property {any} [metadata]\n */\n\n/**\n * @param {any} thing\n * @returns {thing is IterableIterator<any> & Iterator<any>}\n */\nfunction isIterable (thing) {\n  return Symbol.iterator in thing\n}\n\n/**\n * @param {any} thing\n * @returns {thing is AsyncIterableIterator<any> & AsyncIterator<any>}\n */\nfunction isAsyncIterable (thing) {\n  return Symbol.asyncIterator in thing\n}\n\n/**\n * @param {any} thing\n * @returns {thing is CID}\n */\nfunction isCID (thing) {\n  return CID.asCID(thing) != null\n}\n\n/**\n * Transform one of:\n *\n * ```ts\n * CID\n * String\n * { cid: CID recursive, metadata }\n * { path: String recursive, metadata }\n * Iterable<CID>\n * Iterable<String>\n * Iterable<{ cid: CID recursive, metadata }>\n * Iterable<{ path: String recursive, metadata }>\n * AsyncIterable<CID>\n * AsyncIterable<String>\n * AsyncIterable<{ cid: CID recursive, metadata }>\n * AsyncIterable<{ path: String recursive, metadata }>\n * ```\n * Into:\n *\n * ```ts\n * AsyncIterable<{ path: CID|String, recursive:boolean, metadata }>\n * ```\n *\n * @param {Source} input\n * @returns {AsyncIterable<Pin>}\n */\n// eslint-disable-next-line complexity\nexport async function * normaliseInput (input) {\n  // must give us something\n  if (input === null || input === undefined) {\n    throw errCode(new Error(`Unexpected input: ${input}`), 'ERR_UNEXPECTED_INPUT')\n  }\n\n  // CID\n  const cid = CID.asCID(input)\n\n  if (cid) {\n    yield toPin({ cid })\n    return\n  }\n\n  if (input instanceof String || typeof input === 'string') {\n    yield toPin({ path: input })\n    return\n  }\n\n  // { cid: CID recursive, metadata }\n  // @ts-expect-error - it still could be iterable or async iterable\n  if (input.cid != null || input.path != null) {\n    // @ts-expect-error\n    return yield toPin(input)\n  }\n\n  // Iterable<?>\n  if (isIterable(input)) {\n    const iterator = input[Symbol.iterator]()\n    const first = iterator.next()\n\n    if (first.done) {\n      return iterator\n    }\n\n    // Iterable<CID>\n    if (isCID(first.value)) {\n      yield toPin({ cid: first.value })\n      for (const cid of iterator) {\n        yield toPin({ cid })\n      }\n      return\n    }\n\n    // Iterable<String>\n    if (first.value instanceof String || typeof first.value === 'string') {\n      yield toPin({ path: first.value })\n      for (const path of iterator) {\n        yield toPin({ path })\n      }\n      return\n    }\n\n    // Iterable<Pinnable>\n    if (first.value.cid != null || first.value.path != null) {\n      yield toPin(first.value)\n      for (const obj of iterator) {\n        yield toPin(obj)\n      }\n      return\n    }\n\n    throw errCode(new Error('Unexpected input: ' + typeof input), 'ERR_UNEXPECTED_INPUT')\n  }\n\n  // AsyncIterable<?>\n  if (isAsyncIterable(input)) {\n    const iterator = input[Symbol.asyncIterator]()\n    const first = await iterator.next()\n    if (first.done) return iterator\n\n    // AsyncIterable<CID>\n    if (isCID(first.value)) {\n      yield toPin({ cid: first.value })\n      for await (const cid of iterator) {\n        yield toPin({ cid })\n      }\n      return\n    }\n\n    // AsyncIterable<String>\n    if (first.value instanceof String || typeof first.value === 'string') {\n      yield toPin({ path: first.value })\n      for await (const path of iterator) {\n        yield toPin({ path })\n      }\n      return\n    }\n\n    // AsyncIterable<{ cid: CID|String recursive, metadata }>\n    if (first.value.cid != null || first.value.path != null) {\n      yield toPin(first.value)\n      for await (const obj of iterator) {\n        yield toPin(obj)\n      }\n      return\n    }\n\n    throw errCode(new Error('Unexpected input: ' + typeof input), 'ERR_UNEXPECTED_INPUT')\n  }\n\n  throw errCode(new Error('Unexpected input: ' + typeof input), 'ERR_UNEXPECTED_INPUT')\n}\n\n/**\n * @param {Pinnable} input\n */\nfunction toPin (input) {\n  const path = input.cid || `${input.path}`\n\n  if (!path) {\n    throw errCode(new Error('Unexpected input: Please path either a CID or an IPFS path'), 'ERR_UNEXPECTED_INPUT')\n  }\n\n  /** @type {Pin} */\n  const pin = {\n    path,\n    recursive: input.recursive !== false\n  }\n\n  if (input.metadata != null) {\n    pin.metadata = input.metadata\n  }\n\n  return pin\n}\n"
  },
  {
    "path": "packages/ipfs-core-utils/src/to-cid-and-path.js",
    "content": "import { CID } from 'multiformats/cid'\nimport errCode from 'err-code'\n\nconst IPFS_PREFIX = '/ipfs/'\n\n/**\n * @param {string|Uint8Array|CID} string\n * @returns {{cid:CID, path?:string}}\n */\nexport function toCidAndPath (string) {\n  if (string instanceof Uint8Array) {\n    try {\n      string = CID.decode(string)\n    } catch (/** @type {any} */ err) {\n      throw errCode(err, 'ERR_INVALID_CID')\n    }\n  }\n\n  let cid = CID.asCID(string)\n\n  if (cid) {\n    return {\n      cid,\n      path: undefined\n    }\n  }\n\n  string = string.toString()\n\n  if (string.startsWith(IPFS_PREFIX)) {\n    string = string.substring(IPFS_PREFIX.length)\n  }\n\n  const parts = string.split('/')\n  let path\n\n  try {\n    cid = CID.parse(parts.shift() || '')\n  } catch (/** @type {any} */ err) {\n    throw errCode(err, 'ERR_INVALID_CID')\n  }\n\n  if (parts.length) {\n    path = `/${parts.join('/')}`\n  }\n\n  return {\n    cid,\n    path\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core-utils/src/to-url-string.js",
    "content": "import { multiaddr } from '@multiformats/multiaddr'\nimport { multiaddrToUri } from '@multiformats/multiaddr-to-uri'\n\n/**\n * @typedef {import('@multiformats/multiaddr').Multiaddr} Multiaddr\n */\n\n/**\n * @param {string|Multiaddr|URL} url - A string, multiaddr or URL to convert to a url string\n * @returns {string}\n */\nexport function toUrlString (url) {\n  try {\n    // @ts-expect-error\n    url = multiaddrToUri(multiaddr(url))\n  } catch (/** @type {any} */ err) { }\n\n  url = url.toString()\n\n  return url\n}\n"
  },
  {
    "path": "packages/ipfs-core-utils/src/types.ts",
    "content": "import type { MultibaseCodec } from 'multiformats/bases/interface'\nimport type { BlockCodec } from 'multiformats/codecs/interface'\nimport type { MultihashHasher } from 'multiformats/hashes/interface'\n\nexport interface LoadBaseFn { (codeOrName: string): Promise<MultibaseCodec<any>> }\nexport interface LoadCodecFn { (codeOrName: number | string): Promise<BlockCodec<any, any>> }\nexport interface LoadHasherFn { (codeOrName: number | string): Promise<MultihashHasher> }\n"
  },
  {
    "path": "packages/ipfs-core-utils/src/with-timeout-option.js",
    "content": "/* eslint-disable no-unreachable */\n\nimport { TimeoutController } from 'timeout-abort-controller'\nimport { anySignal } from 'any-signal'\nimport parseDuration from 'parse-duration'\nimport { TimeoutError } from './errors.js'\n\n/**\n * @template {any[]} Args\n * @template {Promise<any> | AsyncIterable<any>} R - The return type of `fn`\n * @param {(...args:Args) => R} fn\n * @param {number} [optionsArgIndex]\n * @returns {(...args:Args) => R}\n */\nexport function withTimeoutOption (fn, optionsArgIndex) {\n  // eslint-disable-next-line\n  return /** @returns {R} */(/** @type {Args} */...args) => {\n    const options = args[optionsArgIndex == null ? args.length - 1 : optionsArgIndex]\n    if (!options || !options.timeout) return fn(...args)\n\n    const timeout = typeof options.timeout === 'string'\n      ? parseDuration(options.timeout)\n      : options.timeout\n\n    const controller = new TimeoutController(timeout)\n\n    options.signal = anySignal([options.signal, controller.signal])\n\n    const fnRes = fn(...args)\n    // eslint-disable-next-line promise/param-names\n    const timeoutPromise = new Promise((_resolve, reject) => {\n      controller.signal.addEventListener('abort', () => {\n        reject(new TimeoutError())\n      })\n    })\n\n    const start = Date.now()\n\n    const maybeThrowTimeoutError = () => {\n      if (controller.signal.aborted) {\n        throw new TimeoutError()\n      }\n\n      const timeTaken = Date.now() - start\n\n      // if we have starved the event loop by adding microtasks, we could have\n      // timed out already but the TimeoutController will never know because it's\n      // setTimeout will not fire until we stop adding microtasks\n      if (timeTaken > timeout) {\n        controller.abort()\n        throw new TimeoutError()\n      }\n    }\n\n    // @ts-expect-error\n    if (fnRes[Symbol.asyncIterator]) {\n      // @ts-expect-error\n      return (async function * () {\n        // @ts-expect-error\n        const it = fnRes[Symbol.asyncIterator]()\n\n        try {\n          while (true) {\n            const { value, done } = await Promise.race([it.next(), timeoutPromise])\n\n            if (done) {\n              break\n            }\n\n            maybeThrowTimeoutError()\n\n            yield value\n          }\n        } catch (/** @type {any} */ err) {\n          maybeThrowTimeoutError()\n\n          throw err\n        } finally {\n          controller.clear()\n\n          if (it.return) {\n            it.return()\n          }\n        }\n      })()\n    }\n\n    // @ts-expect-error\n    return (async () => {\n      try {\n        const res = await Promise.race([fnRes, timeoutPromise])\n\n        maybeThrowTimeoutError()\n\n        return res\n      } catch (/** @type {any} */ err) {\n        maybeThrowTimeoutError()\n\n        throw err\n      } finally {\n        controller.clear()\n      }\n    })()\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-core-utils/test/files/format-mode.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { formatMode } from '../../src/files/format-mode.js'\n\ndescribe('format-mode', function () {\n  it('formats mode for directories', function () {\n    expect(formatMode(parseInt('0777', 8), true)).to.equal('drwxrwxrwx')\n  })\n\n  it('formats mode for files', function () {\n    expect(formatMode(parseInt('0777', 8), false)).to.equal('-rwxrwxrwx')\n  })\n\n  it('setgid, setuid and stick bit', function () {\n    expect(formatMode(parseInt('1777', 8), false)).to.equal('-rwxrwxrwt')\n    expect(formatMode(parseInt('2777', 8), false)).to.equal('-rwxrwsrwx')\n    expect(formatMode(parseInt('4777', 8), false)).to.equal('-rwsrwxrwx')\n    expect(formatMode(parseInt('5777', 8), false)).to.equal('-rwsrwxrwt')\n    expect(formatMode(parseInt('6777', 8), false)).to.equal('-rwsrwsrwx')\n    expect(formatMode(parseInt('7777', 8), false)).to.equal('-rwsrwsrwt')\n  })\n\n  it('formats user', function () {\n    expect(formatMode(parseInt('0100', 8), false)).to.equal('---x------')\n    expect(formatMode(parseInt('0200', 8), false)).to.equal('--w-------')\n    expect(formatMode(parseInt('0300', 8), false)).to.equal('--wx------')\n    expect(formatMode(parseInt('0400', 8), false)).to.equal('-r--------')\n    expect(formatMode(parseInt('0500', 8), false)).to.equal('-r-x------')\n    expect(formatMode(parseInt('0600', 8), false)).to.equal('-rw-------')\n    expect(formatMode(parseInt('0700', 8), false)).to.equal('-rwx------')\n  })\n\n  it('formats group', function () {\n    expect(formatMode(parseInt('0010', 8), false)).to.equal('------x---')\n    expect(formatMode(parseInt('0020', 8), false)).to.equal('-----w----')\n    expect(formatMode(parseInt('0030', 8), false)).to.equal('-----wx---')\n    expect(formatMode(parseInt('0040', 8), false)).to.equal('----r-----')\n    expect(formatMode(parseInt('0050', 8), false)).to.equal('----r-x---')\n    expect(formatMode(parseInt('0060', 8), false)).to.equal('----rw----')\n    expect(formatMode(parseInt('0070', 8), false)).to.equal('----rwx---')\n  })\n\n  it('formats other', function () {\n    expect(formatMode(parseInt('0001', 8), false)).to.equal('---------x')\n    expect(formatMode(parseInt('0002', 8), false)).to.equal('--------w-')\n    expect(formatMode(parseInt('0003', 8), false)).to.equal('--------wx')\n    expect(formatMode(parseInt('0004', 8), false)).to.equal('-------r--')\n    expect(formatMode(parseInt('0005', 8), false)).to.equal('-------r-x')\n    expect(formatMode(parseInt('0006', 8), false)).to.equal('-------rw-')\n    expect(formatMode(parseInt('0007', 8), false)).to.equal('-------rwx')\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-core-utils/test/files/format-mtime.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { formatMtime } from '../../src/files/format-mtime.js'\n\ndescribe('format-mtime', function () {\n  it('formats mtime', function () {\n    expect(formatMtime({ secs: 15768000, nsecs: 0 })).to.include('1970')\n  })\n\n  it('formats empty mtime', function () {\n    expect(formatMtime()).to.equal('-')\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-core-utils/test/files/normalise-input-multiple.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport blobToIt from 'blob-to-it'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport all from 'it-all'\nimport { File } from '@web-std/file'\nimport { normaliseInput } from '../../src/files/normalise-input-multiple.js'\nimport { isNode } from 'ipfs-utils/src/env.js'\nimport resolve from 'aegir/resolve'\n\nconst { Blob, ReadableStream } = globalThis\n\nconst STRING = () => 'hello world'\nconst NEWSTRING = () => new String('hello world') // eslint-disable-line no-new-wrappers\nconst BUFFER = () => uint8ArrayFromString(STRING())\nconst ARRAY = () => Array.from(BUFFER())\nconst TYPEDARRAY = () => Uint8Array.from(ARRAY())\nconst FILE = () => new File([BUFFER()], 'test-file.txt')\n/** @type {() => Blob} */\nlet BLOB\n\nif (Blob) {\n  BLOB = () => new Blob([\n    STRING()\n  ])\n}\n\n/**\n * @param {import('ipfs-unixfs-importer').ImportCandidate[]} input\n */\nasync function verifyNormalisation (input) {\n  expect(input.length).to.equal(1)\n  expect(input[0].path).to.equal('')\n\n  let content = input[0].content\n\n  if (Blob && content instanceof Blob) {\n    content = blobToIt(content)\n  }\n\n  if (!content || content instanceof Uint8Array) {\n    throw new Error('Content expected')\n  }\n\n  await expect(all(content)).to.eventually.deep.equal([BUFFER()])\n}\n\n/**\n * @param {*} input\n */\nasync function testContent (input) {\n  const result = await all(normaliseInput(input))\n\n  await verifyNormalisation(result)\n}\n\n/**\n * @param {*} input\n * @param {RegExp} message\n */\nasync function testFailure (input, message) {\n  await expect(all(normaliseInput(input))).to.eventually.be.rejectedWith(message)\n}\n\n/**\n * @template T\n * @param {T} thing\n * @returns {T[]}\n */\nfunction iterableOf (thing) {\n  return [thing]\n}\n\n/**\n * @template T\n * @param {T} thing\n * @returns {AsyncIterable<T>}\n */\nfunction asyncIterableOf (thing) {\n  return (async function * () { // eslint-disable-line require-await\n    yield thing\n  }())\n}\n\n/**\n * @param {*} thing\n */\nfunction browserReadableStreamOf (thing) {\n  return new ReadableStream({\n    start (controller) {\n      controller.enqueue(thing)\n      controller.close()\n    }\n  })\n}\n\ndescribe('normalise-input-multiple', function () {\n  /**\n   * @param {() => any} content\n   * @param {string} name\n   * @param {{ acceptStream: boolean, acceptContentStream: boolean }} options\n   */\n  function testInputType (content, name, { acceptStream, acceptContentStream }) {\n    it(`Failure ${name}`, async function () {\n      await testFailure(content(), /single item passed/)\n    })\n\n    if (acceptStream) {\n      if (ReadableStream) {\n        it(`ReadableStream<${name}>`, async function () {\n          await testContent(browserReadableStreamOf(content()))\n        })\n      }\n\n      it(`Iterable<${name}>`, async function () {\n        await testContent(iterableOf(content()))\n      })\n\n      it(`AsyncIterable<${name}>`, async function () {\n        await testContent(asyncIterableOf(content()))\n      })\n    } else {\n      if (ReadableStream) {\n        it(`Failure ReadableStream<${name}>`, async function () {\n          await testFailure(browserReadableStreamOf(content()), /single item passed/)\n        })\n      }\n\n      it(`Failure Iterable<${name}>`, async function () {\n        await testFailure(iterableOf(content()), /single item passed/)\n      })\n\n      it(`Failure AsyncIterable<${name}>`, async function () {\n        await testFailure(asyncIterableOf(content()), /single item passed/)\n      })\n    }\n\n    it(`Failure { path: '', content: ${name} }`, async function () {\n      await testFailure({ path: '', content: content() }, /single item passed/)\n    })\n\n    it(`Iterable<{ path: '', content: ${name} }>`, async function () {\n      await testContent(iterableOf({ path: '', content: content() }))\n    })\n\n    it(`AsyncIterable<{ path: '', content: ${name} }>`, async function () {\n      await testContent(asyncIterableOf({ path: '', content: content() }))\n    })\n\n    if (acceptContentStream) {\n      if (ReadableStream) {\n        it(`Iterable<{ path: '', content: ReadableStream<${name}> }>`, async function () {\n          await testContent(iterableOf({ path: '', content: browserReadableStreamOf(content()) }))\n        })\n      }\n\n      it(`Iterable<{ path: '', content: Iterable<${name}> }>`, async function () {\n        await testContent(iterableOf({ path: '', content: iterableOf(content()) }))\n      })\n\n      it(`Iterable<{ path: '', content: AsyncIterable<${name}> }>`, async function () {\n        await testContent(iterableOf({ path: '', content: asyncIterableOf(content()) }))\n      })\n\n      if (ReadableStream) {\n        it(`AsyncIterable<{ path: '', content: ReadableStream<${name}> }>`, async function () {\n          await testContent(asyncIterableOf({ path: '', content: browserReadableStreamOf(content()) }))\n        })\n      }\n\n      it(`AsyncIterable<{ path: '', content: Iterable<${name}> }>`, async function () {\n        await testContent(asyncIterableOf({ path: '', content: iterableOf(content()) }))\n      })\n\n      it(`AsyncIterable<{ path: '', content: AsyncIterable<${name}> }>`, async function () {\n        await testContent(asyncIterableOf({ path: '', content: asyncIterableOf(content()) }))\n      })\n    } else {\n      if (ReadableStream) {\n        it(`Failure Iterable<{ path: '', content: ReadableStream<${name}> }>`, async function () {\n          await testFailure(iterableOf({ path: '', content: browserReadableStreamOf(content()) }), /Unexpected input/)\n        })\n      }\n\n      it(`Failure Iterable<{ path: '', content: Iterable<${name}> }>`, async function () {\n        await testFailure(iterableOf({ path: '', content: iterableOf(content()) }), /Unexpected input/)\n      })\n\n      it(`Failure Iterable<{ path: '', content: AsyncIterable<${name}> }>`, async function () {\n        await testFailure(iterableOf({ path: '', content: asyncIterableOf(content()) }), /Unexpected input/)\n      })\n\n      if (ReadableStream) {\n        it(`Failure AsyncIterable<{ path: '', content: ReadableStream<${name}> }>`, async function () {\n          await testFailure(asyncIterableOf({ path: '', content: browserReadableStreamOf(content()) }), /Unexpected input/)\n        })\n      }\n\n      it(`Failure AsyncIterable<{ path: '', content: Iterable<${name}> }>`, async function () {\n        await testFailure(asyncIterableOf({ path: '', content: iterableOf(content()) }), /Unexpected input/)\n      })\n\n      it(`Failure AsyncIterable<{ path: '', content: AsyncIterable<${name}> }>`, async function () {\n        await testFailure(asyncIterableOf({ path: '', content: asyncIterableOf(content()) }), /Unexpected input/)\n      })\n    }\n  }\n\n  describe('String', () => {\n    testInputType(STRING, 'String', {\n      acceptStream: true,\n      acceptContentStream: true\n    })\n    testInputType(NEWSTRING, 'new String()', {\n      acceptStream: true,\n      acceptContentStream: true\n    })\n  })\n\n  describe('Buffer', () => {\n    testInputType(BUFFER, 'Buffer', {\n      acceptStream: true,\n      acceptContentStream: true\n    })\n  })\n\n  describe('Blob', () => {\n    if (!Blob) {\n      return\n    }\n\n    testInputType(BLOB, 'Blob', {\n      acceptStream: true,\n      acceptContentStream: false\n    })\n  })\n\n  describe('@web-std/file', () => {\n    testInputType(FILE, 'File', {\n      acceptStream: true,\n      acceptContentStream: false\n    })\n  })\n\n  describe('Iterable<Number>', () => {\n    testInputType(ARRAY, 'Iterable<Number>', {\n      acceptStream: true,\n      acceptContentStream: false\n    })\n  })\n\n  describe('TypedArray', () => {\n    testInputType(TYPEDARRAY, 'TypedArray', {\n      acceptStream: true,\n      acceptContentStream: true\n    })\n  })\n\n  if (isNode) {\n    /** @type {import('fs')} */\n    let fs\n\n    before(async () => {\n      fs = await import('fs')\n    })\n\n    describe('Node fs.ReadStream', () => {\n      const NODEFSREADSTREAM = () => {\n        const path = resolve('test/fixtures/file.txt', 'ipfs-core-utils')\n\n        return fs.createReadStream(path)\n      }\n\n      testInputType(NODEFSREADSTREAM, 'Node fs.ReadStream', {\n        acceptStream: true,\n        acceptContentStream: false\n      })\n    })\n  }\n})\n"
  },
  {
    "path": "packages/ipfs-core-utils/test/files/normalise-input-single.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport blobToIt from 'blob-to-it'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport all from 'it-all'\nimport { File } from '@web-std/file'\nimport { normaliseInput } from '../../src/files/normalise-input-single.js'\nimport { isNode } from 'ipfs-utils/src/env.js'\nimport resolve from 'aegir/resolve'\n\nconst { Blob, ReadableStream } = globalThis\n\nconst STRING = () => 'hello world'\nconst NEWSTRING = () => new String('hello world') // eslint-disable-line no-new-wrappers\nconst BUFFER = () => uint8ArrayFromString(STRING())\nconst ARRAY = () => Array.from(BUFFER())\nconst TYPEDARRAY = () => Uint8Array.from(ARRAY())\nconst FILE = () => new File([BUFFER()], 'test-file.txt')\n/** @type {() => Blob} */\nlet BLOB\n\nif (Blob) {\n  BLOB = () => new Blob([\n    STRING()\n  ])\n}\n\n/**\n * @param {import('ipfs-unixfs-importer').ImportCandidate[]} input\n */\nasync function verifyNormalisation (input) {\n  expect(input.length).to.equal(1)\n  expect(input[0].path).to.equal('')\n\n  let content = input[0].content\n\n  if (Blob && content instanceof Blob) {\n    content = blobToIt(content)\n  }\n\n  if (!content || content instanceof Uint8Array) {\n    throw new Error('Content expected')\n  }\n\n  await expect(all(content)).to.eventually.deep.equal([BUFFER()])\n}\n\n/**\n * @param {*} input\n */\nasync function testContent (input) {\n  const result = await all(normaliseInput(input))\n\n  await verifyNormalisation(result)\n}\n\n/**\n * @param {*} input\n * @param {RegExp} message\n */\nasync function testFailure (input, message) {\n  await expect(all(normaliseInput(input))).to.eventually.be.rejectedWith(message)\n}\n\n/**\n * @template T\n * @param {T} thing\n * @returns {T[]}\n */\nfunction iterableOf (thing) {\n  return [thing]\n}\n\n/**\n * @template T\n * @param {T} thing\n * @returns {AsyncIterable<T>}\n */\nfunction asyncIterableOf (thing) {\n  return (async function * () { // eslint-disable-line require-await\n    yield thing\n  }())\n}\n\n/**\n * @param {*} thing\n */\nfunction browserReadableStreamOf (thing) {\n  return new ReadableStream({\n    start (controller) {\n      controller.enqueue(thing)\n      controller.close()\n    }\n  })\n}\n\ndescribe('normalise-input-single', function () {\n  /**\n   * @param {() => any} content\n   * @param {string} name\n   * @param {{ acceptStream: boolean }} options\n   */\n  function testInputType (content, name, { acceptStream }) {\n    it(name, async function () {\n      await testContent(content())\n    })\n\n    if (acceptStream) {\n      if (ReadableStream) {\n        it(`ReadableStream<${name}>`, async function () {\n          await testContent(browserReadableStreamOf(content()))\n        })\n      }\n\n      it(`Iterable<${name}>`, async function () {\n        await testContent(iterableOf(content()))\n      })\n\n      it(`AsyncIterable<${name}>`, async function () {\n        await testContent(asyncIterableOf(content()))\n      })\n    } else {\n      if (ReadableStream) {\n        it(`Failure ReadableStream<${name}>`, async function () {\n          await testFailure(browserReadableStreamOf(content()), /Unexpected input/)\n        })\n      }\n\n      it(`Failure Iterable<${name}>`, async function () {\n        await testFailure(iterableOf(content()), /Unexpected input/)\n      })\n\n      it(`Failure AsyncIterable<${name}>`, async function () {\n        await testFailure(asyncIterableOf(content()), /Unexpected input/)\n      })\n    }\n\n    it(`{ path: '', content: ${name} }`, async function () {\n      await testContent({ path: '', content: content() })\n    })\n\n    if (acceptStream) {\n      if (ReadableStream) {\n        it(`{ path: '', content: ReadableStream<${name}> }`, async function () {\n          await testContent({ path: '', content: browserReadableStreamOf(content()) })\n        })\n      }\n\n      it(`{ path: '', content: Iterable<${name}> }`, async function () {\n        await testContent({ path: '', content: iterableOf(content()) })\n      })\n\n      it(`{ path: '', content: AsyncIterable<${name}> }`, async function () {\n        await testContent({ path: '', content: asyncIterableOf(content()) })\n      })\n    }\n\n    if (ReadableStream) {\n      if (acceptStream) {\n        it(`ReadableStream<${name}>`, async function () {\n          await testContent(browserReadableStreamOf(content()))\n        })\n      } else {\n        it(`Failure ReadableStream<${name}>`, async function () {\n          await testFailure(browserReadableStreamOf(content()), /multiple items passed/)\n        })\n      }\n    }\n\n    it(`Failure Iterable<{ path: '', content: ${name} }>`, async function () {\n      await testFailure(iterableOf({ path: '', content: content() }), /multiple items passed/)\n    })\n\n    it(`Failure AsyncIterable<{ path: '', content: ${name} }>`, async function () {\n      await testFailure(asyncIterableOf({ path: '', content: content() }), /multiple items passed/)\n    })\n\n    if (acceptStream) {\n      if (ReadableStream) {\n        it(`Failure Iterable<{ path: '', content: ReadableStream<${name}> }>`, async function () {\n          await testFailure(iterableOf({ path: '', content: browserReadableStreamOf(content()) }), /multiple items passed/)\n        })\n      }\n\n      it(`Failure Iterable<{ path: '', content: Iterable<${name}> }>`, async function () {\n        await testFailure(iterableOf({ path: '', content: iterableOf(content()) }), /multiple items passed/)\n      })\n\n      it(`Failure Iterable<{ path: '', content: AsyncIterable<${name}> }>`, async function () {\n        await testFailure(iterableOf({ path: '', content: asyncIterableOf(content()) }), /multiple items passed/)\n      })\n\n      if (ReadableStream) {\n        it(`Failure AsyncIterable<{ path: '', content: ReadableStream<${name}> }>`, async function () {\n          await testFailure(asyncIterableOf({ path: '', content: browserReadableStreamOf(content()) }), /multiple items passed/)\n        })\n      }\n\n      it(`Failure AsyncIterable<{ path: '', content: Iterable<${name}> }>`, async function () {\n        await testFailure(asyncIterableOf({ path: '', content: iterableOf(content()) }), /multiple items passed/)\n      })\n\n      it(`Failure AsyncIterable<{ path: '', content: AsyncIterable<${name}> }>`, async function () {\n        await testFailure(asyncIterableOf({ path: '', content: asyncIterableOf(content()) }), /multiple items passed/)\n      })\n    }\n  }\n\n  describe('String', () => {\n    testInputType(STRING, 'String', {\n      acceptStream: true\n    })\n    testInputType(NEWSTRING, 'new String()', {\n      acceptStream: true\n    })\n  })\n\n  describe('Buffer', () => {\n    testInputType(BUFFER, 'Buffer', {\n      acceptStream: true\n    })\n  })\n\n  describe('Blob', () => {\n    if (!Blob) {\n      return\n    }\n\n    testInputType(BLOB, 'Blob', {\n      acceptStream: false\n    })\n  })\n\n  describe('@web-std/file', () => {\n    testInputType(FILE, 'File', {\n      acceptStream: false\n    })\n  })\n\n  describe('Iterable<Number>', () => {\n    testInputType(ARRAY, 'Iterable<Number>', {\n      acceptStream: false\n    })\n  })\n\n  describe('TypedArray', () => {\n    testInputType(TYPEDARRAY, 'TypedArray', {\n      acceptStream: true\n    })\n  })\n\n  if (isNode) {\n    /** @type {import('fs')} */\n    let fs\n\n    before(async () => {\n      fs = await import('fs')\n    })\n\n    describe('Node fs.ReadStream', () => {\n      const NODEFSREADSTREAM = () => {\n        const path = resolve('test/fixtures/file.txt', 'ipfs-core-utils')\n\n        return fs.createReadStream(path)\n      }\n\n      testInputType(NODEFSREADSTREAM, 'Node fs.ReadStream', {\n        acceptStream: false\n      })\n    })\n  }\n})\n"
  },
  {
    "path": "packages/ipfs-core-utils/test/fixtures/file.txt",
    "content": "hello world"
  },
  {
    "path": "packages/ipfs-core-utils/test/pins/normalise-input.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { normaliseInput } from '../../src/pins/normalise-input.js'\nimport all from 'it-all'\nimport { CID } from 'multiformats/cid'\n\nconst STRING = () => '/ipfs/QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn/path/to/file.txt'\nconst PLAIN_CID = () => CID.parse('QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn')\nconst OBJECT_CID = () => ({ cid: CID.parse('QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn'), recursive: true, metadata: { key: 'hello world' } })\nconst OBJECT_PATH = () => ({ path: '/ipfs/QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn/path/to/file.txt', recursive: true, metadata: { key: 'hello world' } })\n\n/**\n * @param {import('../../src/pins/normalise-input').Source} input\n * @param {boolean} [withOptions]\n */\nasync function verifyNormalisation (input, withOptions) {\n  const result = await all(normaliseInput(input))\n\n  expect(result).to.have.lengthOf(1)\n  expect(result[0]).to.have.property('path')\n\n  if (withOptions) {\n    expect(result[0]).to.have.property('recursive', true)\n    expect(result[0]).to.have.deep.property('metadata', { key: 'hello world' })\n  }\n}\n\n/**\n * @template T\n * @param {T} thing\n * @returns {T[]}\n */\nfunction iterableOf (thing) {\n  return [thing]\n}\n\n/**\n * @template T\n * @param {T} thing\n * @returns {AsyncIterable<T>}\n */\nfunction asyncIterableOf (thing) {\n  return (async function * () { // eslint-disable-line require-await\n    yield thing\n  }())\n}\n\ndescribe('pin normalise-input', function () {\n  /**\n   * @param {() => any} content\n   * @param {string} name\n   * @param {boolean} [withOptions]\n   */\n  function testInputType (content, name, withOptions) {\n    it(name, async function () {\n      await verifyNormalisation(content(), withOptions)\n    })\n\n    it(`Iterable<${name}>`, async function () {\n      await verifyNormalisation(iterableOf(content()), withOptions)\n    })\n\n    it(`AsyncIterable<${name}>`, async function () {\n      await verifyNormalisation(asyncIterableOf(content()), withOptions)\n    })\n  }\n\n  describe('String', () => {\n    testInputType(STRING, 'String')\n  })\n\n  describe('CID', () => {\n    testInputType(PLAIN_CID, 'CID')\n  })\n\n  describe('Object with CID', () => {\n    testInputType(OBJECT_CID, 'Object with CID', true)\n  })\n\n  describe('Object with path', () => {\n    testInputType(OBJECT_PATH, 'Object with path', true)\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-core-utils/test/tests.spec.js",
    "content": "\nimport './files/format-mode.spec.js'\nimport './files/format-mtime.spec.js'\nimport './files/normalise-input-multiple.spec.js'\nimport './files/normalise-input-single.spec.js'\nimport './pins/normalise-input.spec.js'\n"
  },
  {
    "path": "packages/ipfs-core-utils/tsconfig.json",
    "content": "{\n  \"extends\": \"aegir/src/config/tsconfig.aegir.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"emitDeclarationOnly\": true\n  },\n  \"include\": [\n    \"src\",\n    \"test\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../ipfs-core-types\"\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/ipfs-daemon/CHANGELOG.md",
    "content": "# Change Log\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n### [0.16.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-daemon-v0.16.0...ipfs-daemon-v0.16.1) (2023-05-25)\n\n\n### Bug Fixes\n\n* add deprecation notice to readmes ([#4362](https://www.github.com/ipfs/js-ipfs/issues/4362)) ([7b79c1b](https://www.github.com/ipfs/js-ipfs/commit/7b79c1b8df5c818dc124b346ea28330455732d5c))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core bumped from ^0.18.0 to ^0.18.1\n    * ipfs-core-types bumped from ^0.14.0 to ^0.14.1\n    * ipfs-grpc-server bumped from ^0.12.0 to ^0.12.1\n    * ipfs-http-gateway bumped from ^0.13.0 to ^0.13.1\n    * ipfs-http-server bumped from ^0.15.0 to ^0.15.1\n\n## [0.16.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-daemon-v0.15.0...ipfs-daemon-v0.16.0) (2023-01-11)\n\n\n### ⚠ BREAKING CHANGES\n\n* update multiformats to v11.x.x and related depenendcies (#4277)\n\n### Bug Fixes\n\n* update multiformats to v11.x.x and related depenendcies ([#4277](https://www.github.com/ipfs/js-ipfs/issues/4277)) ([521c84a](https://www.github.com/ipfs/js-ipfs/commit/521c84a958b04d61702577a5adce28519c1b2a3b))\n* use aegir to publish RCs ([#4284](https://www.github.com/ipfs/js-ipfs/issues/4284)) ([6d90cbf](https://www.github.com/ipfs/js-ipfs/commit/6d90cbf321a7dbf4b1084ba20f0c514dc08d8d0a))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core bumped from ^0.17.0 to ^0.18.0\n    * ipfs-core-types bumped from ^0.13.0 to ^0.14.0\n    * ipfs-grpc-server bumped from ^0.11.0 to ^0.12.0\n    * ipfs-http-gateway bumped from ^0.12.0 to ^0.13.0\n    * ipfs-http-server bumped from ^0.14.0 to ^0.15.0\n\n## [0.15.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-daemon-v0.14.2...ipfs-daemon-v0.15.0) (2022-10-24)\n\n\n### ⚠ BREAKING CHANGES\n\n* ipfs is now bundled with libp2p@0.40.x which has different config\n\n### Features\n\n* upgrade libp2p to 0.40.x ([#4237](https://www.github.com/ipfs/js-ipfs/issues/4237)) ([0cee4a4](https://www.github.com/ipfs/js-ipfs/commit/0cee4a4c55767022584dcbade0b0b9b43326f9c9))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core bumped from ^0.16.1 to ^0.17.0\n    * ipfs-core-types bumped from ^0.12.1 to ^0.13.0\n    * ipfs-grpc-server bumped from ^0.10.1 to ^0.11.0\n    * ipfs-http-gateway bumped from ^0.11.1 to ^0.12.0\n    * ipfs-http-server bumped from ^0.13.2 to ^0.14.0\n\n### [0.14.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-daemon-v0.14.1...ipfs-daemon-v0.14.2) (2022-09-21)\n\n\n### Bug Fixes\n\n* update @multiformats/multiadd to 11.0.0 ([2a830bf](https://www.github.com/ipfs/js-ipfs/commit/2a830bf58a5929fcce51dede871c99f62192fbda))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core bumped from ^0.16.0 to ^0.16.1\n    * ipfs-core-types bumped from ^0.12.0 to ^0.12.1\n    * ipfs-grpc-server bumped from ^0.10.0 to ^0.10.1\n    * ipfs-http-gateway bumped from ^0.11.0 to ^0.11.1\n    * ipfs-http-server bumped from ^0.13.1 to ^0.13.2\n\n### [0.14.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-daemon-v0.14.0...ipfs-daemon-v0.14.1) (2022-09-16)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-http-server bumped from ^0.13.0 to ^0.13.1\n\n## [0.14.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-daemon-v0.13.5...ipfs-daemon-v0.14.0) (2022-09-06)\n\n\n### ⚠ BREAKING CHANGES\n\n* update to libp2p@0.38.x (#4151)\n\n### deps\n\n* update to libp2p@0.38.x ([#4151](https://www.github.com/ipfs/js-ipfs/issues/4151)) ([39dbf70](https://www.github.com/ipfs/js-ipfs/commit/39dbf708ec31b263115e44f420651fa4e056a89e))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core bumped from ^0.15.0 to ^0.16.0\n    * ipfs-core-types bumped from ^0.11.0 to ^0.12.0\n    * ipfs-grpc-server bumped from ^0.9.0 to ^0.10.0\n    * ipfs-http-gateway bumped from ^0.10.0 to ^0.11.0\n    * ipfs-http-server bumped from ^0.12.0 to ^0.13.0\n\n### [0.13.5](https://www.github.com/ipfs/js-ipfs/compare/ipfs-daemon-v0.13.4...ipfs-daemon-v0.13.5) (2022-06-24)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core bumped from ^0.15.3 to ^0.15.4\n    * ipfs-grpc-server bumped from ^0.9.3 to ^0.9.4\n    * ipfs-http-gateway bumped from ^0.10.3 to ^0.10.4\n    * ipfs-http-server bumped from ^0.12.4 to ^0.12.5\n\n### [0.13.4](https://www.github.com/ipfs/js-ipfs/compare/ipfs-daemon-v0.13.3...ipfs-daemon-v0.13.4) (2022-06-22)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core bumped from ^0.15.2 to ^0.15.3\n    * ipfs-core-types bumped from ^0.11.0 to ^0.11.1\n    * ipfs-grpc-server bumped from ^0.9.2 to ^0.9.3\n    * ipfs-http-gateway bumped from ^0.10.2 to ^0.10.3\n    * ipfs-http-server bumped from ^0.12.3 to ^0.12.4\n\n### [0.13.3](https://www.github.com/ipfs/js-ipfs/compare/ipfs-daemon-v0.13.2...ipfs-daemon-v0.13.3) (2022-06-13)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core bumped from ^0.15.1 to ^0.15.2\n    * ipfs-grpc-server bumped from ^0.9.1 to ^0.9.2\n    * ipfs-http-gateway bumped from ^0.10.1 to ^0.10.2\n    * ipfs-http-server bumped from ^0.12.2 to ^0.12.3\n\n### [0.13.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-daemon-v0.13.1...ipfs-daemon-v0.13.2) (2022-06-01)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core bumped from ^0.15.0 to ^0.15.1\n    * ipfs-grpc-server bumped from ^0.9.0 to ^0.9.1\n    * ipfs-http-gateway bumped from ^0.10.0 to ^0.10.1\n    * ipfs-http-server bumped from ^0.12.1 to ^0.12.2\n\n### [0.13.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-daemon-v0.13.0...ipfs-daemon-v0.13.1) (2022-05-30)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-http-server bumped from ^0.12.0 to ^0.12.1\n\n## [0.13.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-daemon-v0.12.3...ipfs-daemon-v0.13.0) (2022-05-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* This module is now ESM only and there return types of some methods have changed\n\n### Features\n\n* update to libp2p 0.37.x ([#4092](https://www.github.com/ipfs/js-ipfs/issues/4092)) ([74aee8b](https://www.github.com/ipfs/js-ipfs/commit/74aee8b3d78f233c3199a3e9a6c0ac628a31a433))\n\n\n### Bug Fixes\n\n* update to latest libp2p interfaces ([#4111](https://www.github.com/ipfs/js-ipfs/issues/4111)) ([4e93dd5](https://www.github.com/ipfs/js-ipfs/commit/4e93dd5d4f4be397c2b1cd8ae5d17e593493e6a9))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core bumped from ^0.14.3 to ^0.15.0\n    * ipfs-core-types bumped from ^0.10.3 to ^0.11.0\n    * ipfs-grpc-server bumped from ^0.8.3 to ^0.9.0\n    * ipfs-http-gateway bumped from ^0.9.2 to ^0.10.0\n    * ipfs-http-server bumped from ^0.11.2 to ^0.12.0\n\n### [0.12.3](https://www.github.com/ipfs/js-ipfs/compare/ipfs-daemon-v0.12.2...ipfs-daemon-v0.12.3) (2022-04-20)\n\n\n### Bug Fixes\n\n* upgrade dep of ipfs-utils ^9.0.2->^9.0.6 ([#4086](https://www.github.com/ipfs/js-ipfs/issues/4086)) ([8f7ce23](https://www.github.com/ipfs/js-ipfs/commit/8f7ce23c18be12bdc52b98bfccbd0a5a2a9c9f7e)), closes [#4080](https://www.github.com/ipfs/js-ipfs/issues/4080)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core bumped from ^0.14.2 to ^0.14.3\n    * ipfs-core-types bumped from ^0.10.2 to ^0.10.3\n    * ipfs-grpc-server bumped from ^0.8.3 to ^0.8.4\n    * ipfs-http-gateway bumped from ^0.9.2 to ^0.9.3\n    * ipfs-http-server bumped from ^0.11.2 to ^0.11.3\n\n### [0.12.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-daemon-v0.12.1...ipfs-daemon-v0.12.2) (2022-03-01)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core bumped from ^0.14.1 to ^0.14.2\n    * ipfs-core-types bumped from ^0.10.1 to ^0.10.2\n    * ipfs-grpc-server bumped from ^0.8.2 to ^0.8.3\n    * ipfs-http-gateway bumped from ^0.9.1 to ^0.9.2\n    * ipfs-http-server bumped from ^0.11.1 to ^0.11.2\n\n### [0.12.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-daemon-v0.12.0...ipfs-daemon-v0.12.1) (2022-02-06)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core bumped from ^0.14.0 to ^0.14.1\n    * ipfs-core-types bumped from ^0.10.0 to ^0.10.1\n    * ipfs-grpc-server bumped from ^0.8.1 to ^0.8.2\n    * ipfs-http-gateway bumped from ^0.9.0 to ^0.9.1\n    * ipfs-http-server bumped from ^0.11.0 to ^0.11.1\n\n## [0.12.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-daemon-v0.11.0...ipfs-daemon-v0.12.0) (2022-01-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* peerstore methods are now all async, the repo is migrated to v12\n\n### Features\n\n* libp2p async peerstore ([#4018](https://www.github.com/ipfs/js-ipfs/issues/4018)) ([a6b201a](https://www.github.com/ipfs/js-ipfs/commit/a6b201af2c3697430ab0ebe002dd573d185f1ac0))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core bumped from ^0.13.0 to ^0.14.0\n    * ipfs-core-types bumped from ^0.9.0 to ^0.10.0\n    * ipfs-grpc-server bumped from ^0.8.0 to ^0.8.1\n    * ipfs-http-gateway bumped from ^0.8.0 to ^0.9.0\n    * ipfs-http-server bumped from ^0.10.0 to ^0.11.0\n\n## [0.11.0](https://github.com/ipfs/js-ipfs/compare/ipfs-daemon@0.10.4...ipfs-daemon@0.11.0) (2021-12-15)\n\n\n### Features\n\n* dht client ([#3947](https://github.com/ipfs/js-ipfs/issues/3947)) ([62d8ecb](https://github.com/ipfs/js-ipfs/commit/62d8ecbc723e693a2544e69172d99c576d187c23))\n* improve collected metrics ([#3978](https://github.com/ipfs/js-ipfs/issues/3978)) ([33f1034](https://github.com/ipfs/js-ipfs/commit/33f1034a6fc257f1a87de7bb38d876925f61cb5f))\n\n\n### BREAKING CHANGES\n\n* The DHT API has been refactored to return async iterators of query events\n\n## [0.10.4](https://github.com/ipfs/js-ipfs/compare/ipfs-daemon@0.10.3...ipfs-daemon@0.10.4) (2021-11-24)\n\n**Note:** Version bump only for package ipfs-daemon\n\n\n\n\n\n## [0.10.3](https://github.com/ipfs/js-ipfs/compare/ipfs-daemon@0.10.2...ipfs-daemon@0.10.3) (2021-11-19)\n\n**Note:** Version bump only for package ipfs-daemon\n\n\n\n\n\n## [0.10.2](https://github.com/ipfs/js-ipfs/compare/ipfs-daemon@0.10.1...ipfs-daemon@0.10.2) (2021-11-12)\n\n**Note:** Version bump only for package ipfs-daemon\n\n\n\n\n\n## [0.10.1](https://github.com/ipfs/js-ipfs/compare/ipfs-daemon@0.10.0...ipfs-daemon@0.10.1) (2021-09-28)\n\n**Note:** Version bump only for package ipfs-daemon\n\n\n\n\n\n## [0.10.0](https://github.com/ipfs/js-ipfs/compare/ipfs-daemon@0.9.8...ipfs-daemon@0.10.0) (2021-09-24)\n\n\n### Features\n\n* pull in new globSource ([#3889](https://github.com/ipfs/js-ipfs/issues/3889)) ([be4a542](https://github.com/ipfs/js-ipfs/commit/be4a5428ebc4b05a2edd9a91bf9df6416c1a8c2b))\n* switch to esm ([#3879](https://github.com/ipfs/js-ipfs/issues/3879)) ([9a40109](https://github.com/ipfs/js-ipfs/commit/9a40109632e5b4837eb77a2f57dbc77fbf1fe099))\n\n\n### BREAKING CHANGES\n\n* the globSource api has changed from `globSource(dir, opts)` to `globSource(dir, pattern, opts)`\n* There are no default exports and everything is now dual published as ESM/CJS\n\n\n\n\n\n### [0.9.8](https://github.com/ipfs/js-ipfs/compare/ipfs-daemon@0.9.7...ipfs-daemon@0.9.8) (2021-09-17)\n\n**Note:** Version bump only for package ipfs-daemon\n\n\n\n\n\n### [0.9.7](https://github.com/ipfs/js-ipfs/compare/ipfs-daemon@0.9.6...ipfs-daemon@0.9.7) (2021-09-17)\n\n**Note:** Version bump only for package ipfs-daemon\n\n\n\n\n\n### [0.9.6](https://github.com/ipfs/js-ipfs/compare/ipfs-daemon@0.9.5...ipfs-daemon@0.9.6) (2021-09-08)\n\n**Note:** Version bump only for package ipfs-daemon\n\n\n\n\n\n### [0.9.5](https://github.com/ipfs/js-ipfs/compare/ipfs-daemon@0.9.4...ipfs-daemon@0.9.5) (2021-09-02)\n\n\n### Bug Fixes\n\n* remove use of instanceof for CID class ([#3847](https://github.com/ipfs/js-ipfs/issues/3847)) ([ebbb12d](https://github.com/ipfs/js-ipfs/commit/ebbb12db523c53ce8e4ddae5266cd9acb3504431))\n\n\n\n\n\n### [0.9.4](https://github.com/ipfs/js-ipfs/compare/ipfs-daemon@0.9.3...ipfs-daemon@0.9.4) (2021-08-25)\n\n**Note:** Version bump only for package ipfs-daemon\n\n\n\n\n\n### [0.9.3](https://github.com/ipfs/js-ipfs/compare/ipfs-daemon@0.9.1...ipfs-daemon@0.9.3) (2021-08-17)\n\n**Note:** Version bump only for package ipfs-daemon\n\n\n\n\n\n### [0.9.1](https://github.com/ipfs/js-ipfs/compare/ipfs-daemon@0.9.0...ipfs-daemon@0.9.1) (2021-08-17)\n\n**Note:** Version bump only for package ipfs-daemon\n\n\n\n\n\n## [0.9.0](https://github.com/ipfs/js-ipfs/compare/ipfs-daemon@0.8.1...ipfs-daemon@0.9.0) (2021-08-11)\n\n\n### Features\n\n* make ipfs.get output tarballs ([#3785](https://github.com/ipfs/js-ipfs/issues/3785)) ([1ad6001](https://github.com/ipfs/js-ipfs/commit/1ad60018d39d5b46c484756631e30e1989fd8eba))\n\n\n### BREAKING CHANGES\n\n* the output type of `ipfs.get` has changed and the `recursive` option has been removed from `ipfs.ls` since it was not supported everywhere\n\n\n\n\n\n### [0.8.1](https://github.com/ipfs/js-ipfs/compare/ipfs-daemon@0.8.0...ipfs-daemon@0.8.1) (2021-07-30)\n\n**Note:** Version bump only for package ipfs-daemon\n\n\n\n\n\n## [0.8.0](https://github.com/ipfs/js-ipfs/compare/ipfs-daemon@0.7.2...ipfs-daemon@0.8.0) (2021-07-27)\n\n\n### Features\n\n* upgrade to the new multiformats ([#3556](https://github.com/ipfs/js-ipfs/issues/3556)) ([d13d15f](https://github.com/ipfs/js-ipfs/commit/d13d15f022a87d04a35f0f7822142f9cb898479c))\n\n\n### BREAKING CHANGES\n\n* ipld-formats no longer supported, use multiformat BlockCodecs instead\n\nCo-authored-by: Rod Vagg <rod@vagg.org>\nCo-authored-by: achingbrain <alex@achingbrain.net>\n\n\n\n\n\n### [0.7.2](https://github.com/ipfs/js-ipfs/compare/ipfs-daemon@0.7.1...ipfs-daemon@0.7.2) (2021-06-18)\n\n**Note:** Version bump only for package ipfs-daemon\n\n\n\n\n\n### [0.7.1](https://github.com/ipfs/js-ipfs/compare/ipfs-daemon@0.7.0...ipfs-daemon@0.7.1) (2021-06-05)\n\n\n### Bug Fixes\n\n* move wrtc to optional deps ([#3705](https://github.com/ipfs/js-ipfs/issues/3705)) ([7cf404c](https://github.com/ipfs/js-ipfs/commit/7cf404c8fd11888fa803c6167bd2ec62d94a2b34))\n* stalling subscription on (node) http-client when daemon is stopped ([#3468](https://github.com/ipfs/js-ipfs/issues/3468)) ([0266abf](https://github.com/ipfs/js-ipfs/commit/0266abf0c4b817636172f78c6e91eb4dd5aad451)), closes [#3465](https://github.com/ipfs/js-ipfs/issues/3465)\n\n\n\n\n\n## [0.7.0](https://github.com/ipfs/js-ipfs/compare/ipfs-daemon@0.6.1...ipfs-daemon@0.7.0) (2021-05-26)\n\n\n### Features\n\n* allow passing the id of a network peer to ipfs.id ([#3386](https://github.com/ipfs/js-ipfs/issues/3386)) ([00fd709](https://github.com/ipfs/js-ipfs/commit/00fd709a7b71e7cf354ea452ebce460dd7375d34))\n\n\n\n\n\n### [0.6.1](https://github.com/ipfs/js-ipfs/compare/ipfs-daemon@0.6.0...ipfs-daemon@0.6.1) (2021-05-11)\n\n**Note:** Version bump only for package ipfs-daemon\n\n\n\n\n\n## [0.6.0](https://github.com/ipfs/js-ipfs/compare/ipfs-daemon@0.5.4...ipfs-daemon@0.6.0) (2021-05-10)\n\n\n### Bug Fixes\n\n* mark ipld options as partial ([#3669](https://github.com/ipfs/js-ipfs/issues/3669)) ([f98af8e](https://github.com/ipfs/js-ipfs/commit/f98af8ed24784929898bb5d33a64dc442c77074d))\n\n\n### chore\n\n* upgrade deps with new typedefs ([#3550](https://github.com/ipfs/js-ipfs/issues/3550)) ([a418a52](https://github.com/ipfs/js-ipfs/commit/a418a521574c878d7aabd0ad2fd8d516908a3756))\n\n\n### BREAKING CHANGES\n\n* all core api methods now have types, some method signatures have changed, named exports are now used by the http, grpc and ipfs client modules\n\n\n\n\n\n### [0.5.4](https://github.com/ipfs/js-ipfs/compare/ipfs-daemon@0.5.3...ipfs-daemon@0.5.4) (2021-03-10)\n\n**Note:** Version bump only for package ipfs-daemon\n\n\n\n\n\n### [0.5.3](https://github.com/ipfs/js-ipfs/compare/ipfs-daemon@0.5.2...ipfs-daemon@0.5.3) (2021-03-09)\n\n\n### Bug Fixes\n\n* update to new aegir ([#3528](https://github.com/ipfs/js-ipfs/issues/3528)) ([49f7880](https://github.com/ipfs/js-ipfs/commit/49f78807d7e26483bd926b45cc7e0f797d77e41b))\n\n\n\n\n\n### [0.5.2](https://github.com/ipfs/js-ipfs/compare/ipfs-daemon@0.5.1...ipfs-daemon@0.5.2) (2021-02-08)\n\n**Note:** Version bump only for package ipfs-daemon\n\n\n\n\n\n### [0.5.1](https://github.com/ipfs/js-ipfs/compare/ipfs-daemon@0.5.0...ipfs-daemon@0.5.1) (2021-02-02)\n\n**Note:** Version bump only for package ipfs-daemon\n\n\n\n\n\n## [0.5.0](https://github.com/ipfs/js-ipfs/compare/ipfs-daemon@0.4.2...ipfs-daemon@0.5.0) (2021-02-01)\n\n\n### chore\n\n* update deps ([#3514](https://github.com/ipfs/js-ipfs/issues/3514)) ([061d77c](https://github.com/ipfs/js-ipfs/commit/061d77cc03f40af5a3bc3590481e1e5836e7f0d8))\n\n\n### Features\n\n* enable upnp nat hole punching ([#3426](https://github.com/ipfs/js-ipfs/issues/3426)) ([65dc161](https://github.com/ipfs/js-ipfs/commit/65dc161feebe154b4a2d1472940dc9e70fbb817f))\n\n\n### BREAKING CHANGES\n\n* ipfs-repo upgrade requires repo migration to v10\n\n\n\n\n\n### [0.4.2](https://github.com/ipfs/js-ipfs/compare/ipfs-daemon@0.4.1...ipfs-daemon@0.4.2) (2021-01-22)\n\n**Note:** Version bump only for package ipfs-daemon\n\n\n\n\n\n### [0.4.1](https://github.com/ipfs/js-ipfs/compare/ipfs-daemon@0.4.0...ipfs-daemon@0.4.1) (2021-01-20)\n\n**Note:** Version bump only for package ipfs-daemon\n\n\n\n\n\n## [0.4.0](https://github.com/ipfs/js-ipfs/compare/ipfs-daemon@0.3.2...ipfs-daemon@0.4.0) (2021-01-15)\n\n\n### chore\n\n* update libp2p to 0.30 ([#3427](https://github.com/ipfs/js-ipfs/issues/3427)) ([a39e6fb](https://github.com/ipfs/js-ipfs/commit/a39e6fb372bf9e7782462b6a4b7530a3f8c9b3f1))\n\n\n### Features\n\n* add grpc server and client ([#3403](https://github.com/ipfs/js-ipfs/issues/3403)) ([a9027e0](https://github.com/ipfs/js-ipfs/commit/a9027e0ec0cea9a4f34b4f2f52e09abb35237384)), closes [#2519](https://github.com/ipfs/js-ipfs/issues/2519) [#2838](https://github.com/ipfs/js-ipfs/issues/2838) [#2943](https://github.com/ipfs/js-ipfs/issues/2943) [#2854](https://github.com/ipfs/js-ipfs/issues/2854) [#2864](https://github.com/ipfs/js-ipfs/issues/2864)\n* allow passing a http.Agent to ipfs-http-client in node ([#3474](https://github.com/ipfs/js-ipfs/issues/3474)) ([fe93ba0](https://github.com/ipfs/js-ipfs/commit/fe93ba01a0c62cead7cc4e0023de2d2a00adbc02)), closes [/tools.ietf.org/html/rfc2616#section-8](https://github.com//tools.ietf.org/html/rfc2616/issues/section-8) [#3464](https://github.com/ipfs/js-ipfs/issues/3464)\n\n\n### BREAKING CHANGES\n\n* The websocket transport will only dial DNS+WSS addresses - see https://github.com/libp2p/js-libp2p-websockets/releases/tag/v0.15.0\n\nCo-authored-by: Hugo Dias <hugomrdias@gmail.com>\n\n\n\n\n\n## 0.3.2 (2020-12-16)\n\n**Note:** Version bump only for package ipfs-daemon"
  },
  {
    "path": "packages/ipfs-daemon/CODE_OF_CONDUCT.md",
    "content": "# Contributor Code of Conduct\n\nThe `js-ipfs` project follows the [`IPFS Community Code of Conduct`](https://github.com/ipfs/community/blob/master/code-of-conduct.md)\n"
  },
  {
    "path": "packages/ipfs-daemon/CONTRIBUTING.md",
    "content": "# Contributing guidelines\n\nIPFS as a project, including js-ipfs and all of its modules, follows the [standard IPFS Community contributing guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md).\n\nWe also adhere to the [IPFS JavaScript Community contributing guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING_JS.md) which provide additional information of how to collaborate and contribute in the JavaScript implementation of IPFS.\n\nWe appreciate your time and attention for going over these. Please open an issue on [ipfs/community](https://github.com/ipfs/community) if you have any question.\n\nThank you.\n"
  },
  {
    "path": "packages/ipfs-daemon/COPYRIGHT",
    "content": "This project is transitioning from an MIT-only license to a dual MIT/Apache-2.0 license.\nUnless otherwise noted, all code contributed prior to 2019-11-21 and not contributed by\na user listed in [this signoff issue](https://github.com/ipfs/js-ipfs/issues/2624) is\nlicensed under MIT-only. All new contributions (and past contributions since 2019-11-21)\nare licensed under a dual MIT/Apache-2.0 license.\n"
  },
  {
    "path": "packages/ipfs-daemon/LICENSE",
    "content": "This project is dual licensed under MIT and Apache-2.0.\n\nMIT: https://www.opensource.org/licenses/mit\nApache-2.0: https://www.apache.org/licenses/license-2.0\n"
  },
  {
    "path": "packages/ipfs-daemon/LICENSE-APACHE",
    "content": "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\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless 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.\n"
  },
  {
    "path": "packages/ipfs-daemon/LICENSE-MIT",
    "content": "The MIT License (MIT)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "packages/ipfs-daemon/README.md",
    "content": "> # ⛔️ DEPRECATED: [js-IPFS](https://github.com/ipfs/js-ipfs) has been superseded by [Helia](https://github.com/ipfs/helia)\n>\n> 📚 [Learn more about this deprecation](https://github.com/ipfs/js-ipfs/issues/4336) or [how to migrate](https://github.com/ipfs/helia/wiki/Migrating-from-js-IPFS)\n>\n> ⚠️ If you continue using this repo, please note that security fixes will not be provided\n\n# ipfs-daemon <!-- omit in toc -->\n\n[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech)\n[![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech)\n[![codecov](https://img.shields.io/codecov/c/github/ipfs/js-ipfs.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipfs)\n[![CI](https://img.shields.io/github/actions/workflow/status/ipfs/js-ipfs/test.yml?branch=master\\&style=flat-square)](https://github.com/ipfs/js-ipfs/actions/workflows/test.yml?query=branch%3Amaster)\n\n> JavaScript implementation of the IPFS specification\n\n## Table of contents <!-- omit in toc -->\n\n- [Install](#install)\n- [License](#license)\n- [Contribute](#contribute)\n\n## Install\n\n```console\n$ npm i ipfs-daemon\n```\n\n```console\n$ npm install -g ipfs\n// npm install output\n$ jsipfs daemon\n```\n\n## License\n\nLicensed under either of\n\n- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / <http://www.apache.org/licenses/LICENSE-2.0>)\n- MIT ([LICENSE-MIT](LICENSE-MIT) / <http://opensource.org/licenses/MIT>)\n\n## Contribute\n\nContributions welcome! Please check out [the issues](https://github.com/ipfs/js-ipfs/issues).\n\nAlso 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.\n\nPlease 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).\n\nUnless 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.\n\n[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md)\n"
  },
  {
    "path": "packages/ipfs-daemon/package.json",
    "content": "{\n  \"name\": \"ipfs-daemon\",\n  \"version\": \"0.16.1\",\n  \"description\": \"JavaScript implementation of the IPFS specification\",\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"homepage\": \"https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-daemon#readme\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ipfs/js-ipfs.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ipfs/js-ipfs/issues\"\n  },\n  \"keywords\": [\n    \"IPFS\"\n  ],\n  \"engines\": {\n    \"node\": \">=16.0.0\",\n    \"npm\": \">=7.0.0\"\n  },\n  \"type\": \"module\",\n  \"types\": \"./dist/src/index.d.ts\",\n  \"typesVersions\": {\n    \"*\": {\n      \"*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ],\n      \"src/*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ]\n    }\n  },\n  \"files\": [\n    \"src\",\n    \"dist\",\n    \"!dist/test\",\n    \"!**/*.tsbuildinfo\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/src/index.d.ts\",\n      \"import\": \"./src/index.js\"\n    }\n  },\n  \"eslintConfig\": {\n    \"extends\": \"ipfs\",\n    \"parserOptions\": {\n      \"sourceType\": \"module\"\n    }\n  },\n  \"scripts\": {\n    \"lint\": \"aegir lint\",\n    \"test\": \"aegir test -t node\",\n    \"test:node\": \"aegir test -t node -- --exit\",\n    \"clean\": \"aegir clean\",\n    \"dep-check\": \"aegir dep-check -i ipfs-core-types -i @mapbox/node-pre-gyp\",\n    \"build\": \"aegir build --no-bundle\"\n  },\n  \"dependencies\": {\n    \"@libp2p/logger\": \"^2.0.5\",\n    \"@libp2p/webrtc-star\": \"^6.0.0\",\n    \"@mapbox/node-pre-gyp\": \"^1.0.5\",\n    \"ipfs-core\": \"^0.18.1\",\n    \"ipfs-core-types\": \"^0.14.1\",\n    \"ipfs-grpc-server\": \"^0.12.1\",\n    \"ipfs-http-gateway\": \"^0.13.1\",\n    \"ipfs-http-server\": \"^0.15.1\",\n    \"ipfs-utils\": \"^9.0.13\",\n    \"libp2p\": \"^0.42.0\"\n  },\n  \"devDependencies\": {\n    \"aegir\": \"^37.11.0\",\n    \"node-fetch\": \"^3.2.3\",\n    \"ws\": \"^8.5.0\"\n  },\n  \"optionalDependencies\": {\n    \"electron-webrtc\": \"^0.3.0\",\n    \"prom-client\": \"^14.0.1\",\n    \"wrtc\": \"^0.4.6\"\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-daemon/src/index.js",
    "content": "import { logger } from '@libp2p/logger'\nimport { webRTCStar } from '@libp2p/webrtc-star'\nimport { create } from 'ipfs-core'\nimport { HttpApi } from 'ipfs-http-server'\nimport { HttpGateway } from 'ipfs-http-gateway'\nimport { createServer as gRPCServer } from 'ipfs-grpc-server'\nimport { isElectron } from 'ipfs-utils/src/env.js'\nimport { createLibp2p } from 'libp2p'\n\nconst log = logger('ipfs:daemon')\n\nexport class Daemon {\n  /**\n   * @param {import('ipfs-core').Options} options\n   */\n  constructor (options = {}) {\n    this._options = options\n\n    /** @type {import('ipfs-core-types').IPFS} */\n    // @ts-expect-error we set this in .start()\n    this._ipfs = undefined\n  }\n\n  /**\n   * Starts the IPFS HTTP server\n   */\n  async start () {\n    log('starting')\n\n    // start the daemon\n    this._ipfs = await create(\n      Object.assign({}, { start: true, libp2p: getLibp2p }, this._options)\n    )\n\n    // start HTTP servers (if API or Gateway is enabled in options)\n    this._httpApi = new HttpApi(this._ipfs)\n    await this._httpApi.start()\n\n    this._httpGateway = new HttpGateway(this._ipfs)\n    await this._httpGateway.start()\n\n    const config = await this._ipfs.config.getAll()\n\n    if (config.Addresses && config.Addresses.RPC) {\n      this._grpcServer = await gRPCServer(this._ipfs)\n    }\n\n    log('started')\n  }\n\n  async stop () {\n    log('stopping')\n\n    await Promise.all([\n      this._httpApi && this._httpApi.stop(),\n      this._httpGateway && this._httpGateway.stop(),\n      this._grpcServer && this._grpcServer.stop(),\n      this._ipfs && this._ipfs.stop()\n    ])\n\n    log('stopped')\n  }\n}\n\n/**\n * @type {import('ipfs-core').Libp2pFactoryFn}\n */\nasync function getLibp2p ({ libp2pOptions }) {\n  // Attempt to use any of the WebRTC versions available globally\n  let electronWebRTC\n  let wrtc\n\n  if (isElectron) {\n    try {\n      // @ts-expect-error - cant find type info\n      electronWebRTC = await import('electron-webrtc')()\n    } catch (/** @type {any} */ err) {\n      log('failed to load optional electron-webrtc dependency')\n    }\n  }\n\n  if (!electronWebRTC) {\n    try {\n      // @ts-expect-error - cant find type info\n      wrtc = (await import('wrtc')).default\n    } catch (/** @type {any} */ err) {\n      log('failed to load optional webrtc dependency')\n    }\n  }\n\n  if (wrtc || electronWebRTC) {\n    log(`Using ${wrtc ? 'wrtc' : 'electron-webrtc'} for webrtc support`)\n\n    const transport = webRTCStar({\n      wrtc: wrtc ?? electronWebRTC\n    })\n\n    libp2pOptions.transports = [...libp2pOptions.transports ?? [], transport.transport]\n    libp2pOptions.peerDiscovery = [...libp2pOptions.peerDiscovery ?? [], transport.discovery]\n  }\n\n  return createLibp2p(libp2pOptions)\n}\n"
  },
  {
    "path": "packages/ipfs-daemon/test/index.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { Daemon } from '../src/index.js'\nimport fetch from 'node-fetch'\nimport WebSocket from 'ws'\nimport os from 'os'\n\nfunction createDaemon () {\n  return new Daemon({\n    init: {\n      bits: 512\n    },\n    repo: `${os.tmpdir()}/ipfs-test-${Math.random()}`,\n    config: {\n      Addresses: {\n        Swarm: [],\n        API: '/ip4/127.0.0.1/tcp/0',\n        Gateway: '/ip4/127.0.0.1/tcp/0',\n        RPC: '/ip4/127.0.0.1/tcp/0'\n      }\n    }\n  })\n}\n\ndescribe('daemon', function () {\n  // slow ci is slow\n  this.timeout(60 * 1000)\n\n  let daemon\n\n  it('should start a http api server', async () => {\n    daemon = createDaemon()\n\n    await daemon.start()\n\n    const {\n      uri\n    } = daemon._httpApi._apiServers[0].info\n\n    const idFromCore = await daemon._ipfs.id()\n\n    const httpId = await fetch(`${uri}/api/v0/id`, {\n      method: 'POST'\n    })\n\n    await expect(httpId.json()).to.eventually.have.property('PublicKey', idFromCore.publicKey)\n\n    await daemon.stop()\n  })\n\n  it('should start a http gateway server', async () => {\n    daemon = createDaemon()\n\n    await daemon.start()\n\n    const {\n      uri\n    } = daemon._httpGateway._gatewayServers[0].info\n\n    const result = await fetch(`${uri}/ipfs/QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn`, {\n      method: 'POST'\n    })\n\n    await expect(result.text()).to.eventually.include('Index of /ipfs/QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn/')\n\n    await daemon.stop()\n  })\n\n  it('should start a gRPC server', async () => {\n    daemon = createDaemon()\n\n    await daemon.start()\n\n    const {\n      uri\n    } = daemon._grpcServer.info\n\n    const socket = new WebSocket(`${uri}/ipfs.Root/id`.replace('http', 'ws'))\n    let received = Buffer.alloc(0)\n\n    await new Promise((resolve) => {\n      socket.on('open', () => {\n        socket.send(Buffer.from('Y29udGVudC10eXBlOiBhcHBsaWNhdGlvbi9ncnBjLXdlYitwcm90bw0KeC1ncnBjLXdlYjogMQ0K', 'base64'))\n        socket.send(Buffer.from('AAAAAAAA', 'base64'))\n      })\n\n      socket.on('message', (data) => {\n        received = Buffer.concat([received, data], received.byteLength + data.byteLength)\n      })\n\n      socket.on('close', () => {\n        resolve()\n      })\n    })\n\n    const apiId = await daemon._ipfs.id()\n\n    // don't try to decode protobuf, just look for embedded string\n    expect(received.toString('utf8')).to.include(apiId.id)\n\n    await daemon.stop()\n  })\n\n  it('should stop', async () => {\n    daemon = createDaemon()\n\n    await daemon.start()\n    await daemon.stop()\n\n    const {\n      uri\n    } = daemon._httpApi._apiServers[0].info\n\n    await expect(fetch(`${uri}/api/v0/id`, {\n      method: 'POST'\n    })).to.eventually.be.rejectedWith(/ECONNREFUSED/)\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-daemon/tsconfig.json",
    "content": "{\n  \"extends\": \"aegir/src/config/tsconfig.aegir.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"emitDeclarationOnly\": true\n  },\n  \"include\": [\n    \"src\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../ipfs-core\"\n    },\n    {\n      \"path\": \"../ipfs-core-types\"\n    },\n    {\n      \"path\": \"../ipfs-grpc-server\"\n    },\n    {\n      \"path\": \"../ipfs-http-gateway\"\n    },\n    {\n      \"path\": \"../ipfs-http-server\"\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-client/.aegir.js",
    "content": "\n/** @type {import('aegir').PartialOptions} */\nexport default {\n  build: {\n    bundlesizeMax: '53KB'\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-client/CHANGELOG.md",
    "content": "# Change Log\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n\n### [0.13.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-grpc-client-v0.13.0...ipfs-grpc-client-v0.13.1) (2023-05-25)\n\n\n### Bug Fixes\n\n* add deprecation notice to readmes ([#4362](https://www.github.com/ipfs/js-ipfs/issues/4362)) ([7b79c1b](https://www.github.com/ipfs/js-ipfs/commit/7b79c1b8df5c818dc124b346ea28330455732d5c))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.14.0 to ^0.14.1\n    * ipfs-core-utils bumped from ^0.18.0 to ^0.18.1\n    * ipfs-grpc-protocol bumped from ^0.8.0 to ^0.8.1\n\n## [0.13.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-grpc-client-v0.12.0...ipfs-grpc-client-v0.13.0) (2023-01-11)\n\n\n### ⚠ BREAKING CHANGES\n\n* update multiformats to v11.x.x and related depenendcies (#4277)\n\n### Bug Fixes\n\n* update multiformats to v11.x.x and related depenendcies ([#4277](https://www.github.com/ipfs/js-ipfs/issues/4277)) ([521c84a](https://www.github.com/ipfs/js-ipfs/commit/521c84a958b04d61702577a5adce28519c1b2a3b))\n* use aegir to publish RCs ([#4284](https://www.github.com/ipfs/js-ipfs/issues/4284)) ([6d90cbf](https://www.github.com/ipfs/js-ipfs/commit/6d90cbf321a7dbf4b1084ba20f0c514dc08d8d0a))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.13.0 to ^0.14.0\n    * ipfs-core-utils bumped from ^0.17.0 to ^0.18.0\n    * ipfs-grpc-protocol bumped from ^0.7.0 to ^0.8.0\n\n## [0.12.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-grpc-client-v0.11.1...ipfs-grpc-client-v0.12.0) (2022-10-24)\n\n\n### ⚠ BREAKING CHANGES\n\n* ipfs is now bundled with libp2p@0.40.x which has different config\n\n### Features\n\n* upgrade libp2p to 0.40.x ([#4237](https://www.github.com/ipfs/js-ipfs/issues/4237)) ([0cee4a4](https://www.github.com/ipfs/js-ipfs/commit/0cee4a4c55767022584dcbade0b0b9b43326f9c9))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.12.1 to ^0.13.0\n    * ipfs-core-utils bumped from ^0.16.1 to ^0.17.0\n\n### [0.11.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-grpc-client-v0.11.0...ipfs-grpc-client-v0.11.1) (2022-09-21)\n\n\n### Bug Fixes\n\n* update @multiformats/multiadd to 11.0.0 ([2a830bf](https://www.github.com/ipfs/js-ipfs/commit/2a830bf58a5929fcce51dede871c99f62192fbda))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.12.0 to ^0.12.1\n    * ipfs-core-utils bumped from ^0.16.0 to ^0.16.1\n\n## [0.11.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-grpc-client-v0.10.2...ipfs-grpc-client-v0.11.0) (2022-09-06)\n\n\n### ⚠ BREAKING CHANGES\n\n* update to libp2p@0.38.x (#4151)\n\n### deps\n\n* update to libp2p@0.38.x ([#4151](https://www.github.com/ipfs/js-ipfs/issues/4151)) ([39dbf70](https://www.github.com/ipfs/js-ipfs/commit/39dbf708ec31b263115e44f420651fa4e056a89e))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.11.0 to ^0.12.0\n    * ipfs-core-utils bumped from ^0.15.0 to ^0.16.0\n    * ipfs-grpc-protocol bumped from ^0.6.0 to ^0.7.0\n\n### [0.10.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-grpc-client-v0.10.1...ipfs-grpc-client-v0.10.2) (2022-06-24)\n\n\n### Bug Fixes\n\n* make pubsub message types consistent ([#4145](https://www.github.com/ipfs/js-ipfs/issues/4145)) ([00bd3dd](https://www.github.com/ipfs/js-ipfs/commit/00bd3dd0bca7fc705e5e87272972f586d1f161e8))\n\n### [0.10.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-grpc-client-v0.10.0...ipfs-grpc-client-v0.10.1) (2022-06-22)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.11.0 to ^0.11.1\n    * ipfs-core-utils bumped from ^0.15.0 to ^0.15.1\n\n## [0.10.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-grpc-client-v0.9.4...ipfs-grpc-client-v0.10.0) (2022-05-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* This module is now ESM only and there return types of some methods have changed\n\n### Features\n\n* update to libp2p 0.37.x ([#4092](https://www.github.com/ipfs/js-ipfs/issues/4092)) ([74aee8b](https://www.github.com/ipfs/js-ipfs/commit/74aee8b3d78f233c3199a3e9a6c0ac628a31a433))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.3 to ^0.11.0\n    * ipfs-core-utils bumped from ^0.14.3 to ^0.15.0\n    * ipfs-grpc-protocol bumped from ^0.5.5 to ^0.6.0\n\n### [0.9.4](https://www.github.com/ipfs/js-ipfs/compare/ipfs-grpc-client-v0.9.3...ipfs-grpc-client-v0.9.4) (2022-04-20)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.2 to ^0.10.3\n    * ipfs-core-utils bumped from ^0.14.2 to ^0.14.3\n\n### [0.9.3](https://www.github.com/ipfs/js-ipfs/compare/ipfs-grpc-client-v0.9.2...ipfs-grpc-client-v0.9.3) (2022-03-01)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.1 to ^0.10.2\n    * ipfs-core-utils bumped from ^0.14.1 to ^0.14.2\n\n### [0.9.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-grpc-client-v0.9.1...ipfs-grpc-client-v0.9.2) (2022-02-06)\n\n\n### Bug Fixes\n\n* **dag:** replace custom dag walk with multiformats/traversal ([#3950](https://www.github.com/ipfs/js-ipfs/issues/3950)) ([596b1f4](https://www.github.com/ipfs/js-ipfs/commit/596b1f48a014083b1736e4ad7e746c652d2583b1))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.0 to ^0.10.1\n    * ipfs-core-utils bumped from ^0.14.0 to ^0.14.1\n\n### [0.9.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-grpc-client-v0.9.0...ipfs-grpc-client-v0.9.1) (2022-01-27)\n\n\n## [0.9.0](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-client@0.8.2...ipfs-grpc-client@0.9.0) (2021-12-15)\n\n\n### Features\n\n* dht client ([#3947](https://github.com/ipfs/js-ipfs/issues/3947)) ([62d8ecb](https://github.com/ipfs/js-ipfs/commit/62d8ecbc723e693a2544e69172d99c576d187c23))\n\n\n### BREAKING CHANGES\n\n* The DHT API has been refactored to return async iterators of query events\n\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.9.0 to ^0.10.0\n    * ipfs-core-utils bumped from ^0.13.0 to ^0.14.0\n\n### [0.8.2](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-client@0.8.1...ipfs-grpc-client@0.8.2) (2021-11-24)\n\n**Note:** Version bump only for package ipfs-grpc-client\n\n\n\n\n\n### [0.8.1](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-client@0.8.0...ipfs-grpc-client@0.8.1) (2021-11-19)\n\n**Note:** Version bump only for package ipfs-grpc-client\n\n\n\n\n\n## [0.8.0](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-client@0.7.1...ipfs-grpc-client@0.8.0) (2021-11-12)\n\n\n### Bug Fixes\n\n* do not accept single items for ipfs.add ([#3900](https://github.com/ipfs/js-ipfs/issues/3900)) ([04e3cf3](https://github.com/ipfs/js-ipfs/commit/04e3cf3f46b585c4644cba70516f375e95361f52))\n\n\n### BREAKING CHANGES\n\n* errors will now be thrown if multiple items are passed to `ipfs.add` or single items to `ipfs.addAll` (n.b. you can still pass a list of a single item to `ipfs.addAll`)\n\n\n\n\n\n### [0.7.1](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-client@0.7.0...ipfs-grpc-client@0.7.1) (2021-09-28)\n\n**Note:** Version bump only for package ipfs-grpc-client\n\n\n\n\n\n## [0.7.0](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-client@0.6.5...ipfs-grpc-client@0.7.0) (2021-09-24)\n\n\n### Bug Fixes\n\n* handle node readable streams properly ([#3890](https://github.com/ipfs/js-ipfs/issues/3890)) ([b0f367d](https://github.com/ipfs/js-ipfs/commit/b0f367d666aceb4ea8bdd532a9d8c3501f8cc78d)), closes [#3882](https://github.com/ipfs/js-ipfs/issues/3882)\n\n\n### Features\n\n* switch to esm ([#3879](https://github.com/ipfs/js-ipfs/issues/3879)) ([9a40109](https://github.com/ipfs/js-ipfs/commit/9a40109632e5b4837eb77a2f57dbc77fbf1fe099))\n\n\n### BREAKING CHANGES\n\n* There are no default exports and everything is now dual published as ESM/CJS\n\n\n\n\n\n### [0.6.5](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-client@0.6.4...ipfs-grpc-client@0.6.5) (2021-09-17)\n\n**Note:** Version bump only for package ipfs-grpc-client\n\n\n\n\n\n### [0.6.4](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-client@0.6.3...ipfs-grpc-client@0.6.4) (2021-09-17)\n\n**Note:** Version bump only for package ipfs-grpc-client\n\n\n\n\n\n### [0.6.3](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-client@0.6.2...ipfs-grpc-client@0.6.3) (2021-09-02)\n\n\n### Bug Fixes\n\n* declare types in .ts files ([#3840](https://github.com/ipfs/js-ipfs/issues/3840)) ([eba5fe6](https://github.com/ipfs/js-ipfs/commit/eba5fe6832858107b3e1ae02c99de674622f12b4))\n* remove use of instanceof for CID class ([#3847](https://github.com/ipfs/js-ipfs/issues/3847)) ([ebbb12d](https://github.com/ipfs/js-ipfs/commit/ebbb12db523c53ce8e4ddae5266cd9acb3504431))\n\n\n\n\n\n### [0.6.2](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-client@0.6.1...ipfs-grpc-client@0.6.2) (2021-08-25)\n\n**Note:** Version bump only for package ipfs-grpc-client\n\n\n\n\n\n### [0.6.1](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-client@0.6.0...ipfs-grpc-client@0.6.1) (2021-08-17)\n\n**Note:** Version bump only for package ipfs-grpc-client\n\n\n\n\n\n## [0.6.0](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-client@0.5.0...ipfs-grpc-client@0.6.0) (2021-08-17)\n\n\n### Features\n\n* pubsub over gRPC ([#3813](https://github.com/ipfs/js-ipfs/issues/3813)) ([e7d5509](https://github.com/ipfs/js-ipfs/commit/e7d5509c87e87aed6be3c1d0b2a01ab74cdc1ed9)), closes [#3741](https://github.com/ipfs/js-ipfs/issues/3741)\n\n\n\n\n\n## [0.5.0](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-client@0.4.1...ipfs-grpc-client@0.5.0) (2021-08-11)\n\n\n### Features\n\n* make ipfs.get output tarballs ([#3785](https://github.com/ipfs/js-ipfs/issues/3785)) ([1ad6001](https://github.com/ipfs/js-ipfs/commit/1ad60018d39d5b46c484756631e30e1989fd8eba))\n\n\n### BREAKING CHANGES\n\n* the output type of `ipfs.get` has changed and the `recursive` option has been removed from `ipfs.ls` since it was not supported everywhere\n\n\n\n\n\n### [0.4.1](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-client@0.4.0...ipfs-grpc-client@0.4.1) (2021-07-30)\n\n**Note:** Version bump only for package ipfs-grpc-client\n\n\n\n\n\n## [0.4.0](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-client@0.3.3...ipfs-grpc-client@0.4.0) (2021-07-27)\n\n\n### Features\n\n* implement dag import/export ([#3728](https://github.com/ipfs/js-ipfs/issues/3728)) ([700765b](https://github.com/ipfs/js-ipfs/commit/700765be2634fa5d2d71d8b87cf68c9cd328d2c4)), closes [#2953](https://github.com/ipfs/js-ipfs/issues/2953) [#2745](https://github.com/ipfs/js-ipfs/issues/2745)\n* upgrade to the new multiformats ([#3556](https://github.com/ipfs/js-ipfs/issues/3556)) ([d13d15f](https://github.com/ipfs/js-ipfs/commit/d13d15f022a87d04a35f0f7822142f9cb898479c))\n\n\n### BREAKING CHANGES\n\n* ipld-formats no longer supported, use multiformat BlockCodecs instead\n\nCo-authored-by: Rod Vagg <rod@vagg.org>\nCo-authored-by: achingbrain <alex@achingbrain.net>\n\n\n\n\n\n### [0.3.3](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-client@0.3.2...ipfs-grpc-client@0.3.3) (2021-06-18)\n\n**Note:** Version bump only for package ipfs-grpc-client\n\n\n\n\n\n### [0.3.2](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-client@0.3.1...ipfs-grpc-client@0.3.2) (2021-06-05)\n\n**Note:** Version bump only for package ipfs-grpc-client\n\n\n\n\n\n### [0.3.1](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-client@0.3.0...ipfs-grpc-client@0.3.1) (2021-05-26)\n\n**Note:** Version bump only for package ipfs-grpc-client\n\n\n\n\n\n## [0.3.0](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-client@0.2.4...ipfs-grpc-client@0.3.0) (2021-05-10)\n\n\n### Bug Fixes\n\n* ignore the ts error caused by the recent protobufjs type change ([#3656](https://github.com/ipfs/js-ipfs/issues/3656)) ([084589c](https://github.com/ipfs/js-ipfs/commit/084589c0116d8f27ce1462424fb93b6037b776a9))\n\n\n### chore\n\n* upgrade deps with new typedefs ([#3550](https://github.com/ipfs/js-ipfs/issues/3550)) ([a418a52](https://github.com/ipfs/js-ipfs/commit/a418a521574c878d7aabd0ad2fd8d516908a3756))\n\n\n### BREAKING CHANGES\n\n* all core api methods now have types, some method signatures have changed, named exports are now used by the http, grpc and ipfs client modules\n\n\n\n\n\n### [0.2.4](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-client@0.2.3...ipfs-grpc-client@0.2.4) (2021-03-10)\n\n**Note:** Version bump only for package ipfs-grpc-client\n\n\n\n\n\n### [0.2.3](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-client@0.2.2...ipfs-grpc-client@0.2.3) (2021-03-09)\n\n\n### Bug Fixes\n\n* update to new aegir ([#3528](https://github.com/ipfs/js-ipfs/issues/3528)) ([49f7880](https://github.com/ipfs/js-ipfs/commit/49f78807d7e26483bd926b45cc7e0f797d77e41b))\n\n\n\n\n\n### [0.2.2](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-client@0.2.1...ipfs-grpc-client@0.2.2) (2021-02-08)\n\n**Note:** Version bump only for package ipfs-grpc-client\n\n\n\n\n\n### [0.2.1](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-client@0.2.0...ipfs-grpc-client@0.2.1) (2021-02-02)\n\n**Note:** Version bump only for package ipfs-grpc-client\n\n\n\n\n\n## [0.2.0](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-client@0.1.2...ipfs-grpc-client@0.2.0) (2021-02-01)\n\n\n### chore\n\n* update deps ([#3514](https://github.com/ipfs/js-ipfs/issues/3514)) ([061d77c](https://github.com/ipfs/js-ipfs/commit/061d77cc03f40af5a3bc3590481e1e5836e7f0d8))\n\n\n### BREAKING CHANGES\n\n* ipfs-repo upgrade requires repo migration to v10\n\n\n\n\n\n### [0.1.2](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-client@0.1.1...ipfs-grpc-client@0.1.2) (2021-01-22)\n\n\n### Bug Fixes\n\n* issue with isolateModules flag ([#3495](https://github.com/ipfs/js-ipfs/issues/3495)) ([839e190](https://github.com/ipfs/js-ipfs/commit/839e1908f3c050b45af176883a7e450fb339bef0)), closes [#3494](https://github.com/ipfs/js-ipfs/issues/3494) [#3498](https://github.com/ipfs/js-ipfs/issues/3498) [/github.com/ipfs-shipyard/ipfs-webui/pull/1655#issuecomment-763846124](https://github.com//github.com/ipfs-shipyard/ipfs-webui/pull/1655/issues/issuecomment-763846124)\n\n\n\n\n\n### [0.1.1](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-client@0.1.0...ipfs-grpc-client@0.1.1) (2021-01-20)\n\n\n### Bug Fixes\n\n* use https agent for https requests ([#3490](https://github.com/ipfs/js-ipfs/issues/3490)) ([ac4bb48](https://github.com/ipfs/js-ipfs/commit/ac4bb4841ce7c191408e1b2bb906284ae0dbd975)), closes [#3474](https://github.com/ipfs/js-ipfs/issues/3474)\n\n\n\n\n\n# 0.1.0 (2021-01-15)\n\n\n### Features\n\n* add grpc server and client ([#3403](https://github.com/ipfs/js-ipfs/issues/3403)) ([a9027e0](https://github.com/ipfs/js-ipfs/commit/a9027e0ec0cea9a4f34b4f2f52e09abb35237384)), closes [#2519](https://github.com/ipfs/js-ipfs/issues/2519) [#2838](https://github.com/ipfs/js-ipfs/issues/2838) [#2943](https://github.com/ipfs/js-ipfs/issues/2943) [#2854](https://github.com/ipfs/js-ipfs/issues/2854) [#2864](https://github.com/ipfs/js-ipfs/issues/2864)\n* allow passing a http.Agent to the grpc client ([#3477](https://github.com/ipfs/js-ipfs/issues/3477)) ([c5f0bc5](https://github.com/ipfs/js-ipfs/commit/c5f0bc5eeee15369b7d02901035b04184a8608d2)), closes [#3474](https://github.com/ipfs/js-ipfs/issues/3474)"
  },
  {
    "path": "packages/ipfs-grpc-client/LICENSE",
    "content": "This project is dual licensed under MIT and Apache-2.0.\n\nMIT: https://www.opensource.org/licenses/mit\nApache-2.0: https://www.apache.org/licenses/license-2.0\n"
  },
  {
    "path": "packages/ipfs-grpc-client/LICENSE-APACHE",
    "content": "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\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless 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.\n"
  },
  {
    "path": "packages/ipfs-grpc-client/LICENSE-MIT",
    "content": "The MIT License (MIT)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "packages/ipfs-grpc-client/README.md",
    "content": "> # ⛔️ DEPRECATED: [js-IPFS](https://github.com/ipfs/js-ipfs) has been superseded by [Helia](https://github.com/ipfs/helia)\n>\n> 📚 [Learn more about this deprecation](https://github.com/ipfs/js-ipfs/issues/4336) or [how to migrate](https://github.com/ipfs/helia/wiki/Migrating-from-js-IPFS)\n>\n> ⚠️ If you continue using this repo, please note that security fixes will not be provided\n\n# ipfs-grpc-client <!-- omit in toc -->\n\n[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech)\n[![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech)\n[![codecov](https://img.shields.io/codecov/c/github/ipfs/js-ipfs.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipfs)\n[![CI](https://img.shields.io/github/actions/workflow/status/ipfs/js-ipfs/test.yml?branch=master\\&style=flat-square)](https://github.com/ipfs/js-ipfs/actions/workflows/test.yml?query=branch%3Amaster)\n\n> A client library for the IPFS gRPC API\n\n## Table of contents <!-- omit in toc -->\n\n- [Install](#install)\n  - [Browser `<script>` tag](#browser-script-tag)\n- [Why?](#why)\n- [API](#api)\n  - [`create([options])`](#createoptions)\n  - [Parameters](#parameters)\n  - [Options](#options)\n  - [Returns](#returns)\n  - [Example](#example)\n- [License](#license)\n- [Contribute](#contribute)\n\n## Install\n\n```console\n$ npm i ipfs-grpc-client\n```\n\n### Browser `<script>` tag\n\nLoading this module through a script tag will make it's exports available as `IpfsGrpcClient` in the global namespace.\n\n```html\n<script src=\"https://unpkg.com/ipfs-grpc-client/dist/index.min.js\"></script>\n```\n\nThis module implements part of the [IPFS Core API](https://github.com/ipfs/js-ipfs/tree/master/docs/core-api) using      gRPC over websockets to achieve the bidirectional streaming necessary to have full duplex streams running in the browser.\n\nIt's not recommended you use this directly, instead use the [ipfs-client](https://www.npmjs.com/package/ipfs-client) to combine this with the [ipfs-http-client](https://www.npmjs.com/package/ipfs-http-client) in order to have HTTP fallback for the missing parts of the API.\n\n## Why?\n\nThe [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) and [XHR](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) APIs do not allow for full-duplex streaming, that is, allowing the client to receive bytes from the response while also adding more bytes to the outgoing request.\n\nThis limits what we can do in browsers in terms of the API, for example streaming arbitrarily sized payloads or exposing libp2p duplex streams.\n\ngPRC over websockets has no such limitations so allows us to harness the full power of a remote IPFS node in the browser without the need to work around browser behaviour.\n\n## API\n\n### `create([options])`\n\n### Parameters\n\nNone\n\n### Options\n\nAn optional object which may have the following keys:\n\n| Name  | Type                                                                 | Default     | Description                                                       |\n| ----- | -------------------------------------------------------------------- | ----------- | ----------------------------------------------------------------- |\n| url   | `Multiaddr` or `string` or `URL`                                     | `undefined` | The address of a [ipfs-grpc-server][] to connect to               |\n| agent | [http.Agent](https://nodejs.org/api/http.html#http_class_http_agent) | `undefined` | A http.Agent used to control HTTP client behaviour (node.js only) |\n\n### Returns\n\n| Type     | Description               |\n| -------- | ------------------------- |\n| `object` | An instance of the client |\n\n### Example\n\n```js\nimport { create } from 'ipfs-gprc-client'\n\nconst client = create({\n  url: '/ipv4/127.0.0.1/tcp/1234/ws'\n})\n\nconst id = await client.id()\n```\n\n## License\n\nLicensed under either of\n\n- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / <http://www.apache.org/licenses/LICENSE-2.0>)\n- MIT ([LICENSE-MIT](LICENSE-MIT) / <http://opensource.org/licenses/MIT>)\n\n## Contribute\n\nContributions welcome! Please check out [the issues](https://github.com/ipfs/js-ipfs/issues).\n\nAlso 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.\n\nPlease 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).\n\nUnless 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.\n\n[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md)\n\n[ipfs-grpc-server]: https://www.npmjs.com/package/ipfs-grpc-server\n"
  },
  {
    "path": "packages/ipfs-grpc-client/package.json",
    "content": "{\n  \"name\": \"ipfs-grpc-client\",\n  \"version\": \"0.13.1\",\n  \"description\": \"A client library for the IPFS gRPC API\",\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"homepage\": \"https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-grpc-client#readme\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ipfs/js-ipfs.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ipfs/js-ipfs/issues\"\n  },\n  \"keywords\": [\n    \"ipfs\"\n  ],\n  \"engines\": {\n    \"node\": \">=16.0.0\",\n    \"npm\": \">=7.0.0\"\n  },\n  \"type\": \"module\",\n  \"types\": \"./dist/src/index.d.ts\",\n  \"typesVersions\": {\n    \"*\": {\n      \"*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ],\n      \"src/*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ]\n    }\n  },\n  \"files\": [\n    \"src\",\n    \"dist\",\n    \"!dist/test\",\n    \"!**/*.tsbuildinfo\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/src/index.d.ts\",\n      \"import\": \"./src/index.js\"\n    }\n  },\n  \"eslintConfig\": {\n    \"extends\": \"ipfs\",\n    \"parserOptions\": {\n      \"sourceType\": \"module\"\n    }\n  },\n  \"scripts\": {\n    \"build\": \"aegir build\",\n    \"test\": \"aegir test\",\n    \"test:node\": \"aegir test -t node --cov\",\n    \"test:chrome\": \"aegir test -t browser --cov\",\n    \"test:chrome-webworker\": \"aegir test -t webworker\",\n    \"test:firefox\": \"aegir test -t browser -- --browser firefox\",\n    \"test:firefox-webworker\": \"aegir test -t webworker -- --browser firefox\",\n    \"test:electron-main\": \"aegir test -t electron-main\",\n    \"lint\": \"aegir lint\",\n    \"clean\": \"aegir clean\",\n    \"dep-check\": \"aegir dep-check -i aegir -i ipfs-grpc-protocol -i ipfs-core-types -i protobufjs-cli\"\n  },\n  \"dependencies\": {\n    \"@improbable-eng/grpc-web\": \"^0.15.0\",\n    \"@libp2p/logger\": \"^2.0.5\",\n    \"@libp2p/peer-id\": \"^2.0.0\",\n    \"@multiformats/multiaddr\": \"^11.1.5\",\n    \"change-case\": \"^4.1.1\",\n    \"err-code\": \"^3.0.1\",\n    \"ipfs-core-types\": \"^0.14.1\",\n    \"ipfs-core-utils\": \"^0.18.1\",\n    \"ipfs-grpc-protocol\": \"^0.8.1\",\n    \"ipfs-unixfs\": \"^9.0.0\",\n    \"it-first\": \"^2.0.0\",\n    \"it-pushable\": \"^3.0.0\",\n    \"multiformats\": \"^11.0.0\",\n    \"p-defer\": \"^4.0.0\",\n    \"protobufjs\": \"^7.0.0\",\n    \"uint8arrays\": \"^4.0.2\",\n    \"wherearewe\": \"^2.0.1\",\n    \"ws\": \"^8.5.0\"\n  },\n  \"devDependencies\": {\n    \"aegir\": \"^37.11.0\",\n    \"it-all\": \"^2.0.0\",\n    \"protobufjs-cli\": \"^1.0.0\",\n    \"sinon\": \"^15.0.1\"\n  },\n  \"browser\": {\n    \"./src/grpc/transport.js\": \"./src/grpc/transport.browser.js\",\n    \"./src/grpc/transport.node.js\": \"./src/grpc/transport.browser.js\"\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-client/src/core-api/add-all.js",
    "content": "import { normaliseInput } from 'ipfs-core-utils/files/normalise-input-multiple'\nimport { CID } from 'multiformats/cid'\nimport { bidiToDuplex } from '../utils/bidi-to-duplex.js'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @param {number} index\n * @param {import('it-pushable').Pushable<any>} sink\n * @param {string} path\n * @param {number} [mode]\n * @param {import('ipfs-unixfs').Mtime} [mtime]\n */\nfunction sendDirectory (index, sink, path, mode, mtime) {\n  /**\n   * TODO: type properly after https://github.com/ipfs/js-ipfs/issues/3594\n   *\n   * @type {Record<string, any>}\n   */\n  const message = {\n    index,\n    type: 'DIRECTORY',\n    path\n  }\n\n  if (mtime) {\n    message.mtime = mtime.secs\n    message.mtimeNsecs = mtime.nsecs\n  }\n\n  if (mode != null) {\n    message.mode = mode\n  }\n\n  sink.push(message)\n}\n\n/**\n * @param {number} index\n * @param {import('it-pushable').Pushable<any>} sink\n * @param {AsyncIterable<Uint8Array> | Iterable<Uint8Array> | Uint8Array} content\n * @param {string} [path]\n * @param {number} [mode]\n * @param {import('ipfs-unixfs').Mtime} [mtime]\n */\nasync function sendFile (index, sink, content, path, mode, mtime) {\n  if (content instanceof Uint8Array) {\n    content = [content]\n  }\n\n  for await (const buf of content) {\n    /**\n     * TODO: type properly after https://github.com/ipfs/js-ipfs/issues/3594\n     *\n     * @type {Record<string, any>}\n     */\n    const message = {\n      index,\n      type: 'FILE',\n      path\n    }\n\n    if (mtime) {\n      message.mtime = mtime.secs\n      message.mtimeNsecs = mtime.nsecs\n    }\n\n    if (mode != null) {\n      message.mode = mode\n    }\n\n    message.content = new Uint8Array(buf, buf.byteOffset, buf.byteLength)\n\n    sink.push(message)\n  }\n\n  // signal that the file data has finished\n  const message = {\n    index,\n    type: 'FILE',\n    path\n  }\n\n  sink.push(message)\n}\n\n/**\n * @param {import('ipfs-core-types/src/utils').ImportCandidateStream} stream\n * @param {import('it-pushable').Pushable<any>} sink\n */\nasync function sendFiles (stream, sink) {\n  let i = 1\n\n  for await (const { path, content, mode, mtime } of normaliseInput(stream)) {\n    const index = i\n    i++\n\n    if (content) {\n      await sendFile(index, sink, content, path, mode, mtime)\n    } else if (path) {\n      sendDirectory(index, sink, path, mode, mtime)\n    } else {\n      throw new Error('Must pass path or content or both')\n    }\n  }\n}\n\n/**\n * @param {import('@improbable-eng/grpc-web').grpc} grpc\n * @param {*} service\n * @param {import('../types').Options} opts\n */\nexport function grpcAddAll (grpc, service, opts) {\n  /**\n   * @type {import('ipfs-core-types/src/root').API<{}>[\"addAll\"]}\n   */\n  async function * addAll (stream, options = {}) {\n    const {\n      source,\n      sink\n    } = bidiToDuplex(grpc, service, {\n      host: opts.url,\n      debug: Boolean(process.env.DEBUG),\n      metadata: options,\n      agent: opts.agent\n    })\n\n    sendFiles(stream, sink)\n      .catch(err => {\n        sink.end(err)\n      })\n      .finally(() => {\n        sink.end()\n      })\n\n    for await (const result of source) {\n      // received progress result\n      if (result.type === 'PROGRESS') {\n        if (options.progress) {\n          options.progress(result.bytes, result.path)\n        }\n\n        continue\n      }\n\n      // received file/dir import result\n      yield {\n        path: result.path,\n        cid: CID.parse(result.cid),\n        mode: result.mode,\n        mtime: {\n          secs: result.mtime || 0,\n          nsecs: result.mtimeNsecs || 0\n        },\n        size: result.size\n      }\n    }\n  }\n\n  return withTimeoutOption(addAll)\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-client/src/core-api/files/ls.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { serverStreamToIterator } from '../../utils/server-stream-to-iterator.js'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\n\n/**\n * @param {import('@improbable-eng/grpc-web').grpc} grpc\n * @param {*} service\n * @param {import('../../types').Options} opts\n */\nexport function grpcMfsLs (grpc, service, opts) {\n  /**\n   * @type {import('ipfs-core-types/src/files').API<{}>[\"ls\"]}\n   */\n  async function * mfsLs (path, options = {}) {\n    const request = {\n      path\n    }\n\n    for await (const result of serverStreamToIterator(grpc, service, request, {\n      host: opts.url,\n      debug: Boolean(process.env.DEBUG),\n      metadata: options,\n      agent: opts.agent\n    })) {\n      yield {\n        name: result.name,\n        type: result.type.toLowerCase(),\n        size: result.size,\n        cid: CID.parse(result.cid),\n        mode: result.mode,\n        mtime: {\n          secs: result.mtime || 0,\n          nsecs: result.mtimeNsecs || 0\n        }\n      }\n    }\n  }\n\n  return withTimeoutOption(mfsLs)\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-client/src/core-api/files/write.js",
    "content": "import { clientStreamToPromise } from '../../utils/client-stream-to-promise.js'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport { normaliseContent } from 'ipfs-core-utils/files/normalise-content'\nimport {\n  parseMtime,\n  parseMode\n} from 'ipfs-unixfs'\n\n/**\n * @param {string} path\n * @param {*} content\n */\nasync function * stream (path, content) {\n  for await (const buf of await normaliseContent(content)) {\n    yield { path, content: buf }\n  }\n}\n\n/**\n * @param {import('@improbable-eng/grpc-web').grpc} grpc\n * @param {*} service\n * @param {import('../../types').Options} opts\n */\nexport function grpcMfsWrite (grpc, service, opts) {\n  /**\n   * @type {import('ipfs-core-types/src/files').API<{}>[\"write\"]}\n   */\n  async function mfsWrite (path, content, options = {}) {\n    /**\n     * TODO: fix after https://github.com/ipfs/js-ipfs/issues/3594\n     *\n     * @type {Record<string, any>}\n     */\n    const metadata = {\n      ...options\n    }\n    const mtime = parseMtime(options.mtime)\n\n    if (mtime != null) {\n      metadata.mtime = mtime.secs\n      metadata.mtimeNsecs = mtime.nsecs\n    }\n\n    const mode = parseMode(options.mode)\n\n    if (mode != null) {\n      metadata.mode = mode\n    }\n\n    await clientStreamToPromise(grpc, service, stream(path, content), {\n      host: opts.url,\n      debug: Boolean(process.env.DEBUG),\n      metadata,\n      agent: opts.agent\n    })\n  }\n\n  return withTimeoutOption(mfsWrite)\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-client/src/core-api/id.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport { toHeaders } from '../utils/to-headers.js'\nimport { unaryToPromise } from '../utils/unary-to-promise.js'\nimport { multiaddr } from '@multiformats/multiaddr'\n\n/**\n * @param {import('@improbable-eng/grpc-web').grpc} grpc\n * @param {*} service\n * @param {import('../types').Options} opts\n */\nexport function grpcId (grpc, service, opts) {\n  /**\n   * @type {import('ipfs-core-types/src/root').API<{}>[\"id\"]}\n   */\n  async function id (options = {}) {\n    const request = {}\n\n    const res = await unaryToPromise(grpc, service, request, {\n      host: opts.url,\n      metadata: toHeaders(options),\n      agent: opts.agent\n    })\n\n    return {\n      ...res,\n      addresses: (res.addresses || []).map((/** @type {string} */ str) => multiaddr(str))\n    }\n  }\n\n  return withTimeoutOption(id)\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-client/src/core-api/pubsub/subscribe.js",
    "content": "import { serverStreamToIterator } from '../../utils/server-stream-to-iterator.js'\nimport { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport { subscriptions } from './subscriptions.js'\nimport defer from 'p-defer'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\nimport { peerIdFromString } from '@libp2p/peer-id'\n\n/**\n * @param {import('@improbable-eng/grpc-web').grpc} grpc\n * @param {*} service\n * @param {import('../../types').Options} opts\n */\nexport function grpcPubsubSubscribe (grpc, service, opts) {\n  /**\n   * @type {import('ipfs-core-types/src/pubsub').API<{}>[\"subscribe\"]}\n   */\n  async function pubsubSubscribe (topic, handler, options = {}) {\n    const request = {\n      topic\n    }\n\n    const deferred = defer()\n\n    Promise.resolve().then(async () => {\n      try {\n        for await (const result of serverStreamToIterator(grpc, service, request, {\n          host: opts.url,\n          debug: Boolean(process.env.DEBUG),\n          metadata: options,\n          agent: opts.agent\n        })) {\n          if (result.handler) {\n            const subs = subscriptions.get(topic) || new Map()\n            subs.set(result.handler, handler)\n            subscriptions.set(topic, subs)\n\n            deferred.resolve()\n          } else {\n            /** @type {import('@libp2p/interface-pubsub').Message} */\n            let msg\n\n            if (result.type === 'signed') {\n              msg = {\n                type: 'signed',\n                from: peerIdFromString(result.from),\n                sequenceNumber: BigInt(`0x${uint8ArrayToString(result.sequenceNumber, 'base16')}`),\n                data: result.data,\n                topic: result.topic,\n                key: result.key,\n                signature: result.signature\n              }\n            } else {\n              msg = {\n                type: 'unsigned',\n                data: result.data,\n                topic: result.topic\n              }\n            }\n\n            if (typeof handler === 'function') {\n              handler(msg)\n              continue\n            }\n\n            if (handler != null && typeof handler.handleEvent === 'function') {\n              handler.handleEvent(msg)\n            }\n          }\n        }\n      } catch (/** @type {any} */ err) {\n        if (options && options.onError) {\n          options.onError(err)\n        }\n      }\n    })\n\n    await deferred.promise\n  }\n\n  return withTimeoutOption(pubsubSubscribe)\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-client/src/core-api/pubsub/subscriptions.js",
    "content": "\n/**\n * @typedef {import('@libp2p/interface-pubsub').Message} Message\n * @typedef {import('@libp2p/interfaces/events').EventHandler<Message>} Subscription\n */\n\n/** @type {Map<string, Map<string, Subscription>>} */\nexport const subscriptions = new Map()\n"
  },
  {
    "path": "packages/ipfs-grpc-client/src/core-api/pubsub/unsubscribe.js",
    "content": "import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'\nimport { toHeaders } from '../../utils/to-headers.js'\nimport { unaryToPromise } from '../../utils/unary-to-promise.js'\nimport { subscriptions } from './subscriptions.js'\n\n/**\n * @param {import('@improbable-eng/grpc-web').grpc} grpc\n * @param {*} service\n * @param {import('../../types').Options} opts\n */\nexport function grpcPubsubUnsubscribe (grpc, service, opts) {\n  /**\n   * @type {import('ipfs-core-types/src/pubsub').API<{}>[\"unsubscribe\"]}\n   */\n  async function pubsubUnsubscribe (topic, handler, options = {}) {\n    const handlers = []\n    const subs = subscriptions.get(topic)\n\n    if (!subs) {\n      return\n    }\n\n    if (handler) {\n      for (const [key, value] of subs.entries()) {\n        if (value === handler) {\n          handlers.push(key)\n        }\n      }\n    } else {\n\n    }\n\n    const request = {\n      topic,\n      handlers\n    }\n\n    await unaryToPromise(grpc, service, request, {\n      host: opts.url,\n      metadata: toHeaders(options),\n      agent: opts.agent\n    })\n\n    for (const handlerId of handlers) {\n      subs.delete(handlerId)\n    }\n\n    if (!subs.size) {\n      subscriptions.delete(topic)\n    }\n  }\n\n  return withTimeoutOption(pubsubUnsubscribe)\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-client/src/grpc/transport.browser.js",
    "content": "import grpcWeb from '@improbable-eng/grpc-web'\n\nexport const transport = () => grpcWeb.grpc.WebsocketTransport\n"
  },
  {
    "path": "packages/ipfs-grpc-client/src/grpc/transport.js",
    "content": "import { isElectronRenderer, isBrowser } from 'wherearewe'\nimport { transport as nodeTransport } from './transport.node.js'\nimport { transport as browserTransport } from './transport.browser.js'\n\nexport function transport () {\n  // In electron-renderer we use the browser transport\n  if (isElectronRenderer || isBrowser) {\n    return browserTransport()\n  } else {\n    return nodeTransport()\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-client/src/grpc/transport.node.js",
    "content": "// copied from https://github.com/improbable-eng/grpc-web/blob/master/client/grpc-web/src/transports/websocket/websocket.ts\n// but uses the ws implementation of WebSockets\n// see: https://github.com/improbable-eng/grpc-web/issues/796\nimport WebSocket from 'ws'\nimport { logger } from '@libp2p/logger'\n\nconst log = logger('ipfs:grpc-client:websocket-transport')\n\n/**\n * @typedef {import('http').Agent} HttpAgent\n * @typedef {import('https').Agent} HttpsAgent\n */\n\nconst WebsocketSignal = {\n  FINISH_SEND: 1\n}\n\nconst finishSendFrame = new Uint8Array([1])\n\n/**\n * @param {object} options\n * @param {HttpAgent|HttpsAgent} [options.agent] - http.Agent used to control HTTP client behaviour\n */\nfunction WebsocketTransport (options) {\n  /**\n   * @param {import('@improbable-eng/grpc-web').grpc.TransportOptions} opts\n   */\n  const websocketTransportFactory = (opts) => {\n    return websocketRequest({\n      ...options,\n      ...opts\n    })\n  }\n\n  return websocketTransportFactory\n}\n\n/**\n * @typedef {object} NodeTransportOptions\n * @property {HttpAgent|HttpsAgent} [options.agent]\n *\n * @typedef {NodeTransportOptions & import('@improbable-eng/grpc-web').grpc.TransportOptions} WebSocketTransportOptions\n *\n * @param {WebSocketTransportOptions} options\n */\nfunction websocketRequest (options) {\n  const webSocketAddress = constructWebSocketAddress(options.url)\n\n  /** @type {Array<number | Uint8Array>} */\n  let sendQueue = []\n  /** @type {WebSocket} */\n  let ws\n\n  /**\n   * @param {number | Uint8Array} toSend\n   */\n  function sendToWebsocket (toSend) {\n    if (toSend === WebsocketSignal.FINISH_SEND) {\n      ws.send(finishSendFrame)\n    } else if (toSend instanceof Uint8Array) {\n      const byteArray = toSend\n      const c = new Int8Array(byteArray.byteLength + 1)\n      c.set(new Uint8Array([0]))\n      c.set(byteArray, 1)\n\n      ws.send(c)\n    }\n  }\n\n  return {\n    /**\n     * @param {Uint8Array} msgBytes\n     */\n    sendMessage: (msgBytes) => {\n      if (!ws || ws.readyState === ws.CONNECTING) {\n        sendQueue.push(msgBytes)\n      } else {\n        sendToWebsocket(msgBytes)\n      }\n    },\n    finishSend: () => {\n      if (!ws || ws.readyState === ws.CONNECTING) {\n        sendQueue.push(WebsocketSignal.FINISH_SEND)\n      } else {\n        sendToWebsocket(WebsocketSignal.FINISH_SEND)\n      }\n    },\n    /**\n     * @param {import('@improbable-eng/grpc-web').grpc.Metadata} metadata\n     */\n    start: (metadata) => {\n      ws = new WebSocket(webSocketAddress, ['grpc-websockets'], options)\n      ws.binaryType = 'arraybuffer'\n      ws.onopen = function () {\n        options.debug && log('websocketRequest.onopen')\n        ws.send(headersToBytes(metadata))\n\n        // send any messages that were passed to sendMessage before the connection was ready\n        sendQueue.forEach(toSend => {\n          sendToWebsocket(toSend)\n        })\n        sendQueue = []\n      }\n\n      ws.onclose = function (closeEvent) {\n        options.onEnd()\n      }\n\n      ws.onerror = function (error) {\n        options.debug && log('websocketRequest.onerror', error)\n      }\n\n      ws.onmessage = function (e) {\n        if (e.data instanceof ArrayBuffer) {\n          options.onChunk(new Uint8Array(e.data, 0, e.data.byteLength))\n        } else {\n          options.onEnd(new Error(`Incorrect message type received - expected ArrayBuffer, got ${typeof e.data}`))\n          ws.close()\n        }\n      }\n    },\n    cancel: () => {\n      ws.close()\n    }\n  }\n}\n\n/**\n * @param {string} url\n */\nfunction constructWebSocketAddress (url) {\n  if (url.startsWith('wss://') || url.startsWith('ws://')) {\n    return url\n  } else if (url.substr(0, 8) === 'https://') {\n    return `wss://${url.substr(8)}`\n  } else if (url.substr(0, 7) === 'http://') {\n    return `ws://${url.substr(7)}`\n  }\n\n  throw new Error('Websocket transport url must start with ws:// or wss:// or http:// or https://')\n}\n\n/**\n * TODO: type properly after https://github.com/ipfs/js-ipfs/issues/3594\n *\n * @param {import('@improbable-eng/grpc-web').grpc.Metadata} headers\n */\nfunction headersToBytes (headers) {\n  let asString = ''\n  headers.forEach((key, values) => {\n    asString += `${key}: ${values.join(', ')}\\r\\n`\n  })\n  return encodeASCII(asString)\n}\n\n/**\n * @param {string} input\n */\nfunction encodeASCII (input) {\n  const encoded = new Uint8Array(input.length)\n  for (let i = 0; i !== input.length; ++i) {\n    const charCode = input.charCodeAt(i)\n    if (!isValidHeaderAscii(charCode)) {\n      throw new Error('Metadata contains invalid ASCII')\n    }\n    encoded[i] = charCode\n  }\n  return encoded\n}\n\n/**\n * @param {number} char\n */\nconst isAllowedControlChars = (char) => char === 0x9 || char === 0xa || char === 0xd\n\n/**\n * @param {number} val\n */\nfunction isValidHeaderAscii (val) {\n  return isAllowedControlChars(val) || (val >= 0x20 && val <= 0x7e)\n}\n\nexport const transport = () => WebsocketTransport\n"
  },
  {
    "path": "packages/ipfs-grpc-client/src/index.js",
    "content": "import { toUrlString } from 'ipfs-core-utils/to-url-string'\nimport { loadServices } from './utils/load-services.js'\nimport grpcWeb from '@improbable-eng/grpc-web'\nimport { grpcAddAll } from './core-api/add-all.js'\nimport { grpcId } from './core-api/id.js'\nimport { grpcMfsLs } from './core-api/files/ls.js'\nimport { grpcMfsWrite } from './core-api/files/write.js'\nimport { grpcPubsubSubscribe } from './core-api/pubsub/subscribe.js'\nimport { grpcPubsubUnsubscribe } from './core-api/pubsub/unsubscribe.js'\n\n/**\n * @typedef {import('./types').Options} Options\n */\n\nconst service = loadServices()\n\n/** @type {Record<string, string>} */\nconst protocols = {\n  'ws://': 'http://',\n  'wss://': 'https://'\n}\n\n/**\n * @param {{ url: string }} opts\n */\nfunction normaliseUrls (opts) {\n  Object.keys(protocols).forEach(protocol => {\n    if (opts.url.startsWith(protocol)) {\n      opts.url = protocols[protocol] + opts.url.substring(protocol.length)\n    }\n  })\n}\n\n/**\n * @param {Options} [opts]\n */\nexport function create (opts = { url: '' }) {\n  const options = {\n    ...opts,\n    url: toUrlString(opts.url)\n  }\n\n  // @improbable-eng/grpc-web requires http:// protocol URLs, not ws://\n  normaliseUrls(options)\n\n  const client = {\n    addAll: grpcAddAll(grpcWeb.grpc, service.Root.add, options),\n    id: grpcId(grpcWeb.grpc, service.Root.id, options),\n    files: {\n      ls: grpcMfsLs(grpcWeb.grpc, service.MFS.ls, options),\n      write: grpcMfsWrite(grpcWeb.grpc, service.MFS.write, options)\n    },\n    pubsub: {\n      subscribe: grpcPubsubSubscribe(grpcWeb.grpc, service.PubSub.subscribe, options),\n      unsubscribe: grpcPubsubUnsubscribe(grpcWeb.grpc, service.PubSub.unsubscribe, options)\n    }\n  }\n\n  return client\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-client/src/types.ts",
    "content": "import type { Agent as HttpAgent } from 'http'\nimport type { Agent as HttpsAgent } from 'https'\nimport type { Multiaddr } from '@multiformats/multiaddr'\n\nexport interface Options {\n  url: string | URL | Multiaddr\n  agent?: HttpAgent | HttpsAgent\n}\n\nexport interface RPCOptions<Metadata> {\n  host: string | URL | Multiaddr\n  debug?: boolean\n  metadata: Metadata\n  agent?: HttpAgent | HttpsAgent\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-client/src/utils/bidi-to-duplex.js",
    "content": "import { pushable } from 'it-pushable'\nimport errCode from 'err-code'\nimport { toUrlString } from 'ipfs-core-utils/to-url-string'\nimport { toHeaders } from './to-headers.js'\nimport { transport } from '../grpc/transport.js'\n\n/**\n * @param {*} service\n * @param {import('@improbable-eng/grpc-web').grpc.Client<any, any>} client\n * @param {AsyncIterable<any>} source\n */\nasync function sendMessages (service, client, source) {\n  for await (const obj of source) {\n    client.send({\n      serializeBinary: () => service.requestType.serializeBinary(obj)\n    })\n  }\n}\n\n/**\n * Bidirectional streams are many-to-many operations so returns a sink\n * for the caller to write client messages into and a source to read\n * server messages from.\n *\n * @param {import('@improbable-eng/grpc-web').grpc} grpc - an @improbable-eng/grpc-web instance\n * @param {*} service - an @improbable-eng/grpc-web service\n * @param {import('../types').RPCOptions<any>} options\n * @returns {{ source: AsyncIterable<any>, sink: import('it-pushable').Pushable<any> }}\n **/\nexport function bidiToDuplex (grpc, service, options) {\n  const source = pushable({ objectMode: true })\n  const sink = pushable({ objectMode: true })\n\n  const client = grpc.client(service, {\n    ...options,\n    host: toUrlString(options.host),\n    transport: transport()({\n      agent: options.agent\n    })\n  })\n  client.onMessage(message => {\n    sink.push(message)\n  })\n  client.onEnd((status, message, trailers) => {\n    let err\n\n    if (status) {\n      const error = new Error(message)\n\n      err = errCode(error, trailers.get('grpc-code')[0], {\n        status\n      })\n\n      let stack = trailers.get('grpc-stack')[0]\n      stack = stack && stack.replace(/\\\\n/g, '\\n')\n\n      err.stack = stack || error.stack\n    }\n\n    sink.end(err)\n  })\n\n  sendMessages(service, client, source)\n    .catch(err => {\n      sink.end(err)\n    })\n    .finally(() => {\n      client.finishSend()\n    })\n\n  client.start(toHeaders(options.metadata))\n\n  return {\n    sink: source,\n    source: sink\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-client/src/utils/client-stream-to-promise.js",
    "content": "import first from 'it-first'\nimport { bidiToDuplex } from './bidi-to-duplex.js'\n\n/**\n * @typedef {import('http').Agent} HttpAgent\n * @typedef {import('https').Agent} HttpsAgent\n */\n\n/**\n * Client streaming methods are a many-to-one operation so this\n * function takes a source that can emit multiple messages and\n * returns a promise that resolves to the server response.\n *\n * @param {import('@improbable-eng/grpc-web').grpc} grpc - an @improbable-eng/grpc-web instance\n * @param {*} service - an @improbable-eng/grpc-web service\n * @param {AsyncIterable<any>} source - a source of objects to send\n * @param {import('../types').RPCOptions<any>} options\n * @returns {Promise<any>} - A promise that resolves to a response object\n */\nexport async function clientStreamToPromise (grpc, service, source, options) {\n  const {\n    source: serverSource, sink\n  } = bidiToDuplex(grpc, service, options)\n\n  for await (const obj of source) {\n    sink.push(obj)\n  }\n\n  sink.end()\n\n  return first(serverSource)\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-client/src/utils/load-services.js",
    "content": "\nimport protocol from 'ipfs-grpc-protocol'\nimport protobuf from 'protobufjs/light.js'\n\nconst { Service } = protobuf\n\nconst CONVERSION_OPTS = {\n  keepCase: false,\n  longs: String, // long.js is required\n  enums: String,\n  defaults: false,\n  oneofs: true\n}\n\n/**\n * Converts protobufjs service definitions into the format expected\n * by @improbable-eng/grpc-web.  This is to let us use the same\n * service definition on both the server and the client.\n */\nexport function loadServices () {\n  // @ts-expect-error - recent protobufjs release changed the types\n  const root = protobuf.Root.fromJSON(protocol)\n  /** @type {Record<string, any>} */\n  const output = {}\n\n  Object\n    // @ts-expect-error\n    .keys(root.nested.ipfs)\n    // @ts-expect-error\n    .filter(key => root.nested.ipfs[key] instanceof Service)\n    // @ts-expect-error\n    .map(key => root.nested.ipfs[key])\n    .forEach(service => {\n      /** @type {Record<string, any>} */\n      const serviceDef = {}\n\n      output[service.name] = serviceDef\n\n      Object.keys(service.methods)\n        .forEach(methodName => {\n          const method = service.methods[methodName].resolve()\n\n          serviceDef[methodName] = {\n            service: {\n              serviceName: `ipfs.${service.name}`\n            },\n            methodName,\n            requestStream: method.requestStream,\n            responseStream: method.responseStream,\n            requestType: {\n              /**\n               * @param {any} obj\n               */\n              serializeBinary: (obj) => {\n                const message = method.resolvedRequestType.fromObject(obj)\n                return method.resolvedRequestType.encode(message).finish()\n              },\n              /**\n               * @param {Uint8Array} buf\n               */\n              deserializeBinary: (buf) => {\n                const message = method.resolvedRequestType.decode(buf)\n                const obj = method.resolvedRequestType.toObject(message, CONVERSION_OPTS)\n\n                Object.defineProperty(obj, 'toObject', {\n                  enumerable: false,\n                  configurable: false,\n                  value: () => obj\n                })\n\n                return obj\n              }\n            },\n            responseType: {\n              /**\n               * @param {any} obj\n               */\n              serializeBinary: (obj) => {\n                const message = method.resolvedResponseType.fromObject(obj)\n                return method.resolvedResponseType.encode(message).finish()\n              },\n              /**\n               * @param {Uint8Array} buf\n               */\n              deserializeBinary: (buf) => {\n                const message = method.resolvedResponseType.decode(buf)\n                const obj = method.resolvedResponseType.toObject(message, CONVERSION_OPTS)\n\n                Object.defineProperty(obj, 'toObject', {\n                  enumerable: false,\n                  configurable: false,\n                  value: () => obj\n                })\n\n                return obj\n              }\n            }\n          }\n        })\n    })\n\n  return output\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-client/src/utils/server-stream-to-iterator.js",
    "content": "import { bidiToDuplex } from './bidi-to-duplex.js'\n\n/**\n * @typedef {import('http').Agent} HttpAgent\n * @typedef {import('https').Agent} HttpsAgent\n */\n\n/**\n * Server stream methods are one-to-many operations so this\n * function accepts a client message and returns a source\n * from which multiple server messages can be read.\n *\n * @param {import('@improbable-eng/grpc-web').grpc} grpc - an @improbable-eng/grpc-web instance\n * @param {*} service - an @improbable-eng/grpc-web service\n * @param {object} request - a request object\n * @param {import('../types').RPCOptions<any>} options - RPC options\n * @returns {AsyncIterable<any>}\n **/\nexport function serverStreamToIterator (grpc, service, request, options) {\n  const {\n    source, sink\n  } = bidiToDuplex(grpc, service, options)\n\n  sink.push(request)\n\n  return source\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-client/src/utils/to-headers.js",
    "content": "import { paramCase } from 'change-case'\n\n/**\n * @param {Record<string, any>} [object] - key/value pairs to turn into HTTP headers\n */\nexport function toHeaders (object = {}) {\n  /** @type {Record<string, string>} */\n  const output = {}\n\n  Object.keys(object || {}).forEach(key => {\n    if (typeof object[key] === 'function') {\n      return\n    }\n\n    output[paramCase(key)] = object[key].toString()\n  })\n\n  return output\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-client/src/utils/unary-to-promise.js",
    "content": "import first from 'it-first'\nimport { bidiToDuplex } from './bidi-to-duplex.js'\n\n/**\n * @typedef {import('http').Agent} HttpAgent\n * @typedef {import('https').Agent} HttpsAgent\n */\n\n/**\n * Unary calls are one-to-one operations so this function\n * takes a client message and returns a promise that resolves\n * to the server response.\n *\n * @param {import('@improbable-eng/grpc-web').grpc} grpc - an @improbable-eng/grpc-web instance\n * @param {*} service - an @improbable-eng/grpc-web service\n * @param {any} request - a request object\n * @param {import('../types').RPCOptions<any>} options - RPC options\n * @returns {Promise<any>} - A promise that resolves to a response object\n **/\nexport function unaryToPromise (grpc, service, request, options) {\n  const {\n    source, sink\n  } = bidiToDuplex(grpc, service, options)\n\n  sink.push(request)\n\n  return first(source)\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-client/test/agent.js",
    "content": "/* eslint-env mocha */\n\nimport { create } from '../src/index.js'\nimport { WebSocketServer } from 'ws'\n\nfunction startServer () {\n  return new Promise((resolve) => {\n    const wss = new WebSocketServer({ port: 0 })\n\n    wss.on('listening', () => {\n      resolve({\n        port: wss.address().port,\n        close: () => wss.close()\n      })\n    })\n\n    wss.on('connection', (ws) => {\n      ws.once('message', () => {\n        ws.send('')\n        ws.end()\n      })\n    })\n  })\n}\n\ndescribe('agent', function () {\n  it('uses the passed agent', async () => {\n    const server = await startServer()\n\n    try {\n      await new Promise((resolve) => {\n        const ipfs = create({\n          url: `http://localhost:${server.port}`,\n          agent: {\n            addRequest () {\n              // an agent method was invoked\n              resolve()\n            }\n          }\n        })\n\n        ipfs.id().catch(() => {})\n      })\n    } finally {\n      server.close()\n    }\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-grpc-client/test/node.js",
    "content": "\nimport './agent.js'\n"
  },
  {
    "path": "packages/ipfs-grpc-client/test/utils.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport all from 'it-all'\nimport sinon from 'sinon'\nimport { bidiToDuplex } from '../src/utils/bidi-to-duplex.js'\nimport { toHeaders } from '../src/utils/to-headers.js'\n\ndescribe('utils', () => {\n  describe('bidi-to-duplex', () => {\n    it('should transform a bidirectional client into an async iterable', async () => {\n      const service = 'service'\n      const options = {\n        host: '',\n        metadata: {\n          foo: 'bar'\n        }\n      }\n\n      const client = {\n        onMessage: sinon.stub(),\n        onEnd: sinon.stub(),\n        start: sinon.stub()\n      }\n\n      const grpc = {\n        client: sinon.stub().withArgs(service, options).returns(client)\n      }\n\n      const {\n        source\n      } = bidiToDuplex(grpc, service, options)\n\n      expect(client.start.calledWith(toHeaders(options.metadata))).to.be.true()\n\n      client.onMessage.getCall(0).args[0]('hello')\n      client.onMessage.getCall(0).args[0]('world')\n      client.onEnd.getCall(0).args[0]()\n\n      await expect(all(source)).to.eventually.deep.equal(['hello', 'world'])\n    })\n\n    it('should propagate client errors', async () => {\n      const service = 'service'\n      const options = {\n        host: '',\n        metadata: {\n          foo: 'bar'\n        }\n      }\n\n      const client = {\n        onMessage: sinon.stub(),\n        onEnd: sinon.stub(),\n        start: sinon.stub()\n      }\n\n      const grpc = {\n        client: sinon.stub().withArgs(service, options).returns(client)\n      }\n\n      const {\n        source\n      } = bidiToDuplex(grpc, service, options)\n\n      expect(client.start.calledWith(toHeaders(options.metadata))).to.be.true()\n\n      client.onEnd.getCall(0).args[0](1, 'Erp!', { get: () => [] })\n\n      await expect(all(source)).to.eventually.be.rejectedWith(/Erp!/)\n    })\n  })\n\n  describe('to-headers', () => {\n    it('should rename property fields', () => {\n      const input = {\n        propSimple: 'foo'\n      }\n\n      const output = toHeaders(input)\n\n      expect(output.propSimple).to.be.undefined()\n      expect(output['prop-simple']).to.deep.equal(input.propSimple)\n    })\n\n    it('should remove function fields', () => {\n      const input = {\n        funcProp: () => {}\n      }\n\n      const output = toHeaders(input)\n\n      expect(output.funcProp).to.be.undefined()\n      expect(output.funcProp).to.be.undefined()\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-grpc-client/tsconfig.json",
    "content": "{\n  \"extends\": \"aegir/src/config/tsconfig.aegir.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"emitDeclarationOnly\": true\n  },\n  \"include\": [\n    \"src\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../ipfs-core-types\"\n    },\n    {\n      \"path\": \"../ipfs-core-utils\"\n    },\n    {\n      \"path\": \"../ipfs-grpc-protocol\"\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-protocol/CHANGELOG.md",
    "content": "# Change Log\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n### [0.8.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-grpc-protocol-v0.8.0...ipfs-grpc-protocol-v0.8.1) (2023-05-25)\n\n\n### Bug Fixes\n\n* add deprecation notice to readmes ([#4362](https://www.github.com/ipfs/js-ipfs/issues/4362)) ([7b79c1b](https://www.github.com/ipfs/js-ipfs/commit/7b79c1b8df5c818dc124b346ea28330455732d5c))\n\n## [0.8.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-grpc-protocol-v0.7.0...ipfs-grpc-protocol-v0.8.0) (2023-01-11)\n\n\n### ⚠ BREAKING CHANGES\n\n* update multiformats to v11.x.x and related depenendcies (#4277)\n\n### Bug Fixes\n\n* update multiformats to v11.x.x and related depenendcies ([#4277](https://www.github.com/ipfs/js-ipfs/issues/4277)) ([521c84a](https://www.github.com/ipfs/js-ipfs/commit/521c84a958b04d61702577a5adce28519c1b2a3b))\n* use aegir to publish RCs ([#4284](https://www.github.com/ipfs/js-ipfs/issues/4284)) ([6d90cbf](https://www.github.com/ipfs/js-ipfs/commit/6d90cbf321a7dbf4b1084ba20f0c514dc08d8d0a))\n\n## [0.7.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-grpc-protocol-v0.6.0...ipfs-grpc-protocol-v0.7.0) (2022-09-06)\n\n\n### ⚠ BREAKING CHANGES\n\n* update to libp2p@0.38.x (#4151)\n\n### deps\n\n* update to libp2p@0.38.x ([#4151](https://www.github.com/ipfs/js-ipfs/issues/4151)) ([39dbf70](https://www.github.com/ipfs/js-ipfs/commit/39dbf708ec31b263115e44f420651fa4e056a89e))\n\n## [0.6.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-grpc-protocol-v0.5.5...ipfs-grpc-protocol-v0.6.0) (2022-05-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* This module is now ESM only and there return types of some methods have changed\n\n### Features\n\n* update to libp2p 0.37.x ([#4092](https://www.github.com/ipfs/js-ipfs/issues/4092)) ([74aee8b](https://www.github.com/ipfs/js-ipfs/commit/74aee8b3d78f233c3199a3e9a6c0ac628a31a433))\n\n### [0.5.5](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-protocol@0.5.4...ipfs-grpc-protocol@0.5.5) (2021-12-15)\n\n**Note:** Version bump only for package ipfs-grpc-protocol\n\n\n\n\n\n### [0.5.4](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-protocol@0.5.3...ipfs-grpc-protocol@0.5.4) (2021-11-24)\n\n**Note:** Version bump only for package ipfs-grpc-protocol\n\n\n\n\n\n### [0.5.3](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-protocol@0.5.2...ipfs-grpc-protocol@0.5.3) (2021-11-19)\n\n**Note:** Version bump only for package ipfs-grpc-protocol\n\n\n\n\n\n### [0.5.2](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-protocol@0.5.1...ipfs-grpc-protocol@0.5.2) (2021-11-12)\n\n**Note:** Version bump only for package ipfs-grpc-protocol\n\n\n\n\n\n### [0.5.1](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-protocol@0.5.0...ipfs-grpc-protocol@0.5.1) (2021-09-28)\n\n**Note:** Version bump only for package ipfs-grpc-protocol\n\n\n\n\n\n## [0.5.0](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-protocol@0.4.1...ipfs-grpc-protocol@0.5.0) (2021-09-24)\n\n\n### Features\n\n* switch to esm ([#3879](https://github.com/ipfs/js-ipfs/issues/3879)) ([9a40109](https://github.com/ipfs/js-ipfs/commit/9a40109632e5b4837eb77a2f57dbc77fbf1fe099))\n\n\n### BREAKING CHANGES\n\n* There are no default exports and everything is now dual published as ESM/CJS\n\n\n\n\n\n### [0.4.1](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-protocol@0.4.0...ipfs-grpc-protocol@0.4.1) (2021-09-17)\n\n**Note:** Version bump only for package ipfs-grpc-protocol\n\n\n\n\n\n## [0.4.0](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-protocol@0.3.0...ipfs-grpc-protocol@0.4.0) (2021-08-17)\n\n\n### Features\n\n* pubsub over gRPC ([#3813](https://github.com/ipfs/js-ipfs/issues/3813)) ([e7d5509](https://github.com/ipfs/js-ipfs/commit/e7d5509c87e87aed6be3c1d0b2a01ab74cdc1ed9)), closes [#3741](https://github.com/ipfs/js-ipfs/issues/3741)\n\n\n\n\n\n## [0.3.0](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-protocol@0.2.0...ipfs-grpc-protocol@0.3.0) (2021-05-10)\n\n\n### chore\n\n* upgrade deps with new typedefs ([#3550](https://github.com/ipfs/js-ipfs/issues/3550)) ([a418a52](https://github.com/ipfs/js-ipfs/commit/a418a521574c878d7aabd0ad2fd8d516908a3756))\n\n\n### BREAKING CHANGES\n\n* all core api methods now have types, some method signatures have changed, named exports are now used by the http, grpc and ipfs client modules\n\n\n\n\n\n## [0.2.0](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-protocol@0.1.0...ipfs-grpc-protocol@0.2.0) (2021-02-01)\n\n\n### chore\n\n* update deps ([#3514](https://github.com/ipfs/js-ipfs/issues/3514)) ([061d77c](https://github.com/ipfs/js-ipfs/commit/061d77cc03f40af5a3bc3590481e1e5836e7f0d8))\n\n\n### BREAKING CHANGES\n\n* ipfs-repo upgrade requires repo migration to v10\n\n\n\n\n\n# 0.1.0 (2021-01-15)\n\n\n### Features\n\n* add grpc server and client ([#3403](https://github.com/ipfs/js-ipfs/issues/3403)) ([a9027e0](https://github.com/ipfs/js-ipfs/commit/a9027e0ec0cea9a4f34b4f2f52e09abb35237384)), closes [#2519](https://github.com/ipfs/js-ipfs/issues/2519) [#2838](https://github.com/ipfs/js-ipfs/issues/2838) [#2943](https://github.com/ipfs/js-ipfs/issues/2943) [#2854](https://github.com/ipfs/js-ipfs/issues/2854) [#2864](https://github.com/ipfs/js-ipfs/issues/2864)\n"
  },
  {
    "path": "packages/ipfs-grpc-protocol/LICENSE",
    "content": "This project is dual licensed under MIT and Apache-2.0.\n\nMIT: https://www.opensource.org/licenses/mit\nApache-2.0: https://www.apache.org/licenses/license-2.0\n"
  },
  {
    "path": "packages/ipfs-grpc-protocol/LICENSE-APACHE",
    "content": "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\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless 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.\n"
  },
  {
    "path": "packages/ipfs-grpc-protocol/LICENSE-MIT",
    "content": "The MIT License (MIT)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "packages/ipfs-grpc-protocol/README.md",
    "content": "> # ⛔️ DEPRECATED: [js-IPFS](https://github.com/ipfs/js-ipfs) has been superseded by [Helia](https://github.com/ipfs/helia)\n>\n> 📚 [Learn more about this deprecation](https://github.com/ipfs/js-ipfs/issues/4336) or [how to migrate](https://github.com/ipfs/helia/wiki/Migrating-from-js-IPFS)\n>\n> ⚠️ If you continue using this repo, please note that security fixes will not be provided\n\n# ipfs-grpc-protocol <!-- omit in toc -->\n\n[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech)\n[![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech)\n[![codecov](https://img.shields.io/codecov/c/github/ipfs/js-ipfs.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipfs)\n[![CI](https://img.shields.io/github/actions/workflow/status/ipfs/js-ipfs/test.yml?branch=master\\&style=flat-square)](https://github.com/ipfs/js-ipfs/actions/workflows/test.yml?query=branch%3Amaster)\n\n> Protobuf definitions for the IPFS gRPC API\n\n## Table of contents <!-- omit in toc -->\n\n- [Install](#install)\n- [License](#license)\n- [Contribute](#contribute)\n\n## Install\n\n```console\n$ npm i ipfs-grpc-protocol\n```\n\n## License\n\nLicensed under either of\n\n- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / <http://www.apache.org/licenses/LICENSE-2.0>)\n- MIT ([LICENSE-MIT](LICENSE-MIT) / <http://opensource.org/licenses/MIT>)\n\n## Contribute\n\nContributions welcome! Please check out [the issues](https://github.com/ipfs/js-ipfs/issues).\n\nAlso 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.\n\nPlease 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).\n\nUnless 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.\n\n[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md)\n"
  },
  {
    "path": "packages/ipfs-grpc-protocol/package.json",
    "content": "{\n  \"name\": \"ipfs-grpc-protocol\",\n  \"version\": \"0.8.1\",\n  \"description\": \"Protobuf definitions for the IPFS gRPC API\",\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"homepage\": \"https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-grpc-protocol#readme\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ipfs/js-ipfs.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ipfs/js-ipfs/issues\"\n  },\n  \"keywords\": [\n    \"ipfs\"\n  ],\n  \"engines\": {\n    \"node\": \">=16.0.0\",\n    \"npm\": \">=7.0.0\"\n  },\n  \"type\": \"module\",\n  \"types\": \"./dist/src/index.d.ts\",\n  \"typesVersions\": {\n    \"*\": {\n      \"*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ],\n      \"src/*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ]\n    }\n  },\n  \"files\": [\n    \"src\",\n    \"dist\",\n    \"!dist/test\",\n    \"!**/*.tsbuildinfo\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/src/index.d.ts\",\n      \"import\": \"./src/index.js\"\n    }\n  },\n  \"eslintConfig\": {\n    \"extends\": \"ipfs\",\n    \"parserOptions\": {\n      \"sourceType\": \"module\"\n    }\n  },\n  \"scripts\": {\n    \"clean\": \"aegir clean\",\n    \"build\": \"npm run clean && mkdirp ./dist && pbjs ./src/*.proto -t json -o ./dist/ipfs.json && node ./scripts/update-index.js && npm run lint -- --fix && aegir build\",\n    \"lint\": \"aegir lint\"\n  },\n  \"devDependencies\": {\n    \"aegir\": \"^37.11.0\",\n    \"mkdirp\": \"^1.0.4\",\n    \"protobufjs\": \"^7.0.0\",\n    \"protobufjs-cli\": \"^1.0.0\"\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-protocol/scripts/update-index.js",
    "content": "import { readFile, writeFile } from 'fs/promises'\n\nconst ipfs = JSON.parse(\n  await readFile(\n    new URL('../dist/ipfs.json', import.meta.url)\n  )\n)\n\nawait writeFile(\n  new URL('../src/index.js', import.meta.url),\n  `\nexport default ${JSON.stringify(ipfs, null, 2)}\n`\n)\n"
  },
  {
    "path": "packages/ipfs-grpc-protocol/src/common.proto",
    "content": "syntax = \"proto3\";\n\npackage ipfs;\n\nenum FileType {\n    DIRECTORY = 0;\n    FILE = 1;\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-protocol/src/index.js",
    "content": "\nexport default {\n  nested: {\n    ipfs: {\n      nested: {\n        FileType: {\n          values: {\n            DIRECTORY: 0,\n            FILE: 1\n          }\n        },\n        MFS: {\n          methods: {\n            ls: {\n              requestType: 'LsRequest',\n              responseType: 'LsResponse',\n              responseStream: true\n            },\n            write: {\n              requestType: 'WriteRequest',\n              requestStream: true,\n              responseType: 'WriteResponse'\n            }\n          }\n        },\n        LsRequest: {\n          fields: {\n            path: {\n              type: 'string',\n              id: 1\n            }\n          }\n        },\n        LsResponse: {\n          fields: {\n            name: {\n              type: 'string',\n              id: 1\n            },\n            type: {\n              type: 'FileType',\n              id: 2\n            },\n            size: {\n              type: 'uint32',\n              id: 3\n            },\n            cid: {\n              type: 'string',\n              id: 4\n            },\n            mode: {\n              type: 'uint32',\n              id: 5\n            },\n            mtime: {\n              type: 'int32',\n              id: 6\n            },\n            mtimeNsecs: {\n              type: 'uint32',\n              id: 7\n            }\n          }\n        },\n        WriteRequest: {\n          fields: {\n            path: {\n              type: 'string',\n              id: 1\n            },\n            content: {\n              type: 'bytes',\n              id: 2\n            }\n          }\n        },\n        WriteResponse: {\n          fields: {}\n        },\n        PubSub: {\n          methods: {\n            subscribe: {\n              requestType: 'SubscribeRequest',\n              responseType: 'SubscribeResponse',\n              responseStream: true\n            },\n            unsubscribe: {\n              requestType: 'UnSubscribeRequest',\n              responseType: 'UnSubscribeResponse'\n            }\n          }\n        },\n        SubscribeRequest: {\n          fields: {\n            topic: {\n              type: 'string',\n              id: 1\n            }\n          }\n        },\n        SubscribeResponse: {\n          fields: {\n            handler: {\n              type: 'string',\n              id: 1\n            },\n            from: {\n              type: 'string',\n              id: 2\n            },\n            sequenceNumber: {\n              type: 'bytes',\n              id: 3\n            },\n            data: {\n              type: 'bytes',\n              id: 4\n            },\n            topic: {\n              type: 'string',\n              id: 5\n            },\n            key: {\n              type: 'bytes',\n              id: 6\n            },\n            signature: {\n              type: 'bytes',\n              id: 7\n            },\n            type: {\n              type: 'string',\n              id: 8\n            }\n          }\n        },\n        UnSubscribeRequest: {\n          fields: {\n            topic: {\n              type: 'string',\n              id: 1\n            },\n            handlers: {\n              rule: 'repeated',\n              type: 'string',\n              id: 2\n            }\n          }\n        },\n        UnSubscribeResponse: {\n          fields: {}\n        },\n        Root: {\n          methods: {\n            id: {\n              requestType: 'IdRequest',\n              responseType: 'IdResponse'\n            },\n            add: {\n              requestType: 'AddRequest',\n              requestStream: true,\n              responseType: 'AddResponse',\n              responseStream: true\n            }\n          }\n        },\n        AddResponseType: {\n          values: {\n            PROGRESS: 0,\n            RESULT: 1\n          }\n        },\n        IdRequest: {\n          fields: {\n            peerId: {\n              type: 'string',\n              id: 1\n            }\n          }\n        },\n        IdResponse: {\n          fields: {\n            id: {\n              type: 'string',\n              id: 1\n            },\n            publicKey: {\n              type: 'string',\n              id: 2\n            },\n            addresses: {\n              rule: 'repeated',\n              type: 'string',\n              id: 3\n            },\n            agentVersion: {\n              type: 'string',\n              id: 4\n            },\n            protocolVersion: {\n              type: 'string',\n              id: 5\n            },\n            protocols: {\n              rule: 'repeated',\n              type: 'string',\n              id: 6\n            }\n          }\n        },\n        AddRequest: {\n          fields: {\n            index: {\n              type: 'int32',\n              id: 1\n            },\n            type: {\n              type: 'FileType',\n              id: 2\n            },\n            path: {\n              type: 'string',\n              id: 3\n            },\n            mode: {\n              type: 'uint32',\n              id: 4\n            },\n            mtime: {\n              type: 'int32',\n              id: 5\n            },\n            mtimeNsecs: {\n              type: 'uint32',\n              id: 6\n            },\n            content: {\n              type: 'bytes',\n              id: 7\n            }\n          }\n        },\n        AddResponse: {\n          fields: {\n            type: {\n              type: 'AddResponseType',\n              id: 1\n            },\n            path: {\n              type: 'string',\n              id: 2\n            },\n            bytes: {\n              type: 'int32',\n              id: 3\n            },\n            cid: {\n              type: 'string',\n              id: 4\n            },\n            mode: {\n              type: 'uint32',\n              id: 5\n            },\n            mtime: {\n              type: 'int32',\n              id: 6\n            },\n            mtimeNsecs: {\n              type: 'uint32',\n              id: 7\n            },\n            size: {\n              type: 'uint32',\n              id: 8\n            }\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-protocol/src/mfs.proto",
    "content": "syntax = \"proto3\";\n\nimport \"common.proto\";\n\npackage ipfs;\n\nservice MFS {\n    rpc ls (LsRequest) returns (stream LsResponse) {}\n    rpc write (stream WriteRequest) returns (WriteResponse) {}\n}\n\nmessage LsRequest {\n    string path = 1;\n}\n\nmessage LsResponse {\n    string name = 1;\n    FileType type = 2;\n    uint32 size = 3;\n    string cid = 4;\n    uint32 mode = 5;\n    int32 mtime = 6;\n    uint32 mtime_nsecs = 7;\n}\n\nmessage WriteRequest {\n    string path = 1;\n    bytes content = 2;\n}\n\nmessage WriteResponse {\n\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-protocol/src/pubsub.proto",
    "content": "syntax = \"proto3\";\n\nimport \"common.proto\";\n\npackage ipfs;\n\nservice PubSub {\n    rpc subscribe (SubscribeRequest) returns (stream SubscribeResponse) {}\n    rpc unsubscribe (UnSubscribeRequest) returns (UnSubscribeResponse) {}\n}\n\nmessage SubscribeRequest {\n    string topic = 1;\n}\n\nmessage SubscribeResponse {\n    string handler = 1;\n    string from = 2;\n    bytes sequenceNumber = 3;\n    bytes data = 4;\n    string topic = 5;\n    bytes key = 6;\n    bytes signature = 7;\n    string type = 8;\n}\n\nmessage UnSubscribeRequest {\n    string topic = 1;\n    repeated string handlers = 2;\n}\n\nmessage UnSubscribeResponse {\n\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-protocol/src/root.proto",
    "content": "syntax = \"proto3\";\n\nimport \"common.proto\";\n\npackage ipfs;\n\nservice Root {\n    rpc id (IdRequest) returns (IdResponse) {}\n    rpc add (stream AddRequest) returns (stream AddResponse) {}\n}\n\nenum AddResponseType {\n  PROGRESS = 0;\n  RESULT = 1;\n}\n\nmessage IdRequest {\n    string peer_id = 1;\n}\n\nmessage IdResponse {\n    string id = 1;\n    string public_key = 2;\n    repeated string addresses = 3;\n    string agent_version = 4;\n    string protocol_version = 5;\n    repeated string protocols = 6;\n}\n\nmessage AddRequest {\n    int32 index = 1;\n    FileType type = 2;\n    string path = 3;\n    uint32 mode = 4;\n    int32 mtime = 5;\n    uint32 mtime_nsecs = 6;\n    bytes content = 7;\n}\n\nmessage AddResponse {\n    AddResponseType type = 1;\n    string path = 2;\n    int32 bytes = 3;\n    string cid = 4;\n    uint32 mode = 5;\n    int32 mtime = 6;\n    uint32 mtime_nsecs = 7;\n    uint32 size = 8;\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-protocol/tsconfig.json",
    "content": "{\n  \"extends\": \"aegir/src/config/tsconfig.aegir.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"emitDeclarationOnly\": true\n  },\n  \"include\": [\n    \"src\"\n  ]\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-server/CHANGELOG.md",
    "content": "# Change Log\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n\n### [0.12.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-grpc-server-v0.12.0...ipfs-grpc-server-v0.12.1) (2023-05-25)\n\n\n### Bug Fixes\n\n* add deprecation notice to readmes ([#4362](https://www.github.com/ipfs/js-ipfs/issues/4362)) ([7b79c1b](https://www.github.com/ipfs/js-ipfs/commit/7b79c1b8df5c818dc124b346ea28330455732d5c))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.14.0 to ^0.14.1\n    * ipfs-grpc-protocol bumped from ^0.8.0 to ^0.8.1\n  * devDependencies\n    * ipfs-core bumped from ^0.18.0 to ^0.18.1\n\n## [0.12.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-grpc-server-v0.11.0...ipfs-grpc-server-v0.12.0) (2023-01-11)\n\n\n### ⚠ BREAKING CHANGES\n\n* update multiformats to v11.x.x and related depenendcies (#4277)\n\n### Bug Fixes\n\n* update multiformats to v11.x.x and related depenendcies ([#4277](https://www.github.com/ipfs/js-ipfs/issues/4277)) ([521c84a](https://www.github.com/ipfs/js-ipfs/commit/521c84a958b04d61702577a5adce28519c1b2a3b))\n* use aegir to publish RCs ([#4284](https://www.github.com/ipfs/js-ipfs/issues/4284)) ([6d90cbf](https://www.github.com/ipfs/js-ipfs/commit/6d90cbf321a7dbf4b1084ba20f0c514dc08d8d0a))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.13.0 to ^0.14.0\n    * ipfs-grpc-protocol bumped from ^0.7.0 to ^0.8.0\n  * devDependencies\n    * ipfs-core bumped from ^0.17.0 to ^0.18.0\n\n## [0.11.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-grpc-server-v0.10.1...ipfs-grpc-server-v0.11.0) (2022-10-24)\n\n\n### ⚠ BREAKING CHANGES\n\n* ipfs is now bundled with libp2p@0.40.x which has different config\n\n### Features\n\n* upgrade libp2p to 0.40.x ([#4237](https://www.github.com/ipfs/js-ipfs/issues/4237)) ([0cee4a4](https://www.github.com/ipfs/js-ipfs/commit/0cee4a4c55767022584dcbade0b0b9b43326f9c9))\n\n\n### Bug Fixes\n\n* replace slice with subarray for increased performance ([#4210](https://www.github.com/ipfs/js-ipfs/issues/4210)) ([dfc43d4](https://www.github.com/ipfs/js-ipfs/commit/dfc43d4e9be67fdf25553677f469379d966ff806))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.12.1 to ^0.13.0\n  * devDependencies\n    * ipfs-core bumped from ^0.16.1 to ^0.17.0\n\n### [0.10.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-grpc-server-v0.10.0...ipfs-grpc-server-v0.10.1) (2022-09-21)\n\n\n### Bug Fixes\n\n* update @multiformats/multiadd to 11.0.0 ([2a830bf](https://www.github.com/ipfs/js-ipfs/commit/2a830bf58a5929fcce51dede871c99f62192fbda))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.12.0 to ^0.12.1\n  * devDependencies\n    * ipfs-core bumped from ^0.16.0 to ^0.16.1\n\n## [0.10.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-grpc-server-v0.9.4...ipfs-grpc-server-v0.10.0) (2022-09-06)\n\n\n### ⚠ BREAKING CHANGES\n\n* update to libp2p@0.38.x (#4151)\n\n### deps\n\n* update to libp2p@0.38.x ([#4151](https://www.github.com/ipfs/js-ipfs/issues/4151)) ([39dbf70](https://www.github.com/ipfs/js-ipfs/commit/39dbf708ec31b263115e44f420651fa4e056a89e))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.11.0 to ^0.12.0\n    * ipfs-grpc-protocol bumped from ^0.6.0 to ^0.7.0\n  * devDependencies\n    * ipfs-core bumped from ^0.15.0 to ^0.16.0\n\n### [0.9.4](https://www.github.com/ipfs/js-ipfs/compare/ipfs-grpc-server-v0.9.3...ipfs-grpc-server-v0.9.4) (2022-06-24)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * ipfs-core bumped from ^0.15.3 to ^0.15.4\n\n### [0.9.3](https://www.github.com/ipfs/js-ipfs/compare/ipfs-grpc-server-v0.9.2...ipfs-grpc-server-v0.9.3) (2022-06-22)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.11.0 to ^0.11.1\n  * devDependencies\n    * ipfs-core bumped from ^0.15.2 to ^0.15.3\n\n### [0.9.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-grpc-server-v0.9.1...ipfs-grpc-server-v0.9.2) (2022-06-13)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * ipfs-core bumped from ^0.15.1 to ^0.15.2\n\n### [0.9.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-grpc-server-v0.9.0...ipfs-grpc-server-v0.9.1) (2022-06-01)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * ipfs-core bumped from ^0.15.0 to ^0.15.1\n\n## [0.9.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-grpc-server-v0.8.4...ipfs-grpc-server-v0.9.0) (2022-05-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* This module is now ESM only and there return types of some methods have changed\n\n### Features\n\n* update to libp2p 0.37.x ([#4092](https://www.github.com/ipfs/js-ipfs/issues/4092)) ([74aee8b](https://www.github.com/ipfs/js-ipfs/commit/74aee8b3d78f233c3199a3e9a6c0ac628a31a433))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.3 to ^0.11.0\n    * ipfs-grpc-protocol bumped from ^0.5.5 to ^0.6.0\n  * devDependencies\n    * ipfs-core bumped from ^0.14.3 to ^0.15.0\n\n### [0.8.4](https://www.github.com/ipfs/js-ipfs/compare/ipfs-grpc-server-v0.8.3...ipfs-grpc-server-v0.8.4) (2022-04-20)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.2 to ^0.10.3\n  * devDependencies\n    * ipfs-core bumped from ^0.14.2 to ^0.14.3\n\n### [0.8.3](https://www.github.com/ipfs/js-ipfs/compare/ipfs-grpc-server-v0.8.2...ipfs-grpc-server-v0.8.3) (2022-03-01)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.1 to ^0.10.2\n  * devDependencies\n    * ipfs-core bumped from ^0.14.1 to ^0.14.2\n\n### [0.8.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-grpc-server-v0.8.1...ipfs-grpc-server-v0.8.2) (2022-02-06)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.0 to ^0.10.1\n  * devDependencies\n    * ipfs-core bumped from ^0.14.0 to ^0.14.1\n\n### [0.8.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-grpc-server-v0.8.0...ipfs-grpc-server-v0.8.1) (2022-01-27)\n\n\n## [0.8.0](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-server@0.7.4...ipfs-grpc-server@0.8.0) (2021-12-15)\n\n\n### Features\n\n* dht client ([#3947](https://github.com/ipfs/js-ipfs/issues/3947)) ([62d8ecb](https://github.com/ipfs/js-ipfs/commit/62d8ecbc723e693a2544e69172d99c576d187c23))\n\n\n### BREAKING CHANGES\n\n* The DHT API has been refactored to return async iterators of query events\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.9.0 to ^0.10.0\n  * devDependencies\n    * ipfs-core bumped from ^0.13.0 to ^0.14.0\n\n### [0.7.4](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-server@0.7.3...ipfs-grpc-server@0.7.4) (2021-11-24)\n\n**Note:** Version bump only for package ipfs-grpc-server\n\n\n\n\n\n### [0.7.3](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-server@0.7.2...ipfs-grpc-server@0.7.3) (2021-11-19)\n\n**Note:** Version bump only for package ipfs-grpc-server\n\n\n\n\n\n### [0.7.2](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-server@0.7.1...ipfs-grpc-server@0.7.2) (2021-11-12)\n\n**Note:** Version bump only for package ipfs-grpc-server\n\n\n\n\n\n### [0.7.1](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-server@0.7.0...ipfs-grpc-server@0.7.1) (2021-09-28)\n\n**Note:** Version bump only for package ipfs-grpc-server\n\n\n\n\n\n## [0.7.0](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-server@0.6.6...ipfs-grpc-server@0.7.0) (2021-09-24)\n\n\n### Features\n\n* switch to esm ([#3879](https://github.com/ipfs/js-ipfs/issues/3879)) ([9a40109](https://github.com/ipfs/js-ipfs/commit/9a40109632e5b4837eb77a2f57dbc77fbf1fe099))\n\n\n### BREAKING CHANGES\n\n* There are no default exports and everything is now dual published as ESM/CJS\n\n\n\n\n\n### [0.6.6](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-server@0.6.5...ipfs-grpc-server@0.6.6) (2021-09-17)\n\n**Note:** Version bump only for package ipfs-grpc-server\n\n\n\n\n\n### [0.6.5](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-server@0.6.4...ipfs-grpc-server@0.6.5) (2021-09-17)\n\n**Note:** Version bump only for package ipfs-grpc-server\n\n\n\n\n\n### [0.6.4](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-server@0.6.3...ipfs-grpc-server@0.6.4) (2021-09-08)\n\n**Note:** Version bump only for package ipfs-grpc-server\n\n\n\n\n\n### [0.6.3](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-server@0.6.2...ipfs-grpc-server@0.6.3) (2021-09-02)\n\n\n### Bug Fixes\n\n* declare types in .ts files ([#3840](https://github.com/ipfs/js-ipfs/issues/3840)) ([eba5fe6](https://github.com/ipfs/js-ipfs/commit/eba5fe6832858107b3e1ae02c99de674622f12b4))\n* remove use of instanceof for CID class ([#3847](https://github.com/ipfs/js-ipfs/issues/3847)) ([ebbb12d](https://github.com/ipfs/js-ipfs/commit/ebbb12db523c53ce8e4ddae5266cd9acb3504431))\n\n\n\n\n\n### [0.6.2](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-server@0.6.1...ipfs-grpc-server@0.6.2) (2021-08-25)\n\n\n### Bug Fixes\n\n* grpc server may not be enabled ([#3834](https://github.com/ipfs/js-ipfs/issues/3834)) ([533845e](https://github.com/ipfs/js-ipfs/commit/533845e3d140459ca383b1538e571d08850c0ef8))\n\n\n\n\n\n### [0.6.1](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-server@0.6.0...ipfs-grpc-server@0.6.1) (2021-08-17)\n\n**Note:** Version bump only for package ipfs-grpc-server\n\n\n\n\n\n## [0.6.0](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-server@0.5.0...ipfs-grpc-server@0.6.0) (2021-08-17)\n\n\n### Features\n\n* pubsub over gRPC ([#3813](https://github.com/ipfs/js-ipfs/issues/3813)) ([e7d5509](https://github.com/ipfs/js-ipfs/commit/e7d5509c87e87aed6be3c1d0b2a01ab74cdc1ed9)), closes [#3741](https://github.com/ipfs/js-ipfs/issues/3741)\n\n\n\n\n\n## [0.5.0](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-server@0.4.1...ipfs-grpc-server@0.5.0) (2021-08-11)\n\n\n### Features\n\n* make ipfs.get output tarballs ([#3785](https://github.com/ipfs/js-ipfs/issues/3785)) ([1ad6001](https://github.com/ipfs/js-ipfs/commit/1ad60018d39d5b46c484756631e30e1989fd8eba))\n\n\n### BREAKING CHANGES\n\n* the output type of `ipfs.get` has changed and the `recursive` option has been removed from `ipfs.ls` since it was not supported everywhere\n\n\n\n\n\n### [0.4.1](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-server@0.4.0...ipfs-grpc-server@0.4.1) (2021-07-30)\n\n**Note:** Version bump only for package ipfs-grpc-server\n\n\n\n\n\n## [0.4.0](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-server@0.3.4...ipfs-grpc-server@0.4.0) (2021-07-27)\n\n\n### Features\n\n* implement dag import/export ([#3728](https://github.com/ipfs/js-ipfs/issues/3728)) ([700765b](https://github.com/ipfs/js-ipfs/commit/700765be2634fa5d2d71d8b87cf68c9cd328d2c4)), closes [#2953](https://github.com/ipfs/js-ipfs/issues/2953) [#2745](https://github.com/ipfs/js-ipfs/issues/2745)\n* upgrade to the new multiformats ([#3556](https://github.com/ipfs/js-ipfs/issues/3556)) ([d13d15f](https://github.com/ipfs/js-ipfs/commit/d13d15f022a87d04a35f0f7822142f9cb898479c))\n\n\n### BREAKING CHANGES\n\n* ipld-formats no longer supported, use multiformat BlockCodecs instead\n\nCo-authored-by: Rod Vagg <rod@vagg.org>\nCo-authored-by: achingbrain <alex@achingbrain.net>\n\n\n\n\n\n### [0.3.4](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-server@0.3.3...ipfs-grpc-server@0.3.4) (2021-06-18)\n\n**Note:** Version bump only for package ipfs-grpc-server\n\n\n\n\n\n### [0.3.3](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-server@0.3.2...ipfs-grpc-server@0.3.3) (2021-06-05)\n\n**Note:** Version bump only for package ipfs-grpc-server\n\n\n\n\n\n### [0.3.2](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-server@0.3.1...ipfs-grpc-server@0.3.2) (2021-05-26)\n\n**Note:** Version bump only for package ipfs-grpc-server\n\n\n\n\n\n### [0.3.1](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-server@0.3.0...ipfs-grpc-server@0.3.1) (2021-05-11)\n\n**Note:** Version bump only for package ipfs-grpc-server\n\n\n\n\n\n## [0.3.0](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-server@0.2.4...ipfs-grpc-server@0.3.0) (2021-05-10)\n\n\n### Bug Fixes\n\n* ignore the ts error caused by the recent protobufjs type change ([#3656](https://github.com/ipfs/js-ipfs/issues/3656)) ([084589c](https://github.com/ipfs/js-ipfs/commit/084589c0116d8f27ce1462424fb93b6037b776a9))\n* update data type for ws message event handler ([#3641](https://github.com/ipfs/js-ipfs/issues/3641)) ([4a14d20](https://github.com/ipfs/js-ipfs/commit/4a14d20e727b50a8d98c14573d9a5b6fa0e8699d))\n\n\n### chore\n\n* upgrade deps with new typedefs ([#3550](https://github.com/ipfs/js-ipfs/issues/3550)) ([a418a52](https://github.com/ipfs/js-ipfs/commit/a418a521574c878d7aabd0ad2fd8d516908a3756))\n\n\n### BREAKING CHANGES\n\n* all core api methods now have types, some method signatures have changed, named exports are now used by the http, grpc and ipfs client modules\n\n\n\n\n\n### [0.2.4](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-server@0.2.3...ipfs-grpc-server@0.2.4) (2021-03-10)\n\n**Note:** Version bump only for package ipfs-grpc-server\n\n\n\n\n\n### [0.2.3](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-server@0.2.2...ipfs-grpc-server@0.2.3) (2021-03-09)\n\n\n### Bug Fixes\n\n* update to new aegir ([#3528](https://github.com/ipfs/js-ipfs/issues/3528)) ([49f7880](https://github.com/ipfs/js-ipfs/commit/49f78807d7e26483bd926b45cc7e0f797d77e41b))\n\n\n\n\n\n### [0.2.2](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-server@0.2.1...ipfs-grpc-server@0.2.2) (2021-02-08)\n\n**Note:** Version bump only for package ipfs-grpc-server\n\n\n\n\n\n### [0.2.1](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-server@0.2.0...ipfs-grpc-server@0.2.1) (2021-02-02)\n\n**Note:** Version bump only for package ipfs-grpc-server\n\n\n\n\n\n## [0.2.0](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-server@0.1.2...ipfs-grpc-server@0.2.0) (2021-02-01)\n\n\n### Bug Fixes\n\n* updates webpack example to use v5 ([#3512](https://github.com/ipfs/js-ipfs/issues/3512)) ([c7110db](https://github.com/ipfs/js-ipfs/commit/c7110db71b5c0f0f9f415f31f91b5b228341e13e)), closes [#3511](https://github.com/ipfs/js-ipfs/issues/3511)\n\n\n### chore\n\n* update deps ([#3514](https://github.com/ipfs/js-ipfs/issues/3514)) ([061d77c](https://github.com/ipfs/js-ipfs/commit/061d77cc03f40af5a3bc3590481e1e5836e7f0d8))\n\n\n### Features\n\n* support  remote pinning services in ipfs-http-client ([#3293](https://github.com/ipfs/js-ipfs/issues/3293)) ([ba240fd](https://github.com/ipfs/js-ipfs/commit/ba240fdf93edc88028315483240d7822a7ca88ed))\n\n\n### BREAKING CHANGES\n\n* ipfs-repo upgrade requires repo migration to v10\n\n\n\n\n\n### [0.1.2](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-server@0.1.1...ipfs-grpc-server@0.1.2) (2021-01-22)\n\n**Note:** Version bump only for package ipfs-grpc-server\n\n\n\n\n\n### [0.1.1](https://github.com/ipfs/js-ipfs/compare/ipfs-grpc-server@0.1.0...ipfs-grpc-server@0.1.1) (2021-01-20)\n\n**Note:** Version bump only for package ipfs-grpc-server\n\n\n\n\n\n# 0.1.0 (2021-01-15)\n\n\n### Features\n\n* add grpc server and client ([#3403](https://github.com/ipfs/js-ipfs/issues/3403)) ([a9027e0](https://github.com/ipfs/js-ipfs/commit/a9027e0ec0cea9a4f34b4f2f52e09abb35237384)), closes [#2519](https://github.com/ipfs/js-ipfs/issues/2519) [#2838](https://github.com/ipfs/js-ipfs/issues/2838) [#2943](https://github.com/ipfs/js-ipfs/issues/2943) [#2854](https://github.com/ipfs/js-ipfs/issues/2854) [#2864](https://github.com/ipfs/js-ipfs/issues/2864)"
  },
  {
    "path": "packages/ipfs-grpc-server/LICENSE",
    "content": "This project is dual licensed under MIT and Apache-2.0.\n\nMIT: https://www.opensource.org/licenses/mit\nApache-2.0: https://www.apache.org/licenses/license-2.0\n"
  },
  {
    "path": "packages/ipfs-grpc-server/LICENSE-APACHE",
    "content": "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\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless 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.\n"
  },
  {
    "path": "packages/ipfs-grpc-server/LICENSE-MIT",
    "content": "The MIT License (MIT)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "packages/ipfs-grpc-server/README.md",
    "content": "> # ⛔️ DEPRECATED: [js-IPFS](https://github.com/ipfs/js-ipfs) has been superseded by [Helia](https://github.com/ipfs/helia)\n>\n> 📚 [Learn more about this deprecation](https://github.com/ipfs/js-ipfs/issues/4336) or [how to migrate](https://github.com/ipfs/helia/wiki/Migrating-from-js-IPFS)\n>\n> ⚠️ If you continue using this repo, please note that security fixes will not be provided\n\n# ipfs-grpc-server <!-- omit in toc -->\n\n[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech)\n[![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech)\n[![codecov](https://img.shields.io/codecov/c/github/ipfs/js-ipfs.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipfs)\n[![CI](https://img.shields.io/github/actions/workflow/status/ipfs/js-ipfs/test.yml?branch=master\\&style=flat-square)](https://github.com/ipfs/js-ipfs/actions/workflows/test.yml?query=branch%3Amaster)\n\n> A server library for the IPFS gRPC API\n\n## Table of contents <!-- omit in toc -->\n\n- [Install](#install)\n- [Why?](#why)\n- [Protocol](#protocol)\n  - [1. Metadata](#1-metadata)\n  - [2. Messages](#2-messages)\n    - [Signal](#signal)\n    - [Header](#header)\n    - [Message data](#message-data)\n    - [Trailer](#trailer)\n- [Handlers](#handlers)\n  - [Metadata](#metadata)\n  - [Unary](#unary)\n  - [Server streaming](#server-streaming)\n  - [Client streaming](#client-streaming)\n  - [Bidirectional streaming](#bidirectional-streaming)\n- [License](#license)\n- [Contribute](#contribute)\n\n## Install\n\n```console\n$ npm i ipfs-grpc-server\n```\n\n## Why?\n\n[gRPC-web](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md) allows us to form HTTP requests out of gRPC invocations, but the [official implementation](https://github.com/grpc/grpc-web) supports only unary calls and server streaming, which in terms of functionality doesn't give us much over the existing [ipfs-http-client](https://www.npmjs.com/package/ipfs-http-client).\n\nIn order to support streaming file uploads with errors, pubsub, etc, bi-directional streaming is required.  We can either use Websockets for this, or use two connections, one for upload and one for download though this involves two requests for every operation and some orchestration on the server side to match one up with the other.\n\nWebsockets are a cheaper and simpler way of accomplishing the same thing though sadly the official gRPC implementation has [no plans](https://github.com/grpc/grpc-web/blob/master/doc/streaming-roadmap.md#issues-with-websockets) to implement full-duplex streaming in this way.\n\nThis module implements a Websocket proxy for a gRPC-web server.  It's a js port of the [grpcwebproxy](https://github.com/improbable-eng/grpc-web/tree/master/go/grpcwebproxy) project from [improbable-eng/grpc-web](https://github.com/improbable-eng/grpc-web).\n\n## Protocol\n\nEvery RPC invocation opens a new WebSocket connection, the invocation is completed and the socket connection is closed.\n\nThe connection is opened against the path of the RPC method the client wishes to invoke.  The path is created from the protobuf service definition package, service and procedure name.\n\nE.g. given the following service definition:\n\n```protobuf\npackage ipfs;\n\nservice Root {\n  rpc id (Req) returns (Res) {}\n}\n```\n\nA path of `/ipfs.Root/id` would be created.\n\nThere are three parts to the communication, metadata, messages and trailers.  Communication is symmetrical; that is, the client sends metadata, one or more messages and finally some trailers and the server responds with metadata, one or more messages and finally some trailers.\n\nThe amount of messages a client/server can send is dictated by if the RPC method is unary or streaming and if so in which direction.\n\nUnary will result in one message sent and one received, client streaming is many sent and one received, server streaming is one sent and many received and finally bidirectional is many sent and many received.\n\n### 1. Metadata\n\nMetadata is sent as the first websocket message. It is a utf8 encoded list in the same format as [HTTP Headers][]\n\n### 2. Messages\n\nOne ore more messages will be sent.  Messages are sent as a single websocket message and contain a signal, a header, some message data and an optional trailer.\n\nEvery message sent to or received from the server will have the following format:\n\n| byte index | Notes        |\n| ---------- | ------------ |\n| 0          | Signal       |\n| 1-5        | Header       |\n| n1-n2      | Message data |\n| n3-n3+5    | Trailer      |\n\n#### Signal\n\nA one-byte field.\n\n| Value | Meaning                                                               |\n| ----- | --------------------------------------------------------------------- |\n| 0     | START\\_SEND: Further messages will be sent as part of this context    |\n| 1     | FINISH\\_SEND: This is the final message, no further data will be sent |\n\n#### Header\n\nA five-byte field that contains one byte signifying if it's a Header or a Trailer and four bytes that contain the length of the following data.\n\n| byte index | Meaning                                                                        |\n| ---------- | ------------------------------------------------------------------------------ |\n| 0          | 0: This is a header, 128: This is a footer                                     |\n| 1-4        | An unsigned big-endian 32-bit integer that specifies the length of the message |\n\n#### Message data\n\nA protocol buffer message, the length of which is defined in the header\n\n#### Trailer\n\nA five-byte field that contains one byte signifying if it's a Header or a Trailer and four bytes that contain the length of the following data.\n\n| byte index | Meaning                                                              |\n| ---------- | -------------------------------------------------------------------- |\n| 0          | 0: This is a header, 128: This is a footer                           |\n| 1-4        | A big-endian 32-bit integer that specifies the length of the trailer |\n\nThe trailer contains [HTTP headers][] as a utf8 encoded string in the same way as invocation metadata.\n\n## Handlers\n\nMethod handlers come in four flavours - unary, server streaming, client streaming, bidirectional streaming and accept metadata as an argument.\n\n### Metadata\n\nAll methods accept metadata which are sent as the equivalent of HTTP headers as part of every request.  These are accepted by the client as options to a given method.\n\nE.g.:\n\n```js\nipfs.addAll(source, options)\n// `source` will be turned into a message stream\n// `options` will be sent as metadata\n```\n\n### Unary\n\nThe simplest case, one request message and one response message.\n\n```javascript\nexport function grpcFunction (ipfs, options = {}) {\n  async function handler (request, metadata) {\n    const response = {\n      //... some fields here\n    }\n\n    return response\n  }\n\n  return handler\n}\n```\n\n### Server streaming\n\nWhere the server sends multiple messages.  `sink` is an [it-pushable][].\n\n```javascript\nexport function grpcFunction (ipfs, options = {}) {\n  async function serverStreamingHandler (request, sink, metadata) {\n    sink.push(..)\n    sink.push(..)\n\n    sink.end()\n  }\n\n  return clientStreamingHandler\n}\n```\n\n### Client streaming\n\nWhere the client sends multiple messages.  `source` is an [AsyncIterator][].\n\n```javascript\nexport function grpcFunction (ipfs, options = {}) {\n  async function clientStreamingHandler (source, metadata) {\n    const response = {\n      //... some fields here\n    }\n\n    for await (const thing of source) {\n      // do something with `thing`\n    }\n\n    return response\n  }\n\n  return handler\n}\n```\n\n### Bidirectional streaming\n\nWhere the client and the server both send multiple messages.  `source` is an [AsyncIterator][] and `sink` is an [it-pushable][].\n\n```javascript\nexport function grpcFunction (ipfs, options = {}) {\n  async function bidirectionalHandler (source, sink, metadata) {\n    for await (const thing of source) {\n      sink.push(sink)\n    }\n\n    sink.end()\n  }\n\n  return bidirectionalHandler\n}\n```\n\n## License\n\nLicensed under either of\n\n- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / <http://www.apache.org/licenses/LICENSE-2.0>)\n- MIT ([LICENSE-MIT](LICENSE-MIT) / <http://opensource.org/licenses/MIT>)\n\n## Contribute\n\nContributions welcome! Please check out [the issues](https://github.com/ipfs/js-ipfs/issues).\n\nAlso 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.\n\nPlease 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).\n\nUnless 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.\n\n[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md)\n\n[HTTP headers]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers\n\n[it-pushable]: https://www.npmjs.com/package/it-pushable\n\n[AsyncIterator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncIterator\n"
  },
  {
    "path": "packages/ipfs-grpc-server/package.json",
    "content": "{\n  \"name\": \"ipfs-grpc-server\",\n  \"version\": \"0.12.1\",\n  \"description\": \"A server library for the IPFS gRPC API\",\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"homepage\": \"https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-grpc-server#readme\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ipfs/js-ipfs.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ipfs/js-ipfs/issues\"\n  },\n  \"keywords\": [\n    \"ipfs\"\n  ],\n  \"engines\": {\n    \"node\": \">=16.0.0\",\n    \"npm\": \">=7.0.0\"\n  },\n  \"type\": \"module\",\n  \"types\": \"./dist/src/index.d.ts\",\n  \"typesVersions\": {\n    \"*\": {\n      \"*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ],\n      \"src/*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ]\n    }\n  },\n  \"files\": [\n    \"src\",\n    \"dist\",\n    \"!dist/test\",\n    \"!**/*.tsbuildinfo\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/src/index.d.ts\",\n      \"import\": \"./src/index.js\"\n    }\n  },\n  \"eslintConfig\": {\n    \"extends\": \"ipfs\",\n    \"parserOptions\": {\n      \"sourceType\": \"module\"\n    }\n  },\n  \"scripts\": {\n    \"lint\": \"aegir lint\",\n    \"test\": \"aegir test -t node\",\n    \"test:node\": \"aegir test -t node --cov\",\n    \"clean\": \"aegir clean\",\n    \"dep-check\": \"aegir dep-check -i ipfs-grpc-protocol -i aegir -i ipfs-core -i ipfs-core-types\",\n    \"build\": \"aegir build --no-bundle\"\n  },\n  \"dependencies\": {\n    \"@grpc/grpc-js\": \"^1.1.8\",\n    \"@libp2p/logger\": \"^2.0.5\",\n    \"@libp2p/peer-id\": \"^2.0.0\",\n    \"@multiformats/multiaddr\": \"^11.1.5\",\n    \"change-case\": \"^4.1.1\",\n    \"coercer\": \"^1.1.2\",\n    \"ipfs-core-types\": \"^0.14.1\",\n    \"ipfs-grpc-protocol\": \"^0.8.1\",\n    \"it-first\": \"^2.0.0\",\n    \"it-map\": \"^2.0.0\",\n    \"it-peekable\": \"^2.0.0\",\n    \"it-pipe\": \"^2.0.3\",\n    \"it-pushable\": \"^3.0.0\",\n    \"nanoid\": \"^4.0.0\",\n    \"protobufjs\": \"^7.0.0\",\n    \"uint8arrays\": \"^4.0.2\",\n    \"ws\": \"^8.5.0\"\n  },\n  \"devDependencies\": {\n    \"@types/ws\": \"^8.5.3\",\n    \"aegir\": \"^37.11.0\",\n    \"ipfs-core\": \"^0.18.1\",\n    \"it-all\": \"^2.0.0\",\n    \"it-drain\": \"^2.0.0\",\n    \"sinon\": \"^15.0.1\"\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-server/src/endpoints/add.js",
    "content": "import { pushable } from 'it-pushable'\nimport { pipe } from 'it-pipe'\nimport { encodeMtime } from '../utils/encode-mtime.js'\n\n/**\n * @param {import('ipfs-core-types').IPFS} ipfs\n * @param {import('../types').Options} options\n */\nexport function grpcAdd (ipfs, options = {}) {\n  /**\n   * TODO: Fill out input/output types after https://github.com/ipfs/js-ipfs/issues/3594\n   *\n   * @type {import('../types').BidirectionalStreamingEndpoint<any, any, any>}\n   */\n  async function add (source, sink, metadata) {\n    const opts = {\n      ...metadata,\n      progress: (bytes = 0, path = '') => {\n        sink.push({\n          type: 'PROGRESS',\n          bytes,\n          path\n        })\n      }\n    }\n\n    await pipe(\n      async function * toInput () {\n        const fileInputStream = pushable({ objectMode: true })\n\n        setTimeout(async () => {\n          const streams = []\n\n          try {\n            for await (const { index, type, path, mode, mtime, mtimeNsecs, content } of source) {\n              let mtimeObj\n\n              if (mtime != null) {\n                mtimeObj = {\n                  secs: mtime,\n                  nsecs: undefined\n                }\n\n                if (mtimeNsecs != null) {\n                  mtimeObj.nsecs = mtimeNsecs\n                }\n              }\n\n              if (!type || type === 'DIRECTORY') {\n                // directory\n                fileInputStream.push({\n                  path,\n                  mode: mode !== 0 ? mode : undefined,\n                  mtime: mtimeObj\n                })\n\n                continue\n              }\n\n              let stream = streams[index]\n\n              if (!stream) {\n                // start of new file\n                stream = streams[index] = pushable()\n\n                fileInputStream.push({\n                  path,\n                  mode: mode !== 0 ? mode : undefined,\n                  mtime: mtimeObj,\n                  content: stream\n                })\n              }\n\n              if (content && content.length) {\n                // file is in progress\n                stream.push(content)\n              } else {\n                // file is finished\n                stream.end()\n\n                streams[index] = null\n              }\n            }\n\n            fileInputStream.end()\n          } catch (/** @type {any} */ err) {\n            fileInputStream.end(err)\n          } finally {\n            // clean up any open streams\n            streams.forEach(stream => stream && stream.end())\n          }\n        }, 0)\n\n        yield * fileInputStream\n      },\n      async function (source) {\n        for await (const result of ipfs.addAll(source, opts)) {\n          sink.push({\n            ...result,\n            type: 'RESULT',\n            cid: result.cid.toString(),\n            ...encodeMtime(result.mtime)\n          })\n        }\n\n        sink.end()\n      }\n    )\n  }\n\n  return add\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-server/src/endpoints/id.js",
    "content": "import { callbackify } from 'util'\nimport { peerIdFromString } from '@libp2p/peer-id'\n\n/**\n * @param {import('ipfs-core-types').IPFS} ipfs\n * @param {import('../types').Options} options\n */\nexport function grpcId (ipfs, options = {}) {\n  /**\n   * TODO: Fill out input/output types after https://github.com/ipfs/js-ipfs/issues/3594\n   *\n   * @type {import('../types').UnaryEndpoint<any, any, any>}\n   */\n  function id (request, metadata) {\n    const opts = {\n      ...request,\n      ...metadata\n    }\n\n    return ipfs.id({\n      ...opts,\n      peerId: opts.peerId ? peerIdFromString(opts.peerId) : undefined\n    })\n  }\n\n  return callbackify(id)\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-server/src/endpoints/mfs/ls.js",
    "content": "import { encodeMtime } from '../../utils/encode-mtime.js'\n\n/**\n * @param {import('ipfs-core-types').IPFS} ipfs\n * @param {import('../../types').Options} options\n */\nexport function grpcMfsLs (ipfs, options = {}) {\n  /**\n   * TODO: Fill out input/output types after https://github.com/ipfs/js-ipfs/issues/3594\n   *\n   * @type {import('../../types').ServerStreamingEndpoint<any, any, any>}\n   */\n  async function mfsLs (request, sink, metadata) {\n    const opts = {\n      ...metadata\n    }\n\n    for await (const result of ipfs.files.ls(request.path, opts)) {\n      sink.push({\n        ...result,\n        cid: result.cid.toString(),\n        type: result.type.toUpperCase(),\n        ...encodeMtime(result.mtime)\n      })\n    }\n\n    sink.end()\n  }\n\n  return mfsLs\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-server/src/endpoints/mfs/write.js",
    "content": "import peekable from 'it-peekable'\nimport map from 'it-map'\nimport { callbackify } from 'util'\n\n/**\n * @param {import('ipfs-core-types').IPFS} ipfs\n * @param {import('../../types').Options} options\n */\nexport function grpcMfsWrite (ipfs, options = {}) {\n  /**\n   * TODO: Fill out input/output types after https://github.com/ipfs/js-ipfs/issues/3594\n   *\n   * @type {import('../../types').ClientStreamingEndpoint<any, any, any>}\n   */\n  async function mfsWrite (source, metadata) {\n    const opts = {\n      ...metadata\n    }\n\n    if (opts.mtime) {\n      opts.mtime = {\n        secs: opts.mtime,\n        nsecs: opts.mtimeNsecs\n      }\n    }\n\n    // path is sent with content messages\n    const content = peekable(source)\n    const result = await content.peek()\n    const {\n      value: {\n        path\n      }\n    } = result\n    content.push(result.value)\n\n    await ipfs.files.write(path, map(content, ({ content }) => content), opts)\n\n    return {}\n  }\n\n  return callbackify(mfsWrite)\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-server/src/endpoints/pubsub/subscribe.js",
    "content": "import { subscriptions } from './subscriptions.js'\nimport { nanoid } from 'nanoid'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\n\n/**\n * @typedef {import('@libp2p/interface-pubsub').Message} Message\n */\n\n/**\n * @param {import('ipfs-core-types').IPFS} ipfs\n * @param {import('../../types').Options} options\n */\nexport function grpcPubsubSubscribe (ipfs, options = {}) {\n  /**\n   * TODO: Fill out input/output types after https://github.com/ipfs/js-ipfs/issues/3594\n   *\n   * @type {import('../../types').ServerStreamingEndpoint<any, any, any>}\n   */\n  async function pubsubSubscribe (request, sink, metadata) {\n    const opts = {\n      ...metadata\n    }\n\n    const handlerId = nanoid()\n    const handler = {\n      /** @type {import('@libp2p/interfaces/events').EventHandler<Message>} */\n      onMessage: (message) => {\n        let sequenceNumber\n\n        if (message.type === 'signed' && message.sequenceNumber != null) {\n          let numberString = message.sequenceNumber.toString(16)\n\n          if (numberString.length % 2 !== 0) {\n            numberString = `0${numberString}`\n          }\n\n          sequenceNumber = uint8ArrayFromString(numberString, 'base16')\n        }\n\n        sink.push({\n          ...message,\n          sequenceNumber\n        })\n      },\n      onUnsubscribe: () => {\n        sink.end()\n      }\n    }\n\n    subscriptions.set(handlerId, handler)\n\n    sink.push({\n      handler: handlerId\n    })\n\n    await ipfs.pubsub.subscribe(request.topic, handler.onMessage, opts)\n  }\n\n  return pubsubSubscribe\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-server/src/endpoints/pubsub/subscriptions.js",
    "content": "/**\n * @typedef {import('@libp2p/interface-pubsub').Message} Message\n * @typedef {object} Subscription\n * @property {import('@libp2p/interfaces/events').EventHandler<Message>} onMessage\n * @property {() => void} onUnsubscribe\n */\n\n/** @type {Map<string, Subscription>} */\nexport const subscriptions = new Map()\n"
  },
  {
    "path": "packages/ipfs-grpc-server/src/endpoints/pubsub/unsubscribe.js",
    "content": "import { subscriptions } from './subscriptions.js'\nimport { callbackify } from 'util'\n\n/**\n * @param {import('ipfs-core-types').IPFS} ipfs\n * @param {import('../../types').Options} options\n */\nexport function grpcPubsubUnsubscribe (ipfs, options = {}) {\n  /**\n   * TODO: Fill out input/output types after https://github.com/ipfs/js-ipfs/issues/3594\n   *\n   * @type {import('../../types').UnaryEndpoint<any, any, any>}\n   */\n  async function pubsubUnsubscribe (request, metadata) {\n    const opts = {\n      ...metadata\n    }\n\n    if (!request.handlers || !request.handlers.length) {\n      await ipfs.pubsub.unsubscribe(request.topic, undefined, opts)\n\n      return {}\n    }\n\n    for (const handlerId of request.handlers) {\n      const handler = subscriptions.get(handlerId)\n\n      if (!handler) {\n        continue\n      }\n\n      await ipfs.pubsub.unsubscribe(request.topic, handler.onMessage, opts)\n\n      handler.onUnsubscribe()\n      subscriptions.delete(handlerId)\n    }\n\n    return {}\n  }\n\n  return callbackify(pubsubUnsubscribe)\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-server/src/index.js",
    "content": "import grpc from '@grpc/grpc-js'\nimport first from 'it-first'\nimport { logger } from '@libp2p/logger'\nimport { webSocketServer } from './utils/web-socket-server.js'\nimport { loadServices } from './utils/load-services.js'\nimport { grpcAdd } from './endpoints/add.js'\nimport { grpcId } from './endpoints/id.js'\nimport { grpcMfsLs } from './endpoints/mfs/ls.js'\nimport { grpcMfsWrite } from './endpoints/mfs/write.js'\nimport { grpcPubsubSubscribe } from './endpoints/pubsub/subscribe.js'\nimport { grpcPubsubUnsubscribe } from './endpoints/pubsub/unsubscribe.js'\n\nconst log = logger('ipfs:grpc-server')\n\nconst {\n  Root,\n  MFS,\n  PubSub\n} = loadServices()\n\n/**\n * @param {import('ipfs-core-types').IPFS} ipfs\n * @param {import('./types').Options} options\n */\nexport async function createServer (ipfs, options = {}) {\n  options = options || {}\n\n  const server = new grpc.Server()\n  server.addService(Root, {\n    // @ts-expect-error - types differ because we only invoke via websockets - https://github.com/ipfs/js-ipfs/issues/3594\n    add: grpcAdd(ipfs, options),\n    // @ts-expect-error - types differ because we only invoke via websockets - https://github.com/ipfs/js-ipfs/issues/3594\n    id: grpcId(ipfs, options)\n  })\n  server.addService(MFS, {\n    // @ts-expect-error - types differ because we only invoke via websockets - https://github.com/ipfs/js-ipfs/issues/3594\n    ls: grpcMfsLs(ipfs, options),\n    // @ts-expect-error - types differ because we only invoke via websockets - https://github.com/ipfs/js-ipfs/issues/3594\n    write: grpcMfsWrite(ipfs, options)\n  })\n  server.addService(PubSub, {\n    // @ts-expect-error - types differ because we only invoke via websockets - https://github.com/ipfs/js-ipfs/issues/3594\n    subscribe: grpcPubsubSubscribe(ipfs, options),\n    // @ts-expect-error - types differ because we only invoke via websockets - https://github.com/ipfs/js-ipfs/issues/3594\n    unsubscribe: grpcPubsubUnsubscribe(ipfs, options)\n  })\n\n  const socket = options.socket || await webSocketServer(ipfs, options)\n\n  socket.on('error', (error) => log(error))\n\n  socket.on('data', async ({ path, metadata, channel }) => {\n    // @ts-expect-error - types differ because we only invoke via websockets - https://github.com/ipfs/js-ipfs/issues/3594\n    const handler = server.handlers.get(path)\n\n    if (!handler) {\n      channel.end(new Error(`Request path ${path} unimplemented`))\n      return\n    }\n\n    channel.handler = handler\n\n    switch (handler.type) {\n      case 'bidi':\n        handler.func(channel.source, channel.sink, metadata)\n          // @ts-expect-error - TODO: fix after https://github.com/ipfs/js-ipfs/issues/3594\n          .catch(err => {\n            channel.end(err)\n          })\n\n        channel.sendMetadata({})\n\n        for await (const output of channel.sink) {\n          channel.sendMessage(output)\n        }\n\n        channel.end()\n\n        break\n      case 'unary':\n        // @ts-expect-error - TODO: fix after https://github.com/ipfs/js-ipfs/issues/3594\n        handler.func(await first(channel.source), metadata, (err, value, metadata, flags) => {\n          if (err) {\n            return channel.end(err)\n          }\n\n          channel.sendMetadata(metadata || {})\n\n          if (value) {\n            channel.sendMessage(value)\n          }\n\n          channel.end()\n        })\n        break\n      case 'clientStream':\n        // @ts-expect-error - TODO: fix after https://github.com/ipfs/js-ipfs/issues/3594\n        handler.func(channel.source, metadata, (err, value, metadata, flags) => {\n          if (err) {\n            return channel.end(err)\n          }\n\n          channel.sendMetadata(metadata || {})\n\n          if (value) {\n            channel.sendMessage(value)\n          }\n\n          channel.end()\n        })\n        break\n      case 'serverStream':\n        handler.func(await first(channel.source), channel.sink, metadata)\n          .catch((/** @type {any} **/ err) => {\n            channel.end(err)\n          })\n\n        channel.sendMetadata({})\n\n        for await (const output of channel.sink) {\n          channel.sendMessage(output)\n        }\n\n        channel.end()\n\n        break\n      default:\n        log(`Invalid handler type ${handler.type}`)\n    }\n  })\n\n  return socket\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-server/src/types.ts",
    "content": "import type { Pushable } from 'it-pushable'\nimport type { EventEmitter } from 'events'\nimport type { Multiaddr } from '@multiformats/multiaddr'\n\nexport interface Options {\n  socket?: WebsocketServer\n}\n\nexport interface UnaryEndpoint<InputMessage, OutputMessage, Metadata> { (input: InputMessage, metadata: Metadata): Promise<OutputMessage> }\nexport interface BidirectionalStreamingEndpoint<InputMessage, OutputMessage, Metadata> { (source: AsyncIterable<InputMessage>, sink: Pushable<OutputMessage>, metadata: Metadata): Promise<void> }\nexport interface ClientStreamingEndpoint<InputMessage, OutputMessage, Metadata> { (source: AsyncIterable<InputMessage>, metadata: Metadata): Promise<OutputMessage> }\nexport interface ServerStreamingEndpoint<InputMessage, OutputMessage, Metadata> { (input: InputMessage, sink: Pushable<OutputMessage>, metadata: Metadata): Promise<void> }\n\nexport interface WebsocketMessage {\n  path: string\n  metadata: any\n  channel: any\n}\n\nexport interface WebsocketServerInfo {\n  uri: string\n  ma: Multiaddr\n}\n\nexport interface WebsocketServer extends EventEmitter {\n  // events\n  on: ((event: 'error', listener: (err: Error) => void) => this) & ((event: 'data', listener: (message: WebsocketMessage) => void) => this)\n  stop: () => Promise<void>\n  info: WebsocketServerInfo\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-server/src/utils/encode-mtime.js",
    "content": "\n/**\n * @param {import('ipfs-unixfs').Mtime} [mtime]\n */\nexport function encodeMtime (mtime) {\n  const output = {}\n\n  if (!mtime) {\n    return output\n  }\n\n  const {\n    secs,\n    nsecs\n  } = mtime\n\n  if (secs != null) {\n    output.mtime = secs\n\n    if (nsecs != null) {\n      output.mtime_nsecs = nsecs\n    }\n  }\n\n  return output\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-server/src/utils/load-services.js",
    "content": "import protocol from 'ipfs-grpc-protocol'\nimport protobuf from 'protobufjs/light.js'\n\nconst { Service } = protobuf\n\nconst CONVERSION_OPTS = {\n  keepCase: false,\n  longs: String, // long.js is required\n  enums: String,\n  defaults: false,\n  oneofs: true\n}\n\nexport function loadServices () {\n  // @ts-expect-error - recent protobufjs release changed the types\n  const root = protobuf.Root.fromJSON(protocol)\n\n  /** @type {Record<string, any>} */\n  const output = {}\n\n  Object\n    // @ts-expect-error\n    .keys(root.nested.ipfs)\n    // @ts-expect-error\n    .filter(key => root.nested.ipfs[key] instanceof Service)\n    // @ts-expect-error\n    .map(key => root.nested.ipfs[key])\n    .forEach(service => {\n      /** @type {Record<string, any>} */\n      const serviceDef = {}\n\n      output[service.name] = serviceDef\n\n      Object.keys(service.methods)\n        .forEach(methodName => {\n          const method = service.methods[methodName].resolve()\n\n          serviceDef[methodName] = {\n            path: `/ipfs.${service.name}/${methodName}`,\n            requestStream: method.requestStream,\n            responseStream: method.responseStream,\n            /**\n             * @param {*} obj\n             */\n            requestSerialize: (obj) => {\n              const message = method.resolvedRequestType.fromObject(obj)\n              return method.resolvedRequestType.encode(message).finish()\n            },\n            /**\n             * @param {Uint8Array} buf\n             */\n            requestDeserialize: (buf) => {\n              const message = method.resolvedRequestType.decode(buf)\n              const obj = method.resolvedRequestType.toObject(message, CONVERSION_OPTS)\n\n              Object.defineProperty(obj, 'toObject', {\n                enumerable: false,\n                configurable: false,\n                value: () => obj\n              })\n\n              return obj\n            },\n            /**\n             * @param {any} obj\n             */\n            responseSerialize: (obj) => {\n              const message = method.resolvedResponseType.fromObject(obj)\n              return method.resolvedResponseType.encode(message).finish()\n            },\n            /**\n             * @param {Uint8Array} buf\n             */\n            responseDeserialize: (buf) => {\n              const message = method.resolvedResponseType.decode(buf)\n              const obj = method.resolvedResponseType.toObject(message, CONVERSION_OPTS)\n\n              Object.defineProperty(obj, 'toObject', {\n                enumerable: false,\n                configurable: false,\n                value: () => obj\n              })\n\n              return obj\n            }\n          }\n        })\n    })\n\n  return output\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-server/src/utils/web-socket-message-channel.js",
    "content": "import { pushable } from 'it-pushable'\nimport { paramCase } from 'change-case'\n\nconst WebsocketSignal = {\n  START_SEND: 0,\n  FINISH_SEND: 1\n}\n\nconst HEADER_SIZE = 5\nconst TRAILER_BYTES = 0x80\n\n/**\n * @param {Record<string, any>} object - key/value pairs to turn into HTTP headers\n * @returns {Uint8Array} - HTTP headers\n **/\nconst objectToHeaders = (object) => {\n  /** @type {Record<string, any>} */\n  const output = {}\n\n  Object.keys(object).forEach(key => {\n    if (typeof object[key] === 'function') {\n      return\n    }\n\n    output[paramCase(key)] = object[key]\n  })\n\n  return Buffer.from(\n    Object.entries(output)\n      .filter(([, value]) => value != null)\n      .map(([key, value]) => `${key}: ${JSON.stringify(value)}`)\n      .join('\\r\\n')\n  )\n}\n\nexport class WebSocketMessageChannel {\n  /**\n   * @param {import('ws')} ws\n   */\n  constructor (ws) {\n    this._ws = ws\n\n    this.handler = {\n      /**\n       * @param {Uint8Array} buf\n       */\n      deserialize: (buf) => ({}),\n      /**\n       * @param {any} message\n       */\n      serialize: (message) => Buffer.from([])\n    }\n\n    this.source = pushable({ objectMode: true })\n    this.sink = pushable({ objectMode: true })\n\n    ws.on('message', (buf) => {\n      if (!(buf instanceof Uint8Array)) {\n        this.source.end(new Error(`Incorrect message type received - expected Uint8Array, got ${typeof buf}`))\n        this.sink.end()\n        ws.terminate()\n\n        return\n      }\n\n      const flag = buf[0]\n\n      if (flag === WebsocketSignal.FINISH_SEND) {\n        this.source.end()\n\n        return\n      }\n\n      let offset = 1\n\n      if (buf.length < (HEADER_SIZE + offset)) {\n        return\n      }\n\n      const header = buf.subarray(offset, HEADER_SIZE + offset)\n      const length = header.readUInt32BE(1)\n      offset += HEADER_SIZE\n\n      if (buf.length < (length + offset)) {\n        return\n      }\n\n      const message = buf.subarray(offset, offset + length)\n      const deserialized = this.handler.deserialize(message)\n      this.source.push(deserialized)\n    })\n\n    ws.once('end', () => {\n      this.source.end()\n      this.sink.end()\n    })\n  }\n\n  /**\n   * @param {Record<string, any>} metadata\n   */\n  sendMetadata (metadata) {\n    this._ws.send(objectToHeaders(metadata))\n  }\n\n  /**\n   * @param {object} message - A message object to send to the client\n   */\n  sendMessage (message) {\n    const response = this.handler.serialize(message)\n\n    const header = new DataView(new ArrayBuffer(HEADER_SIZE))\n    header.setUint32(1, response.byteLength)\n\n    this._ws.send(\n      Buffer.concat([\n        new Uint8Array(header.buffer, header.byteOffset, header.byteLength),\n        response\n      ], header.byteLength + response.byteLength)\n    )\n\n    this.sendTrailer()\n  }\n\n  /**\n   * @param {Error & { code?: string }} [err]\n   */\n  sendTrailer (err) {\n    const trailerBuffer = objectToHeaders({\n      'grpc-status': err ? 1 : 0,\n      'grpc-message': err ? err.message : undefined,\n      'grpc-stack': err ? err.stack : undefined,\n      'grpc-code': err ? err.code : undefined\n    })\n\n    const trailer = new DataView(new ArrayBuffer(HEADER_SIZE))\n    trailer.setUint8(0, TRAILER_BYTES)\n    trailer.setUint32(1, trailerBuffer.byteLength)\n\n    this._ws.send(\n      Buffer.concat([\n        new Uint8Array(trailer.buffer, trailer.byteOffset, trailer.byteLength),\n        trailerBuffer\n      ], trailer.byteLength + trailerBuffer.byteLength)\n    )\n  }\n\n  /**\n   * @param {Error} [err]\n   */\n  end (err) {\n    this.sendTrailer(err)\n    this.source.end()\n    this.sink.end()\n    this._ws.close()\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-server/src/utils/web-socket-server.js",
    "content": "import { WebSocketServer } from 'ws'\nimport { EventEmitter } from 'events'\nimport { WebSocketMessageChannel } from './web-socket-message-channel.js'\nimport { logger } from '@libp2p/logger'\n// @ts-expect-error - no types\nimport coerce from 'coercer'\nimport { camelCase } from 'change-case'\nimport { multiaddr } from '@multiformats/multiaddr'\n\nconst log = logger('ipfs:grpc-server:utils:web-socket-server')\n\n/**\n * @param {import('ws').Data} buf - e.g. `Buffer.from('foo-bar: baz\\r\\n')`\n * @returns {Record<string, any>} - e.g. `{ foorBar: 'baz' }`\n **/\nconst fromHeaders = (buf) => {\n  const headers = buf.toString('utf8')\n    .trim()\n    .split('\\r\\n')\n    .map(s => s.split(':').map(s => s.trim()))\n    .reduce((/** @type {Record<string, any> } */ acc, curr) => {\n      if (curr[0] !== 'content-type' && curr[0] !== 'x-grpc-web') {\n        acc[camelCase(curr[0])] = curr[1]\n      }\n\n      return acc\n    }, {})\n\n  return coerce(headers)\n}\n\nclass Messages extends EventEmitter {\n  /**\n   * @param {WebSocketServer} wss\n   */\n  constructor (wss) {\n    super()\n\n    this._wss = wss\n    this.multiaddr = ''\n\n    this.info = {\n      uri: '',\n      ma: multiaddr('/ip4/127.0.0.1/tcp/0/ws')\n    }\n\n    wss.on('connection', (ws, request) => {\n      ws.on('error', error => log(`WebSocket Error: ${error.stack}`))\n\n      ws.once('message', (buf) => {\n        const path = request.url\n        const metadata = fromHeaders(buf)\n        const channel = new WebSocketMessageChannel(ws)\n\n        this.emit('data', {\n          path,\n          metadata,\n          channel\n        })\n      })\n    })\n\n    wss.on('error', error => this.emit('error', error))\n  }\n\n  /**\n   * @returns {Promise<void>}\n   */\n  stop () {\n    return new Promise((resolve, reject) => {\n      this._wss.close((err) => {\n        if (err) {\n          return reject(err)\n        }\n        resolve()\n      })\n    })\n  }\n\n  ready () {\n    return new Promise((resolve) => {\n      this._wss.on('listening', () => {\n        const info = this._wss.address()\n\n        if (typeof info === 'string') {\n          // this is only the case when a net.Server is listening on a pipe or a unix domain socket\n          // which is not how this server runs: https://nodejs.org/dist/latest-v15.x/docs/api/net.html#net_server_address\n          this.info = {\n            uri: info,\n            ma: multiaddr(info)\n          }\n        } else {\n          this.info = {\n            uri: `http://${info.address}:${info.port}`,\n            ma: multiaddr(`/ip4/${info.address}/tcp/${info.port}/ws`)\n          }\n        }\n\n        resolve(this)\n      })\n    })\n  }\n}\n\n/**\n * @param {import('ipfs-core-types').IPFS} ipfs\n * @param {any} options\n * @returns {Promise<import('../types').WebsocketServer>}\n */\nexport async function webSocketServer (ipfs, options = {}) {\n  const config = await ipfs.config.getAll()\n  const grpcAddr = config.Addresses?.RPC\n\n  if (!grpcAddr) {\n    throw new Error('No gRPC address configured, please set an Addresses.RPC key in your IPFS config')\n  }\n\n  const [,, host, , port] = grpcAddr.split('/')\n\n  log(`starting ws server on ${host}:${port}`)\n\n  const wss = new WebSocketServer({\n    host,\n    port: parseInt(port, 10)\n  })\n\n  const messages = new Messages(wss)\n\n  return messages.ready()\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-server/test/add.spec.js",
    "content": "/* eslint-env mocha */\n\nimport sinon from 'sinon'\nimport { server } from './utils/server.js'\nimport { expect } from 'aegir/chai'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport all from 'it-all'\nimport drain from 'it-drain'\n\ndescribe('Root.add', () => {\n  let ipfs\n  let socket\n\n  before(() => {\n    ipfs = {\n      addAll: sinon.stub()\n    }\n    socket = server({ ipfs })\n  })\n\n  it('should add files', async () => {\n    const path1 = '/path/file-1.txt'\n    const cid1 = 'cid-1'\n    const path2 = '/path/file-1.txt'\n    const cid2 = 'cid-2'\n\n    const results = [{\n      type: 'RESULT',\n      path: path1,\n      cid: cid1\n    }, {\n      type: 'RESULT',\n      path: path2,\n      cid: cid2\n    }]\n\n    ipfs.addAll.returns(results)\n\n    const requests = [\n      { index: 1, type: 'FILE', path: path1, content: uint8ArrayFromString('hello world') },\n      { index: 1, type: 'FILE', path: path1 },\n      { index: 2, type: 'FILE', path: path2, content: uint8ArrayFromString('hello world') },\n      { index: 2, type: 'FILE', path: path2 }\n    ]\n\n    const channel = socket.send('/ipfs.Root/add', {})\n    requests.forEach(request => channel.clientSend(request))\n    channel.clientEnd()\n\n    await expect(all(channel.clientSink)).to.eventually.deep.equal(results)\n  })\n\n  it('should propagate error when adding files', async () => {\n    const path = '/path'\n    const err = new Error('halp!')\n\n    ipfs.addAll.throws(err)\n\n    const channel = socket.send('/ipfs.Root/add', {})\n    channel.clientSend({\n      index: 1,\n      type: 'DIRECTORY',\n      path\n    })\n    channel.clientEnd()\n\n    await expect(drain(channel.clientSink)).to.eventually.be.rejectedWith(/halp!/)\n  })\n\n  it('should propagate async error when adding files', async () => {\n    const path = '/path'\n    const err = new Error('halp!')\n\n    ipfs.addAll.returns(async function * () {\n      yield {\n        type: 'file',\n        name: 'name',\n        cid: 'cid'\n      }\n      await Promise.resolve(true)\n      throw err\n    }())\n\n    const channel = socket.send('/ipfs.Root/add', {})\n    channel.clientSend({\n      index: 1,\n      type: 'DIRECTORY',\n      path\n    })\n    channel.clientEnd()\n\n    await expect(drain(channel.clientSink)).to.eventually.be.rejectedWith(/halp!/)\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-grpc-server/test/id.spec.js",
    "content": "/* eslint-env mocha */\n\nimport sinon from 'sinon'\nimport { server } from './utils/server.js'\nimport { expect } from 'aegir/chai'\nimport all from 'it-all'\nimport drain from 'it-drain'\nimport { peerIdFromString } from '@libp2p/peer-id'\n\ndescribe('Root.id', () => {\n  let ipfs\n  let socket\n\n  beforeEach(() => {\n    ipfs = {\n      id: sinon.stub()\n    }\n    socket = server({ ipfs })\n  })\n\n  it('should get the node id', async () => {\n    const id = 'hello world ' + Math.random()\n\n    ipfs.id.withArgs({\n      peerId: undefined\n    }).resolves(id)\n\n    const channel = socket.send('/ipfs.Root/id', {})\n    channel.clientSend({})\n    channel.clientEnd()\n\n    const messages = await all(channel.clientSink)\n    expect(messages).to.have.lengthOf(1)\n    expect(messages).to.have.nested.property('[0]', id)\n  })\n\n  it('should get a different node id', async () => {\n    const peerId = peerIdFromString('Qma5z9bmwPcrWLJxX6Vj6BrcybaFg84c2riNbUKrSVf8h1')\n    const id = 'hello world ' + Math.random()\n\n    ipfs.id.withArgs({\n      peerId\n    }).resolves(id)\n\n    const channel = socket.send('/ipfs.Root/id', {})\n    channel.clientSend({\n      peerId: peerId.toString()\n    })\n    channel.clientEnd()\n\n    const messages = await all(channel.clientSink)\n    expect(messages).to.have.lengthOf(1)\n    expect(messages).to.have.nested.property('[0]', id)\n  })\n\n  it('should propagate error when getting id', async () => {\n    const err = new Error('halp!')\n\n    ipfs.id.rejects(err)\n\n    const channel = socket.send('/ipfs.Root/id', {})\n    channel.clientSend({})\n    channel.clientEnd()\n\n    await expect(drain(channel.clientSink)).to.eventually.be.rejectedWith(/halp!/)\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-grpc-server/test/mfs/ls.spec.js",
    "content": "/* eslint-env mocha */\n\nimport sinon from 'sinon'\nimport { server } from '../utils/server.js'\nimport { expect } from 'aegir/chai'\nimport all from 'it-all'\nimport drain from 'it-drain'\n\ndescribe('MFS.ls', () => {\n  let ipfs\n  let socket\n\n  before(() => {\n    ipfs = {\n      files: {\n        ls: sinon.stub()\n      }\n    }\n    socket = server({ ipfs })\n  })\n\n  it('should list files', async () => {\n    const path = '/path'\n    const results = [{\n      type: 'file',\n      name: 'file',\n      cid: 'cid-1'\n    }, {\n      type: 'directory',\n      name: 'dir',\n      cid: 'cid-2'\n    }]\n\n    ipfs.files.ls.withArgs(path).returns(results)\n\n    const channel = socket.send('/ipfs.MFS/ls', {})\n    channel.clientSend({ path })\n    channel.clientEnd()\n\n    await expect(all(channel.clientSink)).to.eventually.deep.equal(results.map(result => ({\n      ...result,\n      type: result.type.toUpperCase()\n    })))\n  })\n\n  it('should propagate error when listing files', async () => {\n    const path = '/path'\n    const err = new Error('halp!')\n\n    ipfs.files.ls.withArgs(path).throws(err)\n\n    const channel = socket.send('/ipfs.MFS/ls', {})\n    channel.clientSend({ path })\n    channel.clientEnd()\n\n    await expect(drain(channel.clientSink)).to.eventually.be.rejectedWith(/halp!/)\n  })\n\n  it('should propagate async error when listing files', async () => {\n    const path = '/path'\n    const err = new Error('halp!')\n\n    ipfs.files.ls.withArgs(path).returns(async function * () {\n      yield {\n        type: 'file',\n        name: 'name',\n        cid: 'cid'\n      }\n      await Promise.resolve(true)\n      throw err\n    }())\n\n    const channel = socket.send('/ipfs.MFS/ls', {})\n    channel.clientSend({ path })\n    channel.clientEnd()\n\n    await expect(drain(channel.clientSink)).to.eventually.be.rejectedWith(/halp!/)\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-grpc-server/test/mfs/write.spec.js",
    "content": "/* eslint-env mocha */\n\nimport sinon from 'sinon'\nimport { server } from '../utils/server.js'\nimport { expect } from 'aegir/chai'\nimport drain from 'it-drain'\n\ndescribe('MFS.write', () => {\n  let ipfs\n  let socket\n\n  before(() => {\n    ipfs = {\n      files: {\n        write: sinon.stub()\n      }\n    }\n    socket = server({ ipfs })\n  })\n\n  it('should write a file', async () => {\n    const path = '/path'\n\n    const channel = socket.send('/ipfs.MFS/write', {})\n    channel.clientSend({ path })\n    channel.clientEnd()\n\n    await drain(channel.clientSink)\n\n    expect(ipfs.files.write.calledWith(path)).to.be.true()\n  })\n\n  it('should propagate error when writing files', async () => {\n    const path = '/path'\n    const err = new Error('halp!')\n\n    ipfs.files.write.withArgs(path).throws(err)\n\n    const channel = socket.send('/ipfs.MFS/write', {})\n    channel.clientSend({ path })\n    channel.clientEnd()\n\n    await expect(drain(channel.clientSink)).to.eventually.be.rejectedWith(/halp!/)\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-grpc-server/test/utils/channel.js",
    "content": "\nimport { pushable } from 'it-pushable'\n\nclass MessageChannel {\n  constructor () {\n    this.source = pushable({ objectMode: true })\n    this.sink = pushable({ objectMode: true })\n\n    this.clientSink = pushable({ objectMode: true })\n  }\n\n  sendMetadata (metadata) {\n    this.metadata = metadata\n  }\n\n  sendMessage (message) {\n    setTimeout(() => {\n      this.clientSink.push(message)\n    }, 0)\n  }\n\n  sendTrailers (trailers) {\n    this.trailers = trailers\n  }\n\n  end (error) {\n    setTimeout(() => {\n      this.clientSink.end(error)\n    }, 0)\n  }\n\n  clientSend (message) {\n    setTimeout(() => {\n      this.source.push(message)\n    }, 0)\n  }\n\n  clientEnd (err) {\n    setTimeout(() => {\n      this.source.end(err)\n    }, 0)\n  }\n}\n\nexport function createChannel () {\n  return new MessageChannel()\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-server/test/utils/server.js",
    "content": "\nimport { createServer } from '../../src/index.js'\nimport { EventEmitter } from 'events'\nimport { createChannel } from './channel.js'\n\nexport function server ({ ipfs, options }) {\n  const socket = new EventEmitter()\n\n  createServer(ipfs, {\n    socket\n  })\n\n  return {\n    send: (path, metadata) => {\n      const channel = createChannel()\n\n      socket.emit('data', { path, metadata, channel })\n\n      return channel\n    }\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-grpc-server/tsconfig.json",
    "content": "{\n  \"extends\": \"aegir/src/config/tsconfig.aegir.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"emitDeclarationOnly\": true\n  },\n  \"include\": [\n    \"src\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../ipfs-core\"\n    },\n    {\n      \"path\": \"../ipfs-core-types\"\n    },\n    {\n      \"path\": \"../ipfs-grpc-protocol\"\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/.aegir.js",
    "content": "import { createServer } from 'ipfsd-ctl'\nimport getPort from 'aegir/get-port'\n\n/** @type {import('aegir').PartialOptions} */\nexport default {\n  build: {\n    bundlesizeMax: '66KB'\n  },\n  test: {\n    async before (options) {\n      const port = await getPort()\n      const server = createServer({\n        host: '127.0.0.1',\n        port: port\n      }, {\n        type: 'go',\n        ipfsHttpModule: await import('./src/index.js'),\n        ipfsBin: (await import('go-ipfs')).default.path()\n      })\n\n      await server.start()\n      return {\n        server,\n        env: {\n          IPFSD_SERVER: `http://${server.host}:${server.port}`\n        }\n      }\n    },\n    async after (options, before) {\n      await before.server.stop()\n    }\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/CHANGELOG.md",
    "content": "# Change Log\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n\n### [60.0.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-client-v60.0.0...ipfs-http-client-v60.0.1) (2023-05-25)\n\n\n### Bug Fixes\n\n* add deprecation notice to readmes ([#4362](https://www.github.com/ipfs/js-ipfs/issues/4362)) ([7b79c1b](https://www.github.com/ipfs/js-ipfs/commit/7b79c1b8df5c818dc124b346ea28330455732d5c))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.14.0 to ^0.14.1\n    * ipfs-core-utils bumped from ^0.18.0 to ^0.18.1\n\n## [60.0.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-client-v59.0.0...ipfs-http-client-v60.0.0) (2023-01-11)\n\n\n### ⚠ BREAKING CHANGES\n\n* update multiformats to v11.x.x and related depenendcies (#4277)\n\n### Bug Fixes\n\n* update multiformats to v11.x.x and related depenendcies ([#4277](https://www.github.com/ipfs/js-ipfs/issues/4277)) ([521c84a](https://www.github.com/ipfs/js-ipfs/commit/521c84a958b04d61702577a5adce28519c1b2a3b))\n* use aegir to publish RCs ([#4284](https://www.github.com/ipfs/js-ipfs/issues/4284)) ([6d90cbf](https://www.github.com/ipfs/js-ipfs/commit/6d90cbf321a7dbf4b1084ba20f0c514dc08d8d0a))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.13.0 to ^0.14.0\n    * ipfs-core-utils bumped from ^0.17.0 to ^0.18.0\n\n## [59.0.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-client-v58.0.1...ipfs-http-client-v59.0.0) (2022-10-24)\n\n\n### ⚠ BREAKING CHANGES\n\n* ipfs is now bundled with libp2p@0.40.x which has different config\n\n### Features\n\n* upgrade libp2p to 0.40.x ([#4237](https://www.github.com/ipfs/js-ipfs/issues/4237)) ([0cee4a4](https://www.github.com/ipfs/js-ipfs/commit/0cee4a4c55767022584dcbade0b0b9b43326f9c9))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.12.1 to ^0.13.0\n    * ipfs-core-utils bumped from ^0.16.1 to ^0.17.0\n\n### [58.0.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-client-v58.0.0...ipfs-http-client-v58.0.1) (2022-09-21)\n\n\n### Bug Fixes\n\n* update @multiformats/multiadd to 11.0.0 ([2a830bf](https://www.github.com/ipfs/js-ipfs/commit/2a830bf58a5929fcce51dede871c99f62192fbda))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.12.0 to ^0.12.1\n    * ipfs-core-utils bumped from ^0.16.0 to ^0.16.1\n\n## [58.0.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-client-v57.0.3...ipfs-http-client-v58.0.0) (2022-09-06)\n\n\n### ⚠ BREAKING CHANGES\n\n* update to libp2p@0.38.x (#4151)\n\n### deps\n\n* update to libp2p@0.38.x ([#4151](https://www.github.com/ipfs/js-ipfs/issues/4151)) ([39dbf70](https://www.github.com/ipfs/js-ipfs/commit/39dbf708ec31b263115e44f420651fa4e056a89e))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.11.0 to ^0.12.0\n    * ipfs-core-utils bumped from ^0.15.0 to ^0.16.0\n\n### [57.0.3](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-client-v57.0.2...ipfs-http-client-v57.0.3) (2022-06-24)\n\n\n### Bug Fixes\n\n* make pubsub message types consistent ([#4145](https://www.github.com/ipfs/js-ipfs/issues/4145)) ([00bd3dd](https://www.github.com/ipfs/js-ipfs/commit/00bd3dd0bca7fc705e5e87272972f586d1f161e8))\n\n### [57.0.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-client-v57.0.1...ipfs-http-client-v57.0.2) (2022-06-22)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.11.0 to ^0.11.1\n    * ipfs-core-utils bumped from ^0.15.0 to ^0.15.1\n\n### [57.0.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-client-v57.0.0...ipfs-http-client-v57.0.1) (2022-06-01)\n\n\n### Bug Fixes\n\n* export ipfs-http-client types ([#4120](https://www.github.com/ipfs/js-ipfs/issues/4120)) ([764b4ad](https://www.github.com/ipfs/js-ipfs/commit/764b4adca756a4445686d30fd01029abfd7bf682))\n\n## [57.0.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-client-v56.0.3...ipfs-http-client-v57.0.0) (2022-05-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* This module is now ESM only and there return types of some methods have changed\n\n### Features\n\n* update to libp2p 0.37.x ([#4092](https://www.github.com/ipfs/js-ipfs/issues/4092)) ([74aee8b](https://www.github.com/ipfs/js-ipfs/commit/74aee8b3d78f233c3199a3e9a6c0ac628a31a433))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.3 to ^0.11.0\n    * ipfs-core-utils bumped from ^0.14.3 to ^0.15.0\n\n### [56.0.3](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-client-v56.0.2...ipfs-http-client-v56.0.3) (2022-04-20)\n\n\n### Bug Fixes\n\n* exclude fs from bundle ([#4076](https://www.github.com/ipfs/js-ipfs/issues/4076)) ([6c3cb73](https://www.github.com/ipfs/js-ipfs/commit/6c3cb73db7b46211c88431273f61f04463a4f80d))\n* upgrade dep of ipfs-utils ^9.0.2->^9.0.6 ([#4086](https://www.github.com/ipfs/js-ipfs/issues/4086)) ([8f7ce23](https://www.github.com/ipfs/js-ipfs/commit/8f7ce23c18be12bdc52b98bfccbd0a5a2a9c9f7e)), closes [#4080](https://www.github.com/ipfs/js-ipfs/issues/4080)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.2 to ^0.10.3\n    * ipfs-core-utils bumped from ^0.14.2 to ^0.14.3\n\n### [56.0.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-client-v56.0.1...ipfs-http-client-v56.0.2) (2022-03-01)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.1 to ^0.10.2\n    * ipfs-core-utils bumped from ^0.14.1 to ^0.14.2\n\n### [56.0.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-client-v56.0.0...ipfs-http-client-v56.0.1) (2022-02-06)\n\n\n### Bug Fixes\n\n* **dag:** replace custom dag walk with multiformats/traversal ([#3950](https://www.github.com/ipfs/js-ipfs/issues/3950)) ([596b1f4](https://www.github.com/ipfs/js-ipfs/commit/596b1f48a014083b1736e4ad7e746c652d2583b1))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.0 to ^0.10.1\n    * ipfs-core-utils bumped from ^0.14.0 to ^0.14.1\n\n## [56.0.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-client-v55.0.0...ipfs-http-client-v56.0.0) (2022-01-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* peerstore methods are now all async, the repo is migrated to v12\n* node 15+ is required\n\n### Features\n\n* add support for dag-jose codec ([#4028](https://www.github.com/ipfs/js-ipfs/issues/4028)) ([fbe1492](https://www.github.com/ipfs/js-ipfs/commit/fbe1492395ad98e620a872208530a3f8f61535a9))\n* libp2p async peerstore ([#4018](https://www.github.com/ipfs/js-ipfs/issues/4018)) ([a6b201a](https://www.github.com/ipfs/js-ipfs/commit/a6b201af2c3697430ab0ebe002dd573d185f1ac0))\n\n\n### Bug Fixes\n\n* remove abort-controller deps ([#4015](https://www.github.com/ipfs/js-ipfs/issues/4015)) ([902e887](https://www.github.com/ipfs/js-ipfs/commit/902e887e1acac87f607324fa7cb5ad4b14aefcf3))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.9.0 to ^0.10.0\n    * ipfs-core-utils bumped from ^0.13.0 to ^0.14.0\n\n\n## [55.0.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@54.0.2...ipfs-http-client@55.0.0) (2021-12-15)\n\n\n### Bug Fixes\n\n* **pubsub:** multibase in pubsub http rpc ([#3922](https://github.com/ipfs/js-ipfs/issues/3922)) ([6eeaca4](https://github.com/ipfs/js-ipfs/commit/6eeaca452c36fa13be42d704575c577e4ca938f1))\n* return nested value from dag.get ([#3966](https://github.com/ipfs/js-ipfs/issues/3966)) ([45ac973](https://github.com/ipfs/js-ipfs/commit/45ac9730d6484e8324acfbc3579fce052b8452d7)), closes [#3957](https://github.com/ipfs/js-ipfs/issues/3957)\n\n\n### chore\n\n* Bump @ipld/dag-cbor to v7 ([#3977](https://github.com/ipfs/js-ipfs/issues/3977)) ([73476f5](https://github.com/ipfs/js-ipfs/commit/73476f55e39ecfb01eb2b4880637aad658f51bc2))\n\n\n### Features\n\n* dht client ([#3947](https://github.com/ipfs/js-ipfs/issues/3947)) ([62d8ecb](https://github.com/ipfs/js-ipfs/commit/62d8ecbc723e693a2544e69172d99c576d187c23))\n* update DAG API to match go-ipfs@0.10 changes ([#3917](https://github.com/ipfs/js-ipfs/issues/3917)) ([38c01be](https://github.com/ipfs/js-ipfs/commit/38c01be03b4fd5f401cd9b698cfdb4237d835b01))\n\n\n### BREAKING CHANGES\n\n* **pubsub:** We had to make breaking changes to `pubsub` commands sent over HTTP RPC  to fix data corruption caused by topic names and payload bytes that included `\\n`. More details in https://github.com/ipfs/go-ipfs/issues/7939 and https://github.com/ipfs/go-ipfs/pull/8183\n* On decode of CBOR blocks, `undefined` values will be coerced to `null`\n* `ipfs.dag.put` no longer accepts a `format` arg, it is now `storeCodec` and `inputCodec`.  `'json'` has become `'dag-json'`, `'cbor'` has become `'dag-cbor'` and so on\n* The DHT API has been refactored to return async iterators of query events\n\n\n## [54.0.2](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@54.0.1...ipfs-http-client@54.0.2) (2021-11-24)\n\n**Note:** Version bump only for package ipfs-http-client\n\n\n\n\n\n## [54.0.1](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@54.0.0...ipfs-http-client@54.0.1) (2021-11-19)\n\n**Note:** Version bump only for package ipfs-http-client\n\n\n\n\n\n## [54.0.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@53.0.1...ipfs-http-client@54.0.0) (2021-11-12)\n\n\n### Bug Fixes\n\n* do not accept single items for ipfs.add ([#3900](https://github.com/ipfs/js-ipfs/issues/3900)) ([04e3cf3](https://github.com/ipfs/js-ipfs/commit/04e3cf3f46b585c4644cba70516f375e95361f52))\n\n\n### BREAKING CHANGES\n\n* errors will now be thrown if multiple items are passed to `ipfs.add` or single items to `ipfs.addAll` (n.b. you can still pass a list of a single item to `ipfs.addAll`)\n\n\n\n\n\n## [53.0.1](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@53.0.0...ipfs-http-client@53.0.1) (2021-09-28)\n\n**Note:** Version bump only for package ipfs-http-client\n\n\n\n\n\n## [53.0.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@52.0.5...ipfs-http-client@53.0.0) (2021-09-24)\n\n\n### Features\n\n* pull in new globSource ([#3889](https://github.com/ipfs/js-ipfs/issues/3889)) ([be4a542](https://github.com/ipfs/js-ipfs/commit/be4a5428ebc4b05a2edd9a91bf9df6416c1a8c2b))\n* switch to esm ([#3879](https://github.com/ipfs/js-ipfs/issues/3879)) ([9a40109](https://github.com/ipfs/js-ipfs/commit/9a40109632e5b4837eb77a2f57dbc77fbf1fe099))\n\n\n### BREAKING CHANGES\n\n* the globSource api has changed from `globSource(dir, opts)` to `globSource(dir, pattern, opts)`\n* There are no default exports and everything is now dual published as ESM/CJS\n\n\n\n\n\n## [52.0.5](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@52.0.4...ipfs-http-client@52.0.5) (2021-09-17)\n\n**Note:** Version bump only for package ipfs-http-client\n\n\n\n\n\n## [52.0.4](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@52.0.3...ipfs-http-client@52.0.4) (2021-09-17)\n\n**Note:** Version bump only for package ipfs-http-client\n\n\n\n\n\n## [52.0.3](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@52.0.2...ipfs-http-client@52.0.3) (2021-09-02)\n\n\n### Bug Fixes\n\n* declare types in .ts files ([#3840](https://github.com/ipfs/js-ipfs/issues/3840)) ([eba5fe6](https://github.com/ipfs/js-ipfs/commit/eba5fe6832858107b3e1ae02c99de674622f12b4))\n* remove client-side timeout from http rpc calls ([#3178](https://github.com/ipfs/js-ipfs/issues/3178)) ([f11220e](https://github.com/ipfs/js-ipfs/commit/f11220e00a12afed5ebbbd8b4c5134595aea735d)), closes [#3161](https://github.com/ipfs/js-ipfs/issues/3161)\n* remove use of instanceof for CID class ([#3847](https://github.com/ipfs/js-ipfs/issues/3847)) ([ebbb12d](https://github.com/ipfs/js-ipfs/commit/ebbb12db523c53ce8e4ddae5266cd9acb3504431))\n\n\n\n\n\n## [52.0.2](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@52.0.1...ipfs-http-client@52.0.2) (2021-08-25)\n\n**Note:** Version bump only for package ipfs-http-client\n\n\n\n\n\n## [52.0.1](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@52.0.0...ipfs-http-client@52.0.1) (2021-08-17)\n\n\n### Bug Fixes\n\n* pin nanoid version ([#3807](https://github.com/ipfs/js-ipfs/issues/3807)) ([474523a](https://github.com/ipfs/js-ipfs/commit/474523ab8702729f697843d433a7a08baf2d101f))\n\n\n\n\n\n## [52.0.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@51.0.1...ipfs-http-client@52.0.0) (2021-08-11)\n\n\n### Bug Fixes\n\n* return rate in/out as number ([#3798](https://github.com/ipfs/js-ipfs/issues/3798)) ([2f3df7a](https://github.com/ipfs/js-ipfs/commit/2f3df7a70fe94d6bdf20947854dc9d0b88cb759a)), closes [#3782](https://github.com/ipfs/js-ipfs/issues/3782)\n\n\n### Features\n\n* ed25519 keys by default ([#3693](https://github.com/ipfs/js-ipfs/issues/3693)) ([33fa734](https://github.com/ipfs/js-ipfs/commit/33fa7341c3baaf0926d887c071cc6fbce5ac49a8))\n* make ipfs.get output tarballs ([#3785](https://github.com/ipfs/js-ipfs/issues/3785)) ([1ad6001](https://github.com/ipfs/js-ipfs/commit/1ad60018d39d5b46c484756631e30e1989fd8eba))\n\n\n### BREAKING CHANGES\n\n* rateIn/rateOut are returned as numbers\n* the output type of `ipfs.get` has changed and the `recursive` option has been removed from `ipfs.ls` since it was not supported everywhere\n\n\n\n\n\n## [51.0.1](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@51.0.0...ipfs-http-client@51.0.1) (2021-07-30)\n\n**Note:** Version bump only for package ipfs-http-client\n\n\n\n\n\n## [51.0.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@50.1.2...ipfs-http-client@51.0.0) (2021-07-27)\n\n\n### Bug Fixes\n\n* export ipfs http client type and use option extension for client ([#3763](https://github.com/ipfs/js-ipfs/issues/3763)) ([31bddd4](https://github.com/ipfs/js-ipfs/commit/31bddd40ab85848cd283ec66001fb7555b4f2d88)), closes [#3749](https://github.com/ipfs/js-ipfs/issues/3749) [#3736](https://github.com/ipfs/js-ipfs/issues/3736)\n\n\n### Features\n\n* implement dag import/export ([#3728](https://github.com/ipfs/js-ipfs/issues/3728)) ([700765b](https://github.com/ipfs/js-ipfs/commit/700765be2634fa5d2d71d8b87cf68c9cd328d2c4)), closes [#2953](https://github.com/ipfs/js-ipfs/issues/2953) [#2745](https://github.com/ipfs/js-ipfs/issues/2745)\n* upgrade to the new multiformats ([#3556](https://github.com/ipfs/js-ipfs/issues/3556)) ([d13d15f](https://github.com/ipfs/js-ipfs/commit/d13d15f022a87d04a35f0f7822142f9cb898479c))\n\n\n### BREAKING CHANGES\n\n* ipld-formats no longer supported, use multiformat BlockCodecs instead\n\nCo-authored-by: Rod Vagg <rod@vagg.org>\nCo-authored-by: achingbrain <alex@achingbrain.net>\n\n\n\n\n\n## [50.1.2](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@50.1.1...ipfs-http-client@50.1.2) (2021-06-18)\n\n**Note:** Version bump only for package ipfs-http-client\n\n\n\n\n\n## [50.1.1](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@50.1.0...ipfs-http-client@50.1.1) (2021-06-05)\n\n\n### Bug Fixes\n\n* add onError to pubsub.subscribe types ([#3706](https://github.com/ipfs/js-ipfs/issues/3706)) ([d910aea](https://github.com/ipfs/js-ipfs/commit/d910aead8c8be6798cf838245511331b3f69634c)), closes [#3468](https://github.com/ipfs/js-ipfs/issues/3468)\n* stalling subscription on (node) http-client when daemon is stopped ([#3468](https://github.com/ipfs/js-ipfs/issues/3468)) ([0266abf](https://github.com/ipfs/js-ipfs/commit/0266abf0c4b817636172f78c6e91eb4dd5aad451)), closes [#3465](https://github.com/ipfs/js-ipfs/issues/3465)\n\n\n\n\n\n## [50.1.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@50.0.0...ipfs-http-client@50.1.0) (2021-05-26)\n\n\n### Features\n\n* allow passing the id of a network peer to ipfs.id ([#3386](https://github.com/ipfs/js-ipfs/issues/3386)) ([00fd709](https://github.com/ipfs/js-ipfs/commit/00fd709a7b71e7cf354ea452ebce460dd7375d34))\n\n\n\n\n\n## [50.0.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@49.0.4...ipfs-http-client@50.0.0) (2021-05-10)\n\n\n### Bug Fixes\n\n* add missing type import ([#3664](https://github.com/ipfs/js-ipfs/issues/3664)) ([64cc1e1](https://github.com/ipfs/js-ipfs/commit/64cc1e1ea7da77f1553ac127e9fef1905f7c78da))\n* fix types ([#3662](https://github.com/ipfs/js-ipfs/issues/3662)) ([0fe8892](https://github.com/ipfs/js-ipfs/commit/0fe8892361180dab53ed3c3b006479b32a792d44))\n* loosen input type for swarm.connect and swarm.disconnect ([#3673](https://github.com/ipfs/js-ipfs/issues/3673)) ([46618c7](https://github.com/ipfs/js-ipfs/commit/46618c795bf5363ba3186645640fb81349231db7)), closes [#3638](https://github.com/ipfs/js-ipfs/issues/3638)\n* mark ipld options as partial ([#3669](https://github.com/ipfs/js-ipfs/issues/3669)) ([f98af8e](https://github.com/ipfs/js-ipfs/commit/f98af8ed24784929898bb5d33a64dc442c77074d))\n\n\n### chore\n\n* update node version in docker build ([#3603](https://github.com/ipfs/js-ipfs/issues/3603)) ([087fd1e](https://github.com/ipfs/js-ipfs/commit/087fd1eb402d1b933730e09c1d0cfb21067e9992))\n* upgrade deps with new typedefs ([#3550](https://github.com/ipfs/js-ipfs/issues/3550)) ([a418a52](https://github.com/ipfs/js-ipfs/commit/a418a521574c878d7aabd0ad2fd8d516908a3756))\n\n\n### BREAKING CHANGES\n\n* Minimum supported node version is 14\n* all core api methods now have types, some method signatures have changed, named exports are now used by the http, grpc and ipfs client modules\n\n\n\n\n\n## [49.0.4](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@49.0.3...ipfs-http-client@49.0.4) (2021-03-10)\n\n**Note:** Version bump only for package ipfs-http-client\n\n\n\n\n\n## [49.0.3](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@49.0.2...ipfs-http-client@49.0.3) (2021-03-09)\n\n\n### Bug Fixes\n\n* update to new aegir ([#3528](https://github.com/ipfs/js-ipfs/issues/3528)) ([49f7880](https://github.com/ipfs/js-ipfs/commit/49f78807d7e26483bd926b45cc7e0f797d77e41b))\n\n\n\n\n\n## [49.0.2](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@49.0.1...ipfs-http-client@49.0.2) (2021-02-08)\n\n**Note:** Version bump only for package ipfs-http-client\n\n\n\n\n\n## [49.0.1](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@49.0.0...ipfs-http-client@49.0.1) (2021-02-02)\n\n**Note:** Version bump only for package ipfs-http-client\n\n\n\n\n\n## [49.0.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@48.2.2...ipfs-http-client@49.0.0) (2021-02-01)\n\n\n### chore\n\n* update deps ([#3514](https://github.com/ipfs/js-ipfs/issues/3514)) ([061d77c](https://github.com/ipfs/js-ipfs/commit/061d77cc03f40af5a3bc3590481e1e5836e7f0d8))\n\n\n### Features\n\n* support  remote pinning services in ipfs-http-client ([#3293](https://github.com/ipfs/js-ipfs/issues/3293)) ([ba240fd](https://github.com/ipfs/js-ipfs/commit/ba240fdf93edc88028315483240d7822a7ca88ed))\n\n\n### BREAKING CHANGES\n\n* ipfs-repo upgrade requires repo migration to v10\n\n\n\n\n\n## [48.2.2](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@48.2.1...ipfs-http-client@48.2.2) (2021-01-22)\n\n**Note:** Version bump only for package ipfs-http-client\n\n\n\n\n\n## [48.2.1](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@48.2.0...ipfs-http-client@48.2.1) (2021-01-20)\n\n\n### Bug Fixes\n\n* use https agent for https requests ([#3490](https://github.com/ipfs/js-ipfs/issues/3490)) ([ac4bb48](https://github.com/ipfs/js-ipfs/commit/ac4bb4841ce7c191408e1b2bb906284ae0dbd975)), closes [#3474](https://github.com/ipfs/js-ipfs/issues/3474)\n\n\n\n\n\n## [48.2.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@48.1.3...ipfs-http-client@48.2.0) (2021-01-15)\n\n\n### Features\n\n* add grpc server and client ([#3403](https://github.com/ipfs/js-ipfs/issues/3403)) ([a9027e0](https://github.com/ipfs/js-ipfs/commit/a9027e0ec0cea9a4f34b4f2f52e09abb35237384)), closes [#2519](https://github.com/ipfs/js-ipfs/issues/2519) [#2838](https://github.com/ipfs/js-ipfs/issues/2838) [#2943](https://github.com/ipfs/js-ipfs/issues/2943) [#2854](https://github.com/ipfs/js-ipfs/issues/2854) [#2864](https://github.com/ipfs/js-ipfs/issues/2864)\n* allow passing a http.Agent to ipfs-http-client in node ([#3474](https://github.com/ipfs/js-ipfs/issues/3474)) ([fe93ba0](https://github.com/ipfs/js-ipfs/commit/fe93ba01a0c62cead7cc4e0023de2d2a00adbc02)), closes [/tools.ietf.org/html/rfc2616#section-8](https://github.com//tools.ietf.org/html/rfc2616/issues/section-8) [#3464](https://github.com/ipfs/js-ipfs/issues/3464)\n* allow passing a http.Agent to the grpc client ([#3477](https://github.com/ipfs/js-ipfs/issues/3477)) ([c5f0bc5](https://github.com/ipfs/js-ipfs/commit/c5f0bc5eeee15369b7d02901035b04184a8608d2)), closes [#3474](https://github.com/ipfs/js-ipfs/issues/3474)\n\n\n\n\n\n## [48.1.3](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@48.1.2...ipfs-http-client@48.1.3) (2020-12-16)\n\n\n### Bug Fixes\n\n* fix ipfs.ls() for a single file object ([#3440](https://github.com/ipfs/js-ipfs/issues/3440)) ([f243dd1](https://github.com/ipfs/js-ipfs/commit/f243dd1c37fcb9786d77d129cd9b238457d18a15))\n* regressions introduced by new releases of CID & multicodec ([#3442](https://github.com/ipfs/js-ipfs/issues/3442)) ([b5152d8](https://github.com/ipfs/js-ipfs/commit/b5152d8cc93ecc8d39fc353ea66d7eaf1661e3c0)), closes [/github.com/multiformats/js-cid/commit/0e11f035c9230e7f6d79c159ace9b80de88cb5eb#diff-25a6634263c1b1f6fc4697a04e2b9904ea4b042a89af59dc93ec1f5d44848a26](https://github.com//github.com/multiformats/js-cid/commit/0e11f035c9230e7f6d79c159ace9b80de88cb5eb/issues/diff-25a6634263c1b1f6fc4697a04e2b9904ea4b042a89af59dc93ec1f5d44848a26)\n\n\n\n\n\n## [48.1.2](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@48.1.1...ipfs-http-client@48.1.2) (2020-11-25)\n\n**Note:** Version bump only for package ipfs-http-client\n\n\n\n\n\n## [48.1.1](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@48.1.0...ipfs-http-client@48.1.1) (2020-11-16)\n\n\n### Bug Fixes\n\n* report ipfs.add progress over http ([#3310](https://github.com/ipfs/js-ipfs/issues/3310)) ([39cad4b](https://github.com/ipfs/js-ipfs/commit/39cad4b76b950ea6a76477fd01f8631b8bd9aa1e))\n\n\n\n\n\n## [48.1.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@48.0.0...ipfs-http-client@48.1.0) (2020-11-09)\n\n\n### Bug Fixes\n\n* typedef resolution & add examples that use types ([#3359](https://github.com/ipfs/js-ipfs/issues/3359)) ([dc2795a](https://github.com/ipfs/js-ipfs/commit/dc2795a4f3b515683d09967ce611bf87d5e67f86)), closes [#3356](https://github.com/ipfs/js-ipfs/issues/3356) [#3358](https://github.com/ipfs/js-ipfs/issues/3358)\n\n\n### Features\n\n* pass file name to add/addAll progress handler ([#3372](https://github.com/ipfs/js-ipfs/issues/3372)) ([69681a7](https://github.com/ipfs/js-ipfs/commit/69681a7d7a8434c11f6f10e370e324f5a3d31042)), closes [ipfs/js-ipfs-unixfs#87](https://github.com/ipfs/js-ipfs-unixfs/issues/87)\n\n\n\n\n\n## [48.0.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@47.0.1...ipfs-http-client@48.0.0) (2020-10-28)\n\n\n### Bug Fixes\n\n* disable cors by default ([#3275](https://github.com/ipfs/js-ipfs/issues/3275)) ([3ff833d](https://github.com/ipfs/js-ipfs/commit/3ff833db6444a3e931db9b76bf74c3420e57ee02))\n* do not double normalise input url ([#3351](https://github.com/ipfs/js-ipfs/issues/3351)) ([4eb196c](https://github.com/ipfs/js-ipfs/commit/4eb196c07129d0ee90a7ad55feca69b6b349d8b7)), closes [#3331](https://github.com/ipfs/js-ipfs/issues/3331)\n* files ls should return string ([#3352](https://github.com/ipfs/js-ipfs/issues/3352)) ([16ecc74](https://github.com/ipfs/js-ipfs/commit/16ecc7485dfbb1f0c827c5f804974bb804f3dafd)), closes [#3345](https://github.com/ipfs/js-ipfs/issues/3345) [#2939](https://github.com/ipfs/js-ipfs/issues/2939) [#3330](https://github.com/ipfs/js-ipfs/issues/3330) [#2948](https://github.com/ipfs/js-ipfs/issues/2948)\n* use fetch in electron renderer and electron-fetch in main ([#3251](https://github.com/ipfs/js-ipfs/issues/3251)) ([639d71f](https://github.com/ipfs/js-ipfs/commit/639d71f7ac8f66d9633e753a2a6be927e14a5af0))\n\n\n### Features\n\n* enable custom formats for dag put and get ([#3347](https://github.com/ipfs/js-ipfs/issues/3347)) ([3250ff4](https://github.com/ipfs/js-ipfs/commit/3250ff453a1d3275cc4ab746f59f9f70abd5cc5f))\n* type check & generate defs from jsdoc ([#3281](https://github.com/ipfs/js-ipfs/issues/3281)) ([bbcaf34](https://github.com/ipfs/js-ipfs/commit/bbcaf34111251b142273a5675f4754ff68bd9fa0))\n\n\n### BREAKING CHANGES\n\n* types returned by `ipfs.files.ls` are now strings, in line with the docs but different to previous behaviour\n\nCo-authored-by: Geoffrey Cohler <g.cohler@computer.org>\n* - CORS origins will need to be [configured manually](https://github.com/ipfs/js-ipfs/blob/master/packages/ipfs-http-client/README.md#cors) before use with ipfs-http-client\n\n\n\n\n\n## [47.0.1](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@47.0.0...ipfs-http-client@47.0.1) (2020-09-09)\n\n**Note:** Version bump only for package ipfs-http-client\n\n\n\n\n\n## [46.1.1](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@46.1.0...ipfs-http-client@46.1.1) (2020-09-04)\n\n**Note:** Version bump only for package ipfs-http-client\n\n\n\n\n\n## [46.1.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@46.0.1...ipfs-http-client@46.1.0) (2020-09-03)\n\n\n### Bug Fixes\n\n* handle progress for empty files ([#3260](https://github.com/ipfs/js-ipfs/issues/3260)) ([9c36cb8](https://github.com/ipfs/js-ipfs/commit/9c36cb8f0c122e78c3cda3d0769d66c4d380787a)), closes [#3255](https://github.com/ipfs/js-ipfs/issues/3255)\n\n\n### Features\n\n* add protocol list to ipfs id ([#3250](https://github.com/ipfs/js-ipfs/issues/3250)) ([1b6cf60](https://github.com/ipfs/js-ipfs/commit/1b6cf600a6b1348199457ca1fe6f314b6eff8c46))\n* add typeScript support ([#3236](https://github.com/ipfs/js-ipfs/issues/3236)) ([be26dd7](https://github.com/ipfs/js-ipfs/commit/be26dd723ed8c76efee149a993a8ade7f75f960e)), closes [#2945](https://github.com/ipfs/js-ipfs/issues/2945) [#1166](https://github.com/ipfs/js-ipfs/issues/1166)\n* add typescript support ([#3267](https://github.com/ipfs/js-ipfs/issues/3267)) ([6816bc6](https://github.com/ipfs/js-ipfs/commit/6816bc64ccb9bf852c2b9a26d9ddd19b9439dae6)), closes [#2945](https://github.com/ipfs/js-ipfs/issues/2945) [#1166](https://github.com/ipfs/js-ipfs/issues/1166)\n* ipns publish example ([#3207](https://github.com/ipfs/js-ipfs/issues/3207)) ([91faec6](https://github.com/ipfs/js-ipfs/commit/91faec6e3d89b0d9883b8d7815c276d44048e739))\n* store pins in datastore instead of a DAG ([#2771](https://github.com/ipfs/js-ipfs/issues/2771)) ([64b7fe4](https://github.com/ipfs/js-ipfs/commit/64b7fe41738cbe96d5a9075f0c01156c6f889c40))\n* update hapi to v20 ([#3245](https://github.com/ipfs/js-ipfs/issues/3245)) ([1aeef89](https://github.com/ipfs/js-ipfs/commit/1aeef89c73f42a2f6cceb7f0598400141ce40e23))\n\n\n\n\n\n## [46.0.1](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@46.0.0...ipfs-http-client@46.0.1) (2020-08-24)\n\n\n### Bug Fixes\n\n* validate ipns records with inline public keys ([#3224](https://github.com/ipfs/js-ipfs/issues/3224)) ([5cc0e08](https://github.com/ipfs/js-ipfs/commit/5cc0e086b036e7ba40b09768b67b7067adca43c1))\n\n\n\n\n\n## [46.0.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@45.0.0...ipfs-http-client@46.0.0) (2020-08-12)\n\n\n### Bug Fixes\n\n* support keychain without pass ([#3212](https://github.com/ipfs/js-ipfs/issues/3212)) ([7e0e85c](https://github.com/ipfs/js-ipfs/commit/7e0e85c2f003a09845b1dbe4200ca61366933b05))\n* **http-client:** allow stream option to be overridden ([#3205](https://github.com/ipfs/js-ipfs/issues/3205)) ([7aba835](https://github.com/ipfs/js-ipfs/commit/7aba8354134d0d1d8132892c338b474e20e56b76))\n* send blobs when running ipfs-http-client in the browser ([#3184](https://github.com/ipfs/js-ipfs/issues/3184)) ([6b24463](https://github.com/ipfs/js-ipfs/commit/6b24463431497bd13b579a730ad7063345729ad9)), closes [#3138](https://github.com/ipfs/js-ipfs/issues/3138)\n\n\n### Features\n\n* share IPFS node between browser tabs ([#3081](https://github.com/ipfs/js-ipfs/issues/3081)) ([1b8b1b8](https://github.com/ipfs/js-ipfs/commit/1b8b1b822a252498889c54972a1f57e1fedc39d0)), closes [#3022](https://github.com/ipfs/js-ipfs/issues/3022)\n\n\n### BREAKING CHANGES\n\n* remove support for key.export over the http api\n\n\n\n\n\n## [45.0.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@44.3.0...ipfs-http-client@45.0.0) (2020-07-16)\n\n\n### Bug Fixes\n\n* optional arguments go in the options object ([#3118](https://github.com/ipfs/js-ipfs/issues/3118)) ([8cb8c73](https://github.com/ipfs/js-ipfs/commit/8cb8c73037e44894d756b70f344b3282463206f9))\n* small whitespace change ([0d4604d](https://github.com/ipfs/js-ipfs/commit/0d4604dde995edb69483c03622e38448d31eeb88))\n* still load dag-pb, dag-cbor and raw when specifying custom formats ([#3132](https://github.com/ipfs/js-ipfs/issues/3132)) ([a96e3bc](https://github.com/ipfs/js-ipfs/commit/a96e3bc9e3763004beafc24b98efa85ffa665622)), closes [#3129](https://github.com/ipfs/js-ipfs/issues/3129)\n\n\n### Features\n\n* add interface and http client versions to version output ([#3125](https://github.com/ipfs/js-ipfs/issues/3125)) ([65f8b23](https://github.com/ipfs/js-ipfs/commit/65f8b23f550f939e94aaf6939894a513519e6d68)), closes [#2878](https://github.com/ipfs/js-ipfs/issues/2878)\n* store blocks by multihash instead of CID ([#3124](https://github.com/ipfs/js-ipfs/issues/3124)) ([03b17f5](https://github.com/ipfs/js-ipfs/commit/03b17f5e2d290e84aa0cb541079b79e468e7d1bd))\n\n\n### BREAKING CHANGES\n\n* - not really\n\n\n\n\n\n## [44.3.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@44.2.0...ipfs-http-client@44.3.0) (2020-06-24)\n\n\n### Features\n\n* add config.getAll ([#3071](https://github.com/ipfs/js-ipfs/issues/3071)) ([16587f1](https://github.com/ipfs/js-ipfs/commit/16587f16e1b3ae525c099b1975748510638aceee))\n* support loading arbitrary ipld formats in the http client ([#3073](https://github.com/ipfs/js-ipfs/issues/3073)) ([bd12773](https://github.com/ipfs/js-ipfs/commit/bd127730039ab79dd7ad22b31245939ee01a6514))\n\n\n\n\n\n## [44.2.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@44.1.1...ipfs-http-client@44.2.0) (2020-06-05)\n\n\n### Features\n\n* sync with go-ipfs 0.5 ([#3013](https://github.com/ipfs/js-ipfs/issues/3013)) ([0900bb9](https://github.com/ipfs/js-ipfs/commit/0900bb9b8123edb689a137a006c5507d8503f693))\n\n\n\n\n\n## [44.1.1](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@44.1.0...ipfs-http-client@44.1.1) (2020-05-29)\n\n**Note:** Version bump only for package ipfs-http-client\n\n\n\n\n\n## [44.1.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@44.0.3...ipfs-http-client@44.1.0) (2020-05-18)\n\n\n### Bug Fixes\n\n* fixes browser script tag example ([#3034](https://github.com/ipfs/js-ipfs/issues/3034)) ([ee8b769](https://github.com/ipfs/js-ipfs/commit/ee8b769b96f7e3c8414bbf85853ab4e21e8fd11c)), closes [#3027](https://github.com/ipfs/js-ipfs/issues/3027)\n* remove node globals ([#2932](https://github.com/ipfs/js-ipfs/issues/2932)) ([d0d2f74](https://github.com/ipfs/js-ipfs/commit/d0d2f74cef4e439c6d2baadba1f1f9f52534fcba))\n* typeof bug when passing timeout to dag.get ([#3035](https://github.com/ipfs/js-ipfs/issues/3035)) ([026a542](https://github.com/ipfs/js-ipfs/commit/026a5423e00992968840c9236afe47bdab9ee834))\n\n\n### Features\n\n* cancellable api calls ([#2993](https://github.com/ipfs/js-ipfs/issues/2993)) ([2b24f59](https://github.com/ipfs/js-ipfs/commit/2b24f590041a0df9da87b75ae2344232fe22fe3a)), closes [#3015](https://github.com/ipfs/js-ipfs/issues/3015)\n\n\n\n\n\n## [44.0.3](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@44.0.2...ipfs-http-client@44.0.3) (2020-05-05)\n\n**Note:** Version bump only for package ipfs-http-client\n\n\n\n\n\n## [44.0.2](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@44.0.1...ipfs-http-client@44.0.2) (2020-05-05)\n\n\n### Bug Fixes\n\n* pass headers to request ([#3018](https://github.com/ipfs/js-ipfs/issues/3018)) ([3ba00f8](https://github.com/ipfs/js-ipfs/commit/3ba00f8c6a8a057c5776d539a671a74d9565fb29)), closes [#3017](https://github.com/ipfs/js-ipfs/issues/3017)\n\n\n\n\n\n## [44.0.1](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@44.0.0...ipfs-http-client@44.0.1) (2020-04-28)\n\n\n### Bug Fixes\n\n* do not abort dht operation on error responses ([#3001](https://github.com/ipfs/js-ipfs/issues/3001)) ([a69f782](https://github.com/ipfs/js-ipfs/commit/a69f7828ccd77ab502689e45ed1112c9f804b386)), closes [#2991](https://github.com/ipfs/js-ipfs/issues/2991)\n* fix gc tests ([#3008](https://github.com/ipfs/js-ipfs/issues/3008)) ([9f7f03e](https://github.com/ipfs/js-ipfs/commit/9f7f03e1ea672834b7f984657c7d7d7c768bcd6c))\n\n\n\n\n\n## [44.0.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@43.0.1...ipfs-http-client@44.0.0) (2020-04-16)\n\n\n### Bug Fixes\n\n* make http api only accept POST requests ([#2977](https://github.com/ipfs/js-ipfs/issues/2977)) ([943d4a8](https://github.com/ipfs/js-ipfs/commit/943d4a8cf2d4c4ff5ecd4814c59cb0aae0cfa1fd))\n* pass timeout arg to server ([#2979](https://github.com/ipfs/js-ipfs/issues/2979)) ([049f085](https://github.com/ipfs/js-ipfs/commit/049f085fd206a1afb729fa825d8df38bf7aa8549))\n\n\n### BREAKING CHANGES\n\n* Where we used to accept all and any HTTP methods, now only POST is\naccepted.  The API client will now only send POST requests too.\n\n* test: add tests to make sure we are post-only\n\n* chore: upgrade ipfs-utils\n\n* fix: return 405 instead of 404 for bad methods\n\n* fix: reject browsers that do not send an origin\n\nAlso fixes running interface tests over http in browsers against\njs-ipfs\n\n\n\n\n\n## [43.0.1](https://github.com/ipfs/js-ipfs/compare/ipfs-http-client@43.0.0...ipfs-http-client@43.0.1) (2020-04-08)\n\n\n### Bug Fixes\n\n* dht.findPeer API endpoint returns ndjson ([#2965](https://github.com/ipfs/js-ipfs/issues/2965)) ([524ff32](https://github.com/ipfs/js-ipfs/commit/524ff32901e43460fe5b364bc4903ab249792874))\n\n\n\n\n\n# 43.0.0 (2020-03-31)\n\n\n### Bug Fixes\n\n* add default args for ipfs.add ([#2950](https://github.com/ipfs/js-ipfs/issues/2950)) ([a01f5b6](https://github.com/ipfs/js-ipfs/commit/a01f5b63cd92d225b10eff497f79caf4baab1973))\n* dont include util.textencoder in the browser ([#2919](https://github.com/ipfs/js-ipfs/issues/2919)) ([3207e3b](https://github.com/ipfs/js-ipfs/commit/3207e3b35c9c250332c03dd2a066e8ebcda35e43))\n\n\n### chore\n\n* move mfs and multipart files into core ([#2811](https://github.com/ipfs/js-ipfs/issues/2811)) ([82b9e08](https://github.com/ipfs/js-ipfs/commit/82b9e085330e6c6290e6f3dd29678247984ffdce))\n* update dep version and ignore interop test for raw leaves ([#2747](https://github.com/ipfs/js-ipfs/issues/2747)) ([6376cec](https://github.com/ipfs/js-ipfs/commit/6376cec2b4beccef4751c498088f600ec7788118))\n\n\n### Features\n\n* remove ky from http-client and utils ([#2810](https://github.com/ipfs/js-ipfs/issues/2810)) ([9bc9625](https://github.com/ipfs/js-ipfs/commit/9bc96252686d0bbbfdb2a3300bb17b80eafdaf00)), closes [#2801](https://github.com/ipfs/js-ipfs/issues/2801)\n\n\n### BREAKING CHANGES\n\n* When the path passed to `ipfs.files.stat(path)` was a hamt sharded dir, the resovled\nvalue returned by js-ipfs previously had a `type` property of with a value of\n`'hamt-sharded-directory'`.  To bring it in line with go-ipfs this value is now\n`'directory'`.\n* Files that fit into one block imported with either `--cid-version=1`\nor `--raw-leaves=true` previously returned a CID that resolved to\na raw node (e.g. a buffer). Returned CIDs now resolve to a `dag-pb`\nnode that contains a UnixFS entry. This is to allow setting metadata\non small files with CIDv1.\n\n\n\n\n\n<a name=\"42.0.0\"></a>\n## [42.0.0](https://github.com/ipfs/js-ipfs-http-client/compare/v42.0.0-pre.2...v42.0.0) (2020-02-04)\n\nThere are significant and breaking API changes in this release. Please see the [migration guide](https://gist.github.com/alanshaw/04b2ddc35a6fff25c040c011ac6acf26).\n\n### Bug Fixes\n\n* interface tests ([#1233](https://github.com/ipfs/js-ipfs-http-client/issues/1233)) ([d3eee0d](https://github.com/ipfs/js-ipfs-http-client/commit/d3eee0d))\n\n### Features\n\n* `add` results now include `mode` and `mtime` properties if they were set.\n\n* `files.chmod` has been added. See the [core interface docs](https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/SPEC/FILES.md#fileschmod) for info.\n\n*  `files.flush` now returns the root CID for the path that was flushed (`/` by default)\n\n* `files.ls` results now include `mode` and `mtime` properties if they were set. See the [core interface docs](https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/SPEC/FILES.md#ls) for more info.\n\n* `files.mkdir` now accepts `mode` and `mtime` options to allow setting mode and mtime metadata. See the [core interface docs](https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/SPEC/FILES.md#filesmkdir) for more info.\n\n* `files.stat` result now includes `mode` and `mtime` properties if they were set. See the [core interface docs](https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/SPEC/FILES.md#filesstat) for more info.\n\n* `files.touch` has been added. See the [core interface docs](https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/SPEC/FILES.md#filestouch) for info.\n\n* `files.write` now accepts `mode` and `mtime` options to allow setting mode and mtime metadata. See the [core interface docs](https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/SPEC/FILES.md#fileswrite) for more info.\n\n* `object.get` now accepts a `timeout` option. It will cause the method to throw with a `TimeoutError` if no data is received within the timeout window. It can be passed as a `number` or a `string`. If a `number` is passed it is interpreted as milliseconds, if a string is passed it is interpreted as a [human readable duration](https://www.npmjs.com/package/parse-duration).\n\n* `pin.add` now accepts a `timeout` option. It will cause the method to throw with a `TimeoutError` if no data is received within the timeout window. It can be passed as a `number` or a `string`. If a `number` is passed it is interpreted as milliseconds, if a string is passed it is interpreted as a [human readable duration](https://www.npmjs.com/package/parse-duration).\n\n* `refs` now accepts a `timeout` option. It will cause the method to throw with a `TimeoutError` if no data is received within the timeout window. It can be passed as a `number` or a `string`. If a `number` is passed it is interpreted as milliseconds, if a string is passed it is interpreted as a [human readable duration](https://www.npmjs.com/package/parse-duration).\n\n### BREAKING CHANGES\n\n* Callbacks are no longer supported on any API methods. Please use a utility such as [`callbackify`](https://www.npmjs.com/package/callbackify) on API methods that return Promises to emulate previous behaviour. See the [migration guide](https://gist.github.com/alanshaw/04b2ddc35a6fff25c040c011ac6acf26#migrating-from-callbacks) for more info.\n\n* `add` now returns an async iterable.\n\n* `add` now accepts `mode` and `mtime` options on inputs to allow setting mode and mtime metadata for added files. See the [core interface docs](https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/SPEC/FILES.md#add) for more info.\n\n* `add` results now contain a `cid` property (a [CID instance](https://github.com/multiformats/js-cid)) instead of a string `hash` property.\n\n* `addReadableStream`, `addPullStream` have been removed. Please see the [migration guide](https://gist.github.com/alanshaw/04b2ddc35a6fff25c040c011ac6acf26#migrating-to-async-iterables) for more info.\n\n* `addFromStream` has been removed. Use `add` instead.\n\n* `addFromFs` has been removed. Please use the exported `globSource` utility and pass the result to `add`. See the [glob source documentation](https://github.com/ipfs/js-ipfs-http-client#glob-source) for more details and an example.\n\n* `addFromURL` has been removed. Please use the exported `urlSource` utility and pass the result to `add`. See the [URL source documentation](https://github.com/ipfs/js-ipfs-http-client#url-source) for more details and an example.\n\n* `bitswap.stat` result has changed - `wantlist` and values are now an array of [CID](https://github.com/multiformats/js-cid) instances and `peers` is now a `string[]` of peer IDs.\n\n* `bitswap.wantlist` now returns an array of [CID](https://github.com/multiformats/js-cid) instances.\n\n* `block.rm` now returns an async iterable.\n\n* `block.rm` now yields objects of `{ cid: CID, error: Error }`.\n\n* `block.stat` result now contains a `cid` property (whose value is a [CID instance](https://github.com/multiformats/js-cid)) instead of a `key` property.\n\n* `dht.findProvs`, `dht.provide`, `dht.put` and `dht.query` now all return an async iterable.\n\n* `dht.findPeer`, `dht.findProvs`, `dht.provide`, `dht.put` and `dht.query` now yield/return an object `{ id: string, addrs: Multiaddr[] }` instead of a `PeerInfo` instance(s).\n\n* `files.lsPullStream` and `files.lsReadableStream` have been removed. Please see the [migration guide](https://gist.github.com/alanshaw/04b2ddc35a6fff25c040c011ac6acf26#migrating-to-async-iterables) for more info.\n\n* `files.ls` now returns an async iterable.\n\n* `files.ls` results now contain a `cid` property (whose value is a [CID instance](https://github.com/multiformats/js-cid)) instead of a `hash` property.\n\n* `files.ls` no longer takes a `long` option (in core) - you will receive all data by default.\n\n* `files.readPullStream` and `files.readReadableStream` have been removed. Please see the [migration guide](https://gist.github.com/alanshaw/04b2ddc35a6fff25c040c011ac6acf26#migrating-to-async-iterables) for more info.\n\n* `files.read` now returns an async iterable.\n\n* `files.stat` result now contains a `cid` property (whose value is a [CID instance](https://github.com/multiformats/js-cid)) instead of a `hash` property.\n\n* `get` now returns an async iterable. The `content` property value for objects yielded from the iterator is now an async iterable that yields [`BufferList`](https://github.com/rvagg/bl) objects.\n\n* `id` result has changed, the `addresses` property is now a `Multiaddr[]`\n\n* `name.resolve` now returns an async iterable. It yields increasingly more accurate resolved values as they are discovered until the best value is selected from the quorum of 16. The \"best\" resolved value is the last item yielded from the iterator. If you are interested only in this best value you could use `it-last` to extract it like so:\n\n    ```js\n    import last from 'it-last'\n    await last(ipfs.name.resolve('/ipns/QmHash'))\n    ```\n\n* `ls` now returns an async iterable.\n\n* `ls` results now contain a `cid` property (whose value is a [CID instance](https://github.com/multiformats/js-cid)) instead of a `hash` property.\n\n* `ls` results now include `mode` and `mtime` properties if they were set. See the [core interface docs](https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/SPEC/FILES.md#ls) for more info.\n\n* `pin.add` results now contain a `cid` property (a [CID instance](https://github.com/multiformats/js-cid)) instead of a string `hash` property.\n\n* `pin.ls` now returns an async iterable.\n\n* `pin.ls` results now contain a `cid` property (a [CID instance](https://github.com/multiformats/js-cid)) instead of a string `hash` property.\n\n* `pin.rm` results now contain a `cid` property (a [CID instance](https://github.com/multiformats/js-cid)) instead of a string `hash` property.\n\n* `ping` now returns an async iterable.\n\n* `refs` and `refs.local` now return an async iterable.\n\n* `repo.gc` now returns an async iterable.\n\n* `stats.bw` now returns an async iterable.\n\n* `swarm.peers` now returns an array of objects with a `peer` property that is a `string`, instead of a `PeerId` instance.\n\n* `swarm.addrs` now returns an array of objects `{ id: string, addrs: Multiaddr[] }` instead of `PeerInfo` instances.\n\n* The protocol _name_ for peer IDs in multiaddrs has changed from 'ipfs' to 'p2p'. There's no changes to data on the wire but this change is seen when multiaddrs are converted to strings.\n\n\n\n<a name=\"42.0.0-pre.0\"></a>\n## [42.0.0-pre.0](https://github.com/ipfs/js-ipfs-http-client/compare/v41.0.1...v42.0.0-pre.0) (2020-01-23)\n\n\n\n<a name=\"41.0.1\"></a>\n## [41.0.1](https://github.com/ipfs/js-ipfs-http-client/compare/v41.0.0...v41.0.1) (2020-01-23)\n\n\n\n<a name=\"41.0.0\"></a>\n## [41.0.0](https://github.com/ipfs/js-ipfs-http-client/compare/v40.2.1...v41.0.0) (2020-01-12)\n\n\n### Bug Fixes\n\n* return CIDs from files.flush ([#1216](https://github.com/ipfs/js-ipfs-http-client/issues/1216)) ([13f8d7a](https://github.com/ipfs/js-ipfs-http-client/commit/13f8d7a))\n\n\n### Code Refactoring\n\n* removes format option ([#1218](https://github.com/ipfs/js-ipfs-http-client/issues/1218)) ([4ef26cd](https://github.com/ipfs/js-ipfs-http-client/commit/4ef26cd))\n\n\n### BREAKING CHANGES\n\n* `format` option is no longer supported as everything is `dag-pb` all\nof the time.\n\nFollows on from https://github.com/ipfs/js-ipfs-mfs/pull/69\n\n\n\n<a name=\"40.2.1\"></a>\n## [40.2.1](https://github.com/ipfs/js-ipfs-http-client/compare/v40.2.0...v40.2.1) (2020-01-09)\n\n\n\n<a name=\"40.2.0\"></a>\n## [40.2.0](https://github.com/ipfs/js-ipfs-http-client/compare/v40.1.0...v40.2.0) (2020-01-09)\n\n\n### Features\n\n* support UnixFSv1.5 metadata ([#1186](https://github.com/ipfs/js-ipfs-http-client/issues/1186)) ([da9d17a](https://github.com/ipfs/js-ipfs-http-client/commit/da9d17a))\n\n\n\n<a name=\"40.1.0\"></a>\n## [40.1.0](https://github.com/ipfs/js-ipfs-http-client/compare/v40.0.1...v40.1.0) (2019-12-10)\n\n\n### Features\n\n* expose import concurrency controls ([#1187](https://github.com/ipfs/js-ipfs-http-client/issues/1187)) ([47093d5](https://github.com/ipfs/js-ipfs-http-client/commit/47093d5))\n\n\n\n<a name=\"40.0.1\"></a>\n## [40.0.1](https://github.com/ipfs/js-ipfs-http-client/compare/v40.0.0...v40.0.1) (2019-11-27)\n\n\n### Bug Fixes\n\n* pin ls with multiple CIDs ([#1184](https://github.com/ipfs/js-ipfs-http-client/issues/1184)) ([2f3763f](https://github.com/ipfs/js-ipfs-http-client/commit/2f3763f))\n\n\n\n<a name=\"40.0.0\"></a>\n## [40.0.0](https://github.com/ipfs/js-ipfs-http-client/compare/v39.0.2...v40.0.0) (2019-11-22)\n\n\n### Code Refactoring\n\n* async await roundup ([#1173](https://github.com/ipfs/js-ipfs-http-client/issues/1173)) ([3e5967a](https://github.com/ipfs/js-ipfs-http-client/commit/3e5967a)), closes [#1103](https://github.com/ipfs/js-ipfs-http-client/issues/1103)\n* convert config API to async await ([#1155](https://github.com/ipfs/js-ipfs-http-client/issues/1155)) ([621973c](https://github.com/ipfs/js-ipfs-http-client/commit/621973c))\n* move files to root level ([#1150](https://github.com/ipfs/js-ipfs-http-client/issues/1150)) ([559a97d](https://github.com/ipfs/js-ipfs-http-client/commit/559a97d))\n\n\n### Features\n\n* support name.resolve of peerid as cid ([#1145](https://github.com/ipfs/js-ipfs-http-client/issues/1145)) ([2d9afc8](https://github.com/ipfs/js-ipfs-http-client/commit/2d9afc8))\n\n\n### Reverts\n\n* chore: update multiaddr to version 7.2.0 ([#1136](https://github.com/ipfs/js-ipfs-http-client/issues/1136)) ([#1143](https://github.com/ipfs/js-ipfs-http-client/issues/1143)) ([4131d09](https://github.com/ipfs/js-ipfs-http-client/commit/4131d09))\n\n\n### BREAKING CHANGES\n\n* The `log.tail` method now returns an async iterator that yields log messages. Use it like:\n    ```js\n    for await (const message of ipfs.log.tail()) {\n      console.log(message)\n    }\n    ```\n* The response to a call to `log.level` now returns an object that has camel cased keys. i.e. `Message` and `Error` properties have changed to `message` and `error`.\n* Dropped support for go-ipfs <= 0.4.4 in `swarm.peers` response.\n* The signature for `ipfs.mount` has changed from `ipfs.mount([ipfsPath], [ipnsPath])` to `ipfs.mount([options])`. Where `options` is an optional object that may contain two boolean properties `ipfsPath` and `ipnsPath`. The response object has also changed to be camel case. See https://docs.ipfs.io/reference/api/http/#api-v0-mount.\n* Default ping `count` of 1 in client has been removed. The default ping count is now whatever the IPFS node defaults it to (currently 10). If you specifically need 1 ping message then please pass `count: 1` in options for `ipfs.ping()`.\n* Multi parameter constructor options are no longer supported. To create a new IPFS HTTP client, pass a single parameter to the constructor. The parameter can be one of:\n    * String, formatted as one of:\n        * Multiaddr e.g. /ip4/127.0.0.1/tcp/5001\n        * URL e.g. http://127.0.0.1:5001\n    * [Multiaddr](https://www.npmjs.com/package/multiaddr) instance\n    * Object, in format of either:\n        * Address and path e.g. `{ apiAddr: '/ip4/127.0.0.1/tcp/5001': apiPath: '/api/v0' }` (Note: `apiAddr` can also be a string in URL form or a Multiaddr instance)\n        * Node.js style address e.g. `{ host: '127.0.0.1', port: 5001, protocol: 'http' }`\n* Errors returned from request failures are now all [`HTTPError`](https://github.com/sindresorhus/ky/blob/c0d9d2bb07e4c122a08f019b39e9c55a4c9324f3/index.js#L117-L123)s which carry a `response` property. This is a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) that can be used to inspect _all_ information relating to the HTTP response. This means that the `err.status` or `err.statusCode` property should now be accessed via `err.response.status`.\n* files in `src/files-regular` have moved to `src`. The `src/files-mfs` directory has been renamed to `src/files`. If you were previously requiring files from these directories e.g. `require('ipfs-http-client/src/files-regular/add')` then please be aware that they have moved.\n* Kebab case options are no longer supported. Please use camel case option names as defined in the [`interface-ipfs-core`](https://github.com/ipfs/js-ipfs/tree/master/packages/interface-ipfs-core/SPEC) docs. e.g. the `allow-offline` option to `name.publish` should be passed as `allowOffline`.\n  * Note that you can pass [additional query string parameters](https://github.com/ipfs/js-ipfs-http-client#additional-options) in the `searchParams` option available to all API methods.\n\n\n\n<a name=\"39.0.2\"></a>\n## [39.0.2](https://github.com/ipfs/js-ipfs-http-client/compare/v39.0.1...v39.0.2) (2019-10-23)\n\n\n### Bug Fixes\n\n* use non-strict equivalence for options.preload ([#1134](https://github.com/ipfs/js-ipfs-http-client/issues/1134)) ([432e1e8](https://github.com/ipfs/js-ipfs-http-client/commit/432e1e8))\n\n\n\n<a name=\"39.0.1\"></a>\n## [39.0.1](https://github.com/ipfs/js-ipfs-http-client/compare/v39.0.0...v39.0.1) (2019-10-21)\n\n\n### Bug Fixes\n\n* expose preload argument ([#1129](https://github.com/ipfs/js-ipfs-http-client/issues/1129)) ([c82b031](https://github.com/ipfs/js-ipfs-http-client/commit/c82b031))\n* increase default timeout and respect value passed to `ky.extend` ([#1130](https://github.com/ipfs/js-ipfs-http-client/issues/1130)) ([25b6043](https://github.com/ipfs/js-ipfs-http-client/commit/25b6043))\n\n\n\n<a name=\"39.0.0\"></a>\n## [39.0.0](https://github.com/ipfs/js-ipfs-http-client/compare/v38.2.0...v39.0.0) (2019-10-15)\n\n\n\n<a name=\"38.2.0\"></a>\n## [38.2.0](https://github.com/ipfs/js-ipfs-http-client/compare/v38.1.0...v38.2.0) (2019-10-06)\n\n\n### Features\n\n* adds ipfs.block.rm method ([#1123](https://github.com/ipfs/js-ipfs-http-client/issues/1123)) ([2f0eff7](https://github.com/ipfs/js-ipfs-http-client/commit/2f0eff7))\n\n\n\n<a name=\"38.1.0\"></a>\n## [38.1.0](https://github.com/ipfs/js-ipfs-http-client/compare/v38.0.1...v38.1.0) (2019-10-04)\n\n\n### Bug Fixes\n\n* get correct remote node config ([5b53e22](https://github.com/ipfs/js-ipfs-http-client/commit/5b53e22))\n* pull in preconfigured chai from interface tests ([93765c1](https://github.com/ipfs/js-ipfs-http-client/commit/93765c1))\n\n\n### Features\n\n* add methods for listing config profiles ([1c3d92a](https://github.com/ipfs/js-ipfs-http-client/commit/1c3d92a))\n\n\n### BREAKING CHANGES\n\n* Configuration profiles API has changed:\n    ```javascript\n    Promise<{oldCfg, newCfg}> ipfs.config.profile(name, opts)\n\n    // is now\n    Promise<{old, new}> ipfs.config.profiles.apply(name, opts)\n    ```\n\n* Possibly contentious; Adds `callbackify` as a dependency, see https://github.com/ipfs/js-ipfs/issues/2506\nfor discussion.\n\n\n\n<a name=\"38.0.1\"></a>\n## [38.0.1](https://github.com/ipfs/js-ipfs-http-client/compare/v38.0.0...v38.0.1) (2019-10-04)\n\n\n### Bug Fixes\n\n* pull in preconfigured chai from interface tests ([6a7eb8a](https://github.com/ipfs/js-ipfs-http-client/commit/6a7eb8a))\n\n\n\n<a name=\"38.0.0\"></a>\n## [38.0.0](https://github.com/ipfs/js-ipfs-http-client/compare/v37.0.3...v38.0.0) (2019-09-25)\n\n\n\n<a name=\"37.0.3\"></a>\n## [37.0.3](https://github.com/ipfs/js-ipfs-http-client/compare/v37.0.2...v37.0.3) (2019-09-25)\n\n\n\n<a name=\"37.0.2\"></a>\n## [37.0.2](https://github.com/ipfs/js-ipfs-http-client/compare/v37.0.1...v37.0.2) (2019-09-20)\n\n\n### Bug Fixes\n\n* only do the big file workaround in node and electron main ([077c997](https://github.com/ipfs/js-ipfs-http-client/commit/077c997))\n\n\n\n<a name=\"37.0.1\"></a>\n## [37.0.1](https://github.com/ipfs/js-ipfs-http-client/compare/v37.0.0...v37.0.1) (2019-09-17)\n\n\n\n<a name=\"37.0.0\"></a>\n## [37.0.0](https://github.com/ipfs/js-ipfs-http-client/compare/v36.1.0...v37.0.0) (2019-09-17)\n\n\n### Bug Fixes\n\n* big downloads in electron ([9c9aac8](https://github.com/ipfs/js-ipfs-http-client/commit/9c9aac8))\n\n\n\n<a name=\"36.1.0\"></a>\n## [36.1.0](https://github.com/ipfs/js-ipfs-http-client/compare/v36.0.0...v36.1.0) (2019-09-17)\n\n\n### Bug Fixes\n\n* fix electron renderer tests and a couple more bugs ([#1105](https://github.com/ipfs/js-ipfs-http-client/issues/1105)) ([a631a21](https://github.com/ipfs/js-ipfs-http-client/commit/a631a21))\n\n\n\n<a name=\"36.0.0\"></a>\n## [36.0.0](https://github.com/ipfs/js-ipfs-http-client/compare/v35.1.0...v36.0.0) (2019-09-11)\n\n\n\n<a name=\"35.1.0\"></a>\n## [35.1.0](https://github.com/ipfs/js-ipfs-http-client/compare/v35.0.0...v35.1.0) (2019-09-04)\n\n\n### Features\n\n* add config profile endpoint ([#1030](https://github.com/ipfs/js-ipfs-http-client/issues/1030)) ([3aaa3ee](https://github.com/ipfs/js-ipfs-http-client/commit/3aaa3ee))\n\n\n\n<a name=\"35.0.0\"></a>\n## [35.0.0](https://github.com/ipfs/js-ipfs-http-client/compare/v34.0.0...v35.0.0) (2019-09-04)\n\n### BREAKING CHANGES\n\nKebab case options (e.g. `wrap-with-directory`) are no longer supported in `ipfs.add`. Use camel case instead (e.g. `wrapWithDirectory`).\n\n<a name=\"34.0.0\"></a>\n## [34.0.0](https://github.com/ipfs/js-ipfs-http-client/compare/v33.1.1...v34.0.0) (2019-08-29)\n\n\n### Bug Fixes\n\n* **package:** update err-code to version 2.0.0 ([#1053](https://github.com/ipfs/js-ipfs-http-client/issues/1053)) ([3515070](https://github.com/ipfs/js-ipfs-http-client/commit/3515070))\n\n\n### Features\n\n* browser pubsub ([#1059](https://github.com/ipfs/js-ipfs-http-client/issues/1059)) ([3764d06](https://github.com/ipfs/js-ipfs-http-client/commit/3764d06))\n* expose pin and preload arguments ([#1079](https://github.com/ipfs/js-ipfs-http-client/issues/1079)) ([e3ed6e9](https://github.com/ipfs/js-ipfs-http-client/commit/e3ed6e9))\n* support adding files via async iterator ([#1078](https://github.com/ipfs/js-ipfs-http-client/issues/1078)) ([377042b](https://github.com/ipfs/js-ipfs-http-client/commit/377042b))\n\n\n\n<a name=\"33.1.1\"></a>\n## [33.1.1](https://github.com/ipfs/js-ipfs-http-client/compare/v33.1.0...v33.1.1) (2019-07-26)\n\n\n### Bug Fixes\n\n* allow passing timeout option to object stat ([#1055](https://github.com/ipfs/js-ipfs-http-client/issues/1055)) ([92b0594](https://github.com/ipfs/js-ipfs-http-client/commit/92b0594))\n\n\n\n<a name=\"33.1.0\"></a>\n## [33.1.0](https://github.com/ipfs/js-ipfs-http-client/compare/v33.0.2...v33.1.0) (2019-07-11)\n\n\n### Bug Fixes\n\n* changelog for 33.x does not include breaking change ([cd41a16](https://github.com/ipfs/js-ipfs-http-client/commit/cd41a16))\n* invalid multipart/form-data ([#948](https://github.com/ipfs/js-ipfs-http-client/issues/948)) ([9e6dfe7](https://github.com/ipfs/js-ipfs-http-client/commit/9e6dfe7)), closes [/tools.ietf.org/html/rfc7578#section-4](https://github.com//tools.ietf.org/html/rfc7578/issues/section-4)\n\n\n### Features\n\n* add support for js-ipfs dag api and also some tests ([#957](https://github.com/ipfs/js-ipfs-http-client/issues/957)) ([8f378a3](https://github.com/ipfs/js-ipfs-http-client/commit/8f378a3))\n\n\n\n<a name=\"33.0.2\"></a>\n## [33.0.2](https://github.com/ipfs/js-ipfs-http-client/compare/v33.0.1...v33.0.2) (2019-07-11)\n\n\n### Bug Fixes\n\n* make findprovs return all responses ([#1041](https://github.com/ipfs/js-ipfs-http-client/issues/1041)) ([63103bd](https://github.com/ipfs/js-ipfs-http-client/commit/63103bd))\n\n\n\n<a name=\"33.0.1\"></a>\n## [33.0.1](https://github.com/ipfs/js-ipfs-http-client/compare/v33.0.0...v33.0.1) (2019-07-10)\n\n\n### Bug Fixes\n\n* response for findpeer and findprovs ([#1039](https://github.com/ipfs/js-ipfs-http-client/issues/1039)) ([5252f50](https://github.com/ipfs/js-ipfs-http-client/commit/5252f50))\n\n\n\n<a name=\"33.0.0\"></a>\n## [33.0.0](https://github.com/ipfs/js-ipfs-http-client/compare/v32.0.1...v33.0.0) (2019-07-10)\n\n\n### Bug Fixes\n\n* link to ipfs.io ([70cdf25](https://github.com/ipfs/js-ipfs-http-client/commit/70cdf25))\n* prepare for aegir release ([#1021](https://github.com/ipfs/js-ipfs-http-client/issues/1021)) ([806b206](https://github.com/ipfs/js-ipfs-http-client/commit/806b206))\n* sometimes no Addrs element is present in the response ([#1037](https://github.com/ipfs/js-ipfs-http-client/issues/1037)) ([a74b8f7](https://github.com/ipfs/js-ipfs-http-client/commit/a74b8f7))\n* **package:** update bignumber.js to version 9.0.0 ([#1024](https://github.com/ipfs/js-ipfs-http-client/issues/1024)) ([a04edac](https://github.com/ipfs/js-ipfs-http-client/commit/a04edac))\n\n### BREAKING CHANGES\n\n`repo.gc` response objects have changed to `{ err, cid }`, where `err` is an `Error` instance and `cid` is a [`CID`](https://github.com/multiformats/js-cid) instance.\n\n\n\n<a name=\"32.0.1\"></a>\n## [32.0.1](https://github.com/ipfs/js-ipfs-http-client/compare/v32.0.0...v32.0.1) (2019-05-21)\n\n\n### Bug Fixes\n\n* error reporting for non-JSON responses ([#1016](https://github.com/ipfs/js-ipfs-http-client/issues/1016)) ([4251c88](https://github.com/ipfs/js-ipfs-http-client/commit/4251c88)), closes [#912](https://github.com/ipfs/js-ipfs-http-client/issues/912) [#1000](https://github.com/ipfs/js-ipfs-http-client/issues/1000) [#1001](https://github.com/ipfs/js-ipfs-http-client/issues/1001)\n* send trickle param to trigger trickle dag builder ([#1015](https://github.com/ipfs/js-ipfs-http-client/issues/1015)) ([a28b009](https://github.com/ipfs/js-ipfs-http-client/commit/a28b009))\n\n\n\n<a name=\"32.0.0\"></a>\n## [32.0.0](https://github.com/ipfs/js-ipfs-http-client/compare/v31.1.0...v32.0.0) (2019-05-21)\n\n\n### Bug Fixes\n\n* handle empty array return value in dht.findProvs ([#1003](https://github.com/ipfs/js-ipfs-http-client/issues/1003)) ([15ab7c5](https://github.com/ipfs/js-ipfs-http-client/commit/15ab7c5))\n\n\n### Chores\n\n* update ipld formats ([#1010](https://github.com/ipfs/js-ipfs-http-client/issues/1010)) ([a423d7f](https://github.com/ipfs/js-ipfs-http-client/commit/a423d7f))\n\n\n### BREAKING CHANGES\n\n* The default string encoding for version 1 CIDs has changed to `base32`.\n\nIPLD formats have been updated to the latest versions. IPLD nodes returned by `ipfs.dag` and `ipfs.object` commands have significant breaking changes. If you are using these commands in your application you are likely to encounter the following changes to `dag-pb` nodes (the default node type that IPFS creates):\n\n* `DAGNode` properties have been renamed as follows:\n    * `data` => `Data`\n    * `links` => `Links`\n    * `size` => `size` (Note: no change)\n* `DAGLink` properties have been renamed as follows:\n    * `cid` => `Hash`\n    * `name` => `Name`\n    * `size` => `Tsize`\n\nSee CHANGELOGs for each IPLD format for it's respective changes, you can read more about the [`dag-pb` changes in the CHANGELOG](https://github.com/ipld/js-ipld-dag-pb/blob/master)\n\nLicense: MIT\nSigned-off-by: Alan Shaw <alan.shaw@protocol.ai>\n\n\n\n<a name=\"31.1.0\"></a>\n## [31.1.0](https://github.com/ipfs/js-ipfs-http-client/compare/v31.0.2...v31.1.0) (2019-05-16)\n\n\n### Features\n\n* add support for File DOM API to files-regular ([#986](https://github.com/ipfs/js-ipfs-http-client/issues/986)) ([7b49f7e](https://github.com/ipfs/js-ipfs-http-client/commit/7b49f7e))\n\n\n\n<a name=\"31.0.2\"></a>\n## [31.0.2](https://github.com/ipfs/js-ipfs-http-client/compare/v31.0.1...v31.0.2) (2019-05-16)\n\n\n### Bug Fixes\n\n* error handling for refs/refs local ([#997](https://github.com/ipfs/js-ipfs-http-client/issues/997)) ([391351d](https://github.com/ipfs/js-ipfs-http-client/commit/391351d))\n\n\n\n<a name=\"31.0.1\"></a>\n## [31.0.1](https://github.com/ipfs/js-ipfs-http-client/compare/v31.0.0...v31.0.1) (2019-05-15)\n\n\n### Bug Fixes\n\n* config set with number ([#998](https://github.com/ipfs/js-ipfs-http-client/issues/998)) ([4f21bef](https://github.com/ipfs/js-ipfs-http-client/commit/4f21bef)), closes [#881](https://github.com/ipfs/js-ipfs-http-client/issues/881)\n\n\n\n<a name=\"31.0.0\"></a>\n## [31.0.0](https://github.com/ipfs/js-ipfs-http-client/compare/v30.1.4...v31.0.0) (2019-05-13)\n\n\n### Features\n\n* refs endpoint ([#978](https://github.com/ipfs/js-ipfs-http-client/issues/978)) ([a741e10](https://github.com/ipfs/js-ipfs-http-client/commit/a741e10))\n\n\n### BREAKING CHANGES\n\n* ipfs.refs now returns objects with camelCase properties not PascalCase properties. i.e. `{ ref, err }` not `{ Ref, Err }`\n\n\n\n<a name=\"30.1.4\"></a>\n## [30.1.4](https://github.com/ipfs/js-ipfs-http-client/compare/v30.1.3...v30.1.4) (2019-04-29)\n\n\n### Bug Fixes\n\n* uncaught error: stream.push() after EOF ([#980](https://github.com/ipfs/js-ipfs-http-client/issues/980)) ([cc677f0](https://github.com/ipfs/js-ipfs-http-client/commit/cc677f0)), closes [#967](https://github.com/ipfs/js-ipfs-http-client/issues/967)\n* update Babel in upload-file-via-browser example ([#968](https://github.com/ipfs/js-ipfs-http-client/issues/968)) ([#970](https://github.com/ipfs/js-ipfs-http-client/issues/970)) ([17d49de](https://github.com/ipfs/js-ipfs-http-client/commit/17d49de))\n\n\n\n<a name=\"30.1.3\"></a>\n## [30.1.3](https://github.com/ipfs/js-ipfs-http-client/compare/v30.1.2...v30.1.3) (2019-04-11)\n\n\n### Bug Fixes\n\n* fix missing buffer bundling with browserify ([#966](https://github.com/ipfs/js-ipfs-http-client/issues/966)) ([944a64b](https://github.com/ipfs/js-ipfs-http-client/commit/944a64b)), closes [#964](https://github.com/ipfs/js-ipfs-http-client/issues/964)\n\n\n\n<a name=\"30.1.2\"></a>\n## [30.1.2](https://github.com/ipfs/js-ipfs-http-client/compare/v30.1.1...v30.1.2) (2019-04-09)\n\n\n### Bug Fixes\n\n* https multiaddr support in constructor ([#965](https://github.com/ipfs/js-ipfs-http-client/issues/965)) ([5da0bcd](https://github.com/ipfs/js-ipfs-http-client/commit/5da0bcd))\n\n\n\n<a name=\"30.1.1\"></a>\n## [30.1.1](https://github.com/ipfs/js-ipfs-http-client/compare/v30.1.0...v30.1.1) (2019-03-28)\n\n\n\n<a name=\"30.1.0\"></a>\n## [30.1.0](https://github.com/ipfs/js-ipfs-http-client/compare/v30.0.0...v30.1.0) (2019-03-15)\n\n\n### Bug Fixes\n\n* dht.findProvs.js handle valid hash but no providers ([#950](https://github.com/ipfs/js-ipfs-http-client/issues/950)) ([c3cde76](https://github.com/ipfs/js-ipfs-http-client/commit/c3cde76))\n\n\n### Features\n\n* provide access to multicodec ([#954](https://github.com/ipfs/js-ipfs-http-client/issues/954)) ([0c109ab](https://github.com/ipfs/js-ipfs-http-client/commit/0c109ab))\n\n\n### Performance Improvements\n\n* reduce bundle size ([#915](https://github.com/ipfs/js-ipfs-http-client/issues/915)) ([87dff04](https://github.com/ipfs/js-ipfs-http-client/commit/87dff04))\n\n\n\n<a name=\"30.0.0\"></a>\n## [30.0.0](https://github.com/ipfs/js-ipfs-http-client/compare/v29.1.1...v30.0.0) (2019-03-13)\n\n\n### Bug Fixes\n\n* windows travis build ([#952](https://github.com/ipfs/js-ipfs-http-client/issues/952)) ([05f2f6c](https://github.com/ipfs/js-ipfs-http-client/commit/05f2f6c))\n\n\n### Code Refactoring\n\n* export types and utilities statically ([#951](https://github.com/ipfs/js-ipfs-http-client/issues/951)) ([d1e99e7](https://github.com/ipfs/js-ipfs-http-client/commit/d1e99e7)), closes [#902](https://github.com/ipfs/js-ipfs-http-client/issues/902)\n\n\n### Features\n\n* pubsub unsubscribe all ([#956](https://github.com/ipfs/js-ipfs-http-client/issues/956)) ([a57a411](https://github.com/ipfs/js-ipfs-http-client/commit/a57a411))\n\n\n### BREAKING CHANGES\n\n* `ipfs.util.isIPFS` has moved to a static export and should be accessed via `const { isIPFS } = require('ipfs-http-client')`.\n\nThe modules available under `ipfs.types.*` have also become static exports.\n\n`ipfs.util.crypto` has been removed as it is not a dependency of `ipfs-http-client` so reduces the bundle size. If you need to use libp2p crypto primitives then please see the [js-libp2p-crypto](https://github.com/libp2p/js-libp2p-crypto) project for info on how to use it in your project.\n\nFinally `ipfs.util.getEndpointConfig` is now a direct instance method, `ipfs.getEndpointConfig`\n\nLicense: MIT\nSigned-off-by: Alan Shaw <alan.shaw@protocol.ai>\n\n\n\n<a name=\"29.1.1\"></a>\n## [29.1.1](https://github.com/ipfs/js-ipfs-http-client/compare/v29.1.0...v29.1.1) (2019-02-13)\n\n\n### Performance Improvements\n\n* use test profile ([#942](https://github.com/ipfs/js-ipfs-http-client/issues/942)) ([2c90620](https://github.com/ipfs/js-ipfs-http-client/commit/2c90620))\n\n\n\n<a name=\"29.1.0\"></a>\n## [29.1.0](https://github.com/ipfs/js-ipfs-http-client/compare/v29.0.1...v29.1.0) (2019-01-29)\n\n\n### Bug Fixes\n\n* throw on invalid multiaddr to constructor ([#934](https://github.com/ipfs/js-ipfs-http-client/issues/934)) ([bcbf0d2](https://github.com/ipfs/js-ipfs-http-client/commit/bcbf0d2))\n\n\n### Features\n\n* return protocol from getEndpointConfig ([#935](https://github.com/ipfs/js-ipfs-http-client/issues/935)) ([12ddaa3](https://github.com/ipfs/js-ipfs-http-client/commit/12ddaa3))\n\n\n\n<a name=\"29.0.1\"></a>\n## [29.0.1](https://github.com/ipfs/js-ipfs-http-client/compare/v29.0.0...v29.0.1) (2019-01-24)\n\n\n### Bug Fixes\n\n* bundle in meteor ([#931](https://github.com/ipfs/js-ipfs-http-client/issues/931)) ([431c442](https://github.com/ipfs/js-ipfs-http-client/commit/431c442)), closes [#10411](https://github.com/ipfs/js-ipfs-http-client/issues/10411)\n\n\n\n<a name=\"29.0.0\"></a>\n## [29.0.0](https://github.com/ipfs/js-ipfs-http-client/compare/v28.1.2...v29.0.0) (2019-01-15)\n\n\n### Code Refactoring\n\n* switch to bignumber.js ([#927](https://github.com/ipfs/js-ipfs-http-client/issues/927)) ([1a54ae5](https://github.com/ipfs/js-ipfs-http-client/commit/1a54ae5))\n\n\n### BREAKING CHANGES\n\n* All API methods that returned [`big.js`](https://github.com/MikeMcl/big.js/) instances now return [`bignumber.js`](https://github.com/MikeMcl/bignumber.js/) instances.\n\nLicense: MIT\nSigned-off-by: Alan Shaw <alan.shaw@protocol.ai>\n\n\n\n<a name=\"28.1.2\"></a>\n## [28.1.2](https://github.com/ipfs/js-ipfs-http-client/compare/v28.1.1...v28.1.2) (2019-01-14)\n\n\n\n<a name=\"28.1.1\"></a>\n## [28.1.1](https://github.com/ipfs/js-ipfs-http-client/compare/v28.1.0...v28.1.1) (2019-01-04)\n\n\n\n<a name=\"28.1.0\"></a>\n## [28.1.0](https://github.com/ipfs/js-ipfs-http-client/compare/v28.0.3...v28.1.0) (2018-12-16)\n\n\n### Features\n\n* add cidBase option to resolve ([#893](https://github.com/ipfs/js-ipfs-http-client/issues/893)) ([ec6285d](https://github.com/ipfs/js-ipfs-http-client/commit/ec6285d))\n\n\n\n<a name=\"28.0.3\"></a>\n## [28.0.3](https://github.com/ipfs/js-ipfs-http-client/compare/v28.0.2...v28.0.3) (2018-12-15)\n\n\n### Bug Fixes\n\n* re-allow passing path to ls ([#914](https://github.com/ipfs/js-ipfs-http-client/issues/914)) ([442bcdd](https://github.com/ipfs/js-ipfs-http-client/commit/442bcdd))\n\n\n\n<a name=\"28.0.2\"></a>\n## [28.0.2](https://github.com/ipfs/js-ipfs-http-client/compare/v28.0.1...v28.0.2) (2018-12-14)\n\n\n\n<a name=\"28.0.1\"></a>\n## [28.0.1](https://github.com/ipfs/js-ipfs-http-client/compare/v28.0.0...v28.0.1) (2018-12-13)\n\n\n### Bug Fixes\n\n* disable just the rule we're breaking ([bed2687](https://github.com/ipfs/js-ipfs-http-client/commit/bed2687))\n* properly serialize CID instances ([45b344c](https://github.com/ipfs/js-ipfs-http-client/commit/45b344c))\n* skip test that go-ipfs cannot pass ([0e15761](https://github.com/ipfs/js-ipfs-http-client/commit/0e15761))\n\n\n\n<a name=\"28.0.0\"></a>\n## [28.0.0](https://github.com/ipfs/js-ipfs-http-client/compare/v27.1.0...v28.0.0) (2018-12-11)\n\n\n### Bug Fixes\n\n* case for addFromURL ([#907](https://github.com/ipfs/js-ipfs-http-client/issues/907)) ([99ac7be](https://github.com/ipfs/js-ipfs-http-client/commit/99ac7be))\n\n\n### Code Refactoring\n\n* dht api ([#890](https://github.com/ipfs/js-ipfs-http-client/issues/890)) ([05a84a4](https://github.com/ipfs/js-ipfs-http-client/commit/05a84a4))\n\n\n### BREAKING CHANGES\n\n* DHT API methods renamed and return types changed\n\n* `ipfs.dht.findprovs` renamed to `ipfs.dht.findProvs` and returns an array of [PeerInfo](https://github.com/libp2p/js-peer-info)\n* `ipfs.dht.findpeer` renamed to `ipfs.dht.findPeer` and returns a [PeerInfo](https://github.com/libp2p/js-peer-info)\n* `ipfs.dht.query` now returns an array of [PeerId](https://github.com/libp2p/js-peer-id)\n* [More info](https://github.com/ipfs/js-ipfs/blob/master/packages/interface-ipfs-core/SPEC/DHT.md)\n\n\n\n<a name=\"27.1.0\"></a>\n## [27.1.0](https://github.com/ipfs/js-ipfs-http-client/compare/v27.0.0...v27.1.0) (2018-12-05)\n\n\n### Bug Fixes\n\n* add docs for breaking change ([#898](https://github.com/ipfs/js-ipfs-http-client/issues/898)) ([3e794ac](https://github.com/ipfs/js-ipfs-http-client/commit/3e794ac))\n\n\n### Features\n\n* add files.ls*Stream methods ([#903](https://github.com/ipfs/js-ipfs-http-client/issues/903)) ([705855e](https://github.com/ipfs/js-ipfs-http-client/commit/705855e))\n\n\n\n<a name=\"27.0.0\"></a>\n## [27.0.0](https://github.com/ipfs/js-ipfs-http-client/compare/v26.1.2...v27.0.0) (2018-11-28)\n\n\n### Bug Fixes\n\n* also retry with misnemed format \"dag-cbor\" as \"cbor\" ([#888](https://github.com/ipfs/js-ipfs-http-client/issues/888)) ([348a144](https://github.com/ipfs/js-ipfs-http-client/commit/348a144))\n* better input validation for add ([#876](https://github.com/ipfs/js-ipfs-http-client/issues/876)) ([315b7f7](https://github.com/ipfs/js-ipfs-http-client/commit/315b7f7))\n* fix log.tail by calling add after listening for events ([#882](https://github.com/ipfs/js-ipfs-http-client/issues/882)) ([da35b0f](https://github.com/ipfs/js-ipfs-http-client/commit/da35b0f))\n* handle peer-info validation errors ([#887](https://github.com/ipfs/js-ipfs-http-client/issues/887)) ([6e6d7a2](https://github.com/ipfs/js-ipfs-http-client/commit/6e6d7a2)), closes [#885](https://github.com/ipfs/js-ipfs-http-client/issues/885)\n* updates ipld-dag-pb dep to version without .cid properties ([#889](https://github.com/ipfs/js-ipfs-http-client/issues/889)) ([ac30a82](https://github.com/ipfs/js-ipfs-http-client/commit/ac30a82))\n\n\n### Code Refactoring\n\n* object API write methods now return CIDs ([#896](https://github.com/ipfs/js-ipfs-http-client/issues/896)) ([38bed14](https://github.com/ipfs/js-ipfs-http-client/commit/38bed14))\n* rename library to ipfs-http-client ([#897](https://github.com/ipfs/js-ipfs-http-client/issues/897)) ([d40cb6c](https://github.com/ipfs/js-ipfs-http-client/commit/d40cb6c))\n* updated files API ([#878](https://github.com/ipfs/js-ipfs-http-client/issues/878)) ([39f4733](https://github.com/ipfs/js-ipfs-http-client/commit/39f4733))\n\n\n### BREAKING CHANGES\n\n* the `ipfs-api` library has been renamed to `ipfs-http-client`.\n\nNow install via `npm install ipfs-http-client`.\n\nNote that in the browser build the object attached to `window` is now `window.IpfsHttpClient`.\n\nLicense: MIT\nSigned-off-by: Alan Shaw <alan.shaw@protocol.ai>\n\n* Object API refactor.\n\nObject API methods that write DAG nodes now return a CID instead of a DAG node. Affected methods:\n\n* `ipfs.object.new`\n* `ipfs.object.patch.addLink`\n* `ipfs.object.patch.appendData`\n* `ipfs.object.patch.rmLink`\n* `ipfs.object.patch.setData`\n* `ipfs.object.put`\n\nExample:\n\n```js\n// Before\nconst dagNode = await ipfs.object.new()\n```\n\n```js\n// After\nconst cid = await ipfs.object.new() // now returns a CID\nconst dagNode = await ipfs.object.get(cid) // fetch the DAG node that was created\n```\n\nIMPORTANT: `DAGNode` instances, which are part of the IPLD dag-pb format have been refactored.\n\nThese instances no longer have `multihash`, `cid` or `serialized` properties.\n\nThis effects the following API methods that return these types of objects:\n\n* `ipfs.object.get`\n* `ipfs.dag.get`\n\nSee https://github.com/ipld/js-ipld-dag-pb/pull/99 for more information.\n\nLicense: MIT\nSigned-off-by: Alan Shaw <alan.shaw@protocol.ai>\n\n* Files API methods `add*`, `cat*`, `get*` have moved from `files` to the root namespace.\n\nSpecifically, the following changes have been made:\n\n* `ipfs.files.add` => `ipfs.add`\n* `ipfs.files.addPullStream` => `ipfs.addPullStream`\n* `ipfs.files.addReadableStream` => `ipfs.addReadableStream`\n* `ipfs.files.cat` => `ipfs.cat`\n* `ipfs.files.catPullStream` => `ipfs.catPullStream`\n* `ipfs.files.catReadableStream` => `ipfs.catReadableStream`\n* `ipfs.files.get` => `ipfs.get`\n* `ipfs.files.getPullStream` => `ipfs.getPullStream`\n* `ipfs.files.getReadableStream` => `ipfs.getReadableStream`\n\nAdditionally, `addFromFs`, `addFromURL`, `addFromStream` have moved from `util` to the root namespace:\n\n* `ipfs.util.addFromFs` => `ipfs.addFromFs`\n* `ipfs.util.addFromURL` => `ipfs.addFromURL`\n* `ipfs.util.addFromStream` => `ipfs.addFromStream`\n\nLicense: MIT\nSigned-off-by: Alan Shaw <alan.shaw@protocol.ai>\n\n* Previously `swarm.peers` would throw an uncaught error if any peer in the response could not have its peerId or multiaddr validated.\n\nThis change catches errors that occur while validating the peer info. The returned array will contain an entry for every peer in the ipfs response. peer-info objects that couldn't be validated, now have an `error` property and a `rawPeerInfo` property. This at least means the count of peers in the response will be accurate, and there the info is available to the caller.\n\nThis means that callers now have to deal with peer-info objects that may\nnot have a `peer` or `addr` property.\n\nAdds `nock` tests to exercice the code under different error conditions. Doing so uncovered a bug in our legacy go-ipfs <= 0.4.4 peer info parsing, which is also fixed. The code was trying to decapusalate the peerId from the multiaddr, but doing so trims the peerId rather than returning it.\n\nLicense: MIT\nSigned-off-by: Oli Evans <oli@tableflip.io>\n\n\n<a name=\"26.1.2\"></a>\n## [26.1.2](https://github.com/ipfs/js-ipfs-http-client/compare/v26.1.0...v26.1.2) (2018-11-03)\n\n\n### Features\n\n* go-ipfs 0.4.18 ([e3e4d6c](https://github.com/ipfs/js-ipfs-http-client/commit/e3e4d6c))\n* upload example works with big files ([62b844f](https://github.com/ipfs/js-ipfs-http-client/commit/62b844f))\n\n\n\n<a name=\"26.1.1\"></a>\n## [26.1.1](https://github.com/ipfs/js-ipfs-http-client/compare/v26.1.0...v26.1.1) (2018-11-03)\n\n\n### Features\n\n* go-ipfs 0.4.18 ([9178e7d](https://github.com/ipfs/js-ipfs-http-client/commit/9178e7d))\n\n\n\n<a name=\"26.1.0\"></a>\n## [26.1.0](https://github.com/ipfs/js-ipfs-http-client/compare/v26.0.3...v26.1.0) (2018-10-31)\n\n\n### Bug Fixes\n\n* make ping not mix errors with responses ([#883](https://github.com/ipfs/js-ipfs-http-client/issues/883)) ([80725f2](https://github.com/ipfs/js-ipfs-http-client/commit/80725f2))\n\n\n\n<a name=\"26.0.3\"></a>\n## [26.0.3](https://github.com/ipfs/js-ipfs-http-client/compare/v26.0.2...v26.0.3) (2018-10-31)\n\n\n\n<a name=\"26.0.2\"></a>\n## [26.0.2](https://github.com/ipfs/js-ipfs-http-client/compare/v26.0.0...v26.0.2) (2018-10-31)\n\n\n### Bug Fixes\n\n* pin.ls ignored opts when hash was present ([#875](https://github.com/ipfs/js-ipfs-http-client/issues/875)) ([0b46750](https://github.com/ipfs/js-ipfs-http-client/commit/0b46750)), closes [/github.com/ipfs-shipyard/ipfs-companion/issues/360#issuecomment-427525801](https://github.com//github.com/ipfs-shipyard/ipfs-companion/issues/360/issues/issuecomment-427525801)\n\n\n\n<a name=\"26.0.1\"></a>\n## [26.0.1](https://github.com/ipfs/js-ipfs-http-client/compare/v26.0.0...v26.0.1) (2018-10-30)\n\n\n\n<a name=\"26.0.0\"></a>\n## [26.0.0](https://github.com/ipfs/js-ipfs-http-client/compare/v25.0.0...v26.0.0) (2018-10-30)\n\n\n### Bug Fixes\n\n* add missing and remove unused dependencies ([#879](https://github.com/ipfs/js-ipfs-http-client/issues/879)) ([979d8b5](https://github.com/ipfs/js-ipfs-http-client/commit/979d8b5))\n\n\n### Chores\n\n* remove ipld formats re-export ([#872](https://github.com/ipfs/js-ipfs-http-client/issues/872)) ([c534375](https://github.com/ipfs/js-ipfs-http-client/commit/c534375))\n* update to ipld-dag-cbor 0.13 ([0652ac0](https://github.com/ipfs/js-ipfs-http-client/commit/0652ac0))\n\n\n### Features\n\n* ipns over pubsub ([#846](https://github.com/ipfs/js-ipfs-http-client/issues/846)) ([ef49e95](https://github.com/ipfs/js-ipfs-http-client/commit/ef49e95))\n\n\n### BREAKING CHANGES\n\n* dag-cbor nodes now represent links as CID objects\n\nThe API for [dag-cbor](https://github.com/ipld/js-ipld-dag-cbor) changed.\nLinks are no longer represented as JSON objects (`{\"/\": \"base-encoded-cid\"}`,\nbut as [CID objects](https://github.com/ipld/js-cid). `ipfs.dag.get()` and\nnow always return links as CID objects. `ipfs.dag.put()` also expects links\nto be represented as CID objects. The old-style JSON objects representation\nis still supported, but deprecated.\n\nPrior to this change:\n\n```js\nconst cid = new CID('QmXed8RihWcWFXRRmfSRG9yFjEbXNxu1bDwgCFAN8Dxcq5')\n// Link as JSON object representation\nconst putCid = await ipfs.dag.put({link: {'/': cid.toBaseEncodedString()}})\nconst result = await ipfs.dag.get(putCid)\nconsole.log(result.value)\n\n```\n\nOutput:\n\n```js\n{ link:\n   { '/':\n      <Buffer 12 20 8a…> } }\n```\n\nNow:\n\n```js\nconst cid = new CID('QmXed8RihWcWFXRRmfSRG9yFjEbXNxu1bDwgCFAN8Dxcq5')\n// Link as CID object\nconst putCid = await ipfs.dag.put({link: cid})\nconst result = await ipfs.dag.get(putCid)\nconsole.log(result.value)\n```\n\nOutput:\n\n```js\n{ link:\n   CID {\n     codec: 'dag-pb',\n     version: 0,\n     multihash:\n      <Buffer 12 20 8a…> } }\n```\n\nSee https://github.com/ipld/ipld/issues/44 for more information on why this\nchange was made.\n* remove `types.dagCBOR` and `types.dagPB` from public API\n\nIf you need the `ipld-dag-cbor` or `ipld-dag-pb` module in the Browser,\nyou need to bundle them yourself.\n\n\n\n<a name=\"25.0.0\"></a>\n## [25.0.0](https://github.com/ipfs/js-ipfs-http-client/compare/v24.0.2...v25.0.0) (2018-10-15)\n\n\n### Bug Fixes\n\n* >150mb bodies no longer crashing Chromium ([#868](https://github.com/ipfs/js-ipfs-http-client/issues/868)) ([180da77](https://github.com/ipfs/js-ipfs-http-client/commit/180da77)), closes [#654](https://github.com/ipfs/js-ipfs-http-client/issues/654)\n* add bl module to package dependencies ([#853](https://github.com/ipfs/js-ipfs-http-client/issues/853)) ([#854](https://github.com/ipfs/js-ipfs-http-client/issues/854)) ([834934f](https://github.com/ipfs/js-ipfs-http-client/commit/834934f))\n* add lodash dependency ([#873](https://github.com/ipfs/js-ipfs-http-client/issues/873)) ([c510cb7](https://github.com/ipfs/js-ipfs-http-client/commit/c510cb7)), closes [#870](https://github.com/ipfs/js-ipfs-http-client/issues/870)\n\n\n\n<a name=\"24.0.2\"></a>\n## [24.0.2](https://github.com/ipfs/js-ipfs-http-client/compare/v24.0.1...v24.0.2) (2018-09-21)\n\n\n### Bug Fixes\n\n* block.put options ([#844](https://github.com/ipfs/js-ipfs-http-client/issues/844)) ([e290a38](https://github.com/ipfs/js-ipfs-http-client/commit/e290a38))\n\n\n\n<a name=\"24.0.1\"></a>\n## [24.0.1](https://github.com/ipfs/js-ipfs-http-client/compare/v24.0.0...v24.0.1) (2018-08-21)\n\n\n\n<a name=\"24.0.0\"></a>\n## [24.0.0](https://github.com/ipfs/js-ipfs-http-client/compare/v23.0.0...v24.0.0) (2018-08-15)\n\n\n### Bug Fixes\n\n* add test data to IPFS before fetching it ([#832](https://github.com/ipfs/js-ipfs-http-client/issues/832)) ([b2a77d6](https://github.com/ipfs/js-ipfs-http-client/commit/b2a77d6))\n* BREAKING CHANGE use data-encoding arg so data is not corrupted ([#806](https://github.com/ipfs/js-ipfs-http-client/issues/806)) ([553c3fb](https://github.com/ipfs/js-ipfs-http-client/commit/553c3fb))\n* dag.get return error on missing multicodec ([#831](https://github.com/ipfs/js-ipfs-http-client/issues/831)) ([ff7c7e5](https://github.com/ipfs/js-ipfs-http-client/commit/ff7c7e5))\n* remove external urls from addFromURL tests ([#834](https://github.com/ipfs/js-ipfs-http-client/issues/834)) ([7cf7998](https://github.com/ipfs/js-ipfs-http-client/commit/7cf7998)), closes [#803](https://github.com/ipfs/js-ipfs-http-client/issues/803)\n\n\n### BREAKING CHANGES\n\n* Requires go-ipfs 0.4.17 as it allows for specifying the data encoding format when requesting object data.\n\n\n\n<a name=\"23.0.0\"></a>\n## [23.0.0](https://github.com/ipfs/js-ipfs-http-client/compare/v22.3.0...v23.0.0) (2018-08-06)\n\n\n### Bug Fixes\n\n* config get ([#825](https://github.com/ipfs/js-ipfs-http-client/issues/825)) ([ef5a4a3](https://github.com/ipfs/js-ipfs-http-client/commit/ef5a4a3))\n\n\n### Features\n\n* add resolve cmd ([#826](https://github.com/ipfs/js-ipfs-http-client/issues/826)) ([c7ad0e4](https://github.com/ipfs/js-ipfs-http-client/commit/c7ad0e4))\n\n\n\n<a name=\"22.3.0\"></a>\n## [22.3.0](https://github.com/ipfs/js-ipfs-http-client/compare/v22.2.4...v22.3.0) (2018-08-02)\n\n\n### Bug Fixes\n\n* config.set rejects buffer values ([#800](https://github.com/ipfs/js-ipfs-http-client/issues/800)) ([f3e6bf1](https://github.com/ipfs/js-ipfs-http-client/commit/f3e6bf1))\n\n\n### Features\n\n* compatible with go-ipfs 0.4.16 ([8536ee4](https://github.com/ipfs/js-ipfs-http-client/commit/8536ee4))\n* expose mfs files.read*Stream methods ([#823](https://github.com/ipfs/js-ipfs-http-client/issues/823)) ([70c9df1](https://github.com/ipfs/js-ipfs-http-client/commit/70c9df1))\n\n\n\n<a name=\"22.2.4\"></a>\n## [22.2.4](https://github.com/ipfs/js-ipfs-http-client/compare/v22.2.3...v22.2.4) (2018-07-17)\n\n\n### Bug Fixes\n\n* increase browserNoActivityTimeout to account for before ([328e338](https://github.com/ipfs/js-ipfs-http-client/commit/328e338))\n* increase timeout for .name after all ([3dc4313](https://github.com/ipfs/js-ipfs-http-client/commit/3dc4313))\n* missing debug dependency fixes [#809](https://github.com/ipfs/js-ipfs-http-client/issues/809) ([#810](https://github.com/ipfs/js-ipfs-http-client/issues/810)) ([0f1fe95](https://github.com/ipfs/js-ipfs-http-client/commit/0f1fe95))\n\n\n\n<a name=\"22.2.3\"></a>\n## [22.2.3](https://github.com/ipfs/js-ipfs-http-client/compare/v22.2.2...v22.2.3) (2018-07-10)\n\n\n### Bug Fixes\n\n* Request logging broken in Electron ([#808](https://github.com/ipfs/js-ipfs-http-client/issues/808)) ([52298ae](https://github.com/ipfs/js-ipfs-http-client/commit/52298ae))\n\n\n\n<a name=\"22.2.2\"></a>\n## [22.2.2](https://github.com/ipfs/js-ipfs-http-client/compare/v22.2.1...v22.2.2) (2018-07-05)\n\n\n### Bug Fixes\n\n* ignore response body for some mfs commands ([#805](https://github.com/ipfs/js-ipfs-http-client/issues/805)) ([b604a64](https://github.com/ipfs/js-ipfs-http-client/commit/b604a64))\n\n\n### Features\n\n* modular interface tests ([#785](https://github.com/ipfs/js-ipfs-http-client/issues/785)) ([2426072](https://github.com/ipfs/js-ipfs-http-client/commit/2426072)), closes [#339](https://github.com/ipfs/js-ipfs-http-client/issues/339) [#802](https://github.com/ipfs/js-ipfs-http-client/issues/802) [#801](https://github.com/ipfs/js-ipfs-http-client/issues/801)\n\n\n\n<a name=\"22.2.1\"></a>\n## [22.2.1](https://github.com/ipfs/js-ipfs-http-client/compare/v22.2.0...v22.2.1) (2018-06-29)\n\n\n### Bug Fixes\n\n* res.req only in Node.js, in browser use res.url instead ([#798](https://github.com/ipfs/js-ipfs-http-client/issues/798)) ([e8a5ab9](https://github.com/ipfs/js-ipfs-http-client/commit/e8a5ab9))\n\n\n\n<a name=\"22.2.0\"></a>\n## [22.2.0](https://github.com/ipfs/js-ipfs-http-client/compare/v22.1.1...v22.2.0) (2018-06-29)\n\n\n### Features\n\n* logs path & querystring for requests ([#796](https://github.com/ipfs/js-ipfs-http-client/issues/796)) ([4e55d19](https://github.com/ipfs/js-ipfs-http-client/commit/4e55d19))\n\n\n\n<a name=\"22.1.1\"></a>\n## [22.1.1](https://github.com/ipfs/js-ipfs-http-client/compare/v22.1.0...v22.1.1) (2018-06-25)\n\n\n### Bug Fixes\n\n* get block with empty data ([#789](https://github.com/ipfs/js-ipfs-http-client/issues/789)) ([88edd83](https://github.com/ipfs/js-ipfs-http-client/commit/88edd83))\n\n\n\n<a name=\"22.1.0\"></a>\n## [22.1.0](https://github.com/ipfs/js-ipfs-http-client/compare/v22.0.2...v22.1.0) (2018-06-18)\n\n\n### Features\n\n* add support for custom headers to send-request ([#741](https://github.com/ipfs/js-ipfs-http-client/issues/741)) ([7fb2e07](https://github.com/ipfs/js-ipfs-http-client/commit/7fb2e07))\n* implement bitswap wantlist peer ID param and bitswap unwant ([#761](https://github.com/ipfs/js-ipfs-http-client/issues/761)) ([73a153e](https://github.com/ipfs/js-ipfs-http-client/commit/73a153e))\n\n\n\n<a name=\"22.0.2\"></a>\n## [22.0.2](https://github.com/ipfs/js-ipfs-http-client/compare/v22.0.1...v22.0.2) (2018-06-14)\n\n\n### Bug Fixes\n\n* json-loader error in upload-file-via-browser example ([#784](https://github.com/ipfs/js-ipfs-http-client/issues/784)) ([5e7b7c4](https://github.com/ipfs/js-ipfs-http-client/commit/5e7b7c4))\n\n\n\n<a name=\"22.0.1\"></a>\n## [22.0.1](https://github.com/ipfs/js-ipfs-http-client/compare/v22.0.0...v22.0.1) (2018-05-30)\n\n\n### Bug Fixes\n\n* configure webpack to not use esmodules in dependencies ([dc14333](https://github.com/ipfs/js-ipfs-http-client/commit/dc14333))\n* correctly differentiate pong responses ([4ad25a3](https://github.com/ipfs/js-ipfs-http-client/commit/4ad25a3))\n* util.addFromURL with URL-escaped file ([a3bd811](https://github.com/ipfs/js-ipfs-http-client/commit/a3bd811))\n\n\n\n<a name=\"22.0.0\"></a>\n## [22.0.0](https://github.com/ipfs/js-ipfs-http-client/compare/v21.0.0...v22.0.0) (2018-05-20)\n\n\n### Bug Fixes\n\n* callback from unsub after stream ends ([51a80f2](https://github.com/ipfs/js-ipfs-http-client/commit/51a80f2))\n* do not fail stop node if failed start node ([533760f](https://github.com/ipfs/js-ipfs-http-client/commit/533760f))\n* **ping:** convert the ping messages to lowercase ([632af40](https://github.com/ipfs/js-ipfs-http-client/commit/632af40))\n* more robust ping tests ([fc6d301](https://github.com/ipfs/js-ipfs-http-client/commit/fc6d301))\n* remove .only ([0e21c8a](https://github.com/ipfs/js-ipfs-http-client/commit/0e21c8a))\n* result.Peers can be null, ensure callback is called ([f5f2e83](https://github.com/ipfs/js-ipfs-http-client/commit/f5f2e83))\n* update asserted error message ([17c1f1c](https://github.com/ipfs/js-ipfs-http-client/commit/17c1f1c))\n* use async/setImmediate vs process.nextTick ([faa51b4](https://github.com/ipfs/js-ipfs-http-client/commit/faa51b4))\n\n\n\n<a name=\"21.0.0\"></a>\n## [21.0.0](https://github.com/ipfs/js-ipfs-http-client/compare/v20.2.1...v21.0.0) (2018-05-12)\n\n\n### Bug Fixes\n\n* make pubsub.unsubscribe async and alter pubsub.subscribe signature ([b98f8f3](https://github.com/ipfs/js-ipfs-http-client/commit/b98f8f3))\n\n\n### BREAKING CHANGES\n\n* pubsub.unsubscribe is now async and argument order for pubsub.subscribe has changed\n\nLicense: MIT\nSigned-off-by: Alan Shaw <alan@tableflip.io>\n\n\n\n<a name=\"20.2.1\"></a>\n## [20.2.1](https://github.com/ipfs/js-ipfs-http-client/compare/v20.2.0...v20.2.1) (2018-05-06)\n\n\n\n<a name=\"20.2.0\"></a>\n## [20.2.0](https://github.com/ipfs/js-ipfs-http-client/compare/v20.0.1...v20.2.0) (2018-04-30)\n\n\n### Bug Fixes\n\n* adding files by pull stream ([2fa16c5](https://github.com/ipfs/js-ipfs-http-client/commit/2fa16c5))\n* handle request errors in addFromURL ([7c5cea5](https://github.com/ipfs/js-ipfs-http-client/commit/7c5cea5))\n* increase timeout for name.publish and fix setup code ([ceb1106](https://github.com/ipfs/js-ipfs-http-client/commit/ceb1106))\n* ipfs add url wrap doesn't work ([#750](https://github.com/ipfs/js-ipfs-http-client/issues/750)) ([f6f1bf0](https://github.com/ipfs/js-ipfs-http-client/commit/f6f1bf0))\n\n\n### Features\n\n* Add offset/length arguments to files.cat ([17967c1](https://github.com/ipfs/js-ipfs-http-client/commit/17967c1))\n* get it ready for release ([#751](https://github.com/ipfs/js-ipfs-http-client/issues/751)) ([1885af4](https://github.com/ipfs/js-ipfs-http-client/commit/1885af4))\n\n\n\n<a name=\"20.1.0\"></a>\n## [20.1.0](https://github.com/ipfs/js-ipfs-http-client/compare/v20.0.1...v20.1.0) (2018-04-30)\n\n\n### Bug Fixes\n\n* adding files by pull stream ([2fa16c5](https://github.com/ipfs/js-ipfs-http-client/commit/2fa16c5))\n* handle request errors in addFromURL ([7c5cea5](https://github.com/ipfs/js-ipfs-http-client/commit/7c5cea5))\n* increase timeout for name.publish and fix setup code ([ceb1106](https://github.com/ipfs/js-ipfs-http-client/commit/ceb1106))\n* ipfs add url wrap doesn't work ([#750](https://github.com/ipfs/js-ipfs-http-client/issues/750)) ([f6f1bf0](https://github.com/ipfs/js-ipfs-http-client/commit/f6f1bf0))\n\n\n### Features\n\n* Add offset/length arguments to files.cat ([17967c1](https://github.com/ipfs/js-ipfs-http-client/commit/17967c1))\n* get it ready for release ([#751](https://github.com/ipfs/js-ipfs-http-client/issues/751)) ([1885af4](https://github.com/ipfs/js-ipfs-http-client/commit/1885af4))\n\n\n\n<a name=\"20.0.1\"></a>\n## [20.0.1](https://github.com/ipfs/js-ipfs-http-client/compare/v20.0.0...v20.0.1) (2018-04-12)\n\n\n\n<a name=\"20.0.0\"></a>\n## [20.0.0](https://github.com/ipfs/js-ipfs-http-client/compare/v19.0.0...v20.0.0) (2018-04-05)\n\n\n### Bug Fixes\n\n* **dag:** js-ipld format resolver take the raw block ([2683c7e](https://github.com/ipfs/js-ipfs-http-client/commit/2683c7e))\n* **dag:** path logic for DAG get was wrong ([d2b203b](https://github.com/ipfs/js-ipfs-http-client/commit/d2b203b))\n* **dag:** use SendOneFile for dag put ([9c37213](https://github.com/ipfs/js-ipfs-http-client/commit/9c37213))\n\n\n### Features\n\n* dag.put ([9463d3a](https://github.com/ipfs/js-ipfs-http-client/commit/9463d3a))\n* **dag:** proper get implementation ([7ba0343](https://github.com/ipfs/js-ipfs-http-client/commit/7ba0343))\n* **dag:** rebase, use waterfall for put ([ad9eab8](https://github.com/ipfs/js-ipfs-http-client/commit/ad9eab8))\n* **dag:** update option names to reflect go-ipfs API ([9bf1c6c](https://github.com/ipfs/js-ipfs-http-client/commit/9bf1c6c))\n* Provide access to bundled libraries when in browser ([#732](https://github.com/ipfs/js-ipfs-http-client/issues/732)) ([994bdad](https://github.com/ipfs/js-ipfs-http-client/commit/994bdad)), closes [#406](https://github.com/ipfs/js-ipfs-http-client/issues/406)\n* public-readonly-method-for-getting-host-and-port ([41d32e3](https://github.com/ipfs/js-ipfs-http-client/commit/41d32e3)), closes [#580](https://github.com/ipfs/js-ipfs-http-client/issues/580)\n* Wrap with dir ([#730](https://github.com/ipfs/js-ipfs-http-client/issues/730)) ([160860e](https://github.com/ipfs/js-ipfs-http-client/commit/160860e))\n\n\n\n<a name=\"19.0.0\"></a>\n## [19.0.0](https://github.com/ipfs/js-ipfs-http-client/compare/v18.2.1...v19.0.0) (2018-03-28)\n\n\n### Bug Fixes\n\n* **bitswap:** 0.4.14 returns empty array instead of null ([5e37a54](https://github.com/ipfs/js-ipfs-http-client/commit/5e37a54))\n* **ping:** tests were failing and there it was missing to catch when count and n are used at the same time ([2181568](https://github.com/ipfs/js-ipfs-http-client/commit/2181568))\n\n\n### Features\n\n* streamable ping and optional packet number ([#723](https://github.com/ipfs/js-ipfs-http-client/issues/723)) ([3f3ce8a](https://github.com/ipfs/js-ipfs-http-client/commit/3f3ce8a))\n\n\n\n<a name=\"18.2.1\"></a>\n## [18.2.1](https://github.com/ipfs/js-ipfs-http-client/compare/v18.2.0...v18.2.1) (2018-03-22)\n\n\n### Features\n\n* add ability to files.cat with a cid instance ([aeeb94e](https://github.com/ipfs/js-ipfs-http-client/commit/aeeb94e))\n\n\n\n<a name=\"18.2.0\"></a>\n## [18.2.0](https://github.com/ipfs/js-ipfs-http-client/compare/v18.1.2...v18.2.0) (2018-03-16)\n\n\n### Bug Fixes\n\n* disable Browser test on Windows ([385a6c3](https://github.com/ipfs/js-ipfs-http-client/commit/385a6c3))\n* don't create one webpack bundle for every test file ([3967e96](https://github.com/ipfs/js-ipfs-http-client/commit/3967e96))\n* last fixes for green ([#719](https://github.com/ipfs/js-ipfs-http-client/issues/719)) ([658bad2](https://github.com/ipfs/js-ipfs-http-client/commit/658bad2))\n* set the FileResultStreamConverter explicitly ([dfad55e](https://github.com/ipfs/js-ipfs-http-client/commit/dfad55e)), closes [#696](https://github.com/ipfs/js-ipfs-http-client/issues/696)\n* use a different remote server for test ([1fc15a5](https://github.com/ipfs/js-ipfs-http-client/commit/1fc15a5))\n\n\n### Features\n\n* --only-hash ([#717](https://github.com/ipfs/js-ipfs-http-client/issues/717)) ([1137401](https://github.com/ipfs/js-ipfs-http-client/commit/1137401)), closes [#700](https://github.com/ipfs/js-ipfs-http-client/issues/700)\n* add support for ipfs files stat --with-local ([#695](https://github.com/ipfs/js-ipfs-http-client/issues/695)) ([b08f21a](https://github.com/ipfs/js-ipfs-http-client/commit/b08f21a))\n\n\n\n<a name=\"18.1.2\"></a>\n## [18.1.2](https://github.com/ipfs/js-ipfs-http-client/compare/v18.1.1...v18.1.2) (2018-03-09)\n\n\n### Bug Fixes\n\n* regression on files.add and update deps ([#709](https://github.com/ipfs/js-ipfs-http-client/issues/709)) ([85cc2a8](https://github.com/ipfs/js-ipfs-http-client/commit/85cc2a8))\n* remove argument from .stats.bw* ([#699](https://github.com/ipfs/js-ipfs-http-client/issues/699)) ([f81dce5](https://github.com/ipfs/js-ipfs-http-client/commit/f81dce5))\n\n\n\n<a name=\"18.1.1\"></a>\n## [18.1.1](https://github.com/ipfs/js-ipfs-http-client/compare/v18.0.0...v18.1.1) (2018-02-20)\n\n\n### Features\n\n* support recursive ipfs ls  ([cfe95f6](https://github.com/ipfs/js-ipfs-http-client/commit/cfe95f6))\n\n\n\n<a name=\"18.1.0\"></a>\n## [18.1.0](https://github.com/ipfs/js-ipfs-http-client/compare/v18.0.0...v18.1.0) (2018-02-20)\n\n\n### Features\n\n* support recursive ipfs ls  ([cfe95f6](https://github.com/ipfs/js-ipfs-http-client/commit/cfe95f6))\n\n\n\n<a name=\"18.0.0\"></a>\n## [18.0.0](https://github.com/ipfs/js-ipfs-http-client/compare/v17.5.0...v18.0.0) (2018-02-14)\n\n\n### Bug Fixes\n\n* exception when dir is empty ([#680](https://github.com/ipfs/js-ipfs-http-client/issues/680)) ([ec04f6e](https://github.com/ipfs/js-ipfs-http-client/commit/ec04f6e))\n* support all the Buffer shims and load fixtures correctly ([066988f](https://github.com/ipfs/js-ipfs-http-client/commit/066988f))\n* update stats API ([#684](https://github.com/ipfs/js-ipfs-http-client/issues/684)) ([4f7999d](https://github.com/ipfs/js-ipfs-http-client/commit/4f7999d))\n\n\n### Features\n\n* (breaking change) stats spec, spec repo, stream to value on files read ([#679](https://github.com/ipfs/js-ipfs-http-client/issues/679)) ([118456e](https://github.com/ipfs/js-ipfs-http-client/commit/118456e))\n* **breaking change:** use stream on stats.bw ([#686](https://github.com/ipfs/js-ipfs-http-client/issues/686)) ([895760e](https://github.com/ipfs/js-ipfs-http-client/commit/895760e))\n* ipfs.stop ([5091115](https://github.com/ipfs/js-ipfs-http-client/commit/5091115))\n\n\n\n<a name=\"17.5.0\"></a>\n## [17.5.0](https://github.com/ipfs/js-ipfs-http-client/compare/v17.3.0...v17.5.0) (2018-01-24)\n\n\n### Bug Fixes\n\n* normalize stats fields ([#669](https://github.com/ipfs/js-ipfs-http-client/issues/669)) ([5803d39](https://github.com/ipfs/js-ipfs-http-client/commit/5803d39))\n\n\n### Features\n\n* /api/v0/repo/version ([#676](https://github.com/ipfs/js-ipfs-http-client/issues/676)) ([ecf70b9](https://github.com/ipfs/js-ipfs-http-client/commit/ecf70b9))\n* integrate new ipfsd-ctl ([2b1820b](https://github.com/ipfs/js-ipfs-http-client/commit/2b1820b))\n\n\n\n<a name=\"17.4.0\"></a>\n## [17.4.0](https://github.com/ipfs/js-ipfs-http-client/compare/v17.3.0...v17.4.0) (2018-01-24)\n\n\n### Bug Fixes\n\n* normalize stats fields ([#669](https://github.com/ipfs/js-ipfs-http-client/issues/669)) ([5803d39](https://github.com/ipfs/js-ipfs-http-client/commit/5803d39))\n\n\n### Features\n\n* integrate new ipfsd-ctl ([2b1820b](https://github.com/ipfs/js-ipfs-http-client/commit/2b1820b))\n\n\n\n<a name=\"17.3.0\"></a>\n## [17.3.0](https://github.com/ipfs/js-ipfs-http-client/compare/v17.2.7...v17.3.0) (2018-01-12)\n\n\n### Features\n\n* /api/v0/dns ([#665](https://github.com/ipfs/js-ipfs-http-client/issues/665)) ([81016bb](https://github.com/ipfs/js-ipfs-http-client/commit/81016bb))\n\n\n\n<a name=\"17.2.7\"></a>\n## [17.2.7](https://github.com/ipfs/js-ipfs-http-client/compare/v17.2.6...v17.2.7) (2018-01-11)\n\n\n### Bug Fixes\n\n* name and key tests ([#661](https://github.com/ipfs/js-ipfs-http-client/issues/661)) ([5ab1d02](https://github.com/ipfs/js-ipfs-http-client/commit/5ab1d02))\n\n\n### Features\n\n* normalize KEY API ([#659](https://github.com/ipfs/js-ipfs-http-client/issues/659)) ([1b10821](https://github.com/ipfs/js-ipfs-http-client/commit/1b10821))\n* normalize NAME API ([#658](https://github.com/ipfs/js-ipfs-http-client/issues/658)) ([9b8ef48](https://github.com/ipfs/js-ipfs-http-client/commit/9b8ef48))\n\n\n\n<a name=\"17.2.6\"></a>\n## [17.2.6](https://github.com/ipfs/js-ipfs-http-client/compare/v17.2.5...v17.2.6) (2017-12-28)\n\n\n### Features\n\n* support key/export and key/import ([#653](https://github.com/ipfs/js-ipfs-http-client/issues/653)) ([496f08e](https://github.com/ipfs/js-ipfs-http-client/commit/496f08e))\n\n\n\n<a name=\"17.2.5\"></a>\n## [17.2.5](https://github.com/ipfs/js-ipfs-http-client/compare/v17.2.4...v17.2.5) (2017-12-20)\n\n\n### Bug Fixes\n\n* **files.add:** handle weird directory names ([#646](https://github.com/ipfs/js-ipfs-http-client/issues/646)) ([012b86c](https://github.com/ipfs/js-ipfs-http-client/commit/012b86c))\n\n\n### Features\n\n* add files/flush ([#643](https://github.com/ipfs/js-ipfs-http-client/issues/643)) ([5c254eb](https://github.com/ipfs/js-ipfs-http-client/commit/5c254eb))\n* support key/rm and key/rename ([#641](https://github.com/ipfs/js-ipfs-http-client/issues/641)) ([113030a](https://github.com/ipfs/js-ipfs-http-client/commit/113030a))\n\n\n\n<a name=\"17.2.4\"></a>\n## [17.2.4](https://github.com/ipfs/js-ipfs-http-client/compare/v17.2.3...v17.2.4) (2017-12-06)\n\n\n### Bug Fixes\n\n* stats/bw uses stream ([#640](https://github.com/ipfs/js-ipfs-http-client/issues/640)) ([c4e922e](https://github.com/ipfs/js-ipfs-http-client/commit/c4e922e))\n\n\n\n<a name=\"17.2.3\"></a>\n## [17.2.3](https://github.com/ipfs/js-ipfs-http-client/compare/v17.2.2...v17.2.3) (2017-12-05)\n\n\n\n<a name=\"17.2.2\"></a>\n## [17.2.2](https://github.com/ipfs/js-ipfs-http-client/compare/v17.2.1...v17.2.2) (2017-12-05)\n\n\n\n<a name=\"17.2.1\"></a>\n## [17.2.1](https://github.com/ipfs/js-ipfs-http-client/compare/v17.2.0...v17.2.1) (2017-12-05)\n\n\n### Features\n\n* add the stat commands ([#639](https://github.com/ipfs/js-ipfs-http-client/issues/639)) ([76c3068](https://github.com/ipfs/js-ipfs-http-client/commit/76c3068))\n\n\n\n<a name=\"17.2.0\"></a>\n## [17.2.0](https://github.com/ipfs/js-ipfs-http-client/compare/v17.1.3...v17.2.0) (2017-12-01)\n\n\n### Bug Fixes\n\n* propagate trailer errors correctly ([#636](https://github.com/ipfs/js-ipfs-http-client/issues/636)) ([62d733e](https://github.com/ipfs/js-ipfs-http-client/commit/62d733e))\n\n\n\n<a name=\"17.1.3\"></a>\n## [17.1.3](https://github.com/ipfs/js-ipfs-http-client/compare/v17.1.2...v17.1.3) (2017-11-23)\n\n\n\n<a name=\"17.1.2\"></a>\n## [17.1.2](https://github.com/ipfs/js-ipfs-http-client/compare/v17.1.1...v17.1.2) (2017-11-22)\n\n\n### Bug Fixes\n\n* config.replace ([#634](https://github.com/ipfs/js-ipfs-http-client/issues/634)) ([79d79c5](https://github.com/ipfs/js-ipfs-http-client/commit/79d79c5)), closes [#633](https://github.com/ipfs/js-ipfs-http-client/issues/633)\n\n\n\n<a name=\"17.1.1\"></a>\n## [17.1.1](https://github.com/ipfs/js-ipfs-http-client/compare/v17.1.0...v17.1.1) (2017-11-22)\n\n\n### Bug Fixes\n\n* pubsub do not eat error messages ([#632](https://github.com/ipfs/js-ipfs-http-client/issues/632)) ([5a1bf9b](https://github.com/ipfs/js-ipfs-http-client/commit/5a1bf9b))\n\n\n\n<a name=\"17.1.0\"></a>\n## [17.1.0](https://github.com/ipfs/js-ipfs-http-client/compare/v17.0.1...v17.1.0) (2017-11-20)\n\n\n### Features\n\n* send files HTTP request should stream ([#629](https://github.com/ipfs/js-ipfs-http-client/issues/629)) ([dae62cb](https://github.com/ipfs/js-ipfs-http-client/commit/dae62cb))\n\n\n\n<a name=\"17.0.1\"></a>\n## [17.0.1](https://github.com/ipfs/js-ipfs-http-client/compare/v17.0.0...v17.0.1) (2017-11-20)\n\n\n### Bug Fixes\n\n* allow topicCIDs from older peers ([#631](https://github.com/ipfs/js-ipfs-http-client/issues/631)) ([fe7cc22](https://github.com/ipfs/js-ipfs-http-client/commit/fe7cc22))\n\n\n\n<a name=\"17.0.0\"></a>\n## [17.0.0](https://github.com/ipfs/js-ipfs-http-client/compare/v16.0.0...v17.0.0) (2017-11-17)\n\n\n### Features\n\n* Implementing the new interfaces ([#619](https://github.com/ipfs/js-ipfs-http-client/issues/619)) ([e1b38bf](https://github.com/ipfs/js-ipfs-http-client/commit/e1b38bf))\n\n\n\n<a name=\"16.0.0\"></a>\n## [16.0.0](https://github.com/ipfs/js-ipfs-http-client/compare/v15.1.0...v16.0.0) (2017-11-16)\n\n\n### Bug Fixes\n\n* pubsub message fields ([#627](https://github.com/ipfs/js-ipfs-http-client/issues/627)) ([470777d](https://github.com/ipfs/js-ipfs-http-client/commit/470777d))\n\n\n\n<a name=\"15.1.0\"></a>\n## [15.1.0](https://github.com/ipfs/js-ipfs-http-client/compare/v15.0.2...v15.1.0) (2017-11-14)\n\n\n### Bug Fixes\n\n* adapting HTTP API to the interface-ipfs-core spec ([#625](https://github.com/ipfs/js-ipfs-http-client/issues/625)) ([8e58225](https://github.com/ipfs/js-ipfs-http-client/commit/8e58225))\n\n\n### Features\n\n* windows interop ([#624](https://github.com/ipfs/js-ipfs-http-client/issues/624)) ([40557d0](https://github.com/ipfs/js-ipfs-http-client/commit/40557d0))\n\n\n\n<a name=\"15.0.2\"></a>\n## [15.0.2](https://github.com/ipfs/js-ipfs-http-client/compare/v15.0.1...v15.0.2) (2017-11-13)\n\n\n\n<a name=\"15.0.1\"></a>\n## [15.0.1](https://github.com/ipfs/js-ipfs-http-client/compare/v15.0.0...v15.0.1) (2017-10-22)\n\n\n\n<a name=\"15.0.0\"></a>\n## [15.0.0](https://github.com/ipfs/js-ipfs-http-client/compare/v14.3.7...v15.0.0) (2017-10-22)\n\n\n### Features\n\n* update pin API to match interface-ipfs-core ([9102643](https://github.com/ipfs/js-ipfs-http-client/commit/9102643))\n\n\n\n<a name=\"14.3.7\"></a>\n## [14.3.7](https://github.com/ipfs/js-ipfs-http-client/compare/v14.3.6...v14.3.7) (2017-10-18)\n\n\n\n<a name=\"14.3.6\"></a>\n## [14.3.6](https://github.com/ipfs/js-ipfs-http-client/compare/v14.3.5...v14.3.6) (2017-10-18)\n\n\n### Bug Fixes\n\n* pass the config protocol to http requests ([#609](https://github.com/ipfs/js-ipfs-http-client/issues/609)) ([38d7289](https://github.com/ipfs/js-ipfs-http-client/commit/38d7289))\n\n\n### Features\n\n* avoid doing multiple RPC requests for files.add, fixes [#522](https://github.com/ipfs/js-ipfs-http-client/issues/522) ([#595](https://github.com/ipfs/js-ipfs-http-client/issues/595)) ([0ea5f57](https://github.com/ipfs/js-ipfs-http-client/commit/0ea5f57))\n* report progress on ipfs add  ([e2d894c](https://github.com/ipfs/js-ipfs-http-client/commit/e2d894c))\n\n\n\n<a name=\"14.3.5\"></a>\n## [14.3.5](https://github.com/ipfs/js-ipfs-http-client/compare/v14.3.4...v14.3.5) (2017-09-08)\n\n\n### Features\n\n* Support specify hash algorithm in files.add ([#597](https://github.com/ipfs/js-ipfs-http-client/issues/597)) ([ed68657](https://github.com/ipfs/js-ipfs-http-client/commit/ed68657))\n\n\n\n<a name=\"14.3.4\"></a>\n## [14.3.4](https://github.com/ipfs/js-ipfs-http-client/compare/v14.3.3...v14.3.4) (2017-09-07)\n\n\n\n<a name=\"14.3.3\"></a>\n## [14.3.3](https://github.com/ipfs/js-ipfs-http-client/compare/v14.3.2...v14.3.3) (2017-09-07)\n\n\n### Features\n\n* support options for .add / files.add  ([8c717b2](https://github.com/ipfs/js-ipfs-http-client/commit/8c717b2))\n\n\n\n<a name=\"14.3.2\"></a>\n## [14.3.2](https://github.com/ipfs/js-ipfs-http-client/compare/v14.3.1...v14.3.2) (2017-09-04)\n\n\n### Bug Fixes\n\n* new fixed aegir ([93ac472](https://github.com/ipfs/js-ipfs-http-client/commit/93ac472))"
  },
  {
    "path": "packages/ipfs-http-client/CONTRIBUTING.md",
    "content": "# Contributing\n\n## Setup\n\nYou should have [node.js] and [npm] installed.\n\n## Linting\n\nLinting is done using [eslint] and the rules are based on [standard].\n\n```bash\n$ npm run lint\n```\n\n## Tests\n\nTests in node\n\n```bash\n$ npm run test:node\n```\n\nTests in the browser\n\n```bash\n$ npm run test:browser\n```\n\n### Writing a new core interface test\n\nThe core interface tests are kept in a separate repo, because they are used by multiple other projects. To add a core interface test, follow this guide:\n\n1. Clone this project repo and the interface core tests repo:\n    * `git clone https://github.com/ipfs/js-ipfs-http-client.git`\n    * `git clone https://github.com/ipfs/interface-js-ipfs-core.git`\n1. Install dependencies and globally [link](https://docs.npmjs.com/cli/link) the interface core tests:\n    * `cd interface-js-ipfs-core`\n    * `npm install`\n    * `npm link`\n1. Write your test\n1. Install dependencies for this project and link to the interface core tests\n    * `cd ../js-ipfs-http-client`\n    * `npm install`\n    * `npm link interface-ipfs-core`\n1. Run the tests:\n    * `npm test`\n\nNext:\n\n1. Send a PR to `ipfs/interface-js-ipfs-core` (please also add to the documentation!)\n1. This will be reviewed by a core contributor and they will perform the same steps as above\n1. When merged, a new version of `interface-ipfs-core` will be released\n1. Finally, a PR needs to be created or updated to `ipfs/js-ipfs-http-client` to use the new version\n\n## Building browser version\n\n```bash\n$ npm run build\n```\n\n## Releases\n\nThe `release` task will\n\n1. Run a build\n2. Commit the build\n3. Bump the version in `package.json`\n4. Commit the version change\n5. Create a git tag\n6. Run `git push` to `upstream/master` (You can change this with `--remote my-remote`)\n\n```bash\n# Major release\n$ npm run release-major\n# Minor relase\n$ npm run release-minor\n# Patch release\n$ npm run release\n```\n\n[node.js]: https://nodejs.org/\n[npm]: http://npmjs.org/\n[eslint]: http://eslint.org/\n[standard]: https://github.com/feross/standard\n"
  },
  {
    "path": "packages/ipfs-http-client/COPYRIGHT",
    "content": "This project is transitioning from an MIT-only license to a dual MIT/Apache-2.0 license.\nUnless otherwise noted, all code contributed prior to 2019-11-26 and not contributed by\na user listed in [this signoff issue](https://github.com/ipfs/js-ipfs-http-client/issues/1189) is\nlicensed under MIT-only. All new contributions (and past contributions since 2019-11-29)\nare licensed under a dual MIT/Apache-2.0 license.\n"
  },
  {
    "path": "packages/ipfs-http-client/LICENSE",
    "content": "This project is dual licensed under MIT and Apache-2.0.\n\nMIT: https://www.opensource.org/licenses/mit\nApache-2.0: https://www.apache.org/licenses/license-2.0\n"
  },
  {
    "path": "packages/ipfs-http-client/LICENSE-APACHE",
    "content": "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\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless 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.\n"
  },
  {
    "path": "packages/ipfs-http-client/LICENSE-MIT",
    "content": "The MIT License (MIT)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "packages/ipfs-http-client/README.md",
    "content": "> # ⛔️ DEPRECATED: [js-IPFS](https://github.com/ipfs/js-ipfs) has been superseded by [Helia](https://github.com/ipfs/helia)\n>\n> 📚 [Learn more about this deprecation](https://github.com/ipfs/js-ipfs/issues/4336) or [how to migrate](https://github.com/ipfs/helia/wiki/Migrating-from-js-IPFS)\n>\n> ⚠️ If you continue using this repo, please note that security fixes will not be provided\n\n# ipfs-http-client <!-- omit in toc -->\n\n[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech)\n[![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech)\n[![codecov](https://img.shields.io/codecov/c/github/ipfs/js-ipfs.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipfs)\n[![CI](https://img.shields.io/github/actions/workflow/status/ipfs/js-ipfs/test.yml?branch=master\\&style=flat-square)](https://github.com/ipfs/js-ipfs/actions/workflows/test.yml?query=branch%3Amaster)\n\n> A client library for the [IPFS HTTP API](https://github.com/ipfs/js-ipfs/tree/master/docs/core-api) exposed by js-ipfs.\n\n\n> Note: The client library for the [Kubo RPC API](https://docs.ipfs.tech/reference/kubo/rpc/) has moved into [js-kubo-rpc-client](https://github.com/ipfs/js-kubo-rpc-client).\n\n## Table of contents <!-- omit in toc -->\n\n- [Install](#install)\n  - [Browser `<script>` tag](#browser-script-tag)\n- [Getting Started](#getting-started)\n  - [Next Steps](#next-steps)\n- [Usage](#usage)\n    - [`create([options])`](#createoptions)\n    - [Parameters](#parameters)\n    - [Options](#options)\n    - [Returns](#returns)\n    - [Example](#example)\n  - [API](#api)\n  - [Additional Options](#additional-options)\n  - [Instance Utils](#instance-utils)\n  - [Static Types and Utils](#static-types-and-utils)\n    - [Glob source](#glob-source)\n      - [`globSource(path, pattern, [options])`](#globsourcepath-pattern-options)\n      - [Example](#example-1)\n    - [URL source](#url-source)\n      - [`urlSource(url)`](#urlsourceurl)\n      - [Example](#example-2)\n  - [Running the daemon with the right port](#running-the-daemon-with-the-right-port)\n  - [Importing the module and usage](#importing-the-module-and-usage)\n  - [In a web browser](#in-a-web-browser)\n  - [Custom Headers](#custom-headers)\n  - [Infura Header](#infura-header)\n  - [Global Timeouts](#global-timeouts)\n- [Development](#development)\n  - [Testing](#testing)\n- [Historical context](#historical-context)\n- [License](#license)\n- [Contribute](#contribute)\n\n## Install\n\n```console\n$ npm i ipfs-http-client\n```\n\n### Browser `<script>` tag\n\nLoading this module through a script tag will make it's exports available as `IpfsHttpClient` in the global namespace.\n\n```html\n<script src=\"https://unpkg.com/ipfs-http-client/dist/index.min.js\"></script>\n```\n\n<h1 align=\"center\">\n  <a href=\"https://ipfs.io\"><img width=\"650px\" src=\"https://ipfs.io/ipfs/QmQJ68PFMDdAsgCZvA1UVzzn18asVcf7HVvCDgpjiSCAse\" alt=\"IPFS http client lib logo\" /></a>\n</h1>\n\n<h3 align=\"center\">The JavaScript HTTP RPC API client library for js-ipfs.</h3>\n\n<p align=\"center\">\n  <a href=\"https://riot.im/app/#/room/#ipfs-dev:matrix.org\"><img src=\"https://img.shields.io/badge/matrix-%23ipfs%3Amatrix.org-blue.svg?style=flat\" /> </a>\n  <a href=\"http://webchat.freenode.net/?channels=%23ipfs\"><img src=\"https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat\" /></a>\n  <a href=\"https://github.com/ipfs/team-mgmt/blob/master/MGMT_JS_CORE_DEV.md\"><img src=\"https://img.shields.io/badge/team-mgmt-blue.svg?style=flat\" /></a>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://app.fossa.io/projects/git%2Bgithub.com%2Fipfs%2Fjs-ipfs?ref=badge_shield\" alt=\"FOSSA Status\"><img src=\"https://app.fossa.io/api/projects/git%2Bgithub.com%2Fipfs%2Fjs-ipfs.svg?type=shield\"/></a>\n  <a href=\"https://travis-ci.com/ipfs/js-ipfs\"><img src=\"https://flat.badgen.net/travis/ipfs/js-ipfs\" /></a>\n  <a href=\"https://codecov.io/gh/ipfs/js-ipfs-http-client\"><img src=\"https://img.shields.io/codecov/c/github/ipfs/js-ipfs-http-client/master.svg?style=flat-square\"></a>\n   <a href=\"https://bundlephobia.com/result?p=ipfs-http-client\"><img src=\"https://flat.badgen.net/bundlephobia/minzip/ipfs-http-client\"></a>\n  <br>\n  <a href=\"https://david-dm.org/ipfs/js-ipfs?path=packages/ipfs-http-client\"><img src=\"https://david-dm.org/ipfs/js-ipfs.svg?style=flat-square&path=packages/ipfs-http-client\" /></a>\n  <a href=\"https://github.com/feross/standard\"><img src=\"https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square\"></a>\n  <a href=\"https://github.com/RichardLitt/standard-readme\"><img src=\"https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square\" /></a>\n  <a href=\"\"><img src=\"https://img.shields.io/badge/npm-%3E%3D3.0.0-orange.svg?style=flat-square\" /></a>\n  <a href=\"\"><img src=\"https://img.shields.io/badge/Node.js-%3E%3D10.0.0-orange.svg?style=flat-square\" /></a>\n  <a href=\"https://www.npmjs.com/package/ipfs-http-client\"><img src=\"https://img.shields.io/npm/dm/ipfs-http-client.svg\" /></a>\n  <a href=\"https://www.jsdelivr.com/package/npm/ipfs-http-client\"><img src=\"https://data.jsdelivr.com/v1/package/npm/ipfs-http-client/badge\"/></a>\n  <br>\n</p>\n\n## Getting Started\n\nWe've come a long way, but this project is still in Alpha, lots of development is happening, APIs might change, beware of 🐉..\n\n```bash\nnpm install --save ipfs-http-client\n```\n\nBoth the Current and Active LTS versions of Node.js are supported. Please see [nodejs.org](https://nodejs.org/) for what these currently are.\n\n### Next Steps\n\n- Read the [docs](https://github.com/ipfs/js-ipfs/tree/master/docs)\n- Look into the [examples](https://github.com/ipfs-examples/js-ipfs-examples) to learn how to spawn an IPFS node in Node.js and in the Browser\n- Consult the [Core API docs](https://github.com/ipfs/js-ipfs/tree/master/docs/core-api) to see what you can do with an IPFS node\n- Visit <https://dweb-primer.ipfs.io> to learn about IPFS and the concepts that underpin it\n- Head over to <https://proto.school> to take interactive tutorials that cover core IPFS APIs\n- Check out <https://docs.ipfs.io> for tips, how-tos and more\n- See <https://blog.ipfs.io> for news and more\n- Need help? Please ask 'How do I?' questions on <https://discuss.ipfs.io>\n\n## Usage\n\n#### `create([options])`\n\n> create an instance of the HTTP API client\n\n#### Parameters\n\nNone\n\n#### Options\n\n`options` can be a `String`, a `URL` or a `Multiaddr` which will be interpreted as the address of the IPFS node we wish to use the API of.\n\nAlternatively it can be an object which may have the following keys:\n\n| Name     | Type                                                                 | Default                                          | Description                                                                                                    |\n| -------- | -------------------------------------------------------------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- |\n| url      | `String` or `URL` or `Multiaddr`                                     | `'http://localhost:5001/api/v0'`                 | A URL that resolves to a running instance of the IPFS [HTTP RPC API](https://docs.ipfs.io/reference/http/api/) |\n| protocol | `String`                                                             | `'http'`                                         | The protocol to used (ignored if url is specified)                                                             |\n| host     | `String`                                                             | `'localhost'`                                    | The host to used (ignored if url is specified)                                                                 |\n| port     | `number`                                                             | `5001`                                           | The port to used (ignored if url is specified)                                                                 |\n| path     | `String`                                                             | `'api/v0'`                                       | The path to used (ignored if url is specified)                                                                 |\n| agent    | [http.Agent](https://nodejs.org/api/http.html#http_class_http_agent) | `http.Agent({ keepAlive: true, maxSockets: 6 })` | An `http.Agent` used to control client behaviour (node.js only)                                                |\n\n#### Returns\n\n| Type     | Description                                                                                               |\n| -------- | --------------------------------------------------------------------------------------------------------- |\n| `Object` | An object that conforms to the [IPFS Core API](https://github.com/ipfs/js-ipfs/tree/master/docs/core-api) |\n\n#### Example\n\n```JavaScript\nimport { create } from 'ipfs-http-client'\n\n// connect to the default API address http://localhost:5001\nconst client = create()\n\n// connect to a different API\nconst client = create({ url: \"http://127.0.0.1:5002/api/v0\" });\n\n// connect using a URL\nconst client = create(new URL('http://127.0.0.1:5002'))\n\n// call Core API methods\nconst { cid } = await client.add('Hello world!')\n```\n\n### API\n\n[![IPFS Core API Compatible](https://cdn.rawgit.com/ipfs/interface-ipfs-core/master/img/badge.svg)](https://github.com/ipfs/js-ipfs/tree/master/packages/interface-ipfs-core)\n\n> `js-ipfs-http-client` implements the [IPFS Core API](https://github.com/ipfs/js-ipfs/tree/master/docs/core-api) - please follow the previous link to see the methods available.\n\n### Additional Options\n\nAll core API methods take *additional* `options` specific to the HTTP API:\n\n- `headers` - An object or [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) instance that can be used to set custom HTTP headers. Note that this option can also be [configured globally](#custom-headers) via the constructor options.\n- `searchParams` - An object or [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) instance that can be used to add additional query parameters to the query string sent with each request.\n\n### Instance Utils\n\n- `ipfs.getEndpointConfig()`\n\nCall this on your client instance to return an object containing the `host`, `port`, `protocol` and `api-path`.\n\n### Static Types and Utils\n\nAside from the default export, `ipfs-http-client` exports various types and utilities that are included in the bundle:\n\n- [`multiaddr`](https://www.npmjs.com/package/multiaddr)\n- [`multibase`](https://www.npmjs.com/package/multibase)\n- [`multicodec`](https://www.npmjs.com/package/multicodec)\n- [`multihash`](https://www.npmjs.com/package/multihashes)\n- [`CID`](https://www.npmjs.com/package/cids)\n- [`globSource`](https://github.com/ipfs/js-ipfs-utils/blob/master/src/files/glob-source.js) (not available in the browser)\n- [`urlSource`](https://github.com/ipfs/js-ipfs-utils/blob/master/src/files/url-source.js)\n\nThese can be accessed like this, for example:\n\n```js\nconst { CID } = require('ipfs-http-client')\n// ...or from an es-module:\nimport { CID } from 'ipfs-http-client'\n```\n\n#### Glob source\n\nA utility to allow files on the file system to be easily added to IPFS.\n\n##### `globSource(path, pattern, [options])`\n\n- `path`: A path to a single file or directory to glob from\n- `pattern`: A pattern to match files under `path`\n- `options`: Optional options\n- `options.hidden`: Hidden/dot files (files or folders starting with a `.`, for example, `.git/`) are not included by default. To add them, use the option `{ hidden: true }`.\n\nReturns an async iterable that yields `{ path, content }` objects suitable for passing to `ipfs.add`.\n\n##### Example\n\n```js\nimport { create, globSource } from 'ipfs'\n\nconst ipfs = await create()\n\nfor await (const file of ipfs.addAll(globSource('./docs', '**/*'))) {\n  console.log(file)\n}\n/*\n{\n  path: 'docs/assets/anchor.js',\n  cid: CID('QmVHxRocoWgUChLEvfEyDuuD6qJ4PhdDL2dTLcpUy3dSC2'),\n  size: 15347\n}\n{\n  path: 'docs/assets/bass-addons.css',\n  cid: CID('QmPiLWKd6yseMWDTgHegb8T7wVS7zWGYgyvfj7dGNt2viQ'),\n  size: 232\n}\n...\n*/\n```\n\n#### URL source\n\nA utility to allow content from the internet to be easily added to IPFS.\n\n##### `urlSource(url)`\n\n- `url`: A string URL or [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) instance to send HTTP GET request to\n\nReturns an async iterable that yields `{ path, content }` objects suitable for passing to `ipfs.add`.\n\n##### Example\n\n```js\nimport { create, urlSource } from 'ipfs-http-client'\nconst ipfs = create()\n\nconst file = await ipfs.add(urlSource('https://ipfs.io/images/ipfs-logo.svg'))\nconsole.log(file)\n\n/*\n{\n  path: 'ipfs-logo.svg',\n  cid: CID('QmTqZhR6f7jzdhLgPArDPnsbZpvvgxzCZycXK7ywkLxSyU'),\n  size: 3243\n}\n*/\n```\n\n### Running the daemon with the right port\n\nTo interact with the API, you need to have a local daemon running. It needs to be open on the right port. `5001` is the default, and is used in the examples below, but it can be set to whatever you need.\n\n```sh\n# Show the ipfs config API port to check it is correct\n> ipfs config Addresses.API\n/ip4/127.0.0.1/tcp/5001\n# Set it if it does not match the above output\n> ipfs config Addresses.API /ip4/127.0.0.1/tcp/5001\n# Restart the daemon after changing the config\n\n# Run the daemon\n> ipfs daemon\n```\n\n### Importing the module and usage\n\n```javascript\nimport { create } from 'ipfs-http-client'\n\n// connect to ipfs daemon API server\nconst ipfs = create('http://localhost:5001') // (the default in Node.js)\n\n// or connect with multiaddr\nconst ipfs = create('/ip4/127.0.0.1/tcp/5001')\n\n// or using options\nconst ipfs = create({ host: 'localhost', port: '5001', protocol: 'http' })\n\n// or specifying a specific API path\nconst ipfs = create({ host: '1.1.1.1', port: '80', apiPath: '/ipfs/api/v0' })\n```\n\n### In a web browser\n\n**through Browserify**\n\nSame as in Node.js, you just have to [browserify](http://browserify.org) the code before serving it. See the browserify repo for how to do that.\n\nSee the example in the [examples folder](https://github.com/ipfs-examples/js-ipfs-examples/tree/master/examples) to get a boilerplate.\n\n**through webpack**\n\nSee the example in the [examples folder](https://github.com/ipfs-examples/js-ipfs-examples/tree/master/examples/http-client-bundle-webpack) to get an idea on how to use `js-ipfs-http-client` with webpack.\n\n**from CDN**\n\nInstead of a local installation (and browserification) you may request a remote copy of IPFS API from [jsDelivr](https://www.jsdelivr.com/package/npm/ipfs).\n\nTo always request the latest version, use one of the following examples:\n\n```html\n<!-- loading the minified version using jsDelivr -->\n<script src=\"https://cdn.jsdelivr.net/npm/ipfs-http-client/dist/index.min.js\"></script>\n```\n\nFor maximum security you may also decide to:\n\n- reference a specific version of IPFS API (to prevent unexpected breaking changes when a newer latest version is published)\n- [generate a SRI hash](https://www.srihash.org/) of that version and use it to ensure integrity. Learn more also at the [jsdelivr website](https://www.jsdelivr.com/using-sri-with-dynamic-files)\n- set the [CORS settings attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) to make anonymous requests to CDN\n\nExample:\n\n```html\n<script\n  src=\"https://www.jsdelivr.com/package/npm/ipfs-http-client\"\n  integrity=\"sha384-5bXRcW9kyxxnSMbOoHzraqa7Z0PQWIao+cgeg327zit1hz5LZCEbIMx/LWKPReuB\"\n  crossorigin=\"anonymous\"\n></script>\n```\n\nCDN-based IPFS API provides the `IpfsHttpClient` constructor as a method of the global `window` object. Example:\n\n```js\nconst ipfs = window.IpfsHttpClient({ host: 'localhost', port: 5001 })\n```\n\nIf you omit the host and port, the client will parse `window.host`, and use this information. This also works, and can be useful if you want to write apps that can be run from multiple different gateways:\n\n```js\nconst ipfs = window.IpfsHttpClient()\n```\n\n### Custom Headers\n\nIf you wish to send custom headers with each request made by this library, for example, the Authorization header. You can use the config to do so:\n\n```js\nconst ipfs = create({\n  host: 'localhost',\n  port: 5001,\n  protocol: 'http',\n  headers: {\n    authorization: 'Bearer ' + TOKEN\n  }\n})\n```\n\n### Infura Header\n\nIf you wish to send infura headers with each request made by this library, for example, the Authorization header. You can use the config to do so:\n\n```js\nconst auth =\n    'Basic ' + Buffer.from(INFURA_ID + ':' + INFURA_SECRET_KEY).toString('base64');\nconst client = ipfsClient.create({\n    host: 'ipfs.infura.io',\n    port: 5001,\n    protocol: 'https',\n    headers: {\n        authorization: auth,\n    },\n});\n```\n\n### Global Timeouts\n\nTo set a global timeout for *all* requests pass a value for the `timeout` option:\n\n```js\n// Timeout after 10 seconds\nconst ipfs = create({ timeout: 10000 })\n// Timeout after 2 minutes\nconst ipfs = create({ timeout: '2m' })\n// see https://www.npmjs.com/package/parse-duration for valid string values\n```\n\n## Development\n\n### Testing\n\nWe run tests by executing `npm test` in a terminal window. This will run both Node.js and Browser tests, both in Chrome and PhantomJS. To ensure that the module conforms with the [`interface-ipfs-core`](https://github.com/ipfs/js-ipfs/tree/master/packages/interface-ipfs-core) spec, we run the batch of tests provided by the interface module, which can be found [here](https://github.com/ipfs/js-ipfs/tree/master/packages/interface-ipfs-core/src).\n\n## Historical context\n\nThis module started as a direct mapping from the go-ipfs cli to a JavaScript implementation, although this was useful and familiar to a lot of developers that were coming to IPFS for the first time, it also created some confusion on how to operate the core of IPFS and have access to the full capacity of the protocol. After much consideration, we decided to create `interface-ipfs-core` with the goal of standardizing the interface of a core implementation of IPFS, and keep the utility functions the IPFS community learned to use and love, such as reading files from disk and storing them directly to IPFS.\n\n## License\n\nLicensed under either of\n\n- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / <http://www.apache.org/licenses/LICENSE-2.0>)\n- MIT ([LICENSE-MIT](LICENSE-MIT) / <http://opensource.org/licenses/MIT>)\n\n## Contribute\n\nContributions welcome! Please check out [the issues](https://github.com/ipfs/js-ipfs/issues).\n\nAlso 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.\n\nPlease 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).\n\nUnless 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.\n\n[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md)\n"
  },
  {
    "path": "packages/ipfs-http-client/maintainer.json",
    "content": "{\n  \"repoLeadMaintainer\": {\n    \"name\": \"Alan Shaw\",\n    \"email\": \"alan.shaw@protocol.ai\",\n    \"username\": \"alanshaw\"\n  },\n  \"workingGroup\": {\n    \"name\": \"JS IPFS\",\n    \"entryPoint\": \"https://github.com/ipfs/js-core\"\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/package.json",
    "content": "{\n  \"name\": \"ipfs-http-client\",\n  \"version\": \"60.0.1\",\n  \"description\": \"A client library for the IPFS HTTP API\",\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"homepage\": \"https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-http-client#readme\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ipfs/js-ipfs.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ipfs/js-ipfs/issues\"\n  },\n  \"keywords\": [\n    \"ipfs\"\n  ],\n  \"engines\": {\n    \"node\": \">=16.0.0\",\n    \"npm\": \">=7.0.0\"\n  },\n  \"type\": \"module\",\n  \"types\": \"./dist/src/index.d.ts\",\n  \"typesVersions\": {\n    \"*\": {\n      \"*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ],\n      \"src/*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ]\n    }\n  },\n  \"files\": [\n    \"src\",\n    \"dist\",\n    \"!dist/test\",\n    \"!**/*.tsbuildinfo\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/src/index.d.ts\",\n      \"import\": \"./src/index.js\"\n    }\n  },\n  \"eslintConfig\": {\n    \"extends\": \"ipfs\",\n    \"parserOptions\": {\n      \"sourceType\": \"module\"\n    }\n  },\n  \"scripts\": {\n    \"build\": \"aegir build\",\n    \"test\": \"aegir test\",\n    \"test:node\": \"aegir test -t node --cov\",\n    \"test:chrome\": \"aegir test -t browser --cov\",\n    \"test:chrome-webworker\": \"aegir test -t webworker\",\n    \"test:firefox\": \"aegir test -t browser -- --browser firefox\",\n    \"test:firefox-webworker\": \"aegir test -t webworker -- --browser firefox\",\n    \"lint\": \"aegir lint\",\n    \"clean\": \"aegir clean\",\n    \"dep-check\": \"aegir dep-check -i ipfs-core -i ipfs-core-types\"\n  },\n  \"dependencies\": {\n    \"@ipld/dag-cbor\": \"^9.0.0\",\n    \"@ipld/dag-json\": \"^10.0.0\",\n    \"@ipld/dag-pb\": \"^4.0.0\",\n    \"@libp2p/logger\": \"^2.0.5\",\n    \"@libp2p/peer-id\": \"^2.0.0\",\n    \"@multiformats/multiaddr\": \"^11.1.5\",\n    \"any-signal\": \"^3.0.0\",\n    \"dag-jose\": \"^4.0.0\",\n    \"err-code\": \"^3.0.1\",\n    \"ipfs-core-types\": \"^0.14.1\",\n    \"ipfs-core-utils\": \"^0.18.1\",\n    \"ipfs-utils\": \"^9.0.13\",\n    \"it-first\": \"^2.0.0\",\n    \"it-last\": \"^2.0.0\",\n    \"merge-options\": \"^3.0.4\",\n    \"multiformats\": \"^11.0.0\",\n    \"parse-duration\": \"^1.0.0\",\n    \"stream-to-it\": \"^0.2.2\",\n    \"uint8arrays\": \"^4.0.2\"\n  },\n  \"devDependencies\": {\n    \"aegir\": \"^37.11.0\",\n    \"delay\": \"^5.0.0\",\n    \"go-ipfs\": \"^0.12.0\",\n    \"ipfsd-ctl\": \"^13.0.0\",\n    \"it-all\": \"^2.0.0\",\n    \"it-first\": \"^2.0.0\",\n    \"nock\": \"^13.0.2\",\n    \"p-defer\": \"^4.0.0\"\n  },\n  \"browser\": {\n    \"ipfs-utils/src/files/glob-source\": false,\n    \"go-ipfs\": false,\n    \"ipfs-core-utils/src/files/normalise-input\": \"ipfs-core-utils/src/files/normalise-input/index.browser.js\",\n    \"http\": false,\n    \"https\": false\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/add-all.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { objectToCamel } from './lib/object-to-camel.js'\nimport { configure } from './lib/configure.js'\nimport { multipartRequest } from 'ipfs-core-utils/multipart-request'\nimport { toUrlSearchParams } from './lib/to-url-search-params.js'\nimport { abortSignal } from './lib/abort-signal.js'\n\n/**\n * @typedef {import('ipfs-utils/src/types').ProgressFn} IPFSUtilsHttpUploadProgressFn\n * @typedef {import('ipfs-core-types/src/root').AddProgressFn} IPFSCoreAddProgressFn\n * @typedef {import('./types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/root').API<HTTPClientExtraOptions>} RootAPI\n * @typedef {import('ipfs-core-types/src/root').AddResult} AddResult\n */\n\nexport const createAddAll = configure((api) => {\n  /**\n   * @type {RootAPI[\"addAll\"]}\n   */\n  async function * addAll (source, options = {}) {\n    // allow aborting requests on body errors\n    const controller = new AbortController()\n    const signal = abortSignal(controller.signal, options.signal)\n    const { headers, body, total, parts } =\n      await multipartRequest(source, controller, options.headers)\n\n    // In browser response body only starts streaming once upload is\n    // complete, at which point all the progress updates are invalid. If\n    // length of the content is computable we can interpret progress from\n    // `{ total, loaded}` passed to `onUploadProgress` and `multipart.total`\n    // in which case we disable progress updates to be written out.\n    const [progressFn, onUploadProgress] = typeof options.progress === 'function'\n      ? createProgressHandler(total, parts, options.progress)\n      : [undefined, undefined]\n\n    const res = await api.post('add', {\n      searchParams: toUrlSearchParams({\n        'stream-channels': true,\n        ...options,\n        progress: Boolean(progressFn)\n      }),\n      onUploadProgress,\n      signal,\n      headers,\n      body\n    })\n\n    for await (let file of res.ndjson()) {\n      file = objectToCamel(file)\n\n      if (file.hash !== undefined) {\n        yield toCoreInterface(file)\n      } else if (progressFn) {\n        progressFn(file.bytes || 0, file.name)\n      }\n    }\n  }\n  return addAll\n})\n\n/**\n * Returns simple progress callback when content length isn't computable or a\n * progress event handler that calculates progress from upload progress events.\n *\n * @param {number} total\n * @param {{name:string, start:number, end:number}[]|null} parts\n * @param {IPFSCoreAddProgressFn} progress\n * @returns {[IPFSCoreAddProgressFn|undefined, IPFSUtilsHttpUploadProgressFn|undefined]}\n */\nconst createProgressHandler = (total, parts, progress) =>\n  parts ? [undefined, createOnUploadProgress(total, parts, progress)] : [progress, undefined]\n\n/**\n * Creates a progress handler that interpolates progress from upload progress\n * events and total size of the content that is added.\n *\n * @param {number} size - actual content size\n * @param {{name:string, start:number, end:number}[]} parts\n * @param {IPFSCoreAddProgressFn} progress\n * @returns {IPFSUtilsHttpUploadProgressFn}\n */\nconst createOnUploadProgress = (size, parts, progress) => {\n  let index = 0\n  const count = parts.length\n  return ({ loaded, total }) => {\n    // Derive position from the current progress.\n    const position = Math.floor(loaded / total * size)\n    while (index < count) {\n      const { start, end, name } = parts[index]\n      // If within current part range report progress and break the loop\n      if (position < end) {\n        progress(position - start, name)\n        break\n      // If passed current part range report final byte for the chunk and\n      // move to next one.\n      } else {\n        progress(end - start, name)\n        index += 1\n      }\n    }\n  }\n}\n\n/**\n * @param {object} input\n * @param {string} input.name\n * @param {string} input.hash\n * @param {string} input.size\n * @param {string} [input.mode]\n * @param {number} [input.mtime]\n * @param {number} [input.mtimeNsecs]\n */\nfunction toCoreInterface ({ name, hash, size, mode, mtime, mtimeNsecs }) {\n  /** @type {AddResult} */\n  const output = {\n    path: name,\n    cid: CID.parse(hash),\n    size: parseInt(size)\n  }\n\n  if (mode != null) {\n    output.mode = parseInt(mode, 8)\n  }\n\n  if (mtime != null) {\n    output.mtime = {\n      secs: mtime,\n      nsecs: mtimeNsecs || 0\n    }\n  }\n\n  return output\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/add.js",
    "content": "import { createAddAll } from './add-all.js'\nimport last from 'it-last'\nimport { configure } from './lib/configure.js'\nimport { normaliseInput } from 'ipfs-core-utils/files/normalise-input-single'\n\n/**\n * @typedef {import('./types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/root').API<HTTPClientExtraOptions>} RootAPI\n */\n\n/**\n * @param {import('./types').Options} options\n */\nexport function createAdd (options) {\n  const all = createAddAll(options)\n  return configure(() => {\n    /**\n     * @type {RootAPI[\"add\"]}\n     */\n    async function add (input, options = {}) {\n      // @ts-expect-error - last may return undefined if source is empty\n      return await last(all(normaliseInput(input), options))\n    }\n    return add\n  })(options)\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/bitswap/index.js",
    "content": "import { createWantlist } from './wantlist.js'\nimport { createWantlistForPeer } from './wantlist-for-peer.js'\nimport { createStat } from './stat.js'\nimport { createUnwant } from './unwant.js'\n\n/**\n * @param {import('../types').Options} config\n */\nexport function createBitswap (config) {\n  return {\n    wantlist: createWantlist(config),\n    wantlistForPeer: createWantlistForPeer(config),\n    unwant: createUnwant(config),\n    stat: createStat(config)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/bitswap/stat.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\nimport { peerIdFromString } from '@libp2p/peer-id'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/bitswap').API<HTTPClientExtraOptions>} BitswapAPI\n */\n\nexport const createStat = configure(api => {\n  /**\n   * @type {BitswapAPI[\"stat\"]}\n   */\n  async function stat (options = {}) {\n    const res = await api.post('bitswap/stat', {\n      searchParams: toUrlSearchParams(options),\n      signal: options.signal,\n      headers: options.headers\n    })\n\n    return toCoreInterface(await res.json())\n  }\n  return stat\n})\n\n/**\n * @param {any} res\n */\nfunction toCoreInterface (res) {\n  return {\n    provideBufLen: res.ProvideBufLen,\n    wantlist: (res.Wantlist || []).map((/** @type {{ '/': string }} */ k) => CID.parse(k['/'])),\n    peers: (res.Peers || []).map((/** @type {string} */ str) => peerIdFromString(str)),\n    blocksReceived: BigInt(res.BlocksReceived),\n    dataReceived: BigInt(res.DataReceived),\n    blocksSent: BigInt(res.BlocksSent),\n    dataSent: BigInt(res.DataSent),\n    dupBlksReceived: BigInt(res.DupBlksReceived),\n    dupDataReceived: BigInt(res.DupDataReceived)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/bitswap/unwant.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/bitswap').API<HTTPClientExtraOptions>} BitswapAPI\n */\n\nexport const createUnwant = configure(api => {\n  /**\n   * @type {BitswapAPI[\"unwant\"]}\n   */\n  async function unwant (cid, options = {}) {\n    const res = await api.post('bitswap/unwant', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: cid.toString(),\n        ...options\n      }),\n      headers: options.headers\n    })\n\n    return res.json()\n  }\n  return unwant\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/bitswap/wantlist-for-peer.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/bitswap').API<HTTPClientExtraOptions>} BitswapAPI\n */\n\nexport const createWantlistForPeer = configure(api => {\n  /**\n   * @type {BitswapAPI[\"wantlistForPeer\"]}\n   */\n  async function wantlistForPeer (peerId, options = {}) {\n    const res = await (await api.post('bitswap/wantlist', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        ...options,\n        peer: peerId.toString()\n      }),\n      headers: options.headers\n    })).json()\n\n    return (res.Keys || []).map((/** @type {{ '/': string }} */ k) => CID.parse(k['/']))\n  }\n  return wantlistForPeer\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/bitswap/wantlist.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/bitswap').API<HTTPClientExtraOptions>} BitswapAPI\n */\n\nexport const createWantlist = configure(api => {\n  /**\n   * @type {BitswapAPI[\"wantlist\"]}\n   */\n  async function wantlist (options = {}) {\n    const res = await (await api.post('bitswap/wantlist', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams(options),\n      headers: options.headers\n    })).json()\n\n    return (res.Keys || []).map((/** @type {{ '/': string }} */ k) => CID.parse(k['/']))\n  }\n  return wantlist\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/block/get.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/block').API<HTTPClientExtraOptions>} BlockAPI\n */\n\nexport const createGet = configure(api => {\n  /**\n   * @type {BlockAPI[\"get\"]}\n   */\n  async function get (cid, options = {}) {\n    const res = await api.post('block/get', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: cid.toString(),\n        ...options\n      }),\n      headers: options.headers\n    })\n\n    return new Uint8Array(await res.arrayBuffer())\n  }\n  return get\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/block/index.js",
    "content": "import { createGet } from './get.js'\nimport { createPut } from './put.js'\nimport { createRm } from './rm.js'\nimport { createStat } from './stat.js'\n\n/**\n * @param {import('../types').Options} config\n */\nexport function createBlock (config) {\n  return {\n    get: createGet(config),\n    put: createPut(config),\n    rm: createRm(config),\n    stat: createStat(config)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/block/put.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { multipartRequest } from 'ipfs-core-utils/multipart-request'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\nimport { abortSignal } from '../lib/abort-signal.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/block').API<HTTPClientExtraOptions>} BlockAPI\n */\n\nexport const createPut = configure(api => {\n  /**\n   * @type {BlockAPI[\"put\"]}\n   */\n  async function put (data, options = {}) {\n    // allow aborting requests on body errors\n    const controller = new AbortController()\n    const signal = abortSignal(controller.signal, options.signal)\n\n    let res\n    try {\n      const response = await api.post('block/put', {\n        signal: signal,\n        searchParams: toUrlSearchParams(options),\n        ...(\n          await multipartRequest([data], controller, options.headers)\n        )\n      })\n      res = await response.json()\n    } catch (/** @type {any} */ err) {\n      // Retry with \"protobuf\"/\"cbor\" format for go-ipfs\n      // TODO: remove when https://github.com/ipfs/go-cid/issues/75 resolved\n      if (options.format === 'dag-pb') {\n        return put(data, { ...options, format: 'protobuf' })\n      } else if (options.format === 'dag-cbor') {\n        return put(data, { ...options, format: 'cbor' })\n      }\n\n      throw err\n    }\n\n    return CID.parse(res.Key)\n  }\n\n  return put\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/block/rm.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/block').API<HTTPClientExtraOptions>} BlockAPI\n * @typedef {import('ipfs-core-types/src/block').RmResult} RmResult\n */\n\nexport const createRm = configure(api => {\n  /**\n   * @type {BlockAPI[\"rm\"]}\n   */\n  async function * rm (cid, options = {}) {\n    if (!Array.isArray(cid)) {\n      cid = [cid]\n    }\n\n    const res = await api.post('block/rm', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: cid.map(cid => cid.toString()),\n        'stream-channels': true,\n        ...options\n      }),\n      headers: options.headers\n    })\n\n    for await (const removed of res.ndjson()) {\n      yield toCoreInterface(removed)\n    }\n  }\n\n  return rm\n})\n\n/**\n * @param {*} removed\n */\nfunction toCoreInterface (removed) {\n  /** @type {RmResult} */\n  const out = {\n    cid: CID.parse(removed.Hash)\n  }\n\n  if (removed.Error) {\n    out.error = new Error(removed.Error)\n  }\n\n  return out\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/block/stat.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/block').API<HTTPClientExtraOptions>} BlockAPI\n */\n\nexport const createStat = configure(api => {\n  /**\n   * @type {BlockAPI[\"stat\"]}\n   */\n  async function stat (cid, options = {}) {\n    const res = await api.post('block/stat', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: cid.toString(),\n        ...options\n      }),\n      headers: options.headers\n    })\n    const data = await res.json()\n\n    return { cid: CID.parse(data.Key), size: data.Size }\n  }\n\n  return stat\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/bootstrap/add.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\nimport { multiaddr } from '@multiformats/multiaddr'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/bootstrap').API<HTTPClientExtraOptions>} BootstrapAPI\n */\n\nexport const createAdd = configure(api => {\n  /**\n   * @type {BootstrapAPI[\"add\"]}\n   */\n  async function add (addr, options = {}) {\n    const res = await api.post('bootstrap/add', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: addr,\n        ...options\n      }),\n      headers: options.headers\n    })\n\n    const { Peers } = await res.json()\n\n    return { Peers: Peers.map((/** @type {string} */ ma) => multiaddr(ma)) }\n  }\n\n  return add\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/bootstrap/clear.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\nimport { multiaddr } from '@multiformats/multiaddr'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/bootstrap').API<HTTPClientExtraOptions>} BootstrapAPI\n */\n\nexport const createClear = configure(api => {\n  /**\n   * @type {BootstrapAPI[\"clear\"]}\n   */\n  async function clear (options = {}) {\n    const res = await api.post('bootstrap/rm', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        ...options,\n        all: true\n      }),\n      headers: options.headers\n    })\n\n    const { Peers } = await res.json()\n\n    return { Peers: Peers.map((/** @type {string} */ ma) => multiaddr(ma)) }\n  }\n\n  return clear\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/bootstrap/index.js",
    "content": "import { createAdd } from './add.js'\nimport { createClear } from './clear.js'\nimport { createList } from './list.js'\nimport { createReset } from './reset.js'\nimport { createRm } from './rm.js'\n\n/**\n * @param {import('../types').Options} config\n */\nexport function createBootstrap (config) {\n  return {\n    add: createAdd(config),\n    clear: createClear(config),\n    list: createList(config),\n    reset: createReset(config),\n    rm: createRm(config)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/bootstrap/list.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\nimport { multiaddr } from '@multiformats/multiaddr'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/bootstrap').API<HTTPClientExtraOptions>} BootstrapAPI\n */\n\nexport const createList = configure(api => {\n  /**\n   * @type {BootstrapAPI[\"list\"]}\n   */\n  async function list (options = {}) {\n    const res = await api.post('bootstrap/list', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams(options),\n      headers: options.headers\n    })\n\n    const { Peers } = await res.json()\n\n    return { Peers: Peers.map((/** @type {string} */ ma) => multiaddr(ma)) }\n  }\n\n  return list\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/bootstrap/reset.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\nimport { multiaddr } from '@multiformats/multiaddr'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/bootstrap').API<HTTPClientExtraOptions>} BootstrapAPI\n */\n\nexport const createReset = configure(api => {\n  /**\n   * @type {BootstrapAPI[\"reset\"]}\n   */\n  async function reset (options = {}) {\n    const res = await api.post('bootstrap/add', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        ...options,\n        default: true\n      }),\n      headers: options.headers\n    })\n\n    const { Peers } = await res.json()\n\n    return { Peers: Peers.map((/** @type {string} */ ma) => multiaddr(ma)) }\n  }\n\n  return reset\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/bootstrap/rm.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\nimport { multiaddr } from '@multiformats/multiaddr'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/bootstrap').API<HTTPClientExtraOptions>} BootstrapAPI\n */\n\nexport const createRm = configure(api => {\n  /**\n   * @type {BootstrapAPI[\"rm\"]}\n   */\n  async function rm (addr, options = {}) {\n    const res = await api.post('bootstrap/rm', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: addr,\n        ...options\n      }),\n      headers: options.headers\n    })\n\n    const { Peers } = await res.json()\n\n    return { Peers: Peers.map((/** @type {string} */ ma) => multiaddr(ma)) }\n  }\n\n  return rm\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/cat.js",
    "content": "import { configure } from './lib/configure.js'\nimport { toUrlSearchParams } from './lib/to-url-search-params.js'\n\n/**\n * @typedef {import('./types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/root').API<HTTPClientExtraOptions>} RootAPI\n */\n\nexport const createCat = configure(api => {\n  /**\n   * @type {RootAPI[\"cat\"]}\n   */\n  async function * cat (path, options = {}) {\n    const res = await api.post('cat', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: path.toString(),\n        ...options\n      }),\n      headers: options.headers\n    })\n\n    yield * res.iterator()\n  }\n\n  return cat\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/commands.js",
    "content": "import { configure } from './lib/configure.js'\nimport { toUrlSearchParams } from './lib/to-url-search-params.js'\n\n/**\n * @typedef {import('./types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/root').API<HTTPClientExtraOptions>} RootAPI\n */\n\nexport const createCommands = configure(api => {\n  /**\n   * @type {RootAPI[\"commands\"]}\n   */\n  const commands = async (options = {}) => {\n    const res = await api.post('commands', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams(options),\n      headers: options.headers\n    })\n\n    return res.json()\n  }\n  return commands\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/config/get-all.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/config').API<HTTPClientExtraOptions>} ConfigAPI\n */\n\nexport const createGetAll = configure(api => {\n  /**\n   * @type {ConfigAPI[\"getAll\"]}\n   */\n  const getAll = async (options = {}) => {\n    const res = await api.post('config/show', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        ...options\n      }),\n      headers: options.headers\n    })\n    const data = await res.json()\n\n    return data\n  }\n\n  return getAll\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/config/get.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/config').API<HTTPClientExtraOptions>} ConfigAPI\n */\n\nexport const createGet = configure(api => {\n  /**\n   * @type {ConfigAPI[\"get\"]}\n   */\n  const get = async (key, options = {}) => {\n    if (!key) {\n      throw new Error('key argument is required')\n    }\n\n    const res = await api.post('config', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: key,\n        ...options\n      }),\n      headers: options.headers\n    })\n    const data = await res.json()\n\n    return data.Value\n  }\n\n  return get\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/config/index.js",
    "content": "import { createProfiles } from './profiles/index.js'\nimport { createGet } from './get.js'\nimport { createGetAll } from './get-all.js'\nimport { createReplace } from './replace.js'\nimport { createSet } from './set.js'\n\n/**\n * @param {import('../types').Options} config\n */\nexport function createConfig (config) {\n  return {\n    getAll: createGetAll(config),\n    get: createGet(config),\n    set: createSet(config),\n    replace: createReplace(config),\n    profiles: createProfiles(config)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/config/profiles/apply.js",
    "content": "import { configure } from '../../lib/configure.js'\nimport { toUrlSearchParams } from '../../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/config/profiles').API<HTTPClientExtraOptions>} ConfigProfilesAPI\n */\n\nexport const createApply = configure(api => {\n  /**\n   * @type {ConfigProfilesAPI[\"apply\"]}\n   */\n  async function apply (profile, options = {}) {\n    const res = await api.post('config/profile/apply', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: profile,\n        ...options\n      }),\n      headers: options.headers\n    })\n    const data = await res.json()\n\n    return {\n      original: data.OldCfg, updated: data.NewCfg\n    }\n  }\n\n  return apply\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/config/profiles/index.js",
    "content": "import { createApply } from './apply.js'\nimport { createList } from './list.js'\n\n/**\n * @param {import('../../types').Options} config\n */\nexport function createProfiles (config) {\n  return {\n    apply: createApply(config),\n    list: createList(config)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/config/profiles/list.js",
    "content": "import { objectToCamel } from '../../lib/object-to-camel.js'\nimport { configure } from '../../lib/configure.js'\nimport { toUrlSearchParams } from '../../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/config/profiles').API<HTTPClientExtraOptions>} ConfigProfilesAPI\n */\n\nexport const createList = configure(api => {\n  /**\n   * @type {ConfigProfilesAPI[\"list\"]}\n   */\n  async function list (options = {}) {\n    const res = await api.post('config/profile/list', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams(options),\n      headers: options.headers\n    })\n\n    const data = await res.json()\n\n    return data.map((/** @type {Record<string, any>} */ profile) => objectToCamel(profile))\n  }\n  return list\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/config/replace.js",
    "content": "import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { multipartRequest } from 'ipfs-core-utils/multipart-request'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\nimport { abortSignal } from '../lib/abort-signal.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/config').API<HTTPClientExtraOptions>} ConfigAPI\n */\n\nexport const createReplace = configure(api => {\n  /**\n   * @type {ConfigAPI[\"replace\"]}\n   */\n  const replace = async (config, options = {}) => {\n    // allow aborting requests on body errors\n    const controller = new AbortController()\n    const signal = abortSignal(controller.signal, options.signal)\n\n    const res = await api.post('config/replace', {\n      signal,\n      searchParams: toUrlSearchParams(options),\n      ...(\n        await multipartRequest([uint8ArrayFromString(JSON.stringify(config))], controller, options.headers)\n      )\n    })\n\n    await res.text()\n  }\n\n  return replace\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/config/set.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/config').API<HTTPClientExtraOptions>} ConfigAPI\n */\n\nexport const createSet = configure(api => {\n  /**\n   * @type {ConfigAPI[\"set\"]}\n   */\n  const set = async (key, value, options = {}) => {\n    if (typeof key !== 'string') {\n      throw new Error('Invalid key type')\n    }\n\n    const params = {\n      ...options,\n      ...encodeParam(key, value)\n    }\n\n    const res = await api.post('config', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams(params),\n      headers: options.headers\n    })\n\n    await res.text()\n  }\n\n  return set\n})\n\n/**\n * @param {*} key\n * @param {*} value\n */\nconst encodeParam = (key, value) => {\n  switch (typeof value) {\n    case 'boolean':\n      return { arg: [key, value.toString()], bool: true }\n    case 'string':\n      return { arg: [key, value] }\n    default:\n      return { arg: [key, JSON.stringify(value)], json: true }\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/dag/export.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/dag').API<HTTPClientExtraOptions>} DAGAPI\n */\n\nexport const createExport = configure(api => {\n  /**\n   * @type {DAGAPI[\"export\"]}\n   */\n  async function * dagExport (root, options = {}) {\n    const res = await api.post('dag/export', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: root.toString()\n      }),\n      headers: options.headers\n    })\n\n    yield * res.iterator()\n  }\n\n  return dagExport\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/dag/get.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { resolve } from '../lib/resolve.js'\nimport first from 'it-first'\nimport last from 'it-last'\nimport errCode from 'err-code'\nimport { createGet as createBlockGet } from '../block/get.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/dag').API<HTTPClientExtraOptions>} DAGAPI\n */\n\n/**\n * @param {import('ipfs-core-utils/multicodecs').Multicodecs} codecs\n * @param {import('../types').Options} options\n */\nexport const createGet = (codecs, options) => {\n  const fn = configure((api, opts) => {\n    const getBlock = createBlockGet(opts)\n\n    /**\n     * @type {DAGAPI[\"get\"]}\n     */\n    const get = async (cid, options = {}) => {\n      if (options.path) {\n        const entry = options.localResolve\n          ? await first(resolve(cid, options.path, codecs, getBlock, options))\n          : await last(resolve(cid, options.path, codecs, getBlock, options))\n        /** @type {import('ipfs-core-types/src/dag').GetResult | undefined} - first and last will return undefined when empty */\n        const result = (entry)\n\n        if (!result) {\n          throw errCode(new Error('Not found'), 'ERR_NOT_FOUND')\n        }\n\n        return result\n      }\n\n      const codec = await codecs.getCodec(cid.code)\n      const block = await getBlock(cid, options)\n      const node = codec.decode(block)\n\n      return {\n        value: node,\n        remainderPath: ''\n      }\n    }\n\n    return get\n  })\n\n  return fn(options)\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/dag/import.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\nimport { abortSignal } from '../lib/abort-signal.js'\nimport { multipartRequest } from 'ipfs-core-utils/multipart-request'\nimport { CID } from 'multiformats/cid'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/dag').API<HTTPClientExtraOptions>} DAGAPI\n */\n\nexport const createImport = configure(api => {\n  /**\n   * @type {DAGAPI[\"import\"]}\n   */\n  async function * dagImport (source, options = {}) {\n    const controller = new AbortController()\n    const signal = abortSignal(controller.signal, options.signal)\n    const { headers, body } = await multipartRequest(source, controller, options.headers)\n\n    const res = await api.post('dag/import', {\n      signal,\n      headers,\n      body,\n      searchParams: toUrlSearchParams({ 'pin-roots': options.pinRoots })\n    })\n\n    for await (const { Root } of res.ndjson()) {\n      if (Root !== undefined) {\n        const { Cid: { '/': Cid }, PinErrorMsg } = Root\n\n        yield {\n          root: {\n            cid: CID.parse(Cid),\n            pinErrorMsg: PinErrorMsg\n          }\n        }\n      }\n    }\n  }\n\n  return dagImport\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/dag/index.js",
    "content": "import { createExport } from './export.js'\nimport { createGet } from './get.js'\nimport { createImport } from './import.js'\nimport { createPut } from './put.js'\nimport { createResolve } from './resolve.js'\n\n/**\n * @param {import('ipfs-core-utils/multicodecs').Multicodecs} codecs\n * @param {import('../types').Options} config\n */\nexport function createDag (codecs, config) {\n  return {\n    export: createExport(config),\n    get: createGet(codecs, config),\n    import: createImport(config),\n    put: createPut(codecs, config),\n    resolve: createResolve(config)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/dag/put.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { configure } from '../lib/configure.js'\nimport { multipartRequest } from 'ipfs-core-utils/multipart-request'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\nimport { abortSignal } from '../lib/abort-signal.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/dag').API<HTTPClientExtraOptions>} DAGAPI\n */\n\n/**\n * @param {import('ipfs-core-utils/multicodecs').Multicodecs} codecs\n * @param {import('../types').Options} options\n */\nexport const createPut = (codecs, options) => {\n  const fn = configure((api) => {\n    /**\n     * @type {DAGAPI[\"put\"]}\n     */\n    const put = async (dagNode, options = {}) => {\n      const settings = {\n        storeCodec: 'dag-cbor',\n        hashAlg: 'sha2-256',\n        ...options\n      }\n\n      let serialized\n\n      if (settings.inputCodec) {\n        // if you supply an inputCodec, we assume you're passing in a raw, encoded\n        // block using that codec, so we'll just pass that on to the server and let\n        // it deal with the decode/encode/store cycle\n        if (!(dagNode instanceof Uint8Array)) {\n          throw new Error('Can only inputCodec on raw bytes that can be decoded')\n        }\n        serialized = dagNode\n      } else {\n        // if you don't supply an inputCodec, we assume you've passed in a JavaScript\n        // object you want to have encoded using storeCodec, so we'll prepare it for\n        // you if we have the codec\n        const storeCodec = await codecs.getCodec(settings.storeCodec)\n        serialized = storeCodec.encode(dagNode)\n        // now we have a serialized form, the server should be told to receive it\n        // in that format\n        settings.inputCodec = settings.storeCodec\n      }\n\n      // allow aborting requests on body errors\n      const controller = new AbortController()\n      const signal = abortSignal(controller.signal, settings.signal)\n\n      const res = await api.post('dag/put', {\n        timeout: settings.timeout,\n        signal,\n        searchParams: toUrlSearchParams(settings),\n        ...(\n          await multipartRequest([serialized], controller, settings.headers)\n        )\n      })\n      const data = await res.json()\n\n      return CID.parse(data.Cid['/'])\n    }\n\n    return put\n  })\n\n  return fn(options)\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/dag/resolve.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/dag').API<HTTPClientExtraOptions>} DAGAPI\n */\n\nexport const createResolve = configure(api => {\n  /**\n   * @type {DAGAPI[\"resolve\"]}\n   */\n  const resolve = async (ipfsPath, options = {}) => {\n    const res = await api.post('dag/resolve', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: `${ipfsPath}${options.path ? `/${options.path}`.replace(/\\/[/]+/g, '/') : ''}`,\n        ...options\n      }),\n      headers: options.headers\n    })\n\n    const data = await res.json()\n\n    return { cid: CID.parse(data.Cid['/']), remainderPath: data.RemPath }\n  }\n\n  return resolve\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/dht/find-peer.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\nimport { mapEvent } from './map-event.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/dht').API<HTTPClientExtraOptions>} DHTAPI\n */\n\nexport const createFindPeer = configure(api => {\n  /**\n   * @type {DHTAPI[\"findPeer\"]}\n   */\n  async function * findPeer (peerId, options = {}) {\n    const res = await api.post('dht/findpeer', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: peerId,\n        ...options\n      }),\n      headers: options.headers\n    })\n\n    for await (const event of res.ndjson()) {\n      yield mapEvent(event)\n    }\n  }\n\n  return findPeer\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/dht/find-provs.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\nimport { mapEvent } from './map-event.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/dht').API<HTTPClientExtraOptions>} DHTAPI\n */\n\nexport const createFindProvs = configure(api => {\n  /**\n   * @type {DHTAPI[\"findProvs\"]}\n   */\n  async function * findProvs (cid, options = {}) {\n    const res = await api.post('dht/findprovs', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: cid.toString(),\n        ...options\n      }),\n      headers: options.headers\n    })\n\n    for await (const event of res.ndjson()) {\n      yield mapEvent(event)\n    }\n  }\n\n  return findProvs\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/dht/get.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\nimport { mapEvent } from './map-event.js'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/dht').API<HTTPClientExtraOptions>} DHTAPI\n */\n\nexport const createGet = configure(api => {\n  /**\n   * @type {DHTAPI[\"get\"]}\n   */\n  async function * get (key, options = {}) {\n    const res = await api.post('dht/get', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        // arg: base36.encode(key),\n        arg: key instanceof Uint8Array ? uint8ArrayToString(key) : key.toString(),\n        ...options\n      }),\n      headers: options.headers\n    })\n\n    for await (const event of res.ndjson()) {\n      yield mapEvent(event)\n    }\n  }\n\n  return get\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/dht/index.js",
    "content": "import { createFindPeer } from './find-peer.js'\nimport { createFindProvs } from './find-provs.js'\nimport { createGet } from './get.js'\nimport { createProvide } from './provide.js'\nimport { createPut } from './put.js'\nimport { createQuery } from './query.js'\n\n/**\n * @param {import('../types').Options} config\n */\nexport function createDht (config) {\n  return {\n    findPeer: createFindPeer(config),\n    findProvs: createFindProvs(config),\n    get: createGet(config),\n    provide: createProvide(config),\n    put: createPut(config),\n    query: createQuery(config)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/dht/map-event.js",
    "content": "import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport {\n  SendingQuery,\n  PeerResponse,\n  FinalPeer,\n  QueryError,\n  Provider,\n  Value,\n  AddingPeer,\n  DialingPeer\n} from './response-types.js'\nimport { multiaddr } from '@multiformats/multiaddr'\nimport { peerIdFromString } from '@libp2p/peer-id'\n\n/**\n * @typedef {import('@libp2p/interface-peer-id').PeerId} PeerId\n * @typedef {import('@multiformats/multiaddr').Multiaddr} Multiaddr\n */\n\n/**\n * @param {{Type: number, ID: string, Extra: string, Responses: {ID: string, Addrs: string[]}[]}} event\n * @returns {import('ipfs-core-types/src/dht').QueryEvent}\n */\nexport const mapEvent = (event) => {\n  if (event.Type === SendingQuery) {\n    return {\n      name: 'SENDING_QUERY',\n      type: event.Type\n    }\n  }\n\n  if (event.Type === PeerResponse) {\n    return {\n      from: peerIdFromString(event.ID),\n      name: 'PEER_RESPONSE',\n      type: event.Type,\n      // TODO: how to infer this from the go-ipfs response\n      messageType: 0,\n      // TODO: how to infer this from the go-ipfs response\n      messageName: 'PUT_VALUE',\n      closer: (event.Responses || []).map(({ ID, Addrs }) => ({ id: peerIdFromString(ID), multiaddrs: Addrs.map(addr => multiaddr(addr)), protocols: [] })),\n      providers: (event.Responses || []).map(({ ID, Addrs }) => ({ id: peerIdFromString(ID), multiaddrs: Addrs.map(addr => multiaddr(addr)), protocols: [] }))\n      // TODO: how to infer this from the go-ipfs response\n      // record: ???\n    }\n  }\n\n  if (event.Type === FinalPeer) {\n    // dht.query ends with a FinalPeer event with no Responses\n    /** @type {import('@libp2p/interface-peer-info').PeerInfo} */\n    let peer = {\n      // @ts-expect-error go-ipfs does not return this\n      id: event.ID ?? peerIdFromString(event.ID),\n      /** @type {Multiaddr[]} */\n      multiaddrs: [],\n      protocols: []\n    }\n\n    if (event.Responses && event.Responses.length) {\n      // dht.findPeer has the result in the Responses field\n      peer = {\n        id: peerIdFromString(event.Responses[0].ID),\n        multiaddrs: event.Responses[0].Addrs.map(addr => multiaddr(addr)),\n        protocols: []\n      }\n    }\n\n    return {\n      name: 'FINAL_PEER',\n      type: event.Type,\n      peer\n    }\n  }\n\n  if (event.Type === QueryError) {\n    return {\n      name: 'QUERY_ERROR',\n      type: event.Type,\n      error: new Error(event.Extra)\n    }\n  }\n\n  if (event.Type === Provider) {\n    return {\n      name: 'PROVIDER',\n      type: event.Type,\n      providers: event.Responses.map(({ ID, Addrs }) => ({ id: peerIdFromString(ID), multiaddrs: Addrs.map(addr => multiaddr(addr)), protocols: [] }))\n    }\n  }\n\n  if (event.Type === Value) {\n    return {\n      name: 'VALUE',\n      type: event.Type,\n      value: uint8ArrayFromString(event.Extra, 'base64pad')\n    }\n  }\n\n  if (event.Type === AddingPeer) {\n    const peers = event.Responses.map(({ ID }) => peerIdFromString(ID))\n\n    if (!peers.length) {\n      throw new Error('No peer found')\n    }\n\n    return {\n      name: 'ADDING_PEER',\n      type: event.Type,\n      peer: peers[0]\n    }\n  }\n\n  if (event.Type === DialingPeer) {\n    return {\n      name: 'DIALING_PEER',\n      type: event.Type,\n      peer: peerIdFromString(event.ID)\n    }\n  }\n\n  throw new Error('Unknown DHT event type')\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/dht/provide.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\nimport { mapEvent } from './map-event.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/dht').API<HTTPClientExtraOptions>} DHTAPI\n * @typedef {import('multiformats/cid').CID} CID\n */\n\nexport const createProvide = configure(api => {\n  /**\n   * @type {DHTAPI[\"provide\"]}\n   */\n  async function * provide (cids, options = { recursive: false }) {\n    /** @type {CID[]} */\n    const cidArr = Array.isArray(cids) ? cids : [cids]\n\n    const res = await api.post('dht/provide', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: cidArr.map(cid => cid.toString()),\n        ...options\n      }),\n      headers: options.headers\n    })\n\n    for await (const event of res.ndjson()) {\n      yield mapEvent(event)\n    }\n  }\n\n  return provide\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/dht/put.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\nimport { multipartRequest } from 'ipfs-core-utils/multipart-request'\nimport { abortSignal } from '../lib/abort-signal.js'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\nimport { mapEvent } from './map-event.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/dht').API<HTTPClientExtraOptions>} DHTAPI\n */\n\nexport const createPut = configure(api => {\n  /**\n   * @type {DHTAPI[\"put\"]}\n   */\n  async function * put (key, value, options = {}) {\n    // allow aborting requests on body errors\n    const controller = new AbortController()\n    const signal = abortSignal(controller.signal, options.signal)\n\n    const res = await api.post('dht/put', {\n      signal,\n      searchParams: toUrlSearchParams({\n        arg: key instanceof Uint8Array ? uint8ArrayToString(key) : key.toString(),\n        ...options\n      }),\n      ...(\n        await multipartRequest([value], controller, options.headers)\n      )\n    })\n\n    for await (const event of res.ndjson()) {\n      yield mapEvent(event)\n    }\n  }\n\n  return put\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/dht/query.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\nimport { mapEvent } from './map-event.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/dht').API<HTTPClientExtraOptions>} DHTAPI\n */\n\nexport const createQuery = configure(api => {\n  /**\n   * @type {DHTAPI[\"query\"]}\n   */\n  async function * query (peerId, options = {}) {\n    const res = await api.post('dht/query', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: peerId.toString(),\n        ...options\n      }),\n      headers: options.headers\n    })\n\n    for await (const event of res.ndjson()) {\n      yield mapEvent(event)\n    }\n  }\n\n  return query\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/dht/response-types.js",
    "content": "\n// Response types are defined here =\n// https://github.com/libp2p/go-libp2p-core/blob/6e566d10f4a5447317a66d64c7459954b969bdab/routing/query.go#L15-L24\nexport const SendingQuery = 0\nexport const PeerResponse = 1\nexport const FinalPeer = 2\nexport const QueryError = 3\nexport const Provider = 4\nexport const Value = 5\nexport const AddingPeer = 6\nexport const DialingPeer = 7\n"
  },
  {
    "path": "packages/ipfs-http-client/src/diag/cmds.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/diag').API<HTTPClientExtraOptions>} DiagAPI\n */\n\nexport const createCmds = configure(api => {\n  /**\n   * @type {DiagAPI[\"cmds\"]}\n   */\n  async function cmds (options = {}) {\n    const res = await api.post('diag/cmds', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams(options),\n      headers: options.headers\n    })\n\n    return res.json()\n  }\n  return cmds\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/diag/index.js",
    "content": "import { createCmds } from './cmds.js'\nimport { createNet } from './net.js'\nimport { createSys } from './sys.js'\n\n/**\n * @param {import('../types').Options} config\n */\nexport function createDiag (config) {\n  return {\n    cmds: createCmds(config),\n    net: createNet(config),\n    sys: createSys(config)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/diag/net.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/diag').API<HTTPClientExtraOptions>} DiagAPI\n */\n\nexport const createNet = configure(api => {\n  /**\n   * @type {DiagAPI[\"net\"]}\n   */\n  async function net (options = {}) {\n    const res = await api.post('diag/net', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams(options),\n      headers: options.headers\n    })\n    return res.json()\n  }\n  return net\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/diag/sys.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/diag').API<HTTPClientExtraOptions>} DiagAPI\n */\n\nexport const createSys = configure(api => {\n  /**\n   * @type {DiagAPI[\"sys\"]}\n   */\n  async function sys (options = {}) {\n    const res = await api.post('diag/sys', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams(options),\n      headers: options.headers\n    })\n\n    return res.json()\n  }\n  return sys\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/dns.js",
    "content": "import { configure } from './lib/configure.js'\nimport { toUrlSearchParams } from './lib/to-url-search-params.js'\n\n/**\n * @typedef {import('./types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/root').API<HTTPClientExtraOptions>} RootAPI\n */\n\nexport const createDns = configure(api => {\n  /**\n   * @type {RootAPI[\"dns\"]}\n   */\n  const dns = async (domain, options = {}) => {\n    const res = await api.post('dns', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: domain,\n        ...options\n      }),\n      headers: options.headers\n    })\n    const data = await res.json()\n\n    return data.Path\n  }\n\n  return dns\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/files/chmod.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/files').API<HTTPClientExtraOptions>} FilesAPI\n */\n\nexport const createChmod = configure(api => {\n  /**\n   * @type {FilesAPI[\"chmod\"]}\n   */\n  async function chmod (path, mode, options = {}) {\n    const res = await api.post('files/chmod', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: path,\n        mode,\n        ...options\n      }),\n      headers: options.headers\n    })\n\n    await res.text()\n  }\n  return chmod\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/files/cp.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/files').API<HTTPClientExtraOptions>} FilesAPI\n */\n\nexport const createCp = configure(api => {\n  /**\n   * @type {FilesAPI[\"cp\"]}\n   */\n  async function cp (sources, destination, options = {}) {\n    /** @type {import('ipfs-core-types/src/utils').IPFSPath[]} */\n    const sourceArr = Array.isArray(sources) ? sources : [sources]\n\n    const res = await api.post('files/cp', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: sourceArr.concat(destination).map(src => CID.asCID(src) ? `/ipfs/${src}` : src),\n        ...options\n      }),\n      headers: options.headers\n    })\n\n    await res.text()\n  }\n  return cp\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/files/flush.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/files').API<HTTPClientExtraOptions>} FilesAPI\n */\n\nexport const createFlush = configure(api => {\n  /**\n   * @type {FilesAPI[\"flush\"]}\n   */\n  async function flush (path, options = {}) {\n    if (!path || typeof path !== 'string') {\n      throw new Error('ipfs.files.flush requires a path')\n    }\n\n    const res = await api.post('files/flush', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: path,\n        ...options\n      }),\n      headers: options.headers\n    })\n    const data = await res.json()\n\n    return CID.parse(data.Cid)\n  }\n  return flush\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/files/index.js",
    "content": "import { createChmod } from './chmod.js'\nimport { createCp } from './cp.js'\nimport { createFlush } from './flush.js'\nimport { createLs } from './ls.js'\nimport { createMkdir } from './mkdir.js'\nimport { createMv } from './mv.js'\nimport { createRead } from './read.js'\nimport { createRm } from './rm.js'\nimport { createStat } from './stat.js'\nimport { createTouch } from './touch.js'\nimport { createWrite } from './write.js'\n\n/**\n * @param {import('../types').Options} config\n */\nexport function createFiles (config) {\n  return {\n    chmod: createChmod(config),\n    cp: createCp(config),\n    flush: createFlush(config),\n    ls: createLs(config),\n    mkdir: createMkdir(config),\n    mv: createMv(config),\n    read: createRead(config),\n    rm: createRm(config),\n    stat: createStat(config),\n    touch: createTouch(config),\n    write: createWrite(config)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/files/ls.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { objectToCamelWithMetadata } from '../lib/object-to-camel-with-metadata.js'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/files').API<HTTPClientExtraOptions>} FilesAPI\n */\nexport const createLs = configure(api => {\n  /**\n   * @type {FilesAPI[\"ls\"]}\n   */\n  async function * ls (path, options = {}) {\n    if (!path) {\n      throw new Error('ipfs.files.ls requires a path')\n    }\n\n    const res = await api.post('files/ls', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: CID.asCID(path) ? `/ipfs/${path}` : path,\n        // default long to true, diverges from go-ipfs where its false by default\n        long: true,\n        ...options,\n        stream: true\n      }),\n      headers: options.headers\n    })\n\n    for await (const result of res.ndjson()) {\n      // go-ipfs does not yet support the \"stream\" option\n      if ('Entries' in result) {\n        for (const entry of result.Entries || []) {\n          yield toCoreInterface(objectToCamelWithMetadata(entry))\n        }\n      } else {\n        yield toCoreInterface(objectToCamelWithMetadata(result))\n      }\n    }\n  }\n  return ls\n})\n\n/**\n * @param {*} entry\n */\nfunction toCoreInterface (entry) {\n  if (entry.hash) {\n    entry.cid = CID.parse(entry.hash)\n  }\n\n  delete entry.hash\n\n  entry.type = entry.type === 1 ? 'directory' : 'file'\n\n  return entry\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/files/mkdir.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/files').API<HTTPClientExtraOptions>} FilesAPI\n */\n\nexport const createMkdir = configure(api => {\n  /**\n   * @type {FilesAPI[\"mkdir\"]}\n   */\n  async function mkdir (path, options = {}) {\n    const res = await api.post('files/mkdir', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: path,\n        ...options\n      }),\n      headers: options.headers\n    })\n\n    await res.text()\n  }\n  return mkdir\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/files/mv.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/files').API<HTTPClientExtraOptions>} FilesAPI\n */\n\nexport const createMv = configure(api => {\n  /**\n   * @type {FilesAPI[\"mv\"]}\n   */\n  async function mv (sources, destination, options = {}) {\n    if (!Array.isArray(sources)) {\n      sources = [sources]\n    }\n\n    const res = await api.post('files/mv', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: sources.concat(destination),\n        ...options\n      }),\n      headers: options.headers\n    })\n    await res.text()\n  }\n\n  return mv\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/files/read.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n// @ts-expect-error no types\nimport toIterable from 'stream-to-it/source.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/files').API<HTTPClientExtraOptions>} FilesAPI\n */\n\nexport const createRead = configure(api => {\n  /**\n   * @type {FilesAPI[\"read\"]}\n   */\n  async function * read (path, options = {}) {\n    const res = await api.post('files/read', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: path,\n        count: options.length,\n        ...options\n      }),\n      headers: options.headers\n    })\n\n    yield * toIterable(res.body)\n  }\n  return read\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/files/rm.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\nimport HTTP from 'ipfs-utils/src/http.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/files').API<HTTPClientExtraOptions>} FilesAPI\n */\n\nexport const createRm = configure(api => {\n  /**\n   * @type {FilesAPI[\"rm\"]}\n   */\n  async function rm (path, options = {}) {\n    const res = await api.post('files/rm', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: path,\n        ...options\n      }),\n      headers: options.headers\n    })\n\n    const body = await res.text()\n    // we don't expect text body to be ever present\n    // (if so, it means an error such as https://github.com/ipfs/go-ipfs/issues/8606)\n    if (body !== '') {\n      /** @type {Error} */\n      const error = new HTTP.HTTPError(res)\n      error.message = body\n      throw error\n    }\n  }\n  return rm\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/files/stat.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { objectToCamelWithMetadata } from '../lib/object-to-camel-with-metadata.js'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/files').API<HTTPClientExtraOptions>} FilesAPI\n */\n\nexport const createStat = configure(api => {\n  /**\n   * @type {FilesAPI[\"stat\"]}\n   */\n  async function stat (path, options = {}) {\n    const res = await api.post('files/stat', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: path,\n        ...options\n      }),\n      headers: options.headers\n    })\n    const data = await res.json()\n\n    data.WithLocality = data.WithLocality || false\n    return toCoreInterface(objectToCamelWithMetadata(data))\n  }\n  return stat\n})\n\n/**\n * @param {*} entry\n */\nfunction toCoreInterface (entry) {\n  entry.cid = CID.parse(entry.hash)\n  delete entry.hash\n  return entry\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/files/touch.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/files').API<HTTPClientExtraOptions>} FilesAPI\n */\n\nexport const createTouch = configure(api => {\n  /**\n   * @type {FilesAPI[\"touch\"]}\n   */\n  async function touch (path, options = {}) {\n    const res = await api.post('files/touch', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: path,\n        ...options\n      }),\n      headers: options.headers\n    })\n\n    await res.text()\n  }\n  return touch\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/files/write.js",
    "content": "import { modeToString } from '../lib/mode-to-string.js'\nimport { parseMtime } from '../lib/parse-mtime.js'\nimport { configure } from '../lib/configure.js'\nimport { multipartRequest } from 'ipfs-core-utils/multipart-request'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\nimport { abortSignal } from '../lib/abort-signal.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/files').API<HTTPClientExtraOptions>} FilesAPI\n */\n\nexport const createWrite = configure(api => {\n  /**\n   * @type {FilesAPI[\"write\"]}\n   */\n  async function write (path, input, options = {}) {\n    // allow aborting requests on body errors\n    const controller = new AbortController()\n    const signal = abortSignal(controller.signal, options.signal)\n\n    const res = await api.post('files/write', {\n      signal,\n      searchParams: toUrlSearchParams({\n        arg: path,\n        streamChannels: true,\n        count: options.length,\n        ...options\n      }),\n      ...(\n        await multipartRequest([{\n          content: input,\n          path: 'arg',\n          mode: modeToString(options.mode),\n          mtime: parseMtime(options.mtime)\n        }], controller, options.headers)\n      )\n    })\n\n    await res.text()\n  }\n  return write\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/get-endpoint-config.js",
    "content": "import { configure } from './lib/configure.js'\n\nexport const createGetEndpointConfig = configure(api => {\n  return () => {\n    const url = new URL(api.opts.base || '')\n    return {\n      host: url.hostname,\n      port: url.port,\n      protocol: url.protocol,\n      pathname: url.pathname,\n      'api-path': url.pathname\n    }\n  }\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/get.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { configure } from './lib/configure.js'\nimport { toUrlSearchParams } from './lib/to-url-search-params.js'\n\n/**\n * @typedef {import('./types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/root').API<HTTPClientExtraOptions>} RootAPI\n */\n\nexport const createGet = configure(api => {\n  /**\n   * @type {RootAPI[\"get\"]}\n   */\n  async function * get (path, options = {}) {\n    /** @type {Record<string, any>} */\n    const opts = {\n      arg: `${path instanceof Uint8Array ? CID.decode(path) : path}`,\n      ...options\n    }\n\n    if (opts.compressionLevel) {\n      opts['compression-level'] = opts.compressionLevel\n      delete opts.compressionLevel\n    }\n\n    const res = await api.post('get', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams(opts),\n      headers: options.headers\n    })\n\n    yield * res.iterator()\n  }\n\n  return get\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/id.js",
    "content": "import { objectToCamel } from './lib/object-to-camel.js'\nimport { multiaddr } from '@multiformats/multiaddr'\nimport { configure } from './lib/configure.js'\nimport { toUrlSearchParams } from './lib/to-url-search-params.js'\nimport { peerIdFromString } from '@libp2p/peer-id'\n\n/**\n * @typedef {import('./types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/root').API<HTTPClientExtraOptions>} RootAPI\n */\n\nexport const createId = configure(api => {\n  /**\n   * @type {RootAPI[\"id\"]}\n   */\n  async function id (options = {}) {\n    const res = await api.post('id', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: options.peerId ? options.peerId.toString() : undefined,\n        ...options\n      }),\n      headers: options.headers\n    })\n    const data = await res.json()\n\n    const output = {\n      ...objectToCamel(data)\n    }\n\n    output.id = peerIdFromString(output.id)\n\n    if (output.addresses) {\n      output.addresses = output.addresses.map((/** @type {string} */ ma) => multiaddr(ma))\n    }\n\n    // @ts-expect-error server output is not typed\n    return output\n  }\n  return id\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/index.js",
    "content": "/* eslint-env browser */\n\nimport { Multibases } from 'ipfs-core-utils/multibases'\nimport { Multicodecs } from 'ipfs-core-utils/multicodecs'\nimport { Multihashes } from 'ipfs-core-utils/multihashes'\nimport * as dagPB from '@ipld/dag-pb'\nimport * as dagCBOR from '@ipld/dag-cbor'\nimport * as dagJSON from '@ipld/dag-json'\nimport * as dagJOSE from 'dag-jose'\nimport { identity } from 'multiformats/hashes/identity'\nimport { bases, hashes, codecs } from 'multiformats/basics'\nimport { createBitswap } from './bitswap/index.js'\nimport { createBlock } from './block/index.js'\nimport { createBootstrap } from './bootstrap/index.js'\nimport { createConfig } from './config/index.js'\nimport { createDag } from './dag/index.js'\nimport { createDht } from './dht/index.js'\nimport { createDiag } from './diag/index.js'\nimport { createFiles } from './files/index.js'\nimport { createKey } from './key/index.js'\nimport { createLog } from './log/index.js'\nimport { createName } from './name/index.js'\nimport { createObject } from './object/index.js'\nimport { createPin } from './pin/index.js'\nimport { createPubsub } from './pubsub/index.js'\nimport { createRefs } from './refs/index.js'\nimport { createRepo } from './repo/index.js'\nimport { createStats } from './stats/index.js'\nimport { createSwarm } from './swarm/index.js'\nimport { createAdd } from './add.js'\nimport { createAddAll } from './add-all.js'\nimport { createCat } from './cat.js'\nimport { createCommands } from './commands.js'\nimport { createDns } from './dns.js'\nimport { createGetEndpointConfig } from './get-endpoint-config.js'\nimport { createGet } from './get.js'\nimport { createId } from './id.js'\nimport { createIsOnline } from './is-online.js'\nimport { createLs } from './ls.js'\nimport { createMount } from './mount.js'\nimport { createPing } from './ping.js'\nimport { createResolve } from './resolve.js'\nimport { createStart } from './start.js'\nimport { createStop } from './stop.js'\nimport { createVersion } from './version.js'\nimport globSourceImport from 'ipfs-utils/src/files/glob-source.js'\n\n/**\n * @typedef {import('multiformats/codecs/interface').BlockCodec<any, any>} BlockCodec\n * @typedef {import('multiformats/hashes/interface').MultihashHasher} MultihashHasher\n * @typedef {import('multiformats/bases/interface').MultibaseCodec<any>} MultibaseCodec\n * @typedef {import('./types').Options} Options\n * @typedef {import('./types').LoadBaseFn} LoadBaseFn\n * @typedef {import('./types').LoadCodecFn} LoadCodecFn\n * @typedef {import('./types').LoadHasherFn} LoadHasherFn\n * @typedef {import('./types').IPLDOptions} IPLDOptions\n * @typedef {import('./types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('./types').EndpointConfig} EndpointConfig\n * @typedef {import('./types').IPFSHTTPClient} IPFSHTTPClient\n */\n\n/**\n * @param {Options} options\n */\nexport function create (options = {}) {\n  /**\n   * @type {BlockCodec}\n   */\n  const id = {\n    name: identity.name,\n    code: identity.code,\n    encode: (id) => id,\n    decode: (id) => id\n  }\n\n  /** @type {MultibaseCodec[]} */\n  const multibaseCodecs = Object.values(bases);\n\n  (options.ipld && options.ipld.bases ? options.ipld.bases : []).forEach(base => multibaseCodecs.push(base))\n\n  const multibases = new Multibases({\n    bases: multibaseCodecs,\n    loadBase: options.ipld && options.ipld.loadBase\n  })\n\n  /** @type {BlockCodec[]} */\n  const blockCodecs = Object.values(codecs);\n\n  [dagPB, dagCBOR, dagJSON, dagJOSE, id].concat((options.ipld && options.ipld.codecs) || []).forEach(codec => blockCodecs.push(codec))\n\n  const multicodecs = new Multicodecs({\n    codecs: blockCodecs,\n    loadCodec: options.ipld && options.ipld.loadCodec\n  })\n\n  /** @type {MultihashHasher[]} */\n  const multihashHashers = Object.values(hashes);\n\n  (options.ipld && options.ipld.hashers ? options.ipld.hashers : []).forEach(hasher => multihashHashers.push(hasher))\n\n  const multihashes = new Multihashes({\n    hashers: multihashHashers,\n    loadHasher: options.ipld && options.ipld.loadHasher\n  })\n\n  /** @type {IPFSHTTPClient} */\n  const client = {\n    add: createAdd(options),\n    addAll: createAddAll(options),\n    bitswap: createBitswap(options),\n    block: createBlock(options),\n    bootstrap: createBootstrap(options),\n    cat: createCat(options),\n    commands: createCommands(options),\n    config: createConfig(options),\n    dag: createDag(multicodecs, options),\n    dht: createDht(options),\n    diag: createDiag(options),\n    dns: createDns(options),\n    files: createFiles(options),\n    get: createGet(options),\n    getEndpointConfig: createGetEndpointConfig(options),\n    id: createId(options),\n    isOnline: createIsOnline(options),\n    key: createKey(options),\n    log: createLog(options),\n    ls: createLs(options),\n    mount: createMount(options),\n    name: createName(options),\n    object: createObject(multicodecs, options),\n    pin: createPin(options),\n    ping: createPing(options),\n    pubsub: createPubsub(options),\n    refs: createRefs(options),\n    repo: createRepo(options),\n    resolve: createResolve(options),\n    start: createStart(options),\n    stats: createStats(options),\n    stop: createStop(options),\n    swarm: createSwarm(options),\n    version: createVersion(options),\n    bases: multibases,\n    codecs: multicodecs,\n    hashers: multihashes\n  }\n\n  return client\n}\n\nexport { CID } from 'multiformats/cid'\nexport { multiaddr } from '@multiformats/multiaddr'\nexport { default as urlSource } from 'ipfs-utils/src/files/url-source.js'\nexport const globSource = globSourceImport\n"
  },
  {
    "path": "packages/ipfs-http-client/src/is-online.js",
    "content": "import { createId } from './id.js'\n\n/**\n * @typedef {import('./types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/root').API<HTTPClientExtraOptions>} RootAPI\n */\n\n/**\n * @param {import('./types').Options} options\n */\nexport const createIsOnline = options => {\n  const id = createId(options)\n\n  /**\n   * @type {RootAPI[\"isOnline\"]}\n   */\n  async function isOnline (options = {}) {\n    const res = await id(options)\n\n    return Boolean(res && res.addresses && res.addresses.length)\n  }\n  return isOnline\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/key/export.js",
    "content": "import { configure } from '../lib/configure.js'\nimport errCode from 'err-code'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/key').API<HTTPClientExtraOptions>} KeyAPI\n */\n\nexport const createExport = configure(api => {\n  /**\n   * @type {KeyAPI[\"export\"]}\n   */\n  const exportKey = async (name, password, options = {}) => {\n    throw errCode(new Error('Not implemented'), 'ERR_NOT_IMPLEMENTED')\n  }\n\n  return exportKey\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/key/gen.js",
    "content": "import { objectToCamel } from '../lib/object-to-camel.js'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/key').API<HTTPClientExtraOptions>} KeyAPI\n */\n\nexport const createGen = configure(api => {\n  /**\n   * @type {KeyAPI[\"gen\"]}\n   */\n  async function gen (name, options) {\n    const opts = options ?? { type: 'Ed25519' }\n\n    const res = await api.post('key/gen', {\n      signal: opts.signal,\n      searchParams: toUrlSearchParams({\n        arg: name,\n        ...opts\n      }),\n      headers: opts.headers\n    })\n    const data = await res.json()\n\n    // @ts-expect-error server output is not typed\n    return objectToCamel(data)\n  }\n  return gen\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/key/import.js",
    "content": "import { objectToCamel } from '../lib/object-to-camel.js'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/key').API<HTTPClientExtraOptions>} KeyAPI\n */\n\nexport const createImport = configure(api => {\n  /**\n   * @type {KeyAPI[\"import\"]}\n   */\n  async function importKey (name, pem, password, options = {}) {\n    const res = await api.post('key/import', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: name,\n        pem,\n        password,\n        ...options\n      }),\n      headers: options.headers\n    })\n    const data = await res.json()\n\n    // @ts-expect-error server output is not typed\n    return objectToCamel(data)\n  }\n  return importKey\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/key/index.js",
    "content": "import { createExport } from './export.js'\nimport { createGen } from './gen.js'\nimport { createImport } from './import.js'\nimport { createInfo } from './info.js'\nimport { createList } from './list.js'\nimport { createRename } from './rename.js'\nimport { createRm } from './rm.js'\n\n/**\n * @param {import('../types').Options} config\n */\nexport function createKey (config) {\n  return {\n    export: createExport(config),\n    gen: createGen(config),\n    import: createImport(config),\n    info: createInfo(config),\n    list: createList(config),\n    rename: createRename(config),\n    rm: createRm(config)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/key/info.js",
    "content": "import { configure } from '../lib/configure.js'\nimport errCode from 'err-code'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/key').API<HTTPClientExtraOptions>} KeyAPI\n */\n\nexport const createInfo = configure(api => {\n  /**\n   * @type {KeyAPI[\"info\"]}\n   */\n  const info = async (name, options = {}) => {\n    throw errCode(new Error('Not implemented'), 'ERR_NOT_IMPLEMENTED')\n  }\n\n  return info\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/key/list.js",
    "content": "import { objectToCamel } from '../lib/object-to-camel.js'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/key').API<HTTPClientExtraOptions>} KeyAPI\n */\n\nexport const createList = configure(api => {\n  /**\n   * @type {KeyAPI[\"list\"]}\n   */\n  async function list (options = {}) {\n    const res = await api.post('key/list', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams(options),\n      headers: options.headers\n    })\n    const data = await res.json()\n\n    return (data.Keys || []).map((/** @type {any} **/ k) => objectToCamel(k))\n  }\n  return list\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/key/rename.js",
    "content": "import { objectToCamel } from '../lib/object-to-camel.js'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/key').API<HTTPClientExtraOptions>} KeyAPI\n */\n\nexport const createRename = configure(api => {\n  /**\n   * @type {KeyAPI[\"rename\"]}\n   */\n  async function rename (oldName, newName, options = {}) {\n    const res = await api.post('key/rename', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: [\n          oldName,\n          newName\n        ],\n        ...options\n      }),\n      headers: options.headers\n    })\n\n    // @ts-expect-error server output is not typed\n    return objectToCamel(await res.json())\n  }\n  return rename\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/key/rm.js",
    "content": "import { objectToCamel } from '../lib/object-to-camel.js'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/key').API<HTTPClientExtraOptions>} KeyAPI\n */\n\nexport const createRm = configure(api => {\n  /**\n   * @type {KeyAPI[\"rm\"]}\n   */\n  async function rm (name, options = {}) {\n    const res = await api.post('key/rm', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: name,\n        ...options\n      }),\n      headers: options.headers\n    })\n    const data = await res.json()\n\n    // @ts-expect-error server output is not typed\n    return objectToCamel(data.Keys[0])\n  }\n  return rm\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/lib/abort-signal.js",
    "content": "import { anySignal } from 'any-signal'\n\n/**\n * @param {any[]} signals\n * @returns {AbortSignal[]}\n */\nfunction filter (signals) {\n  return signals.filter(Boolean)\n}\n\n/**\n * @param  {...AbortSignal|undefined} signals\n */\nexport function abortSignal (...signals) {\n  return anySignal(filter(signals))\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/lib/configure.js",
    "content": "\n/* eslint-env browser */\n\nimport { Client } from './core.js'\n\n// Set default configuration and call create function with them\n/**\n * @typedef { import(\"../types\").Options } Options\n */\n\n/**\n * @template T\n * @typedef {(client: Client, clientOptions: Options) => T} Fn\n */\n\n/**\n * @template T\n * @typedef {(clientOptions: Options) => T} Factory\n */\n\n/**\n * @template T\n * @param {Fn<T>} fn\n * @returns {Factory<T>}\n */\nexport const configure = (fn) => {\n  return (options) => {\n    return fn(new Client(options), options)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/lib/core.js",
    "content": "\n/* eslint-env browser */\n\nimport { isMultiaddr } from '@multiformats/multiaddr'\nimport { isBrowser, isWebWorker, isNode } from 'ipfs-utils/src/env.js'\nimport parseDuration from 'parse-duration'\nimport { logger } from '@libp2p/logger'\nimport HTTP from 'ipfs-utils/src/http.js'\nimport mergeOpts from 'merge-options'\nimport { toUrlString } from 'ipfs-core-utils/to-url-string'\nimport getAgent from 'ipfs-core-utils/agent'\n\nconst log = logger('ipfs-http-client:lib:error-handler')\nconst merge = mergeOpts.bind({ ignoreUndefined: true })\n\nconst DEFAULT_PROTOCOL = isBrowser || isWebWorker ? location.protocol : 'http'\nconst DEFAULT_HOST = isBrowser || isWebWorker ? location.hostname : 'localhost'\nconst DEFAULT_PORT = isBrowser || isWebWorker ? location.port : '5001'\n\n/**\n * @typedef {import('ipfs-utils/src/types').HTTPOptions} HTTPOptions\n * @typedef {import('../types').Options} Options\n * @typedef {import('@multiformats/multiaddr').Multiaddr} Multiaddr\n */\n\n/**\n * @param {Options|URL|Multiaddr|string} [options]\n * @returns {Options}\n */\nconst normalizeOptions = (options = {}) => {\n  let url\n  /** @type {Options} */\n  let opts = {}\n  let agent\n\n  if (typeof options === 'string' || isMultiaddr(options)) {\n    url = new URL(toUrlString(options))\n  } else if (options instanceof URL) {\n    url = options\n  } else if (typeof options.url === 'string' || isMultiaddr(options.url)) {\n    url = new URL(toUrlString(options.url))\n    opts = options\n  } else if (options.url instanceof URL) {\n    url = options.url\n    opts = options\n  } else {\n    opts = options || {}\n\n    const protocol = (opts.protocol || DEFAULT_PROTOCOL).replace(':', '')\n    const host = (opts.host || DEFAULT_HOST).split(':')[0]\n    const port = (opts.port || DEFAULT_PORT)\n\n    url = new URL(`${protocol}://${host}:${port}`)\n  }\n\n  if (opts.apiPath) {\n    url.pathname = opts.apiPath\n  } else if (url.pathname === '/' || url.pathname === undefined) {\n    url.pathname = 'api/v0'\n  }\n\n  if (isNode) {\n    const Agent = getAgent(url)\n\n    agent = opts.agent || new Agent({\n      keepAlive: true,\n      // Similar to browsers which limit connections to six per host\n      maxSockets: 6\n    })\n  }\n\n  return {\n    ...opts,\n    host: url.host,\n    protocol: url.protocol.replace(':', ''),\n    port: Number(url.port),\n    apiPath: url.pathname,\n    url,\n    agent\n  }\n}\n\n/**\n * @param {Response} response\n */\nexport const errorHandler = async (response) => {\n  let msg\n\n  try {\n    if ((response.headers.get('Content-Type') || '').startsWith('application/json')) {\n      const data = await response.json()\n      log(data)\n      msg = data.Message || data.message\n    } else {\n      msg = await response.text()\n    }\n  } catch (/** @type {any} */ err) {\n    log('Failed to parse error response', err)\n    // Failed to extract/parse error message from response\n    msg = err.message\n  }\n\n  /** @type {Error} */\n  let error = new HTTP.HTTPError(response)\n\n  if (msg) {\n    // This is what rs-ipfs returns where there's a timeout\n    if (msg.includes('deadline has elapsed')) {\n      error = new HTTP.TimeoutError()\n    }\n\n    // This is what go-ipfs returns where there's a timeout\n    if (msg && msg.includes('context deadline exceeded')) {\n      error = new HTTP.TimeoutError()\n    }\n  }\n\n  // This also gets returned\n  if (msg && msg.includes('request timed out')) {\n    error = new HTTP.TimeoutError()\n  }\n\n  // If we managed to extract a message from the response, use it\n  if (msg) {\n    error.message = msg\n  }\n\n  throw error\n}\n\nconst KEBAB_REGEX = /[A-Z\\u00C0-\\u00D6\\u00D8-\\u00DE]/g\n\n/**\n * @param {string} str\n */\nconst kebabCase = (str) => {\n  return str.replace(KEBAB_REGEX, function (match) {\n    return '-' + match.toLowerCase()\n  })\n}\n\n/**\n * @param {string | number} value\n */\nconst parseTimeout = (value) => {\n  return typeof value === 'string' ? parseDuration(value) : value\n}\n\nexport class Client extends HTTP {\n  /**\n   * @param {Options|URL|Multiaddr|string} [options]\n   */\n  constructor (options = {}) {\n    const opts = normalizeOptions(options)\n\n    super({\n      timeout: parseTimeout(opts.timeout || 0) || undefined,\n      headers: opts.headers,\n      base: `${opts.url}`,\n      handleError: errorHandler,\n      transformSearchParams: (search) => {\n        const out = new URLSearchParams()\n\n        for (const [key, value] of search) {\n          if (\n            value !== 'undefined' &&\n            value !== 'null' &&\n            key !== 'signal'\n          ) {\n            out.append(kebabCase(key), value)\n          }\n\n          // @ts-expect-error server timeouts are strings\n          if (key === 'timeout' && !isNaN(value)) {\n            out.append(kebabCase(key), value)\n          }\n        }\n\n        return out\n      },\n      // @ts-expect-error this can be a https agent or a http agent\n      agent: opts.agent\n    })\n\n    // @ts-expect-error - cannot delete no-optional fields\n    delete this.get\n    // @ts-expect-error - cannot delete no-optional fields\n    delete this.put\n    // @ts-expect-error - cannot delete no-optional fields\n    delete this.delete\n    // @ts-expect-error - cannot delete no-optional fields\n    delete this.options\n\n    const fetch = this.fetch\n\n    /**\n     * @param {string | Request} resource\n     * @param {HTTPOptions} options\n     */\n    this.fetch = (resource, options = {}) => {\n      if (typeof resource === 'string' && !resource.startsWith('/')) {\n        resource = `${opts.url}/${resource}`\n      }\n\n      return fetch.call(this, resource, merge(options, {\n        method: 'POST'\n      }))\n    }\n  }\n}\n\nexport const HTTPError = HTTP.HTTPError\n"
  },
  {
    "path": "packages/ipfs-http-client/src/lib/http-rpc-wire-format.js",
    "content": "import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\nimport { base64url } from 'multiformats/bases/base64'\n\n/* HTTP RPC:\n * - wraps binary data in multibase. base64url is used to avoid issues\n *   when a binary data is passed as search param in URL.\n *   Historical context: https://github.com/ipfs/go-ipfs/issues/7939\n *   Multibase wrapping introduced in: https://github.com/ipfs/go-ipfs/pull/8183\n */\n\n/**\n * @param {Array<string>} strings\n * @returns {Array<string>} strings\n */\nconst rpcArrayToTextArray = strings => {\n  if (Array.isArray(strings)) {\n    return strings.map(rpcToText)\n  }\n  return strings\n}\n\n/**\n * @param {string} mb\n * @returns {string}\n */\nconst rpcToText = mb => uint8ArrayToString(rpcToBytes(mb))\n\n/**\n * @param {string} mb\n * @returns {Uint8Array}\n */\nconst rpcToBytes = mb => base64url.decode(mb)\n\n/**\n * @param {string} mb\n * @returns {bigint}\n */\nconst rpcToBigInt = mb => BigInt(`0x${uint8ArrayToString(base64url.decode(mb), 'base16')}`)\n\n/**\n * @param {string} text\n * @returns {string}\n */\nconst textToUrlSafeRpc = text => base64url.encode(uint8ArrayFromString(text))\n\nexport { rpcArrayToTextArray, rpcToText, rpcToBytes, rpcToBigInt, textToUrlSafeRpc }\n"
  },
  {
    "path": "packages/ipfs-http-client/src/lib/mode-to-string.js",
    "content": "\n/**\n * @param {number | string | undefined} mode\n */\nexport function modeToString (mode) {\n  if (mode == null) {\n    return undefined\n  }\n\n  if (typeof mode === 'string') {\n    return mode\n  }\n\n  return mode.toString(8).padStart(4, '0')\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/lib/object-to-camel-with-metadata.js",
    "content": "import { objectToCamel } from './object-to-camel.js'\n\n/**\n * @param {Record<string, any>} entry\n */\nexport function objectToCamelWithMetadata (entry) {\n  const file = objectToCamel(entry)\n\n  if (Object.prototype.hasOwnProperty.call(file, 'mode')) {\n    file.mode = parseInt(file.mode, 8)\n  }\n\n  if (Object.prototype.hasOwnProperty.call(file, 'mtime')) {\n    file.mtime = {\n      secs: file.mtime,\n      nsecs: file.mtimeNsecs || 0\n    }\n\n    delete file.mtimeNsecs\n  }\n\n  return file\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/lib/object-to-camel.js",
    "content": "\n/**\n * Convert object properties to camel case.\n * NOT recursive!\n * e.g.\n * AgentVersion => agentVersion\n * ID => id\n *\n * @param {Record<string, any>} obj\n */\nexport function objectToCamel (obj) {\n  if (obj == null) {\n    return obj\n  }\n\n  const caps = /^[A-Z]+$/\n\n  /** @type {Record<string, any>} */\n  const output = {}\n\n  return Object.keys(obj).reduce((camelObj, k) => {\n    if (caps.test(k)) { // all caps\n      camelObj[k.toLowerCase()] = obj[k]\n    } else if (caps.test(k[0])) { // pascal\n      camelObj[k[0].toLowerCase() + k.slice(1)] = obj[k]\n    } else {\n      camelObj[k] = obj[k]\n    }\n    return camelObj\n  }, output)\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/lib/parse-mtime.js",
    "content": "import errCode from 'err-code'\n\n/**\n * @param {any} input\n */\nexport function parseMtime (input) {\n  if (input == null) {\n    return undefined\n  }\n\n  /** @type {{ secs: number, nsecs?: number } | undefined} */\n  let mtime\n\n  // { secs, nsecs }\n  if (input.secs != null) {\n    mtime = {\n      secs: input.secs,\n      nsecs: input.nsecs\n    }\n  }\n\n  // UnixFS TimeSpec\n  if (input.Seconds != null) {\n    mtime = {\n      secs: input.Seconds,\n      nsecs: input.FractionalNanoseconds\n    }\n  }\n\n  // process.hrtime()\n  if (Array.isArray(input)) {\n    mtime = {\n      secs: input[0],\n      nsecs: input[1]\n    }\n  }\n\n  // Javascript Date\n  if (input instanceof Date) {\n    const ms = input.getTime()\n    const secs = Math.floor(ms / 1000)\n\n    mtime = {\n      secs: secs,\n      nsecs: (ms - (secs * 1000)) * 1000\n    }\n  }\n\n  /*\n  TODO: https://github.com/ipfs/aegir/issues/487\n\n  // process.hrtime.bigint()\n  if (input instanceof BigInt) {\n    const secs = input / BigInt(1e9)\n    const nsecs = input - (secs * BigInt(1e9))\n\n    mtime = {\n      secs: parseInt(secs.toString()),\n      nsecs: parseInt(nsecs.toString())\n    }\n  }\n  */\n\n  if (!Object.prototype.hasOwnProperty.call(mtime, 'secs')) {\n    return undefined\n  }\n\n  if (mtime != null && mtime.nsecs != null && (mtime.nsecs < 0 || mtime.nsecs > 999999999)) {\n    throw errCode(new Error('mtime-nsecs must be within the range [0,999999999]'), 'ERR_INVALID_MTIME_NSECS')\n  }\n\n  return mtime\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/lib/resolve.js",
    "content": "import { CID } from 'multiformats/cid'\nimport errCode from 'err-code'\n\n/**\n * @typedef {import('ipfs-core-types/src/utils').AbortOptions} AbortOptions\n */\n\n/**\n * Retrieves IPLD Nodes along the `path` that is rooted at `cid`.\n *\n * @param {CID} cid - the CID where the resolving starts\n * @param {string} path - the path that should be resolved\n * @param {import('ipfs-core-utils/multicodecs').Multicodecs} codecs\n * @param {(cid: CID, options?: AbortOptions) => Promise<Uint8Array>} getBlock\n * @param {AbortOptions} [options]\n */\nexport async function * resolve (cid, path, codecs, getBlock, options) {\n  /**\n   * @param {CID} cid\n   */\n  const load = async (cid) => {\n    const codec = await codecs.getCodec(cid.code)\n    const block = await getBlock(cid, options)\n\n    return codec.decode(block)\n  }\n\n  const parts = path.split('/').filter(Boolean)\n  let value = await load(cid)\n  let lastCid = cid\n\n  // End iteration if there isn't a CID to follow any more\n  while (parts.length) {\n    const key = parts.shift()\n\n    if (!key) {\n      throw errCode(new Error(`Could not resolve path \"${path}\"`), 'ERR_INVALID_PATH')\n    }\n\n    if (Object.prototype.hasOwnProperty.call(value, key)) {\n      value = value[key]\n\n      yield {\n        value,\n        remainderPath: parts.join('/')\n      }\n    } else {\n      throw errCode(new Error(`no link named \"${key}\" under ${lastCid}`), 'ERR_NO_LINK')\n    }\n\n    const cid = CID.asCID(value)\n\n    if (cid) {\n      lastCid = cid\n      value = await load(value)\n    }\n  }\n\n  yield {\n    value,\n    remainderPath: ''\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/lib/to-url-search-params.js",
    "content": "import { modeToString } from './mode-to-string.js'\nimport { parseMtime } from '../lib/parse-mtime.js'\n\n/**\n * @param {*} params\n * @returns {URLSearchParams}\n */\nexport function toUrlSearchParams ({ arg, searchParams, hashAlg, mtime, mode, ...options } = {}) {\n  if (searchParams) {\n    options = {\n      ...options,\n      ...searchParams\n    }\n  }\n\n  if (hashAlg) {\n    options.hash = hashAlg\n  }\n\n  if (mtime != null) {\n    mtime = parseMtime(mtime)\n\n    options.mtime = mtime.secs\n    options.mtimeNsecs = mtime.nsecs\n  }\n\n  if (mode != null) {\n    options.mode = modeToString(mode)\n  }\n\n  if (options.timeout && !isNaN(options.timeout)) {\n    // server API expects timeouts as strings\n    options.timeout = `${options.timeout}ms`\n  }\n\n  if (arg === undefined || arg === null) {\n    arg = []\n  } else if (!Array.isArray(arg)) {\n    arg = [arg]\n  }\n\n  const urlSearchParams = new URLSearchParams(options)\n\n  arg.forEach((/** @type {any} */ arg) => urlSearchParams.append('arg', arg))\n\n  return urlSearchParams\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/log/index.js",
    "content": "import { createLevel } from './level.js'\nimport { createLs } from './ls.js'\nimport { createTail } from './tail.js'\n\n/**\n * @param {import('../types').Options} config\n */\nexport function createLog (config) {\n  return {\n    level: createLevel(config),\n    ls: createLs(config),\n    tail: createTail(config)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/log/level.js",
    "content": "import { objectToCamel } from '../lib/object-to-camel.js'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/log').API<HTTPClientExtraOptions>} LogAPI\n */\n\nexport const createLevel = configure(api => {\n  /**\n   * @type {LogAPI[\"level\"]}\n   */\n  async function level (subsystem, level, options = {}) {\n    const res = await api.post('log/level', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: [\n          subsystem,\n          level\n        ],\n        ...options\n      }),\n      headers: options.headers\n    })\n\n    return objectToCamel(await res.json())\n  }\n  return level\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/log/ls.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/log').API<HTTPClientExtraOptions>} LogAPI\n */\n\nexport const createLs = configure(api => {\n  /**\n   * @type {LogAPI[\"ls\"]}\n   */\n  async function ls (options = {}) {\n    const res = await api.post('log/ls', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams(options),\n      headers: options.headers\n    })\n\n    const data = await res.json()\n    return data.Strings\n  }\n  return ls\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/log/tail.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/log').API<HTTPClientExtraOptions>} LogAPI\n */\n\nexport const createTail = configure(api => {\n  /**\n   * @type {LogAPI[\"tail\"]}\n   */\n  async function * tail (options = {}) {\n    const res = await api.post('log/tail', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams(options),\n      headers: options.headers\n    })\n\n    yield * res.ndjson()\n  }\n  return tail\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/ls.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { configure } from './lib/configure.js'\nimport { toUrlSearchParams } from './lib/to-url-search-params.js'\nimport { createStat } from './files/stat.js'\n\n/**\n * @typedef {import('./types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/root').API<HTTPClientExtraOptions>} RootAPI\n */\n\nexport const createLs = configure((api, opts) => {\n  /**\n   * @type {RootAPI[\"ls\"]}\n   */\n  async function * ls (path, options = {}) {\n    const pathStr = `${path instanceof Uint8Array ? CID.decode(path) : path}`\n\n    /**\n     * @param {*} link\n     */\n    async function mapLink (link) {\n      let hash = link.Hash\n\n      if (hash.includes('/')) {\n        // the hash is a path, but we need the CID\n        const ipfsPath = hash.startsWith('/ipfs/') ? hash : `/ipfs/${hash}`\n        const stats = await createStat(opts)(ipfsPath)\n\n        hash = stats.cid\n      } else {\n        hash = CID.parse(hash)\n      }\n\n      /** @type {import('ipfs-core-types/src/root').IPFSEntry} */\n      const entry = {\n        name: link.Name,\n        path: pathStr + (link.Name ? `/${link.Name}` : ''),\n        size: link.Size,\n        cid: hash,\n        type: typeOf(link)\n      }\n\n      if (link.Mode) {\n        entry.mode = parseInt(link.Mode, 8)\n      }\n\n      if (link.Mtime !== undefined && link.Mtime !== null) {\n        entry.mtime = {\n          secs: link.Mtime\n        }\n\n        if (link.MtimeNsecs !== undefined && link.MtimeNsecs !== null) {\n          entry.mtime.nsecs = link.MtimeNsecs\n        }\n      }\n\n      return entry\n    }\n\n    const res = await api.post('ls', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: pathStr,\n        ...options\n      }),\n      headers: options.headers\n    })\n\n    for await (let result of res.ndjson()) {\n      result = result.Objects\n\n      if (!result) {\n        throw new Error('expected .Objects in results')\n      }\n\n      result = result[0]\n      if (!result) {\n        throw new Error('expected one array in results.Objects')\n      }\n\n      const links = result.Links\n      if (!Array.isArray(links)) {\n        throw new Error('expected one array in results.Objects[0].Links')\n      }\n\n      if (!links.length) {\n        // no links, this is a file, yield a single result\n        yield mapLink(result)\n\n        return\n      }\n\n      yield * links.map(mapLink)\n    }\n  }\n  return ls\n})\n\n/**\n * @param {any} link\n */\nfunction typeOf (link) {\n  switch (link.Type) {\n    case 1:\n    case 5:\n      return 'dir'\n    case 2:\n      return 'file'\n    default:\n      return 'file'\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/mount.js",
    "content": "import { objectToCamel } from './lib/object-to-camel.js'\nimport { configure } from './lib/configure.js'\nimport { toUrlSearchParams } from './lib/to-url-search-params.js'\n\n/**\n * @typedef {import('./types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/root').API<HTTPClientExtraOptions>} RootAPI\n */\n\nexport const createMount = configure(api => {\n  /**\n   * @type {RootAPI[\"mount\"]}\n   */\n  async function mount (options = {}) {\n    const res = await api.post('dns', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams(options),\n      headers: options.headers\n    })\n\n    return objectToCamel(await res.json())\n  }\n  return mount\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/name/index.js",
    "content": "import { createPublish } from './publish.js'\nimport { createResolve } from './resolve.js'\nimport { createPubsub } from './pubsub/index.js'\n\n/**\n * @param {import('../types').Options} config\n */\nexport function createName (config) {\n  return {\n    publish: createPublish(config),\n    resolve: createResolve(config),\n    pubsub: createPubsub(config)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/name/publish.js",
    "content": "import { objectToCamel } from '../lib/object-to-camel.js'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/name').API<HTTPClientExtraOptions>} NameAPI\n */\n\nexport const createPublish = configure(api => {\n  /**\n   * @type {NameAPI[\"publish\"]}\n   */\n  async function publish (path, options = {}) {\n    const res = await api.post('name/publish', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: `${path}`,\n        ...options\n      }),\n      headers: options.headers\n    })\n\n    // @ts-expect-error server output is not typed\n    return objectToCamel(await res.json())\n  }\n  return publish\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/name/pubsub/cancel.js",
    "content": "import { objectToCamel } from '../../lib/object-to-camel.js'\nimport { configure } from '../../lib/configure.js'\nimport { toUrlSearchParams } from '../../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/name/pubsub').API<HTTPClientExtraOptions>} NamePubsubAPI\n */\n\nexport const createCancel = configure(api => {\n  /**\n   * @type {NamePubsubAPI[\"cancel\"]}\n   */\n  async function cancel (name, options = {}) {\n    const res = await api.post('name/pubsub/cancel', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: name,\n        ...options\n      }),\n      headers: options.headers\n    })\n\n    // @ts-expect-error server output is not typed\n    return objectToCamel(await res.json())\n  }\n  return cancel\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/name/pubsub/index.js",
    "content": "import { createCancel } from './cancel.js'\nimport { createState } from './state.js'\nimport { createSubs } from './subs.js'\n\n/**\n * @param {import('../../types').Options} config\n */\nexport function createPubsub (config) {\n  return {\n    cancel: createCancel(config),\n    state: createState(config),\n    subs: createSubs(config)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/name/pubsub/state.js",
    "content": "import { objectToCamel } from '../../lib/object-to-camel.js'\nimport { configure } from '../../lib/configure.js'\nimport { toUrlSearchParams } from '../../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/name/pubsub').API<HTTPClientExtraOptions>} NamePubsubAPI\n */\n\nexport const createState = configure(api => {\n  /**\n   * @type {NamePubsubAPI[\"state\"]}\n   */\n  async function state (options = {}) {\n    const res = await api.post('name/pubsub/state', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams(options),\n      headers: options.headers\n    })\n\n    // @ts-expect-error server output is not typed\n    return objectToCamel(await res.json())\n  }\n  return state\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/name/pubsub/subs.js",
    "content": "import { configure } from '../../lib/configure.js'\nimport { toUrlSearchParams } from '../../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/name/pubsub').API<HTTPClientExtraOptions>} NamePubsubAPI\n */\n\nexport const createSubs = configure(api => {\n  /**\n   * @type {NamePubsubAPI[\"subs\"]}\n   */\n  async function subs (options = {}) {\n    const res = await api.post('name/pubsub/subs', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams(options),\n      headers: options.headers\n    })\n    const data = await res.json()\n\n    return data.Strings || []\n  }\n  return subs\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/name/resolve.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/name').API<HTTPClientExtraOptions>} NameAPI\n */\n\nexport const createResolve = configure(api => {\n  /**\n   * @type {NameAPI[\"resolve\"]}\n   */\n  async function * resolve (path, options = {}) {\n    const res = await api.post('name/resolve', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: path,\n        stream: true,\n        ...options\n      }),\n      headers: options.headers\n    })\n\n    for await (const result of res.ndjson()) {\n      yield result.Path\n    }\n  }\n  return resolve\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/object/data.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/object').API<HTTPClientExtraOptions>} ObjectAPI\n */\n\nexport const createData = configure(api => {\n  /**\n   * @type {ObjectAPI[\"data\"]}\n   */\n  async function data (cid, options = {}) {\n    const res = await api.post('object/data', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: `${cid instanceof Uint8Array ? CID.decode(cid) : cid}`,\n        ...options\n      }),\n      headers: options.headers\n    })\n    const data = await res.arrayBuffer()\n\n    return new Uint8Array(data, 0, data.byteLength)\n  }\n  return data\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/object/get.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/object').API<HTTPClientExtraOptions>} ObjectAPI\n */\n\nexport const createGet = configure(api => {\n  /**\n   * @type {ObjectAPI[\"get\"]}\n   */\n  async function get (cid, options = {}) {\n    const res = await api.post('object/get', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: `${cid instanceof Uint8Array ? CID.decode(cid) : cid}`,\n        dataEncoding: 'base64',\n        ...options\n      }),\n      headers: options.headers\n    })\n    const data = await res.json()\n\n    return {\n      Data: uint8ArrayFromString(data.Data, 'base64pad'),\n      Links: (data.Links || []).map((/** @type {any} */ link) => ({\n        Name: link.Name,\n        Hash: CID.parse(link.Hash),\n        Tsize: link.Size\n      }))\n    }\n  }\n  return get\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/object/index.js",
    "content": "import { createData } from './data.js'\nimport { createGet } from './get.js'\nimport { createLinks } from './links.js'\nimport { createNew } from './new.js'\nimport { createPut } from './put.js'\nimport { createStat } from './stat.js'\nimport { createPatch } from './patch/index.js'\n\n/**\n * @param {import('ipfs-core-utils/multicodecs').Multicodecs} codecs\n * @param {import('../types').Options} config\n */\nexport function createObject (codecs, config) {\n  return {\n    data: createData(config),\n    get: createGet(config),\n    links: createLinks(config),\n    new: createNew(config),\n    put: createPut(codecs, config),\n    stat: createStat(config),\n    patch: createPatch(config)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/object/links.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/object').API<HTTPClientExtraOptions>} ObjectAPI\n */\n\nexport const createLinks = configure(api => {\n  /**\n   * @type {ObjectAPI[\"links\"]}\n   */\n  async function links (cid, options = {}) {\n    const res = await api.post('object/links', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: `${cid instanceof Uint8Array ? CID.decode(cid) : cid}`,\n        ...options\n      }),\n      headers: options.headers\n    })\n    const data = await res.json()\n\n    return (data.Links || []).map((/** @type {any} */ l) => ({\n      Name: l.Name,\n      Tsize: l.Size,\n      Hash: CID.parse(l.Hash)\n    }))\n  }\n  return links\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/object/new.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/object').API<HTTPClientExtraOptions>} ObjectAPI\n */\n\nexport const createNew = configure(api => {\n  /**\n   * @type {ObjectAPI[\"new\"]}\n   */\n  async function newObject (options = {}) {\n    const res = await api.post('object/new', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: options.template,\n        ...options\n      }),\n      headers: options.headers\n    })\n\n    const { Hash } = await res.json()\n\n    return CID.parse(Hash)\n  }\n  return newObject\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/object/patch/add-link.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { configure } from '../../lib/configure.js'\nimport { toUrlSearchParams } from '../../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/object/patch').API<HTTPClientExtraOptions>} ObjectPatchAPI\n */\n\nexport const createAddLink = configure(api => {\n  /**\n   * @type {ObjectPatchAPI[\"addLink\"]}\n   */\n  async function addLink (cid, dLink, options = {}) {\n    const res = await api.post('object/patch/add-link', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: [\n          `${cid}`,\n          // @ts-expect-error loose types\n          dLink.Name || dLink.name || '',\n          // @ts-expect-error loose types\n          (dLink.Hash || dLink.cid || '').toString() || null\n        ],\n        ...options\n      }),\n      headers: options.headers\n    })\n\n    const { Hash } = await res.json()\n\n    return CID.parse(Hash)\n  }\n\n  return addLink\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/object/patch/append-data.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { multipartRequest } from 'ipfs-core-utils/multipart-request'\nimport { configure } from '../../lib/configure.js'\nimport { toUrlSearchParams } from '../../lib/to-url-search-params.js'\nimport { abortSignal } from '../../lib/abort-signal.js'\n\n/**\n * @typedef {import('../../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/object/patch').API<HTTPClientExtraOptions>} ObjectPatchAPI\n */\n\nexport const createAppendData = configure(api => {\n  /**\n   * @type {ObjectPatchAPI[\"appendData\"]}\n   */\n  async function appendData (cid, data, options = {}) {\n    // allow aborting requests on body errors\n    const controller = new AbortController()\n    const signal = abortSignal(controller.signal, options.signal)\n\n    const res = await api.post('object/patch/append-data', {\n      signal,\n      searchParams: toUrlSearchParams({\n        arg: `${cid}`,\n        ...options\n      }),\n      ...(\n        await multipartRequest([data], controller, options.headers)\n      )\n    })\n\n    const { Hash } = await res.json()\n\n    return CID.parse(Hash)\n  }\n  return appendData\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/object/patch/index.js",
    "content": "import { createAddLink } from './add-link.js'\nimport { createAppendData } from './append-data.js'\nimport { createRmLink } from './rm-link.js'\nimport { createSetData } from './set-data.js'\n\n/**\n * @param {import('../../types').Options} config\n */\nexport function createPatch (config) {\n  return {\n    addLink: createAddLink(config),\n    appendData: createAppendData(config),\n    rmLink: createRmLink(config),\n    setData: createSetData(config)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/object/patch/rm-link.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { configure } from '../../lib/configure.js'\nimport { toUrlSearchParams } from '../../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/object/patch').API<HTTPClientExtraOptions>} ObjectPatchAPI\n */\n\nexport const createRmLink = configure(api => {\n  /**\n   * @type {ObjectPatchAPI[\"rmLink\"]}\n   */\n  async function rmLink (cid, dLink, options = {}) {\n    const res = await api.post('object/patch/rm-link', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: [\n          `${cid}`,\n          // @ts-expect-error loose types\n          dLink.Name || dLink.name || null\n        ],\n        ...options\n      }),\n      headers: options.headers\n    })\n\n    const { Hash } = await res.json()\n\n    return CID.parse(Hash)\n  }\n  return rmLink\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/object/patch/set-data.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { multipartRequest } from 'ipfs-core-utils/multipart-request'\nimport { configure } from '../../lib/configure.js'\nimport { toUrlSearchParams } from '../../lib/to-url-search-params.js'\nimport { abortSignal } from '../../lib/abort-signal.js'\n\n/**\n * @typedef {import('../../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/object/patch').API<HTTPClientExtraOptions>} ObjectPatchAPI\n */\n\nexport const createSetData = configure(api => {\n  /**\n   * @type {ObjectPatchAPI[\"setData\"]}\n   */\n  async function setData (cid, data, options = {}) {\n    // allow aborting requests on body errors\n    const controller = new AbortController()\n    const signal = abortSignal(controller.signal, options.signal)\n\n    const res = await api.post('object/patch/set-data', {\n      signal,\n      searchParams: toUrlSearchParams({\n        arg: [\n          `${cid}`\n        ],\n        ...options\n      }),\n      ...(\n        await multipartRequest([data], controller, options.headers)\n      )\n    })\n\n    const { Hash } = await res.json()\n\n    return CID.parse(Hash)\n  }\n  return setData\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/object/put.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { createPut as createDagPut } from '../dag/put.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/object').API<HTTPClientExtraOptions>} ObjectAPI\n */\n\n/**\n * @param {import('ipfs-core-utils/multicodecs').Multicodecs} codecs\n * @param {import('../types').Options} options\n */\nexport const createPut = (codecs, options) => {\n  const fn = configure((api) => {\n    const dagPut = createDagPut(codecs, options)\n\n    /**\n     * @type {ObjectAPI[\"put\"]}\n     */\n    async function put (obj, options = {}) {\n      return dagPut(obj, {\n        ...options,\n        storeCodec: 'dag-pb',\n        hashAlg: 'sha2-256',\n        version: 1\n      })\n    }\n    return put\n  })\n\n  return fn(options)\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/object/stat.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/object').API<HTTPClientExtraOptions>} ObjectAPI\n */\n\nexport const createStat = configure(api => {\n  /**\n   * @type {ObjectAPI[\"stat\"]}\n   */\n  async function stat (cid, options = {}) {\n    const res = await api.post('object/stat', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: `${cid}`,\n        ...options\n      }),\n      headers: options.headers\n    })\n\n    const output = await res.json()\n\n    return {\n      ...output,\n      Hash: CID.parse(output.Hash)\n    }\n  }\n  return stat\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/pin/add-all.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { configure } from '../lib/configure.js'\nimport { normaliseInput } from 'ipfs-core-utils/pins/normalise-input'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/pin').API<HTTPClientExtraOptions>} PinAPI\n */\n\nexport const createAddAll = configure(api => {\n  /**\n   * @type {PinAPI[\"addAll\"]}\n   */\n  async function * addAll (source, options = {}) {\n    for await (const { path, recursive, metadata } of normaliseInput(source)) {\n      const res = await api.post('pin/add', {\n        signal: options.signal,\n        searchParams: toUrlSearchParams({\n          ...options,\n          arg: path,\n          recursive,\n          metadata: metadata ? JSON.stringify(metadata) : undefined,\n          stream: true\n        }),\n        headers: options.headers\n      })\n\n      for await (const pin of res.ndjson()) {\n        if (pin.Pins) { // non-streaming response\n          for (const cid of pin.Pins) {\n            yield CID.parse(cid)\n          }\n          continue\n        }\n\n        yield CID.parse(pin)\n      }\n    }\n  }\n  return addAll\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/pin/add.js",
    "content": "import { createAddAll } from './add-all.js'\nimport last from 'it-last'\nimport { configure } from '../lib/configure.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/pin').API<HTTPClientExtraOptions>} PinAPI\n */\n\n/**\n * @param {import('../types').Options} config\n */\nexport function createAdd (config) {\n  const all = createAddAll(config)\n\n  return configure(() => {\n    /**\n     * @type {PinAPI[\"add\"]}\n     */\n    async function add (path, options = {}) {\n      // @ts-expect-error last can return undefined\n      return last(all([{\n        path,\n        ...options\n      }], options))\n    }\n    return add\n  })(config)\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/pin/index.js",
    "content": "import { createAddAll } from './add-all.js'\nimport { createAdd } from './add.js'\nimport { createLs } from './ls.js'\nimport { createRmAll } from './rm-all.js'\nimport { createRm } from './rm.js'\nimport { createRemote } from './remote/index.js'\n\n/**\n * @param {import('../types').Options} config\n */\nexport function createPin (config) {\n  return {\n    addAll: createAddAll(config),\n    add: createAdd(config),\n    ls: createLs(config),\n    rmAll: createRmAll(config),\n    rm: createRm(config),\n    remote: createRemote(config)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/pin/ls.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/pin').API<HTTPClientExtraOptions>} PinAPI\n */\n\n/**\n * @param {string} type\n * @param {string} cid\n * @param {Record<string, string>} metadata\n */\nfunction toPin (type, cid, metadata) {\n  /** @type {import('ipfs-core-types/src/pin').LsResult} */\n  const pin = {\n    type,\n    cid: CID.parse(cid)\n  }\n\n  if (metadata) {\n    pin.metadata = metadata\n  }\n\n  return pin\n}\n\nexport const createLs = configure(api => {\n  /**\n   * @type {PinAPI[\"ls\"]}\n   */\n  async function * ls (options = {}) {\n    /** @type {any[]} */\n    let paths = []\n\n    if (options.paths) {\n      paths = Array.isArray(options.paths) ? options.paths : [options.paths]\n    }\n\n    const res = await api.post('pin/ls', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        ...options,\n        arg: paths.map(path => `${path}`),\n        stream: true\n      }),\n      headers: options.headers\n    })\n\n    for await (const pin of res.ndjson()) {\n      if (pin.Keys) { // non-streaming response\n        for (const cid of Object.keys(pin.Keys)) {\n          yield toPin(pin.Keys[cid].Type, cid, pin.Keys[cid].Metadata)\n        }\n        return\n      }\n\n      yield toPin(pin.Type, pin.Cid, pin.Metadata)\n    }\n  }\n  return ls\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/pin/remote/add.js",
    "content": "import { encodeAddParams, decodePin } from './utils.js'\n\n/**\n * @typedef {import('../../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/pin/remote').API<HTTPClientExtraOptions>} RemotePiningAPI\n */\n\n/**\n * @param {import('../../lib/core').Client} client\n */\nexport function createAdd (client) {\n  /**\n   * @type {RemotePiningAPI[\"add\"]}\n   */\n  async function add (cid, { timeout, signal, headers, ...query }) {\n    const response = await client.post('pin/remote/add', {\n      timeout,\n      signal,\n      headers,\n      searchParams: encodeAddParams({ cid, ...query })\n    })\n\n    return decodePin(await response.json())\n  }\n\n  return add\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/pin/remote/index.js",
    "content": "import { Client } from '../../lib/core.js'\nimport { createAdd } from './add.js'\nimport { createLs } from './ls.js'\nimport { createRm } from './rm.js'\nimport { createRmAll } from './rm-all.js'\nimport { createService } from './service/index.js'\n\n/**\n * @param {import('../../types').Options} config\n */\nexport function createRemote (config) {\n  const client = new Client(config)\n\n  return {\n    add: createAdd(client),\n    ls: createLs(client),\n    rm: createRm(client),\n    rmAll: createRmAll(client),\n    service: createService(config)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/pin/remote/ls.js",
    "content": "import { encodeQuery, decodePin } from './utils.js'\n\n/**\n * @typedef {import('../../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/pin/remote').API<HTTPClientExtraOptions>} RemotePiningAPI\n */\n\n/**\n * @param {import('../../lib/core').Client} client\n */\nexport function createLs (client) {\n  /**\n   * @type {RemotePiningAPI[\"ls\"]}\n   */\n  async function * ls ({ timeout, signal, headers, ...query }) {\n    const response = await client.post('pin/remote/ls', {\n      timeout,\n      signal,\n      headers,\n      searchParams: encodeQuery(query)\n    })\n\n    for await (const pin of response.ndjson()) {\n      yield decodePin(pin)\n    }\n  }\n\n  return ls\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/pin/remote/rm-all.js",
    "content": "import { encodeQuery } from './utils.js'\n\n/**\n * @typedef {import('../../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/pin/remote').API<HTTPClientExtraOptions>} RemotePiningAPI\n */\n\n/**\n * @param {import('../../lib/core').Client} client\n */\nexport function createRmAll (client) {\n  /**\n   * @type {RemotePiningAPI[\"rmAll\"]}\n   */\n  async function rmAll ({ timeout, signal, headers, ...query }) {\n    await client.post('pin/remote/rm', {\n      timeout,\n      signal,\n      headers,\n      searchParams: encodeQuery({\n        ...query,\n        all: true\n      })\n    })\n  }\n\n  return rmAll\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/pin/remote/rm.js",
    "content": "import { encodeQuery } from './utils.js'\n\n/**\n * @typedef {import('../../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/pin/remote').API<HTTPClientExtraOptions>} RemotePiningAPI\n */\n\n/**\n * @param {import('../../lib/core').Client} client\n */\nexport function createRm (client) {\n  /**\n   * @type {RemotePiningAPI[\"rm\"]}\n   */\n  async function rm ({ timeout, signal, headers, ...query }) {\n    await client.post('pin/remote/rm', {\n      timeout,\n      signal,\n      headers,\n      searchParams: encodeQuery({\n        ...query,\n        all: false\n      })\n    })\n  }\n\n  return rm\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/pin/remote/service/add.js",
    "content": "import { toUrlSearchParams } from '../../../lib/to-url-search-params.js'\nimport { encodeEndpoint } from './utils.js'\n\n/**\n * @typedef {import('../../../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/pin/remote/service').API<HTTPClientExtraOptions>} RemotePiningServiceAPI\n */\n\n/**\n * @param {import('../../../lib/core').Client} client\n */\nexport function createAdd (client) {\n  /**\n   * @type {RemotePiningServiceAPI[\"add\"]}\n   */\n  async function add (name, options) {\n    const { endpoint, key, headers, timeout, signal } = options\n\n    await client.post('pin/remote/service/add', {\n      timeout,\n      signal,\n      searchParams: toUrlSearchParams({\n        arg: [name, encodeEndpoint(endpoint), key]\n      }),\n      headers\n    })\n  }\n\n  return add\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/pin/remote/service/index.js",
    "content": "import { Client } from '../../../lib/core.js'\nimport { createAdd } from './add.js'\nimport { createLs } from './ls.js'\nimport { createRm } from './rm.js'\n\n/**\n * @param {import('../../../types').Options} config\n */\nexport function createService (config) {\n  const client = new Client(config)\n\n  return {\n    add: createAdd(client),\n    ls: createLs(client),\n    rm: createRm(client)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/pin/remote/service/ls.js",
    "content": "import { toUrlSearchParams } from '../../../lib/to-url-search-params.js'\nimport { decodeRemoteService } from './utils.js'\n\n/**\n * @typedef {import('../../../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/pin/remote/service').API<HTTPClientExtraOptions>} RemotePiningServiceAPI\n */\n\n/**\n * @param {import('../../../lib/core').Client} client\n */\nexport function createLs (client) {\n  /**\n   * @type {RemotePiningServiceAPI[\"ls\"]}\n   */\n  async function ls (options = {}) {\n    // @ts-expect-error cannot derive option type from typedef\n    const { stat, headers, timeout, signal } = options\n\n    const response = await client.post('pin/remote/service/ls', {\n      timeout,\n      signal,\n      headers,\n      searchParams: stat === true ? toUrlSearchParams({ stat }) : undefined\n    })\n\n    /** @type {{RemoteServices: object[]}} */\n    const { RemoteServices } = await response.json()\n\n    return RemoteServices.map(decodeRemoteService)\n  }\n\n  return ls\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/pin/remote/service/rm.js",
    "content": "import { toUrlSearchParams } from '../../../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../../../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/pin/remote/service').API<HTTPClientExtraOptions>} RemotePiningServiceAPI\n */\n\n/**\n * @param {import('../../../lib/core').Client} client\n */\nexport function createRm (client) {\n  /**\n   * @type {RemotePiningServiceAPI[\"rm\"]}\n   */\n  async function rm (name, options = {}) {\n    await client.post('pin/remote/service/rm', {\n      signal: options.signal,\n      headers: options.headers,\n      searchParams: toUrlSearchParams({\n        arg: name\n      })\n    })\n  }\n\n  return rm\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/pin/remote/service/utils.js",
    "content": "/**\n * @typedef {import('ipfs-core-types/src/pin/remote/service').RemotePinServiceWithStat} RemotePinServiceWithStat\n */\n\n/**\n * @param {URL} url\n */\nexport function encodeEndpoint (url) {\n  const href = String(url)\n  if (href === 'undefined') {\n    throw Error('endpoint is required')\n  }\n  // Workaround trailing `/` issue in go-ipfs\n  // @see https://github.com/ipfs/go-ipfs/issues/7826\n  return href[href.length - 1] === '/' ? href.slice(0, -1) : href\n}\n\n/**\n * @param {any} json\n * @returns {RemotePinServiceWithStat}\n */\nexport function decodeRemoteService (json) {\n  return {\n    service: json.Service,\n    endpoint: new URL(json.ApiEndpoint),\n    ...(json.Stat && { stat: decodeStat(json.Stat) })\n  }\n}\n\n/**\n * @param {any} json\n * @returns {import('ipfs-core-types/src/pin/remote/service').Stat}\n */\nexport function decodeStat (json) {\n  switch (json.Status) {\n    case 'valid': {\n      const { Pinning, Pinned, Queued, Failed } = json.PinCount\n      return {\n        status: 'valid',\n        pinCount: {\n          queued: Queued,\n          pinning: Pinning,\n          pinned: Pinned,\n          failed: Failed\n        }\n      }\n    }\n    case 'invalid': {\n      return { status: 'invalid' }\n    }\n    default: {\n      return { status: json.Status }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/pin/remote/utils.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { toUrlSearchParams } from '../../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('ipfs-core-types/src/utils').AbortOptions} AbortOptions\n * @typedef {import('ipfs-core-types/src/pin/remote').Pin} Pin\n * @typedef {import('ipfs-core-types/src/pin/remote').AddOptions} AddOptions\n * @typedef {import('ipfs-core-types/src/pin/remote').Query} Query\n * @typedef {import('ipfs-core-types/src/pin/remote').Status} Status\n */\n\n/**\n * @param {object} json\n * @param {string} json.Name\n * @param {string} json.Cid\n * @param {Status} json.Status\n * @returns {Pin}\n */\nexport const decodePin = ({ Name: name, Status: status, Cid: cid }) => {\n  return {\n    cid: CID.parse(cid),\n    name,\n    status\n  }\n}\n\n/**\n * @param {any} service\n * @returns {string}\n */\nexport const encodeService = (service) => {\n  if (typeof service === 'string' && service !== '') {\n    return service\n  } else {\n    throw new TypeError('service name must be passed')\n  }\n}\n\n/**\n * @param {any} cid\n * @returns {string}\n */\nexport const encodeCID = (cid) => {\n  if (CID.asCID(cid)) {\n    return cid.toString()\n  } else {\n    throw new TypeError(`CID instance expected instead of ${typeof cid}`)\n  }\n}\n\n/**\n * @param {Query & { all?: boolean }} query\n * @returns {URLSearchParams}\n */\nexport const encodeQuery = ({ service, cid, name, status, all }) => {\n  const query = toUrlSearchParams({\n    service: encodeService(service),\n    name,\n    force: all ? true : undefined\n  })\n\n  if (cid) {\n    for (const value of cid) {\n      query.append('cid', encodeCID(value))\n    }\n  }\n\n  if (status) {\n    for (const value of status) {\n      query.append('status', value)\n    }\n  }\n\n  return query\n}\n\n/**\n * @param {AddOptions & {cid:CID}} options\n * @returns {URLSearchParams}\n */\nexport const encodeAddParams = ({ cid, service, background, name, origins }) => {\n  const params = toUrlSearchParams({\n    arg: encodeCID(cid),\n    service: encodeService(service),\n    name,\n    background: background ? true : undefined\n  })\n\n  if (origins) {\n    for (const origin of origins) {\n      params.append('origin', origin.toString())\n    }\n  }\n\n  return params\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/pin/rm-all.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { configure } from '../lib/configure.js'\nimport { normaliseInput } from 'ipfs-core-utils/pins/normalise-input'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/pin').API<HTTPClientExtraOptions>} PinAPI\n */\n\nexport const createRmAll = configure(api => {\n  /**\n   * @type {PinAPI[\"rmAll\"]}\n   */\n  async function * rmAll (source, options = {}) {\n    for await (const { path, recursive } of normaliseInput(source)) {\n      const searchParams = new URLSearchParams(options.searchParams)\n      searchParams.append('arg', `${path}`)\n\n      if (recursive != null) searchParams.set('recursive', String(recursive))\n\n      const res = await api.post('pin/rm', {\n        signal: options.signal,\n        headers: options.headers,\n        searchParams: toUrlSearchParams({\n          ...options,\n          arg: `${path}`,\n          recursive\n        })\n      })\n\n      for await (const pin of res.ndjson()) {\n        if (pin.Pins) { // non-streaming response\n          yield * pin.Pins.map((/** @type {string} */ cid) => CID.parse(cid))\n          continue\n        }\n        yield CID.parse(pin)\n      }\n    }\n  }\n  return rmAll\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/pin/rm.js",
    "content": "import { createRmAll } from './rm-all.js'\nimport last from 'it-last'\nimport { configure } from '../lib/configure.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/pin').API<HTTPClientExtraOptions>} PinAPI\n */\n\n/**\n * @param {import('../types').Options} config\n */\nexport const createRm = (config) => {\n  const all = createRmAll(config)\n\n  return configure(() => {\n    /**\n     * @type {PinAPI[\"rm\"]}\n     */\n    async function rm (path, options = {}) {\n      // @ts-expect-error last can return undefined\n      return last(all([{\n        path,\n        ...options\n      }], options))\n    }\n    return rm\n  })(config)\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/ping.js",
    "content": "import { objectToCamel } from './lib/object-to-camel.js'\nimport { configure } from './lib/configure.js'\nimport { toUrlSearchParams } from './lib/to-url-search-params.js'\n\n/**\n * @typedef {import('./types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/root').API<HTTPClientExtraOptions>} RootAPI\n */\n\nexport const createPing = configure(api => {\n  /**\n   * @type {RootAPI[\"ping\"]}\n   */\n  async function * ping (peerId, options = {}) {\n    const res = await api.post('ping', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: `${peerId}`,\n        ...options\n      }),\n      headers: options.headers,\n      transform: objectToCamel\n    })\n\n    yield * res.ndjson()\n  }\n  return ping\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/pubsub/index.js",
    "content": "import { createLs } from './ls.js'\nimport { createPeers } from './peers.js'\nimport { createPublish } from './publish.js'\nimport { createSubscribe } from './subscribe.js'\nimport { createUnsubscribe } from './unsubscribe.js'\nimport { SubscriptionTracker } from './subscription-tracker.js'\n\n/**\n * @param {import('../types').Options} config\n */\nexport function createPubsub (config) {\n  const subscriptionTracker = new SubscriptionTracker()\n\n  return {\n    ls: createLs(config),\n    peers: createPeers(config),\n    publish: createPublish(config),\n    subscribe: createSubscribe(config, subscriptionTracker),\n    unsubscribe: createUnsubscribe(config, subscriptionTracker)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/pubsub/ls.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\nimport { rpcArrayToTextArray } from '../lib/http-rpc-wire-format.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/pubsub').API<HTTPClientExtraOptions>} PubsubAPI\n */\n\nexport const createLs = configure(api => {\n  /**\n   * @type {PubsubAPI[\"ls\"]}\n   */\n  async function ls (options = {}) {\n    const { Strings } = await (await api.post('pubsub/ls', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams(options),\n      headers: options.headers\n    })).json()\n\n    return rpcArrayToTextArray(Strings) || []\n  }\n  return ls\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/pubsub/peers.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\nimport { textToUrlSafeRpc } from '../lib/http-rpc-wire-format.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/pubsub').API<HTTPClientExtraOptions>} PubsubAPI\n */\n\nexport const createPeers = configure(api => {\n  /**\n   * @type {PubsubAPI[\"peers\"]}\n   */\n  async function peers (topic, options = {}) {\n    const res = await api.post('pubsub/peers', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: textToUrlSafeRpc(topic),\n        ...options\n      }),\n      headers: options.headers\n    })\n\n    const { Strings } = await res.json()\n\n    return Strings || []\n  }\n  return peers\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/pubsub/publish.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\nimport { multipartRequest } from 'ipfs-core-utils/multipart-request'\nimport { abortSignal } from '../lib/abort-signal.js'\nimport { textToUrlSafeRpc } from '../lib/http-rpc-wire-format.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/pubsub').API<HTTPClientExtraOptions>} PubsubAPI\n */\n\nexport const createPublish = configure(api => {\n  /**\n   * @type {PubsubAPI[\"publish\"]}\n   */\n  async function publish (topic, data, options = {}) {\n    const searchParams = toUrlSearchParams({\n      arg: textToUrlSafeRpc(topic),\n      ...options\n    })\n\n    // allow aborting requests on body errors\n    const controller = new AbortController()\n    const signal = abortSignal(controller.signal, options.signal)\n\n    const res = await api.post('pubsub/pub', {\n      signal,\n      searchParams,\n      ...(\n        await multipartRequest([data], controller, options.headers)\n      )\n    })\n\n    await res.text()\n  }\n  return publish\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/pubsub/subscribe.js",
    "content": "import { logger } from '@libp2p/logger'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\nimport { textToUrlSafeRpc, rpcToText, rpcToBytes, rpcToBigInt } from '../lib/http-rpc-wire-format.js'\nimport { peerIdFromString } from '@libp2p/peer-id'\nconst log = logger('ipfs-http-client:pubsub:subscribe')\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('@libp2p/interface-pubsub').Message} Message\n * @typedef {(err: Error, fatal: boolean, msg?: Message) => void} ErrorHandlerFn\n * @typedef {import('ipfs-core-types/src/pubsub').API<HTTPClientExtraOptions & { onError?: ErrorHandlerFn }>} PubsubAPI\n * @typedef {import('../types').Options} Options\n */\n\n/**\n * @param {Options} options\n * @param {import('./subscription-tracker').SubscriptionTracker} subsTracker\n */\nexport const createSubscribe = (options, subsTracker) => {\n  return configure((api) => {\n    /**\n     * @type {PubsubAPI[\"subscribe\"]}\n     */\n    async function subscribe (topic, handler, options = {}) { // eslint-disable-line require-await\n      options.signal = subsTracker.subscribe(topic, handler, options.signal)\n\n      /** @type {(value?: any) => void} */\n      let done\n      /** @type {(error: Error) => void} */\n      let fail\n\n      const result = new Promise((resolve, reject) => {\n        done = resolve\n        fail = reject\n      })\n\n      // In Firefox, the initial call to fetch does not resolve until some data\n      // is received. If this doesn't happen within 1 second assume success\n      const ffWorkaround = setTimeout(() => done(), 1000)\n\n      // Do this async to not block Firefox\n      api.post('pubsub/sub', {\n        signal: options.signal,\n        searchParams: toUrlSearchParams({\n          arg: textToUrlSafeRpc(topic),\n          ...options\n        }),\n        headers: options.headers\n      })\n        .catch((err) => {\n          // Initial subscribe fail, ensure we clean up\n          subsTracker.unsubscribe(topic, handler)\n\n          fail(err)\n        })\n        .then((response) => {\n          clearTimeout(ffWorkaround)\n\n          if (!response) {\n            // if there was no response, the subscribe failed\n            return\n          }\n\n          readMessages(response, {\n            onMessage: (message) => {\n              if (!handler) {\n                return\n              }\n\n              if (typeof handler === 'function') {\n                handler(message)\n                return\n              }\n\n              if (typeof handler.handleEvent === 'function') {\n                handler.handleEvent(message)\n              }\n            },\n            onEnd: () => subsTracker.unsubscribe(topic, handler),\n            onError: options.onError\n          })\n\n          done()\n        })\n\n      return result\n    }\n    return subscribe\n  })(options)\n}\n\n/**\n * @param {import('ipfs-utils/src/types').ExtendedResponse} response\n * @param {object} options\n * @param {(message: Message) => void} options.onMessage\n * @param {() => void} options.onEnd\n * @param {ErrorHandlerFn} [options.onError]\n */\nasync function readMessages (response, { onMessage, onEnd, onError }) {\n  onError = onError || log\n\n  try {\n    for await (const msg of response.ndjson()) {\n      try {\n        if (!msg.from) {\n          continue\n        }\n\n        if (msg.from != null && msg.seqno != null) {\n          onMessage({\n            type: 'signed',\n            from: peerIdFromString(msg.from),\n            data: rpcToBytes(msg.data),\n            sequenceNumber: rpcToBigInt(msg.seqno),\n            topic: rpcToText(msg.topicIDs[0]),\n            key: rpcToBytes(msg.key ?? 'u'),\n            signature: rpcToBytes(msg.signature ?? 'u')\n          })\n        } else {\n          onMessage({\n            type: 'unsigned',\n            data: rpcToBytes(msg.data),\n            topic: rpcToText(msg.topicIDs[0])\n          })\n        }\n      } catch (/** @type {any} */ err) {\n        err.message = `Failed to parse pubsub message: ${err.message}`\n        onError(err, false, msg) // Not fatal\n      }\n    }\n  } catch (/** @type {any} */ err) {\n    if (!isAbortError(err)) {\n      onError(err, true) // Fatal\n    }\n  } finally {\n    onEnd()\n  }\n}\n\n/**\n * @param {Error & {type?:string}} error\n * @returns {boolean}\n */\nconst isAbortError = error => {\n  switch (error.type) {\n    case 'aborted':\n      return true\n    // It is `abort` in Electron instead of `aborted`\n    case 'abort':\n      return true\n    default:\n      // FIXME: In testing with Chrome, err.type is undefined (should not be!)\n      // Temporarily use the name property instead.\n      return error.name === 'AbortError'\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/pubsub/subscription-tracker.js",
    "content": "\n/**\n * @typedef {import('@libp2p/interface-pubsub').Message} Message\n * @typedef {import('@libp2p/interfaces/events').EventHandler<Message>} MessageHandlerFn\n *\n * @typedef {object} Subscription\n * @property {MessageHandlerFn} handler\n * @property {AbortController} controller\n */\n\nexport class SubscriptionTracker {\n  constructor () {\n    /** @type {Map<string, Subscription[]>} */\n    this._subs = new Map()\n  }\n\n  /**\n   * @param {string} topic\n   * @param {MessageHandlerFn} handler\n   * @param {AbortSignal} [signal]\n   */\n  subscribe (topic, handler, signal) {\n    const topicSubs = this._subs.get(topic) || []\n\n    if (topicSubs.find(s => s.handler === handler)) {\n      throw new Error(`Already subscribed to ${topic} with this handler`)\n    }\n\n    // Create controller so a call to unsubscribe can cancel the request\n    const controller = new AbortController()\n\n    this._subs.set(topic, [{ handler, controller }].concat(topicSubs))\n\n    // If there is an external signal, forward the abort event\n    if (signal) {\n      signal.addEventListener('abort', () => this.unsubscribe(topic, handler))\n    }\n\n    return controller.signal\n  }\n\n  /**\n   * @param {string} topic\n   * @param {MessageHandlerFn} [handler]\n   */\n  unsubscribe (topic, handler) {\n    const subs = this._subs.get(topic) || []\n    let unsubs\n\n    if (handler) {\n      this._subs.set(topic, subs.filter(s => s.handler !== handler))\n      unsubs = subs.filter(s => s.handler === handler)\n    } else {\n      this._subs.set(topic, [])\n      unsubs = subs\n    }\n\n    if (!(this._subs.get(topic) || []).length) {\n      this._subs.delete(topic)\n    }\n\n    unsubs.forEach(s => s.controller.abort())\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/pubsub/unsubscribe.js",
    "content": "\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/pubsub').API<HTTPClientExtraOptions>} PubsubAPI\n * @typedef {import('../types').Options} Options\n */\n\n/**\n * @param {Options} options\n * @param {import('./subscription-tracker').SubscriptionTracker} subsTracker\n */\nexport const createUnsubscribe = (options, subsTracker) => {\n  /**\n   * @type {PubsubAPI[\"unsubscribe\"]}\n   */\n  async function unsubscribe (topic, handler) {\n    subsTracker.unsubscribe(topic, handler)\n  }\n  return unsubscribe\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/refs/index.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { objectToCamel } from '../lib/object-to-camel.js'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\nimport { createLocal } from './local.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/refs').API<HTTPClientExtraOptions>} RefsAPI\n */\n\nexport const createRefs = configure((api, opts) => {\n  /**\n   * @type {RefsAPI[\"refs\"]}\n   */\n  const refs = async function * (args, options = {}) {\n    /** @type {import('ipfs-core-types/src/utils').IPFSPath[]} */\n    const argsArr = Array.isArray(args) ? args : [args]\n\n    const res = await api.post('refs', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: argsArr.map(arg => `${arg instanceof Uint8Array ? CID.decode(arg) : arg}`),\n        ...options\n      }),\n      headers: options.headers,\n      transform: objectToCamel\n    })\n\n    yield * res.ndjson()\n  }\n\n  return Object.assign(refs, {\n    local: createLocal(opts)\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/refs/local.js",
    "content": "import { objectToCamel } from '../lib/object-to-camel.js'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/refs').API<HTTPClientExtraOptions>} RefsAPI\n */\n\nexport const createLocal = configure(api => {\n  /**\n   * @type {RefsAPI[\"local\"]}\n   */\n  async function * refsLocal (options = {}) {\n    const res = await api.post('refs/local', {\n      signal: options.signal,\n      transform: objectToCamel,\n      searchParams: toUrlSearchParams(options),\n      headers: options.headers\n    })\n\n    yield * res.ndjson()\n  }\n  return refsLocal\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/repo/gc.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/repo').API<HTTPClientExtraOptions>} RepoAPI\n */\n\nexport const createGc = configure(api => {\n  /**\n   * @type {RepoAPI[\"gc\"]}\n   */\n  async function * gc (options = {}) {\n    const res = await api.post('repo/gc', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams(options),\n      headers: options.headers,\n      transform: (res) => {\n        return {\n          err: res.Error ? new Error(res.Error) : null,\n          cid: (res.Key || {})['/'] ? CID.parse(res.Key['/']) : null\n        }\n      }\n    })\n\n    yield * res.ndjson()\n  }\n  return gc\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/repo/index.js",
    "content": "import { createGc } from './gc.js'\nimport { createStat } from './stat.js'\nimport { createVersion } from './version.js'\n\n/**\n * @param {import('../types').Options} config\n */\nexport function createRepo (config) {\n  return {\n    gc: createGc(config),\n    stat: createStat(config),\n    version: createVersion(config)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/repo/stat.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/repo').API<HTTPClientExtraOptions>} RepoAPI\n */\n\nexport const createStat = configure(api => {\n  /**\n   * @type {RepoAPI[\"stat\"]}\n   */\n  async function stat (options = {}) {\n    const res = await api.post('repo/stat', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams(options),\n      headers: options.headers\n    })\n    const data = await res.json()\n\n    return {\n      numObjects: BigInt(data.NumObjects),\n      repoSize: BigInt(data.RepoSize),\n      repoPath: data.RepoPath,\n      version: data.Version,\n      storageMax: BigInt(data.StorageMax)\n    }\n  }\n  return stat\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/repo/version.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/repo').API<HTTPClientExtraOptions>} RepoAPI\n */\n\nexport const createVersion = configure(api => {\n  /**\n   * @type {RepoAPI[\"version\"]}\n   */\n  async function version (options = {}) {\n    const res = await (await api.post('repo/version', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams(options),\n      headers: options.headers\n    })).json()\n\n    return res.Version\n  }\n  return version\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/resolve.js",
    "content": "import { configure } from './lib/configure.js'\nimport { toUrlSearchParams } from './lib/to-url-search-params.js'\n\n/**\n * @typedef {import('./types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/root').API<HTTPClientExtraOptions>} RootAPI\n */\n\nexport const createResolve = configure(api => {\n  /**\n   * @type {RootAPI[\"resolve\"]}\n   */\n  async function resolve (path, options = {}) {\n    const res = await api.post('resolve', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: path,\n        ...options\n      }),\n      headers: options.headers\n    })\n    const { Path } = await res.json()\n    return Path\n  }\n  return resolve\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/start.js",
    "content": "import { configure } from './lib/configure.js'\nimport errCode from 'err-code'\n\n/**\n * @typedef {import('./types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/root').API<HTTPClientExtraOptions>} RootAPI\n */\n\nexport const createStart = configure(api => {\n  /**\n   * @type {RootAPI[\"start\"]}\n   */\n  const start = async (options = {}) => {\n    throw errCode(new Error('Not implemented'), 'ERR_NOT_IMPLEMENTED')\n  }\n\n  return start\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/stats/bw.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/stats').API<HTTPClientExtraOptions>} StatsAPI\n */\n\nexport const createBw = configure(api => {\n  /**\n   * @type {StatsAPI[\"bw\"]}\n   */\n  async function * bw (options = {}) {\n    const res = await api.post('stats/bw', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams(options),\n      headers: options.headers,\n      transform: (stats) => ({\n        totalIn: BigInt(stats.TotalIn),\n        totalOut: BigInt(stats.TotalOut),\n        rateIn: parseFloat(stats.RateIn),\n        rateOut: parseFloat(stats.RateOut)\n      })\n    })\n\n    yield * res.ndjson()\n  }\n  return bw\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/stats/index.js",
    "content": "import { createStat as createBitswap } from '../bitswap/stat.js'\nimport { createStat as createRepo } from '../repo/stat.js'\nimport { createBw } from './bw.js'\n\n/**\n * @param {import('../types').Options} config\n */\nexport function createStats (config) {\n  return {\n    bitswap: createBitswap(config),\n    repo: createRepo(config),\n    bw: createBw(config)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/stop.js",
    "content": "import { configure } from './lib/configure.js'\nimport { toUrlSearchParams } from './lib/to-url-search-params.js'\n\n/**\n * @typedef {import('./types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/root').API<HTTPClientExtraOptions>} RootAPI\n */\n\nexport const createStop = configure(api => {\n  /**\n   * @type {RootAPI[\"stop\"]}\n   */\n  async function stop (options = {}) {\n    const res = await api.post('shutdown', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams(options),\n      headers: options.headers\n    })\n\n    await res.text()\n  }\n  return stop\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/swarm/addrs.js",
    "content": "import { multiaddr } from '@multiformats/multiaddr'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\nimport { peerIdFromString } from '@libp2p/peer-id'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/swarm').API<HTTPClientExtraOptions>} SwarmAPI\n */\n\nexport const createAddrs = configure(api => {\n  /**\n   * @type {SwarmAPI[\"addrs\"]}\n   */\n  async function addrs (options = {}) {\n    const res = await api.post('swarm/addrs', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams(options),\n      headers: options.headers\n    })\n\n    /** @type {{ Addrs: Record<string, string[]> }} */\n    const { Addrs } = await res.json()\n\n    return Object.keys(Addrs).map(id => ({\n      id: peerIdFromString(id),\n      addrs: (Addrs[id] || []).map(a => multiaddr(a))\n    }))\n  }\n  return addrs\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/swarm/connect.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/swarm').API<HTTPClientExtraOptions>} SwarmAPI\n */\n\nexport const createConnect = configure(api => {\n  /**\n   * @type {SwarmAPI[\"connect\"]}\n   */\n  async function connect (addr, options = {}) {\n    const res = await api.post('swarm/connect', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: addr,\n        ...options\n      }),\n      headers: options.headers\n    })\n    const { Strings } = await res.json()\n\n    return Strings || []\n  }\n  return connect\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/swarm/disconnect.js",
    "content": "import { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/swarm').API<HTTPClientExtraOptions>} SwarmAPI\n */\n\nexport const createDisconnect = configure(api => {\n  /**\n   * @type {SwarmAPI[\"disconnect\"]}\n   */\n  async function disconnect (addr, options = {}) {\n    const res = await api.post('swarm/disconnect', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams({\n        arg: addr,\n        ...options\n      }),\n      headers: options.headers\n    })\n    const { Strings } = await res.json()\n\n    return Strings || []\n  }\n  return disconnect\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/swarm/index.js",
    "content": "import { createAddrs } from './addrs.js'\nimport { createConnect } from './connect.js'\nimport { createDisconnect } from './disconnect.js'\nimport { createLocalAddrs } from './local-addrs.js'\nimport { createPeers } from './peers.js'\n\n/**\n * @param {import('../types').Options} config\n */\nexport function createSwarm (config) {\n  return {\n    addrs: createAddrs(config),\n    connect: createConnect(config),\n    disconnect: createDisconnect(config),\n    localAddrs: createLocalAddrs(config),\n    peers: createPeers(config)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/swarm/local-addrs.js",
    "content": "import { multiaddr } from '@multiformats/multiaddr'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/swarm').API<HTTPClientExtraOptions>} SwarmAPI\n */\n\nexport const createLocalAddrs = configure(api => {\n  /**\n   * @type {SwarmAPI[\"localAddrs\"]}\n   */\n  async function localAddrs (options = {}) {\n    const res = await api.post('swarm/addrs/local', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams(options),\n      headers: options.headers\n    })\n\n    /** @type {{ Strings: string[] }} */\n    const { Strings } = await res.json()\n\n    return (Strings || []).map(a => multiaddr(a))\n  }\n  return localAddrs\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/swarm/peers.js",
    "content": "import { multiaddr } from '@multiformats/multiaddr'\nimport { configure } from '../lib/configure.js'\nimport { toUrlSearchParams } from '../lib/to-url-search-params.js'\nimport { peerIdFromString } from '@libp2p/peer-id'\n\n/**\n * @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/swarm').API<HTTPClientExtraOptions>} SwarmAPI\n */\n\nexport const createPeers = configure(api => {\n  /**\n   * @type {SwarmAPI[\"peers\"]}\n   */\n  async function peers (options = {}) {\n    const res = await api.post('swarm/peers', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams(options),\n      headers: options.headers\n    })\n\n    /** @type {{ Peers: { Peer: string, Addr: string, Muxer?: string, Latency?: string, Streams?: string[], Direction?: 0 | 1 }[] }} */\n    const { Peers } = await res.json()\n\n    return (Peers || []).map(peer => {\n      return {\n        addr: multiaddr(peer.Addr),\n        peer: peerIdFromString(peer.Peer),\n        muxer: peer.Muxer,\n        latency: peer.Latency,\n        streams: peer.Streams,\n        direction: peer.Direction == null ? undefined : peer.Direction === 0 ? 'inbound' : 'outbound'\n      }\n    })\n  }\n  return peers\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/src/types.ts",
    "content": "import type { Agent as HttpAgent } from 'http'\nimport type { Agent as HttpsAgent } from 'https'\nimport type { Multiaddr } from '@multiformats/multiaddr'\nimport type { BlockCodec } from 'multiformats/codecs/interface'\nimport type { MultihashHasher } from 'multiformats/hashes/interface'\nimport type { MultibaseCodec } from 'multiformats/bases/interface'\nimport type { IPFS } from 'ipfs-core-types'\n\nexport interface Options {\n  host?: string\n  port?: number\n  protocol?: string\n  headers?: Headers | Record<string, string>\n  timeout?: number | string\n  apiPath?: string\n  url?: URL|string|Multiaddr\n  ipld?: Partial<IPLDOptions>\n  agent?: HttpAgent | HttpsAgent\n}\n\nexport interface LoadBaseFn { (codeOrName: number | string): Promise<MultibaseCodec<any>> }\nexport interface LoadCodecFn { (codeOrName: number | string): Promise<BlockCodec<any, any>> }\nexport interface LoadHasherFn { (codeOrName: number | string): Promise<MultihashHasher> }\n\nexport interface IPLDOptions {\n  loadBase: LoadBaseFn\n  loadCodec: LoadCodecFn\n  loadHasher: LoadHasherFn\n  bases: Array<MultibaseCodec<any>>\n  codecs: Array<BlockCodec<any, any>>\n  hashers: MultihashHasher[]\n}\n\nexport interface HTTPClientExtraOptions {\n  headers?: Record<string, string>\n  searchParams?: URLSearchParams\n}\n\nexport interface EndpointConfig {\n  host: string\n  port: string\n  protocol: string\n  pathname: string\n  'api-path': string\n}\n\nexport interface IPFSHTTPClient extends IPFS<HTTPClientExtraOptions> {\n  getEndpointConfig: () => EndpointConfig\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/src/version.js",
    "content": "import { objectToCamel } from './lib/object-to-camel.js'\nimport { configure } from './lib/configure.js'\nimport { toUrlSearchParams } from './lib/to-url-search-params.js'\n\n/**\n * @typedef {import('./types').HTTPClientExtraOptions} HTTPClientExtraOptions\n * @typedef {import('ipfs-core-types/src/root').API<HTTPClientExtraOptions>} RootAPI\n */\n\nexport const createVersion = configure(api => {\n  /**\n   * @type {RootAPI[\"version\"]}\n   */\n  async function version (options = {}) {\n    const res = await api.post('version', {\n      signal: options.signal,\n      searchParams: toUrlSearchParams(options),\n      headers: options.headers\n    })\n\n    // @ts-expect-error server output is not typed\n    return {\n      ...objectToCamel(await res.json()),\n      'ipfs-http-client': '1.0.0'\n    }\n  }\n\n  return version\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/test/commands.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { factory } from './utils/factory.js'\nconst f = factory()\n\ndescribe('.commands', function () {\n  this.timeout(60 * 1000)\n\n  /** @type {import('ipfs-core-types').IPFS} */\n  let ipfs\n\n  before(async () => {\n    ipfs = (await f.spawn()).api\n  })\n\n  after(() => f.clean())\n\n  it('lists commands', async () => {\n    const res = await ipfs.commands()\n\n    expect(res).to.exist()\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/test/constructor.spec.js",
    "content": "/* eslint-env mocha, browser */\n\nimport { multiaddr } from '@multiformats/multiaddr'\nimport { expect } from 'aegir/chai'\nimport { factory } from './utils/factory.js'\nimport { create as ipfsClient } from '../src/index.js'\nimport { isBrowser } from 'ipfs-utils/src/env.js'\n\nconst f = factory()\n\ndescribe('ipfs-http-client constructor tests', () => {\n  describe('parameter permuations', () => {\n    it('none', () => {\n      const ipfs = ipfsClient()\n      if (typeof self !== 'undefined') {\n        const { hostname, port } = self.location\n        expectConfig(ipfs, { host: hostname, port })\n      } else {\n        expectConfig(ipfs, {})\n      }\n    })\n\n    it('opts', () => {\n      const host = 'wizard.world'\n      const port = '999'\n      const protocol = 'https'\n      const ipfs = ipfsClient({ host, port, protocol })\n      expectConfig(ipfs, { host, port, protocol })\n    })\n\n    it('opts with URL components from URL', () => {\n      const host = 'wizard.world'\n      const port = '999'\n      const protocol = 'https'\n      const url = new URL(`${protocol}://${host}:${port}`)\n      const ipfs = ipfsClient({ host: url.host, port: url.port, protocol: url.protocol })\n      expectConfig(ipfs, { host, port, protocol })\n    })\n\n    it('multiaddr dns4 string (implicit http)', () => {\n      const host = 'foo.com'\n      const port = '1001'\n      const protocol = 'http' // default to http if not specified in multiaddr\n      const addr = `/dns4/${host}/tcp/${port}`\n      const ipfs = ipfsClient(addr)\n      expectConfig(ipfs, { host, port, protocol })\n    })\n\n    it('multiaddr dns4 string (explicit https)', () => {\n      const host = 'foo.com'\n      const port = '1001'\n      const protocol = 'https'\n      const addr = `/dns4/${host}/tcp/${port}/${protocol}`\n      const ipfs = ipfsClient(addr)\n      expectConfig(ipfs, { host, port, protocol })\n    })\n\n    it('multiaddr ipv4 string (implicit http)', () => {\n      const host = '101.101.101.101'\n      const port = '1001'\n      const protocol = 'http'\n      const addr = `/ip4/${host}/tcp/${port}`\n      const ipfs = ipfsClient(addr)\n      expectConfig(ipfs, { host, port, protocol })\n    })\n\n    it('multiaddr ipv4 string (explicit https)', () => {\n      const host = '101.101.101.101'\n      const port = '1001'\n      const protocol = 'https'\n      const addr = `/ip4/${host}/tcp/${port}/${protocol}`\n      const ipfs = ipfsClient(addr)\n      expectConfig(ipfs, { host, port, protocol })\n    })\n\n    it('multiaddr instance', () => {\n      const host = 'ace.place'\n      const port = '1001'\n      const addr = multiaddr(`/dns4/${host}/tcp/${port}`)\n      const ipfs = ipfsClient(addr)\n      expectConfig(ipfs, { host, port })\n    })\n\n    it('host and port strings', () => {\n      const host = '1.1.1.1'\n      const port = '9999'\n      const ipfs = ipfsClient({ host, port })\n      expectConfig(ipfs, { host, port })\n    })\n\n    it('URL as string', () => {\n      const host = '10.100.100.255'\n      const port = '9999'\n      const apiPath = '/future/api/v1/'\n      const ipfs = ipfsClient(`http://${host}:${port}${apiPath}`)\n      expectConfig(ipfs, { host, port, apiPath })\n    })\n\n    it('URL as URL', () => {\n      const host = '10.100.100.255'\n      const port = '9999'\n      const apiPath = '/future/api/v1/'\n      const ipfs = ipfsClient(new URL(`http://${host}:${port}${apiPath}`))\n      expectConfig(ipfs, { host, port, apiPath })\n    })\n\n    it('host, port and api path', () => {\n      const host = '10.100.100.255'\n      const port = '9999'\n      const apiPath = '/future/api/v1/'\n      const ipfs = ipfsClient({ host, port, apiPath })\n      expectConfig(ipfs, { host, port, apiPath })\n    })\n\n    it('options.url as URL string', () => {\n      const host = '10.100.100.255'\n      const port = '9999'\n      const apiPath = '/future/api/v1/'\n      const ipfs = ipfsClient({ url: `http://${host}:${port}${apiPath}` })\n      expectConfig(ipfs, { host, port, apiPath })\n    })\n\n    it('options.url as URL', () => {\n      const host = '10.100.100.255'\n      const port = '9999'\n      const apiPath = '/future/api/v1/'\n      const ipfs = ipfsClient({ url: new URL(`http://${host}:${port}${apiPath}`) })\n      expectConfig(ipfs, { host, port, apiPath })\n    })\n\n    it('options.url as multiaddr (implicit http)', () => {\n      const host = 'foo.com'\n      const port = '1001'\n      const protocol = 'http' // default to http if not specified in multiaddr\n      const addr = `/dns4/${host}/tcp/${port}`\n      const ipfs = ipfsClient({ url: multiaddr(addr) })\n      expectConfig(ipfs, { host, port, protocol })\n    })\n\n    it('options.url as multiaddr (explicit https)', () => {\n      const host = 'foo.com'\n      const port = '1001'\n      const protocol = 'https'\n      const addr = `/dns4/${host}/tcp/${port}/https`\n      const ipfs = ipfsClient({ url: multiaddr(addr) })\n      expectConfig(ipfs, { host, port, protocol })\n    })\n\n    it('options.url as multiaddr string (implicit http)', () => {\n      const host = 'foo.com'\n      const port = '1001'\n      const protocol = 'http' // default to http if not specified in multiaddr\n      const addr = `/dns4/${host}/tcp/${port}`\n      const ipfs = ipfsClient({ url: addr })\n      expectConfig(ipfs, { host, port, protocol })\n    })\n\n    it('options.url as multiaddr string (explicit https)', () => {\n      const host = 'foo.com'\n      const port = '1001'\n      const protocol = 'https'\n      const addr = `/dns4/${host}/tcp/${port}/https`\n      const ipfs = ipfsClient({ url: addr })\n      expectConfig(ipfs, { host, port, protocol })\n    })\n  })\n\n  describe('integration', () => {\n    let ipfsd\n\n    before(async function () {\n      this.timeout(60 * 1000) // slow CI\n\n      ipfsd = await f.spawn()\n    })\n\n    after(() => f.clean())\n\n    it('can connect to an ipfs http api', async () => {\n      await clientWorks(ipfsClient(ipfsd.apiAddr))\n    })\n  })\n})\n\nasync function clientWorks (client) {\n  const id = await client.id()\n\n  expect(id).to.have.a.property('id')\n  expect(id).to.have.a.property('publicKey')\n}\n\nfunction expectConfig (ipfs, { host, port, protocol, apiPath }) {\n  const conf = ipfs.getEndpointConfig()\n  if (protocol) {\n    protocol = protocol + ':'\n  }\n  if (isBrowser) {\n    expect(conf.host).to.be.oneOf([host, globalThis.location.hostname, ''])\n    expect(conf.port).to.be.oneOf([port, globalThis.location.port, '80'])\n    expect(conf.protocol).to.equal(protocol || 'http:')\n    expect(conf.pathname).to.equal(apiPath || '/api/v0')\n  } else {\n    expect(conf.host).to.be.oneOf([host, 'localhost', ''])\n    expect(conf.port).to.be.oneOf([port, '5001', '80'])\n    expect(conf.protocol).to.equal(protocol || 'http:')\n    expect(conf.pathname).to.equal(apiPath || '/api/v0')\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/test/dag.spec.js",
    "content": "/* eslint-env mocha */\n/* eslint max-nested-callbacks: [\"error\", 8] */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { expect } from 'aegir/chai'\nimport * as dagPB from '@ipld/dag-pb'\nimport * as dagCBOR from '@ipld/dag-cbor'\nimport * as raw from 'multiformats/codecs/raw'\nimport { base32 } from 'multiformats/bases/base32'\nimport { create as httpClient } from '../src/index.js'\nimport { factory } from './utils/factory.js'\nconst f = factory()\n\nlet ipfs\n\ndescribe('.dag', function () {\n  this.timeout(20 * 1000)\n  before(async function () {\n    ipfs = (await f.spawn()).api\n  })\n\n  after(() => f.clean())\n\n  it('should be able to put and get a DAG node with dag-pb codec', async () => {\n    const data = uint8ArrayFromString('some data')\n    const node = {\n      Data: data,\n      Links: []\n    }\n\n    const cid = await ipfs.dag.put(node, { storeCodec: 'dag-pb', hashAlg: 'sha2-256' })\n    expect(cid.code).to.equal(dagPB.code)\n    expect(cid.toV0().toString()).to.equal('Qmd7xRhW5f29QuBFtqu3oSD27iVy35NRB91XFjmKFhtgMr')\n\n    const result = await ipfs.dag.get(cid)\n\n    expect(result.value.Data).to.deep.equal(data)\n  })\n\n  it('should be able to put and get a DAG node with dag-cbor codec', async () => {\n    const cbor = { foo: 'dag-cbor-bar' }\n    const cid = await ipfs.dag.put(cbor, { storeCodec: 'dag-cbor', hashAlg: 'sha2-256' })\n\n    expect(cid.code).to.equal(dagCBOR.code)\n    expect(cid.toString(base32)).to.equal('bafyreic6f672hnponukaacmk2mmt7vs324zkagvu4hcww6yba6kby25zce')\n\n    const result = await ipfs.dag.get(cid)\n\n    expect(result.value).to.deep.equal(cbor)\n  })\n\n  it('should be able to put and get a DAG node with raw codec', async () => {\n    const node = uint8ArrayFromString('some data')\n    const cid = await ipfs.dag.put(node, { storeCodec: 'raw', hashAlg: 'sha2-256' })\n\n    expect(cid.code).to.equal(raw.code)\n    expect(cid.toString(base32)).to.equal('bafkreiata6mq425fzikf5m26temcvg7mizjrxrkn35swuybmpah2ajan5y')\n\n    const result = await ipfs.dag.get(cid)\n\n    expect(result.value).to.deep.equal(node)\n  })\n\n  it('should error when missing DAG resolver for multicodec from requested CID', async () => {\n    const cid = await ipfs.block.put(Uint8Array.from([0, 1, 2, 3]), {\n      format: 'git-raw'\n    })\n\n    await expect(ipfs.dag.get(cid)).to.eventually.be.rejectedWith(/No codec found/)\n  })\n\n  it('should error when putting node with esoteric codec', () => {\n    const node = uint8ArrayFromString('some data')\n\n    return expect(ipfs.dag.put(node, { storeCodec: 'git-raw', hashAlg: 'sha2-256' })).to.eventually.be.rejectedWith(/No codec found/)\n  })\n\n  it('should pass through raw bytes with inputCodec', async () => {\n    const node = uint8ArrayFromString('blob 9\\0some data')\n    // we don't support git-raw in the HTTP client, but inputCodec and a Uint8Array should make\n    // the raw data pass through to go-ipfs, which does talk git-raw\n    const cid = await ipfs.dag.put(node, { inputCodec: 'git-raw', storeCodec: 'git-raw', hashAlg: 'sha1' })\n    expect(cid.code).to.equal(0x78)\n    expect(cid.toString(base32)).to.equal('baf4bcfd4azdl7vj4d4hnix75qfld6mabo4l4uwa')\n  })\n\n  it('should attempt to load an unsupported codec', async () => {\n    let askedToLoadCodec\n    const ipfs2 = httpClient({\n      url: `http://${ipfs.apiHost}:${ipfs.apiPort}`,\n      ipld: {\n        loadCodec: (codec) => {\n          askedToLoadCodec = codec === 'boop'\n          return {\n            encode: (buf) => buf\n          }\n        }\n      }\n    })\n\n    const node = uint8ArrayFromString('some data')\n\n    // error is from go-ipfs, this means the client serialized it ok\n    await expect(ipfs2.dag.put(node, { storeCodec: 'boop', hashAlg: 'sha2-256' })).to.eventually.be.rejectedWith(/unknown multicodec: \"boop\"/)\n\n    expect(askedToLoadCodec).to.be.true()\n  })\n\n  it('should allow formats to be specified without overwriting others', async () => {\n    const ipfs2 = httpClient({\n      url: `http://${ipfs.apiHost}:${ipfs.apiPort}`,\n      ipld: {\n        codecs: [{\n          name: 'custom-codec',\n          code: 1337,\n          encode: (thing) => thing,\n          decode: (thing) => thing\n        }]\n      }\n    })\n\n    const dagCborNode = {\n      hello: 'world'\n    }\n    const cid1 = await ipfs2.dag.put(dagCborNode, {\n      storeCodec: 'dag-cbor',\n      hashAlg: 'sha2-256'\n    })\n\n    const dagPbNode = {\n      Data: new Uint8Array(0),\n      Links: []\n    }\n    const cid2 = await ipfs2.dag.put(dagPbNode, {\n      storeCodec: 'dag-pb',\n      hashAlg: 'sha2-256'\n    })\n\n    await expect(ipfs2.dag.get(cid1)).to.eventually.have.property('value').that.deep.equals(dagCborNode)\n    await expect(ipfs2.dag.get(cid2)).to.eventually.have.property('value').that.deep.equals(dagPbNode)\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/test/diag.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { factory } from './utils/factory.js'\nconst f = factory()\n\ndescribe('.diag', function () {\n  this.timeout(50 * 1000)\n\n  // go-ipfs does not support these on Windows\n  if (global.process && global.process.platform === 'win32') { return }\n  let ipfs\n\n  before(async () => {\n    ipfs = (await f.spawn()).api\n  })\n\n  after(() => f.clean())\n\n  describe('api API', () => {\n    // Disabled in go-ipfs 0.4.10\n    it.skip('.diag.net', async () => {\n      const res = await ipfs.diag.net()\n\n      expect(res).to.exist()\n    })\n\n    it('.diag.sys', async () => {\n      const res = await ipfs.diag.sys()\n\n      expect(res).to.exist()\n      expect(res).to.have.a.property('memory')\n      expect(res).to.have.a.property('diskinfo')\n    })\n\n    it('.diag.cmds', async () => {\n      const res = await ipfs.diag.cmds()\n\n      expect(res).to.exist()\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/test/endpoint-config.spec.js",
    "content": "/* eslint-env mocha */\n/* eslint max-nested-callbacks: [\"error\", 8] */\n\nimport { expect } from 'aegir/chai'\nimport { create as httpClient } from '../src/index.js'\n\ndescribe('.getEndpointConfig', () => {\n  it('should return the endpoint configuration', function () {\n    const ipfs = httpClient('https://127.0.0.1:5501/ipfs/api/')\n    const endpoint = ipfs.getEndpointConfig()\n\n    expect(endpoint.host).to.equal('127.0.0.1')\n    expect(endpoint.protocol).to.equal('https:')\n    expect(endpoint.pathname).to.equal('/ipfs/api/')\n    expect(endpoint.port).to.equal('5501')\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/test/exports.spec.js",
    "content": "/* eslint-env mocha, browser */\n\nimport { CID } from 'multiformats/cid'\nimport { multiaddr } from '@multiformats/multiaddr'\nimport { expect } from 'aegir/chai'\nimport * as IpfsHttpClient from '../src/index.js'\n\ndescribe('exports', () => {\n  it('should export the expected types and utilities', () => {\n    expect(IpfsHttpClient.CID).to.equal(CID)\n    expect(IpfsHttpClient.multiaddr).to.equal(multiaddr)\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/test/files.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { expect } from 'aegir/chai'\nimport * as dagPB from '@ipld/dag-pb'\nimport { factory } from './utils/factory.js'\nconst f = factory()\n\ndescribe('.add', function () {\n  this.timeout(20 * 1000)\n\n  let ipfs\n\n  before(async function () {\n    ipfs = (await f.spawn()).api\n  })\n\n  after(() => f.clean())\n\n  it('should ignore metadata until https://github.com/ipfs/go-ipfs/issues/6920 is implemented', async () => {\n    const data = uint8ArrayFromString('some data')\n    const result = await ipfs.add(data, {\n      mode: 0o600,\n      mtime: {\n        secs: 1000,\n        nsecs: 0\n      }\n    })\n\n    expect(result).to.not.have.property('mode')\n    expect(result).to.not.have.property('mtime')\n    expect(result).to.have.property('cid')\n\n    const { cid } = result\n    expect(cid).to.have.property('code', dagPB.code)\n    expect(cid.toString()).to.equal('QmVv4Wz46JaZJeH5PMV4LGbRiiMKEmszPYY3g6fjGnVXBS')\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/test/fixtures/.gitattributes",
    "content": "* -text\n"
  },
  {
    "path": "packages/ipfs-http-client/test/fixtures/r-config.json",
    "content": "{}\n"
  },
  {
    "path": "packages/ipfs-http-client/test/fixtures/ssl/cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDOzCCAiMCCQCVqVeRIp9pFDANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJV\nUzENMAsGA1UECAwEVXRhaDEOMAwGA1UEBwwFUHJvdm8xIzAhBgNVBAoMGkFDTUUg\nU2lnbmluZyBBdXRob3JpdHkgSW5jMRQwEgYDVQQDDAtleGFtcGxlLmNvbTAeFw0x\nODA4MTQyMDEzNTdaFw0xOTEyMjcyMDEzNTdaMFgxCzAJBgNVBAYTAlVTMQ0wCwYD\nVQQIDARVdGFoMQ4wDAYDVQQHDAVQcm92bzEWMBQGA1UECgwNQUNNRSBUZWNoIElu\nYzESMBAGA1UEAwwJMTI3LjAuMC4xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\nCgKCAQEA6x6mTXV+rC35QW/sPutT1O1cugtnw+UsJx7EGgzyjh7EoXE3gb7sO96P\ntOI5zknb0vecckbiVkesmLnAs2iNa1u9EiRr6WHdc+1MfUCxyHRfP731vRZyo0kx\nbSXerE0qZ2N3M1XyndZF7VMthKDKIg0ZR0TvdjwLqyLYEHAnRBhJLRS0Oy0fC6Of\nVWCO3gIuk1HkTXH+/ZMA/obqrtlisxY85mMdlRz+1PNdZBMf+NxmrXN59uq+JqUu\n8/v1oQ8jH2iU9IWeqyawHDEvPW3aDorfaWGyats5Xd3cT2Ph4xF9tBLT+3PDGU8c\noBmTHWDenYn+TCkCseayo1JCO5igJQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQCr\nR7eZxicHjJoRcbsPBDQpzx9uSux3uvpN93pyJrXXHiil/5SE7CYeDqv5+nV2p6HA\n6KONUAmpId0iHAEi9u+0/LgPWyYQMzT3sfBhkO8RRaMYI87VuKbk5PFmlZbD843+\nQmg3Se2F7BDnTf88xA6QWR4DCejy+ZHfDRFrh3xfFl4tX1UNgqiTGfjPCzblhWx9\nygzlT+flN2j3NkAlhUEV89pnH4EQWILePMTT4wh2XOQj1VFJ+2ATojHFVUTtNWAJ\nxrY/Q9cMYsZ++I8i9bHMZoyc1bSUd5CNFpQdfjVzlgMPT9Jj/fzWIQz+wq0KeRLI\ndLWsa2MZr0GZnTU39YwH\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "packages/ipfs-http-client/test/fixtures/ssl/privkey.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEA6x6mTXV+rC35QW/sPutT1O1cugtnw+UsJx7EGgzyjh7EoXE3\ngb7sO96PtOI5zknb0vecckbiVkesmLnAs2iNa1u9EiRr6WHdc+1MfUCxyHRfP731\nvRZyo0kxbSXerE0qZ2N3M1XyndZF7VMthKDKIg0ZR0TvdjwLqyLYEHAnRBhJLRS0\nOy0fC6OfVWCO3gIuk1HkTXH+/ZMA/obqrtlisxY85mMdlRz+1PNdZBMf+NxmrXN5\n9uq+JqUu8/v1oQ8jH2iU9IWeqyawHDEvPW3aDorfaWGyats5Xd3cT2Ph4xF9tBLT\n+3PDGU8coBmTHWDenYn+TCkCseayo1JCO5igJQIDAQABAoIBAH5fbfFqOpie6T8T\nwj4bTGbA4bsZkD9JeU7ZiXubA/ABd5xyduwky2JugH0vrvRC3IVrE0qU8OiBA7Le\n/EUx5/kRSPFsZBf/wwChRiB4WlYsvllLZ76nRxyepZNN7H5dx3Hkk1gjVREi71jd\nATUtGxfsRG77DV5WbcshIlLLhT9iaohsalmClAFBmwhqnRMvOXHiQyRbvB0fOX08\nuVlObOqo9jLB8N5C/ux+wFEP4wi/AxVqs9ih7Ss7T7+pmOCVWhOnbYcoY2jdaJ11\niLK4F3rv/jQ82OwUpzrWsPedmZUzlOO8xdV3b8hOcPHs/BKvYed7aHSn6b5eVKKT\nzT8vQoECgYEA+K9pvw9K/7+F810MHG+nZ0gtVWmXJp49jB7zQ6QMIex2sUajY2y9\nbEJX8T6rdu3qd+HYU4zl3qt+MUnsVQEBNkLPAn3od0qIWXxu1SL2GF8SDV1xJWK1\nFp0YDe9blaz1JsmSgieNcSoSwqE2V97Wfd/m+EUfyhQt9HX55H5UgAUCgYEA8gkW\n0xZKuHhBMYpcES2P5H5q6HN2fcEQycMuS3agAOhrFPYUT1DVNhbfNVmbOvL2NRWI\nhXixo5SkuEuq2fjmEoeLPTmeKO5LM4IVqovWCYomSftKDpzw4HRn2jvKzi2+mg8J\nqktIMqRqHu/O1NUIsszCIu4c5DzUdhr4N7GXOaECgYAEd1oF1Wd6al0kfsJN7G9s\nOm6d/xR43BSs5I1n5JVXMqD7FBKxIW3ReOuNaJu5uhIg7wxsi7ZBJoFQr0wwRqFX\n8SE4oTxAkDUcrlBrQYJ785Embkwu6LPp4Q5iia7yZDXO6YXZEo7GvoOxvSV1tInT\nnubOBKfKgExG/KttQBuSZQKBgAzYOqPdLP35M8yDQTuQJXDE3LuVVRZ7Zn6uowhS\nNU+XBgfIv28uJQKH2DSmmrxYJITQrbwXmaXKv6sgKOMEeIFHPDZ1llUpwEftgWTZ\novRCpqGKenWoEoh25QQJ5Eto1hKq9aJZ+GznmNIne9yDqcCDaVIdPN9H8yaJa97Y\nx+PBAoGAOiK6xAbPyJSKDSTGZzdv8+yeOdNeJjRHxKJs+4YsDchmdumrqow83DBP\n7ulIJD9pcmsWj+8fntMcsTX5mvzJd5LsKc7Maa5/LtitsLsynu78QFg4Njj8sAKn\n3991i8J98DZ9zqmkxJJhGwstCHG+c+Q7lA6kZ1UdbWJwYwIHjAs=\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "packages/ipfs-http-client/test/fixtures/test-folder/.hiddenTest.txt",
    "content": "Aha! You found me!\n"
  },
  {
    "path": "packages/ipfs-http-client/test/fixtures/test-folder/add",
    "content": "\n\nconst { Buffer } = require('buffer')\nconst ipfs = require('../src')('localhost', 5001)\n\nconst f1 = 'Hello'\nconst f2 = 'World'\n\nipfs.add([new Buffer(f1), new Buffer(f2)], function (err, res) {\n  if (err || !res) return console.log(err)\n\n  for (let i = 0; i < res.length; i++) {\n    console.log(res[i])\n  }\n})\n\nipfs.add(['./files/hello.txt', './files/ipfs.txt'], function (err, res) {\n  if (err || !res) return console.log(err)\n\n  for (let i = 0; i < res.length; i++) {\n    console.log(res[i])\n  }\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/test/fixtures/test-folder/cat",
    "content": "\n\nconst ipfs = require('../src')('localhost', 5001)\n\nconst hash = [\n  'QmdFyxZXsFiP4csgfM5uPu99AvFiKH62CSPDw5TP92nr7w',\n  'QmY9cxiHqTFoWamkQVkpmmqzBrY3hCBEL2XNu3NtX74Fuu'\n]\n\nipfs.cat(hash, function (err, res) {\n  if (err || !res) return console.log(err)\n\n  if (res.readable) {\n    res.pipe(process.stdout)\n  } else {\n    console.log(res)\n  }\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/test/fixtures/test-folder/files/hello.txt",
    "content": "Hello\n"
  },
  {
    "path": "packages/ipfs-http-client/test/fixtures/test-folder/files/ipfs.txt",
    "content": "IPFS\n"
  },
  {
    "path": "packages/ipfs-http-client/test/fixtures/test-folder/ipfs-add",
    "content": "#!/usr/bin/env node\n\n\n\nconst ipfs = require('../src')('localhost', 5001)\nconst files = process.argv.slice(2)\n\nipfs.add(files, {recursive: true}, function (err, res) {\n  if (err || !res) return console.log(err)\n\n  for (let i = 0; i < res.length; i++) {\n    console.log('added', res[i].Hash, res[i].Name)\n  }\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/test/fixtures/test-folder/ls",
    "content": "\n\nconst ipfs = require('../src')('localhost', 5001)\n\nconst hash = ['QmdbHK6gMiecyjjSoPnfJg6iKMF7v6E2NkoBgGpmyCoevh']\n\nipfs.ls(hash, function (err, res) {\n  if (err || !res) return console.log(err)\n\n  res.Objects.forEach(function (node) {\n    console.log(node.Hash)\n\n    console.log('Links [%d]', node.Links.length)\n    node.Links.forEach(function (link, i) {\n      console.log('[%d]', i, link)\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/test/fixtures/test-folder/version",
    "content": "\n\nconst ipfs = require('../src')('localhost', 5001)\n\nipfs.commands(function (err, res) {\n  if (err) throw err\n  console.log(res)\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/test/fixtures/testconfig.json",
    "content": "{\n  \"test\": \"beep boop\",\n  \"Addresses\": {\n    \"API\": \"/ip4/127.0.0.1/tcp/5001\",\n    \"Gateway\": \"/ip4/127.0.0.1/tcp/8080\",\n    \"Swarm\": [\n      \"/ip4/0.0.0.0/tcp/4001\"\n    ]\n  },\n  \"Bootstrap\": [\n    \"/ip4/104.131.131.82/tcp/4001/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ\",\n    \"/ip4/104.236.176.52/tcp/4001/ipfs/QmSoLnSGccFuZQJzRadHn95W2CrSFmZuTdDWP8HXaHca9z\",\n    \"/ip4/104.236.179.241/tcp/4001/ipfs/QmSoLpPVmHKQ4XTPdz8tjDFgdeRFkpV8JgYq8JVJ69RrZm\",\n    \"/ip4/162.243.248.213/tcp/4001/ipfs/QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm\",\n    \"/ip4/128.199.219.111/tcp/4001/ipfs/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu\",\n    \"/ip4/104.236.76.40/tcp/4001/ipfs/QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64\",\n    \"/ip4/178.62.158.247/tcp/4001/ipfs/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd\",\n    \"/ip4/178.62.61.185/tcp/4001/ipfs/QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3\",\n    \"/ip4/104.236.151.122/tcp/4001/ipfs/QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx\"\n  ],\n  \"Datastore\": {\n    \"Path\": \"/home/krl/.ipfs/datastore\",\n    \"Type\": \"leveldb\"\n  },\n  \"Discovery\": {\n    \"MDNS\": {\n      \"Enabled\": false,\n      \"Interval\": 0\n    }\n  },\n  \"Gateway\": {\n    \"RootRedirect\": \"\",\n    \"Writable\": false\n  },\n  \"Identity\": {\n    \"PeerID\": \"QmTF1MqD5hFgRztwxeosess2S1PpSm6fpy2jt15gZjr39c\",\n    \"PrivKey\": \"CAASqxIwggknAgEAAoICAQC8RXr0/XtKFAn5ReHm0AoUQYpMxckyISsdKkDiGTXQW6h3IuU7kypoeSUNPerp+/+dJCXe5dY+GnT9jRXbZBUfVWYuEnMbCCnGHhi28wD71TYgfLuJlZjRKy1eBIz57bOtAVY8CnLM4WPLQLhcwgSZ1G83D6sRTKNOOqZMf37V/I1dxDg2kqi0JujBodlIP0WjDyBJvtPfLUSoytYznIOmJfi/K5ofPm54y5zX81lwuRGAdCXDIwCwcIwFY2EC6gWVECDLTTBnr7ZXITNtTBOWx6tIEmTF8K/hQN3KzazVEkYn7p4r0QL4zJnQCBIr09HhvOuNTK9XdqBeQJsVVm2uM+//acgdlYS8V+F8FQQHCihsaKAEYeDSGuKuzEbLohc8donWy+3k5jn/SwY8jJLaXtBJpux1sGdRG+ko5mFdXViII5e8gbiSdC1JiDKdeYmUSAm27IvuGay4FplK8wczZrComdsS3sxpSvTrS6KulNv7WTZEKPAWtMFGX+58krOpDgOkKg8IvwucmXsrYD5Q+razorP3v4QAVhouhxQNKzVpmOYZemOeJrTf5smUBjTGLIp343N36kWqZVp+bqTjl8x5uRbgn/wthDWHsyV81M6OwCCPtiKLJMKkztlArFnnT5sZ3aG7sFj4AgnxcqHHDo4nUAI+t4tOb9WxcAMR2wIDAQABAoICAG7OxetjNSkIWkZoJujeENCTMn60+hGTC/kCYWWxSMb061YTJ6/EkfUjN/dvNc+5DVzDZbamt02d7LU+UFrrsLLcZGNBYJXMXCnKlOk4ZJ/TgSPlxcrYTTTuoKjxLLf8ev+cBdEYpTCIh1+dG+UcG/Ed4scZZams3YCxbCch8tim590EG8Gi18AQFnXAeE3ZT3cE9A/zTGfSENL3btK5j5I+TwTU+MTizcoyrIE9LKr3gaGEuqT6+PDfjMmvD+3TJq3w7Bw7tf2QoVTuqYHugKkBo4Grsbv+SMXek6tFGi/drYTbICTRw1oDsZOK7Ib3CFRACLMFKz3jB8fxZlVMpQgawiyeBPx0Z4xf6+OG0yUGxGUKBcuATzON29NJWplZxPtfClCozdsm/3VCCPe7ruDUoDEWobhvKh3Ax+8Cdvt9ajoon6qNK3QgJqrm8Xc50MtjaPhZASGw1YweOfxAeQRHVYAJHrkBwblNzOnCMT8F4uKpeI4C9rUjKTOJu53qV1shwIM+zUGtmRvJUTKA15twqnntQrkhO990Dtl3tON/yx/D2ilLYCVuEPqfmW5/xiOrabgS1OlXLZ7teat85JOyqgZ1R/uxFzQljRwVsnCG6DlobkGHbnKwHq4zyV5UXZUDTM1QAgw6ksmkDWOQ9iYJrTk7UTmnNeAb0g4G2tJhAoIBAQDyT8qoPmZkONFktdnUUnjAeuNZ55b2u7pBq+1b2N4ZzhZQjCRCJwK3zxDYpoxWmH9etJcvv1RvZRaWngr9vECMzUEJmklZlCpW2fdxhvprABHaywklFt2heOo8krmfvbZIVMQ6Q9xWRz5sfiV+k3C8q2Y6RIMLuGquYzk8o2pAPG5aD7gHxIeq4JRGCXyJyovgX2AOZmtZe9ttc+dhxKm71V8bI0e88JrusKeapUnVJUm8qgP6PBg+F8MZh1U3kjHczKeB1ND3X9oFQtLP9ypsi4Ngqgvw0O3+CGY/nvJ6c6ynryiVcmURdQY66f+57CtPjjdUVsSoR/bce5S2lrHNAoIBAQDG6C5iHVuX9OplWcA70U7Tc4eiac803QRUL+zdDOC74Qw2Coxr5Nsxjz8/i+6DnOCeWkJBh/uGEaxdYYZHU90LVf/NSA3zguGt6Z0g3MMvMNT9KGgoZl2T5Y/CCgCfrt6gTGRy7PmF+uLtgHcsKQLqCzMarOSVyue35ztxuI6NeRPvlNkoJ54F/+jnY1/6MrMTPXpybHXwf40piyLPuyapqBgyWkRBwPORR9Ucdiz7KhBnZhj7NYpwvut/KgufuU3WF9bkknfFlOJzVp8ZgefxHE8lHPuR0PnWyHwv3v5hgALYwenREcM5MUbRZ7tc18FkWBVOPlvszqSGujq3nMpHAoIBAGV2R9OfHVzF9dgH1Yh0aB+g1WYl9S6neNxa027sJkQD6ZAcvmn8z8SLrfAp/QWdoWfUkqHpqb9jQswarVuF4jmTELKmqiQaSIhJiLU+4cjAJLnK3q9rHa6pZNusTJG47ITpCamkFLUD6/2d7LFNp5043/tyCLV1qSYQYj0j6C+xnjuT7WlDP9OrairRehZwe5WeGiitdjHoDP+N0ss7gB8ov0Qrx7Qzw4xC6Et2/q2DiZa4UiYL19LYPFeKNYKpcruT7mgM5ttOhYpCaueuBVOiL4bgbVOPCLigZ8AoHDxuB1PHomTBm9RtfghZRz1gyNntIPntwzb7u0Cjdqfl/dkCggEAauDxo3jg8ZsBpCoA2GOUtpw6gnPWijJElDQYU4MK8wlvNU6fu44ClfPB6ZR4OjI+o/gd5/Z4mca/VoID1Cnk+aVhSV3xWSq3t2pzKuhU3POhTtK6fRLcL49Hmt0jDqq5J2tFAlgBkBOKglHoN0tmLHqOIERMo4yezDusvmOL/crUgoT51tDK4bBr5oGIXfmGLc14ESnkibEQGgWQVAzdLoaLUesdCDP07NirU5rQerlUjSrYO4u+cuyzv+XIzy+T+nle1/037Gwe7hjabqtWBUHP6UJUjzq6NMYPrO1mxN8zKGOyDsw7mWy3/+d8TtwEJ7YI5L0vSeSTlW1WBblzGwKCAQBeL4WoC0rH7B+MBw/+q3NMozlq4pE1vAC/7L06GhTPQi3Sklz/HHfw1g2aeYYIYpZ3S0Q+QHBs+fpspuPIBE0xTZPNJIAPTOW3WxODCya1rl521Syy8t57m9UPyVG3scSbWrJxH99BQVSPZzVMI3WO2XWGYupUIkVo+MW4WKhBkHd6a8inuJmjExDWm+bwaoQgUxNwE3c1C5OFwkAU1ofhNijE02IJX0EOaYjvNELT0Na3B0T7KZ72Ut4u33byPgEHa9SsPEurctbHy2HdpOGHdpxx4r+7ZQUtg1UZ0ZRQN8B8TyozD05AXQ9V8ybdzU1jSpQYWqbfkXl+LllJBa4E\"\n  },\n  \"Log\": {\n    \"MaxAgeDays\": 0,\n    \"MaxBackups\": 1,\n    \"MaxSizeMB\": 250\n  },\n  \"Mounts\": {\n    \"FuseAllowOther\": false,\n    \"IPFS\": \"/ipfs\",\n    \"IPNS\": \"/ipns\"\n  },\n  \"SupernodeRouting\": {\n    \"Servers\": [\n      \"/ip4/104.236.176.52/tcp/4002/ipfs/QmXdb7tWTxdFEQEFgWBqkuYSrZd3mXrC7HxkD4krGNYx2U\",\n      \"/ip4/104.236.179.241/tcp/4002/ipfs/QmVRqViDByUxjUMoPnjurjKvZhaEMFDtK35FJXHAM4Lkj6\",\n      \"/ip4/104.236.151.122/tcp/4002/ipfs/QmSZwGx8Tn8tmcM4PtDJaMeUQNRhNFdBLVGPzRiNaRJtFH\",\n      \"/ip4/162.243.248.213/tcp/4002/ipfs/QmbHVEEepCi7rn7VL7Exxpd2Ci9NNB6ifvqwhsrbRMgQFP\",\n      \"/ip4/128.199.219.111/tcp/4002/ipfs/Qmb3brdCYmKG1ycwqCbo6LUwWxTuo3FisnJV2yir7oN92R\",\n      \"/ip4/104.236.76.40/tcp/4002/ipfs/QmdRBCV8Cz2dGhoKLkD3YjPwVFECmqADQkx5ZteF2c6Fy4\",\n      \"/ip4/178.62.158.247/tcp/4002/ipfs/QmUdiMPci7YoEUBkyFZAh2pAbjqcPr7LezyiPD2artLw3v\",\n      \"/ip4/178.62.61.185/tcp/4002/ipfs/QmVw6fGNqBixZE4bewRLT2VXX7fAHUHs8JyidDiJ1P7RUN\"\n    ]\n  },\n  \"Tour\": {\n    \"Last\": \"\"\n  },\n  \"Version\": {\n    \"AutoUpdate\": \"minor\",\n    \"Check\": \"error\",\n    \"CheckDate\": \"0001-01-01T00:00:00Z\",\n    \"CheckPeriod\": \"172800000000000\",\n    \"Current\": \"0.3.0\"\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/test/fixtures/testfile.txt",
    "content": "Plz add me!\n"
  },
  {
    "path": "packages/ipfs-http-client/test/key.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { factory } from './utils/factory.js'\nconst f = factory()\n\ndescribe('.key', function () {\n  this.timeout(50 * 1000)\n\n  let ipfs\n\n  before(async () => {\n    ipfs = (await f.spawn()).api\n  })\n\n  after(() => f.clean())\n\n  describe('.gen', () => {\n    it('create a new rsa key', async () => {\n      const res = await ipfs.key.gen('foobarsa', { type: 'rsa', size: 2048 })\n\n      expect(res).to.exist()\n    })\n\n    it('create a new ed25519 key', async () => {\n      const res = await ipfs.key.gen('bazed', { type: 'ed25519' })\n\n      expect(res).to.exist()\n    })\n  })\n\n  describe('.list', () => {\n    it('both keys show up + self', async () => {\n      const res = await ipfs.key.list()\n\n      expect(res).to.exist()\n      expect(res.length).to.equal(3)\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/test/lib.error-handler.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { throwsAsync } from './utils/throws-async.js'\nimport { errorHandler, HTTPError } from '../src/lib/core.js'\n\ndescribe('lib/error-handler', () => {\n  it('should parse json error response', async () => {\n    const res = {\n      ok: false,\n      statusText: 'test',\n      headers: { get: () => 'application/json' },\n      json: () => Promise.resolve({\n        Message: 'boom',\n        Code: 0,\n        Type: 'error'\n      }),\n      status: 500\n    }\n\n    const err = await throwsAsync(errorHandler(res))\n\n    expect(err instanceof HTTPError).to.be.true()\n    expect(err.message).to.eql('boom')\n    expect(err.response.status).to.eql(500)\n  })\n\n  it('should gracefully fail on parse json', async () => {\n    const res = {\n      ok: false,\n      headers: { get: () => 'application/json' },\n      json: () => 'boom', // not valid json!\n      status: 500\n    }\n\n    const err = await throwsAsync(errorHandler(res))\n    expect(err instanceof HTTPError).to.be.true()\n  })\n\n  it('should gracefully fail on read text', async () => {\n    const res = {\n      ok: false,\n      headers: { get: () => 'text/plain' },\n      text: () => Promise.reject(new Error('boom')),\n      status: 500\n    }\n\n    const err = await throwsAsync(errorHandler(res))\n    expect(err instanceof HTTPError).to.be.true()\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/test/log.spec.js",
    "content": "/* eslint-env mocha */\n/* eslint max-nested-callbacks: [\"error\", 8] */\n\nimport { expect } from 'aegir/chai'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport first from 'it-first'\nimport { factory } from './utils/factory.js'\nconst f = factory()\n\ndescribe('.log', function () {\n  this.timeout(100 * 1000)\n\n  let ipfs\n\n  before(async () => {\n    ipfs = (await f.spawn()).api\n  })\n\n  after(() => f.clean())\n\n  it('.log.tail', async () => {\n    const i = setInterval(async () => {\n      try {\n        await ipfs.add(uint8ArrayFromString('just adding some data to generate logs'))\n      } catch (/** @type {any} */ _) {\n        // this can error if the test has finished and we're shutting down the node\n      }\n    }, 1000)\n\n    const message = await first(ipfs.log.tail())\n\n    clearInterval(i)\n    expect(message).to.be.an('object')\n  })\n\n  it('.log.ls', async () => {\n    const res = await ipfs.log.ls()\n\n    expect(res).to.exist()\n    expect(res).to.be.an('array')\n  })\n\n  it('.log.level', async () => {\n    const res = await ipfs.log.level('all', 'error')\n\n    expect(res).to.exist()\n    expect(res).to.be.an('object')\n    expect(res).to.not.have.property('error')\n    expect(res).to.have.property('message')\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/test/node/agent.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport delay from 'delay'\nimport { create as httpClient } from '../../src/index.js'\nimport http, { Agent } from 'http'\n\n/**\n * @typedef {import('http').IncomingMessage} IncomingMessage\n *\n * @param {(message: IncomingMessage) => Promise<any>} handler\n */\nfunction startServer (handler) {\n  return new Promise((resolve) => {\n    // spin up a test http server to inspect the requests made by the library\n    const server = http.createServer((req, res) => {\n      req.on('data', () => {})\n      req.on('end', async () => {\n        const out = await handler(req)\n\n        res.writeHead(200)\n        res.write(JSON.stringify(out))\n        res.end()\n      })\n    })\n\n    server.listen(0, () => {\n      const addressInfo = server.address()\n\n      resolve({\n        port: addressInfo && (typeof addressInfo === 'string' ? addressInfo : addressInfo.port),\n        close: () => server.close()\n      })\n    })\n  })\n}\n\ndescribe.skip('agent', function () {\n  /** @type {import('http').Agent} */\n  let agent\n\n  before(() => {\n    agent = new Agent({\n      maxSockets: 2\n    })\n  })\n\n  it('restricts the number of concurrent connections', async () => {\n    /** @type {((arg: any) => void)[]} */\n    const responses = []\n\n    const server = await startServer(() => {\n      const p = new Promise((resolve) => {\n        responses.push(resolve)\n      })\n\n      return p\n    })\n\n    const ipfs = httpClient({\n      url: `http://localhost:${server.port}`,\n      agent\n    })\n\n    // make three requests\n    const requests = Promise.all([\n      ipfs.id(),\n      ipfs.id(),\n      ipfs.id()\n    ])\n\n    // wait for the first two to arrive\n    for (let i = 0; i < 5; i++) {\n      await delay(100)\n\n      if (responses.length === 2) {\n        // wait a little longer, the third should not arrive\n        await delay(1000)\n\n        expect(responses).to.have.lengthOf(2)\n\n        // respond to the in-flight requests\n        responses[0]({\n          res: 0,\n          id: 'QmfGBRT6BbWJd7yUc2uYdaUZJBbnEFvTqehPFoSMQ6wgdr'\n        })\n        responses[1]({\n          res: 1,\n          id: 'QmfGBRT6BbWJd7yUc2uYdaUZJBbnEFvTqehPFoSMQ6wgdr'\n        })\n\n        break\n      }\n\n      if (i === 4) {\n        // should have first two responses by now\n        expect(responses).to.have.lengthOf(2)\n      }\n    }\n\n    // wait for the final request to arrive\n    for (let i = 0; i < 5; i++) {\n      await delay(100)\n\n      if (responses.length === 3) {\n        // respond to it\n        responses[2]({\n          res: 2,\n          id: 'QmfGBRT6BbWJd7yUc2uYdaUZJBbnEFvTqehPFoSMQ6wgdr'\n        })\n      }\n    }\n\n    let results = await requests\n    results = results.map(r => r.res)\n    expect(results).to.have.lengthOf(3)\n    expect(results).to.include(0)\n    expect(results).to.include(1)\n    expect(results).to.include(2)\n\n    server.close()\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/test/node/custom-headers.js",
    "content": "/* eslint-env mocha */\n\nimport { isNode } from 'ipfs-utils/src/env.js'\nimport { expect } from 'aegir/chai'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { create as httpClient } from '../../src/index.js'\nimport http from 'http'\n\nfunction startServer (fn) {\n  let headersResolve\n  const headers = new Promise((resolve) => {\n    headersResolve = resolve\n  })\n\n  // spin up a test http server to inspect the requests made by the library\n  const server = http.createServer((req, res) => {\n    req.on('data', () => {})\n    req.on('end', () => {\n      res.writeHead(200)\n      res.write(JSON.stringify({}))\n      res.end()\n      server.close(() => {\n        headersResolve(req.headers)\n      })\n    })\n  })\n\n  server.listen(6001, () => {\n    fn().then(() => {}, () => {})\n  })\n\n  return headers\n}\n\ndescribe('custom headers', function () {\n  // do not test in browser\n  if (!isNode) {\n    return\n  }\n\n  let ipfs\n\n  describe('supported in the constructor', () => {\n    // initialize ipfs with custom headers\n    before(() => {\n      ipfs = httpClient({\n        host: 'localhost',\n        port: 6001,\n        protocol: 'http',\n        headers: {\n          authorization: 'Bearer YOLO'\n        }\n      })\n    })\n\n    it('regular API calls', async () => {\n      const headers = await startServer(() => ipfs.id())\n\n      expect(headers.authorization).to.equal('Bearer YOLO')\n    })\n\n    it('multipart API calls', async () => {\n      const headers = await startServer(() => ipfs.files.write('/foo/bar', uint8ArrayFromString('derp'), {\n        create: true\n      }))\n\n      expect(headers.authorization).to.equal('Bearer YOLO')\n    })\n  })\n\n  describe('supported as API call arguemnts', () => {\n    // initialize ipfs with custom headers\n    before(() => {\n      ipfs = httpClient({\n        host: 'localhost',\n        port: 6001,\n        protocol: 'http'\n      })\n    })\n\n    it('regular API calls', async () => {\n      const headers = await startServer(() => ipfs.id({\n        headers: {\n          authorization: 'Bearer OLOY'\n        }\n      }))\n\n      expect(headers.authorization).to.equal('Bearer OLOY')\n    })\n\n    it('multipart API calls', async () => {\n      const headers = await startServer(() => ipfs.files.write('/foo/bar', uint8ArrayFromString('derp'), {\n        create: true,\n        headers: {\n          authorization: 'Bearer OLOY'\n        }\n      }))\n\n      expect(headers.authorization).to.equal('Bearer OLOY')\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/test/node/request-api.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { create as httpClient } from '../../src/index.js'\nimport http from 'http'\n\ndescribe('\\'deal with HTTP weirdness\\' tests', () => {\n  let server\n\n  afterEach(async () => {\n    if (server != null) {\n      await new Promise((resolve, reject) => {\n        server.close((err) => {\n          if (err) {\n            reject(err)\n            return\n          }\n\n          resolve()\n        })\n      })\n    }\n  })\n\n  it('does not crash if no content-type header is provided', async function () {\n    // go-ipfs always (currently) adds a content-type header, even if no content is present,\n    // the standard behaviour for an http-api is to omit this header if no content is present\n    server = http.createServer((req, res) => {\n      // Consume the entire request, before responding.\n      req.on('data', () => {})\n      req.on('end', () => {\n        res.writeHead(200)\n        res.end()\n      })\n    })\n\n    await new Promise(resolve => server.listen(6001, resolve))\n    await httpClient('/ip4/127.0.0.1/tcp/6001').config.replace('test/fixtures/r-config.json')\n  })\n})\n\ndescribe('trailer headers', () => {\n  let server\n\n  afterEach(async () => {\n    if (server != null) {\n      await new Promise((resolve, reject) => {\n        server.close((err) => {\n          if (err) {\n            reject(err)\n            return\n          }\n\n          resolve()\n        })\n      })\n    }\n  })\n\n  // TODO: needs fixing https://github.com/ipfs/js-ipfs-http-client/pull/624#issuecomment-344181950\n  it.skip('should deal with trailer x-stream-error correctly', async () => {\n    server = http.createServer((req, res) => {\n      res.setHeader('x-chunked-output', '1')\n      res.setHeader('content-type', 'application/json')\n      res.setHeader('Trailer', 'X-Stream-Error')\n      res.addTrailers({ 'X-Stream-Error': JSON.stringify({ Message: 'ups, something went wrong', Code: 500 }) })\n      res.write(JSON.stringify({ Bytes: 1 }))\n      res.end()\n    })\n\n    await new Promise(resolve => server.listen(6001, resolve))\n\n    // TODO: errors are not being correctly propagated with Trailer headers yet\n    // const ipfs = httpClient('/ip4/127.0.0.1/tcp/6001')\n    // await expect(ipfs.add(uint8ArrayFromString('Hello there!'))).to.eventually.be.rejected()\n  })\n})\n\ndescribe('error handling', () => {\n  let server\n\n  afterEach(async () => {\n    if (server != null) {\n      await new Promise((resolve, reject) => {\n        server.close((err) => {\n          if (err) {\n            reject(err)\n            return\n          }\n\n          resolve()\n        })\n      })\n    }\n  })\n\n  it('should handle plain text error response', async function () {\n    server = http.createServer((req, res) => {\n      // Consume the entire request, before responding.\n      req.on('data', () => {})\n      req.on('end', () => {\n        // Write a text/plain response with a 403 (forbidden) status\n        res.writeHead(403, { 'Content-Type': 'text/plain' })\n        res.write('ipfs method not allowed')\n        res.end()\n      })\n    })\n\n    await new Promise(resolve => server.listen(6001, resolve))\n\n    await expect(httpClient('/ip4/127.0.0.1/tcp/6001').config.replace('test/fixtures/r-config.json'))\n      .to.eventually.be.rejectedWith('ipfs method not allowed')\n      .and.to.have.nested.property('response.status').that.equals(403)\n  })\n\n  it('should handle JSON error response', async function () {\n    server = http.createServer((req, res) => {\n      // Consume the entire request, before responding.\n      req.on('data', () => {})\n      req.on('end', () => {\n        // Write a application/json response with a 400 (bad request) header\n        res.writeHead(400, { 'Content-Type': 'application/json' })\n        res.write(JSON.stringify({ Message: 'client error', Code: 1 }))\n        res.end()\n      })\n    })\n\n    await new Promise(resolve => server.listen(6001, resolve))\n\n    await expect(httpClient('/ip4/127.0.0.1/tcp/6001').config.replace('test/fixtures/r-config.json'))\n      .to.eventually.be.rejectedWith('client error')\n      .and.to.have.nested.property('response.status').that.equals(400)\n  })\n\n  it('should handle JSON error response with invalid JSON', async function () {\n    server = http.createServer((req, res) => {\n      // Consume the entire request, before responding.\n      req.on('data', () => {})\n      req.on('end', () => {\n        // Write a application/json response with a 400 (bad request) header\n        res.writeHead(400, { 'Content-Type': 'application/json' })\n        res.write('{ Message: ')\n        res.end()\n      })\n    })\n\n    await new Promise(resolve => server.listen(6001, resolve))\n\n    await expect(httpClient('/ip4/127.0.0.1/tcp/6001').config.replace('test/fixtures/r-config.json'))\n      .to.eventually.be.rejected()\n      .and.to.have.property('message').that.includes('Unexpected token M in JSON at position 2')\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/test/node/swarm.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport nock from 'nock'\nimport { create as httpClient } from '../../src/index.js'\n\n// skipped as nock cannot mock undici/fetch requests yet - https://github.com/nock/nock/issues/2183\ndescribe.skip('.swarm.peers', function () {\n  this.timeout(50 * 1000) // slow CI\n\n  const ipfs = httpClient('/ip4/127.0.0.1/tcp/5001')\n  const apiUrl = 'http://127.0.0.1:5001'\n\n  it('handles a peer response', async () => {\n    const response = { Peers: [{ Addr: '/ip4/104.131.131.82/tcp/4001', Peer: 'QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ', Latency: '', Muxer: '', Streams: null }] }\n\n    const scope = nock(apiUrl)\n      .post('/api/v0/swarm/peers')\n      .query(true)\n      .reply(200, response)\n\n    const res = await ipfs.swarm.peers()\n\n    expect(res).to.be.a('array')\n    expect(res.length).to.equal(1)\n    expect(res[0].error).to.not.exist()\n    expect(res[0].addr.toString()).to.equal(response.Peers[0].Addr)\n    expect(res[0].peer.toString()).to.equal(response.Peers[0].Peer)\n    expect(scope.isDone()).to.equal(true)\n  })\n\n  it('handles an ip6 quic peer', async () => {\n    const response = { Peers: [{ Addr: '/ip6/2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095/udp/4001/quic', Peer: 'QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC', Latency: '', Muxer: '', Streams: null }] }\n\n    const scope = nock(apiUrl)\n      .post('/api/v0/swarm/peers')\n      .query(true)\n      .reply(200, response)\n\n    const res = await ipfs.swarm.peers()\n\n    expect(res).to.be.a('array')\n    expect(res.length).to.equal(1)\n    expect(res[0].error).to.not.exist()\n    expect(res[0].addr.toString()).to.equal(response.Peers[0].Addr)\n    expect(res[0].peer.toString()).to.equal(response.Peers[0].Peer)\n    expect(scope.isDone()).to.equal(true)\n  })\n\n  it('handles an error response', async () => {\n    const scope = nock(apiUrl)\n      .post('/api/v0/swarm/peers')\n      .query(true)\n      .replyWithError('something awful happened')\n\n    await expect(ipfs.swarm.peers()).to.eventually.be.rejectedWith('something awful happened')\n\n    expect(scope.isDone()).to.equal(true)\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/test/node.js",
    "content": "\nimport './node/agent.js'\nimport './node/swarm.js'\nimport './node/request-api.js'\nimport './node/custom-headers.js'\n"
  },
  {
    "path": "packages/ipfs-http-client/test/ping.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport all from 'it-all'\n\nimport { factory } from './utils/factory.js'\nconst f = factory()\n\n// Determine if a ping response object is a pong, or something else, like a status message\nfunction isPong (pingResponse) {\n  return Boolean(pingResponse && pingResponse.success && !pingResponse.text)\n}\n\ndescribe('.ping', function () {\n  this.timeout(20 * 1000)\n\n  let ipfs\n  let other\n  let otherId\n\n  before(async function () {\n    this.timeout(30 * 1000) // slow CI\n\n    ipfs = (await f.spawn()).api\n    other = (await f.spawn()).api\n\n    const ma = (await ipfs.id()).addresses[0]\n    await other.swarm.connect(ma)\n\n    otherId = (await other.id()).id\n  })\n\n  after(() => f.clean())\n\n  it('.ping with default count', async () => {\n    const res = await all(ipfs.ping(otherId))\n    expect(res).to.be.an('array')\n    expect(res.filter(isPong)).to.have.lengthOf(10)\n    res.forEach(packet => {\n      expect(packet).to.have.keys('success', 'time', 'text')\n      expect(packet.time).to.be.a('number')\n    })\n    const resultMsg = res.find(packet => packet.text.includes('Average latency'))\n    expect(resultMsg).to.exist()\n  })\n\n  it('.ping with count = 2', async () => {\n    const res = await all(ipfs.ping(otherId, { count: 2 }))\n    expect(res).to.be.an('array')\n    expect(res.filter(isPong)).to.have.lengthOf(2)\n    res.forEach(packet => {\n      expect(packet).to.have.keys('success', 'time', 'text')\n      expect(packet.time).to.be.a('number')\n    })\n    const resultMsg = res.find(packet => packet.text.includes('Average latency'))\n    expect(resultMsg).to.exist()\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/test/pubsub.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport defer from 'p-defer'\nimport { factory } from './utils/factory.js'\n\nconst f = factory()\n\ndescribe('.pubsub', function () {\n  this.timeout(20 * 1000)\n  describe('.subscribe', () => {\n    /** @type {import('ipfs-core-types').IPFS} */\n    let ipfs\n    /** @type {any} */\n    let ctl\n\n    beforeEach(async function () {\n      this.timeout(30 * 1000) // slow CI\n\n      ctl = await await f.spawn({\n        args: ['--enable-pubsub-experiment']\n      })\n\n      ipfs = ctl.api\n    })\n\n    afterEach(() => f.clean())\n\n    it('.onError when connection is closed', async () => {\n      const topic = 'gossipboom'\n      let messageCount = 0\n      const onError = defer()\n\n      await ipfs.pubsub.subscribe(topic, message => {\n        messageCount++\n\n        if (messageCount === 2) {\n          // Stop the daemon\n          ctl.stop().catch()\n        }\n      }, {\n        onError: onError.resolve\n      })\n\n      await ipfs.pubsub.publish(topic, uint8ArrayFromString('hello'))\n      await ipfs.pubsub.publish(topic, uint8ArrayFromString('bye'))\n\n      await expect(onError.promise).to.eventually.be.fulfilled().and.to.be.instanceOf(Error)\n    })\n\n    it('does not call onError when aborted', async () => {\n      const controller = new AbortController()\n      const topic = 'gossipabort'\n      const messages = []\n      const onError = defer()\n      const onReceived = defer()\n\n      await ipfs.pubsub.subscribe(topic, message => {\n        messages.push(message)\n        if (messages.length === 2) {\n          onReceived.resolve()\n        }\n      }, {\n        onError: onError.resolve,\n        signal: controller.signal\n      })\n\n      await ipfs.pubsub.publish(topic, uint8ArrayFromString('hello'))\n      await ipfs.pubsub.publish(topic, uint8ArrayFromString('bye'))\n\n      await onReceived.promise\n      controller.abort()\n\n      // Stop the daemon\n      await ctl.stop()\n      // Just to make sure no error is caused by above line\n      setTimeout(onError.resolve, 200, 'aborted')\n\n      await expect(onError.promise).to.eventually.be.fulfilled().and.to.equal('aborted')\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/test/repo.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { factory } from './utils/factory.js'\nconst f = factory()\n\ndescribe('.repo', function () {\n  this.timeout(50 * 1000) // slow CI\n\n  let ipfs\n\n  before(async () => {\n    ipfs = (await f.spawn()).api\n  })\n\n  after(() => f.clean())\n\n  it('.repo.gc', async () => {\n    const res = await ipfs.repo.gc()\n\n    expect(res).to.exist()\n  })\n\n  it('.repo.stat', async () => {\n    const res = await ipfs.repo.stat()\n\n    expect(res).to.exist()\n    expect(res).to.have.a.property('numObjects')\n    expect(res).to.have.a.property('repoSize')\n  })\n\n  it('.repo.version', async () => {\n    const res = await ipfs.repo.version()\n\n    expect(res).to.exist()\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/test/stats.spec.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport all from 'it-all'\nimport { factory } from './utils/factory.js'\nconst f = factory()\n\ndescribe('stats', function () {\n  this.timeout(50 * 1000) // slow CI\n\n  let ipfs\n\n  before(async () => {\n    ipfs = (await f.spawn()).api\n  })\n\n  after(() => f.clean())\n\n  it('.stats.bitswap', async () => {\n    const res = await ipfs.stats.bitswap()\n\n    expect(res).to.exist()\n    expect(res).to.have.a.property('provideBufLen')\n    expect(res).to.have.a.property('wantlist')\n    expect(res).to.have.a.property('peers')\n    expect(res).to.have.a.property('blocksReceived')\n    expect(res).to.have.a.property('dataReceived')\n    expect(res).to.have.a.property('blocksSent')\n    expect(res).to.have.a.property('dataSent')\n    expect(res).to.have.a.property('dupBlksReceived')\n    expect(res).to.have.a.property('dupDataReceived')\n  })\n\n  it('.stats.bw', async () => {\n    const res = (await all(ipfs.stats.bw()))[0]\n\n    expect(res).to.exist()\n    expect(res).to.have.a.property('totalIn')\n    expect(res).to.have.a.property('totalOut')\n    expect(res).to.have.a.property('rateIn')\n    expect(res).to.have.a.property('rateOut')\n  })\n\n  it('.stats.repo', async () => {\n    const res = await ipfs.stats.repo()\n\n    expect(res).to.exist()\n    expect(res).to.have.a.property('numObjects')\n    expect(res).to.have.a.property('repoSize')\n    expect(res).to.have.a.property('repoPath')\n    expect(res).to.have.a.property('version')\n    expect(res).to.have.a.property('storageMax')\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-client/test/utils/factory.js",
    "content": "import { createFactory } from 'ipfsd-ctl'\nimport mergeOpts from 'merge-options'\nimport { isNode } from 'ipfs-utils/src/env.js'\nimport * as ipfsHttpModule from '../../src/index.js'\n// @ts-expect-error go-ipfs has no types\nimport { path } from 'go-ipfs'\n\nconst merge = mergeOpts.bind({ ignoreUndefined: true })\n\nconst commonOptions = {\n  test: true,\n  type: 'go',\n  ipfsHttpModule,\n  endpoint: process.env.IPFSD_SERVER\n}\n\nconst commonOverrides = {\n  go: {\n    ipfsBin: isNode ? (process.env.IPFS_GO_EXEC || path()) : undefined\n  }\n}\n\nexport const factory = (options = {}, overrides = {}) => createFactory(\n  merge(commonOptions, options),\n  merge(commonOverrides, overrides)\n)\n"
  },
  {
    "path": "packages/ipfs-http-client/test/utils/throws-async.js",
    "content": "\nexport async function throwsAsync (fnOrPromise) {\n  try {\n    await (fnOrPromise.then ? fnOrPromise : fnOrPromise())\n  } catch (/** @type {any} */ err) {\n    return err\n  }\n  throw new Error('did not throw')\n}\n"
  },
  {
    "path": "packages/ipfs-http-client/tsconfig.json",
    "content": "{\n  \"extends\": \"aegir/src/config/tsconfig.aegir.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"emitDeclarationOnly\": true\n  },\n  \"include\": [\n    \"src\",\n    \"test/utils/factory.js\",\n    \"test/pubsub.spec.js\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../ipfs-core-types\"\n    },\n    {\n      \"path\": \"../ipfs-core-utils\"\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/ipfs-http-gateway/CHANGELOG.md",
    "content": "# Change Log\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n\n### [0.13.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-gateway-v0.13.0...ipfs-http-gateway-v0.13.1) (2023-05-25)\n\n\n### Bug Fixes\n\n* add deprecation notice to readmes ([#4362](https://www.github.com/ipfs/js-ipfs/issues/4362)) ([7b79c1b](https://www.github.com/ipfs/js-ipfs/commit/7b79c1b8df5c818dc124b346ea28330455732d5c))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.14.0 to ^0.14.1\n    * ipfs-http-response bumped from ^6.0.0 to ^6.0.1\n\n## [0.13.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-gateway-v0.12.0...ipfs-http-gateway-v0.13.0) (2023-01-11)\n\n\n### ⚠ BREAKING CHANGES\n\n* update multiformats to v11.x.x and related depenendcies (#4277)\n\n### Bug Fixes\n\n* update multiformats to v11.x.x and related depenendcies ([#4277](https://www.github.com/ipfs/js-ipfs/issues/4277)) ([521c84a](https://www.github.com/ipfs/js-ipfs/commit/521c84a958b04d61702577a5adce28519c1b2a3b))\n* use aegir to publish RCs ([#4284](https://www.github.com/ipfs/js-ipfs/issues/4284)) ([6d90cbf](https://www.github.com/ipfs/js-ipfs/commit/6d90cbf321a7dbf4b1084ba20f0c514dc08d8d0a))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.13.0 to ^0.14.0\n    * ipfs-http-response bumped from ^5.0.0 to ^6.0.0\n\n## [0.12.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-gateway-v0.11.1...ipfs-http-gateway-v0.12.0) (2022-10-24)\n\n\n### ⚠ BREAKING CHANGES\n\n* ipfs is now bundled with libp2p@0.40.x which has different config\n\n### Features\n\n* upgrade libp2p to 0.40.x ([#4237](https://www.github.com/ipfs/js-ipfs/issues/4237)) ([0cee4a4](https://www.github.com/ipfs/js-ipfs/commit/0cee4a4c55767022584dcbade0b0b9b43326f9c9))\n\n\n### Bug Fixes\n\n* replace slice with subarray for increased performance ([#4210](https://www.github.com/ipfs/js-ipfs/issues/4210)) ([dfc43d4](https://www.github.com/ipfs/js-ipfs/commit/dfc43d4e9be67fdf25553677f469379d966ff806))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.12.1 to ^0.13.0\n    * ipfs-http-response bumped from ^4.0.1 to ^5.0.0\n\n### [0.11.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-gateway-v0.11.0...ipfs-http-gateway-v0.11.1) (2022-09-21)\n\n\n### Bug Fixes\n\n* update @multiformats/multiadd to 11.0.0 ([2a830bf](https://www.github.com/ipfs/js-ipfs/commit/2a830bf58a5929fcce51dede871c99f62192fbda))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.12.0 to ^0.12.1\n    * ipfs-http-response bumped from ^4.0.0 to ^4.0.1\n\n## [0.11.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-gateway-v0.10.4...ipfs-http-gateway-v0.11.0) (2022-09-06)\n\n\n### ⚠ BREAKING CHANGES\n\n* update to libp2p@0.38.x (#4151)\n\n### deps\n\n* update to libp2p@0.38.x ([#4151](https://www.github.com/ipfs/js-ipfs/issues/4151)) ([39dbf70](https://www.github.com/ipfs/js-ipfs/commit/39dbf708ec31b263115e44f420651fa4e056a89e))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.11.0 to ^0.12.0\n    * ipfs-http-response bumped from ^3.0.0 to ^4.0.0\n\n### [0.10.4](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-gateway-v0.10.3...ipfs-http-gateway-v0.10.4) (2022-06-24)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-http-response bumped from ^3.0.3 to ^3.0.4\n\n### [0.10.3](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-gateway-v0.10.2...ipfs-http-gateway-v0.10.3) (2022-06-22)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.11.0 to ^0.11.1\n    * ipfs-http-response bumped from ^3.0.2 to ^3.0.3\n\n### [0.10.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-gateway-v0.10.1...ipfs-http-gateway-v0.10.2) (2022-06-13)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-http-response bumped from ^3.0.1 to ^3.0.2\n\n### [0.10.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-gateway-v0.10.0...ipfs-http-gateway-v0.10.1) (2022-06-01)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-http-response bumped from ^3.0.0 to ^3.0.1\n\n## [0.10.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-gateway-v0.9.3...ipfs-http-gateway-v0.10.0) (2022-05-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* This module is now ESM only and there return types of some methods have changed\n\n### Features\n\n* update to libp2p 0.37.x ([#4092](https://www.github.com/ipfs/js-ipfs/issues/4092)) ([74aee8b](https://www.github.com/ipfs/js-ipfs/commit/74aee8b3d78f233c3199a3e9a6c0ac628a31a433))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.3 to ^0.11.0\n    * ipfs-http-response bumped from ^2.0.2 to ^3.0.0\n\n### [0.9.3](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-gateway-v0.9.2...ipfs-http-gateway-v0.9.3) (2022-04-20)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.2 to ^0.10.3\n    * ipfs-http-response bumped from ^2.0.2 to ^2.0.3\n\n### [0.9.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-gateway-v0.9.1...ipfs-http-gateway-v0.9.2) (2022-03-01)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.1 to ^0.10.2\n    * ipfs-http-response bumped from ^2.0.1 to ^2.0.2\n\n### [0.9.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-gateway-v0.9.0...ipfs-http-gateway-v0.9.1) (2022-02-06)\n\n\n### Bug Fixes\n\n* **dag:** replace custom dag walk with multiformats/traversal ([#3950](https://www.github.com/ipfs/js-ipfs/issues/3950)) ([596b1f4](https://www.github.com/ipfs/js-ipfs/commit/596b1f48a014083b1736e4ad7e746c652d2583b1))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.0 to ^0.10.1\n    * ipfs-http-response bumped from ^2.0.0 to ^2.0.1\n\n## [0.9.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-gateway-v0.8.0...ipfs-http-gateway-v0.9.0) (2022-01-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* peerstore methods are now all async, the repo is migrated to v12\n\n### Features\n\n* libp2p async peerstore ([#4018](https://www.github.com/ipfs/js-ipfs/issues/4018)) ([a6b201a](https://www.github.com/ipfs/js-ipfs/commit/a6b201af2c3697430ab0ebe002dd573d185f1ac0))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.9.0 to ^0.10.0\n    * ipfs-http-response bumped from ^1.0.6 to ^2.0.0\n\n\n## [0.8.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-gateway@0.7.4...ipfs-http-gateway@0.8.0) (2021-12-15)\n\n\n### Features\n\n* dht client ([#3947](https://github.com/ipfs/js-ipfs/issues/3947)) ([62d8ecb](https://github.com/ipfs/js-ipfs/commit/62d8ecbc723e693a2544e69172d99c576d187c23))\n\n\n### BREAKING CHANGES\n\n* The DHT API has been refactored to return async iterators of query events\n\n\n\n### [0.7.4](https://github.com/ipfs/js-ipfs/compare/ipfs-http-gateway@0.7.3...ipfs-http-gateway@0.7.4) (2021-11-24)\n\n**Note:** Version bump only for package ipfs-http-gateway\n\n\n\n\n\n### [0.7.3](https://github.com/ipfs/js-ipfs/compare/ipfs-http-gateway@0.7.2...ipfs-http-gateway@0.7.3) (2021-11-19)\n\n**Note:** Version bump only for package ipfs-http-gateway\n\n\n\n\n\n### [0.7.2](https://github.com/ipfs/js-ipfs/compare/ipfs-http-gateway@0.7.1...ipfs-http-gateway@0.7.2) (2021-11-12)\n\n**Note:** Version bump only for package ipfs-http-gateway\n\n\n\n\n\n### [0.7.1](https://github.com/ipfs/js-ipfs/compare/ipfs-http-gateway@0.7.0...ipfs-http-gateway@0.7.1) (2021-09-28)\n\n**Note:** Version bump only for package ipfs-http-gateway\n\n\n\n\n\n## [0.7.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-gateway@0.6.5...ipfs-http-gateway@0.7.0) (2021-09-24)\n\n\n### Features\n\n* switch to esm ([#3879](https://github.com/ipfs/js-ipfs/issues/3879)) ([9a40109](https://github.com/ipfs/js-ipfs/commit/9a40109632e5b4837eb77a2f57dbc77fbf1fe099))\n\n\n### BREAKING CHANGES\n\n* There are no default exports and everything is now dual published as ESM/CJS\n\n\n\n\n\n### [0.6.5](https://github.com/ipfs/js-ipfs/compare/ipfs-http-gateway@0.6.4...ipfs-http-gateway@0.6.5) (2021-09-17)\n\n**Note:** Version bump only for package ipfs-http-gateway\n\n\n\n\n\n### [0.6.4](https://github.com/ipfs/js-ipfs/compare/ipfs-http-gateway@0.6.3...ipfs-http-gateway@0.6.4) (2021-09-17)\n\n**Note:** Version bump only for package ipfs-http-gateway\n\n\n\n\n\n### [0.6.3](https://github.com/ipfs/js-ipfs/compare/ipfs-http-gateway@0.6.2...ipfs-http-gateway@0.6.3) (2021-09-08)\n\n**Note:** Version bump only for package ipfs-http-gateway\n\n\n\n\n\n### [0.6.2](https://github.com/ipfs/js-ipfs/compare/ipfs-http-gateway@0.6.1...ipfs-http-gateway@0.6.2) (2021-09-02)\n\n\n### Bug Fixes\n\n* declare types in .ts files ([#3840](https://github.com/ipfs/js-ipfs/issues/3840)) ([eba5fe6](https://github.com/ipfs/js-ipfs/commit/eba5fe6832858107b3e1ae02c99de674622f12b4))\n* remove use of instanceof for CID class ([#3847](https://github.com/ipfs/js-ipfs/issues/3847)) ([ebbb12d](https://github.com/ipfs/js-ipfs/commit/ebbb12db523c53ce8e4ddae5266cd9acb3504431))\n\n\n\n\n\n### [0.6.1](https://github.com/ipfs/js-ipfs/compare/ipfs-http-gateway@0.6.0...ipfs-http-gateway@0.6.1) (2021-08-25)\n\n**Note:** Version bump only for package ipfs-http-gateway\n\n\n\n\n\n## [0.6.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-gateway@0.5.1...ipfs-http-gateway@0.6.0) (2021-08-11)\n\n\n### Features\n\n* make ipfs.get output tarballs ([#3785](https://github.com/ipfs/js-ipfs/issues/3785)) ([1ad6001](https://github.com/ipfs/js-ipfs/commit/1ad60018d39d5b46c484756631e30e1989fd8eba))\n\n\n### BREAKING CHANGES\n\n* the output type of `ipfs.get` has changed and the `recursive` option has been removed from `ipfs.ls` since it was not supported everywhere\n\n\n\n\n\n### [0.5.1](https://github.com/ipfs/js-ipfs/compare/ipfs-http-gateway@0.5.0...ipfs-http-gateway@0.5.1) (2021-07-30)\n\n**Note:** Version bump only for package ipfs-http-gateway\n\n\n\n\n\n## [0.5.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-gateway@0.4.3...ipfs-http-gateway@0.5.0) (2021-07-27)\n\n\n### Features\n\n* upgrade to the new multiformats ([#3556](https://github.com/ipfs/js-ipfs/issues/3556)) ([d13d15f](https://github.com/ipfs/js-ipfs/commit/d13d15f022a87d04a35f0f7822142f9cb898479c))\n\n\n### BREAKING CHANGES\n\n* ipld-formats no longer supported, use multiformat BlockCodecs instead\n\nCo-authored-by: Rod Vagg <rod@vagg.org>\nCo-authored-by: achingbrain <alex@achingbrain.net>\n\n\n\n\n\n### [0.4.3](https://github.com/ipfs/js-ipfs/compare/ipfs-http-gateway@0.4.2...ipfs-http-gateway@0.4.3) (2021-06-18)\n\n**Note:** Version bump only for package ipfs-http-gateway\n\n\n\n\n\n### [0.4.2](https://github.com/ipfs/js-ipfs/compare/ipfs-http-gateway@0.4.1...ipfs-http-gateway@0.4.2) (2021-06-05)\n\n**Note:** Version bump only for package ipfs-http-gateway\n\n\n\n\n\n### [0.4.1](https://github.com/ipfs/js-ipfs/compare/ipfs-http-gateway@0.4.0...ipfs-http-gateway@0.4.1) (2021-05-26)\n\n**Note:** Version bump only for package ipfs-http-gateway\n\n\n\n\n\n## [0.4.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-gateway@0.3.2...ipfs-http-gateway@0.4.0) (2021-05-10)\n\n\n### chore\n\n* upgrade deps with new typedefs ([#3550](https://github.com/ipfs/js-ipfs/issues/3550)) ([a418a52](https://github.com/ipfs/js-ipfs/commit/a418a521574c878d7aabd0ad2fd8d516908a3756))\n\n\n### BREAKING CHANGES\n\n* all core api methods now have types, some method signatures have changed, named exports are now used by the http, grpc and ipfs client modules\n\n\n\n\n\n### [0.3.2](https://github.com/ipfs/js-ipfs/compare/ipfs-http-gateway@0.3.1...ipfs-http-gateway@0.3.2) (2021-03-09)\n\n\n### Bug Fixes\n\n* update to new aegir ([#3528](https://github.com/ipfs/js-ipfs/issues/3528)) ([49f7880](https://github.com/ipfs/js-ipfs/commit/49f78807d7e26483bd926b45cc7e0f797d77e41b))\n\n\n\n\n\n### [0.3.1](https://github.com/ipfs/js-ipfs/compare/ipfs-http-gateway@0.3.0...ipfs-http-gateway@0.3.1) (2021-02-08)\n\n**Note:** Version bump only for package ipfs-http-gateway\n\n\n\n\n\n## [0.3.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-gateway@0.2.1...ipfs-http-gateway@0.3.0) (2021-02-01)\n\n\n### chore\n\n* update deps ([#3514](https://github.com/ipfs/js-ipfs/issues/3514)) ([061d77c](https://github.com/ipfs/js-ipfs/commit/061d77cc03f40af5a3bc3590481e1e5836e7f0d8))\n\n\n### BREAKING CHANGES\n\n* ipfs-repo upgrade requires repo migration to v10\n\n\n\n\n\n### [0.2.1](https://github.com/ipfs/js-ipfs/compare/ipfs-http-gateway@0.2.0...ipfs-http-gateway@0.2.1) (2021-01-22)\n\n**Note:** Version bump only for package ipfs-http-gateway\n\n\n\n\n\n## [0.2.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-gateway@0.1.4...ipfs-http-gateway@0.2.0) (2021-01-15)\n\n\n### Features\n\n* allow passing a http.Agent to the grpc client ([#3477](https://github.com/ipfs/js-ipfs/issues/3477)) ([c5f0bc5](https://github.com/ipfs/js-ipfs/commit/c5f0bc5eeee15369b7d02901035b04184a8608d2)), closes [#3474](https://github.com/ipfs/js-ipfs/issues/3474)\n\n\n\n\n\n### [0.1.4](https://github.com/ipfs/js-ipfs/compare/ipfs-http-gateway@0.1.3...ipfs-http-gateway@0.1.4) (2020-12-16)\n\n\n### Bug Fixes\n\n* regressions introduced by new releases of CID & multicodec ([#3442](https://github.com/ipfs/js-ipfs/issues/3442)) ([b5152d8](https://github.com/ipfs/js-ipfs/commit/b5152d8cc93ecc8d39fc353ea66d7eaf1661e3c0)), closes [/github.com/multiformats/js-cid/commit/0e11f035c9230e7f6d79c159ace9b80de88cb5eb#diff-25a6634263c1b1f6fc4697a04e2b9904ea4b042a89af59dc93ec1f5d44848a26](https://github.com//github.com/multiformats/js-cid/commit/0e11f035c9230e7f6d79c159ace9b80de88cb5eb/issues/diff-25a6634263c1b1f6fc4697a04e2b9904ea4b042a89af59dc93ec1f5d44848a26)\n\n\n\n\n\n### [0.1.3](https://github.com/ipfs/js-ipfs/compare/ipfs-http-gateway@0.1.2...ipfs-http-gateway@0.1.3) (2020-11-25)\n\n**Note:** Version bump only for package ipfs-http-gateway\n\n\n\n\n\n### [0.1.2](https://github.com/ipfs/js-ipfs/compare/ipfs-http-gateway@0.1.1...ipfs-http-gateway@0.1.2) (2020-11-16)\n\n**Note:** Version bump only for package ipfs-http-gateway\n\n\n\n\n\n### [0.1.1](https://github.com/ipfs/js-ipfs/compare/ipfs-http-gateway@0.1.0...ipfs-http-gateway@0.1.1) (2020-11-09)\n\n**Note:** Version bump only for package ipfs-http-gateway\n\n\n\n\n\n# 0.1.0 (2020-10-28)\n\n\n### Features\n\n* type check & generate defs from jsdoc ([#3281](https://github.com/ipfs/js-ipfs/issues/3281)) ([bbcaf34](https://github.com/ipfs/js-ipfs/commit/bbcaf34111251b142273a5675f4754ff68bd9fa0))"
  },
  {
    "path": "packages/ipfs-http-gateway/CODE_OF_CONDUCT.md",
    "content": "# Contributor Code of Conduct\n\nThe `js-ipfs` project follows the [`IPFS Community Code of Conduct`](https://github.com/ipfs/community/blob/master/code-of-conduct.md)\n"
  },
  {
    "path": "packages/ipfs-http-gateway/CONTRIBUTING.md",
    "content": "# Contributing guidelines\n\nIPFS as a project, including js-ipfs and all of its modules, follows the [standard IPFS Community contributing guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md).\n\nWe also adhere to the [IPFS JavaScript Community contributing guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING_JS.md) which provide additional information of how to collaborate and contribute in the JavaScript implementation of IPFS.\n\nWe appreciate your time and attention for going over these. Please open an issue on [ipfs/community](https://github.com/ipfs/community) if you have any question.\n\nThank you.\n"
  },
  {
    "path": "packages/ipfs-http-gateway/COPYRIGHT",
    "content": "This project is transitioning from an MIT-only license to a dual MIT/Apache-2.0 license.\nUnless otherwise noted, all code contributed prior to 2019-11-21 and not contributed by\na user listed in [this signoff issue](https://github.com/ipfs/js-ipfs/issues/2624) is\nlicensed under MIT-only. All new contributions (and past contributions since 2019-11-21)\nare licensed under a dual MIT/Apache-2.0 license.\n"
  },
  {
    "path": "packages/ipfs-http-gateway/LICENSE",
    "content": "This project is dual licensed under MIT and Apache-2.0.\n\nMIT: https://www.opensource.org/licenses/mit\nApache-2.0: https://www.apache.org/licenses/license-2.0\n"
  },
  {
    "path": "packages/ipfs-http-gateway/LICENSE-APACHE",
    "content": "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\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless 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.\n"
  },
  {
    "path": "packages/ipfs-http-gateway/LICENSE-MIT",
    "content": "The MIT License (MIT)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "packages/ipfs-http-gateway/README.md",
    "content": "> # ⛔️ DEPRECATED: [js-IPFS](https://github.com/ipfs/js-ipfs) has been superseded by [Helia](https://github.com/ipfs/helia)\n>\n> 📚 [Learn more about this deprecation](https://github.com/ipfs/js-ipfs/issues/4336) or [how to migrate](https://github.com/ipfs/helia/wiki/Migrating-from-js-IPFS)\n>\n> ⚠️ If you continue using this repo, please note that security fixes will not be provided\n\n# ipfs-http-gateway <!-- omit in toc -->\n\n[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech)\n[![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech)\n[![codecov](https://img.shields.io/codecov/c/github/ipfs/js-ipfs.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipfs)\n[![CI](https://img.shields.io/github/actions/workflow/status/ipfs/js-ipfs/test.yml?branch=master\\&style=flat-square)](https://github.com/ipfs/js-ipfs/actions/workflows/test.yml?query=branch%3Amaster)\n\n> JavaScript implementation of the IPFS specification\n\n## Table of contents <!-- omit in toc -->\n\n- [Install](#install)\n- [License](#license)\n- [Contribute](#contribute)\n\n## Install\n\n```console\n$ npm i ipfs-http-gateway\n```\n\n```console\n$ npm install -g ipfs\n// npm install output\n$ jsipfs daemon\n$ curl http://localhost:9090/ipfs/Qmfoo\n```\n\n## License\n\nLicensed under either of\n\n- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / <http://www.apache.org/licenses/LICENSE-2.0>)\n- MIT ([LICENSE-MIT](LICENSE-MIT) / <http://opensource.org/licenses/MIT>)\n\n## Contribute\n\nContributions welcome! Please check out [the issues](https://github.com/ipfs/js-ipfs/issues).\n\nAlso 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.\n\nPlease 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).\n\nUnless 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.\n\n[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md)\n"
  },
  {
    "path": "packages/ipfs-http-gateway/package.json",
    "content": "{\n  \"name\": \"ipfs-http-gateway\",\n  \"version\": \"0.13.1\",\n  \"description\": \"JavaScript implementation of the IPFS specification\",\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"homepage\": \"https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-http-gateway#readme\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ipfs/js-ipfs.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ipfs/js-ipfs/issues\"\n  },\n  \"keywords\": [\n    \"IPFS\"\n  ],\n  \"engines\": {\n    \"node\": \">=16.0.0\",\n    \"npm\": \">=7.0.0\"\n  },\n  \"type\": \"module\",\n  \"types\": \"./dist/src/index.d.ts\",\n  \"typesVersions\": {\n    \"*\": {\n      \"*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ],\n      \"src/*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ]\n    }\n  },\n  \"files\": [\n    \"src\",\n    \"dist\",\n    \"!dist/test\",\n    \"!**/*.tsbuildinfo\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/src/index.d.ts\",\n      \"import\": \"./src/index.js\"\n    },\n    \"./resources/index\": {\n      \"types\": \"./src/resources/index.d.ts\",\n      \"import\": \"./src/resources/index.js\"\n    }\n  },\n  \"eslintConfig\": {\n    \"extends\": \"ipfs\",\n    \"parserOptions\": {\n      \"sourceType\": \"module\"\n    }\n  },\n  \"scripts\": {\n    \"lint\": \"aegir lint\",\n    \"test\": \"aegir test -t node\",\n    \"test:node\": \"aegir test -t node --cov\",\n    \"clean\": \"aegir clean\",\n    \"dep-check\": \"aegir dep-check -i ipfs-core-types\",\n    \"build\": \"aegir build --no-bundle\"\n  },\n  \"dependencies\": {\n    \"@hapi/ammo\": \"^5.0.1\",\n    \"@hapi/boom\": \"^9.1.0\",\n    \"@hapi/hapi\": \"^20.0.0\",\n    \"@libp2p/logger\": \"^2.0.5\",\n    \"@multiformats/uri-to-multiaddr\": \"^7.0.0\",\n    \"hapi-pino\": \"^8.5.0\",\n    \"ipfs-core-types\": \"^0.14.1\",\n    \"ipfs-http-response\": \"^6.0.1\",\n    \"is-ipfs\": \"^8.0.0\",\n    \"it-last\": \"^2.0.0\",\n    \"it-to-stream\": \"^1.0.0\",\n    \"joi\": \"^17.2.1\",\n    \"multiformats\": \"^11.0.0\",\n    \"uint8arrays\": \"^4.0.2\"\n  },\n  \"devDependencies\": {\n    \"@types/hapi-pino\": \"^8.0.1\",\n    \"@types/hapi__hapi\": \"^20.0.5\",\n    \"aegir\": \"^37.11.0\",\n    \"file-type\": \"^18.0.0\",\n    \"sinon\": \"^15.0.1\"\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-gateway/src/index.js",
    "content": "import Hapi from '@hapi/hapi'\nimport Pino from 'hapi-pino'\nimport { logger, enabled } from '@libp2p/logger'\nimport toMultiaddr from '@multiformats/uri-to-multiaddr'\nimport routes from './routes/index.js'\n\nconst LOG = 'ipfs:http-gateway'\nconst LOG_ERROR = 'ipfs:http-gateway:error'\n\n/**\n * @typedef {import('ipfs-core-types').IPFS} IPFS\n * @typedef {import('./types').Server} Server\n */\n\n/**\n * @param {import('@hapi/hapi').ServerInfo} info\n */\nfunction hapiInfoToMultiaddr (info) {\n  let hostname = info.host\n  let uri = info.uri\n  // ipv6 fix\n  if (hostname.includes(':') && !hostname.startsWith('[')) {\n    // hapi 16 produces invalid URI for ipv6\n    // we fix it here by restoring missing square brackets\n    hostname = `[${hostname}]`\n    uri = uri.replace(`://${info.host}`, `://${hostname}`)\n  }\n  return toMultiaddr(uri)\n}\n\n/**\n * @param {string | string[]} serverAddrs\n * @param {(host: string, port: string, ipfs: IPFS) => Promise<Server>} createServer\n * @param {IPFS} ipfs\n */\nasync function serverCreator (serverAddrs, createServer, ipfs) {\n  serverAddrs = serverAddrs || []\n  // just in case the address is just string\n  serverAddrs = Array.isArray(serverAddrs) ? serverAddrs : [serverAddrs]\n\n  /** @type {Server[]} */\n  const servers = []\n  for (const address of serverAddrs) {\n    const addrParts = address.split('/')\n    const server = await createServer(addrParts[2], addrParts[4], ipfs)\n    await server.start()\n    server.info.ma = hapiInfoToMultiaddr(server.info)\n    servers.push(server)\n  }\n  return servers\n}\n\nexport class HttpGateway {\n  /**\n   * @param {IPFS} ipfs\n   */\n  constructor (ipfs) {\n    this._ipfs = ipfs\n    this._log = logger(LOG)\n    /** @type {Server[]} */\n    this._gatewayServers = []\n  }\n\n  async start () {\n    this._log('starting')\n\n    const ipfs = this._ipfs\n    const config = await ipfs.config.getAll()\n    const addresses = config.Addresses || { Swarm: [], Gateway: [] }\n    const gatewayAddrs = addresses?.Gateway || []\n\n    this._gatewayServers = await serverCreator(gatewayAddrs, this._createGatewayServer, ipfs)\n\n    this._log('started')\n  }\n\n  /**\n   * @param {string} host\n   * @param {string} port\n   * @param {IPFS} ipfs\n   */\n  async _createGatewayServer (host, port, ipfs) {\n    const server = Hapi.server({\n      host,\n      port,\n      routes: {\n        cors: true,\n        response: {\n          emptyStatusCode: 200\n        }\n      }\n    })\n    server.app.ipfs = ipfs\n\n    await server.register({\n      plugin: Pino,\n      options: {\n        prettyPrint: Boolean(enabled(LOG)),\n        logEvents: ['onPostStart', 'onPostStop', 'response', 'request-error'],\n        level: enabled(LOG) ? 'debug' : (enabled(LOG_ERROR) ? 'error' : 'fatal')\n      }\n    })\n\n    server.route(routes.gateway)\n\n    return server\n  }\n\n  async stop () {\n    this._log('stopping')\n    /**\n     * @param {Server[]} servers\n     */\n    const stopServers = servers => Promise.all((servers || []).map(s => s.stop()))\n    await Promise.all([\n      stopServers(this._gatewayServers)\n    ])\n    this._log('stopped')\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-gateway/src/resources/gateway.js",
    "content": "import { logger } from '@libp2p/logger'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport Boom from '@hapi/boom'\nimport Ammo from '@hapi/ammo'\nimport last from 'it-last'\nimport { CID } from 'multiformats/cid'\nimport { base32 } from 'multiformats/bases/base32'\nimport { resolver, utils } from 'ipfs-http-response'\nimport * as isIPFS from 'is-ipfs'\n// @ts-expect-error no types\nimport toStream from 'it-to-stream'\nimport * as PathUtils from '../utils/path.js'\n\nconst { detectContentType } = utils\n\nconst log = logger('ipfs:http-gateway')\n\nexport const Gateway = {\n\n  /**\n   * @param {import('../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const { ipfs } = request.server.app\n    const path = request.path\n\n    let ipfsPath = path\n\n    if (path.startsWith('/ipns/')) {\n      ipfsPath = await last(ipfs.name.resolve(path, { recursive: true })) || path\n    }\n\n    // The resolver from ipfs-http-response supports only immutable /ipfs/ for now,\n    // so we convert /ipns/ to /ipfs/ before passing it to the resolver ¯\\_(ツ)_/¯\n    // This could be removed if a solution proposed in\n    //  https://github.com/ipfs/js-ipfs-http-response/issues/22 lands upstream\n    ipfsPath = decodeURI(ipfsPath)\n\n    let directory = false\n    let data\n    try {\n      data = await resolver.cid(ipfs, ipfsPath)\n    } catch (/** @type {any} */ err) {\n      const errorToString = err.toString()\n      log.error('err: ', errorToString, ' fileName: ', err.fileName)\n\n      // switch case with true feels so wrong.\n      switch (true) {\n        case (errorToString === 'Error: This dag node is a directory'):\n          directory = true\n          data = await resolver.directory(ipfs, ipfsPath, err.cid)\n\n          if (typeof data === 'string') {\n            // no index file found\n            if (!path.endsWith('/')) {\n              // add trailing slash for directory listings\n              return h.redirect(`${path}/`).permanent(true)\n            }\n            // send directory listing\n            return h.response(data)\n          }\n\n          // found index file: return <ipfsPath>/<found-index-file>\n          ipfsPath = PathUtils.joinURLParts(ipfsPath, data[0].Name)\n          data = await resolver.cid(ipfs, ipfsPath)\n          break\n        case (errorToString.startsWith('Error: no link named')):\n          throw Boom.boomify(err, { statusCode: 404 })\n        case (errorToString.startsWith('Error: multihash length inconsistent')):\n        case (errorToString.startsWith('Error: Non-base58 character')):\n        case (errorToString.startsWith('Error: invalid character')):\n          throw Boom.boomify(err, { statusCode: 400 })\n        default:\n          log.error(err)\n          throw err\n      }\n    }\n\n    if (!directory && path.endsWith('/')) {\n      // remove trailing slash for files\n      return h.redirect(PathUtils.removeTrailingSlash(path)).permanent(true)\n    }\n    if (directory && !path.endsWith('/')) {\n      // add trailing slash for directories with implicit index.html\n      return h.redirect(`${path}/`).permanent(true)\n    }\n    if (request.headers['service-worker'] === 'script') {\n      // Disallow Service Worker registration on /ipfs scope\n      // https://github.com/ipfs/go-ipfs/issues/4025\n      if (path.match(/^\\/ip[nf]s\\/[^/]+$/)) throw Boom.badRequest('navigator.serviceWorker: registration is not allowed for this scope')\n    }\n\n    // Support If-None-Match & Etag (Conditional Requests from RFC7232)\n    // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag\n    const etag = `\"${data.cid}\"`\n    const cachedEtag = request.headers['if-none-match']\n    if (cachedEtag === etag || cachedEtag === `W/${etag}`) {\n      return h.response().code(304) // Not Modified\n    }\n\n    // Immutable content produces 304 Not Modified for all values of If-Modified-Since\n    if (path.startsWith('/ipfs/') && request.headers['if-modified-since']) {\n      return h.response().code(304) // Not Modified\n    }\n\n    // This necessary to set correct Content-Length and validate Range requests\n    // Note: we need `size` (raw data), not `cumulativeSize` (data + DAGNodes)\n    const { size } = await ipfs.files.stat(`/ipfs/${data.cid}`)\n\n    // Handle Byte Range requests (https://tools.ietf.org/html/rfc7233#section-2.1)\n    const catOptions = {}\n    let rangeResponse = false\n    if (request.headers.range) {\n      // If-Range is respected (when present), but we compare it only against Etag\n      // (Last-Modified date is too weak for IPFS use cases)\n      if (!request.headers['if-range'] || request.headers['if-range'] === etag) {\n        const ranges = Ammo.header(request.headers.range, size)\n        if (!ranges) {\n          const error = Boom.rangeNotSatisfiable()\n          error.output.headers['content-range'] = `bytes */${size}`\n          throw error\n        }\n\n        if (ranges.length === 1) { // Ignore requests for multiple ranges (hard to map to ipfs.cat and not used in practice)\n          rangeResponse = true\n          const range = ranges[0]\n          catOptions.offset = range.from\n          catOptions.length = (range.to - range.from + 1)\n        }\n      }\n    }\n\n    const { source, contentType } = await detectContentType(ipfsPath, ipfs.cat(data.cid, catOptions))\n    const responseStream = toStream.readable(source)\n\n    const res = h.response(responseStream).code(rangeResponse ? 206 : 200)\n\n    // Etag maps directly to an identifier for a specific version of a resource\n    // and enables smart client-side caching thanks to If-None-Match\n    res.header('etag', etag)\n\n    // Set headers specific to the immutable namespace\n    if (path.startsWith('/ipfs/')) {\n      res.header('Cache-Control', 'public, max-age=29030400, immutable')\n    }\n\n    log('HTTP path ', path)\n    log('IPFS path ', ipfsPath)\n    log('content-type ', contentType)\n\n    if (contentType) {\n      log('writing content-type header')\n      res.header('Content-Type', contentType)\n    }\n\n    if (rangeResponse) {\n      const from = catOptions.offset\n      const to = catOptions.offset + catOptions.length - 1\n      res.header('Content-Range', `bytes ${from}-${to}/${size}`)\n      res.header('Content-Length', `${catOptions.length}`)\n    } else {\n      // Announce support for Range requests\n      res.header('Accept-Ranges', 'bytes')\n      res.header('Content-Length', `${size}`)\n    }\n\n    // Support Content-Disposition via ?filename=foo parameter\n    // (useful for browser vendor to download raw CID into custom filename)\n    // Source: https://github.com/ipfs/go-ipfs/blob/v0.4.20/core/corehttp/gateway_handler.go#L232-L236\n    if (request.query.filename) {\n      res.header('Content-Disposition', `inline; filename*=UTF-8''${encodeURIComponent(request.query.filename)}`)\n    }\n\n    return res\n  },\n\n  /**\n   * @param {import('../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  afterHandler (request, h) {\n    const { response } = request\n\n    if (Boom.isBoom(response)) {\n      return h.continue\n    }\n\n    // Add headers to successful responses (regular or range)\n    if (response.statusCode === 200 || response.statusCode === 206) {\n      const path = request.path\n      response.headers['X-Ipfs-Path'] = path\n      if (path.startsWith('/ipfs/')) {\n        // \"set modtime to a really long time ago, since files are immutable and should stay cached\"\n        // Source: https://github.com/ipfs/go-ipfs/blob/v0.4.20/core/corehttp/gateway_handler.go#L228-L229\n        response.headers['Last-Modified'] = 'Thu, 01 Jan 1970 00:00:01 GMT'\n        // Suborigin for /ipfs/: https://github.com/ipfs/in-web-browsers/issues/66\n        const rootCid = path.split('/')[2]\n        const ipfsOrigin = CID.parse(rootCid).toV1().toString(base32)\n        response.headers.Suborigin = `ipfs000${ipfsOrigin}`\n      } else if (path.startsWith('/ipns/')) {\n        // Suborigin for /ipns/: https://github.com/ipfs/in-web-browsers/issues/66\n        const root = path.split('/')[2]\n        // encode CID/FQDN in base32 (Suborigin allows only a-z)\n        const ipnsOrigin = isIPFS.cid(root)\n          ? CID.parse(root).toV1().toString(base32)\n          : base32.encode(uint8ArrayFromString(root))\n        response.headers.Suborigin = `ipns000${ipnsOrigin}`\n      }\n    }\n    return h.continue\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-gateway/src/resources/index.js",
    "content": "import { Gateway } from './gateway.js'\n\nexport default {\n  gateway: Gateway\n}\n"
  },
  {
    "path": "packages/ipfs-http-gateway/src/routes/gateway.js",
    "content": "import Joi from 'joi'\nimport resources from '../resources/index.js'\n\nexport default [\n  {\n    method: '*',\n    path: '/ipfs/{path*}',\n    options: {\n      handler: resources.gateway.handler,\n      validate: {\n        params: Joi.object({\n          path: Joi.string().required()\n        })\n      },\n      response: {\n        ranges: false // disable built-in support, handler does it manually\n      },\n      ext: {\n        onPostHandler: { method: resources.gateway.afterHandler }\n      }\n    }\n  },\n  {\n    method: '*',\n    path: '/ipns/{path*}',\n    options: {\n      handler: resources.gateway.handler,\n      validate: {\n        params: Joi.object({\n          path: Joi.string().required()\n        })\n      },\n      response: {\n        ranges: false // disable built-in support, handler does it manually\n      },\n      ext: {\n        onPostHandler: { method: resources.gateway.afterHandler }\n      }\n    }\n  }\n]\n"
  },
  {
    "path": "packages/ipfs-http-gateway/src/routes/index.js",
    "content": "import Gateway from './gateway.js'\n\nexport default {\n  gateway: Gateway\n}\n"
  },
  {
    "path": "packages/ipfs-http-gateway/src/types.ts",
    "content": "import type { IPFS } from 'ipfs-core-types'\nimport type { Request, Server } from '@hapi/hapi'\nimport type { Multiaddr } from '@multiformats/multiaddr'\n\ndeclare module '@hapi/hapi' {\n  interface ServerApplicationState {\n    ipfs: IPFS\n  }\n  interface ServerInfo {\n    ma: Multiaddr\n  }\n}\n\nexport type { Request, Server }\n"
  },
  {
    "path": "packages/ipfs-http-gateway/src/utils/path.js",
    "content": "\n/**\n * @param {string} path\n */\nexport function splitPath (path) {\n  if (path[path.length - 1] === '/') {\n    path = path.substring(0, path.length - 1)\n  }\n\n  return path.substring(6).split('/')\n}\n\n/**\n * @param {string} url\n */\nfunction removeLeadingSlash (url) {\n  if (url[0] === '/') {\n    url = url.substring(1)\n  }\n\n  return url\n}\n\n/**\n * @param {string} url\n */\nexport function removeTrailingSlash (url) {\n  if (url.endsWith('/')) {\n    url = url.substring(0, url.length - 1)\n  }\n\n  return url\n}\n\n/**\n * @param {string} url\n */\nfunction removeSlashFromBothEnds (url) {\n  url = removeLeadingSlash(url)\n  url = removeTrailingSlash(url)\n\n  return url\n}\n\n/**\n * @param  {...string} urls\n */\nexport function joinURLParts (...urls) {\n  urls = urls.filter((url) => url.length > 0)\n  urls = [''].concat(urls.map((url) => removeSlashFromBothEnds(url)))\n\n  return urls.join('/')\n}\n"
  },
  {
    "path": "packages/ipfs-http-gateway/test/fixtures/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>IPFS test index.html</title>\n</head>\n<body>\n  index.html\n</body>\n</html>\n"
  },
  {
    "path": "packages/ipfs-http-gateway/test/fixtures/nested-folder/hello.txt",
    "content": "Hello\n"
  },
  {
    "path": "packages/ipfs-http-gateway/test/fixtures/nested-folder/ipfs.txt",
    "content": "IPFS\n"
  },
  {
    "path": "packages/ipfs-http-gateway/test/fixtures/nested-folder/nested.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>IPFS test nested.html</title>\n</head>\n<body>\n  nested.html\n</body>\n</html>\n"
  },
  {
    "path": "packages/ipfs-http-gateway/test/routes.spec.js",
    "content": "/* eslint-env mocha */\n/* eslint dot-notation: 0, dot-notation: 0, quote-props: 0 */\n\nimport { expect } from 'aegir/chai'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { fileTypeFromBuffer } from 'file-type'\nimport { CID } from 'multiformats/cid'\nimport { base32 } from 'multiformats/bases/base32'\nimport { http } from './utils/http.js'\nimport sinon from 'sinon'\nimport fs from 'fs'\n\ndescribe('HTTP Gateway', function () {\n  this.timeout(80 * 1000)\n\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      name: {\n        resolve: sinon.stub()\n      },\n      files: {\n        stat: sinon.stub()\n      },\n      cat: sinon.stub(),\n      dag: {\n        get: sinon.stub()\n      }\n    }\n  })\n\n  it('returns 400 for request without argument', async () => {\n    const res = await http({\n      method: 'GET',\n      url: '/ipfs'\n    })\n\n    expect(res).to.have.property('statusCode', 400)\n    expect(res.headers['cache-control']).to.equal('no-cache')\n    expect(res.headers.etag).to.equal(undefined)\n    expect(res.headers['x-ipfs-path']).to.equal(undefined)\n    expect(res.headers.suborigin).to.equal(undefined)\n  })\n\n  it('returns 400 for request with invalid argument', async () => {\n    const url = '/ipfs/invalid'\n    ipfs.files.stat.withArgs(url).rejects(new Error('invalid character'))\n\n    const res = await http({\n      method: 'GET',\n      url\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 400)\n    expect(res.headers['cache-control']).to.equal('no-cache')\n    expect(res.headers.etag).to.equal(undefined)\n    expect(res.headers['x-ipfs-path']).to.equal(undefined)\n    expect(res.headers.suborigin).to.equal(undefined)\n  })\n\n  it('returns 400 for service worker registration outside of an IPFS content root', async () => {\n    const cid = CID.parse('QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o')\n    ipfs.files.stat.withArgs(`/ipfs/${cid}`).resolves({\n      cid,\n      type: 'file'\n    })\n\n    const res = await http({\n      method: 'GET',\n      url: `/ipfs/${cid}?filename=sw.js'`,\n      headers: { 'Service-Worker': 'script' }\n    }, { ipfs })\n\n    // Expect 400 Bad Request\n    // https://github.com/ipfs/go-ipfs/issues/4025#issuecomment-342250616\n    expect(res).to.have.property('statusCode', 400)\n  })\n\n  it('valid CIDv0', async () => {\n    const cid = CID.parse('QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o')\n    const content = uint8ArrayFromString('hello world\\n')\n    ipfs.files.stat.withArgs(`/ipfs/${cid}`).resolves({\n      cid,\n      type: 'file',\n      size: content.length\n    })\n    ipfs.cat.withArgs(cid).returns([\n      content\n    ])\n    const res = await http({\n      method: 'GET',\n      url: `/ipfs/${cid}`\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n    expect(res.rawPayload).to.eql(uint8ArrayFromString('hello world' + '\\n'))\n    expect(res.payload).to.equal('hello world' + '\\n')\n    expect(res.headers['cache-control']).to.equal('public, max-age=29030400, immutable')\n    expect(res.headers['content-length']).to.equal(res.rawPayload.length.toString()).to.equal('12')\n    expect(res.headers['last-modified']).to.equal('Thu, 01 Jan 1970 00:00:01 GMT')\n    expect(res.headers.etag).to.equal('\"QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o\"')\n    expect(res.headers['x-ipfs-path']).to.equal('/ipfs/QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o')\n    expect(res.headers.suborigin).to.equal('ipfs000bafybeicg2rebjoofv4kbyovkw7af3rpiitvnl6i7ckcywaq6xjcxnc2mby')\n  })\n\n  it('returns CORS headers', async () => {\n    const res = await http({\n      method: 'OPTIONS',\n      url: '/ipfs/QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o',\n      headers: {\n        origin: 'http://example.com',\n        'access-control-request-method': 'GET',\n        'access-control-request-headers': ''\n      }\n    })\n\n    expect(res).to.have.property('statusCode', 200)\n    expect(res.headers['access-control-allow-origin']).to.equal('http://example.com')\n    expect(res.headers['access-control-allow-methods']).to.equal('GET')\n  })\n\n  /* TODO when support for CIDv1 lands\n  it('valid CIDv1', (done) => {\n    gateway.inject({\n      method: 'GET',\n      url: '/ipfs/TO-DO'\n    }, (res) => {\n      expect(res).to.have.property('statusCode', 200)\n      expect(res.rawPayload).to.eql(uint8ArrayFromString('hello world' + '\\n'))\n      expect(res.payload).to.equal('hello world' + '\\n')\n      expect(res.headers.etag).to.equal(TO-DO)\n      expect(res.headers['x-ipfs-path']).to.equal(TO-DO)\n      expect(res.headers.suborigin).to.equal(TO-DO)\n      expect(res.headers['cache-control']).to.equal('public, max-age=29030400, immutable')\n\n      done()\n    })\n  })\n  */\n\n  it('return 304 Not Modified if client announces cached CID in If-None-Match', async () => {\n    const cid = CID.parse('QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o')\n    const content = uint8ArrayFromString('hello world\\n')\n    ipfs.files.stat.withArgs(`/ipfs/${cid}`).resolves({\n      cid,\n      type: 'file',\n      size: content.length\n    })\n    ipfs.cat.withArgs(cid).returns([\n      content\n    ])\n\n    // Get file first to simulate caching it and reading Etag\n    const resFirst = await http({\n      method: 'GET',\n      url: `/ipfs/${cid}`\n    }, { ipfs })\n    expect(resFirst.statusCode).to.equal(200)\n    expect(resFirst.headers['etag']).to.equal(`\"${cid}\"`)\n    expect(resFirst.headers['cache-control']).to.equal('public, max-age=29030400, immutable')\n\n    // second request, this time announcing we have bigFileHash already in cache\n    const resSecond = await http({\n      method: 'GET',\n      url: `/ipfs/${cid}`,\n      headers: {\n        'If-None-Match': resFirst.headers.etag\n      }\n    }, { ipfs })\n\n    // expect HTTP 304 Not Modified without payload\n    expect(resSecond.statusCode).to.equal(304)\n    expect(resSecond.rawPayload).to.be.empty()\n\n    // should only have fetched content once\n    expect(ipfs.cat.callCount).to.equal(1)\n  })\n\n  it('return 304 Not Modified if /ipfs/ was requested with any If-Modified-Since', async () => {\n    const cid = CID.parse('QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o')\n    const content = uint8ArrayFromString('hello world\\n')\n    ipfs.files.stat.withArgs(`/ipfs/${cid}`).resolves({\n      cid,\n      type: 'file',\n      size: content.length\n    })\n    ipfs.cat.withArgs(cid).returns([\n      content\n    ])\n\n    // Get file first to simulate caching it and reading Etag\n    const resFirst = await http({\n      method: 'GET',\n      url: `/ipfs/${cid}`\n    }, { ipfs })\n    expect(resFirst.statusCode).to.equal(200)\n    expect(resFirst.headers['etag']).to.equal(`\"${cid}\"`)\n    expect(resFirst.headers['cache-control']).to.equal('public, max-age=29030400, immutable')\n\n    // second request, this time with If-Modified-Since equal present\n    const resSecond = await http({\n      method: 'GET',\n      url: `/ipfs/${cid}`,\n      headers: {\n        'If-Modified-Since': new Date().toUTCString()\n      }\n    }, { ipfs })\n\n    // expect HTTP 304 Not Modified without payload\n    expect(resSecond.statusCode).to.equal(304)\n    expect(resSecond.rawPayload).to.be.empty()\n\n    // should only have fetched content once\n    expect(ipfs.cat.callCount).to.equal(1)\n  })\n\n  it('return proper Content-Disposition if ?filename=foo is included in URL', async () => {\n    const cid = CID.parse('QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o')\n    const content = uint8ArrayFromString('hello world\\n')\n    ipfs.files.stat.withArgs(`/ipfs/${cid}`).resolves({\n      cid,\n      type: 'file',\n      size: content.length\n    })\n    ipfs.cat.withArgs(cid).returns([\n      content\n    ])\n\n    // Get file first to simulate caching it and reading Etag\n    const resFirst = await http({\n      method: 'GET',\n      url: `/ipfs/${cid}?filename=pretty-name-in-utf8-%C3%B3%C3%B0%C5%9B%C3%B3%C3%B0%C5%82%C4%85%C5%9B%C5%81.txt`\n    }, { ipfs })\n    expect(resFirst.statusCode).to.equal(200)\n    expect(resFirst.headers['etag']).to.equal(`\"${cid}\"`)\n    expect(resFirst.headers['content-disposition']).to.equal('inline; filename*=UTF-8\\'\\'pretty-name-in-utf8-%C3%B3%C3%B0%C5%9B%C3%B3%C3%B0%C5%82%C4%85%C5%9B%C5%81.txt')\n  })\n\n  it('load a big file (15MB)', async () => {\n    const cid = CID.parse('QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o')\n    const content = uint8ArrayFromString(new Array(15000000).fill('0').join(''))\n    ipfs.files.stat.withArgs(`/ipfs/${cid}`).resolves({\n      cid,\n      type: 'file',\n      size: content.length\n    })\n    ipfs.cat.withArgs(cid).returns([\n      content\n    ])\n\n    const res = await http({\n      method: 'GET',\n      url: `/ipfs/${cid}`\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n    expect(res.rawPayload).to.eql(content)\n    expect(res.headers['content-length']).to.equal(res.rawPayload.length.toString()).to.equal('15000000')\n    expect(res.headers['x-ipfs-path']).to.equal(`/ipfs/${cid}`)\n    expect(res.headers['etag']).to.equal(`\"${cid}\"`)\n    expect(res.headers['last-modified']).to.equal('Thu, 01 Jan 1970 00:00:01 GMT')\n    expect(res.headers['cache-control']).to.equal('public, max-age=29030400, immutable')\n    expect(res.headers['content-type']).to.equal('application/octet-stream')\n  })\n\n  it('load specific byte range of a file (from-)', async () => {\n    // use 12 byte text file to make it easier to debug ;-)\n    const fileLength = 12\n    const range = { from: 1, length: 11 }\n    const cid = CID.parse('QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o')\n    const content = uint8ArrayFromString(new Array(fileLength).fill('0').join(''))\n    ipfs.files.stat.withArgs(`/ipfs/${cid}`).resolves({\n      cid,\n      type: 'file',\n      size: content.length\n    })\n    ipfs.cat.withArgs(cid).returns([\n      content\n    ])\n    ipfs.cat.withArgs(cid, {\n      offset: range.from,\n      length: range.length\n    }).returns([\n      content.slice(1, 12)\n    ])\n\n    // get full file first to read accept-ranges and etag headers\n    const resFull = await http({\n      method: 'GET',\n      url: `/ipfs/${cid}`\n    }, { ipfs })\n    expect(resFull.statusCode).to.equal(200)\n    expect(resFull.headers['accept-ranges']).to.equal('bytes')\n    expect(resFull.headers['etag']).to.equal(`\"${cid}\"`)\n    expect(resFull.headers['cache-control']).to.equal('public, max-age=29030400, immutable')\n    expect(resFull.headers['content-length']).to.equal(resFull.rawPayload.length.toString()).to.equal(fileLength.toString())\n\n    // extract expected chunk of interest\n    const rangeValue = `bytes=${range.from}-`\n    const expectedChunk = resFull.rawPayload.slice(range.from)\n\n    // const expectedChunkBytes = bigFile.slice(range.from, range.to)\n    const resRange = await http({\n      method: 'GET',\n      url: `/ipfs/${cid}`,\n      headers: {\n        'range': rangeValue,\n        'if-range': resFull.headers.etag // if-range is meaningless for immutable /ipfs/, but will matter for /ipns/\n      }\n    }, { ipfs })\n\n    // range headers\n    expect(resRange.statusCode).to.equal(206)\n    expect(resRange.headers['content-range']).to.equal(`bytes ${range.from}-${range.length}/${fileLength}`)\n    expect(resRange.headers['content-length']).to.equal(resRange.rawPayload.length.toString()).to.equal(range.length.toString())\n    expect(resRange.headers['accept-ranges']).to.equal(undefined)\n    expect(resRange.rawPayload).to.deep.equal(expectedChunk)\n    // regular headers that should also be present\n    expect(resRange.headers['x-ipfs-path']).to.equal(`/ipfs/${cid}`)\n    expect(resRange.headers['etag']).to.equal(`\"${cid}\"`)\n    expect(resRange.headers['cache-control']).to.equal('public, max-age=29030400, immutable')\n    expect(resRange.headers['last-modified']).to.equal('Thu, 01 Jan 1970 00:00:01 GMT')\n    expect(resRange.headers['content-type']).to.equal('application/octet-stream')\n  })\n\n  it('load specific byte range of a file (from-to)', async () => {\n    // use 12 byte text file to make it easier to debug ;-)\n    const fileLength = 12\n    const range = { from: 1, to: 3, length: 3 }\n    const cid = CID.parse('QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o')\n    const content = uint8ArrayFromString(new Array(fileLength).fill('0').join(''))\n    ipfs.files.stat.withArgs(`/ipfs/${cid}`).resolves({\n      cid,\n      type: 'file',\n      size: content.length\n    })\n    ipfs.cat.withArgs(cid).returns([\n      content\n    ])\n    ipfs.cat.withArgs(cid, {\n      offset: range.from,\n      length: range.length\n    }).returns([\n      content.slice(1, 4)\n    ])\n\n    // get full file first to read accept-ranges and etag headers\n    const resFull = await http({\n      method: 'GET',\n      url: `/ipfs/${cid}`\n    }, { ipfs })\n    expect(resFull.statusCode).to.equal(200)\n    expect(resFull.headers['accept-ranges']).to.equal('bytes')\n    expect(resFull.headers['etag']).to.equal(`\"${cid}\"`)\n    expect(resFull.headers['cache-control']).to.equal('public, max-age=29030400, immutable')\n    expect(resFull.headers['content-length']).to.equal(resFull.rawPayload.length.toString()).to.equal(fileLength.toString())\n\n    // extract expected chunk of interest\n    const rangeValue = `bytes=${range.from}-${range.to}`\n    const expectedChunk = resFull.rawPayload.slice(range.from, range.to + 1) // include end\n\n    // const expectedChunkBytes = bigFile.slice(range.from, range.to)\n    const resRange = await http({\n      method: 'GET',\n      url: `/ipfs/${cid}`,\n      headers: {\n        'range': rangeValue,\n        'if-range': resFull.headers.etag // if-range is meaningless for immutable /ipfs/, but will matter for /ipns/\n      }\n    }, { ipfs })\n\n    // range headers\n    expect(resRange.statusCode).to.equal(206)\n    expect(resRange.headers['content-range']).to.equal(`bytes ${range.from}-${range.to}/${fileLength}`)\n    expect(resRange.headers['content-length']).to.equal(resRange.rawPayload.length.toString()).to.equal(range.length.toString())\n    expect(resRange.headers['accept-ranges']).to.equal(undefined)\n    expect(resRange.rawPayload).to.deep.equal(expectedChunk)\n    // regular headers that should also be present\n    expect(resRange.headers['x-ipfs-path']).to.equal(`/ipfs/${cid}`)\n    expect(resRange.headers['etag']).to.equal(`\"${cid}\"`)\n    expect(resRange.headers['cache-control']).to.equal('public, max-age=29030400, immutable')\n    expect(resRange.headers['last-modified']).to.equal('Thu, 01 Jan 1970 00:00:01 GMT')\n    expect(resRange.headers['content-type']).to.equal('application/octet-stream')\n  })\n\n  // This one is tricky, as \"-to\" does not mean implicit \"0-to\",\n  // but \"give me last N bytes\"\n  // More at https://tools.ietf.org/html/rfc7233#section-2.1\n  it('load specific byte range of a file (-tail AKA bytes from end)', async () => {\n    // use 12 byte text file to make it easier to debug ;-)\n    const fileLength = 12\n    const range = { tail: 7, from: 5, to: 11, length: 7 }\n    const cid = CID.parse('QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o')\n    const content = uint8ArrayFromString(new Array(fileLength).fill('0').join(''))\n    ipfs.files.stat.withArgs(`/ipfs/${cid}`).resolves({\n      cid,\n      type: 'file',\n      size: content.length\n    })\n    ipfs.cat.withArgs(cid).returns([\n      content\n    ])\n    ipfs.cat.withArgs(cid, {\n      offset: range.from,\n      length: range.length\n    }).returns([\n      content.slice(1, 8)\n    ])\n\n    // get full file first to read accept-ranges and etag headers\n    const resFull = await http({\n      method: 'GET',\n      url: `/ipfs/${cid}`\n    }, { ipfs })\n    expect(resFull.statusCode).to.equal(200)\n    expect(resFull.headers['accept-ranges']).to.equal('bytes')\n    expect(resFull.headers['etag']).to.equal(`\"${cid}\"`)\n    expect(resFull.headers['cache-control']).to.equal('public, max-age=29030400, immutable')\n    expect(resFull.headers['content-length']).to.equal(resFull.rawPayload.length.toString()).to.equal(fileLength.toString())\n\n    // extract expected chunk of interest\n    const rangeValue = `bytes=-${range.tail}`\n    const expectedChunk = resFull.rawPayload.slice(range.from, range.to + 1) // include end\n\n    // const expectedChunkBytes = resFull.rawPayload.slice(range.from, range.to)\n    const resRange = await http({\n      method: 'GET',\n      url: `/ipfs/${cid}`,\n      headers: {\n        'range': rangeValue,\n        'if-range': resFull.headers.etag // if-range is meaningless for immutable /ipfs/, but will matter for /ipns/\n      }\n    }, { ipfs })\n\n    // range headers\n    expect(resRange.statusCode).to.equal(206)\n    expect(resRange.headers['content-range']).to.equal(`bytes ${range.from}-${range.to}/${fileLength}`)\n    expect(resRange.headers['content-length']).to.equal(resRange.rawPayload.length.toString()).to.equal(range.length.toString())\n    expect(resRange.headers['accept-ranges']).to.equal(undefined)\n    expect(resRange.rawPayload).to.deep.equal(expectedChunk)\n    // regular headers that should also be present\n    expect(resRange.headers['x-ipfs-path']).to.equal(`/ipfs/${cid}`)\n    expect(resRange.headers['etag']).to.equal(`\"${cid}\"`)\n    expect(resRange.headers['cache-control']).to.equal('public, max-age=29030400, immutable')\n    expect(resRange.headers['last-modified']).to.equal('Thu, 01 Jan 1970 00:00:01 GMT')\n    expect(resRange.headers['content-type']).to.equal('application/octet-stream')\n  })\n\n  it('return 416 (Range Not Satisfiable) on invalid range request', async () => {\n    // requesting range outside of file length\n    const rangeValue = 'bytes=42-100'\n\n    // use 12 byte text file to make it easier to debug ;-)\n    const fileLength = 12\n    const cid = CID.parse('QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o')\n    const content = uint8ArrayFromString(new Array(fileLength).fill('0').join(''))\n    ipfs.files.stat.withArgs(`/ipfs/${cid}`).resolves({\n      cid,\n      type: 'file',\n      size: content.length\n    })\n\n    // const expectedChunkBytes = bigFile.slice(range.from, range.to)\n    const resRange = await http({\n      method: 'GET',\n      url: `/ipfs/${cid}`,\n      headers: { 'range': rangeValue }\n    }, { ipfs })\n\n    // Expect 416 Range Not Satisfiable\n    // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/416\n    expect(resRange.statusCode).to.equal(416)\n    expect(resRange.headers['content-range']).to.equal('bytes */12')\n    expect(resRange.headers['cache-control']).to.equal('no-cache')\n  })\n\n  it('load a jpg file', async () => {\n    const fileCid = CID.parse('Qmd286K6pohQcTKYqnS1YhWrCiS4gz7Xi34sdwMe9USZ7u')\n    const dirCid = CID.parse('QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o')\n    const content = fs.readFileSync('test/fixtures/cat-folder/cat.jpg')\n    ipfs.files.stat.withArgs(`/ipfs/${dirCid}/cat.jpg`).resolves({\n      cid: fileCid,\n      type: 'file',\n      size: content.length\n    })\n    ipfs.files.stat.withArgs(`/ipfs/${fileCid}`).resolves({\n      cid: fileCid,\n      type: 'file',\n      size: content.length\n    })\n    ipfs.cat.withArgs(fileCid).returns([\n      content\n    ])\n\n    const res = await http({\n      method: 'GET',\n      url: `/ipfs/${dirCid}/cat.jpg`\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n    expect(res.headers['content-type']).to.equal('image/jpeg')\n    expect(res.headers['content-length']).to.equal(res.rawPayload.length.toString()).to.equal(content.length.toString())\n    expect(res.headers['x-ipfs-path']).to.equal(`/ipfs/${dirCid}/cat.jpg`)\n    expect(res.headers['etag']).to.equal(`\"${fileCid}\"`)\n    expect(res.headers['cache-control']).to.equal('public, max-age=29030400, immutable')\n    expect(res.headers['last-modified']).to.equal('Thu, 01 Jan 1970 00:00:01 GMT')\n    expect(res.headers.etag).to.equal(`\"${fileCid}\"`)\n    expect(res.headers.suborigin).to.equal(`ipfs000${dirCid.toV1().toString(base32)}`)\n\n    const fileSignature = await fileTypeFromBuffer(res.rawPayload)\n    expect(fileSignature.mime).to.equal('image/jpeg')\n    expect(fileSignature.ext).to.equal('jpg')\n  })\n\n  it('load a svg file (unsniffable)', async () => {\n    const fileCid = CID.parse('Qmd286K6pohQcTKYqnS1YhWrCiS4gz7Xi34sdwMe9USZ7u')\n    const dirCid = CID.parse('QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o')\n    const content = fs.readFileSync('test/fixtures/unsniffable-folder/hexagons.svg')\n    ipfs.files.stat.withArgs(`/ipfs/${dirCid}/hexagons.svg`).resolves({\n      cid: fileCid,\n      type: 'file',\n      size: content.length\n    })\n    ipfs.files.stat.withArgs(`/ipfs/${fileCid}`).resolves({\n      cid: fileCid,\n      type: 'file',\n      size: content.length\n    })\n    ipfs.cat.withArgs(fileCid).returns([\n      content\n    ])\n\n    const res = await http({\n      method: 'GET',\n      url: `/ipfs/${dirCid}/hexagons.svg`\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n    expect(res.headers['content-type']).to.equal('image/svg+xml')\n  })\n\n  it('load a svg file with xml leading declaration (unsniffable)', async () => {\n    const fileCid = CID.parse('Qmd286K6pohQcTKYqnS1YhWrCiS4gz7Xi34sdwMe9USZ7u')\n    const dirCid = CID.parse('QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o')\n    const content = fs.readFileSync('test/fixtures/unsniffable-folder/hexagons-xml.svg')\n    ipfs.files.stat.withArgs(`/ipfs/${dirCid}/hexagons-xml.svg`).resolves({\n      cid: fileCid,\n      type: 'file',\n      size: content.length\n    })\n    ipfs.files.stat.withArgs(`/ipfs/${fileCid}`).resolves({\n      cid: fileCid,\n      type: 'file',\n      size: content.length\n    })\n    ipfs.cat.withArgs(fileCid).returns([\n      content\n    ])\n\n    const res = await http({\n      method: 'GET',\n      url: `/ipfs/${dirCid}/hexagons-xml.svg`\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n    expect(res.headers['content-type']).to.equal('image/svg+xml')\n  })\n\n  it('load a directory', async () => {\n    const dirCid = CID.parse('QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o')\n    ipfs.files.stat.withArgs(`/ipfs/${dirCid}/`).resolves({\n      cid: dirCid,\n      type: 'directory'\n    })\n    ipfs.files.stat.withArgs(`/ipfs/${dirCid}//index.html`).rejects(new Error('does not exist'))\n    ipfs.files.stat.withArgs(`/ipfs/${dirCid}//index.htm`).rejects(new Error('does not exist'))\n    ipfs.files.stat.withArgs(`/ipfs/${dirCid}//index.shtml`).rejects(new Error('does not exist'))\n    ipfs.dag.get.withArgs(dirCid).returns({\n      value: {\n        Links: [{\n          Name: 'cat.jpg',\n          Tsize: 12\n        }]\n      }\n    })\n\n    const res = await http({\n      method: 'GET',\n      url: `/ipfs/${dirCid}/`\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n    expect(res.headers['content-type']).to.equal('text/html; charset=utf-8')\n    expect(res.headers['x-ipfs-path']).to.equal(`/ipfs/${dirCid}/`)\n    expect(res.headers['cache-control']).to.equal('no-cache')\n    expect(res.headers['last-modified']).to.equal('Thu, 01 Jan 1970 00:00:01 GMT')\n    expect(res.headers['content-length']).to.equal(res.rawPayload.length)\n    expect(res.headers.etag).to.equal(undefined)\n    expect(res.headers.suborigin).to.equal(`ipfs000${dirCid.toV1().toString(base32)}`)\n\n    // check if the cat picture is in the payload as a way to check\n    // if this is an index of this directory\n    const listedFile = res.payload.match(/\\/ipfs\\/QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o\\/cat\\.jpg/g)\n    expect(listedFile).to.have.lengthOf(1)\n  })\n\n  it('load a webpage index.html', async () => {\n    const dirCid = CID.parse('QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o')\n    const fileCid = CID.parse('Qmd286K6pohQcTKYqnS1YhWrCiS4gz7Xi34sdwMe9USZ7u')\n    const content = fs.readFileSync('test/fixtures/index.html')\n    ipfs.files.stat.withArgs(`/ipfs/${dirCid}/index.html`).resolves({\n      cid: fileCid,\n      type: 'file',\n      size: content.length\n    })\n    ipfs.files.stat.withArgs(`/ipfs/${fileCid}`).resolves({\n      cid: fileCid,\n      type: 'file',\n      size: content.length\n    })\n    ipfs.cat.withArgs(fileCid).returns([\n      content\n    ])\n\n    const res = await http({\n      method: 'GET',\n      url: `/ipfs/${dirCid}/index.html`\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n    expect(res.headers['content-type']).to.equal('text/html; charset=utf-8')\n    expect(res.headers['x-ipfs-path']).to.equal(`/ipfs/${dirCid}/index.html`)\n    expect(res.headers['cache-control']).to.equal('public, max-age=29030400, immutable')\n    expect(res.headers['last-modified']).to.equal('Thu, 01 Jan 1970 00:00:01 GMT')\n    expect(res.headers['content-length']).to.equal(res.rawPayload.length.toString())\n    expect(res.headers.etag).to.equal(`\"${fileCid}\"`)\n    expect(res.headers.suborigin).to.equal(`ipfs000${dirCid.toV1().toString(base32)}`)\n    expect(res.rawPayload).to.deep.equal(content)\n  })\n\n  it('load a webpage {hash}/nested-folder/nested.html', async () => {\n    const dirCid = CID.parse('QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o')\n    const fileCid = CID.parse('Qmd286K6pohQcTKYqnS1YhWrCiS4gz7Xi34sdwMe9USZ7u')\n    const content = fs.readFileSync('test/fixtures/index.html')\n    ipfs.files.stat.withArgs(`/ipfs/${dirCid}/nested-folder/nested.html`).resolves({\n      cid: fileCid,\n      type: 'file',\n      size: content.length\n    })\n    ipfs.files.stat.withArgs(`/ipfs/${fileCid}`).resolves({\n      cid: fileCid,\n      type: 'file',\n      size: content.length\n    })\n    ipfs.cat.withArgs(fileCid).returns([\n      content\n    ])\n\n    const res = await http({\n      method: 'GET',\n      url: `/ipfs/${dirCid}/nested-folder/nested.html`\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n    expect(res.headers['content-type']).to.equal('text/html; charset=utf-8')\n    expect(res.headers['x-ipfs-path']).to.equal(`/ipfs/${dirCid}/nested-folder/nested.html`)\n    expect(res.headers['cache-control']).to.equal('public, max-age=29030400, immutable')\n    expect(res.headers['last-modified']).to.equal('Thu, 01 Jan 1970 00:00:01 GMT')\n    expect(res.headers['content-length']).to.equal(res.rawPayload.length.toString())\n    expect(res.headers.etag).to.equal(`\"${fileCid}\"`)\n    expect(res.headers.suborigin).to.equal(`ipfs000${dirCid.toV1().toString(base32)}`)\n    expect(res.rawPayload).to.deep.equal(content)\n  })\n\n  it('redirects to generated index', async () => {\n    const dirCid = CID.parse('QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o')\n    ipfs.files.stat.withArgs(`/ipfs/${dirCid}`).resolves({\n      cid: dirCid,\n      type: 'directory'\n    })\n    ipfs.files.stat.withArgs(`/ipfs/${dirCid}/index.html`).throws(new Error('does not exist'))\n    ipfs.files.stat.withArgs(`/ipfs/${dirCid}/index.htm`).throws(new Error('does not exist'))\n    ipfs.files.stat.withArgs(`/ipfs/${dirCid}/index.shtml`).throws(new Error('does not exist'))\n    ipfs.dag.get.withArgs(dirCid).returns({\n      value: {\n        Links: []\n      }\n    })\n\n    const res = await http({\n      method: 'GET',\n      url: `/ipfs/${dirCid}`\n    }, { ipfs })\n\n    expect(res.statusCode).to.equal(301)\n    expect(res.headers.location).to.equal(`/ipfs/${dirCid}/`)\n    expect(res.headers['x-ipfs-path']).to.equal(undefined)\n  })\n\n  it('redirect to a directory with index.html', async () => {\n    const dirCid = CID.parse('QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o')\n    const fileCid = CID.parse('Qmd286K6pohQcTKYqnS1YhWrCiS4gz7Xi34sdwMe9USZ7u')\n    ipfs.files.stat.withArgs(`/ipfs/${dirCid}`).resolves({\n      cid: dirCid,\n      type: 'directory'\n    })\n    ipfs.files.stat.withArgs(`/ipfs/${dirCid}/index.html`).resolves({\n      cid: fileCid,\n      type: 'file'\n    })\n\n    const res = await http({\n      method: 'GET',\n      url: `/ipfs/${dirCid}` // note lack of '/' at the end\n    }, { ipfs })\n\n    // we expect redirect to the same path but with '/' at the end\n    expect(res.statusCode).to.equal(301)\n    expect(res.headers.location).to.equal(`/ipfs/${dirCid}/`)\n    expect(res.headers['x-ipfs-path']).to.equal(undefined)\n  })\n\n  it('load a directory with index.html', async () => {\n    const dirCid = CID.parse('QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o')\n    const fileCid = CID.parse('Qmd286K6pohQcTKYqnS1YhWrCiS4gz7Xi34sdwMe9USZ7u')\n    const content = fs.readFileSync('test/fixtures/index.html')\n    ipfs.files.stat.withArgs(`/ipfs/${dirCid}/`).resolves({\n      cid: dirCid,\n      type: 'directory'\n    })\n    ipfs.files.stat.withArgs(`/ipfs/${dirCid}//index.html`).resolves({\n      cid: fileCid,\n      type: 'file',\n      size: content.length\n    })\n    ipfs.files.stat.withArgs(`/ipfs/${dirCid}/index.html`).resolves({\n      cid: fileCid,\n      type: 'file',\n      size: content.length\n    })\n    ipfs.files.stat.withArgs(`/ipfs/${fileCid}`).resolves({\n      cid: fileCid,\n      type: 'file',\n      size: content.length\n    })\n    ipfs.cat.withArgs(fileCid).returns([\n      content\n    ])\n\n    const res = await http({\n      method: 'GET',\n      url: `/ipfs/${dirCid}/` // note '/' at the end\n    }, { ipfs })\n\n    // confirm payload is index.html\n    expect(res).to.have.property('statusCode', 200)\n    expect(res.headers['content-type']).to.equal('text/html; charset=utf-8')\n    expect(res.headers['x-ipfs-path']).to.equal(`/ipfs/${dirCid}/`)\n    expect(res.headers['cache-control']).to.equal('public, max-age=29030400, immutable')\n    expect(res.headers['last-modified']).to.equal('Thu, 01 Jan 1970 00:00:01 GMT')\n    expect(res.headers['content-length']).to.equal(res.rawPayload.length.toString())\n    expect(res.headers.etag).to.equal(`\"${fileCid}\"`)\n    expect(res.headers.suborigin).to.equal(`ipfs000${dirCid.toV1().toString(base32)}`)\n    expect(res.rawPayload).to.deep.equal(content)\n  })\n\n  it('test(gateway): load from URI-encoded path', async () => {\n    const dirCid = CID.parse('QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o')\n    const fileCid = CID.parse('Qmd286K6pohQcTKYqnS1YhWrCiS4gz7Xi34sdwMe9USZ7u')\n    // non-ascii characters will be URI-encoded by the browser\n    const utf8path = `/ipfs/${dirCid}/cat-with-óąśśł-and-أعظم._.jpg`\n    const escapedPath = encodeURI(utf8path) // this is what will be actually requested\n    const content = fs.readFileSync('test/fixtures/index.html')\n    ipfs.files.stat.withArgs(utf8path).resolves({\n      cid: fileCid,\n      type: 'file',\n      size: content.length\n    })\n    ipfs.files.stat.withArgs(`/ipfs/${fileCid}`).resolves({\n      cid: fileCid,\n      type: 'file',\n      size: content.length\n    })\n    ipfs.cat.withArgs(fileCid).returns([\n      content\n    ])\n\n    const res = await http({\n      method: 'GET',\n      url: escapedPath\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n    expect(res.headers['content-type']).to.equal('image/jpeg')\n    expect(res.headers['x-ipfs-path']).to.equal(escapedPath)\n    expect(res.headers['cache-control']).to.equal('public, max-age=29030400, immutable')\n    expect(res.headers['last-modified']).to.equal('Thu, 01 Jan 1970 00:00:01 GMT')\n    expect(res.headers['content-length']).to.equal(res.rawPayload.length.toString())\n    expect(res.headers.etag).to.equal(`\"${fileCid}\"`)\n    expect(res.headers.suborigin).to.equal(`ipfs000${dirCid.toV1().toString(base32)}`)\n  })\n\n  it('load a file from IPNS', async () => {\n    const id = 'Qmd286K6pohQcTKYqnS1YhWrCiS4gz7Xi34sdwMe9USZ7A'\n    const ipnsPath = `/ipns/${id}/cat.jpg`\n    const fileCid = CID.parse('Qmd286K6pohQcTKYqnS1YhWrCiS4gz7Xi34sdwMe9USZ7u')\n    const content = fs.readFileSync('test/fixtures/cat-folder/cat.jpg')\n\n    ipfs.name.resolve.withArgs(ipnsPath).returns([`/ipfs/${fileCid}`])\n    ipfs.files.stat.withArgs(`/ipfs/${fileCid}`).resolves({\n      cid: fileCid,\n      type: 'file',\n      size: content.length\n    })\n    ipfs.files.stat.withArgs(`/ipfs/${fileCid}`).resolves({\n      cid: fileCid,\n      type: 'file',\n      size: content.length\n    })\n    ipfs.cat.withArgs(fileCid).returns([\n      content\n    ])\n\n    const res = await http({\n      method: 'GET',\n      url: ipnsPath\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n    expect(res.headers['content-type']).to.equal('image/jpeg')\n    expect(res.headers['content-length']).to.equal(res.rawPayload.length.toString()).to.equal('443230')\n    expect(res.headers['x-ipfs-path']).to.equal(ipnsPath)\n    expect(res.headers['etag']).to.equal(`\"${fileCid}\"`)\n    expect(res.headers['cache-control']).to.equal('no-cache') // TODO: should be record TTL\n    expect(res.headers['last-modified']).to.equal(undefined)\n    expect(res.headers.etag).to.equal(`\"${fileCid}\"`)\n    expect(res.headers.suborigin).to.equal(`ipns000${CID.parse(id).toV1().toString()}`)\n\n    const fileSignature = await fileTypeFromBuffer(res.rawPayload)\n    expect(fileSignature.mime).to.equal('image/jpeg')\n    expect(fileSignature.ext).to.equal('jpg')\n  }, { ipfs })\n\n  it('load a directory from IPNS', async () => {\n    const id = 'Qmd286K6pohQcTKYqnS1YhWrCiS4gz7Xi34sdwMe9USZ7A'\n    const ipnsPath = `/ipns/${id}/`\n    const dirCid = CID.parse('QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o')\n    ipfs.name.resolve.withArgs(ipnsPath).returns([`/ipfs/${dirCid}`])\n    ipfs.files.stat.withArgs(`/ipfs/${dirCid}`).resolves({\n      cid: dirCid,\n      type: 'directory'\n    })\n    ipfs.files.stat.withArgs(`/ipfs/${dirCid}/index.html`).rejects(new Error('does not exist'))\n    ipfs.files.stat.withArgs(`/ipfs/${dirCid}/index.htm`).rejects(new Error('does not exist'))\n    ipfs.files.stat.withArgs(`/ipfs/${dirCid}/index.shtml`).rejects(new Error('does not exist'))\n    ipfs.dag.get.withArgs(dirCid).returns({\n      value: {\n        Links: [{\n          Name: 'cat.jpg',\n          Tsize: 12\n        }]\n      }\n    })\n\n    const res = await http({\n      method: 'GET',\n      url: ipnsPath\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n    expect(res.headers['content-type']).to.equal('text/html; charset=utf-8')\n    expect(res.headers['x-ipfs-path']).to.equal(ipnsPath)\n    expect(res.headers['cache-control']).to.equal('no-cache')\n    expect(res.headers['last-modified']).to.equal(undefined)\n    expect(res.headers['content-length']).to.equal(res.rawPayload.length)\n    expect(res.headers.etag).to.equal(undefined)\n    expect(res.headers.suborigin).to.equal(`ipns000${CID.parse(id).toV1().toString()}`)\n\n    // check if the cat picture is in the payload as a way to check\n    // if this is an index of this directory\n    const listedFile = res.payload.match(/\\/cat\\.jpg/g)\n    expect(listedFile).to.have.lengthOf(1)\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-gateway/test/utils/http.js",
    "content": "\nimport { HttpGateway } from '../../src/index.js'\n\nexport async function http (request, { ipfs } = {}) {\n  const api = new HttpGateway(ipfs)\n  const server = await api._createGatewayServer('127.0.0.1', 8080, ipfs)\n\n  return server.inject(request)\n}\n"
  },
  {
    "path": "packages/ipfs-http-gateway/tsconfig.json",
    "content": "{\n  \"extends\": \"aegir/src/config/tsconfig.aegir.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"emitDeclarationOnly\": true\n  },\n  \"include\": [\n    \"src\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../ipfs-core-types\"\n    },\n    {\n      \"path\": \"../ipfs-http-response\"\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/ipfs-http-response/CHANGELOG.md",
    "content": "# Change Log\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n### [6.0.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-response-v6.0.0...ipfs-http-response-v6.0.1) (2023-05-25)\n\n\n### Bug Fixes\n\n* add deprecation notice to readmes ([#4362](https://www.github.com/ipfs/js-ipfs/issues/4362)) ([7b79c1b](https://www.github.com/ipfs/js-ipfs/commit/7b79c1b8df5c818dc124b346ea28330455732d5c))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * ipfs-core bumped from ^0.18.0 to ^0.18.1\n\n## [6.0.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-response-v5.0.0...ipfs-http-response-v6.0.0) (2023-01-11)\n\n\n### ⚠ BREAKING CHANGES\n\n* update multiformats to v11.x.x and related depenendcies (#4277)\n\n### Bug Fixes\n\n* update multiformats to v11.x.x and related depenendcies ([#4277](https://www.github.com/ipfs/js-ipfs/issues/4277)) ([521c84a](https://www.github.com/ipfs/js-ipfs/commit/521c84a958b04d61702577a5adce28519c1b2a3b))\n* use aegir to publish RCs ([#4284](https://www.github.com/ipfs/js-ipfs/issues/4284)) ([6d90cbf](https://www.github.com/ipfs/js-ipfs/commit/6d90cbf321a7dbf4b1084ba20f0c514dc08d8d0a))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * ipfs-core bumped from ^0.17.0 to ^0.18.0\n\n## [5.0.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-response-v4.0.1...ipfs-http-response-v5.0.0) (2022-10-24)\n\n\n### ⚠ BREAKING CHANGES\n\n* ipfs is now bundled with libp2p@0.40.x which has different config\n\n### Features\n\n* upgrade libp2p to 0.40.x ([#4237](https://www.github.com/ipfs/js-ipfs/issues/4237)) ([0cee4a4](https://www.github.com/ipfs/js-ipfs/commit/0cee4a4c55767022584dcbade0b0b9b43326f9c9))\n\n\n### Bug Fixes\n\n* replace slice with subarray for increased performance ([#4210](https://www.github.com/ipfs/js-ipfs/issues/4210)) ([dfc43d4](https://www.github.com/ipfs/js-ipfs/commit/dfc43d4e9be67fdf25553677f469379d966ff806))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * ipfs-core bumped from ^0.16.1 to ^0.17.0\n\n### [4.0.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-response-v4.0.0...ipfs-http-response-v4.0.1) (2022-09-21)\n\n\n### Bug Fixes\n\n* update @multiformats/multiadd to 11.0.0 ([2a830bf](https://www.github.com/ipfs/js-ipfs/commit/2a830bf58a5929fcce51dede871c99f62192fbda))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * ipfs-core bumped from ^0.16.0 to ^0.16.1\n\n## [4.0.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-response-v3.0.4...ipfs-http-response-v4.0.0) (2022-09-06)\n\n\n### ⚠ BREAKING CHANGES\n\n* update to libp2p@0.38.x (#4151)\n\n### deps\n\n* update to libp2p@0.38.x ([#4151](https://www.github.com/ipfs/js-ipfs/issues/4151)) ([39dbf70](https://www.github.com/ipfs/js-ipfs/commit/39dbf708ec31b263115e44f420651fa4e056a89e))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * ipfs-core bumped from ^0.15.0 to ^0.16.0\n\n### [3.0.4](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-response-v3.0.3...ipfs-http-response-v3.0.4) (2022-06-24)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * ipfs-core bumped from ^0.15.3 to ^0.15.4\n\n### [3.0.3](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-response-v3.0.2...ipfs-http-response-v3.0.3) (2022-06-22)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * ipfs-core bumped from ^0.15.2 to ^0.15.3\n\n### [3.0.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-response-v3.0.1...ipfs-http-response-v3.0.2) (2022-06-13)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * ipfs-core bumped from ^0.15.1 to ^0.15.2\n\n### [3.0.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-response-v3.0.0...ipfs-http-response-v3.0.1) (2022-06-01)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * ipfs-core bumped from ^0.15.0 to ^0.15.1\n\n## [3.0.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-response-v2.0.3...ipfs-http-response-v3.0.0) (2022-05-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* This module is now ESM only and there return types of some methods have changed\n\n### Features\n\n* update to libp2p 0.37.x ([#4092](https://www.github.com/ipfs/js-ipfs/issues/4092)) ([74aee8b](https://www.github.com/ipfs/js-ipfs/commit/74aee8b3d78f233c3199a3e9a6c0ac628a31a433))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * ipfs-core bumped from ^0.14.3 to ^0.15.0\n\n### [2.0.3](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-response-v2.0.2...ipfs-http-response-v2.0.3) (2022-04-20)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * ipfs-core bumped from ^0.14.2 to ^0.14.3\n\n### [2.0.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-response-v2.0.1...ipfs-http-response-v2.0.2) (2022-03-01)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * ipfs-core bumped from ^0.14.1 to ^0.14.2\n\n### [2.0.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-response-v2.0.0...ipfs-http-response-v2.0.1) (2022-02-06)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * ipfs-core bumped from ^0.14.0 to ^0.14.1\n\n## [2.0.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-response-v1.0.6...ipfs-http-response-v2.0.0) (2022-01-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* peerstore methods are now all async, the repo is migrated to v12\n\n### Features\n\n* libp2p async peerstore ([#4018](https://www.github.com/ipfs/js-ipfs/issues/4018)) ([a6b201a](https://www.github.com/ipfs/js-ipfs/commit/a6b201af2c3697430ab0ebe002dd573d185f1ac0))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * ipfs-core bumped from ^0.13.0 to ^0.14.0\n\n### [1.0.6](https://github.com/ipfs/js-ipfs-http-response/compare/ipfs-http-response@1.0.5...ipfs-http-response@1.0.6) (2021-12-15)\n\n**Note:** Version bump only for package ipfs-http-response\n\n\n\n\n\n### [1.0.5](https://github.com/ipfs/js-ipfs-http-response/compare/ipfs-http-response@1.0.4...ipfs-http-response@1.0.5) (2021-11-24)\n\n**Note:** Version bump only for package ipfs-http-response\n\n\n\n\n\n### [1.0.4](https://github.com/ipfs/js-ipfs-http-response/compare/ipfs-http-response@1.0.3...ipfs-http-response@1.0.4) (2021-11-19)\n\n**Note:** Version bump only for package ipfs-http-response\n\n\n\n\n\n### [1.0.3](https://github.com/ipfs/js-ipfs-http-response/compare/ipfs-http-response@1.0.2...ipfs-http-response@1.0.3) (2021-11-12)\n\n**Note:** Version bump only for package ipfs-http-response\n\n\n\n\n\n## 1.0.2 (2021-09-28)\n\n\n### Bug Fixes\n\n* add types versions to http-response ([f8cc058](https://github.com/ipfs/js-ipfs-http-response/commit/f8cc058b1d7fa1f116b58ad7a7ebd332c3150714))\n\n\n\n\n\n### [1.0.1](https://github.com/ipfs/js-ipfs-http-response/compare/v1.0.0...v1.0.1) (2021-09-08)\n\n\n\n## [1.0.0](https://github.com/ipfs/js-ipfs-http-response/compare/v0.7.0...v1.0.0) (2021-09-07)\n\n\n### Bug Fixes\n\n* update module ([#104](https://github.com/ipfs/js-ipfs-http-response/issues/104)) ([319e2b4](https://github.com/ipfs/js-ipfs-http-response/commit/319e2b416bb6283e0f0e67c7dc9f609851e16909))\n\n\n\n## [0.7.0](https://github.com/ipfs/js-ipfs-http-response/compare/v0.6.4...v0.7.0) (2021-07-12)\n\n\n### chore\n\n* update to new multiformats ([#98](https://github.com/ipfs/js-ipfs-http-response/issues/98)) ([1641cef](https://github.com/ipfs/js-ipfs-http-response/commit/1641cefaa2cc965ddd7fdaa2d9be8dd7b0150665))\n\n\n### BREAKING CHANGES\n\n* pulls in new multiformats modules\n\n\n\n### [0.6.4](https://github.com/ipfs/js-ipfs-http-response/compare/v0.6.3...v0.6.4) (2021-04-16)\n\n\n\n### [0.6.3](https://github.com/ipfs/js-ipfs-http-response/compare/v0.6.2...v0.6.3) (2021-04-12)\n\n\n\n### [0.6.2](https://github.com/ipfs/js-ipfs-http-response/compare/v0.6.1...v0.6.2) (2021-03-17)\n\n\n### Bug Fixes\n\n* add node dev deps ([#81](https://github.com/ipfs/js-ipfs-http-response/issues/81)) ([9fcc821](https://github.com/ipfs/js-ipfs-http-response/commit/9fcc8215d511e6e9071f0fb9fd927d9005a72670))\n\n\n\n### [0.6.1](https://github.com/ipfs/js-ipfs-http-response/compare/v0.6.0...v0.6.1) (2020-12-22)\n\n\n\n<a name=\"0.6.0\"></a>\n## [0.6.0](https://github.com/ipfs/js-ipfs-http-response/compare/v0.5.1...v0.6.0) (2020-08-14)\n\n\n### Bug Fixes\n\n* replace node buffers with uint8arrays ([#55](https://github.com/ipfs/js-ipfs-http-response/issues/55)) ([710a96d](https://github.com/ipfs/js-ipfs-http-response/commit/710a96d))\n* webpack build ([#56](https://github.com/ipfs/js-ipfs-http-response/issues/56)) ([0c61a36](https://github.com/ipfs/js-ipfs-http-response/commit/0c61a36))\n\n\n### BREAKING CHANGES\n\n* - All deps of this module use Uint8Arrays instead of node Buffers\n\n* chore: remove browser build steps\n\n\n\n<a name=\"0.5.1\"></a>\n### [0.5.1](https://github.com/ipfs/js-ipfs-http-response/compare/v0.5.0...v0.5.1) (2020-06-30)\n\n\n### Bug Fixes\n\n* **ci:** add empty commit to fix lint checks on master ([1db03b0](https://github.com/ipfs/js-ipfs-http-response/commit/1db03b0))\n\n\n\n<a name=\"0.5.0\"></a>\n## [0.5.0](https://github.com/ipfs/js-ipfs-http-response/compare/v0.4.0...v0.5.0) (2020-01-07)\n\n\n### Code Refactoring\n\n* use new IPFS async/await APIs ([#30](https://github.com/ipfs/js-ipfs-http-response/issues/30)) ([68f1204](https://github.com/ipfs/js-ipfs-http-response/commit/68f1204))\n\n\n### BREAKING CHANGES\n\n* Switch to using async/await and async iterators.\n\n\n\n<a name=\"0.4.0\"></a>\n## [0.4.0](https://github.com/ipfs/js-ipfs-http-response/compare/v0.3.1...v0.4.0) (2019-10-14)\n\n\n### Chores\n\n* convert to async await syntax ([#28](https://github.com/ipfs/js-ipfs-http-response/issues/28)) ([a22900a](https://github.com/ipfs/js-ipfs-http-response/commit/a22900a))\n\n\n### BREAKING CHANGES\n\n* All places in the API that used callbacks are now replaced with async/await\n\nCo-authored-by: PedroMiguelSS <pedro.santos@moxy.studio>\n\n\n\n<a name=\"0.3.1\"></a>\n### [0.3.1](https://github.com/ipfs/js-ipfs-http-response/compare/v0.2.2...v0.3.1) (2019-06-06)\n\n\n### Bug Fixes\n\n* create .npmignore to include dist on npm ([#16](https://github.com/ipfs/js-ipfs-http-response/issues/16)) ([7746dab](https://github.com/ipfs/js-ipfs-http-response/commit/7746dab))\n\n\n### Chores\n\n* update ipld formats ([#25](https://github.com/ipfs/js-ipfs-http-response/issues/25)) ([529613a](https://github.com/ipfs/js-ipfs-http-response/commit/529613a))\n\n\n### Features\n\n* load files/dirs from hamt shards ([#19](https://github.com/ipfs/js-ipfs-http-response/issues/19)) ([25edfbc](https://github.com/ipfs/js-ipfs-http-response/commit/25edfbc))\n\n\n### BREAKING CHANGES\n\n* v1 CIDs created by this module now default to base32 encoding when stringified\n\nNot a direct dependency of this module but ipld-dag-pb changed the\ncase of some property names that are used by this module.\n\nLicense: MIT\nSigned-off-by: Alan Shaw <alan.shaw@protocol.ai>\n\n\n\n<a name=\"0.3.0\"></a>\n## [0.3.0](https://github.com/ipfs/js-ipfs-http-response/compare/v0.2.2...v0.3.0) (2019-05-21)\n\nBREAKING CHANGE: v1 CIDs created by this module now default to base32 encoding when stringified\n\nNot a direct dependency of this module but ipld-dag-pb changed the\ncase of some property names that are used by this module.\n\n<a name=\"0.2.2\"></a>\n### [0.2.2](https://github.com/ipfs/js-ipfs-http-response/compare/v0.2.1...v0.2.2) (2019-01-19)\n\n\n\n<a name=\"0.2.1\"></a>\n### [0.2.1](https://github.com/ipfs/js-ipfs-http-response/compare/v0.1.4...v0.2.1) (2018-11-09)\n\n\n### Bug Fixes\n\n* use .cid property before falling back to .multihash ([#12](https://github.com/ipfs/js-ipfs-http-response/issues/12)) ([1c1a478](https://github.com/ipfs/js-ipfs-http-response/commit/1c1a478))\n\n\n\n<a name=\"0.2.0\"></a>\n## [0.2.0](https://github.com/ipfs/js-ipfs-http-response/compare/v0.1.4...v0.2.0) (2018-09-28)\n\n\n\n<a name=\"0.1.4\"></a>\n### [0.1.4](https://github.com/ipfs/js-ipfs-http-response/compare/v0.1.3...v0.1.4) (2018-08-02)\n\n\n### Bug Fixes\n\n* fix content-type by doing a fall-back using extensions ([#5](https://github.com/ipfs/js-ipfs-http-response/issues/5)) ([19acbae](https://github.com/ipfs/js-ipfs-http-response/commit/19acbae))\n\n\n\n<a name=\"0.1.3\"></a>\n### [0.1.3](https://github.com/ipfs/js-ipfs-http-response/compare/v0.1.2...v0.1.3) (2018-07-28)\n\n\n### Bug Fixes\n\n* firefox using readable stream ([#3](https://github.com/ipfs/js-ipfs-http-response/issues/3)) ([0bff82d](https://github.com/ipfs/js-ipfs-http-response/commit/0bff82d))\n\n\n\n<a name=\"0.1.2\"></a>\n## 0.1.2 (2018-06-01)\n\n\n### Bug Fixes\n\n* update package name ([91b99b3](https://github.com/ipfs/js-ipfs-http-response/commit/91b99b3))\n\n\n### Features\n\n* export resolver ([d9e56b8](https://github.com/ipfs/js-ipfs-http-response/commit/d9e56b8))\n* initial implementation ([d9d0c08](https://github.com/ipfs/js-ipfs-http-response/commit/d9d0c08))"
  },
  {
    "path": "packages/ipfs-http-response/LICENSE",
    "content": "This project is dual licensed under MIT and Apache-2.0.\n\nMIT: https://www.opensource.org/licenses/mit\nApache-2.0: https://www.apache.org/licenses/license-2.0\n"
  },
  {
    "path": "packages/ipfs-http-response/LICENSE-APACHE",
    "content": "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\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless 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.\n"
  },
  {
    "path": "packages/ipfs-http-response/LICENSE-MIT",
    "content": "The MIT License (MIT)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "packages/ipfs-http-response/README.md",
    "content": "> # ⛔️ DEPRECATED: [js-IPFS](https://github.com/ipfs/js-ipfs) has been superseded by [Helia](https://github.com/ipfs/helia)\n>\n> 📚 [Learn more about this deprecation](https://github.com/ipfs/js-ipfs/issues/4336) or [how to migrate](https://github.com/ipfs/helia/wiki/Migrating-from-js-IPFS)\n>\n> ⚠️ If you continue using this repo, please note that security fixes will not be provided\n\n# ipfs-http-response <!-- omit in toc -->\n\n[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech)\n[![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech)\n[![codecov](https://img.shields.io/codecov/c/github/ipfs/js-ipfs.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipfs)\n[![CI](https://img.shields.io/github/actions/workflow/status/ipfs/js-ipfs/test.yml?branch=master\\&style=flat-square)](https://github.com/ipfs/js-ipfs/actions/workflows/test.yml?query=branch%3Amaster)\n\n> Creates an HTTP response from an IPFS Hash\n\n## Table of contents <!-- omit in toc -->\n\n- [Install](#install)\n- [Usage](#usage)\n  - [Creating HTTP Response](#creating-http-response)\n  - [Using protocol-agnostic resolver](#using-protocol-agnostic-resolver)\n- [License](#license)\n- [Contribute](#contribute)\n\n## Install\n\n```console\n$ npm i ipfs-http-response\n```\n\n## Usage\n\n### Creating HTTP Response\n\nThis project creates a HTTP response for an IPFS Path. This response can be a file, a HTML with directory listing or the entry point of a web page.\n\n```js\nimport { getResponse } from 'ipfs-http-response'\n\nconst result = await getResponse(ipfsNode, ipfsPath)\nconsole.log(result)\n```\n\n### Using protocol-agnostic resolver\n\nThis module also exports the used ipfs `resolver`, which should be used when the response needs to be customized or non-HTTP transport is used:\n\n```js\nimport { resolver } from 'ipfs-http-response'\n\nconst result = await resolver.cid(ipfsNode, ipfsPath)\nconsole.log(result)\n```\n\nIf `ipfsPath` points at a directory, `resolver.cid` will throw Error `This dag node is a directory` with a `cid` attribute that can be passed to `resolver.directory`:\n\n```js\nimport { resolver } from 'ipfs-http-response'\n\nconst result = await resolver.directory(ipfsNode, ipfsPath, cid)\nconsole.log(result)\n```\n\n`result` will be either a `string` with HTML directory listing or an array with CIDs of `index` pages present in inspected directory.\n\n![ipfs-http-response usage](docs/ipfs-http-response.png \"ipfs-http-response usage\")\n\n## License\n\nLicensed under either of\n\n- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / <http://www.apache.org/licenses/LICENSE-2.0>)\n- MIT ([LICENSE-MIT](LICENSE-MIT) / <http://opensource.org/licenses/MIT>)\n\n## Contribute\n\nContributions welcome! Please check out [the issues](https://github.com/ipfs/js-ipfs/issues).\n\nAlso 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.\n\nPlease 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).\n\nUnless 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.\n\n[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md)\n"
  },
  {
    "path": "packages/ipfs-http-response/package.json",
    "content": "{\n  \"name\": \"ipfs-http-response\",\n  \"version\": \"6.0.1\",\n  \"description\": \"Creates an HTTP response from an IPFS Hash\",\n  \"author\": \"Vasco Santos <vasco.santos@moxy.studio>\",\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"homepage\": \"https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-http-response#readme\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ipfs/js-ipfs.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ipfs/js-ipfs/issues\"\n  },\n  \"keywords\": [\n    \"http\",\n    \"ipfs\",\n    \"response\"\n  ],\n  \"engines\": {\n    \"node\": \">=16.0.0\",\n    \"npm\": \">=7.0.0\"\n  },\n  \"type\": \"module\",\n  \"types\": \"./dist/src/index.d.ts\",\n  \"typesVersions\": {\n    \"*\": {\n      \"*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ],\n      \"src/*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ]\n    }\n  },\n  \"files\": [\n    \"src\",\n    \"dist\",\n    \"!dist/test\",\n    \"!**/*.tsbuildinfo\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/src/index.d.ts\",\n      \"import\": \"./src/index.js\"\n    }\n  },\n  \"eslintConfig\": {\n    \"extends\": \"ipfs\",\n    \"parserOptions\": {\n      \"sourceType\": \"module\"\n    }\n  },\n  \"scripts\": {\n    \"lint\": \"aegir lint\",\n    \"clean\": \"aegir clean\",\n    \"build\": \"aegir build --no-bundle\",\n    \"test\": \"aegir test -t node\",\n    \"test:node\": \"aegir test -t node --cov\",\n    \"dep-check\": \"aegir dep-check -i global\"\n  },\n  \"dependencies\": {\n    \"@libp2p/logger\": \"^2.0.5\",\n    \"ejs\": \"^3.1.6\",\n    \"file-type\": \"^18.0.0\",\n    \"filesize\": \"^10.0.5\",\n    \"it-map\": \"^2.0.0\",\n    \"it-reader\": \"^6.0.1\",\n    \"it-to-stream\": \"^1.0.0\",\n    \"mime-types\": \"^2.1.30\",\n    \"p-try-each\": \"^1.0.1\"\n  },\n  \"devDependencies\": {\n    \"@types/ejs\": \"^3.1.0\",\n    \"@types/mime-types\": \"^2.1.1\",\n    \"aegir\": \"^37.11.0\",\n    \"get-stream\": \"^6.0.0\",\n    \"ipfs-core\": \"^0.18.1\",\n    \"ipfsd-ctl\": \"^13.0.0\",\n    \"it-all\": \"^2.0.0\",\n    \"uint8arrays\": \"^4.0.2\"\n  },\n  \"browser\": {\n    \"file-type\": \"file-type/browser\",\n    \"fs\": false\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-response/src/dir-view/index.js",
    "content": "import { filesize } from 'filesize'\nimport style from './style.js'\nimport { cidArray } from '../utils/path.js'\nimport ejs from 'ejs'\n\n/**\n * @param {string} path\n */\nfunction getParentHref (path) {\n  const parts = cidArray(path).slice()\n  if (parts.length > 1) {\n    // drop the last segment in a safe way that works for both paths and urls\n    return path.replace(`/${parts.pop()}`, '')\n  }\n  return path\n}\n\n/**\n * @param {string} path\n * @param {({ Name: string, Tsize: number })[]} links\n */\nexport function render (path, links) {\n  return ejs.render(`<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title><%= path %></title>\n  <style>${style}</style>\n</head>\n<body>\n  <div id=\"header\" class=\"row\">\n    <div class=\"col-xs-2\">\n      <div id=\"logo\" class=\"ipfs-logo\"></div>\n    </div>\n  </div>\n  <br>\n  <div class=\"col-xs-12\">\n    <div class=\"panel panel-default\">\n      <div class=\"panel-heading\">\n        <strong>Index of <%= path %></strong>\n      </div>\n      <table class=\"table table-striped\">\n        <tbody>\n          <tr>\n            <td class=\"narrow\">\n              <div class=\"ipfs-icon ipfs-_blank\">&nbsp;</div>\n            </td>\n            <td class=\"padding\">\n              <a href=\"<%= parentHref %>\">..</a>\n            </td>\n            <td></td>\n          </tr>\n          <% links.forEach(function (link) { %>\n          <tr>\n            <td><div class=\"ipfs-icon ipfs-_blank\">&nbsp;</div></td>\n            <td><a href=\"<%= link.link %>\"><%= link.name %></a></t>\n            <td><%= link.size %></td>\n          </td>\n          </tr>\n          <% }) %>\n        </tbody>\n      </table>\n    </div>\n  </div>\n</body>\n</html>\n`, {\n    path,\n    links: links.map((link) => ({\n      name: link.Name,\n      size: filesize(link.Tsize),\n      link: `${path}${path.endsWith('/') ? '' : '/'}${link.Name}`\n    })),\n    parentHref: getParentHref(path)\n  })\n}\n"
  },
  {
    "path": "packages/ipfs-http-response/src/dir-view/style.js",
    "content": "export default `html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a{background-color:transparent}a:active,a:hover{outline:0}strong{font-weight:700}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}table{border-spacing:0;border-collapse:collapse}td{padding:0} @media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:\" (\" attr(href) \")\"}tr{page-break-inside:avoid}.table{border-collapse:collapse!important}.table td{background-color:#fff!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:\"Helvetica Neue\",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.row{margin-right:-15px;margin-left:-15px}.col-xs-12,.col-xs-2{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-12,.col-xs-2{float:left}.col-xs-12{width:100%}.col-xs-2{width:16.66666667%}table{background-color:transparent}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table{margin-bottom:0}.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child{border-bottom-right-radius:3px}.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.row:after,.row:before{display:table;content:\" \"}.row:after{clear:both}@-ms-viewport{width:device-width}.ipfs-_blank{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAWBJREFUeNqEUj1LxEAQnd1MVA4lyIEWx6UIKEGUExGsbC3tLfwJ/hT/g7VlCnubqxXBwg/Q4hQP/LhKL5nZuBsvuGfW5MGyuzM7jzdvVuR5DgYnZ+f99ai7Vt5t9K9unu4HLweI3qWYxI6PDosdy0fhcntxO44CcOBzPA7mfEyuHwf7ntQk4jcnywOxIlfxOCNYaLVgb6cXbkTdhJXq2SIlNMC0xIqhHczDbi8OVzpLSUa0WebRfmigLHqj1EcPZnwf7gbDIrYVRyEinurj6jTBHyI7pqVrFQqEbt6TEmZ9v1NRAJNC1xTYxIQh/MmRUlmFQE3qWOW1nqB2TWk1/3tgJV0waVvkFIEeZbHq4ElyKzAmEXOx6gnEVJuWBzmkRJBRPYGZBDsVaOlpSgVJE2yVaAe/0kx/3azBRO0VsbMFZE3CDSZKweZfYIVg+DZ6v7h9GDVOwZPw/PoxKu/fAgwALbDAXf7DdQkAAAAASUVORK5CYII=');background-repeat:no-repeat;background-size:contain}.ipfs-_page{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmhJREFUeNpsUztv01AYPfdhOy/XTZ80VV1VoCqlA2zQqUgwMEErWBALv4GJDfEDmOEHsFTqVCTExAiiSI2QEKJKESVFFBWo04TESRzfy2c7LY/kLtf2d8+555zvM9NaI1ora5svby9OnbUEBxgDlIKiWjXQeLy19/X17sEtcPY2rtHS96/Hu0RvXXLz+cUzM87zShsI29DpHCYt4E6Box4IZzTnbDx7V74GjhOSfwgE0H2638K9h08A3iHGVbjTw7g6YmAyw/BgecHNGGJjvfQhIfmfIFDAXJpjuugi7djIFVI4P0plctgJQ0xnFe5eOO02OwEp2VkhSCnC8WOCdqgwnzFx4/IyppwRVN+XYXsecqZA1pB48ekAnw9/4GZx3L04N/GoTwEjX4cNH5vlPfjtAIYp8cWrQutxrC5Mod3VsXVTMFSqtaE+gl9dhaUxE2tXZiF7nYiiatJ3v5s8R/1yOCNLOuwjkELiTbmC9dJHpIaGASsDkoFQGJQwHWMcHWJYOmUj1OjvQotuytt5nHMLEGkCyx6QU384jwkUAd2sxJbS/QShZtg/8rHzzQOzSaFhxQrA6YgQMQHojCUlgnCAAvKFBoXXaHfArSCZDE0gyWJgFIKmvUFKO4MUNIk2a4+hODtDUVuJ/J732AKS6ZtImdTyAQQB3bZN8l9t75IFh0JMUdVKsohsUPqRgnka0tYgggYpCHkKGTsHI5NOMojB4iTICCepvX53AIEfQta1iUCmoTiBmdEri2RgddKFhuJoqb/af/yw/d3zTNM6UkaOfis62aUgddAbnz+rXuPY+Vnzjt9/CzAAbmLjCrfBiRgAAAAASUVORK5CYII=');background-repeat:no-repeat;background-size:contain}.ipfs-aac{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnhJREFUeNp0Uk1PE0EYftruVlvAUkhVEPoBcsEoLRJBY01MPHjCs3cvogcT/4qJJN5NvHhoohcOnPw4YEGIkCh+oLGBKm3Z7nZ3dme2vjOhTcjiJJvZzPvOM8/HG2q325Dr3kLp7Y1ibpIxjs4KhQBZfvV6s7K5Vb0bjeof5ZlcGysP1a51mifODybvzE8mzCbrAoTDIThMoGXZiZ4YSiurf+Z1XeuCqJ7Oj+sK3jQcNAmg8xkGQ71mYejcAB49vpmeuzJccl0+dUj6KIAvfHCPg3N+uAv4vg9BOxcCmfEzuP/genpmeqhEMgude10Jwm+DuUIyUdTlqu2byoMfX/dRermBeExHsTiWNi3+lMpzRwDki8zxCIATmzbevfmClukiP5NFhJgwkjeRTeLShdOoVJqnAgwkgCAZ6+UdLC9twjQZ8pdzioFkZBHY3q6B3l4dJEEEPOCeD4cYVH7Xsf15F+FImC775INAJBJSkVoWo0QY9YqgiR4ZZzRaGBkdwK3bFxGLRZUfB3Rm2x4x9CGtsUxH9QYkKICDFuLxKAozGZwdTqBRs2FbLlXbiPdECMCHadj/AaDXZNFqedCIvnRcS4UpRo7+hC5zUmw8Ope9wUFinvpmZ7NKt2RTmB4hKZo6n8qP4Oq1HBkKlVYAQBrUlziB0XQSif4YmQhksgNIJk9iaLhPaV9b/Um+uJSCdzyDbGZQRSkvjo+n4JNxubGUSsCj+ZCpODYjkGMAND2k7exUsfhkCd+29yguB88Wl7FW/o6tT7/gcXqAgGv7hhx1LWBireHVn79YP6ChQ3njb/eFlfWqGqT3H3ZlGIhGI2i2UO/U/wkwAAmoalcxlNA1AAAAAElFTkSuQmCC');background-repeat:no-repeat;background-size:contain}.ipfs-ai{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAk5JREFUeNpsU01vElEUPTPzZqBAQaSFQiJYUmlKYhoTF41L3Tbu/Q/+AvsX3Bp/gPsuWLrqyqQ7TUxMtAvF1tYGoXwNw7wv7zwYgtKX3Lw379575p5z77O01ohW+/DVh8zj7aYKhflGdG9ZsGwLNydffgVfr19YHvsEa+Zu/nxndob5StQK+dyzvZzyw/gKlmMj7IygFM+xvNcanp4/t5dAomXHBy2UUBOO2MAl/B9/cPb6PULuoHx0WM0e3GvpUOxD3wZAJWutZqYUYmqpSg5OMgH3YQObL59W0/ullpryR3HegkKEqiWBSGV4R3vQ7sIhScTZFTpHx3A215B5sluVY/WWMg7+ATB/lcLsKpTonHzD+OMFEuTz8ikkt9Kwt9YJZB38cpBdoQAZJdLvCGByfoPB6Xdk90pYy6Xg3c/DaWwArg09DaG5lCsUFN0pckZAojdC8m4auBqaALuSgez7VB1RtDSUWOQvUaBLFUzJBMJ2DwmPgd1Jwm0WoSgJfjDvrTKxtwAIyEkAOQ5hU//Zdg5uowDlUNMnwZLW0sSuUuACYhwQRwFvJxupCjEYUUccOkoaKmdOlZnY1TkgAcXAhxhOwLsDsHoN3u4O5JTDfVCH6I9nfjId3gIgSUATFJk/hVevGtOMwS0XwQ3AzB/FrlKg8Q27I2javVoZrFgwD4qVipAEyMlnaFArzaj/D0DiMXlJAFQyK2r8fnMMRZp4lQ1MaSL5tU/1kqAkMCh2tYI+7+kh70cjPbr4bEZ51jZr8TJnB9PJXpz3V4ABAPOQVJn2Q60GAAAAAElFTkSuQmCC');background-repeat:no-repeat;background-size:contain}.ipfs-aiff{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAohJREFUeNpkU9tqE1EUXZmZpE3aTBLbJFPTtFURtSCthr7UCyKKFJ/9An3og6Ag/oXfoUj7og9asCBYKT6UIPHaWtpq7NU2aZK5z5wZ9xxMpMwZDuewz9prr32ZiO/7CNaDx3OLt6fOjBqGg/aKRCIInp8+KzfKH7fudnVF58nE16el+/yU2mBFSWZKpWJKVc0OgUBo02K4NDmU6o75Mx+Wdu9IUXFeiOA/pn1xHeYaugVDdzpbp91qGlAKGTx8dC19/Wpxhjnsxj/RRwk85hGJC9d1O6fneWAuoztDYSSLe9OT6SuXB2ccx73Z9uukwDwfls1g0xZIY/Ad/Gnyt/XVfbyYrSDRE8PExHB6/8B6QuaxIwRBFMt0iIAiMx+LCys8jfGJEUik2WpZOD2SQf9oDtVqQwopCAiY66FS/om3b75CVS2MlU7AJ2WiJBCZjZ2dJuRkDJZFwFAR7UCBja3fNfxY2YEoCtRCj9em3Tpds6FpJseGCBxS0GgYGBzqw62p84gnYnAI2CSbSbPhEpFAaE2zODaUAlWWwDoS5DheGqbWpVE/0CmqCY9qkEyINBceb2uADRNQ8bSWAVVzIFKomCQim+0luS4yKYlsHlRyZo7EsSEC23K5vAsXh/H92zZkuRvxeBS5nEx2yp2KqhxPoV5TYS/8CtdApylM9sZQKKSQzyeRTseRV2QoAzIYY8jme5DN9fI0dQoUIjANGydP9VM7PZw9p/AiBpNYrdbw/t0yTJqRtdU9UrfJCUMpSJIgbWzsYe51BcViHzLHeqCRqhZ1YX1tFwNfZBxS9O3NWkAcHqR606k/n/3coKAoV/Y7vQ/OYCZevlrmv3c0GsFh06u3/f4KMABvSWfDHmbK2gAAAABJRU5ErkJggg==');background-repeat:no-repeat;background-size:contain}.ipfs-avi{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAm1JREFUeNpsU8tu00AUPXZcN0nzTpq2KQ3pAwkIAnWHqCoeexBb+AQ+ABZ8A2s+AIkdm266QUJIFWKBkHg1KpRHi5omJGkbJ3bGHj+4M1EQrTvSyGPPueeec++1EgQBxHp+/9mbyuriRZdxjJaiKBD3W+u1+p9a856max+gDO8ebT+WT20Ezi9NZi/crqadvn2MQBAGfpCOpqNru2937vxPIpY6Onjccx3Twck9MBiSU0ncfHirXFmZX3Md9wqCUwiEVN/zaQfHt0vfbBe5uQyuPVgpl5Zn11ybL4/i/lkICOw5niQRGQShoiqI6Bo43W2ub8n3hRtLZT7gTynk6gkCX9gAOxpAnxhHZDwC1/aI1EViJolu/QhKRMHZ1UX0Gr1USIEn5FPWHy+/wTokkrQOq2vBaHZBN4hmY9Jwfr4An/teiEB45ZZDwDiMhoExT0N+sYDCuUkkplLIlXP4/XEXdo+RUhdhBSSfUwtVTUG8MIHK9QVqI7D/uY6vr2pwmCPrkz+Tk9gwARWQ9WxppbXZhNnpw+ya4A5HZi6L4lIR8WyCcL6sTZiAWjWgAmpxkn5+kqTamK6WkCwmERmLDLvjB0ML9ikWXPLFuozYOap3L8HYN6DHdbS/d5CeTVBndBz87FCBLYkNTyIjBQemnIEsSY5lYrK1+UoWcToLMjEHAyIQ2BCBSx/NVh+ZUhrqmEqBebS3WyhdLg0zt/ugAaIklsSGLHCLa6zDMGhZ2HjyGsnpFPqNHnY2fmHv3R5SMymYbROszSQ2ROAY9qHiofvlxSc5xsKKqqnY3diRE9h4X5d/pzg7lnM4ivsrwADe9Wg/CQJgFAAAAABJRU5ErkJggg==');background-repeat:no-repeat;background-size:contain}.ipfs-bmp{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmZJREFUeNp0U+1rUlEY/13v9YV0vq2wttI5CdpL9aEGBZUDv0df668I6n+or0UQ/RuuD0EgVDAZrsKF4AR1a6COKW5qXvXec27PuVeda3bgcF6e8/ye5/d7niMZhgExnK9fbTrm5pbBGMZDkgCyq+VyhTUaT6Eo2ZHJePPWXJXRhez3B1yxmM/QdctXUSCgtV4Py4CvY3cky4e1x5DlLCaGbbzjXDcousG5OQe5HPRSCQPK4PpsEM/XH4WvhS4noeu3JwHGGRiULhsMoKZS4I0GtEIB9mgULJGA0+9DPBpBT7sffvf1W/Lg6OgJufw8C0CRGEXWazUwiiyFQjA8bsjVKjaJzovMD/Q5gxyJhG2cvyeXe2cAuADQNGBmBvLaGuTFRaDfh31lBTWi9pumjbK0B4JQul3vOQpM8JdskOLrdCvDcDjAsjtg5TIkoiKLaokMNR2cnZbqNAMycqG7XbHKR2fMzwO/dsxSwu0BiBJsNsv2LwAJAJCI5ux2gXYbqNetcz5PoORI1cDS0n8AxGW7A+zvEYBKZ2ZlcsEtJLbedMjePBaCTQMghx45ulyWkzxMVUQ2RMQhLfFO16YAqCrixPnm6iqKrRb2W23EfF4cUNSrHg90cr7hDyB33MTnSmUKALVs4uIlROjxg+AsPhGVl3fuIl2tIOB0Ya91gkOi9mxhAal0ekork1ic/kGLBORMxy2K1qS9V1ZQbNThIj2EGh+2tsyOnSai8r1UxMNIBB+LRTTULr4Uds0K1tU/uOLxIrmbNz8XXSrnASSpubG9fbKRyVh1n/zSw29t9oC1b47MfwUYAAUsLiWr4QUJAAAAAElFTkSuQmCC');background-repeat:no-repeat;background-size:contain}.ipfs-c{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAcxJREFUeNqEUk1rE0EYfmZnkgoJCaGNCehuJTalhJZSUZB66a0HwXsP/Qn+FM+9+hty0LNYCr2I7UVLIW0Fc0hpQpSS7O7MrO9MspuvVV8YMnk/nn2e5x0WRRFMvP/w6WSz5jbi/9NxfP693Wp3DrJCnMW5d28P7a+IE15lufR8o1ZEStwPhkWHsWbrZ+eNEPxsuubEF6m0TBv2Q4liPofXuzveulttSqW2UwH+GjqC0horpSL2njU89+FyMwjlTlxOJMTa9ZQHzDQIjgwdom9zLzfXPc75kbnOAswBJTlC2XrqQRMLxhi442DgB4UFBhgPpm3B5pgBHNUUxQKAHs8pHf3TEuFMetM9IKr/i2mWMwC0SnuSFTG2YKyppwKYVdGO7TFhzBqGIenVeLCUtfURgErucx5ECKREKBU4d3B718PHz6cICGT/1Qs8qpQtGOdyhtGEARWDQFqQJSeDL98u4VbLaKw9IRAJPwjtoJGlVAoDQ800+fRFTTYXcjlcXN2g++s36p5Lzzlve1iEROa8BGH1EbrSAeqrjxEqicHQt8/YSDHMpaNs7wJAp9vvfb287idboAVkRAa5fBYXP9rxO4Mgf0xvPPdHgAEA8OoGd40i1j0AAAAASUVORK5CYII=');background-repeat:no-repeat;background-size:contain}.ipfs-cpp{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAfJJREFUeNqEUs9PE0EU/mZ2WgqpXX+QIDFdalVslh8NlAOQaOKFAwfvHvwT/FM8e/U/MOnBmwcj8WD0ACEGghIkbU0baaEthe3OTJ0ZWV26q37JZt68ee/b9733yGAwgMbL12/fz+azbnAPY2Nrt7Zfqz9JMrYZ+J4/e2pOFjiciRvXlgp5GzHonXk2o6S8V6k/TjBrM/xGA4MLyeOSPZ8jkx7D+uqCU3Amy1yIYizB36AlCSkwfjWDR4uu40yMl/s+XwjeWThQQ4Z6QNSnSkYykcDXasP4lmfvOZTSF9q8TDBEFPbN5bOqCglCCCxK0TvvZyIV4CIxbgpC+4gm/PUmFCIE8iJPyME/e8Lon9j4HvyHYLjKSwRCSEUgf9+15mFbx8QS6CZJMzJ9SlBCwX3fJDLG4PX7ykcwkmQmJtpEhWa7g1dvNlSwjwelebz7tAXLolh0p/Fxe9fErK2WDFGEgKjxfNjegX0lDTc/heNuF99/HGEslcKXwyoazWNDdlCr6+DoJgrBzdI0T9rYO6yg2zszMlaKM3Dv5OBzbuyZuzm1B16U4Nzz2f3cFOx0Gq12F9cztpExncsqYoaHpSIKtx0zJdVIFpHQ6py29muNk1uTN829o/6SHEnh80HFaE6NjmLnWxUJy1LyTltB3k8BBgBeEeQTiWRskAAAAABJRU5ErkJggg==');background-repeat:no-repeat;background-size:contain}.ipfs-css{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAk1JREFUeNpsUktvUlEQ/u5DoCLl/RAKKKUvWmIxjYntQtcu3LvwJ/hTXLt16coFC2PsojEaMKZtCqFaTdGmjbS0CG3By+vei3OOBSGXSU7uzNyZ78z3zRF6vR6YvXzzPrMUCyf68bB9zO+VfpROn5hkOdfPPX/2lH/lfiLidztX5mN2jLGG0rKLENIE8liWpdzwP7HvqJqujmvudFU4bFY8Wk1FZsOBtKppd8YCDNu77CZevd3gflfTUFcUhP0ePLibiIR9rjSBpgwAfe4dVcV6dhtep4PH5msylGYLrzeybErcT85FYiH/CyPAf74gObC2vMhzsiRhPhpC6eQUM+EA1pJzILEnjRSuJsju7MJqsUCSRei6Dp3yXqcdGlHZ/rLPazQWGCn8+6YW4pAkEW0SjzUzanWlCa/LgcR0lNfovTEi6lcIkzesnM/R8RlN0INGp3h4DHoDsE5YRvQyiKiRSMzikRAOS2WoqoZWu41K7RwzlOOAVDMMMHhIGvFlRxJFrKYW0ep0IYgC3SDh4b1lTJjNfENsrazOAMAw680mPuW+8lFno1P4XDigRhOiwQAyJK7TbsNS/PaA7giAIAhYz2yRgBIfsVA8wIetPG6FAqhdNrC5u0f+TUyHgyMTDDToEt/ftQsEvW4EPG5OZcrvw0mlimarTXkPfpXPcNlQoGtjACgpryQXsPNtH/nvRXqBJpoKHMzGNkNB0Odls7LNyAYKpUq1dt1iuvB7fRDp9kr9D1xOFwkpoksXusmXaZWFn0coV89r/b6/AgwAkUENaQaRxswAAAAASUVORK5CYII=');background-repeat:no-repeat;background-size:contain}.ipfs-dat{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAfVJREFUeNqMU01PE1EUPe/Na0uptmlASg3MoiZgCA3hQ8PHAjbqwsS9C3+CP8W1W/+BSReyYUPwI4QAVkAgUEgIbVIg1FZb2pl5b3zv2cHBjsaTTOa+e989OffcGeK6LhTevFv+OJoZHPHOfrz/sl86KpWfhxnLe7lXL1/oN/MSZqonOXU/k0AA6lfNhEFIrlAsP2PMyPtr1AscLpyg5pbtIHErhqez4+awmc45nI8FEvwNaiQuBHqTcSxMjJhmX0/Osp1xr878FxWEzwMinxAzEA4xFIpnOjedHTKpYbxW4U2CP4j8uWxmUKsghMCgFI2mFe9QgHZj0Ba4yhFF+KvGJToIRLuPC/efnjD6+26wB1Lq/xgbSCBXKeWJG/OTdky8cWTdT3C9RmWSGk2XCLlWo4xTNbfN5qh7PpXM72GjZeHt0gpq9QbmH4whGb+NpU/reDQ7hcWVVXxvXOHxzCQopQEKXKEbL6o1ZIcy+LC5g62DY2zsHeC0fA4zndIrHOjvg2XbAQRSfsuy9XxC2qzi/H5B6/68W0AsGkW0KyJPBLbDO0fg3JX/CUM81i0bD6WKe6j9qOPJ3EMcF0tSNsFA6g6alqW+VtZBUL78Vtk+Oqne7U9rs5qOQCjSheJFBeFIFOfVujSUYu3rIc4uqxWv76cAAwCwbvRb3SgYxQAAAABJRU5ErkJggg==');background-repeat:no-repeat;background-size:contain}.ipfs-dmg{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAn9JREFUeNpsU01rE1EUPe9lkk47yWTStCmtNhFSWxos2EXVhSsRcasuxYV05V8Qf4DgD/AvCK5EV1oFI7iUBqmCNdDvppq2mWSSzEzy3vPOpFFq+uDNfR/3nnvueXeYUgrBWH1/9/NE7k5BKRnuRcfF2qdnmJq9DeF9tQ+2isuMsxXGWHh/a1mEVsPJSI5fSU3OPEj291IIlN49RXz0KqzEQjIeZS/L5Y/3wPGhDxIM/i/A7fZWgVG0t5EaG0ZUa0JGM8gvPrZmLt58QYwv91mfAqCIE0sAqgumBFITGQzpUYhuF0KfRa7waDyXXXolpVrsh/0tgSLDr5I+wUZo1UHCSkAficPzY6juFSmbRPrC/azjq+fkcO00gAqoU7B0ETKkfWbuCTjTYeq5oESAauexcTScX+ZACWFm0YQSLZKhHdr67+/wW0e0dgjYo3sCEXXybYtBDVSHLp2es3IpsILS24c42lkBg6DzRjgRzCDZ/xr0GNRJwwYiWgzt+hYMawleu0V3wbkT+kUirOc7IGJAz68R/Qak1BAlx3hqASPGBJRXpXOv58dkz3eAgQoOm4hyj57NgZm0MHvpBmK6QdUdg/DAg9cRkhicBSDaKJdeo1bdxmR2DtWDDUxl51HZ+QHTysD3XdQO95Gfv06aeGcAdBrY3Chi8lwO3768QWX7J5q1XWyVSxgajiOXLyBG2hzurRKV9lmt7ISNkkjo6HhNyjoK+2gXRsKE57ZIE2ot10Z1fz0Ue4ABVw3NMjnW14rInh8jTYywoTg3EOFpOM4mXNfH9PQUfGlrAwBOs3I8ljbtuMWhRWzIIPrkn+GcYcgIWEowbZ+0qB334/4IMADESjqbnHbH0gAAAABJRU5ErkJggg==');background-repeat:no-repeat;background-size:contain}.ipfs-doc{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAppJREFUeNpsU79PFEEU/mZ39vZu77g7DokcP04BBSUmiEKCSCxs7Ei00JAYO2NlTKyMrX+CJhaGwopSQ0dMtFEsbDRBgiZEQIF4IHcg+2t2Z8eZ5QDlnM1mZ9+8973vfe8NEUJArfSNhzPG0VIfeIiDRSDkw1cWVt3N8rhG6SdSO2Gvn8dfuueqZwuNZqk3Jxg7iNcIfBbgXD6ZC8u5qffzX8eoYeyDxC77uygKhcouovgVUQj1H4YB2ovNuD9+tTTU0zMVBmG/+C8AIYh8F361DL/yE5HnADKYlVdg6MDAmW7cuz5WGuw+PsWDYGAvbL8ECFUt4K7/AHd/I9c7BLaxinD2Ld5Zo7g78RLuRhlBS2cpWbGfStfhfwCEpK0nUjCbWuGsLciSOELPhkq/YgdY3l6HsLfRcLYf+pHNbH0JigEPkLAyMsiEJ7NrqQzM1i7wyhoMZqOhvQs6Z0ovXgdAJACRoulEg5HOwrOroKk0zOY2BDtVpTF0CU6kLkQJXa+BNEoG0lMSsBBKQXWNQktmoGcaYeSaQCIVWOvUYQAiWZFQtk5mSMoSzEILtBrTfEcviC5bwVwQmoh96wA0ic5dB57ngeoaTIPCdb34zDITYNLOOIeVSsW+dQC+7+NSWx6jJ4tY/rWNV7PfcGv0tBoPTM7M4eKJVgx2FTE9u4QPS6x+kHzfw/mOAjarW2hJG3hy8zIceweuY+PRtREMdzbjzcd5WBqPB6xeRGUMGRzHjWvMmxQ7tiOF1JBN6FiTd6Sy9RuFbHpX7MMMqOD088Ii+op5OUAO7jyeRGfBwrF8Cg8mXuDL4neMXzgFwhwZz+hf7a9d5yu3Z6DTPjVQIY9k7erO7Y63Lvc8ErEeyq6JaM6efjai4v4IMABI0DEPqPKkigAAAABJRU5ErkJggg==');background-repeat:no-repeat;background-size:contain}.ipfs-dotx{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAndJREFUeNpsU01rE1EUPTPzJk0y+WhMStW2qdVWxUVEQUF0I+4ELQiC7lz4N9z0T+hG9wrdZKUgLqulhrbSag1CKpT0g7RpYjqZmffle5NEKdMHlzfvvXvPPffcO4aUEno9f3Vt4dTp+BXOe+fB0u/NbVpv7h89NU1j1TCM8H7+xY9wJwPHZMbOjRadLAvE/2gToJTiTPx89k+OlVd/LT+0TPIPpO/SzyQk40xCMxBSZ9Z3CoAx5DOjeHT7SbE0XSpzwa8OWB9jINELolQg8AR0EgUKn1PIlIWpkUt4cPNxkTOU12trs8p95RiAXpqaztqou8q6SKQJJmZSqGwsodFsIJk1kcyLYv7IeafcLx4HUNkFF4jFTExMZ0B9DrfD4HUEusYhWs4GPEJg5wly/tBYRIOeDhpEwlS34xcyajdQr3UwOT2MlJOEBRuGNHWp9AQRVXDfQiFV/U5GBSiQ5p6ngBEa5z3fiIhC6g6IMDBwOdoHPkYnHPVyhN0tF7E4QSpr94CEOKELffq+y9Bq+DCJ7rWBoQQBVbPR2O6G4OlsLASJMtCZfQqm0NP5IVWnamdAkUxbyuIYtD7wWegb0YAzAVMkkI6NwPM9xEwHloyDGAmk7AKS9rAS0FKOdugbYeAHPu7OPEM+MY7q3hIKqTFQHmC3XcONc/fxdfMDrk/ew/edzyhvvTmBAddocVRqH3Frahau56qpZDho7+PnTgXffi/gbHYmLEvPSIQBp5JU62sYz13G609zKBXvoOMdYn2zgm7Xg2MVML/4Eu3uPgxhk2gXmNl8v/i2pcXTP8tKdTEcbWLZqDQXwu/l6pfwbEnSGsT9FWAA4mdHv2/9YJ4AAAAASUVORK5CYII=');background-repeat:no-repeat;background-size:contain}.ipfs-dwg{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAoFJREFUeNpsU0tPE2EUPfOg006hD4rQh8WgbCSwkKgbF2owujaCiQsXxpX+D6MmbtXEsHCLmIAbE6NLo8YlGIxREIshIqVl+mQ6j8/zFVCb4UtuZua795577rl3FCEE5Bl79vPd5LHYiOP7cH1AUWi85ytmvlas1bJ9E5ryBntH3BpuP/X9i7ovkluuiE8N9SDepaLpCcRCCqa/VDCaMuIjSWP25Upl6n+QDoCz6Yh7KKzh3sI2LuUimPtRRyaqodj0MDloYiITSTi+mH29Wu0AUf9CsZPJoW5czJl48LmCc5kIKo5Al67B9gUGYxrun+5NnMlFZ+GKiQADj2a7AquseLIvjMv5KMaSBu4sWVir+3i8VIVKYSby0UTdFU8Znu8AYBHQgVOJEN5uOXi4UsdawwU0FSf6TaSoyw6DRvukPkgGWpDKy4F8a3jImCrqFDFn6rhKPR4VGnhvOTAY3WLcjifcQAsqRfhUc/Gq1MKNbBh9nIAMDjEppocxs9HCMktfGTCwP/oOBkUKNk/qF3pDYC6Ktk8RfWzyaaoKrqdDaBDwya8W1m0/CPCR3kFy7CcnmWQRUJqcRJFUKtTnPCeR71LwoeYF92CYyVnCFZpCTrRtCv5to2St8SOrKxiPqEEA4fkYT+mI0rdoeUiH1XZVuQPpsIKqw2QmfifTsnOABiWySlH9uU0Hh2MqjsZV5LtpPSoGeN9rKnhBX7ehoOSLIIPfnGONXGMMWN7xUfVldYDbjM3mrh5HCDgS17DhHgDQcIU+XbBxnDTn1x1UuQcJ9iv7l5Q5e1zLGri92EDJFnoAgHtcfr6wbbVXUqq193+0z97n3UJt1+d51n7aHwEGAAHXJoAuZNlzAAAAAElFTkSuQmCC');background-repeat:no-repeat;background-size:contain}.ipfs-dxf{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAo5JREFUeNpsU0trE1EYPfNMmtdoH2kDNmJbaVFcaBVFpAsREQpFwY0bu3HjQnTj1mVd+ANcuC3qQixmry6E0kWFVIQ+bKy2tbFJm3emyXTujGca+4DkwsedfLnn3POd77uS67rw1vC79ek7fZEzpu3AYUqS9tKQGZPLpa3VXP0uFCmJ/8t9OLC3q/uJbcs5bkIybvdHoMsSbLKENRmvU2WcNnTjRFD7ML1WGSPJHI6sA4KRWMAWVDPxLYex3iCmfpuIh1QsFSyMxQO4GvXHHwOJ6XWSyIck8v6HQsnjAxFc7vTj2VwBg4aG78VdBHQFCk+dbVcxMdwev9gTSEC455sIBOu2KLsoJFzqasP9vjCeDBlYqzn4VXXwarGKZN7Crd5QfLDT/7KpBM84c9fFUFjFp2wdk6smflRsKKqMa7EgfJJ3Ac2OKlit2pEmBTQfngdpnupoU7BUtRGiiTe7fXiRqmK+KuDn6TpvYogmBRJcrOwIJLIWxmM+dOsyLKryQAaJpjJ1/AxrGO3SqdZt7kKZJrzJWBg5piHENuY8vV6e0UOye1TyftvC5l+gZB8SHJTwpSx4q4JeTUKaxhXoR57h7Rn+3iFolJ3xvPhab6HgJG/pJ7jsNP4sUX+jZiCgEsWd/DjH5IrSYpBUAr0yHpzSoXKOP25a6OBhndh0zcX1qIYM2RIbu6i0KiHD5B/GTMHG03kTGpEL7H80wHFOWwhqDZ+SpkBOtCDYJDhZE4gRcKNbYynAqbCMbXpwpVPFbEng0aKJGbYzK1p4wIegLlcEPmdt+DjXbzcsxFlCynRwwVAwW6hjqeg0Zt521SYCWCJvbe0Un29UDx7Hgrs3IEitHXkw3jOv2fl92D8BBgAJeyqBh90ENQAAAABJRU5ErkJggg==');background-repeat:no-repeat;background-size:contain}.ipfs-eps{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmlJREFUeNp0U01vElEUPfMFCEVArdoSqEA0KV246UJdUJM2Lo2JK/9FjXu3utJqTNz4D9worrsQExbFpAFT0TYp0CZ8pIAiyMfMvBnvm2Foa9uX3Lw7c98979x77hNM0wRf7ufPsq7Z2SQYw2QJAkDxQalUZa3WI8hy3gmZr15bu+z8kILBkCeRCJi6bufKMji0NhwiCQR6iitdatTvQ5LyOLLEiWcYukm3m4Zhmbq1BX13FyoxuH7xAlbvpqKRK1fT0PWbRwEmDEyiy1QVg/V1GO02tO1tKLEY2PIy3KEAlmJRDLXb0TeZL+n9g4MHlLJ5HIBuYnSzXq+DlcsQLk/D9Hoh1WrIUjlPcpsYGQzS3LWoaBhvKeXWMQCDA1D9pt8PaXERUjwOjEZQFhZQp9L2yERiqYRCkPt/z58ogTGqHQLE1BLgUmC6XGD5AlipBIFKkbhanKHGYLBDqQ4ZED0OAbfLlo8OIxwGvhVgyTHlA3xkomjH/gegBgDURMv6faDbBZpN+/tHkUApkdTA/PwZAPxntwdUyjYA/+ZMqJHjLgM9iv/6zRt2GgMaIE21aVIjnSm0DGPfmhzyde0UAE2Dj+p7urKCPvkZku9eJILOSMUnkvVhIo7GYIB3xSKYdhoA1erXGVKXpvFxZwdBonnD68PQ7YEwM4O4xwMPxc8RYE87g4FIcz+kvfmnA0YzIJIy77/m0OCqsTkkCTysKPjJG3viLei63Gm3kCO6UWqcMejjxecMPmxsoFKtYop6UNirYL9Wtc5OHqzznIXHq1na7OfMJROcK8a6O7MjW7nfzZdrd7jzT4ABACh3NGsh3GcdAAAAAElFTkSuQmCC');background-repeat:no-repeat;background-size:contain}.ipfs-exe{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAo1JREFUeNp0k8tPE1EUxr+ZzvRJO62lUAQaKIQ0FVJFjBBdoIkrDDHuXJi4NnHtX+HCjW408Q/QmHTRaCRRohIJifgiiBICTQu29mHfnc7MHc+MlECKdxZz595zf+c737nD6boOYzxJLC6Nhwej7e/24HkO779s7G6mMjcEwfKZ21+/d+em+RbagaFev28qEpZwzKg3ZckqCPH1nfS8hScIdyhBe6JqTG3PfyTTeLrwFhvbKdy9/xi5QglXL0yGJsKDccZY7LDIAwWHpSferWBh+RN8ni4UylVER8MY6PHj0uSpUK0hxzfTmWsUtnoEwO3rer64jEyxim6/Hy67DXaHExvJX3jw7CX8XjfORUdDlOohhU4fAVjILCPbm9V1yIqK2FgYt+ZmsZcv4lH8Nb5upXD7+hVMjIRQa8qeDg8UTYPU5cTcxSk4nS709XTD53ZhpD+IYMAPj+TBz93fZiz5oHV4AP1fGdlyHZIkIZkrI7GyhnK9CZXy+Aig6p1+HQAY003AcF8AVtGGfLWG9XTO4MLZ5cL0WAixoT4zVmPHADSiMo3hzHA/xgeDWFjbNg8H3A7kKnX0koEcPdTu/ylgRGZgOjNv38zoSXC8BZJDRKOlwGEV0VJVGM0y4joAPO1spXbx6sNHeD1uRIYGUCxVSRlDt1fC8rfvcDnsmJ+dOaLgoAs6AVLZPJJ7WdhEkUyT8GJpBflSBcVKDTvpDBw2GzQqQT1OgaZqUOhtFQUTUKnVTVWNpgy51YLVKph7sqKYkA4A1ScEfT66vm5kC3+ofh6Xz59FQ5bpkvE4QW3M5Apoyorhl9ABIKnFgNdTOh2NkJG6WSf9eRBJtmFwLDJmriUzeaOkYvvcXwEGAIVNH6cDA1DkAAAAAElFTkSuQmCC');background-repeat:no-repeat;background-size:contain}.ipfs-flv{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmtJREFUeNpsUl1PE0EUPbssLYUCXdpaC9gWoSTgAyFigiRGY+KjvuuTr/4A44MP/gx/gMYfwIsan0RjIjGiJIZgSIGFIoXSD0t3Z3dnd70zpITazuZmJzP3nnvumaMEQQCx3jx69SV3a3KWMxetpSgKxP3m242Do43SQy2k/YRydvds67n8a63k+FRSn7l/bdg5tdsAuM3he/5weDC8vLdqPLgIIpba2niux52mg//DqlsYSg3iztO7mczN3DJ3+ByCLgCBH4hOFEF7cDpzPCRyOpaeLGXSc2PL3HbnW3XaRQCPEgWI2MsRVAVqrwbX9bHxbhOKpiJ/bzpDOr2k68V2BtRNzMtqDEqPejY/4zSGjb54BM0mQ8k4xsDoIMauXxnqYOD7PmwScP31d0SS/eAuh1lrolFpIBQNQw2pqJdqsAlIceB1AJCIkkE/FZskXDQVRXw6IYHiE0nBEcaPXSSvJnGwWkQXAE4acAhbxPMJpOdHweoMhc9b2F8zwKizbdlyPLVH7QLg+JKBYzoorxzjz3oRzUoToaEw9KyO8XQW5AE5jrFT6AbAYVVNxCZ0Ka3So+DSTAoDiej5ywTySbls1OEDobhFlMcXxrHw+AbINEjNXgb7y6BndLhk8cRkHHbD7g4gEhiJFxsdhrDqaamBaDKKerGGSKwPI9kR9EZCaNA5ubE7A5s8IFhsrxQkgJhZoa/06xC5xRz2v+3BOjFlbqcGlquxsondT9vY+2pAJdeZR6fI355CgQCN2A4O1w7gkQ7cdLUOAKdhV6uFSv3kd/n8mT68eC8dKWLnY4FsfeZQh7nVVt0/AQYAsf5g+SvepeQAAAAASUVORK5CYII=');background-repeat:no-repeat;background-size:contain}.ipfs-gif{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmVJREFUeNp0U0tPE1EU/trplAqlL0laiw40xASByEJIZFGVnSvj1j+gWxNXJq7VrbrwF7h10cSNhMRHojEuACVBKmH6SJQyJeXRxzzv9dyZPiCtN5lMe8853znf953xcc4hztDzZ1+C6fQMHAfd4/MBFG+p6h/n4OAeAoGNToi/eOm+A50LKRaLh6amoty2vVpZdotNXccMEK3LwZxa2bsDSdrAqePv/mLM5tSdMwYBYqyvw9zdhUn/L59P4OGtG8qlZCoH254/DdCdQBCxqZu+ugqnWoW9swN5ehp2NotgIo6bGQWGtaS8+vQ5V9a0u5S+1gfABEilAqdUgm98HDwUQkDT8JXoPPq+BoM5kCYmFT9jryn1+hkAt7heBx8dhbSwACmTAUwTgdlZ/CVKJaLnI1GD8TikZiPSR8Gxib8chH95mZTxgwWHwH7+gFMswqcokIRbjMO2HDCnZ1VvArpjEmnKZc8+cZJJYGsLsMiZ8AgwEqaY6Mb6RQR33JFhGECzCRyfAFXNu9v+RVNRZWIMuDJNuYMAaDycUFGhCOgtuAtFVDA83G5A8TrFDw+F5QMAxAKJJxz2xnW3RPJGbm+rCyjotZetH4DGzaSSeDA3h4Zl4R0JOEZWTpIzF4n/m995bNdqZwB6m0gFft3Ak6vz+KYWwFsGlqIxXItEcDt1ARMEtKdVgZb+fwA0G2C2hXM0ZTZNRcSf0b1pmXi7uYnjI+Lfanm5fRQsK8BIxKcrK7i/uIgP+Tw+FlREqHN5fx/vyU4uHBE6UO4gDWqk/JFaLuMxcXeFk6TuJ90V0HOk1in7J8AAjmgkPfjU+isAAAAASUVORK5CYII=');background-repeat:no-repeat;background-size:contain}.ipfs-h{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAbRJREFUeNqMUk1Lw0AQnf0woK0ttVqp0hwqVCl+UBERT94F7x78Cf4Uz179DT14F8WbYHtRkBYRLNqDtdaPZLObuLs1NGlXcWDJZGbey+x7QUEQgIqT07PL5WKhHL5H46J+22q22vsWpbWwdnR4oJ80LNiz2czGUjENhvj4ctIE4Wrj8XmPUlKL9nCYcOFzE9j1OKSTCdjdrtiLdr7KhVgzEvwW6krC92E6k4Kd9bJt57JV5vFK2KfRQRV+RAMkzxglYI1RaDy2dW1rpWRjQo5VGicYIorWVooFvQVCCAjG8Omw1MgG8AM0uSBUDSnCfk/IGCHwf3DCD/7UhOLBrFkDuep/hDUSSCv1iYo4rIfqGwmUSNJjfYbBcQKhZw0aBMA4B48LwBhBt/cON80HmM9NQ6fXg/Wlku4TwmNWDzaQqzHG+0PSKod5cH5Vh2RiAhYKc8DlV1UPSyuFMGygVlMg1/P6BC6DqXQK8jNZDXAYA1f21V34wMXYFaiyVw0rJyzLgs3VMkxOjGtix/V0XWChZ0cI2i/dzvXdfTd0Qf91BMPrhyNzgKfOmxaWypqaDXHfAgwAtCL8XOfF47gAAAAASUVORK5CYII=');background-repeat:no-repeat;background-size:contain}.ipfs-hpp{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAehJREFUeNqEUk1v00AUHK/XKf1yZdESVRBXjRSRFqMQVBA5Ic5I3DnwE/gpnLnyG3LgXglx4UDDLZS0RWkDLiRxSusk9u6GXSembmLgWZbX7+2bnZl92mg0goo3b3ffO/ncdvyfjHef6q2Dlvs8Q2ktzr16+SL60jhhZ69bO8X8ClLC7w9XdKJVG8fuM0r1WrJG4gXjgqU1D0MGc2kBTytl+7a9XmWcl1IB/hZKEhccq5aJJ/e3bTu7Wg1CVo7rNLlRhUh4oMnXoDoyhoHGyWmUe+QUbELIa7W8CjAFlMzdzeckCwFN06ATAn8QmDMMMGlMuwWucpoCHNe4jBkAMenjYvRPTyi53JvuwX8AplleAeBcRFrH6rXIxLim9I/pi3QA1RhKaYxdjkN8IwalCMIwWs9ljMkh0wzk+9M7w179C3LZNXxve2h+c3Hu91HeKmD/6zHOLnw83ilB1/V0CeqU3Q81LC/O41b2Btx2N2JVP2riR8eTUxmi0TzBwrKZMsqMoz8MsDh/DWuWhUBKURLKxQIeOMWoptYPnS1c+INZBkwISomOSsmBZS7B+3WOzZvrKGzkMAiGqNy7g+LmRkRfekBnANy2163PZXrSbrQ6vch19Xz8fPDHyL39QzkHBKedXjfu+y3AAGU37INBJto1AAAAAElFTkSuQmCC');background-repeat:no-repeat;background-size:contain}.ipfs-html{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmBJREFUeNqEUktPE1EU/mY605a+hhZTBNKRDApNrWIRA4nEBUZdmCgLNi4MK5f+FNdu3bFv1J1EXODCR1JJSMTwpqUP6NiCpe10Zjz3hj5Mm3iSybl37jnf+c53jmDbNpi9eb+6Ftcisea909bWNzNb6dwzSXKkhIt/r14+515qBqmDA8HpqKagh53XaopblpIbe+knDpFAhPab2Dw0TKvRK7lmNODzePBgZlK9oUWSpmVNdpIU8T+jaMsyMaD4MDcZVa+NhJMN00w0n6V2nN3yQgdHWZag+LzYPTomIAtT0THVtPGanmb/BbjwLFkvn2IttYGYplKyDzsHh7gdmyAWfh5zVq0Guhg4RAHFUhmfvq3j134aXo8bd+ITnMFOOovU5jbGRoZwNxFn1cxuAIcDW/sZDjA/c4u+BNxOJyxqaenpI3z88gMfPn9Hv98HQZS6RazW6kjExvFi8TGdDSy/W0Emf4LS6R8sv11BmfzSwkPcm74Jo9Ei0GZgmkw8QCOao8OXcaz/5vSZnPdnp3ApqBBLkWJE0Ci7ASzbIhCLLQ1E0iOkBDh9NpUgiUejo8oNuJwyn0YPABtn51UYFFivG3yBGCNZkuDtc/MW+ZQI3OrYpBaARCKufk3B5XIiWyhiL5ODp8+FfFHH+KiKSqWKUL8fC/NznGlPBmz+24dZjKnD0CJDcMoyW0SqXuMtHBFw7rhIAD1ErNUNafxKBNevapwu65NpEQ4FqXIA+RMd6VwBP3cPSERb6gLIFIq61+UqGWaFdcrVt/lmAuWjAi2aiMFwmOYuIJ/N6M28vwIMAMoNDyg4rcU9AAAAAElFTkSuQmCC');background-repeat:no-repeat;background-size:contain}.ipfs-ics{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAhRJREFUeNqEUkFPE0EU/mZ2dra7bLNpi2AxQFKalkJrohICiYkXPagXrx78Df4K48GDBzmQePLMhUODNxQ5ciEkJVqDtJGmMWrCATRbd2ecoS5u3aovmezsvu9973vfPiKlhI4XL7c2r5YL81LIELEghLA3u/udxmHnPmfGW/Wuv+LpwwdneRYBx7PeWK0wOYYhcXxyckGV1fdbnbuMsXcklqPRJQxFMKz4RxDCtVO4s3xlRjWoB0FYjlQPEEBieChwKCRGMx5uLtaKs1P5ei8IKlGa/YkXMXYtlTEDlsnw/mMXhBJcqxSK6vlcpa4PEpCooUyIqs5M6hG1o2CUwqA091cFcYLf/sjzcX75EiQIojI9779CTYR4jwTBf+r7GAwh0AxCiL6JMT/04vQ79u8aI2O/7Jzg69o6Go8ewycUahtBpADhHKLnK/eVbkMdtROWIv80NQ2sPhncA9Htwn+9hZG0rY6DzFwJl+7dhs0ZstUy8rduwPS/wd/ehmi3kwq4zTHiWUgXp+EuL8FvNvFl5Rn4xAS86iyI2kY3n0Mv48ByrOQmancdi8I0Kcj3U5iuA29xAelKCUHrEIayzltagG2E4IwkFaQgSC6lYI09iN0d8It5uNV5nG5sgJdKYC0G8WoTOZvBISFNEBxnsuzD3GX4vfDsszzqAu0jkJQDedCGbB6AWg54pYbPo+NGVPdTgAEAqQq70PytIL0AAAAASUVORK5CYII=');background-repeat:no-repeat;background-size:contain}.ipfs-iso{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAjlJREFUeNp0kstrU0EUxr/k5qbJzdPYpGkpsUJoA2q1oLjTdiGiIC5cuXHlxv9BEOrStTvBnQvRrSAIsejCrlqpsURq2hCJNQ+TNLm5uc/x3MmzJh34mDNnvvnNzOE4GGOwx8+t9XQkfn0VE0Y5/7Z+kHm+dvOhtd3P9c/xwNZh7nWaMYtNUmX/Fct/vlN7/8J5aRRgyzm8xzpRDjGE2aVH4VTqdnoUYg/XkEhmy+Cx3DhA5tMzdFolvg5Mx3Fx9SmH0JIg79Zo3j4GADMIokJTKtjbfAKXU4Y/2NvSfyH75TFOxa9Cmr0XnlPFl5ReOQ6wNMDsoFX6AElqQlNV1KsOuNwS/AGFjEUIDhmn5+/DMM16/9igBowAzFKIswPJr6MjlxFP3sV04gaP7RzMPe6xvWM1gNUBM2UKYlBau3QghGphg29J3gDlLLilWNdD3gkvIIDRhD9yGe2mCV0V4HFXuCxT5Dlv8Dz3sIkAs03FalDxBMQSt9BRBMhNncuO7dyU28c9tnf8C/Q0ZtR4GImeQSj8APLRH772BWcgiFODffCv/t8H9tO0v3RjV7VqkeeXLlzDfvYjj88uXhl4JwIsrYxmLY/M1gYclIvGE9jZfNPrSCD3/QgLyeWTADV6wW9AryIcCkB0u1Aq/oCPumlufoF72vIheaLDr4wCLIOqrYnULA14PSoqpSJEAUilZrD77Sv3LK+cI0+Be8cAbbmAOrob0agtD491LYfkoqvnyZLsWRkA/gkwABL4S3L78XYyAAAAAElFTkSuQmCC');background-repeat:no-repeat;background-size:contain}.ipfs-java{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAjxJREFUeNp8U01v00AUnNiOEyepQyhQobRBSlVIoRCBEPTAjQsSEneE+An8FM5cuXLNoQduIAE3qopKNJAIIppA2jrOR93aa6/N8yZuUxyxkrXr3ffmzczbTQRBgHC83nj3ca28dD36nx6fvnzrNNrdp4oibyUmey9fPBezEgWVFuYLdyvlPGaMY4fl1aRS+9pqP5ElAkmcnknRwuO+Nyt5u/ETYfyj9WrpZnmpxn2/Ok1Swn/GvtnH5k4TLue4kNfxoFoprRQv1TzOb8cAIu3+ZD7oD/Hm7XuxzqRUNDtdkuLiTmW5tFxceBXlnXgQTAORSMt2oGezUJJJrK9dFWdEH7Ik4dB29LiESeUEJXd7/dAT3L+1ivlCHr8NEzutXTBvbJPPSdO/AH5wysChwM/1HzCGlmAzOrKxu2eCud6Z2Jke2MwThpUXL6Nn2ZAVFTlNw70bK0iRnGAq9qwHtOmTRpsx1NsHyKRVnNPnoMoK9kc2BjbD4vk5JGV5NkBoEPM4FFnCteJFWOS4ntHEfphQyKaFTWFLw704AJ26ZFx/ZEEi3YyY0O1Dmr4EKTUHA8hUnS6siI0DEHLYog+b28RCRuNXR/iQUpPUEQ+NVht6Lodnjx+GXYgDSFRnq97Ed2pXSlXhUSeGhxYc5sKlNXM5DGLR2TMwfZVPAIi+otGNWy1fEZUKeo4qc4ysI+F8VksLIJfYcD9QYgB/DNPMptWBlsnBIS86xmDMTBo/PWd0LB6VZfdEbJT3V4ABAA5HIzlv9dtdAAAAAElFTkSuQmCC');background-repeat:no-repeat;background-size:contain}.ipfs-jpg{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmlJREFUeNpsU8luUlEY/s4dmMpkWxRopGJNNbiwhk1tItbGtXHr0hcwmvgOdWld6Bu4coXumtREE3ZKu8FgOlC1kIoXtC3jPfdc/8PUIpzkBM7wf+f/hsts24YczuerGUc0moBlYTAYA+i8sbdXtAzjITRtq39kr73s/Gr9DTUYPOeamwvYnHdrdR0SnDebuCbswJGqpX+Uf92Hqm7hzFAG/4TgNr1uCwEJ0trcBC8U0Kb1/PQkHt9JxSLnL6TB+Y2zAIMOJBGLXmtsbEAYBsx8HnqCGKVScAX8uHf5EpqmGXv18VO6VDEe0PXsKABN8+AAgiabmYFNNJTDQ2RUFc8+Z9G0OPR4PKYwvKari0MAgiY/OQGCAajhMNR4nDZMaInrKBGl70SPMScck1NQG3X/CAWLE3/dAWV5hRRVIJxOWNksrP19sFgMqqAebUGYHMI6teq0A9oTVAhqu2sfbYYjsL7lCZ3683gA70T3TK7/B4BNoO020GwB9TpwfAz8LgMtWn/NkV8EHgoB81c7nYwCyBZlEVkHcqMTKFnkmehJTOPvEfCnKi0fAyADJKfXC/h83TaZTJjaa5lANLpOFqAXtlEAorAwO9u5syT5UxLfU0e3o1FMu1x4u7ODYq02BKAMAVSrSNLrK1MhLPj8mNF0vFm+C1ZvwKBwXXE4AGn1WAASazESwUW3BzUSMeJ2o1Aq4sPurvQYSRLwlhRR6mSaYyi0WlpAJrFRx3ouh5/lMt5lv8BLwXp0M4lSpYL17e2uK5wP6lj/c2ZPn2RI+YT8fDvqoyegVLyfG5kBKaQQOfvF2pLc+ifAABiQH3PEc1i/AAAAAElFTkSuQmCC');background-repeat:no-repeat;background-size:contain}.ipfs-js{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RUQ5ODY5Q0NGMTE4MTFFMTlDRjlDN0VBQTY3QTk0MTEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RUQ5ODY5Q0RGMTE4MTFFMTlDRjlDN0VBQTY3QTk0MTEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpFRDk4NjlDQUYxMTgxMUUxOUNGOUM3RUFBNjdBOTQxMSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpFRDk4NjlDQkYxMTgxMUUxOUNGOUM3RUFBNjdBOTQxMSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PoT8zQ8AAAJdSURBVHjadFNbTxNREP52t7S0bktbKFAvTUVaw60YqkExUTD6oD74qC/yD/wp/gh885XEEI0RAyYQUiMpIBGMkYR6o23abi+73e2uc04v1LROMtnZPTPffvPNHMGyLDB7sbJ2ciUSli3U35smkK9t7x9v7n2dD/g8KUkUwWqeP3vKz23NxJGzgwOx0RC6mSgIo+WKuvP56MeUzy2nJEk8PWsGJVVTuhWbpgmHw47FB7d98Wg4mVWK52o1sxOg3Va3PmFp+Q2PdUquaFUM9/vw+O6cP3bxwm46Xwh1ALR3/vL1e+hGjcc9koScUsTSq3coVDQsXJ3wzo5HEs3clgZNMTVdx1T0Ep7cn6//QRQwMhzA6uZHLD5cIFEFSKIU+G8LK+tb0KsGZKcTJoEyP08AbpcLy6sbPKdQrigdAGaDwWxsDH1uGbliCYIgcM8WFPg8Mq5Pjzdyu4jYbCE44EepXMHuwXe+A8x3KKYxYsjvbUzmlPGpBmYdgI1oYjSMbL4Ao1YXMkcM2Dd2xnbAamPQAqg1GORLZdycmYTdJqFKk2DPR3fmwI4zBDrg9RADqxPAbPBif2WTSB584/3/TGegEOit+DRcvQ4OZJi1LgwIQKVCg2i6nb1I7H3Br3QWqT9pBAP9uDY5xjdSM3RqxeoUkfVnEOW8UkLykERTNXjkM7h3Iw6NNvHw6JjuhAhVrba0+QeALozcI9nQR0VvNxJc/ZmxCNGvIBQcpDG6udA22kyW29HC72wu8yG579ZoiSYuR/ly2+y9CA4NceWLmo717T1i5ULqJNtapL8CDACskxPFZRxLwQAAAABJRU5ErkJggg==');background-repeat:no-repeat;background-size:contain}.ipfs-key{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAlZJREFUeNpsU11PE0EUPbM7u/2AtJUWU6qiiSYYo5EmmPDCD9AH46sx8cEnja/+CB989z+Y+MKPgMiDsYQACcbaWBBogYD92t2Zud7ZlQZsbzKZ3bl3zj3n3IwgItjYeDO3MlWme0bjUth8e8/fO2tHzx3XqUEk50uft+Ndnhdmc3SlfNPkVZT8Cy600DoIISvVfKYtlvfX1p66XmoIYsMZdjJQWvEFbbsC/S5g2QhSkKUK7rx6OzvzqLpsovAhaAxA3DUBQn2TUFsl7KwTfm4Z9DoO5LW7uPXi9Wxpfn7ZKF09vyPxX2iWcNRkKGZz0mQWKoNs8AVB6x1yRY2pYnc2LLofuXTxMgAlmlXIfngCxNxEzM+DPv6NQa2BygLgZyX6JT83ngHTN5GAL0WSoUQkSQnXkyBh/k0GegTAaldM20sTKvet+yyhIZApECamL0jUSe3oFChx3TopM4TeEQP2gc6BgGIwb4KGNXRhCkMGxgg2kJeybRiZM45D8W61qEAknSmpHStBhywu0nFVupSCTAcM4ECwqapv+NQ6LS9JGALoMIIoPYDjZiEL1xHtbyO39AQUDaA7R1AH23DSeSA4hv5RG/VAhxomPYP8sw9A4TaC9iHkjUWmrtGvbyC18BLe3GP0m3WW4I5hEBEnPIStXzyuFIxb4EkMEJ79Qa/xHbKxCdM7xeCwzUZOjgEwnuzt7qLz6T3cySmQP43uzjeIiTJM6io6W19B/NLCKMVGCzkCoLR/0lrfOI2fNy/huKC1FTsK/rbGNeMRC8dHpHByfu+vAAMAL/0jvAVZQl0AAAAASUVORK5CYII=');background-repeat:no-repeat;background-size:contain}.ipfs-less{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RjZERjZENTJGMTE4MTFFMUIwOEVERjQ5MTZEMkVBREUiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RjZERjZENTNGMTE4MTFFMUIwOEVERjQ5MTZEMkVBREUiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpGNkRGNkQ1MEYxMTgxMUUxQjA4RURGNDkxNkQyRUFERSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpGNkRGNkQ1MUYxMTgxMUUxQjA4RURGNDkxNkQyRUFERSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pl1w97IAAAJhSURBVHjahJNLbxJRFMf/wPAIMIxMkUI7tS0VYqlGDLGhjdKkqyZ24cJFN925de+XcONHaHRj4k7TND6SGo1VWwmp2kSLhlqMDbQ87gzPYcY7k4GgoJ6bmdw598zvnvM/95pUVYVma+svcovx8yMnFZHAMJPJBJfDzq5vpX6+/vD5qo/z7DOMBdo/d26t6jFMJ3iY51jBz4M+LP6wxEw40Gy23qYzB3HO7fpmpZCOmfEfa7Xb4NxOrC4lvbPToe2yKE3K1PdPwNOtHdx79ESfq4qKkijB5/XgevIyHxEC24USmewDqD2ABxubaLRkfW6zMqjWGlh7/ByyAtxYnOPnL0Q2+gGGmKRaw8zUBJaTiS5QOO1FJnuIAM8hciaIWHgi8NcSNt+loVDY8JBXh2ojJAR1HbTSNFMUpV8Dxcjg0nSYBrtBxdLbqI1iheCUh9XXNGurAwCdEkb9QyBSFam9TDfoPZ1LUg1BH28IiwEARTVAQOzcFKRaHZpLoa9avY6L1Gfs0c32t4PU6W2lWsV8LAorw0Cs1nXftYWE3qZGqwWHzYp2zzlgetuolVFvtiDLbRRKFTAWCxx2G/KlMtXFhWPqOzsWHJwBx7rxKv2R7mwFz3lw9/5DLC/M4Us2RwV0g3U58XJnF7dvrsBOoX0Abbej/DFKRMKI30fTVGC32WA2m5H9cQQvhYi0vE/7Wdgczn6ARA9QPBrBszcp/XvpyqxebzQ0Tlsq6llxLhe9bD4cFMr9XdjLHpLv+SLGBYHAYiVu1kNOpAaRTWbCejgiw0zGhFGSK1aw+zXbvfK/BBgAPwADAs5GpGsAAAAASUVORK5CYII=');background-repeat:no-repeat;background-size:contain}.ipfs-logo{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAoAAAAEACAYAAAAjlcdmAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALEwAACxMBAJqcGAAABCZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIgogICAgICAgICAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyI+CiAgICAgICAgIDx0aWZmOlJlc29sdXRpb25Vbml0PjI8L3RpZmY6UmVzb2x1dGlvblVuaXQ+CiAgICAgICAgIDx0aWZmOkNvbXByZXNzaW9uPjU8L3RpZmY6Q29tcHJlc3Npb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjcyPC90aWZmOlhSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPgogICAgICAgICA8dGlmZjpZUmVzb2x1dGlvbj43MjwvdGlmZjpZUmVzb2x1dGlvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjY0MDwvZXhpZjpQaXhlbFhEaW1lbnNpb24+CiAgICAgICAgIDxleGlmOkNvbG9yU3BhY2U+MTwvZXhpZjpDb2xvclNwYWNlPgogICAgICAgICA8ZXhpZjpQaXhlbFlEaW1lbnNpb24+MjU2PC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgICAgPGRjOnN1YmplY3Q+CiAgICAgICAgICAgIDxyZGY6QmFnLz4KICAgICAgICAgPC9kYzpzdWJqZWN0PgogICAgICAgICA8eG1wOk1vZGlmeURhdGU+MjAxNTowMjoxNiAwMDowMjo4ODwveG1wOk1vZGlmeURhdGU+CiAgICAgICAgIDx4bXA6Q3JlYXRvclRvb2w+UGl4ZWxtYXRvciAzLjMuMTwveG1wOkNyZWF0b3JUb29sPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KqqO/2AAAQABJREFUeAHtnQecXFXZ/+/W2dmd7Uk2mx469sJfQJHXKKCiiA2UEl+KRiyI8NrA8oIVeVVQEAERUQRRwAIIhmIihBAg1JhGetmS7X1nZ2d2/7/f3b2whE323LlT7sz8Dp+Hu5m559xzv/fO3N+c85znybNUcpLAWauePqpr544HRqLRorzR0ZP+fsYnHspJEDppERABERABEchBAnk5eM45fcpL1q2r3/3E6uuLSoIfiEaG8gmjoDgwOtCy57G6N7z2tFsXLdqd04B08iIgAiIgAiKQAwQkAHPgIvMUl6xeXdqyZdvFI9GRr+QV5JdEw2FrdGTEPvu8/HyrsKTEGhmORsNdHTcuPPrI/7nhiCMGcgSNTlMEREAEREAEco6ABGAOXPJP3P2Pk/vaO64vKiuto/DDtO+kZ51fWGgLwXBXV/fo4MCFSy84/7eT7qgXRUAEREAEREAEMpqABGBGX779d/6c5zcc2vTUqlsDlRVvgfDLGxke3n+F8Xfzi4qswkDA6mtu3lw6ve7Uu0/7+LNGFbWTCIiACIiACIhARhCQAMyIy+Suk+euXVvT8NiqnxaVlS3Oy8sriA0NWaOjo64aQT2rACIQ08Qjvbt33z9v0bH//fsjj2x31Yh2FgEREAEREAER8CUBCUBfXpb4OgU/v6LmDVu+NBIbvrSgpCQUGxy0oN/ia2y8Vj78AwuCQWu4r28o3Nl15UHvOuY78A80G0r0dGRVFgEREAEREAERSBYBCcBkkU1xu6c9tPz4ru1bfxOoqJwbxYif6XSvaTedaeGB1pZ2a9T64gNf/NztpnW1nwiIgAiIgAiIgL8ISAD663q47s2StWvn7Vz5xB8ClZXHjEQieTH6+bmc7jU+KKeF4R9IMdjb2PCf0LSaj9+9ePFG4/raUQREQAREQAREwBcEJAB9cRncd+KMVasq2tdu+GFRcfGSvMLCItvPz+N0r2kvGDbG9g+MDsf6W1r/XP+Oo8679aijekzraz8REAEREAEREIH0EpAATC//uI7+kdvvOCMSDl9dWFpabfv5xWJxteO1Un5Bge0fGOnp6Y90tH/z4a995ede21R9ERABERABERCB5BOQAEw+44Qd4VMrn3rznv+8cGtJZeXhsUgk4X5+8XaUU8IFxcUMG7O7qDiw+J+f+8zyeNtSPREQAREQAREQgeQTkABMPmPPR/j0mjV1u1c99euSUOgDsZGRfPj6uQ7r4rkTUzTAsDH5EIFYNTza07D78eqF8z/xl499TGnlpuCmt0VABERABEQgHQQkANNB3fCYZ23bVtL80CNfLygq+EZhSeAV6dsMm0j5bk5aueH+wWhvY+OvDzzzExfdvHBhOOUd0QFFQAREQAREQAT2SUACcJ9o0vvGR+74y8nhru7ri8vL62IRhHXZR/q2VPTSHt1Dmjh7hbHhAZlWrqA4YA12dHRHerv/Z/nXv/obw6raTQREQAREQAREIMkEJACTDNht82c9u+7gpiceu72kpubNI0NDeTEKP8OwLhRcpbU1tlCD8GIWD7eHf9X+hQgCHZpZbwXKy63Bzk6rf0+zRf9Do8KwMRCCnBru3d2wpShUfurSz57zjFFd7SQCIiACIiACIpA0AhKASUPrruHPvfBC9eZHHvt5oLLq9PyC/AKKLFMBl19QaAWn1VpldTPt8Cw8cqSv1xpoabGGenrcLxaBcCuC8AvWTkO70yyO5jmFo5H9zc3WYFu7NRKDODUodtgYiMCRaGyke8f2B+a86fWL/3jSSW0GVbWLCIiACIiACIhAEghIACYBqpsm37VsWWFw244v51l5lyJ3b1ksHIawMgzrAqFWUlVthWbNsopKSyc9bBTtDXV3WQjVYkUHwxgdxOgdRxRRd2LJY0gXrOZFH6yS6hqrOBSy+Nq+yvDAgNXX2GiFuzqNRyjtsDElJexLpG9P8y8K3nH0xcsXLTJTkfvqiF4XAREQAREQARFwTeCVKsB1dVXwQuCUfyw9vmfXrt+UVFfNdRvWhYIvVD8LYq36VWJuX31iejja6CRTyhzlo+1P9L2qXbQTxrRwX1OjRUFoWpywMf179rSPRqNffPiiC5RWzhSe9hMBERABERCBBBCQAEwARLdNnPfcxtnbVi6/vaS29h0QZK7St1E8lc2YYZXOqHvF1KzbPiRyfy5QGWjZY/Vjytk4BzH9A3EuFJ09O3euDZaWf/S+Ly55MZH9UlsiIAIiIAIiIAKTE5AAnJxLUl49Z8WG8oYXll9RWBY6t6CouIj+dKZ+fvSjK4U/XmldnVVYEkxK/7w2Gg0PWgN79lgDbW2uzouLV2JD4ZHeXTv/PveI48669USllfN6LVRfBERABERABPZHQAJwf3QS917eib+7ZcnIcOzHxaGySqzuNffzQx8CFRX2dG8xtplQ6G/IaWEuQDEt9A/MDwTgU9gzONje8qNHv3nx91EXzooqIiACIiACIiACiSYgAZhoonu1d9pDy4/u3LLpFqzuPZBx9IynSNFOIRZM2H5+NTUWRwAzqXBkM4xQNBSCXIhiWmz/QEwNI9zMntFo7NMP/8+X7zWtq/1EQAREQAREQATMCEgAmnFyvddZT66d2bBq2c3B6TNOgBjKs2PnTbL4YrKG6RdXBh+/Uvj6URBlcqHgZTiafvgIGgezpn8gwsZA9I52b9n8dKBy+qkPnP+ZbZnMQX0XAREQAREQAT8RkABM8NVYsnp16c7VT19aECy9AH5+xW7i+THjBkOwhOrrrcJ9hHVJcHdT1lyUYWOamrBqGAGqDYWwEz8wOhSO9e3aeethJ77/czcccYT5cuOUnZ0OJAIiIAIiIAKZRUACMIHX66SbbzktGo1ejRh6tXZYFxfp24pD5Yjnh4wblVUJ7JH/mmJMwr7GJjtQtWnvOCLKEcFwR2f/QEvztx699DtXmdbVfiIgAiIgAiIgAq8mIAH4aiauX1n8+OOHNz25+k9Yofv6keFoHH5+9VZJTW3G+fm5BjVeYcw/sN0eEXTrH5hfhLAx27bvyCuwzlj2ta89Fm8fVE8EREAEREAEcpmABKCHq79k48Zp25Y+dC3y9n4MmTXy6e9mGtaFo1qM5VeGsC78OxcLfQIRDNqOIWjqH8hpYfpFYhqZ/oGPVB9y0Ol3n3ZaYy7y0zmLgAiIgAiIQLwEJADjIHf+ffcFXtzV8LXCQMnFsCB81IyFH1OwBZG9g6t7s83PLw6UdpUx/8BGaxBZRew0dQYNUQiCvRUZ6B9G/MBfv/ltR1x09YknDhlU1S4iIAIiIAIikPMEJABd3gIn3/ank4Z6em8IVFfNjA1FsLJ12LiF4rKQVVY/087fu3cuXuNGsnVHppVDXuH+pmYr0t9nfJb5hUVWQaCYK427w+1tX4F/4I3GlbWjCIiACIiACOQoAQlAwwv/6ec3LNz56LI7g9Onv3kkgvRtFH6Gq1m5gKFs5kxk8pjuLteuYd+yabfRWAyZRFqt/uZmyw6dY3JyDBsDIWj7B+7YsTUvr+CUf33twmdMqmofERABERABEchFAhKAU1z1Jau3VG57fOnPiysrzoTIKHAT1oXZLYIQfRR/FIEq5gTImSJwEGJwBKLQpDhhY2LDkZHubdsfqK4/cPE9nz29zaSu9hEBERABERCBXCIgAbiPq33ppaP5T9Tf+CUrP/97xeUVoZjL9G0lVVW2n19RKLSPI+hlEwLDfX12NpFwV5fJ7vY+FN4FSCs31NU51NfYeNVx+f97yaWX5o0YN6AdRUAEREAERCDLCUgATnKBT7rttmPCHd1/wJTtfIwmuQrrUlRWZoUw4seAzvLzmwRuPC/RPxABpPswIjjc32/cwlhauWKrt2F3W39r83lPXH75XcaVtaMIiIAIiIAIZDEBCcAJF3fJunX1m5Y+dFvZ9On/BTB5zN3rys+vDn5+0+XnNwFpQv+0/QNb4R+4x6V/4FjYGKtr29Y1RYGiU5Z9/esbE9oxNSYCIiACIiACGUZAAhAXjOnbtqxcdUWgvGIJQosUxSJDxmFd6HcWRBBnpm8rKCnJsMufmd2NhcN2EOnBjnZX16mgOGBFBwdGunZs/+u0+XPPvvvcc3szk4B6LQIiIAIiIALeCBR4q575tY//+TWLe/e0PlhaO+1YjPbZizxMR/0CFRVW5fwF9iKPXA3mnI47gKxLEEsRKfcwPR+x6J85ZcE0MoNNY1o4L1RX/5pwd+8Fta95TbTh8ZXKJjIlPO0gAiIgAiKQbQRydgTwlAcffH3H2o13lM2YfihXmTKLh2kpxEgfV/YGa6flTPo2Uzap3o+ZVwbb2+wVw67TymGxSPeOnU2Rwd7Fj3/3uw+nuu86ngiIgAiIgAiki0DOCcDFzz8/o/Hh5TeVTp/xfis/jvRt02eMpW+DX5mKfwhQwNtp5Vpb7JE+k569lFZuZHS0c9OLq/OCxZ9ccfHFW03qah8REAEREAERyGQCOSMA4edXtO2xVZcVlZdfhBG8QCzCvL2G8eUQaBj5frG6t17p23x+t9tp5ZqbrHBHB2byR416m5ePsDHFRdbwwECsa8uWP7z+Yx/+zA1HHGE+JGx0FO0kAiIgAiIgAv4hkBMC8H3X/OojsdHR6xCUeQZHilylb4OfWWjWbCtQWemfq6aeTElgqLvb6mtssCKII2hamFaOoWNQr7d/186Ln7jqZ780rav9REAEREAERCCTCGS1ADzz6acPavj3o3eUz6x/0wh8xWw/P8NRIdvPb0adhdRv8vPLpDt6Ql9t/0CGjWnZYxn7B2K0lyIwH6u7O7du2R6NRs54/NJLV05oVn+KgAiIgAiIQMYTyEoBeO7atTXb7n/gV2XTZ3w8r6gw3136tkKrdAbStyGmH4WASuYTGPMPbLYGWphWLmp0Qk5auZHh6GjHpo2PlNRUnfGviy5qMKqsnURABERABETA5wSyLAzMaN67r6r9eri17e5gbe2bsLrXOJhzHkZ+gvDzq1ywAPl7sboXK0RVsoMAr2WgohJWYTGYNOMITlnGw8bk5eflIcbjAux/fs1hh09vWLnin5Z12ZTVtYMIiIAIiIAI+JlA1owAvu9XN7w3OjT0m9K6utmM92ZP9xqSZzy5MizwKKmuQo2sQWJ49rm2G9PKdSFsTJM7/0BOCyP+YO+unV3IL3zhU1f97OZcI6fzFQEREAERyB4CGa92zl6zZu72pQ/+OVQ/60hcljxO95oGci4oLrbj+SHnr0b8sueeNjoTO61cG/wDkV/YvmdMamGUmPcM76/OzZtehFfpJ1ZeeulzJlW1jwiIgAiIgAj4iUDGCsBTVq4Mtjy68qpQXd05SPFV6CZ9Wz6mBDnNSz+/gkDAT9dDfUkxAWYRYW7hwbY2+AcahgXCAhGmlYuFB0da16+/LzIcXvzcVVd1pbjrOpwIiIAIiIAIxE0gIx3d3vXTqz4ba29fCgH3dozG2Is8TEf9SqqqrIoFCy0Egran9OImp4pZQYDTuoHKKqu4HP6B0WGz1cLj/oGom1c+e84hxSUlX64+9LBA06rHl2UFFJ2ECIiACIhA1hPIqBHAD/7ud0eHu7pvDU6rW8ggzm78/AqDQQvTxPZCDwtTeSoi8CoCEHaDCCDd19RoRQcHX/X2vl7ganEGk+7dvaNtoLlpyZNXXvnXfe2r10VABERABETADwQyQgl9es2auq1LH7qtrK5uEQK05Y0wi8foiBE/288PU72c8uVoj4oITEWAi4g4JcypYVP/wLy8fCsf2UQsTCN3bt68ZnRk+JTHvv/9jVMdS++LgAiIgAiIQDoI+HoK+JS1a4tnzFlwOUb9bi+pqT0IU3R5fDjDC39KVozjVgrRV4npXk778t8qImBCgPcKV4YHcN8gX+DYtDBGB/dfRu0QM/xFVVo3s64oWPq56gMOPrz+/e+9v2n5cqWV2z88vSsCIiACIpBiAr4VgO/5yZVnDO1qeDg0a9Z7MGVbwJEY09yuAfhzVSyYPxbMWaN+Kb6lsudwHDEuqaq2ikJlFkedudBoqsJ7lD9SMPKcXzFv3usKorELaw8+ONr45BOPTVVX74uACIiACIhAqgj4bgr45D//7TW9u7beFZo567ARPkyRu9d0gQfTt4UYz6+2ViN+qbqDcuQ4TCsXbm+3+hA/0HVaOficdm3b0tTX3HrmM9dc9a8cQabTFAEREAER8DEB3wjAL6xfX7vmrr/8unz23JPzMXpC4ceHrkkpgBN+KXL2liJ3r9K3mRDTPvES4H05gNzCA8gxHOOPE4PCKWXelxjFHm1du2ZVdHTktGd/+tMdBlW1iwiIgAiIgAgkhUDaBeApf/5zQeumbT8orZv+5cLSsgAfsG7isZVUV9ure7nKV0UEUkWAq4S5Wjjc2Wn8Q4XxJykEw50d0faNG2+2wgOff/qGG8xUZKpOTMcRAREQARHICQJp9QF8z49//PGBgcFl5XPmnmDlFyCYs7mfX3Go3KqcvwDir16jfjlxq/rrJCnkSqprrKKyMis2FDFaLez4BxaWluZXzp//FtzzF9YcsHCw6emnV/nr7NQbERABERCBbCeQlhHAT/zjH4c0Pf+fOyvnzXs9AdtTaVOushy7FMzcEZrJsC5I36aVvdl+f2bE+dFVYRBp5fqYVg6ZRYwK/ALpusDSvmHjrkhX26dWX3PNcvsF/U8EREAEREAEkkwgpQLwrGefrdp239Jfl82q/yhSabny8xtL3zbdFn/wEUwyFjUvAu4JjGAEmyKQYtCNGwNHE6ORodGOtWtXRoeHTn/65z/f6f7oqiECIiACIiAC5gRSNgV8zPd/+I1IR9fdZfX1b7RGRvPcrO6lnx+ne7nQIw9+VCoi4EcCvDcDlZUWwxCNxKJmq4Ux8j2K4NEFBQV55XPnzisOlp1fPn9BffPqp+7z4zmqTyIgAiIgAtlBIOkjgMf9389OjlmjvwrNml3PqTJb+Bmyo38VffwYi03p2wyhaTd/EICwC3d1YqFIkzXc32/cJ44G0rWhe9vWnv7dDd94+rpf/sq4snYUAREQAREQAUMCSROAn/z3v+c2Pb7qbxVz4OyOo3B6zDSQM/38ypC+jZk8NOJneCW1my8JcHRvwEkrZ+gfmAf/QNvNAclH2jeu2963p/mTL9xwwxO+PEF1SgREQAREICMJJFwALlm9unT9/UuvR0Dm0zCCN5bBwzCeHwkWIZxL9cEHWwWBkowEqk6LwGQEuMK9H6OBA60txj+EOBLIXNaRvr7RtvVrl0faWj/5wi23tEzWvl4TAREQgWwjgEEjrpQLwSgICmH0AeOWr/Nv5oZlKC1uY+NbrsTrww9phdgCiP2VhArAd/7gRxeWVFR+L1hbW8Z0WGN5e/d3+Fe/F6ypsaoOPOjVb+gVEcgCAgO7d1ndEIJuClPS0QZaWiJtGzdc+9yvfnmhm/raVwREQAT8SAACj+JuOqweNhd2IGwWbNq4wf/LqoCVwij48seNf1O/MFvERKMQDMO6YR2wVlgbrB22FcYFdo2wVgjELmxzuiREAB500klvmX30MX+pXLBgPlc/uvHz25u+BODeRPTvbCIwiAwiXdu3xXVK9A/kavj2jRs6Nt/z97P2PPPMPXE1pEoiIAIikGICEHsUb/Nhh8DeMm6vxZYCsBwWgCW7UCD2wCgOd8H+A3sGtha2FaKQQjFnCodSPZfaw17zVMW8+fmMgWbq5+f5oGpABDKQgJfPB39YjWJkvebgQ2sWvPu4v0MA8ot0cwZiyJgu43oxxdC3YHNgHGnwa2HfIuPGKTA+6HphdBngCAhHO/jQ2wNrx4OO02UqIpBUAvj8UNi9AfZu2HtgFHwc3UtXoeapGTdONS4a78ggts3o73psGZh/JewFfE44gpi1JSECsOaQQ/Pp42RZ8Fr3XBIyKOm5F2pABJJCwDDg+b6OTQHJz1rNoYfxg1K2r/38/DrOgX3nQ6EC5kZUcQSBX9TP44s5Vf499DU6FcaHRSYXCsIBGKfGGnENOB22CfYMbANsB5hy6iwjCvo/Dx3laJKb+ycjzs1DJ/n52IPr+KKHNjxXxbXh9OzbYPzcnADjZ6cY5ufCH3oLx+1EbPkDaTfO5TFsH4Q9Aq78zGRVSYgAZHiXvIJEiD98mqOp+l7Pquuok8kAApG+XjtItPeuInbg2MKqxHzovHfIbQsUVQxvcwTMzTlQOPJL+FhYKhfDGKZ3Qa/8W/hdT8FNo6/VkTCndOKPnXjYcaX5Q7An8LDb6bzp0+3Z6Nc3YW7uH5+eSsK6xc/HzbAlCWvRRUO4f+qw+4dhn4AdDcvklZwUsfPH7XRs23B+j2L7V9iD+Hw0Y5vxJSECMJEUIr29iIG2zULAaKuwJJPvn0RSUVuZTICuEf3NWAGMcDDjwi2TTydRfed3D4Wg25IKPyG3fcr0/eloT3sjjOKhZVwM/g1//xMPu0Zs/Vb4gI7n/vHbeSS6PykfxR0XfotxIrx3Dk70CfmkPU5bf2Tc+GPpbvz9B9iT+Hxk7I8Q3wlAgMWDstUKd3dZZTNmWKUz6uwVkACtIgIZRcCOAYhFH/17kCPYdpHIqO4nu7Pxfmlqyi/ZV8ayZuAQJ43bLnwnc7HRrXjQrUz+oY2PEO/9Y3yADN2Rfp8pKbgvuIL307DPw7JV+E3Gch5e/CLsXNiD4MDZjIfw+aCbRUYV+gz4stDhvbehwWrfsN4abNfIiS8vkjo1OQFmAenssDpe3Gj17Nop8Tc5Jb2aGQQ4XcwH/MN40N0J+6/M6HbO9rIvFWeO+4BuGP+AXQnLJfE3ES/9Bj8Eu5cGJidMfDMT/vatAHTgRQcHra6tW63OTS9akW76L6uIgH8JMO1b5+ZNsM0M4OzfjqpnIuCOAP1xPgZ7YFwIvtVdde2dIgL9yTwOrn0l7Aoc458wikCVsfiE7wWIf4DNn2CvyxQovpsC3he4oZ4ei/6BCDIN/8BZ8g/cFyi9nhYCdqYP+PkNws+PsTBVRCBLCRTjvCgEj8OD7ufYXo2pL4aZUfEHgaQJQFzvg3CK18He449T9V0vqKe48nkd7D++690kHfL9CODEPuMGtB3pOzAtTKd6+lipiEA6CfAe7N+zx3ZV4FbiL51XQ8dOIYFKHOs7sKX4Xn5nCo+rQ+2fwOD+347vXVzjd6MmR/0k/vaPcABvc2o8I4pvBCB+RRoDi8E/sGfXLtvHKtzZiXryBzaGpx0TRmAILgm2n9/OHRZX+poW3utu7nfTdrWfCKSBADM63A2B8FmY+Zd4GjqaI4dMuADEdT0R7O6AHZgjDL2c5mpUfsFLA6ms65spYD4QC5DmagTxzWgmhT5WkS2brZKqKiuEaeGisoyMi2tyqtrHRwSiAwNWH/L5cqEHvhyNe+YIP9ZxU8/4ANpRBNJDoAqH5UrIw3Fffw33ObMCqKSeAL+MEjoFjOvJEb+bYcyeka7C+4kjazSutOXUH43nWwSjjuGWVgpLZ/y4v2bS/e8bAUjRx4ciRWB+fr4Vw9Sa0UMSdTgKGIGPYHDadKusrs4qCChUGD4EKgkmwJXpDOkygNAuI0jJZloc4cf9TX/cmLat/UTAJwQ4+ncBrADf2xfhnldE/9RfGIqihAlAXMcj0d4fYNNTcCo9OMZO2BbYtvG/d2PL1IVMY8jzcgQgR4icUSLOYk4UgBwFYn9njm9nYMuRywWw2bA6GP1Yk1E4HXl/MhpOVpu+EYA8QQq+KB6sFIAUgvy3IwynAkDfKz6cOSpTVjfTKp0+HdlJCqaqpvdFYEoCDN4c7mi3R/2i4fCU+0/cwRF/vJdpKiKQ5QQYHy2Ce/3ruPfNfyVlOZQUnR4F4GAijoXrV4t2roVRSCWjUMytgT0Oe3T8b6axoxBMeMH5cFSQfqv1sMNgb4FxJfuhsFmwRLgvrEA7m2AZU3wlAB1qFH20iUKQI4ImhasxGXuNQjA0a5YVqOTshIoIxEeAoYf6mhqtIaxAd1MmCj+N+rkhl3X77sAZcfQiEQ+YqeDwFy+/02kcCQnB0jEdchGOy/P+BUwltQQ8j7xCLPE++hGMIinRhbmn74b9HbYB35PuflHH2Zvx4/BYHFF8Dnb7+HlytPBw2FGwd8LeBKNIjKf8BcdxRibjqZ/yOr4UgA4FRwhyNLCwsNAWhaYPU/oHdm7aNBY2ZibSygUZs1FFBMwIcKSvH8JvsL3d1cidhJ8Z3xzZiw+Dz8Eeg6VCAPIYnBLjA5y+UMz7Ow22YNw42sGH3QEwisNklsvwgH0Gn4cVyTxInG1zlIbTdWSVTaUHJ0PzWk5BA5/22she9Zfh3xxRvB/3BH8Qpb2gHxxVah63ZbhfL8ffHA1kHuPjYf8FOwRm8tltwn4PwzKqpFwA8gFZXF5u8QFrmh6Lo3+s59Y/EBfUDhsT7uqyU8oxtVx+Ef1EVURgcgL07Rto2WP1t7RY9PkzLbw/aSymP1KctvPg8lBaU2PHtuTiEtPRbqe+tr4mMID7IhEP5YScJL4Ty9HQbNjbYcxc8A7YHFiiC6dersHxjsf5tya6cY/tfQ31l3psw2/V+eVDHxNPI2q4Xrxul8BMRA92m7Ksxx7fhd2F+8D8C3XKZhO/A/pHfg2wO2njLI7E3yfD+Fk5ELavsgz1d+3rTb++nlIBWBgoscrnzLZKqmusKMJm2CMsHVhJieneqQouRvz+gXio9zU2jE0LYzSwBA9bPnRVROAlAri/uJiI073DWOXrpuCDb4s/3qM0NyWAH0OhWbOt4goO1sA7OVRu9ezeZUWQUcRtW26Oq31TRsBXXzS4V+nLsGHcbsI9xumuD8DOhB0LS9SDH01Zb4R9GfZN/sNHZRgcEuIr56NzSlRXzkFDr09AY/wi/DXsW2Dttx8ARqeHfndhR/5QYKzLWmw5KngGbBGMLhZOoYC5y/lHJm1T8uXEVbnls2dbtYcdBvEFjnhgFpaUWJULD7BqDj7ECow//EzAcXSFC0VYnBFBk3rcx04rt208rVyvb36Um3Zf+yWJgO0uwPRtWza7En/4grD9VNkt3pduBFtRSdCqXrjQqjn0sJfEH9sphCDka5Xz5ln5cHtQEYFkEsA93AS7Ecfgw+3DsOWwRBbGB3xtIhtMQFuJFLkJ6I4/mhj/MXB+AnrDX9Bfgp2Heysjxd/eDHAe7bDb8fqHYPyh9AsYp49ZdsIesf/KsP8l/QlTBN+7mkMOtfKLiydFw5GPajz0wkih1YfsHqarLJ1pYS4UcesfqLRyk16KnHsxNhTGPddsp28zGYV2AOGLIO7p3kL8GCpDOsMgVqrn7Uvg4Z4Ozqiz3Rci4z92nGNrKwLJIIB7mtNzDOj8T2w5CsRRu0RMDXPk5Kto9xwcY+qpHuyskjYCnOpc4PHoFH+fx7X+ncd2fFkd58WRzWdouKeZCvECWB9eb8M240rSBWABRvr2Jf4cWoBnBRG2JVBViVAuLYizBv8rgwcfLoDtL8X6zmgghSFfn6pwnwGIzjBWeYbwMObxNeIyFbXseJ/31mBbK9IJNlvMKuOm8F6j8f4xuc+ctnlvMTRRGYRdAX4MTXWHjo5iRNGprK0IpIgA7u0IDnUd7u1/Y3st7F0wr+XjaOBq2NNeG1L95BDA9eYIzSc9tk6Bz/A/WSn+9maD89yK1y4Au6TrqL2Pnah/+6rj+UXF8BGcAx/B6rHQG1i8YfKQ5T57xw80daSnoz99rgYR5y1UD/9A+CdyilolOwkwPFBfozc/PzeLPCgWA3tlqpGwy857K5vOCvftenyvfgTn9EvY6R7Pjf5SZ8MkAD2CTGL1t6JtLnjwUm5GZd4vOVXwWRnzScvAs/aVAHT4MaVb9UEHW0NdnVYvH9ZwiDcpfDDT4gkbQ8f/zi1bkFau3SpDWrniULKjJJickfZJFAHeQ1zgwRXh+FVh3CwFHI0/MtwIPx6A93E5Y1FWVRsfTzuKgF8I4L7vwn3/WfSHo0McxfNSPoS2vo82Hb8pL22pbuIJMN9viYdmORr2TVxf8y9XDwdT1cQQ8KUAdE6ND87i8gpM1WK6bs8eK4aVwybFi38gBQJ9BIPTptlTw5zCVslcArEIVps377HvoVHDYOI8W4o+Gotb4cdFT0xJWIrUhMpGYyPU/zKUAD4DfRBun0f3D4C9xcNpzEXdd8Nu89CGqiaBAK4vdcAxHpv+Ce4ViXuPEFNd3dcCkDD4AGVqN04L80FO3y2mfZuq4KZ+hX8gH+Z8kPP1qQoXBAwgDtwQwoLw2PIPnIqY/97nNeS9wkUepj8cnLPgwiIW3ism98tL9XCv2vmoZ9LPL+C8rK0IZDQBfHe24nPAcC73wbxMjXCUSQLQf3cDF/swQHi8ZSMq3h5vZdVLHwHfC0AHDR+oFQiNEaytwVRekx2zzXlvf1s+wOP1D+QCgZf8AzGVZ/sH7u9ges8XBOg60NfUbEX6el31hz8SaG6FHw/CHyj0IS0q8/J8dNVd7SwCKSOAz8Wj+Fz8HgfkaGC85Ui0UYu22uNtQPWSQuANaLXOQ8t34pp2eqivqmkikDEC0OHDByz9A8MIIO0maC9H/2hx+wdu3mwLQPp0FZaWOt3R1kcEovDjtP38MHJLEWdaKPporON2urcI90IIPqMMLq4iAllOgLHPPgFjaJd4yjxUOgT2eDyVVSdpBF7voWX6Zd3job6qppFAxglAhxUfuIFKhI3BVC1TdyU7rRyPyxWkEfoHTp82Fs4Dvl4q6SfAldycsu/HfWASPsjpsSP8+G+KPzeikaFcShHShekF5efnENU2mwng87IRnxFmRoh3VTAXkzA7iASgv24U+nfGW7agIjPLqGQggYwVgGTNBy+n3YIQgxz5GWxvT35auVjUjh8X7oB/IHy95Oifvruefn72SDADiA+6y+zkiD+3wo8pBIMI5MxRPy72UBGBHCPwZ5wv48WNOcq6P/nXua+iGskigO8/Xsf5Htp/Ad+l3R7qq2oaCWS0AHS48UFcuWAhhGCtLQS5itekONPCdPrn1DDFgGn8QK4u7dm50xYgIeQXDsAHTCV1BDgSS9Fveq2dnk0Ufm6ne5mykMLPydvrtKmtCOQQAcby42rPWXGe88H4nsXHUOFC4uSX6GoVaHC2h0Y3eairqmkmkBUC0GHIB3MN0soNjgf7NR0VcoRgPP6Bdh5Z5JAtQciakPwDnUuRtC2vaT9G/OzR3hT5+RUinSGvbVBBwpN2XdVwxhBoQk/XwuIVgDNRl07UZsFdsaNKUgmUo3UvK9cY/08lQwlklQC0rwGc+TkSGKhgWrlm2zfM1C/MiR9IIchRQf6bo4JTFe5D0TnU22P7BpbCLyy/qGiqanrfBQFeQ/p6Mh6k6fV0mo87rAvTt+FaMhSQ0gQ6NLXNZQIYuYvh+24dGBwfJ4cq1KuESQDGCTDB1eiXSYu3ILK+SqYSyD4BOH4l+MAunz3HHrXpw4gRfcVMxVy8YWMoTHobG8bTyo2tDKXPmIoXAqO4dgzr4i19m8m1d3rJaWIuMuLUvlZ8O1S0FYGXCNDxP97CtHA0FX8QoPiL15mZoyMS8v64jnH1ImsFoEODD/CqAw60hmqn2SIi0msWG87LtHA0HLa6tm21Am1t9tShfMacq+Fu+1L6NoR1cVMo4GgUfW79/IrhQkA/P64wVxEBEZiUAKeB4y1BVKSp+IOAlxFACsBhf5yGehEPgawXgA4UPtD5cB+EKOPUMEWaSXGmhTmNWIhRRUcYmtTllHDkxV571WgZR5PgS6YyNQFm7rCn71tbjVZ1Oy16EX6FSPlnZ31BCkCN2jpEtRWBSQl4GfUpQIs0FX8QoAaId5qK9ZQr1R/XMa5e5IwAJB0+2OnTZaeVo38gBIaJPxlHkhwhGI9/4ABEJ3MM2/lhETtO/mST36vM1ctrYud9xiprN8WTn9/06WN+fvLbdINc++YugQGcOvNxxiPkmGA7p547Pr9NOIIXhcU7DazwFz6/wPvrXk5+ELlAo3zOXPh5IWwMfPbChlOMFIKe/AMbxo5lZ45g2BhMU6qMEeA16IefX6Tf3eDCxFE/N35+PKqdvm3WbIvZPFREQASMCVD8Tb06bvLmWG9k8rf0ahoIOAIw3kMviLei6qWfQE4KQAc7H/xpSSvHsDHKHWtfhmGmb3Mhwp1rN1H4ufXzU/o2h6K2IhAXAY78xfvrleKPAlLFHwQ4mkuL1+n5Tf44DfUiHgI5LQAdYE5auYFWpBNDmBG3aeUc/0DTsDE8Lke8hrq7rdLx6cdcyyoxEonYrAfazKbhnWvlCD/+263wY/o2exp+utK3OTy1FYE4CDDGVbx+YxKAcQBPYhWusKPVx3mMt2DmpQbfyx1x1le1NBKQAByHz7RyXKhRgmC/TqDhEfikTVX29g/kvylMTKYjmcqMgpNisGzmzJxIK8dzHuxox3Qv0rcZLsRxroEj/sjWhK9TLx/XlunbeH1zTWg7DLQVgQQS4Gq2eEcAI6irlaMJvBgem+pD/RbYa+Js5wDUezvs3jjrq1oaCUgA7gWfAqFi/gKrhGFjMDXJUTqTQkESr38gRxzttHLIZVyG3MYUodlYhrq7EIqnyTINxeMwmCj83I76cfV3CH5+xSEvwe6dnmgrAiIAAl4+TJxudOfoK+RJI4Dv1hE8uzbiAO+K8yAcCT4HbdzHtuJsQ9XSREACcB/gKRhqDj7EHp1zE4SYAoUWV1o5LICIbB73D8yixQlM30bhx5E/DN3tg/irX/Yi/F7y89Nim1eD1Ssi4I0A88fGWyj+KAJV/ENgNbryWQ/deR/qLoI97KENVU0DAQnA/UEfzwjBQM5MQzbQ0mLFhs1mL+ING8PucEqYo2Sl0xieBGFj4LuWiWUErMisH+xMwu045+gIP/7b7YhfAVZ4M9RPqcLtODi1FYFEE6jz0CAFoEYAPQBMQtXn0SYD48Yb048uAd/HKOBqfHebTZkl4STUpHsCEoAGzBi3j9OInJrlSFYYI1mc8p2qcB9OC1PQcESQ/6agMalLwWSnsEOOYfqu0YeNfoqZUOjnx9R7HDlNlZ8fGTOsTwhT6Aq4nQl3ifqYwQTmeuh7M+q6C/Lp4WCqakRgHfZaD3uz0d6T73QUXr4cz7Yv4rt4auf5ydvQqykmIAHoAjiFRdUBByCtXK29UGSop8eotiMEuVqYQtCZJjapHEVWjO4d2+3p03L6smE00s+FI5cUfqa+k865UMDRHJHsvG6yDYAJRbLSt5nQ0j4iED8BfD75K/Sg+FuwduFzPvWvZw8HUFV3BHA9+nFdH0ItLwKQBz0P1oa2/hdtyh+QRHxeJADjuEAUGhQdg1i04WaUyxF+cfkHQlh1vLhxPH7gLIs5jv1UONLX39yMVHtI32YwOur0naKPxjpup3uZvo1BtTk6qqDaDlFtRSCpBGag9QM9HGGbh7qqmjwCf0XT58PinQZ2evYt/FGJ7/NL8L3OFcYqPiYgARjvxYFoCSJvLMUgfdwGkcIs2f6BFEmDmFrlyGMZfNzo68asJsaFIgtTy1x1bPvkOUIN58JpbsbJs9PU4d+mhe0wfdsAUuuZnr/Tdrzp2+jnF2T8RPr5uTl/58DaioAIxEvgjag4O97KqMcVpyr+I/AUuvRv2HsT0DUKycPwvLoIIvA/CWhPTSSJgASgR7B2WrnZc6wg08ph6pO+bxRqUxXus3fYGDf+gb0IUcNVtXZauZoaO8/xvo45jNXFnJKN9PZYXJHL+Ib005tYmCeZ8fI4zV1cXmEL26Kysom7vPJv9D8M/8Q+jPqxfTdl4qifCSunbdZj0G6es/z8HCraikBKCZyIo8UbBLoXdV9MaW91MCMC+G6N4rv4eux8HCwRzubHo51laPP/sP012mewaRWfEZAATNAFGfMPPNAasvMLM6et2ei3My0cl38gpl27tm21AuNCsLi8/OWzgUAb6um2V+FyxHBvwffyjmN/8f0YDSt3uT/FLKe5OcoYqKh8xRTrcF/fWPo2wxiJzrEmCj+3073FZSEsxJllBaqqnOa0FQERSCEBPMyn4XAUgPGW3ahIU/EngX+gWw/C3peg7vF++THsdNw7V2P7VzwDlDEkQXAT0YwEYCIoTmiDAoVCbLC9zfaJ4yIOk+KM/jlp5RxhaFLXHt2DaAsibEwIGUWQJ8Pqa+AIYfyfNXslb1eXFYYFOeqGUU4KOGYuYcq8qQTlxH57EX6FCMzNLClBBObOlFXQE89df4tAFhH4AM7Fi//fc/guUJgQn94QuDYRCLUfoHvHwhLpZE63gRthX0H7f8D2b7B1ON7UU2XYUSV5BCQAk8CWQoVx6AJV1WPxA+EjZ/vcTXEsfDisifEDKZwcYThFVXvamcKM07IsJsebqk3nfcfvkAstGNvPTYnXz4++iMyTTI70TVQRARFIHwF8N3FxwGc89kCBgj0CTHZ1PHNW4FpfheNckoRjHYY2vw/7Bmw5jnMvto/ANuG4UWxVUkxAAjCJwClcyufMHYsfCJ+9MKdMIfKmKhSCe/sHUhialEQKv4nHc9suxSuN50IzLqhTMp6+bb8+iMYNakcREIEEEPgw2jjaQzsc+XvcQ31VTR0BTtseA+NIYDIKUwl+cNzoK/UcnhErsF0JWwPbLUEICikoEoApgEwhU420ckOYTuXiDdNFE840cDxhY1JwWpMeYqLwY//dFHJirEP5+bmhpn1FILkE8HBmcnKOCMW7+IMdfAKmBSAk4fOC7/AeXPMl6OZ9sAOS3F2KQYpNGgsXi/wHx38S21WwF2A70CczXyrsrGJOQALQnJXnPR3/QCc9GsOxmBRnWtjxD+S/8QExqZqyfRzhxwO6FX4cKXXC2sjPL2WXTAcSAVMCl2HH15vuvI/9/oTvCE3z7QOO317GtdqIZ8zZ6NedsOkp7F81jvXOceNhuXJ8wwRB+Dz+vQX9Uz5p0vFYJAA9AnRbnQKnDOnKSqqr7VRvDCZtsqCCgs8Rgm7Tyrnto9v9HfHHPtJMC0PPMIhzCFk8ChDUWUUERMBfBPB55kjQeR57tQv1/+mxDVVPMQF8rz+C638GDnsLzEv+Zy89Z2iL/zduX8B2ELYZ/eIIIV0KnoXRh5BCUcUlAQlAl8AStTsFT+WChbYAYn5h09RpFFiOfyBHBE19AxPV773bYR/YJ7ejfgygzby9jDmoIgIi4D8C4w//n6FnXp8Tf8ADutF/Z6geTUUA1+1B3AefxH6/hS2Yav8UvB/EMTgaTTsXxqnh7ejjU9hyyvgZ2Ab0m1PJKlMQ8PrBnqJ5vT0VAQqgGoSNCXd02rH3hgfMRrbdCq6p+hHv+277UYQUdmPBqznSnxfvYVVPBEQgSQTwMOVz4cuw78G8Ds23oI3fwFQylADE1HLcEyei+9fDOD3rpxJAZw4dtzOxZZiKBvR3Nbb0O+V2HawV52E+PYUKuVAkAH1xlccyXBQj8PIA0srRR9BtWjVfnMZ+OsH0bQwqzbAudrq5/eyrt0RABNJDAA/O1+LI34V9NEE9+C0evFsS1JaaSRMBXMP1uDdOxuG/D2M4oKI0dWWqw7JfC8bt49jGYHtgz6D/HCG0VxrjfNrwd84XCUAf3QIURiGsgi0ZzyYSRoYP3LQ+6qH7ruCDZp8Ps3gUys/PPUDVEIEUEMD3zCE4DKfUzoExg0MiyiY0cmUiGkpwG5n9pZpgGKbN4buc06pfwL3yILY/gL3GtG4a9yvAsWeN2wexpSDkCCHF4MOw5bDNODd3IStQKRuKBKAPryKFUtUBB1hDtTVIuYa0cki9lomlODSevq2yKhO7rz6LQFYTwEMwhBM8CkYfL47uJEr4oSmkI8JoER6sHH3xWykbP3eKg0wv9KMZBOeUhUnBsf4Gfo/huBfAPgtL5H2D5pJaeM3njdup2PbAHsf53IvtfTi3rdjmTJEA9PGlDkA40UdwsI1p5Zos07Ry6T6lsfRt9UhNh/RtWCSiIgIikH4CeMjx4ceQHm+CHQc7HvY6WDI+pLej3T/C/Fh+gU79EJaM8071+fKa/gTGXLspKxBKrTjYt3BP/QlbCkG6DNCxO9MKVyG+d9z+F+dzP/7mfbsc58gVx1ldJAB9fnkpoOg7V4Icw/3wDWS6N7dZOVJ1imPp22Ygpt8MKx+x/VREQARsp/SUYsBDjN/rAVgtbDZsLoyi7y2wN8IY0iOZK7DWo/2v4AFKh3w/Fp4/LVtK2oQXrvEaQPw07rkrsV0CoxCcA8vEwpHMxTAuJuGo4HXY3oNz7MI2K4sEYIZcVgqq8jlzbCHYvXWLNTyUshF/I0JFgYBVecCBVhGmfVVEQARsAhRZ38CDZOc4D/oZ0eiHNHHrvL73ltOozmv8e6KxbUfo8dcWBR99LSgGaBQ49H0qg9ExPlWlAwf6PB6ajak6oI5j3yNpxYDrvRYduAD3+o+x/TCMbgWM3+d1FTmaSHnhZ+vt4/bCuLj9M87RLERHyrsb/wElAONnl5aaFFjFCBvjNwHIPkn8peWW0EH9S4APkg/4t3sJ71kfWlyCB+XyhLesBjOCwLjwvxai6QZ0+K0w3v/vhTFuXxCWaeUN6PBvYRzl/BHO7x+ZdgL76282+EDs7/yy8r0RH64M9mOfsvLi66REwJ8EKP6+gAfkXf7snnqVSgK4D6KwJ2DfwXHfOW4XYXs3bBcs09ICvgN9/jtE4K9h8/F3VhSNAGbFZdRJiIAIiEDaCOzGkT+Dh/0/09YDHdi3BHBfRNC5p8ftSggo+qYeDuMUMada3wabBfO7HuGCm0/DjsM5XILz+iP+zujid+AZDVedFwEREIEsJ/AUzu+zeBg+m+XnqdNLEAHcK+1oasW4OYKQMQUpBI+GURj6WRAuQP9+DxHIhVWX4Xwy1jdQAhBXMNMK8+/6rfixT35jpP6IQBYR4BTeNTDG+uMDXUUE4iIwfv88iso0Jj/gCCEF4ZEwRxDW428/6RX25WuwQ9HfczP1M+AnoGCpYkKgACtu/Vb82Ce/MVJ/RCBLCDCLAoVfVjnEZ8m1yfjTmEQQMjzLZIKQU7LpLiejA+UQgf+Nfu9Od2fcHl8C0C0xH+xfFCq3cLP5Jk0c+8I+qYiACGQ1gQ04u5/BbsNnvj+rz1Qn5xsCuNfa0JlHxo3Pven4e29BOBOvpUsQvhvHvg39+hj62oq/M6ZIAGbMpXq5o4XBoB1oOeaTWICMUcg+qYiACGQdAcYhXAm7CcaguHwYq4hA2giMi6x/owM0CsIZ2EwUhEfg36kWhFzpfAP6shj944r4jCgSgBlxmV7ZSWbcCFRUICuIP35ssC/sk4oIiEDWENiGM1kKuwv2KB5qQ1lzZtl3IqkM9O07erg3W9Ap2nIIMMbe5Ajha2FcYXwsjIKwBpbswgDYl8K+kuwDJap9PbUTRTLF7ZRU1/hGALIvKiIgAhlNIIzeb4I9BrsPthIP1mxd3PE3nN9mWLqmDHHohBWew4qEtZbhDeGeZbYcRxAugyC8HP+eC2Mcvw/A3gPjiGGyyhdwTOYRvjdZB0hkuxKAiaSZwraKkRGkqKzMGu5PrysO+8C+qIiACGQMAaai64Ftgf0H9jjsSdgmPLjS+4WCTqSg/Arn+UAKjqNDpJkArjPv9e3jdivEGYM4vw92OuwYWKJDajD13RU4zpM4NoWor4sEoK8vz747l1dQYIVm1ltdyAuMm23fOybxHdzgdh/YFxUREIFXEaD/3G9gHEljbl7m6OVwOcNcVMHoOMsl/fwe5oeIxiksr4UhWmgRWBdsF2w3rAHGkS8u5qD424PPcC5O7TJ3skoOEsD9vgOnfT2emb/F9r9gn4N9CJbIhxiDXLPdy2C+LhKAvr48++9coLraClRVWeHOzv3vmKR3eWz2QUUERGCfBG7GQ4eLKOyCBw+/cylAOFJQBuPy+dLxv7l1jO9xHxr3p00UiBR3wzBnSyFHsUfrhfXBOMpH8dmHPnBfFREQARDA54GfmwfxeXwI2+NhF8PeBUtUOQ9t/wHH4Q8t3xYJQN9emqk7hpvLqpg7z4oODlrRMF14UlcKS0rsY7MPKiIgAvsk8Iqgnfi8OKNzA6jRsc9aekMERCDpBPB55PTZAxBrK7D9POxbsEqY18JVyGfD2J5vS6Lnv317otnaMQZgrly40MovSt1CMB6Lx1Tw52y9q3ReIiACIpA7BCAEB2A/wRlzOnhjgs78FAhLX6+QlABM0JVOZzPFCMJcdcABKRGBFH88Fo+pIgIiIAIiIALZQgAi8BGcC7N7rEnAOR2CNk5IQDtJa0ICMGloU9twoKLSqj7gwKSOynHEj8fgsVREQAREQAREINsIQARyBPB0GBdPeS0Uk74tEoC+vTTuO1aMgMw1Bx9iBcoTPzrHNtk2j6EiAiIgAiIgAtlKACKQ4ZHOh3FxlZdyJKaBkxl30EvfEh4Dx1NnVNk7AaZkq4ZQq5g3zyoofoX/eVyNsw22xTaV7i0uhKokAiIgAiKQYQQgAv+OLv/OY7fnof6bPbaRtOoaAUwa2vQ1zLh8ZXUzrdrDD7dKpzErTnyldNo0uw22pVh/8TFULREQAREQgYwlcAV63uKh9wzddISH+kmtKgGYVLzpbbyguNgqnT6dMY9cd4R1gqjLNlREQAREQAREINcI4DnIOH63ejzvN3isn7TqEoBJQ+v/hvPz8y2aigiIgAiIgAiIwKQEbserjNsZbzkUfoAM7O67oqe/7y5JYjuUniRxiT0HtSYCIiACIiACaSLwHI77godjcxFI/L5YHg48VVUJwKkI6X0REAEREAEREIGcJIBpYKaNe8TDyTNumi9jp0kAeriqqioCIiACIiACIpD1BJ7ycIbM5z3HQ/2kVZUATBpaNSwCIiACIiACIpAFBDbjHOL1A6TOkgDMgptApyACIiACIiACIpBbBNpxup0eTjnkoW7SqmoEMGlo1bAIiIAIiIAIiEAWEOjBOdDiLbXxVkxmPQnAZNJV2yIgAiIgAiIgAplOgAtBaPGWafFWTGY9CcBk0lXbIiACIiACIiACmU5gBCcQ83ASrO+7IgHou0uiDomACIiACIiACPiIALUS07rFW/bEWzGZ9SQAk0lXbYuACIiACIiACGQ6gSKcAC3ewkUkvisSgL67JOqQCIiACIiACIiAjwgE0JdiD/2JN4SMh0NOXVUCcGpG2kMEREAEREAERCB3CVTg1L1k8/Cygjhp1CUAk4ZWDYuACIiACIiACGQBgSqcA0VgPIWrh3fFUzHZdSQAk01Y7YuACIiACIiACGQygdnoPKeB4yldqNQST8Vk15EATDZhtS8CIiACIiACIpDJBF7vofPMINLhoX7SqkoAJg2tGhYBERABERABEcgCAm/2cA6tqNvroX7SqkoAJg2tGhYBERABERABEchkAqOjo0zj9gYP57AxLy8v6qF+0qpKACYNrRoWAREQAREQARHIcAJvQf8P8HAOT3qom9SqEoBJxavGRUAEREAEREAEMpjAieh7vFlAhlD3Ob+euwSgX6+M+iUCIiACIiACIpA2Apj+nYmDf8RDBxj+ZbOH+kmtKgGYVLxqXAREQAREQAREIEMJfAz9nu+h70/B/8+XK4B5ThKAHq6sqoqACIiACIiACGQfAYz+TcdZne/xzP7msX5Sq0sAJhWvGhcBERABERCBzCAA0ZMHky4Yu1xfxuZQD1duJ+o+4qF+0qvqQicdsQ4gAiIgAiIgAhlB4BD08u8QgYth5RnR4yR0Euf+HjTrdfRvGaZ/m5PQvYQ1KQGYMJRqSAREQAREQAQymsD70fsPwn4PexRC6AJYXUafkcvO43znoco1MC8CmKt/b4b5ukgA+vryqHO5SiDP4n8qIiACIpAaAhA+xTjSRycc7Y34+yrY43jvCthbYfGGQ5nQrH//xPnVo3e3wg7z2MuHUH+FxzaSXj3pAjA2NGSNDA8n/UR0ABHIJgKxobA1Eotl0ynpXBmX9xkAAByGSURBVERABPxNgILviEm6uBCvfRX2KOyfEEnnwBgeJasKzulAnNCfYMd4PDF+cV/j1+wfE8+tcOI/kvH38MCA1b5xgxWaOdMK1k6zrDyNaySDs9rMDgKj0WFrYM8eq7+11Yrph1N2XFSdhQhkBoEPo5vB/XSV7x03bjsgmDjK9Q/YCogd5rvN2IJz4XldCzs4ASdxP9r4VwLaSXoTSReAPIPo4KDVtW2bFe7stMrnzLUKg/u7x5J+zjqACPiSQKSnx+rZtdPijyYVERABEUgVAQgg+rud5OJ4jI137rhtR32ODlIMctsEQTiKre8L+j0DnfwK7HOwUAI6zJh/38b5RxLQVtKbSIkAdM4i3NVlP9zK58wZGw103tBWBHKcwGBjo9XT3KRp3xy/D3T6IpAmAkfjuIfHeewFqEdbDGuCrYGwegzbFbAXIIbasPVVQf8Y4+80GIWfV3+/ief2E5zvcxNf8PPfKRWABBGLRKzu7dut0diIVTqD4ltFBHKYwOio1bd7l9WHaV98KeUwCJ26CIhAGgkw40Ui9AAXUdBOgPELjVPFL2D7PIzb/8A4QtiNbUoL+sGpxzfBmNqNdhAskWUZGrs6kQ0mu61EXHDXfRwdGbGnunATWMHpFOIqIpB7BOgN29fQIPGXe5deZywCviEAYcQwL+9NQof4Fbdg3D403n4ftrtwzE3YboTtgG0b39KPsB8WhjbwtAIO7XOBayWMo0wUfVzYcSSMC1242jnRZSsaPA/95vllTEmLACQdisDunTusgkCxVVzB66QiArlFINzWBvHXrJG/3LrsOlsR8BuBRejQ/BR1in52nGree7p5EK9xqpjWBQHXhW07jKKQ2zBsCEbfOm5pFIlFsBJYAFYF44gSVyjPhTGe3ywYj5nM0onGz4X4ezGZB0lG22kTgDyZsZHAXVbNoWVWfmFau5IMtmpTBPZJIBYOW92Y+h3BDyEVERABEUgHAQgtjtKdko5j73VMTs9StNEyqXDE7wsQf8szqdNOX5MeB9A50L62XPHYj1EQFRHIGQLw9ett2K0wLzlzwXWiIuBbAlwAcbxve+fvjnHF71kQf3/0dzf33bu0C0B2baClxQ4Vs+9u6h0RyB4Ckb5eOyRS9pyRzkQERCBDCXAq9YkM7Xs6u92Eg58O8XdXOjvh9di+EIAj0ag12JbRcSS9XgfVzyEC4c4u+f3l0PXWqYqAXwlAwHDxwgdhn4I949d++qxfDHHzQbBb6rN+ue6OLwQge80YgRSCKiKQzQR4jw91079ZRQREQATSTwBCZgh2C3rybthnYE+mv1e+7AEXnVwD+xB4ZYVY9o0AjMIpPtLb68urrk6JQKII8B7nva4iAiIgAn4iAFHTDbsRfaIQ/Cjs7zCGZVEZi194GvicD6PvX1YU3whA0pQAzIp7SiexHwK6x/cDR2+JgAiknQAETj/sr7APozPHwn4AWwvLxZAFFHuXwd4NHndgm1XFV7FXomGGAlIRgewloHs8e6+tzkwEso0ARA+nOp9BuJgrsD0a9iEYVw0fCPPVABL6k8jCeIR3wq4BA4rfrCy+EoBMEzcai1l5BQVZCVsnldsEeG/zHlcRAREQgUwiABHUg/5y0cNSiMEabN8KY7q398AOhiU72DIOkZKyHUe5DfY7nHPGBXZ2SyghAjAvnz8EGE+Sqf88FMRHw81lt+ShFVUVAV8S4L2NGzwBfcuzxj5zGf1RKYoTBOvxyyaVJd6+8osxm0dJUnkN9j6Wl1ECXZO9abr4N4QRp0UfpOE7jVk4mNWDadbeBaMwnA1jYOdMKQxBsgz2V9hynF/OBCZOiABs37h+tO5Nb80bGcYIXkIecJly36ifIpBaAvhysvKLiq09z62hkhxI7dETdjT2/XkYV9XRTAsf3Lthw6YVErAf+/c0jCMgbvpKkUqfKS35BoQklJ1ok9fFTegI54cDBYxKAgjg+4gr2p4dt+vw/Gde10Nhbx63N2C7EFYNC8D8ULiwZTuM8Q8fhT2C89iKbc6VhAjAtnVr3xasmXZn5YIF80cwzTUyHO/3s/P5zLnroBPOGQLx3+P5RUVWPtwj2jdu6Njx8ENnA9mmTMSGL1t+QZyNh0VcMFA/EcOoRuhwLD4sTo+nr6nsp9HJZNFOYHsDrsmv4zklXZd4qJnVAdtu7MkwMnYoGVwjjtTWw+bCFsKYeWT+uDFPL/P3crSwFJbokVn+YGNoEfrzbYGth62DPQPbgL7ys53TJa4v4H0Re+cPfnRhSUXl94K1tWWMd+Y2rl9BcbE17TWvxQhHvDMu++pZ7r4e6euzOjasn3RkNt+euscwxST5aPHhsGoOO8wqDpXnLrwEnzl/GOHHkms/QObJpiFjTqRt44Zrn/vVLy9McNfUnAiIgAiklADEIUcLOTJIEUi/wtpx42sVMPoVUhg6ApHCgIKSRrE4NG4chaRzNfPytsP2wDiN6/zdOC5M8ZLKRAIJFYBseMnq1aXr7196fWhm/WlFZWUF9sKOSQTGxE5M/Lt0+nQrNGu2RTGo4p2ABKB3hologZ+DvsYGa6DVPOMN/fz4OcA1HG1bv3Z5pK31ky/ccktLIvqjNkRABERABHKbQMIFoIPzk//+99zGFSvvKp87/4j8/Ly8GKeFDf0DCwIBKzRzphWcNt1xdnea1dYlAQlAl8ASvPsofvwwzWFfc7MVG+IPVoOC0dcCjIKPxEYwertu60Bn+2nPX3edPaViUFu7iIAIiIAIiMCUBJImAJ0jL/rB5R/ILwncVFY/a4Zb/8DiUMgK1c+yAlUcKU56V50uZ9VWAjBdl3PUGurqtvqaGjmCZ9wJx8+vZ+eO3s4NG772/M03XWdcWTuKgAiIgAiIgCEBzqUntWz/10Ob/nvp0T/bcE9vLK+g8OiiUKgwDyOBmP+f8ricNgt3dljRwUGrIFCsaeEpib16BzIcbKMP7KsL/fxYJrsWfC84bRqY+2Xh1qv779dXhvv7rN5du+wpX/I3KVzckY+R76GurmjLM0/fFAg8duwTV/1Vo34m8LSPCIiACIiAawIpHVY769lnqzbfe/9vKmbP+TAEXT6d4jlFZlL4gKR/YFndTCtf/oEmyOx9NAJojMrzjiMQe/17mm0/P452mxT6+XHULzoUGW1bu2ZVNDxw2rO//OUOk7raRwREQAREQATiJZBSAeh08iO3/+Xwrt3b/lJeP/uwEYwE2mFjDEYEWb8QoyRl8g90UE65lQCcEpHnHRw/v374+UVd+PnZ070Yae3curmpb3fT4mev/+XDnjujBkRABERABETAgEBaBKDTr3f9+CdnFAZLf1E6rbZmJIr4gVGGBzMrDE8SmgX/wEr6B6rsi4AE4L7IJOb1oW74+TXSz4/hpsxKfiHi+RUWcGHIQNfmTZc9c+01V5jV1F4iIAIiIAIikBgCaRWAPAWEjSnatPTBH5TMqLsAYWOKORroZvqspLraXihSGGSoIJW9CUgA7k0kMf+mXyoXeIQ7O125MXDUL9LbG+tYv+722EDfkqdvuCFTs3kkBqRaEQEREAERSAuBtAtA56whBKe9+NCym0Oz6t+PxSKu/AMZMoP+gaUz6hRE2gE6vpUA3AuIx3/yB8pAyx7bz88ObWTQnuPnNxKLjnasX/+0BT+/x6+8crNBVe0iAiIgAiIgAkkh4BsB6JzdB2+5/Y39zQ1/Lp81+xDbP9BwFSXrF5aUIH5gvVVSW6v4geNAJQCdO8vbln5+4fZ2TNs2WdEwA8+bFS5YQhhMq3PL5ub+5pazn7n2F/80q6m9REAEREAERCB5BHwnAJ1TffePf7K4sLT05yXTplXb08JILWdaAuUVVtmseitQIf9ACUDTu2bf+w31dFv9jU3WUG/Pvnfa6x07fRtGpvubmgbh5/fdp6+95vK9dtE/RUAEREAERCBtBHwrAEnkrGXLSnY8/dxP4Oe3pCgYLGKYjcny1k5Gj9NuyEkM/8B6xBAsmWyXnHhNAjD+yxwbCsPPr8kaxMifcbgihnXBqN9wf/9Ix6ZNdxdasU89dsUV5itE4u+uaoqACIiACIiAMQFfC0DnLD69Zk3dlvv/+SdkEzk2L78gbySC+IGjZvEDmUuVYWNKp8/IyWlhCUDnLjLfUuwNtLZYDOtiGsg5L4/Cr8gajUWtzk0vroV4PGXVFVesNz+q9hQBERABERCB1BHICAHo4Hj/jTe+Y6in99ayuvr5fEjb8QOdN6fYMq0cg0hz1bA1ngFjiipZ8bYEoIvLiFiUXNXLYM7kZlq4spcjzj07t7f3Nzact/rqq+80rav9REAEREAERCAdBDJKADqAjr38ii8Eq6ouD1RVh+xpYRf+gSVVVXbYGKSkc5rL6q0EoNnlHYbgs8O6dHWZVcBetp8fRpjDbW2R1g3rf/bsNb+42LiydhQBERABERCBNBLISAFIXuesWFG+7cmnrg7W1J5RWFxSGIsMmftpIa0c89xyRLAAmUWyuUgA7v/qxpC5gyN+zJfsJv4kcyQjbdsIVvc+VDgSO/PRH/2odf9H0rsiIAIiIAIi4B8CGSsAHYRnr1kzd/v9D9xZNmv2/8uzRvPs2GyGaeVywT9QAtC5U165jcfPj64DjDnJ26tz84ubR0aipz7+ve89+8qW9S8REAEREAER8D+BjBeADuITrr32/daIdVOwdvrMGFLKufUPDM2anZVp5SQAnTvk5e1Y+rYG135+BUjh1tfU0NO9c/uFq6+66qaXW9RfIiACIiACIpBZBLJGABL7u5YtKyx8fs03ikpDlxSVh4K2f2AsZnRF8jC6U1JTY4eNQX5iozqZsJME4MtXKTo4YId1CXd0YBQPw3gGJR/uAgzrMtTVFe3etvXGkve/9/zlixaZB6U0OIZ2EQEREAEREIFUE8gqAejAO3ft2pot9y29PjRjxkfzCt2llaNjP0PGlNVlR1o5CUDLHg3u38P0bS3WiOGCoZfSt0WRvm3jxhWBytDpy7/+9d3OPaatCIiACIiACGQygawUgM4FOX3VqoObVqy8E2nl3sAA0va0sOHID9PKMX5gsHZaRscPzGUBSD+/wfY2O56fcfo2jAQzrEs+wrp0bH5xZ3Q4cvqq733vMeee0lYEREAEREAEsoFAVgtA5wKdcM2vPplXUPDLQFVVzchwFKNAw85bU26Ly8vtsDGBysxMK5erAtD282tqtCK95kk48uHjl19UaA22tvZ379j+7Sd/+n9XTnmDaAcREAEREAERyEACOSEAeV3O37QpsO7u+74bqK78cmFJsJgZHjhCZFLoH8iRQI4IFgaDJlV8s0+uCcDo4KA94seRP1M/P073ckX48EB/rPPFTbcf9v4TPn3zokVh31xEdUQEREAEREAEEkwgZwSgw23x88/P2P3Aw78rq69/L4Qd0spBCBpOCzMESOmMOtgMOwiw06aft7kiAOnbN9DSAttj2aGADC4KhT0XeOD6j3Zu3PjMaHHBJ1Z++9tbDKpqFxEQAREQARHIaAI5JwCdq3XKPfe8rXvH7ttKamsPpHhwEzamqLQUo4H1WDVcjdBw+U6TvtxmuwBkTuhwB9K3NTdhBG/A+BrYfn5Y8NPXsLsl0tdz7opLL73XuLJ2FAEREAEREIEMJ5CzAtC5bidce/0ShPq4oriisnJkOGK8SpT1AxWVVmjWLIt+gn4t2SwA6d/X19hoDfV0G+O307cVIX1bR3u4Z9fOy1dd/sPLjCtrRxEQAREQARHIEgI5LwB5HZesbizd/MgdPw1UV51bFCgpcuUfCP+x0mnTMSJYh7RyJb67LbJRAMaGwhjxQ1iXtlZzP07Hz29wYKRry9Z7qmYe+qn7v3Rmj+8umDokAiIgAiIgAikgIAE4AfKSdevqtyx98M+ldfXvQGJY92nlkFu4dPp0CyuOJ7Sa3j+zSQCOIqj3QGurnbuXIt2owM+PvpuI5WN1bdm8rqAw72PLL7lkg1Fd7SQCIiACIiACWUpAAnCSC3vyrX86brCz46bgtGlzuaDAlX9gWZkdNqakqgqiI/14s0IAYpFOuKsLWTwareH+/kmu2OQv0c+P4q+voaED2T++9NgPvnvr5HvqVREQAREQARHILQL+GaryEfeNf7lz6+K33fvzHfmP9iCA9DHFZWVcKmq0Wphicaiz0xpG2rFCTAkzvEg6C0fKBtvaJu0CV8GyTLYKmu9BAKP/gUnrpupFCr6enTusfog/01E/pm9jIO9IT0+ka/OmKxdFLz7hlu+9+4VU9VnHEQEREAEREAG/E0j/EJXPCS1ZvaVyyyP3XoPVwqdhNKnAjX8ghUiQ/oFIK1cQSI+QytQRwNjQEKZ690C8tmI23jCf87ifXzQSGe3esuWhiunzzrj/S+e0+vwWU/dEQAREQAREIOUEJAANkZ/z1AsH7Fr16F3IE/xGZBPJizGbiGn8QIwCMog0F4uk2j8w0wSg7ecH0dff3Gw84sep9gJm8SgssLp37tiWN5J3yrKLv/K04aXVbiIgAiIgAiKQcwQkAF1e8pN+e8uHI0Ph6+DjVxeLwD/QTVq5spCFANRWKv0DM0YAjvv59Tc1WZH+PuOrwvRtBcVFDALdgxRuX13xvUtvMK6sHUVABERABEQgRwn4O4qxDy/KPWcv/tsh82bPxyKRSyH+woXBUiwwNcNIYdO5ZbPVtXWLq6DFPsSQ0C4xgDOZkI2p+CNzskdImGjb+rXXHXbQwhkSfwm9LGpMBERABEQgiwloBNDDxT1706bpDUsfuqmkuuZELKTI52igaX5hBiRmyJgyhI7hatVkFT+PAHLBTP+eZju0C7OxmBQKP476Ydp3tGf7ticC9TNOfeCcc3aZ1NU+IiACIiACIiACYwQkABNwJ5z56BNvbHnhuduRGu4wt2nluFKY/oFccWs6kuimy34UgBTJXJlMP78ogjqblpfStzU2Nlgjo5/611cv/JdpXe0nAiIgAiIgAiLwMgEJwJdZeP7rA7++6cyR0ZFfIK1cNVcLm45q8cBMJxeCf2CgEvEDE1j8JgCHuhnPD35+SONmWjhaynA6SN820N/U/G1M9f7MtK72EwEREAEREAEReDUBCcBXM/H0ypLVq0t3PPHUdwtD5V+CaCliOBPTaWHG3iupqbFCM+utwtJST/1wKvtFAEbh59fX3AQR1zFp3EGnvxO3HBFl+BwwjPVs33r74R/8wJIbjjhiYOI++lsEREAEREAERMA9AQlA98yManzquedmN69cdUtJVfW74B+YZwcxNgwbwxGvshl1ViniB/JvLyXdApCjoAOI59ffssd8RJRhXTDiB0E82rNr5wtFleWnPHjeeZu8cFBdERABERABERCBlwlIAL7MIil/nXrfA+/o2bHt94GqmgMYO9BVWjmscrXDxmBU0Mna4baT6RKAzC7C0T6GdWFWFNNip2/DIo/+5qYWjPwtWfaNr/7dtK72EwEREAEREAERMCMgAWjGyeteee+78befQyM/Ki4LVYxEhoyzW/DA9AukfyD9BN2WdAhA+vfRz4/+fqaFWVPykXYu3N0VHtjT/OMV//vty1B31LS+9hMBERABERABETAnIAFozsrznues2FC++/nlPw1UVZ2dl5dfGIMQNPYPhD9cKVYKlyJsDPPcmpZUCsBoOIzpXoR1wQpfN+fFfMOjsehI17Zt98487G2fuuPU47tNz0/7iYAIiIAIiIAIuCcgAeiemeca57z44gEND/7rDyW1046KDUfy7NXChv6ByEds5xYuhY+gSVq5VAhAO30bfPyYuzeG2H5GBX5+9upeTPf27NqxIThr5qn3nXnmGqO62kkEREAEREAERMATAQlAT/i8Vf74HXe/v7+n8zfFobL62BDDxhiKJxy2CKuEQ/WzrJLqagZF3mdHhvv6rPYN6yddeev4FdJfb+/C92oOO9wqDoX2fuvlf9PPr7MT072NrjKb2OnbAsXWYGdnV7Sj+0v/uuQrt7zcqP4SAREQAREQARFINoF9K4dkH1nt2wTOWraspHVHw/9AcH2zIFAc5DSq6fQphR9WGVuhWbNsQTgZ0qGuLqtz86ZJBeBk+7/0GgXgQQdbmK5+6aWJfzB9W19joxXu6oSn3qsF5MR9nb/t9G2Yvh4eGBzGaOGvFh51xNdvXrTIPBK005C2IiACIiACIiACnghIAHrCl7jKTCvX+PC/rg5UVJ6CIb18ho0xFYKcSg3W1iKjSL0dPoW9Yl2Kvz4kzRgeHIyro0XBIMTlbFsEOllK2C+s0LUG29uNw7rY8fwQ1mUUSrFnx/ZltW9+05l/ed/7muLqlCqJgAiIgAiIgAh4JiAB6BlhYhs4+5lnXtOw6qlbg9Nq34hp4Tw3YWMYNLkMsQMLse1vacEq3MSspQhUViIu4QykbRsa8/PD1rTYYV2K6ee3a1tJZfnp93/mM6tM62o/ERABERABERCB5BCQAEwOV8+tnvzHP38k0tt3XXFl5YwY8uW6SStH/73J/Pq8dMptm/YCD+Q5Dre19mCa+Kv//ubFN3g5vuqKgAiIgAiIgAgkjoAEYOJYJrylU1auDA5u3PQdzOtemF9UGKAQNJ0WTnhnDBscS99WYmGqODrQ1PD7acce88U73v72+OagDY+p3URABERABERABNwRkAB0xyste5+1du3MxkdX/iZYUfG+kZHRfISOMV54kbIOY9SxoAjp2/KRvm33rifLF8w79Z5TT92ZsuPrQCIgAiIgAiIgAsYEJACNUaV/x9MfffSt7Rs23hqsrD6U/nhu/AOT2Xv6+dHvsHdPU0N+nvXfD37xiw8n83hqWwREQAREQAREwBsBCUBv/FJfe3Q076Q/3blkNBy+vCAYrIohbMxILJb6fuCITN9WwLAuvb0DA91dP1x+0Zd/iNA0ZjFh0tJjHVQEREAEREAERIAEJAAz9D44Y9Wqis71G3+EtHBL8kbzCqMu0sp5PWU7nh/Tt43GRnoaG/4y+53HnHvrUUf1eG1X9UVABERABERABFJDQAIwNZyTdpSznl13cPMTj/0uUF19FKaE8+xUbIaBmV13yvbzK7JTuPU17F5fVj/3k3ef/vEXXLejCiIgAiIgAiIgAmklIAGYVvyJO/ip997/3t6mhhuLK6vnxJLgH2jH84Of32DLno6RyPD5D110wW2J671aEgEREAEREAERSCUBCcBU0k7ysZasXl3U+J8NF46OjnynMFhaFg0Peg4bY0/3lgSt4b7eyEBb2y8OOW7RJTcccYR50uIkn7OaFwEREAEREAERcE9AAtA9M9/XOHft2prGx578ZXFF+SmjsVhBjP6BLqeFGfi5AH5+Vn7+aO+uXQ/P/H9vPu2Pixa1+f7k1UEREAEREAEREIEpCUgATokoc3dY/Pzzr2t54qlbSiqr30QRaBo2Zix9W8Dqa2rcWjyt+oz7zzxT6dsy9zZQz0VABERABETgVQQkAF+FJPte+Ohdfzt9qLf3F4XBYK3tHxiNTnqSY+nbAtZQT09ftK/3kocuvODqSXfUiyIgAiIgAiIgAhlNQAIwoy+feedPWbY2FN75zLes/KIv5xcWBKIT0srZfn7I24sVxNHBlqbf17z1fRfcsei1feata08REAEREAEREIFMIiABmElXKwF9XbJuXf3Oxx6/KVBeeQJEYD6bLAiUjPY17n6y9uADT73jpJOUvi0BnNWECIiACIiACPiZgASgn69OEvt2+qOPHzfQ3Hg3FokUxqKxT9x75ml/TeLh1LQIiIAIiIAIiICPCPx/9LZZ0UZyLiQAAAAASUVORK5CYII=');background-repeat:no-repeat;background-size:contain}.ipfs-mid{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnhJREFUeNpsU01PE1EUPdOZKWUotKUKFLEWkQ1EASGGxGBi4sIVrt27IixN/Cn+CxfVnQsXJiz8IAoqRBGEaMUUWzofnXkz781436QDkjKTyXuZe96595x3rxJFEeTzaKW6dmdpfIoxjuRRFECGn7/4Utvarj/syWgflU5s891qvGoJePJasfBgeSpnW+yEIJVS4DEBx3FzGT2qfvh0tJxOE4mCU0yy8X3BLdODRQTJZ5oMzYaD0UuDePzkbnnx1mjV9/lMp+izBKEIwQMOzvnJGoYhhBDgFKtMjmBl9XZ54WapSjLnknMnEkQYgflCVhKXLt+/dRMy2d5OHdVnPoxeHUtLV8u2w5/S78UzBJwLMC8gAsosIqy9/ga37WNmvgKVKmEkb7JSwI3pIdRq1kBXBZJAUKkb6wd49fIzbJthdn6cIhE0XUWbyP4cmshmdZAE0eUBD6gCN0DtZwM7Xw+RUlVEJCui7CmyPaS94zC06ZMedREERNA6djBWHsS9+9fRS3p9AraOXbhELMlUQju2G2O7JAQENk0XhpHG3MIVlEZzaDbdOKO8jWy/TraGsMmL4L8KTgnIfcfy4JBWeQNp0j10MQtB4EJOg6qFMI/bEH3pGNtF4LOAjHMxO1dGvW4jXzDi7Iw60TB0jJRyONhv4MdunbDneMA6BMPDA6iMFzExcQH9AxkUiwby+QzevtnF2OU8lBT1i8fOa2UO1/FwdGTHE2STHM/14+vlPOz0RxibKPfn9AHXZHBzYx866ZdTKkuVndhHuqenS1h/v4ffvxqyvbUuAtPizZ0Dp7X1fTs+FA9cMnWd4ZG90NOjomVFzeTcPwEGACDGeYddZX86AAAAAElFTkSuQmCC');background-repeat:no-repeat;background-size:contain}.ipfs-mp3{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnxJREFUeNp0U89PE0EU/ra7XWxpSsFYIbVQf9REFBHkYBRIPJh4wrN3DsZ4MPGP8b/wUCIHEw5EY0w04o9ILcREGmwVgaXbbXdnd2bXNxPahGyczebtzrz3ve99740WRRHkWn5cebu4cH6SMY7e0jRAHr9c3WxsVvcemmbys9yT6+uHJ8oaPefypdPDD5Ymh5w26wMkEho8JtDtuEOZFCrvN/4uJZNGH0T59D58X/C27aFNAL3Xthmsww5GCyN4+uzu+OLtQsUPxPQx6ZMAoQjBAw7O+bEVCMMQgqygs+LFs1h+dGd8bna0QmXO9OL6JYgwAvOFZKKoy3V44CgNfv7Yx8oLH+lUEgvzF8Ydhz+n41snAGRG5gUEwClzhHdvttFxfNyYK0EnJozKK5eGcf1qHo1GOxtjwI+pfvm4g/W1qtJgerYE2SXJSIL9+W0jk0mCShAxDXgQKgbNXxZq35vQKCiKQkSUXdc1+gcch1FHGPmKuIgBCdc66qJQHMG9+1NIpUylxxHtuW6gEiTIu+N4yjdWgty0yTmdNjFzcwKjY0MU7MLt+IjoSad16FoIx3b/A0DZ7FYXnsdpAjUMDOjI5zPgfoBsRodhhGhZHfBBU/nGAGRtxWIOg5lT2NtrI5dL0SB5KJzLodloqXaOEatPGztKq5gG3S5DNjuAK5NjKJfPYKI0okBkSdemCiSgS/rkQNLSePtxBj4LSCwfFtE0krqqX7ZVMnu9XlMXy2l7ME0dzA3iANQyY6vWxC61UY41zTyNcYh6/QCNXQvzi5dR39nHVq1BUyuMGAARsF6tbbe4iKD1r7Om5iFBdmW1SsDflLiuB6sX90+AAQDHAW7dW0YnzgAAAABJRU5ErkJggg==');background-repeat:no-repeat;background-size:contain}.ipfs-mp4{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnBJREFUeNpsk99r01AUx79psrTrujVtbceabnZs4DYRHSoMh6Dgq77rn+AfoA/+If4Bok+C0CfxVRDBh+I2NqZzrpS1DVvbtU3SJPcm8SSlsJlecsn9dT73nO85V/B9H0H78OLdt/LDlQ1uMYybIAgI9n99OWxoe83nkiz9hDDae330JvxL48O51Xxm/enNtKPbVwAh0Ec6kYpXat9Pnl2GBC02HrjM5Y7h4P8+7FtIFVJ49OrxUnl7ucIdfhv+BIDv+fBcj7p/tXMPrs2RXVTw4OX2UnFTrXCbbY7tpMsA13FDSDAOQ4gJEGUJLs0PPh9CkESsPrmxxEz2lra3rnpAt3G6adgdQhBpmeLkFodNmsjpOPoXBrQTDcmFFNS7i3MRDzzPCw/vva8ikU+COQxm14BBhvJcHLGpGPTOAJxxeLbrRgAkYujBdH4G5oWJWXUW19YL4XqunAMFhnq1BqWYgaY1MAHASQOiU96zKzkU76mwehaOvx6h9uMv7KFN3RopL4oTAI4HRh4wSl399xla+00YbR3yrIzM9SzSqgJJnoKcklGrH08CcJjnBtLLCsSEGGpSWJvHtDKNoFippsJ0ulIsDDUCCATMlBQkNuahEyiZTcLsmFBKaQxaOk53TlHeKkM70AjAooCghBOk9sKtIvqtPqS4FBaRnJSRX8tj2DOh3lFB5Qw2ZNFK5LRo6w4sKt2ggAzywidAMN/9uIPSZglBLDO5FF3mRD3wHE9qVRvoHrUpfn+UEQK0/7ShtwboHJ6jdH8RZxSC57hSVETb7e5/2u0FxqPHJow+8iZ4lYY2QGu3idhIxO7Y7p8AAwALCGZKEPBGCgAAAABJRU5ErkJggg==');background-repeat:no-repeat;background-size:contain}.ipfs-mpg{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnxJREFUeNpsU0tPE1EU/ubRdlqmnUBboa0UeUQDiUGCC1+JmrhxoXt/gBvXJi74If4AV0Y3sNKF0YUaICqoIfjgVShEiGF4tDOdO/fOeOaSKtie5GZu7pzz3e/c7ztKGIaI4vn9p+/P3h4e4a6Pv6EoQBDiy7P5rc1P1Xt6XP8M5ejXo6UJ+dWbuemeTGdpvNdiNe9YvQLe4Bi4PmTpRmyq8m71rp74BxKF2twIHvAo+f/l1T2Yp0zceHizfOZa/xRnfBRhG4CQqAYioBWeXDyA8Di6ei1ceXC1XBwrTXHPH2vW6ccBBBMI6BsSUEQzakGL6xB0tvjyBxRNxdCtc2Xf8R9TyaWTDOg2TjfVdw6hqIoE9B2GxkEDWlLH7s4ette2kSp0oDRezrQwCIIA3oGHr0/mKMmE53qo23W4+w5S+Q5ohob9X3tgHgO8ULQACC7gMx9mKQP30EW6mEHpYi8xcJEdzMucjfkKcrTfmqmiFYBxCF/Id+gayKJwoQjHdrA5v4HK7Cq44KjZNWpagaqp7QACks0H9znW365ia24DzoEDozOJbH8eVtGShXHTwNracnsG7q6LzsEuaAlNPm9h7DSSVjLyCMkppDI+GS2StQWA1RlKo0X56n2X+6QHkmkDakxF9WMVqWyK+s/BrthYfvWz1Ug+zUDcjMPMm0h3pxEjFma3CbIuCud7oMc0LL1ZgmElpGJtW3B+15HIGNITrMYIlOH7i0U41NrInREylYbu4R5qQbQBaAh95fVKZCnpQCnb9DrWZyrRERS6NDeUw+yHaXh7rt4C4B8y+9vkwn7kwKNRpDoa9aiFKBYnF+RcREqQ2e1m3R8BBgAy9kz9ysCE6QAAAABJRU5ErkJggg==');background-repeat:no-repeat;background-size:contain}.ipfs-odf{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAi5JREFUeNp0UktrU0EU/mbu3FfE1KRRUpWYheALNBURUVy7cy9UkO6KW/+Lbt0IPsFui4gLBbUqFaUuXETUKCYa0jS5yZ2ZO557b5MmTXpgmDPnfOc7jznMGINYPi0de5UvmpORxpjE/kbNqW005DVu8TWw1H758ZfkFgNgJmtyxSPRjJIj0QTW/RDiYGXGb7Dl32/eXrVsd0gSCx9miqC0ooCdp69g5Q/h6OLN0ty5ynIkwzMwUwh2FwMdcbDiCZQXlkqFCpEoPT/wih1YjLInANcD+/Ua9bu3wJlGvrBZCmet2+S6ME5g4oGlZ9A/I70XCDhhDexPNTFmswJBwcnuXkF86VSNZxVu0ukLSGnBcqlnN4HoCQIaIuIv7LUooMOgQ7q75LAAb59B9gCBHSKgqemRr94mMKmD24CfM8nb7THYGQNLpAkUkcb66JyGBFFEWRVL57gFEH5qj8Lxwca2qS3EZaugmzAw24dR/XQgwtsCSBjPIdWbUoE2UJLBnV8Ac/ciWHsK9/glWLnD6K2vgPszsOdOQdfeQ1c/ThKoTgDn9A3KUED/52d45xchZsvorD6Bf/Z60riV3Q9Z/0bbGU1uopYGkfERSQ3VbsMwl0qlqoIARmSoPYXWy0dor79LfBMEEd8jGs/uQ3Yl7PJFNFbuEXiV2riCf88fovXhBbo/vqP3t02/ZYmJFqTkzY160Go9uEMbFK8hR/NrdXtFuUVmnmySVGgO4v4LMAAjRgmO+SJJiQAAAABJRU5ErkJggg==');background-repeat:no-repeat;background-size:contain}.ipfs-ods{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAetJREFUeNqMUj1IHEEU/i7u7Z23e8tGgneGQPw3hZDkkhQiSuwMQREba4uUgpVlCrvEQhurkCoWqcQQ0oTAaYKNqJygGEwgHCSB6Knn7eXcdX/GmdHVPWYFP3gw78173/vmvYkQQsAwNvckq96UnyIEh7/d4t7uUd/8y+85P+bXSX4grkhI6nJYPW7LrXpBK2YxiSoShhu4Buq1NPofDeqdrZ3Z4cl7D4J3UtA5VyVAlmJoru9Af2ZAp1lcCQ3nqgiuKmbY3l/BH+MnHM9GVLP0Ww3KNA33CQoQQnL834Fj74PUGkANEIkCSSsa8gQqgYTIcB0PVsXB318GInRiCVWCkpRFAs+j5gKlA4t29Ggh4d0t04FKt9PQqF4UFgumSEA8ApeaElilWbYRVy/lsns/N1QBkxtENF4jxPxcgcB1CZVOrvMteK5IQDtJJIGh++PcX9iYwWjXK37+vP0WdYk0Ht99jtX8JywWFkQChw4tc+cZcvlF7rMze+ubbxN40fMalRMDP/6twaiUeK7wlZ0TD0a5hLTWxo2d45KKprqHKJslTsy209s2wnMFBTYNZjc/oLt9gPvLOx+hxVJIKS2YW5pCbSyJTGMK775O8VyBwDJd2LTDl/X5i8v3S7NVw9vJb51tITDEUwEGANCx2/rXEEFFAAAAAElFTkSuQmCC');background-repeat:no-repeat;background-size:contain}.ipfs-odt{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAepJREFUeNqMkz1II1EQx/+7Ca6JkqyYiJ8cKEpAQbBQFDm0sVOsFBS9wt5KOTgEG5twxVlZ+XEnKNiIghYKxx5nwEpIIXaiSAgKGmMi0d23u8+3T7OaZJEMLG9mmPnN/w1vBUopLPNNhRWXHOyDg0nx82TiJtZPlPVoNpftc2cTotcHtxx06kdXpSQ/BvzKESZzIDmAz6y+NojOjpDMZiqRPIgNoFyWM8DrKUV7axO+gcp4g7AzmquAdVNqOgL2z2I4id1B0wgeygOyt/rLL5buLwAIDgA9dY+L+DkuDQOCrkMgBsRglcMOqAGwIstMg8AkGsuZMNUMRMkLqE+QGloglvlA7uIOAKvZajR0qJkUj/XHe0BTIclVKKlrfKsj9qA8gA6wqSJzPaXlr7ky//tdLEUfawsBjExUFGVWbT7AxSa42H2LMfODmvd3wKb7RAMLYwM8nts8xJ/pEe7/3PmP2eGv3D+9usb35W0bINoA7RmjXSHsH0f5Z/mUSZ0Ir2JmsBtD80s8/rGyzWsLFTD5yUQCbfUBHl9d38LvkdDTXIuHVBo0k+bbt06qO+yAPGXwe/cA4wO9PN44jKDG70GougIzi2tQ00ms7/3lpwnBBgjZ37Kkd1Shht5XzBIFl/ufFtniT/lFgAEAU//g6kvdGBMAAAAASUVORK5CYII=');background-repeat:no-repeat;background-size:contain}.ipfs-otp{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAcJJREFUeNqMkssvA1EUxr+ZjkdbrfFKVD12ErYSRELY2fkH+BMsLcQaSwsrSzZi47EjJEQkEhYkFlhYSVtFpdqOqpk717l3jKZmiC+5mZlzv/s795wzCuccQncz3YeRBj4KHz0/RrOZe2NsZPP20o255zQ3EAxzEAC+6uzTw13G4TFQAakA/CWtIYbY0KBOrx7IvwDQqlHV1o3YxKTOvyAUvfQCfqmA3e4ikyS/zRAKvOot7eoSHEgZIHrCfQAfBqBaKQQDKScQAExd8emBANg+2U2CvNMkkgSqBmrCxFB8mujeoJBWwEqARcssKTAJEGrmaGrjqK1zvNknH4BtyxKl2VUpRxmj5W+x73q9AEaZrR/ND1EJluIpS3i9JQiA+a+hSq8HwJjTsLrRaWitPTCOlhEZn5N75sM1qigmlN+dB3u++Qao5W4TtbEXXIsiszGL4PA00itTsu6XnQWo0TjMTAJqfMDx/ryBJcaVzSNSH4fW0Q+rkIf5rsjRiid7yyN7uoXS3Zn0egE0NiORAN9bQ017D1Lri7CLlP2EDr3Rf7C/itzV2bfXA/igLDaRixfngFhSCooH2xVPCWBlwKcAAwBX1suA6te+hAAAAABJRU5ErkJggg==');background-repeat:no-repeat;background-size:contain}.ipfs-ots{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAfZJREFUeNqMUk1rE1EUPS8zmabJdDKB2glEwY9ExJYiBUEQpV25qgtBXfgbpEtXuujKf+AfEKRddOdOGHClbYVCvyKWaijT2mhjphk7Sd7Me76ZONp0EsiBYWbOvfe88+69hHOOAE9f3zTVnDKNHvhlsfqPw/rM0ovyWsRFdXJEpDIyRnSlVz0KSkmvabaJeXSJBEhgAJzTDNybmtUnS5Pmg/lrN07H5NM/f13FoMgpXDSuhiIiK3Qi6LUugX7FAbaPPsJqfIHHKCStqRsXVFPQuZgD9BBxjikSiRq41AAkgCQBzVf0+BWEBX7GBm0xgHHUqk1UbBuEcIydzyCZlOI9YEGuDxwduCCitS3Xh3viCZ4jrcq4PJ6DLHd67tjtuAAXib54dCPVEfQ5XIcik/0/2iDeOYz3ceCxrisMi904y0XiMQFfkB7lg6xFHwFxEqUMV0anUNBLWKm8xd3i4zBWOzmASx0UsiW831mA59Xjm+h7HCOygduXHqJatzA7Poey9QnXjTuoVD/j/sRcmDOWLgqnLC5A2wwST+Pn8T629lahSCo291bwu9XA7vcy3m2+gTaUR14thrk9BXasbdiOjSe3nmPpwys0xSi/HpbDd3bIQC6dx/q3ZbRb/j8BEi3Po5cTJpHI9CBNDEa++GyDBN9/BBgAwfDlCVUQaNAAAAAASUVORK5CYII=');background-repeat:no-repeat;background-size:contain}.ipfs-ott{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAdFJREFUeNqMU89r02AYfpJ0iVm7EqhVOxw7dDBEdpiCE1RoEZRddvUgbIex/Rs7eehppyF4LOzQu4MxwYp0HgShIuwwUVSCVtl0s13afl+SzzcpyZYmyF74eN583/s+PO+PSEIIeJZdrtQVI19Cgmk/Ph39bpllXq82g7sgLxVcyKNZpIx8Uj5u5zSjc9Gov8ZihCRC8D+7On4JczevGeTGSEIC4ctKJtB1DTPXi1iCCEkIm1EFlC2Em0iwtWfinXkIzjiO0jljtDC5TtflGIGUQMB+mfja/oPv2Rx9MMjpMdJxOXyXTwkcwIkewfqQ1QtQNB385zcI14FrtQexsSb6SRysZ4Fbf+F6eHwATc9gJGNAm5iCTL5n/LCVRGADNoeaGoHqyaXj5gqQlTODovcwNk5Aj6wXqV8eCo7EDhMonEHpW+dZC7gUG98D3geo7vkb01h9cAvPdt76OGy1xntUd3bjUxAk3+l2sHJ/FgtrT0MUJNfDSm0bjQ/72Hzxxo+NK+h3B7XRNO4UrwymQtMIkdTBU0m+sBOayLsn8Ka78mQDjx/e87HXPkb1+UsfP37+AmZ1fP/suknBb6nefVQXjl06TxMlJfWKNWr+Kv8TYAAkUueexJF47QAAAABJRU5ErkJggg==');background-repeat:no-repeat;background-size:contain}.ipfs-pdf{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmhJREFUeNp0U0trU0EYPTP35qYxaW6TlDapNKWGbgo2FkF8rARB6rboXusf0F/hyq2U4krFqugqSBeuAyL4SERBstHa0iR9JKZJ7mvu+M0tqZGkH3x8987jzDnnm2FSSqh4ns0VU1ybFzj674Wa3uWiWbfsFQb+jrGj8Xvbm0HlvYVRxhJprpmTlGmum+OMm5uNPZNbtjk3l82ey8++8oW4Jv/H/wdA456g2kvH99FyHNiuAz2dwflbN8YW8zMK5Go/CMfQkAhpGsyQgRCtlpE4jIULyC9fHzu7MPPEl/5ib6WOE0JJNRiHHg6j86mMjw/2gG4bkbY4PW4Yj2j64skA5FTHdaEMPiAJszt1sK0d4suJmY4k0+IDDGRfqmh0u5gejQc+fG8eYCIahRQCEfgQnIuhEkgtONE+dGxYxEDj1DhiEycZ+1YXdUpHCqTMJIYyEES5aXXQsi2kYlGEia5GtHVKn+amPBeCutPgfLALPuVu+xDVPw2EQyFEjHDghbpYNm1yKVVnYjTOerepn4E6XQmLGSPkPkOXWATMSDcjQEkAaqOu6+i/rccALtFL53LI3r0Nq1ZD4/MXZJaWYFer+PXiJc6s3IEgY3+uPYZHTAcAHM+DTE8gnM1CSyaCulv+GrRy8uYyElcu4XfhLVpkpNtn/DGA5Uu0abFH36WnzzCayWAkmYJvWeCkfb9SwY+NDbSoOx4bYqJF8rZqVRRXV/HhzWtUSmWwmWl0RmN4v76OUqGASrmMOkntSHF8MOs954dT08W248wzYsJDOujRBAaqqikTpRo/qqd0/dv97c3Lat9fAQYA4z8bX9nTsb8AAAAASUVORK5CYII=');background-repeat:no-repeat;background-size:contain}.ipfs-php{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAhNJREFUeNqMkltrE0EUx//ZbDaXNrvZzdIkbYOXGgxYQlCK2IIY6EufxGdB8Av44AdR8AP44JOPBR+Ego0PClUKTTXQSmkTYtOkmubSJrQ1e3H2yJSEJNIDs3PmP+f89pyZcdm2DcdWvn7LzkxFHmCIra7nm9ulg8yLZ09yXON55Dgjt1PM2iPs0+aW/frdh8bzV2/SvQBnCLiEqcFxLKSSodlrU9leiGPihWePBkgeEZO6ShC2dCAZNuf6ADb+ldQ5PUPx4BCFcgXfdwq4Ph1Dtd5CZi4Nw7SQiMdCXkl6yVIy/QBWgcU+yx/XsLK2cdHndqlK/lZxH/OpJO7fnsWY3z/YAq+g0TmHpoUH2vB5PXi8RD9Fo10aAmDJTgWyIuOupmK38rsPcOvqJO33XWEvwLJsmKxHRVEwf/MKWl/yUMf8mIloWN8rw+sP0D6PHQmYuzGNgCRiMZVA17IQV4OIaTI8buH/AJMFd02Tkp05PO4jnWvc57EDAINt7u1X8Pb9KgI+Lxbv3cFR8xjx6AQ+b+Txs/qL9KePlih2CMBCq92hg2qzt1AoV7H5YxdhdqhHzRbgcpFeqdUplpvQW4FhmAixZ/sws4BoWCM/qmsE5XqE3dDQCrqGAYWdejqZgK6GUD8+IV9VghBFN1RZJv3sT5diBwC15gncggCPJKF0WCPN8dun55jQdVpz3Ynl9leAAQAJhiGatD9AOgAAAABJRU5ErkJggg==');background-repeat:no-repeat;background-size:contain}.ipfs-png{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmtJREFUeNpsU9tOE1EUXXPp0CAUWmJbC04xBANNTF+kKhG8fID6aqL/gPEj9E0lIf6Dj30HL03wxQtVIC0QKrWxNG1Dk9Z2Oj1zxn1m0oIZTnIyZ8/ee+211z5Hsm0bYg29fLGpxWIJWBYGS5IA8ncKhT9Wvf4Yqprtu+w3q85X7f9QxseD/pmZMZsxN9fnc5JNw0ACGGv6tPSvyvEDKEoWZ5Y8OHHObKpucw4B0t3agnl4CJPs2YkQVu4s61ORaBqMJc8CDBiIRhhVM9bXYdVqYAcH8M3NgS0tQQsFcfdKHEbvlr6WyaR/V6uPKPy7B4DT7lUq4MUipMlJ2MPDUKtVfKZ2nn/5BoNbkONxXeb8LYXe/A9AJLNWCxgdhZJagDI9DZg9qIkEytRSkdqTSFQtGILSbgc8LViM+tc0yPfukzIyOJ359k9YR0eQdB2KmBbpwXoM3Dod1SkD+scpEapCI5DdpsJhIJcjajQZagcjI+5oLe4VkeQnyiZgdIH2X6BJ7dSqQLfrggjw0AQwP+/GegCIHppNoFAgEMO1RZKo7BQgRi3yN05cnwdA0BQMAgF3C6pnbuNg92M9AFT1diSCh6kb+FGvo2MxnBB9ocZxp4Mns1cde213B81e7xwAcl4jkaa0IUSjUdLJwkL0Ej6VSvArCt7l81iku6GrKnYEU89VJlSJRmR0Dax+fI9suYxSo4HlWIw6M3FBlnD9YhiXabyOsOeIqG7TzDeIYo6EDGp+ZPb2kKKqH8h+mkxiI5/D1/19J3bwYPvPWXq2skkiJVxesqt0XzghpKM8nRVV2Lv2q9eLIvSfAAMAaacnllcFBmYAAAAASUVORK5CYII=');background-repeat:no-repeat;background-size:contain}.ipfs-ppt{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAkhJREFUeNpsU11rE0EUPTM7ySZpmzT9DNamWAtFfSiCigr+AxF9zKtv/hvf/Aki+FEi6ov4ItWHPGiwiBUKoUqqTUJImmR3M7Mz3t0kNe1m4LIwc+65595zlxljEJzdR5uf5nLmsvZx6gSvtd9W9bjhF7jg5dH9nRc/wq8YXaTSJptb0xklx7IZoKUEz1zJ2DUU69/37vFYrDxegJ9U0lC+AoIIVGg9CL+vIObP48KDQn7x0sWiVnJrnEDg7KGk+i/Ac4iUM/R7BsmrSSxtXMfa3X7el8+Kjf3KfUJ+iRJQw4w0Tc8BRyWGRAZY3rBR/VlC+XED2ayDhZyXl03+hNA3TxNQshlGLAnE44zCIL1goXZwiMNvB1i6zbC0KuAsxNITWwgNMYPeLVJiFEO9ArjHAivrAjNzBr4f4vwIgdGD4YUACsZCE8AtYGWT5jCsGQw5wEYJzP/pj5RwYTA1b07eQmfZ8P0sgdaM2FlYwWkMgMpl6NQAO33GKM0wsQWflkh1uqGVmVWblsiDkQyqxwfag35SqcktaEWTUTHYNx4iGU/C29+BvX4Lpu/C7zYgFjegSY63WySsHyXwpYHU00ieu0bAOuJbBTArBkiXKiaAmTzcvRJUV9E8rOgqBwqlY8ASs/AadbRLb8CzeTjVClqft6FdB17tL7yeCbFRBYoLr6vR/PiSEl5BZJaBD0/R2nkOZqfQ2fsKt+0SEQ+GLSIEUvJm+6jbah2+pS2aon+4g/afd4SYJVuA7vvXdC/IHQtSoTnK+yfAAIEaId1m+vudAAAAAElFTkSuQmCC');background-repeat:no-repeat;background-size:contain}.ipfs-psd{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAqxJREFUeNpsU01ME0EYfbtdKKWGtoItRWgJHApCBE2I0YuoiSaaeDJeOJh41YN3TfTixcRwMfEk8eDJGA+Eg0YTTRRMg02KKFooCBbTlkJLS7f7P+u3K9Xo8iWT3Zn55s173/uGM00TVlwZfzJztD92iKO5ouvQGQPHcQDN380vlDPr65fdLj4Oa41i9sFt+ytgN7o7woGOrqgvvpLBaF8vWj1NUAwGTVNRM3mf5vU/zaU+XySQuTqIFXz9hxmGLkoS7r+YxvVnrzGzlgXPDOzUZPT4m3Dt/KlIuH9oUjXYEHZZ/wOgGQZi4TZcGI5hLb+FO++TSOSKcLtcMA0dI0EPrp4+HtnfG5skiUecDGwQE2MjAwiGWlFVNDz+tIyCokJhPKYSX7Gdz2I01hOJdnY9rJ/7UwPGTEiqjtbmJtw4MYx78S/4Wa3h5UoOYwPdIOp2Xi/t18rlFgcDw6o+ydiWVRwOBnCpL0oOAMmNEhLZIgSeoxwGSWcERon/M9DoBknTIdNQNAMnO4PIVGpIFXcwndlA2OtGc4MAxml27p4AIulWSIa9QVadiYSoJxhqBJivKgh5ad3k9gaw6JdlDaqq7q5wINY4F22HaLHSDZQkBW72O9cBYFEviBIURQH7a7MN0uDisUW12ZZcaGlmdq4DwCqeTo1zNtZuW7hUqGIw7MNqSUS2ImNsKEpSdEwt5lGhfQdAkQBEoub3NNrDJfAIeBuRrcrY5xGQ2RFJAjl00I8PCckJUCB9q1URBnk38XEJEuk41tmGwZAf66s1VOh2keqwoUnYpFxHH4iKIixkN3HzVQKP3iQR/5GDKMuYmE3h+fx3MHqh1sMafztHLuiCg0FAk0uFdLqcpGY5QEXbTC/j7mIaVjc18DxufUtBJ/vcggs+3ijVz/0SYABsJHPUtu/OYwAAAABJRU5ErkJggg==');background-repeat:no-repeat;background-size:contain}.ipfs-py{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAlVJREFUeNpsUktvEmEUPTPzTUFmgJK2UqXQFG3pA6OBLrQxamJcaYwuu3Dp0l9iXLvVtRuDpgt3JIYaTVSaxtRHsJq2xEJBHgXmifebMhECXzKZme+ee+65516h2+2Cn2cb2VwyHl12//vP2/zOQaF4uD7GWN69e/LogfNm7kUsPBFaXYwHMeK0OlpQEJApHJTuykzK98dE98O0bLM/UNgr4v32Dj1fwSQRt9dSsfmZcMa0rIv9ODaqYrPVxuPnL1Cu1aEbJu7fvIZUIo4bqeVYRzcyv/8c3SPYpwECt/dmu4ON3Ed4TymI+hQc1ZqoE+F+uQLDsnHlwkKMscJTgl4eJOi9fxZLePNhGx6ZQRRFqH4VjZaGSv0Y6cQcJLpra0ZguIWegqDiw7lYBBZV6xiGk9DQDLzK5bEyF4Hi9VLMsoYI7J6Es5PjeHjnOl5ubqHaaJGBEkzbxplQAKIgDmBHekDTgI+qKKqKLvNApgmEgyquLs1CoFn2Y4cIeLJpkjoCLkWnUSIF3JxISIUsCjAoxhWNJLBIJs3YeXj/08oYZkOKY65HllE/bkMmY504YUd40HUq2JSSyW6iVPmLiXE/ZMYQCU+hXK3h1toqdNN0sEObyKtqtDQ6kXDwcadDS2TBryp4nX2HxXjsJK6bDnZIAZem6Tp5YMMmicn5OC4lztNWtvB9cg+hQABtWjKL2jH/T3GgBcYDXEE6mcDM6SlaJAGMWkivLBC54ZgniZaDHSI4rNSqn7/t1vgkGJPwZXffSeCjk2iUWz9+nSTQN8e6ef8EGAClUi/qoiOc3wAAAABJRU5ErkJggg==');background-repeat:no-repeat;background-size:contain}.ipfs-qt{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnVJREFUeNpsU8tu00AUPU5sp41NkzRxpfSZqi0VIIQqEEJUZYXECvbwCWxYsuBD+ABUFrDrCnWBQEJdIWigBSr6pqRJ1ebhxrE9M7aZmSrQ4o505fHMnXPPPWdGiaIIYrx89GKpNDdxmXkU3aEoCsT+z8W1Sm21+jCpJctQTvaerj+TX7WbnJ+0cpfuX8mQtn8GgJ4AZtIFY2Hz3foDVRcgyt+cRHcS0IARh+D/8G0PpmVi7smd0dLs+AIjwTVEiANEYYQwCHlEZyJgIQKfoX84g9uPZ0cHZ4YWmE9nuufU0wABCSSImMsWEgqSuoqA/39/swZFTWLy7vQo7dDnfPvWWQa8GuOV3IYLJXmyzDzG2/ChZ3pwbHdQ267BKJoYuj7SF2MQhiF8LuDK/Gf0DKTBKINz1IbTbEMzU1ANDW7LAfEIQKIgBsBFlAx6LYOz6MAcvoDCtAVGGPKlAiIu/F55F33FDA6W93EOAOMaMOl7biKPwRtD8Foetj5sYPfTDtxjl1f3Ubo5jkQieQ4ACSUD2iE4XDpAdbUiW9D7UsiN9WNkZgxajwbd0LGzt3keAJPUc1N5SVeENT0Ao2BKV6QzwlZeRBSKAYhe3aYHcZWn7l1EfjyPypcK9LQGa8qCvW9j9+MvaasQOHaRhGWdhsNLR8hwodYWf6B4tYjDjSOovRqq32rSYq/lytw4A77o1V2ERiAtzY5kkUrrsH+3QF2KY87ArTtQuQ6nAf4x6FCV1D001+vYersBM2vA4y1Rm2D7/Rac/TZIw4d/6MrcGAPf9htN0miJh7Lyuoyvr8rQeP9iVJcrSKgJ+TrFcyYebXTP/RFgAFQobmIOBxbsAAAAAElFTkSuQmCC');background-repeat:no-repeat;background-size:contain}.ipfs-rar{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnpJREFUeNpsUktPE1EU/u68OgylZXi0hZACQU1LEKKCMcat7jTRnQsXxsQtv4E/4M74P1iriUaNCw1FgxpjCJQKKAU60+m8mJnrmSll4XCTc8+959zz3e88GOcc8aq9evChOHl/lvMoubvWX/z4+BwTlbvw7bXdg8b7h6LE1gGW+O88CRMt4XTlR6/rYxce5Xv3jlHH19fPkBu+gWy5mlcFb3Wn/umeKOEMJF5C7xCFbtA9dRXjFoYKGiTRAlPGUV1aKU9O3VwNQ74A8DQAIZxqAuAhBPIMFYpQVAVB4CPSZjEzv1weH5tbDQN+JQ2Abu488mnzIbAAA3o/VK2PwDJo7r5Fy7ZRuvi4PFS6+qIXdVYD8Jg6BUcuOD8BozSLlRWyicgVKkTMQWwUlFF0Ooe5FIPk57BD7G0SiywyjD8bCDyHsOkeeeR3SUxEkROmU6BfQYFJMHfhWXV8efkUrb13VPMTsrcTQSzxZ/+n0GVA6EGbSGdgG9vo15fg2nFgbO8k70SRdd+mahDT81vUxTZRlJBRMsjq89C0EXCvSf7TIBZ136YZUJEiE7LgJ2dN01BZuE0dkIhxE7KcQTK1QUj+cwAEyrPZ+IydzRoyah+mLy2isbWBweESJEnB9q+1RM9Ub9GQOWkABg8HjRr2d9Yh0hTlBlRsfn+D4vg0BvUC9rZqECUJuk7Tzr1zahCYlB6HJAREPwfbbMBzLBzsbUKVI0qBgQkc+SxgWUYaIAqOpKwKXJ6bgGlaaDV/YvHaFNrtDsKTfVSrJeqIg/bRNwjclFIALeP3saybhu8SC4VBHwnhBXXIKocYRXD9QzBi4Xgchmkd9+L+CTAAMqwy+ZzluBgAAAAASUVORK5CYII=');background-repeat:no-repeat;background-size:contain}.ipfs-rb{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAixJREFUeNqEUktvElEU/mag5f2yJhXLwxIt0kiqsVEXujP+A925cu1Pce3WtXVtYuJCF7KtTY0NrVQIpRVKeXTkMcO9F8+9ZVooJJ5kcmbmfOe733fO1YbDIWS8+/g1dycVX7W/xyO3vdsuVKqvnE7HZ230783rlyo7bVBicSGyfjsVwozomVbIPe/c+FmsPHfoRKJd1HT7hXHBZjVbA4aA14NnD9bC2VR8gwuxPi5Sx39Cp+M0XUP0ahhP1jLhW7HFD4zze3b93ILtXYyyVKlR8/5hFbnvO9gtlrGSjOF+OpXkYviWyo8mCS4R6bqO4p86vm3v4fC4DrPfw4unj1XN6JvBaQtjChzUXK43sVU4wNFJA43Tv/B73edQwTmfIhAjCVL6UdPAj1IVFSKhCdAcAI9rnjBiAjtBYEu3GEeh1sKJ0YXR68sVIujzIhzwY8DEBHZqiLRKkicQDfvABxaiQTc4Y/C65pCOXwcjcmlvJgHtlwi4epYifiQWgmoLZwPW6HQG07LgcOgKO0UglAKOTt/E+09fwAiUWU7QAE9xUK3jbvomsispZVHMVEDSZdHo9rCZ/4VIMKAu0XGjpU7d2S8hk0pCELHEzrjKnCQOYJoD+Dxu1RyiwUm5LaMDo9NFt2cqDLvY4oQFp/QpfT/MrmI5FkWebt+NpWto0j2QmQkOjZ9hpwhqjXZzM/+7LU+cc7lRrjXh8/lVLRK5ovLWXglOsiOxdt8/AQYAzv8qbmu6vgEAAAAASUVORK5CYII=');background-repeat:no-repeat;background-size:contain}.ipfs-rtf{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAe5JREFUeNqEU01PE0EYfnZmd5FSvgLYFuwWt9EgHyEaox68eDJevHvwJ/hTPHv1N/QgZ2NC4g3kUAQKFKGhjVKqRrvbnRlnht262FHfy+y8877PPM8z71pCCKh4/ebt+rJfXEz26Vjf2mnsN5rPKKWbVpx7+eK5Xu2kyMtNTd5d8MdhiJ9BOO7atFI9ajy1UyAqSPIRMR6ZmoNehNHMMB7fX/UWvEKFMbYKE8DfQnAhwRmmJkbx6M6S5+WmK2Evup2c9yUk2nnKA0XVcSiGXAe1k5beP1i+4RFCXqnPywB/AKVzK34RjHNYlgVKCH50w7EBBogbTa/AVM5SgBdn0gc2AMDjPsbFPz2xye9asweS6n+NTbG8BCCfUtLjff2WoVnVpAH6z6hMUtJE3EykYfpF4vUiL3QNS7FMeSAQRBHW3r1Hq91B+VoBQRji4+ExFsvz6Hz7jm7Yw5OH92AcJKW9G4SoHhzhy/lXbB98Qmm2oCXN5WawsV2TACEoJXqwTKOsb3BtR2ucmZxANpPB8JUhyPnHWDaDpfJ1eZFALzJJ4MKO5MEtv4TSXB7V/br8iQLMz+almRZWbvoo5q9qRlxwewCgeXbe3qrVO5ZkUD/9jJGRLPaOm6COi92TU1DbxYe9umRD0DrrtJO+XwIMABWp9nS+FgaoAAAAAElFTkSuQmCC');background-repeat:no-repeat;background-size:contain}.ipfs-sass{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MDNDMTBBM0JGMTE5MTFFMTg3N0NFOTIyMTQ2QzhBNkQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MDNDMTBBM0NGMTE5MTFFMTg3N0NFOTIyMTQ2QzhBNkQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowM0MxMEEzOUYxMTkxMUUxODc3Q0U5MjIxNDZDOEE2RCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowM0MxMEEzQUYxMTkxMUUxODc3Q0U5MjIxNDZDOEE2RCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Po72XUcAAAJcSURBVHjahFJdTxNBFD1bykc/ttvdtttWGgI0bYrUgDZoNYqRJ014kMRXHvwB/hQTH/wFhMREJfFBQxBjhMRIFEQSCAlQxKYGggiU3e3HbnfX2bFt1EU9k9m9mblz5p4zlzFNExYmpue/jmTSZw5PZAl1MAwDT0c7O72wvPdudeNakPNtOZ0tsM7cvzdOc5yN5LDAsTFRAJks/kC2PxFRVe39Si6f4byez62EpAEH/gNN18F53Ri/Ocxf7OtdLMpKT42s/ZPg1cISJp/P0tg0TBzLCoK8D7eHh4RkLLJ4cCz12AjMXwgez8yhqtVo3NbqRKlcxcSL16gZwJ2Ry8KVc8kZO0HdTKlURn+8G6PD2SZhLMQj96WAiMAh2RXFYKI78lcJcx9WYBCycICnpNbojUWpD5Y0C4Zh2D0w6hWc70uQZC+IWfQZrXF0IsHvY+meBd08haAhoVMMQFJKWF7PNZM+klhRyogGhbqxOIXAMOtEwGAqDqVcgbVkkE+5UsEAWavf0az2t0ZqvK2qabh6IU3joizDwTgwej1LdVfJXkdbK8mt2QkayO99A0/0trQ46I1lVcX+UREhnsP34yLp1AD1xibBMuntpzU8mJyi3Tc1O4+l9U06n7x8Q/8PHz1DrrALt8tlr0CrkbJMHTop9Sk5sLa1g8L+ARJdnShKClY3tunN69t5iGLYTlCtakjFY7gxNABdN3B37BaqqoYT8pyX0in4ORbRkIA46YlDRbUTbBZ2Jb/Pw4qiKFnapcpPo9pdbrg8DjAOBsFgELJmsGs7eWkkc5bu/xBgAHkWC6UPADTOAAAAAElFTkSuQmCC');background-repeat:no-repeat;background-size:contain}.ipfs-scss{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RkM4QjYyNDVGMTE4MTFFMTlBREZCNDNEM0ExMTk0MUIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RkM4QjYyNDZGMTE4MTFFMTlBREZCNDNEM0ExMTk0MUIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpGQzhCNjI0M0YxMTgxMUUxOUFERkI0M0QzQTExOTQxQiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpGQzhCNjI0NEYxMTgxMUUxOUFERkI0M0QzQTExOTQxQiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pkf1yeMAAAJbSURBVHjahFNdTxNBFD0tLULpB91uodVWPmorUIxo0VSiNSExMYYHE33l0Ud/in+C+OSjYgjRGDBRCKJIUkIEWi0WKlja0ul22+5219lJ26gLeiezuXvn7rnnnrlrUFUVms3Mvd2bjIyezRVLBA0zGAzo6jhjm1te+7EU37rFO+w7JlMbtG+ePJ5mOaZmci/nsPl6ONBtw18WDQc9tZq0sp7YjTisXV/NFKRpRvzHpHodDqsF03djzuvDg6vHJWFAprF/Arxe/oins6+YryoqCiUBvNOO+7FrXMjnWc0WyIAOQP0N4Nn8IqqSzPx2swllsYqZl28gK8DDyRvcxKXQvB6gISYpiwgH+jEVi7YAfW4nEqk0PJwDofNejAX7Pae2sPhhHQoF63U5Gai2Bn1epoPWmmaKoug1UBoMrgwHabIVVCx2jdrKFwm67TZ2plldPQGg2cK5HheIUMbaZqKV9In6giDCy3MNYXECgKI2gICxoQAEsQItpNCHWKngMo01arTY/jFIzbutShJuXh1Fm9FImYiM7tTtKOtbO+toN9Nc+fQ5SGUOIVYl7HzPIH2YRZ0y2KZ+sVzBHn2v1mpMGx0DTaR3nzfwfGEJdybGkdo/wEigDyvxLzg4yiESvojZhfd49OAeLJ2degaSLIPOO6vwgiYaaRErTRREEdn8MeJbSVZ5M7nLdNExqFLaQwEfFfACQn1+HBWKSKb3MT4Sgstuh9vVDa+bQ4DORE6o6RlspzMk9TOPfr+fiLJCLFYr3TZSKNcI7+aJwWQmPM+TkqRg49tu65f/JcAAMwMas6WUKd8AAAAASUVORK5CYII=');background-repeat:no-repeat;background-size:contain}.ipfs-sql{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAh5JREFUeNp8kctrE1EUxr+ZyXMkoa1NBROaSkpTBE23PhZ25cql2y5duvAPUdGFS1FxIRRBXZlFQ9GVdDENIhGJxkDsw2mneZnM83ruNZlOmNoDhzlzz3d/9zv3Sowx8Ch/qlYK2XM3cEJsbH0+qjV/rd6/u6aN18b7RMFT+9aosP/Ex+0ae/puw7j36PlKEMAzctKJ3aGFamMHjV0d+wcGitkMrpWWp6hVIciEk2MAOwbUWjosx0UiFoWqJpGMx5DNzODq5aIPoa82AWBg/lyKLMH1PMp/a9XvLXLzG1cuFlBaWpiKxaIPSLY6CaC93ggQjyiQZRkeQSzLRovGaPciWLt5faSWEBoh6KBvOhiaNga0+Y9pwaFxvu7rfp8F5pWDt+qNMp2IijHGwddWCvN+33/CoAOP5nVdT9SdoQ1JkggiQ6Yvr7V60+9z7akA2gfH9cRF8hO5F5Ve4lQAF9uuK+qFsylkzsQxrcaQm04hdWkR83Mzfp9rQ3fAFzu9Ph6+WMfjl6/pGBdb2jbKmx8QlRjWy5vkyhUZBPgOeGNHN9AbDLGUz6He2hVj3Ll9C8/evsdgaMK0HV8bcmDTU0UUBYXcedR+NLGnH0I3jvDk1Rsy46FP4C/1BtrdntCGHNiOAzWZgEKQ5Qt5lIqLojbaXSQTcRy2OwT4SZqk0IYAOgkVWUE+lxX/zb0DpFNpkTzmZmfFtzewhHYcfwUYAMZmVaZQlLFHAAAAAElFTkSuQmCC');background-repeat:no-repeat;background-size:contain}.ipfs-tga{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnxJREFUeNp0U89PE0EU/ra725K22ILRGipb22pMG6JcSEQTbUIwnozxpBcvepeEP0KPogcT/wlNT17kIKbEmChFUYKGVtL0R2gLtNCl3Z1Z3+zSAlonmezOe/O+973vvZEsy4JYnqdPMu6RkSQYQ29JEkB+PZcrslrtPhQl23VZc8/tr9I1yMHg0EA8HrBM04lVFAhoY38fSSDQVN3pfKV8G7KcxZHl6v1xblqU3eLc3p2VFZjr6+gQgwsnhzGTuq6Nhs6kYZqXjwL0GFhEl3U60OfnwWs1GGtrUKNRsKkpeIIBpKIRtI1J7cX7hXRhc/MOhXw5DkCZGG2zXAajzFIoBMvng1ypIKOqmP30GW3OIEcimovzlxRy5RgAFwDEAIODkCcmIMdiQLsNdWwMZdJlg8pzEUt1aBhKq3XinxKYqF9yQbqRIqsMy+0Gyy47bKgUWXSLtDENE5wdtuqQATm50F1VnPbRGeEw8HXZbiV8fsDvI9ldju9vADAyihLEbrWAZhOoVp3z6iqBUiB1A4nEfwCEsbkL/M4TgE5n5jDx+oTEzp1d8m9tC8H6MaAB0imzx0NU/WKUYE+loEyawDBo2ui6TGfT6ANAxrvx87gYCGCxXEKVJvCWFsG3eh1vN/J4OD6Od4UC8o0G3TX7TGLHwI9iEQmvF9X6Fh7F4/iYy+GcLOMSlfEgGsP0qdNOmX0BiGKpVkV1bw/1nW2b/gCpf1PTcI+Y7eg6ps+G4bG4PR99SjAVo9HE4q+fKNE0vl5awuSohjeijbRefVjAtUgEQRK7Yhi9OKn7nKWZxxlSPWl3QwgnaIrW8QMhD542vUbx/W49m7sq4v4IMABOqi3Ej7bAEAAAAABJRU5ErkJggg==');background-repeat:no-repeat;background-size:contain}.ipfs-tgz{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnhJREFUeNpsU1trE0EYPbMzSTfdtInFtkkpiaXVWou2FRUEn/so6JugL/oH/Af+B1988if40jcFERQURNBSQdDWlLQN2lsue8neZsZvc7FoOrDszM75znfOmVmmtUYyvry++36yfOeS1qqzDtvH2P76ApPlW3Drb2sHex/uccHWAdbZX30kO2+B3siN3zhTnHuQ66+95i423jzFzOVljBdKOZNHazvVT7e5wF+SZBj9iZJ+3J11mbW2kR8T4LwFli5i4fqTUvnczTUp9RLtDhKgJx0q4dEwWAxrREKICHEsoYYXMXvlcWmquLgmY71yCkG/c0AkARgLMZpnMDMpGNzEYe0dGp6HwvmHpbHC1Wf9MnFCkHQOyYEPzSJwQ2B65Tm5NZG3Fshim6wbMNJn4bpHowMKtIqo2COgR2IcAptwjvcgo6i77igjEmVDqbY8xQJ1VwRULhiBI6+G9Zf3cbTziuzIDkmHSNqECTFgQScEcYuc2NA8TcdYwXD+GkK/TYVN+u72WrIudiAD8o6oAR2RRCmQMjis3CIy1iSpPySCXhFTXeyAgh4BR+JVw8pauLi0Cp4yCX9A90FQhnSBYtnF/k+Q+HYam9itfIZB3QvT8zj8XSW5EhNTs9ivbSLwPUzPLNPJBIMEKnaQYg6aB9+RGR5F5VsNgnNKXMI1NdJGG5WfHzFVLJ7k8c8xUngpVodlDSGbFYj8Y4yMpOG09lHf3yIFPzA3fwHZTAQVtU4JUTeFDrdgDdlI8wAz5Qy2KxswReI7QODZcOr0ZH3q2hIDBI7zq16tuk3FNPxAI4wN+pkoccYoE4YJU5EdUtM4Qst26v26PwIMAKj3P/2YUKgYAAAAAElFTkSuQmCC');background-repeat:no-repeat;background-size:contain}.ipfs-tiff{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmRJREFUeNp0UktPE1EU/qYzHWstlrYJNcWUElyUJsaNGh9B0g1Lo0v9Ey78EbrVxBhXuHShm25YGBJRQpAYBDEWpaEPEhksdVpbyjzveO4MfZDCTWbauefc736PIziOA77OPH2yJCcSGdg2uksQAKofFou/7VrtASRpvVNynj13f6XOhjg8HAlMTIQdy/LO+v3uYUPTkAHCTb+cK+0pdyGK6+hbvu4/xiyHbncYAwfR19ZgbG/DoO9LsSgeTd9JXoxfyMG2rvQDdBlwIZauQ5ufh12twioU4E+nYU1NIRCNIDs+Bt28mXzx8VNuZ796j9q/DgAwomwqClilAmF0FE4wCInAlkjO4y+r0JgNX2os6XPYS2q/cQyAcQatFjA0BPH6NYipccAwIGUy2CVJFZInkKlyJAqx3T4/IMGmJkeWIWSz5KgI5pdhb3yDXS5DSCYh8rTID8s0wexeVD0GtMd85KkkefFxUfE47M1NokbJkByEQl6tL+ouAI+MUwbFhnYbaJKc/Sqg0x4H4eDRGDA56fUOABA9/GsCpaIHwr8FOhQ823O5RfW66tUGADhNy3RNRDjcN41HLxdQ8J6jYTsOQLfOJBK4f+s2/uoathoNGKT1MtFeVHZxdWTEZfEq/wMKl3rCJOIzTV6ADs2R5ulYDDNkYjp0DhrF+zCVgkw31+v1UxjQZkNV0SADd2o1MIuc9gmY+/kLxb0/UFoHePd9A1qzeUoKpilx9xcLWzgg+u/zeVfuQqkM9bCN1ysrWKXxdtPgvScwUAm58XZ52W16QyPtifRUzi588GbEi1ztHPsvwAC4uC9qhnsZvwAAAABJRU5ErkJggg==');background-repeat:no-repeat;background-size:contain}.ipfs-txt{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAeJJREFUeNp8UrtOG1EQPfsyXiyzBguIJSyChZBBEFCKpKHLo6egpErNn8CHgH8gkZIiTSIXLhJAWCgkoMgRMSiRBSK29z4y9+I1d/HCrFb3MTPnnjkzlpQSynY+fP70fGF2gQuByCz6lfdd9Uurfvrrjes6762eb3tzQ69uFJwPsqOPC+MBEmxxphi4tlU5OGmsOzaBWLc+O9oIIVhScidkyGZ8vH62nHtSKlaI4cse6TjAfSaFBBcco0EWqyvzubmpyQrj/FXk75cQaSEMeMXU8xykPA/Hjd/6/LRcyjEpt2i7HAe4A2TeLZWKUOJaVLxj27j813EHGKCXaAJExu/4BOdiAED08riQD2riOrexyRoYc3CvsAbLGAAjZga7vgZG23WMCdBvoxKJc36TRBlMiaa2JByjNqqD8qkYc1pjDK7abey+/YhrWlfKswhpiCR96aEU9o5+QE3g2ovVWDm2Sc22bBQm8vrVpbkS9r+doPr1EOWZaQ0yFoxg2PcREosEAI4uvZhJpzFMP+cSXRbq+043RManez+tNWKMI6GN0g0Z04HFR+NoNC/0yx717efZOSbzY3AcR4Op2AGA5p/W31r9e0vNgSrh9OwCrpeCkqvZuqTybnpRqx/r2CjvvwADAJC/7lzAzQmwAAAAAElFTkSuQmCC');background-repeat:no-repeat;background-size:contain}.ipfs-wav{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAApFJREFUeNpsU1tPE0EYPXtpKbX0wqUQKVQMFdIXQBNCQBs06KP+B8ODGh+Mf4b/4IsGE54kxhcMBrkp7YOQgBRvSKG73fvsrt8Otoask0xmd+b7zpxzvm8E3/cRjPkniyulW0NFy2JoDkEAguOlpXJ9p3L8MBqVl4O9YHxae8pXuRlcGO7KPLhfTDVUqwUgigJMy4Whm6lEXHjxYf3XnByRN0QB/2KaH7btMlUxoRJAcyqKhdOaht7+DJ49n+2cvTnwynXcsb+kLwJ4rgfmMDDGWqvneXCZS9ND7mov5h9ND85M9y86Dpto5rUkuJ4Py3YDJpy6QGJPayqB+Njf+43XL220t0cwOZkfrNXsBUqZugDA6CbLdAiAwaek1ZU9LmP8Rh6S78GsGxjOp9FdzKJaVZIhBgGASzK21w/wbrnCk8euX+EMAjaaZuPHdwUdHVFYluuGPGCORwwYjg5rqOwccRk+3Ux0IEvntmsNG4ZmUayL/wAwKHUNfZfTKN0ZRaw9Cof8qJ/pMAyHy5KkAMTksSEJtnMenM7EMVMawbejMzJRh67bXEYiIXEAVTW50SEAhzqwfqrBcXx4VOhYm4RsNgHbsJFOyZTsQ1MN+hcohoUlkFiMT+TQFpMwXOjGpXgE+XwGk1N5pFJtKNCequgYGupCRBbCDOp0KBJc4VoP3dyBONW8uydBgBHUThqQKCk3mEZ/LoUG+RBioJO7VarAwEAntjYPiUUW9Hh4b2R7k9j98hN37xWx8fGAt3eIAdVMLn+uUv+b2KReSCZjZJiB9bV9jIz2ofr1BKvvd7G9dRC80lae0HzOt+cWVnrSKDrMJykifwNBpCgE/UAllEXufmDu8Zlffvvm8XSQ90eAAQA0pF7c08o4PAAAAABJRU5ErkJggg==');background-repeat:no-repeat;background-size:contain}.ipfs-xls{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmxJREFUeNpsU0trFEEQ/mamZ3Y2+0zIC2MmITEkUYgERFQErx5E8KTi1b/h79A/4SW3nCNeYggBYZVEMU/y3N3Z7M7OTD/G6lk2ruw20zRdU/XV91VVG0mSQK/3n1a/jky6d6Xs3G8WXS+Pw5N6LXjLLGuna/78oZKerGsYKtrDE16uJGL1L9gEOOcYd2dL1fNwrbL//aXN7J1efPMmkUqEFAk0A0VZNbFEaQCBscIkXj975y3NLq9xye8PBkAniHOFph+j2eC4rsdoB4LsFubGl/Hq8RtvYWpxTQi52o1jvWiGYaRZL0/auDgOkC/Z8BYL2Pqxidp1FZkhoDxpeaXA/Ujuj/4HoOxKKjiOiek7RUShRNQWaNYFQuMafrYCxiw4ozZKfqbYJ0EvRdl1DQyyTs8XCNTA6UELMwvDyLpZWIZNNlNLlQOK2LMJRJ+5AkuZ1S7CFFzJzk56GnUjQWlYkqCoBWFbonEVYcLLA4dNnB624GQsDBWIgfZJEgxkoChzSFWvn4VpQemDm2VwXQsXJwF1h6c+gxlQ5jgSiEUEt0wdIe7tMES+nEG2aCLiJMOIIWIr9e0DEELAMUrwRuchVAyTKimUwO75Jm6VF3Bv7imOaj+xd7UFKVS/BPJF1b/E4tgTrE49J60O5kceoNqowiuuYKa8ghHXA48U9MT2AQgyRvTThE30bQiaSGa4yLMJNFo+Dq/2cHt4CYlwyFf2S6BHwwrMw/avDbR5C1k7h1YQ4KH3Amf+AcZyEbZPv9CItzQD1l9EbtYOjv74v/d3O9RMPTDrsEwGIWN8q2yk7XNYRs9JrRv3V4ABADSGR6eQ0/NQAAAAAElFTkSuQmCC');background-repeat:no-repeat;background-size:contain}.ipfs-xlsx{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmlJREFUeNpsU8tqFEEUPVXdPY/ueWZIoiYZiSYKYhJc6EbduHOhgijo3t/wH1z6B0JAhOyMILhxo4kJGk1ASTAxwWF0Mpp5dHc9vFUzYwidaoqmq+8959xzbzGtNcx69PTS26ETmQtS9r4Hy/xv7MW7jV+th5yzVcaYPX/++It9u4NAv+CVR6tBUUTqMJsDcRzjZOZM8W9ZLKx+/XDb4e5/kH5In0lpIYWGUaC0YTZnBCAEKoVR3L36oDo7NbsglZwbqD6iQKOXFMcKUVfBkBAoQhlD5xxMDp/HrSv3q1JgYW3z0x0KXzkCYJaRZljru23aHWTzLiamAyytv0O9UYdf5PArqlppBfMUfu4oALErqZBKcUxMFRCHEp0DgW5Lo4N9NIN1dF0XXsVFOUyPJTzo+WBANDidjp8tgHGG3c0DnJ4uIRf4cOCBaW5KjY8xkZL72xpJ9QcFz5bVqHUJGHZL2YtNmKi06YCyiVFb4s/vEKMTAf1p4edOG6mMi1zR6wEpdUwX+vLDtkCzHoK7ptcM6ayLmGajvtex4PliyoIkFRjmUEASelB2rXQRSfjUCT9PlWpmW21iTGzCAyEkUixPRqXhe2V4zKczbdmybgkpJ0cGOuA6Y2MTCsKoi5HsNK7N3MN+uwYaWbxYfoLLkzdxcew6lrYWaZhm8PHHG3zffp1UwJSHz9vvkU8PodbcQYYYS5lxYkxTkGdVDQdV1Js1qPgYD6JIuIE7gsXVefIhIuM05k7dwMbeMmh87a18ufIMaVYyprrJLgje2Nr+1tzYXANnDnr3zRhHj37Vvy2wpXHtNAd5/wQYAD6WMuT2CwoVAAAAAElFTkSuQmCC');background-repeat:no-repeat;background-size:contain}.ipfs-xml{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAilJREFUeNqMks1PE0EYxh+g3W2t1G0sEqyISynUFJsSOShNwCamiYZED3LgIkcuxoN/iCZePZiYGD2aGD+i0F5KMChxlVaakAK2ykcAt+WzdLu7zkxo3WZL4pu8mXfmeeY3885ug67roPFh5nvc62m9hjoR+5LMp7MrkYf370qVtco+VtCUFpbj+jGR+JbWn76OyQ8ePwsZATQb8R/hanZgINgj9IqeuBFCw1Kt9OMBnNWCs24XwkG/QKYUEiGjVAPQof/rq0783pShET3ULQo8xz0iS5FaANmrHQH2DoqY+DSLSz6RzecWlnD9ymU47LYjd4O5BXqDTG4FM3NpTEkpdJ5rw0AowLRMbhUfp58gTOaD/UHmNQPI6YmvKWRX1zESHUJ/oBs2nmPa+Mgw0ZIM3tZyGoJwygzQNB2jNyJIZX7iB0lpPoM70UGmPX8zCU+rG8NDVxHwdiC5mKsPUFUN/gvtLLf39sFzVqaN3YrC6TjBauqhXhNA1TQoqloV7Da+pjZq1FsXUCamF29j6LvYhf3iISamZ3Fv9DZevouhRzzPfOG+3hpA9U9UyioOlTJ7pFeTCQS6RGzIebyf+oz5pSzWtmSW1EO9phvQ00slBRt/8qR3DoWdXbiczUiTzd52D+tdLmyTB14mx1rMAKVcRpEATjrsuElee/HXGmnFRyBOGD30C/nEDjNgs7CDpsYmnHG3YPegBCvHs9oYfm8nG9dJa5X4K8AAQzQX4KSN3wcAAAAASUVORK5CYII=');background-repeat:no-repeat;background-size:contain}.ipfs-yml{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAdxJREFUeNqMUl1rE0EUPbM7m5Y0Zptu21AwWwhYpfSDFh+kvvRd8N0Hf4I/xWdf/Q158F0QoQ+CVsFKaLSQpt/dpmvztTOzzky6cetOpWcZZvbO3MO5514SxzEU3r57/3GpWllM/tP4sL3TarROXuSo/SWJvX71Uu80Cfhlr/T4UdWFAVfdnmsTUtvdP35OUyQKVnJgXDBTcj9icAsTeLax7j/052qM81UjwW1QJXEhMF0qYnN90fdnvdogYmvJPU0/VBApD4hcDrWRcyikfB17srzgW7b9Rh1vEvxDlI4tVytaBSEEtmWh0xsUMwpwnWjqAlcxogiHd1wiQyCu87iI/+sJtf6+NXsgpd7FWCMB50KvkYMGMbLdZgLlfj+K9K4+FnFQ2x7WntIs50AbmiGwLILt+k+EvzvSNIHzdigdJ/AmXQRhiHv5POSwYmG+cqPVo0HqDxj8uTK2vn1Hfa+JmdIkvtZ/4fOPXU3WPDpFeNWVyUKryCiIGMN4zsH98gym3CIcOTwT+XHdXrdQQHAZotE8kBPpSqPNHtBOr48HUmLOcXRJT9dWNMGYJFby91pHOAvaykSaITg+bwefdhrteDRTMSwyrFCgI88E056Hy+4Ah2cXQZL3R4ABALUe7fqXWFN6AAAAAElFTkSuQmCC');background-repeat:no-repeat;background-size:contain}.ipfs-zip{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAm9JREFUeNpsk0tv00AUhc+MY6dOmgeFJg1FoVVpUWlFC0s2IFF1jxBbhKj4BSxYdscPYcEmQmIDq0gsERIViy4TpD7VFzF1Ho5je2a4thOqNhlp5Mz4zudzzp0wpRTC8fPrk0/TC6+fDtYicLH97T1Kc2vQDcs+rH3eUAxVznn0fn1DRM8E+iOdv5ct3XmZG6yVlNj6solUbgVTt0q5FGtX6vXqC6VklTE+KAO/OODHSIQPRQpsXC+kkEz2ELA0ystv84tLzyucsbWByisAGf+QAS2CCDRRLMJMmxC+i8C4jdLCm/zM7OOKFGptcO6/BTpJ0yeQB0Y+mfKQuZZG0jQgeRbW8Xdomobs9LN8scc+UPHNy4Dwq8IljotIIQEm59/RoSyM1CKkXKZNBm7kIVgyM6wgAnSgRK9vqQfHPiMFDHqyFVsLR9Cm0o4YzoAASrSjCelQfRPb1Vc4qn0EY5L2W9GEaBLcxQgFHpGbkMIDJ69e+wjJ8VXqRgKid0r7ftQdxkRs9SqA2kgAm14SSIQh9uhuLGPMnKJs/5KquL1x0N0RCsizigoDaLqBdHoMiyvrlBsHVx1wphD4BCewoqxGKKDwAgtOy8JufYuk+5golGGaGZwc1sIGoDz3AOPZSVLaHgVwydoJDM1H4DbQODughB3YpOD44HfoHgnu4e7So0uAi0stHLJ3Aud8B9bpHu6vPoSu9TtDl6tUuoFiIYOgu0+158MKmOxomtyD3Qi/3MTR7i8K0EDG1GHO5DE3X4DvNahZlJOwEkOATvdPc2//hx3mXJ5lFJaF8K8bStd0YGfnOJbMGex21x6c+yfAAOlIPDJzr7cLAAAAAElFTkSuQmCC');background-repeat:no-repeat;background-size:contain}\n.narrow {width: 0px;}\n.padding { margin: 100px;}\n#header {\n  background: #000;\n}\n#logo {\n  height: 25px;\n  margin: 10px;\n}\n.ipfs-icon {\n  width:16px;\n}\n`\n"
  },
  {
    "path": "packages/ipfs-http-response/src/index.js",
    "content": "/* global Response */\n\n// @ts-expect-error no types\nimport toStream from 'it-to-stream'\nimport { logger } from '@libp2p/logger'\nimport * as ipfsResolver from './resolver.js'\nimport * as pathUtils from './utils/path.js'\nimport { detectContentType } from './utils/content-type.js'\n\nconst log = logger('ipfs:http:response')\n\n// TODO: pass path and add Etag and X-Ipfs-Path + tests\nconst getHeader = (status = 200, statusText = 'OK', headers = {}) => ({\n  status,\n  statusText,\n  headers\n})\n\n/**\n * handle hash resolve error (simple hash, test for directory now)\n *\n * @param {*} node\n * @param {string} path\n * @param {*} error\n */\nconst handleResolveError = async (node, path, error) => {\n  const errorString = error.toString()\n\n  if (errorString.includes('dag node is a directory')) {\n    try {\n      const content = await ipfsResolver.directory(node, path, error.cid)\n      // dir render\n      if (typeof content === 'string') {\n        return new Response(content, getHeader(200, 'OK', { 'Content-Type': 'text/html' }))\n      }\n\n      // redirect to dir entry point (index)\n      return Response.redirect(pathUtils.joinURLParts(path, content[0].Name))\n    } catch (/** @type {any} */ error) {\n      log(error)\n      return new Response(errorString, getHeader(500, error.toString()))\n    }\n  }\n\n  if (errorString.startsWith('Error: no link named')) {\n    return new Response(errorString, getHeader(404, errorString))\n  }\n\n  if (errorString.startsWith('Error: multihash length inconsistent') || errorString.startsWith('Error: Non-base58 character')) {\n    return new Response(errorString, getHeader(400, errorString))\n  }\n\n  return new Response(errorString, getHeader(500, errorString))\n}\n\n/**\n *\n * @param {*} ipfsNode\n * @param {*} ipfsPath\n * @returns\n */\nexport async function getResponse (ipfsNode, ipfsPath) {\n  // remove trailing slash for files if needed\n  if (ipfsPath.endsWith('/')) {\n    return Response.redirect(pathUtils.removeTrailingSlash(ipfsPath))\n  }\n\n  try {\n    const resolvedData = await ipfsResolver.cid(ipfsNode, ipfsPath)\n    const { source, contentType } = await detectContentType(ipfsPath, ipfsNode.cat(resolvedData.cid))\n    const responseStream = toStream.readable(source)\n\n    return contentType\n      ? new Response(responseStream, getHeader(200, 'OK', { 'Content-Type': contentType }))\n      : new Response(responseStream, getHeader())\n  } catch (/** @type {any} */ error) {\n    log(error)\n    return handleResolveError(ipfsNode, ipfsPath, error)\n  }\n}\n\nexport const resolver = {\n  ...ipfsResolver\n}\n\nexport const utils = {\n  detectContentType\n}\n"
  },
  {
    "path": "packages/ipfs-http-response/src/resolver.js",
    "content": "import pTryEach from 'p-try-each'\nimport { render } from './dir-view/index.js'\n\nconst INDEX_HTML_FILES = [\n  'index.html',\n  'index.htm',\n  'index.shtml'\n]\n\n/**\n * @param {*} ipfs\n * @param {*} path\n */\nconst findIndexFile = (ipfs, path) => {\n  return pTryEach(INDEX_HTML_FILES.map(file => {\n    return async () => {\n      const stats = await ipfs.files.stat(`${path}/${file}`)\n\n      return {\n        name: file,\n        cid: stats.cid\n      }\n    }\n  }))\n}\n\n/**\n * @param {*} ipfs\n * @param {string} path\n * @param {*} cid\n */\nexport const directory = async (ipfs, path, cid) => {\n  // Test if it is a Website\n  try {\n    const res = await findIndexFile(ipfs, path)\n\n    return [{ Name: res.name }]\n  } catch (/** @type {any} */ err) {\n    if (err.message.includes('does not exist')) {\n      // not a website, just show a directory listing\n      const result = await ipfs.dag.get(cid)\n\n      return render(path, result.value.Links)\n    }\n\n    throw err\n  }\n}\n\n/**\n * @param {*} ipfs\n * @param {string} path\n */\nexport const cid = async (ipfs, path) => {\n  const stats = await ipfs.files.stat(path)\n\n  if (stats.type.includes('directory')) {\n    const err = Object.assign(new Error('This dag node is a directory'), {\n      cid: stats.cid,\n      fileName: stats.fileName,\n      dagDirType: stats.type\n    })\n\n    throw err\n  }\n\n  return { cid: stats.cid }\n}\n"
  },
  {
    "path": "packages/ipfs-http-response/src/utils/content-type.js",
    "content": "import { fileTypeFromBuffer } from 'file-type'\nimport mime from 'mime-types'\nimport { reader } from 'it-reader'\nimport map from 'it-map'\n\nconst minimumBytes = 4100\n\n/**\n * @typedef {import('uint8arraylist').Uint8ArrayList} Uint8ArrayList\n */\n\n/**\n * @param {string} path\n * @param {AsyncIterable<Uint8Array>} source\n * @returns {Promise<{ source: AsyncIterable<Uint8Array>, contentType?: string }>}\n */\nexport const detectContentType = async (path, source) => {\n  let fileSignature\n  /** @type {AsyncIterable<Uint8ArrayList> | undefined} */\n  let output\n\n  // try to guess the filetype based on the first bytes\n  // note that `file-type` doesn't support svgs, therefore we assume it's a svg if path looks like it\n  if (!path.endsWith('.svg')) {\n    try {\n      const stream = reader(source)\n      const { value, done } = await stream.next(minimumBytes)\n\n      if (done) {\n        return {\n          source: map(stream, (buf) => buf.subarray())\n        }\n      }\n\n      fileSignature = await fileTypeFromBuffer(value.subarray())\n\n      output = (async function * () { // eslint-disable-line require-await\n        yield value\n        yield * stream\n      })()\n    } catch (/** @type {any} */ err) {\n      if (err.code !== 'ERR_UNDER_READ') {\n        throw err\n      }\n\n      // not enough bytes for sniffing, just yield the data\n      output = (async function * () { // eslint-disable-line require-await\n        yield err.buffer // these are the bytes that were read (if any)\n      })()\n    }\n  }\n\n  // if we were unable to, fallback to the `path` which might contain the extension\n  const mimeType = mime.lookup(fileSignature ? fileSignature.ext : path)\n\n  let contentType\n\n  if (mimeType !== false) {\n    contentType = mime.contentType(mimeType)\n\n    if (contentType === false) {\n      contentType = undefined\n    }\n  }\n\n  if (output != null) {\n    return {\n      source: (async function * () {\n        for await (const list of output) {\n          yield * list\n        }\n      }()),\n      contentType\n    }\n  }\n\n  return { source, contentType }\n}\n"
  },
  {
    "path": "packages/ipfs-http-response/src/utils/path.js",
    "content": "/* eslint-disable no-unused-vars */\n\n/**\n * Converts path or url to an array starting at CID\n *\n * @param {string} path\n */\nexport function cidArray (path) {\n  if (path[path.length - 1] === '/') {\n    path = path.substring(0, path.length - 1)\n  }\n  // skip /ipxs/ prefix\n  if (path.match(/^\\/ip[fn]s\\//)) {\n    path = path.substring(6)\n  }\n  // skip ipxs:// protocol\n  if (path.match(/^ip[fn]s:\\/\\//)) {\n    path = path.substring(7)\n  }\n  return path.split('/')\n}\n\n/**\n * @param {string} url\n */\nexport function removeLeadingSlash (url) {\n  if (url[0] === '/') {\n    url = url.substring(1)\n  }\n\n  return url\n}\n\n/**\n * @param {string} url\n */\nexport function removeTrailingSlash (url) {\n  if (url.endsWith('/')) {\n    url = url.substring(0, url.length - 1)\n  }\n\n  return url\n}\n\n/**\n * @param {string} url\n */\nexport function removeSlashFromBothEnds (url) {\n  url = removeLeadingSlash(url)\n  url = removeTrailingSlash(url)\n\n  return url\n}\n\n/**\n * @param {string[]} urls\n */\nexport function joinURLParts (...urls) {\n  urls = urls.filter((url) => url.length > 0)\n  urls = [''].concat(urls.map((url) => removeSlashFromBothEnds(url)))\n\n  return urls.join('/')\n}\n"
  },
  {
    "path": "packages/ipfs-http-response/test/fixtures/.gitattributes",
    "content": "# Make sure fixtures have correct line endings on windows\n\n*.txt text eol=lf\n*.svg text eol=lf\n*.html text eol=lf\n"
  },
  {
    "path": "packages/ipfs-http-response/test/fixtures/test-folder/files/hello.txt",
    "content": "hello world"
  },
  {
    "path": "packages/ipfs-http-response/test/fixtures/test-folder/holmes.txt",
    "content": "Project Gutenberg's The Adventures of Sherlock Holmes, by Arthur Conan Doyle\n\nThis eBook is for the use of anyone anywhere at no cost and with\nalmost no restrictions whatsoever.  You may copy it, give it away or\nre-use it under the terms of the Project Gutenberg License included\nwith this eBook or online at www.gutenberg.net\n\n\nTitle: The Adventures of Sherlock Holmes\n\nAuthor: Arthur Conan Doyle\n\nPosting Date: April 18, 2011 [EBook #1661]\nFirst Posted: November 29, 2002\n\nLanguage: English\n\n\n*** START OF THIS PROJECT GUTENBERG EBOOK THE ADVENTURES OF SHERLOCK HOLMES ***\n\n\n\n\nProduced by an anonymous Project Gutenberg volunteer and Jose Menendez\n\n\n\n\n\n\n\n\n\nTHE ADVENTURES OF SHERLOCK HOLMES\n\nby\n\nSIR ARTHUR CONAN DOYLE\n\n\n\n   I. A Scandal in Bohemia\n  II. The Red-headed League\n III. A Case of Identity\n  IV. The Boscombe Valley Mystery\n   V. The Five Orange Pips\n  VI. The Man with the Twisted Lip\n VII. The Adventure of the Blue Carbuncle\nVIII. The Adventure of the Speckled Band\n  IX. The Adventure of the Engineer's Thumb\n   X. The Adventure of the Noble Bachelor\n  XI. The Adventure of the Beryl Coronet\n XII. The Adventure of the Copper Beeches\n\n\n\n\nADVENTURE I. A SCANDAL IN BOHEMIA\n\nI.\n\nTo Sherlock Holmes she is always THE woman. I have seldom heard\nhim mention her under any other name. In his eyes she eclipses\nand predominates the whole of her sex. It was not that he felt\nany emotion akin to love for Irene Adler. All emotions, and that\none particularly, were abhorrent to his cold, precise but\nadmirably balanced mind. He was, I take it, the most perfect\nreasoning and observing machine that the world has seen, but as a\nlover he would have placed himself in a false position. He never\nspoke of the softer passions, save with a gibe and a sneer. They\nwere admirable things for the observer--excellent for drawing the\nveil from men's motives and actions. But for the trained reasoner\nto admit such intrusions into his own delicate and finely\nadjusted temperament was to introduce a distracting factor which\nmight throw a doubt upon all his mental results. Grit in a\nsensitive instrument, or a crack in one of his own high-power\nlenses, would not be more disturbing than a strong emotion in a\nnature such as his. And yet there was but one woman to him, and\nthat woman was the late Irene Adler, of dubious and questionable\nmemory.\n\nI had seen little of Holmes lately. My marriage had drifted us\naway from each other. My own complete happiness, and the\nhome-centred interests which rise up around the man who first\nfinds himself master of his own establishment, were sufficient to\nabsorb all my attention, while Holmes, who loathed every form of\nsociety with his whole Bohemian soul, remained in our lodgings in\nBaker Street, buried among his old books, and alternating from\nweek to week between cocaine and ambition, the drowsiness of the\ndrug, and the fierce energy of his own keen nature. He was still,\nas ever, deeply attracted by the study of crime, and occupied his\nimmense faculties and extraordinary powers of observation in\nfollowing out those clues, and clearing up those mysteries which\nhad been abandoned as hopeless by the official police. From time\nto time I heard some vague account of his doings: of his summons\nto Odessa in the case of the Trepoff murder, of his clearing up\nof the singular tragedy of the Atkinson brothers at Trincomalee,\nand finally of the mission which he had accomplished so\ndelicately and successfully for the reigning family of Holland.\nBeyond these signs of his activity, however, which I merely\nshared with all the readers of the daily press, I knew little of\nmy former friend and companion.\n\nOne night--it was on the twentieth of March, 1888--I was\nreturning from a journey to a patient (for I had now returned to\ncivil practice), when my way led me through Baker Street. As I\npassed the well-remembered door, which must always be associated\nin my mind with my wooing, and with the dark incidents of the\nStudy in Scarlet, I was seized with a keen desire to see Holmes\nagain, and to know how he was employing his extraordinary powers.\nHis rooms were brilliantly lit, and, even as I looked up, I saw\nhis tall, spare figure pass twice in a dark silhouette against\nthe blind. He was pacing the room swiftly, eagerly, with his head\nsunk upon his chest and his hands clasped behind him. To me, who\nknew his every mood and habit, his attitude and manner told their\nown story. He was at work again. He had risen out of his\ndrug-created dreams and was hot upon the scent of some new\nproblem. I rang the bell and was shown up to the chamber which\nhad formerly been in part my own.\n\nHis manner was not effusive. It seldom was; but he was glad, I\nthink, to see me. With hardly a word spoken, but with a kindly\neye, he waved me to an armchair, threw across his case of cigars,\nand indicated a spirit case and a gasogene in the corner. Then he\nstood before the fire and looked me over in his singular\nintrospective fashion.\n\n\"Wedlock suits you,\" he remarked. \"I think, Watson, that you have\nput on seven and a half pounds since I saw you.\"\n\n\"Seven!\" I answered.\n\n\"Indeed, I should have thought a little more. Just a trifle more,\nI fancy, Watson. And in practice again, I observe. You did not\ntell me that you intended to go into harness.\"\n\n\"Then, how do you know?\"\n\n\"I see it, I deduce it. How do I know that you have been getting\nyourself very wet lately, and that you have a most clumsy and\ncareless servant girl?\"\n\n\"My dear Holmes,\" said I, \"this is too much. You would certainly\nhave been burned, had you lived a few centuries ago. It is true\nthat I had a country walk on Thursday and came home in a dreadful\nmess, but as I have changed my clothes I can't imagine how you\ndeduce it. As to Mary Jane, she is incorrigible, and my wife has\ngiven her notice, but there, again, I fail to see how you work it\nout.\"\n\nHe chuckled to himself and rubbed his long, nervous hands\ntogether.\n\n\"It is simplicity itself,\" said he; \"my eyes tell me that on the\ninside of your left shoe, just where the firelight strikes it,\nthe leather is scored by six almost parallel cuts. Obviously they\nhave been caused by someone who has very carelessly scraped round\nthe edges of the sole in order to remove crusted mud from it.\nHence, you see, my double deduction that you had been out in vile\nweather, and that you had a particularly malignant boot-slitting\nspecimen of the London slavey. As to your practice, if a\ngentleman walks into my rooms smelling of iodoform, with a black\nmark of nitrate of silver upon his right forefinger, and a bulge\non the right side of his top-hat to show where he has secreted\nhis stethoscope, I must be dull, indeed, if I do not pronounce\nhim to be an active member of the medical profession.\"\n\nI could not help laughing at the ease with which he explained his\nprocess of deduction. \"When I hear you give your reasons,\" I\nremarked, \"the thing always appears to me to be so ridiculously\nsimple that I could easily do it myself, though at each\nsuccessive instance of your reasoning I am baffled until you\nexplain your process. And yet I believe that my eyes are as good\nas yours.\"\n\n\"Quite so,\" he answered, lighting a cigarette, and throwing\nhimself down into an armchair. \"You see, but you do not observe.\nThe distinction is clear. For example, you have frequently seen\nthe steps which lead up from the hall to this room.\"\n\n\"Frequently.\"\n\n\"How often?\"\n\n\"Well, some hundreds of times.\"\n\n\"Then how many are there?\"\n\n\"How many? I don't know.\"\n\n\"Quite so! You have not observed. And yet you have seen. That is\njust my point. Now, I know that there are seventeen steps,\nbecause I have both seen and observed. By-the-way, since you are\ninterested in these little problems, and since you are good\nenough to chronicle one or two of my trifling experiences, you\nmay be interested in this.\" He threw over a sheet of thick,\npink-tinted note-paper which had been lying open upon the table.\n\"It came by the last post,\" said he. \"Read it aloud.\"\n\nThe note was undated, and without either signature or address.\n\n\"There will call upon you to-night, at a quarter to eight\no'clock,\" it said, \"a gentleman who desires to consult you upon a\nmatter of the very deepest moment. Your recent services to one of\nthe royal houses of Europe have shown that you are one who may\nsafely be trusted with matters which are of an importance which\ncan hardly be exaggerated. This account of you we have from all\nquarters received. Be in your chamber then at that hour, and do\nnot take it amiss if your visitor wear a mask.\"\n\n\"This is indeed a mystery,\" I remarked. \"What do you imagine that\nit means?\"\n\n\"I have no data yet. It is a capital mistake to theorize before\none has data. Insensibly one begins to twist facts to suit\ntheories, instead of theories to suit facts. But the note itself.\nWhat do you deduce from it?\"\n\nI carefully examined the writing, and the paper upon which it was\nwritten.\n\n\"The man who wrote it was presumably well to do,\" I remarked,\nendeavouring to imitate my companion's processes. \"Such paper\ncould not be bought under half a crown a packet. It is peculiarly\nstrong and stiff.\"\n\n\"Peculiar--that is the very word,\" said Holmes. \"It is not an\nEnglish paper at all. Hold it up to the light.\"\n\nI did so, and saw a large \"E\" with a small \"g,\" a \"P,\" and a\nlarge \"G\" with a small \"t\" woven into the texture of the paper.\n\n\"What do you make of that?\" asked Holmes.\n\n\"The name of the maker, no doubt; or his monogram, rather.\"\n\n\"Not at all. The 'G' with the small 't' stands for\n'Gesellschaft,' which is the German for 'Company.' It is a\ncustomary contraction like our 'Co.' 'P,' of course, stands for\n'Papier.' Now for the 'Eg.' Let us glance at our Continental\nGazetteer.\" He took down a heavy brown volume from his shelves.\n\"Eglow, Eglonitz--here we are, Egria. It is in a German-speaking\ncountry--in Bohemia, not far from Carlsbad. 'Remarkable as being\nthe scene of the death of Wallenstein, and for its numerous\nglass-factories and paper-mills.' Ha, ha, my boy, what do you\nmake of that?\" His eyes sparkled, and he sent up a great blue\ntriumphant cloud from his cigarette.\n\n\"The paper was made in Bohemia,\" I said.\n\n\"Precisely. And the man who wrote the note is a German. Do you\nnote the peculiar construction of the sentence--'This account of\nyou we have from all quarters received.' A Frenchman or Russian\ncould not have written that. It is the German who is so\nuncourteous to his verbs. It only remains, therefore, to discover\nwhat is wanted by this German who writes upon Bohemian paper and\nprefers wearing a mask to showing his face. And here he comes, if\nI am not mistaken, to resolve all our doubts.\"\n\nAs he spoke there was the sharp sound of horses' hoofs and\ngrating wheels against the curb, followed by a sharp pull at the\nbell. Holmes whistled.\n\n\"A pair, by the sound,\" said he. \"Yes,\" he continued, glancing\nout of the window. \"A nice little brougham and a pair of\nbeauties. A hundred and fifty guineas apiece. There's money in\nthis case, Watson, if there is nothing else.\"\n\n\"I think that I had better go, Holmes.\"\n\n\"Not a bit, Doctor. Stay where you are. I am lost without my\nBoswell. And this promises to be interesting. It would be a pity\nto miss it.\"\n\n\"But your client--\"\n\n\"Never mind him. I may want your help, and so may he. Here he\ncomes. Sit down in that armchair, Doctor, and give us your best\nattention.\"\n\nA slow and heavy step, which had been heard upon the stairs and\nin the passage, paused immediately outside the door. Then there\nwas a loud and authoritative tap.\n\n\"Come in!\" said Holmes.\n\nA man entered who could hardly have been less than six feet six\ninches in height, with the chest and limbs of a Hercules. His\ndress was rich with a richness which would, in England, be looked\nupon as akin to bad taste. Heavy bands of astrakhan were slashed\nacross the sleeves and fronts of his double-breasted coat, while\nthe deep blue cloak which was thrown over his shoulders was lined\nwith flame-coloured silk and secured at the neck with a brooch\nwhich consisted of a single flaming beryl. Boots which extended\nhalfway up his calves, and which were trimmed at the tops with\nrich brown fur, completed the impression of barbaric opulence\nwhich was suggested by his whole appearance. He carried a\nbroad-brimmed hat in his hand, while he wore across the upper\npart of his face, extending down past the cheekbones, a black\nvizard mask, which he had apparently adjusted that very moment,\nfor his hand was still raised to it as he entered. From the lower\npart of the face he appeared to be a man of strong character,\nwith a thick, hanging lip, and a long, straight chin suggestive\nof resolution pushed to the length of obstinacy.\n\n\"You had my note?\" he asked with a deep harsh voice and a\nstrongly marked German accent. \"I told you that I would call.\" He\nlooked from one to the other of us, as if uncertain which to\naddress.\n\n\"Pray take a seat,\" said Holmes. \"This is my friend and\ncolleague, Dr. Watson, who is occasionally good enough to help me\nin my cases. Whom have I the honour to address?\"\n\n\"You may address me as the Count Von Kramm, a Bohemian nobleman.\nI understand that this gentleman, your friend, is a man of honour\nand discretion, whom I may trust with a matter of the most\nextreme importance. If not, I should much prefer to communicate\nwith you alone.\"\n\nI rose to go, but Holmes caught me by the wrist and pushed me\nback into my chair. \"It is both, or none,\" said he. \"You may say\nbefore this gentleman anything which you may say to me.\"\n\nThe Count shrugged his broad shoulders. \"Then I must begin,\" said\nhe, \"by binding you both to absolute secrecy for two years; at\nthe end of that time the matter will be of no importance. At\npresent it is not too much to say that it is of such weight it\nmay have an influence upon European history.\"\n\n\"I promise,\" said Holmes.\n\n\"And I.\"\n\n\"You will excuse this mask,\" continued our strange visitor. \"The\naugust person who employs me wishes his agent to be unknown to\nyou, and I may confess at once that the title by which I have\njust called myself is not exactly my own.\"\n\n\"I was aware of it,\" said Holmes dryly.\n\n\"The circumstances are of great delicacy, and every precaution\nhas to be taken to quench what might grow to be an immense\nscandal and seriously compromise one of the reigning families of\nEurope. To speak plainly, the matter implicates the great House\nof Ormstein, hereditary kings of Bohemia.\"\n\n\"I was also aware of that,\" murmured Holmes, settling himself\ndown in his armchair and closing his eyes.\n\nOur visitor glanced with some apparent surprise at the languid,\nlounging figure of the man who had been no doubt depicted to him\nas the most incisive reasoner and most energetic agent in Europe.\nHolmes slowly reopened his eyes and looked impatiently at his\ngigantic client.\n\n\"If your Majesty would condescend to state your case,\" he\nremarked, \"I should be better able to advise you.\"\n\nThe man sprang from his chair and paced up and down the room in\nuncontrollable agitation. Then, with a gesture of desperation, he\ntore the mask from his face and hurled it upon the ground. \"You\nare right,\" he cried; \"I am the King. Why should I attempt to\nconceal it?\"\n\n\"Why, indeed?\" murmured Holmes. \"Your Majesty had not spoken\nbefore I was aware that I was addressing Wilhelm Gottsreich\nSigismond von Ormstein, Grand Duke of Cassel-Felstein, and\nhereditary King of Bohemia.\"\n\n\"But you can understand,\" said our strange visitor, sitting down\nonce more and passing his hand over his high white forehead, \"you\ncan understand that I am not accustomed to doing such business in\nmy own person. Yet the matter was so delicate that I could not\nconfide it to an agent without putting myself in his power. I\nhave come incognito from Prague for the purpose of consulting\nyou.\"\n\n\"Then, pray consult,\" said Holmes, shutting his eyes once more.\n\n\"The facts are briefly these: Some five years ago, during a\nlengthy visit to Warsaw, I made the acquaintance of the well-known\nadventuress, Irene Adler. The name is no doubt familiar to you.\"\n\n\"Kindly look her up in my index, Doctor,\" murmured Holmes without\nopening his eyes. For many years he had adopted a system of\ndocketing all paragraphs concerning men and things, so that it\nwas difficult to name a subject or a person on which he could not\nat once furnish information. In this case I found her biography\nsandwiched in between that of a Hebrew rabbi and that of a\nstaff-commander who had written a monograph upon the deep-sea\nfishes.\n\n\"Let me see!\" said Holmes. \"Hum! Born in New Jersey in the year\n1858. Contralto--hum! La Scala, hum! Prima donna Imperial Opera\nof Warsaw--yes! Retired from operatic stage--ha! Living in\nLondon--quite so! Your Majesty, as I understand, became entangled\nwith this young person, wrote her some compromising letters, and\nis now desirous of getting those letters back.\"\n\n\"Precisely so. But how--\"\n\n\"Was there a secret marriage?\"\n\n\"None.\"\n\n\"No legal papers or certificates?\"\n\n\"None.\"\n\n\"Then I fail to follow your Majesty. If this young person should\nproduce her letters for blackmailing or other purposes, how is\nshe to prove their authenticity?\"\n\n\"There is the writing.\"\n\n\"Pooh, pooh! Forgery.\"\n\n\"My private note-paper.\"\n\n\"Stolen.\"\n\n\"My own seal.\"\n\n\"Imitated.\"\n\n\"My photograph.\"\n\n\"Bought.\"\n\n\"We were both in the photograph.\"\n\n\"Oh, dear! That is very bad! Your Majesty has indeed committed an\nindiscretion.\"\n\n\"I was mad--insane.\"\n\n\"You have compromised yourself seriously.\"\n\n\"I was only Crown Prince then. I was young. I am but thirty now.\"\n\n\"It must be recovered.\"\n\n\"We have tried and failed.\"\n\n\"Your Majesty must pay. It must be bought.\"\n\n\"She will not sell.\"\n\n\"Stolen, then.\"\n\n\"Five attempts have been made. Twice burglars in my pay ransacked\nher house. Once we diverted her luggage when she travelled. Twice\nshe has been waylaid. There has been no result.\"\n\n\"No sign of it?\"\n\n\"Absolutely none.\"\n\nHolmes laughed. \"It is quite a pretty little problem,\" said he.\n\n\"But a very serious one to me,\" returned the King reproachfully.\n\n\"Very, indeed. And what does she propose to do with the\nphotograph?\"\n\n\"To ruin me.\"\n\n\"But how?\"\n\n\"I am about to be married.\"\n\n\"So I have heard.\"\n\n\"To Clotilde Lothman von Saxe-Meningen, second daughter of the\nKing of Scandinavia. You may know the strict principles of her\nfamily. She is herself the very soul of delicacy. A shadow of a\ndoubt as to my conduct would bring the matter to an end.\"\n\n\"And Irene Adler?\"\n\n\"Threatens to send them the photograph. And she will do it. I\nknow that she will do it. You do not know her, but she has a soul\nof steel. She has the face of the most beautiful of women, and\nthe mind of the most resolute of men. Rather than I should marry\nanother woman, there are no lengths to which she would not\ngo--none.\"\n\n\"You are sure that she has not sent it yet?\"\n\n\"I am sure.\"\n\n\"And why?\"\n\n\"Because she has said that she would send it on the day when the\nbetrothal was publicly proclaimed. That will be next Monday.\"\n\n\"Oh, then we have three days yet,\" said Holmes with a yawn. \"That\nis very fortunate, as I have one or two matters of importance to\nlook into just at present. Your Majesty will, of course, stay in\nLondon for the present?\"\n\n\"Certainly. You will find me at the Langham under the name of the\nCount Von Kramm.\"\n\n\"Then I shall drop you a line to let you know how we progress.\"\n\n\"Pray do so. I shall be all anxiety.\"\n\n\"Then, as to money?\"\n\n\"You have carte blanche.\"\n\n\"Absolutely?\"\n\n\"I tell you that I would give one of the provinces of my kingdom\nto have that photograph.\"\n\n\"And for present expenses?\"\n\nThe King took a heavy chamois leather bag from under his cloak\nand laid it on the table.\n\n\"There are three hundred pounds in gold and seven hundred in\nnotes,\" he said.\n\nHolmes scribbled a receipt upon a sheet of his note-book and\nhanded it to him.\n\n\"And Mademoiselle's address?\" he asked.\n\n\"Is Briony Lodge, Serpentine Avenue, St. John's Wood.\"\n\nHolmes took a note of it. \"One other question,\" said he. \"Was the\nphotograph a cabinet?\"\n\n\"It was.\"\n\n\"Then, good-night, your Majesty, and I trust that we shall soon\nhave some good news for you. And good-night, Watson,\" he added,\nas the wheels of the royal brougham rolled down the street. \"If\nyou will be good enough to call to-morrow afternoon at three\no'clock I should like to chat this little matter over with you.\"\n\n\nII.\n\nAt three o'clock precisely I was at Baker Street, but Holmes had\nnot yet returned. The landlady informed me that he had left the\nhouse shortly after eight o'clock in the morning. I sat down\nbeside the fire, however, with the intention of awaiting him,\nhowever long he might be. I was already deeply interested in his\ninquiry, for, though it was surrounded by none of the grim and\nstrange features which were associated with the two crimes which\nI have already recorded, still, the nature of the case and the\nexalted station of his client gave it a character of its own.\nIndeed, apart from the nature of the investigation which my\nfriend had on hand, there was something in his masterly grasp of\na situation, and his keen, incisive reasoning, which made it a\npleasure to me to study his system of work, and to follow the\nquick, subtle methods by which he disentangled the most\ninextricable mysteries. So accustomed was I to his invariable\nsuccess that the very possibility of his failing had ceased to\nenter into my head.\n\nIt was close upon four before the door opened, and a\ndrunken-looking groom, ill-kempt and side-whiskered, with an\ninflamed face and disreputable clothes, walked into the room.\nAccustomed as I was to my friend's amazing powers in the use of\ndisguises, I had to look three times before I was certain that it\nwas indeed he. With a nod he vanished into the bedroom, whence he\nemerged in five minutes tweed-suited and respectable, as of old.\nPutting his hands into his pockets, he stretched out his legs in\nfront of the fire and laughed heartily for some minutes.\n\n\"Well, really!\" he cried, and then he choked and laughed again\nuntil he was obliged to lie back, limp and helpless, in the\nchair.\n\n\"What is it?\"\n\n\"It's quite too funny. I am sure you could never guess how I\nemployed my morning, or what I ended by doing.\"\n\n\"I can't imagine. I suppose that you have been watching the\nhabits, and perhaps the house, of Miss Irene Adler.\"\n\n\"Quite so; but the sequel was rather unusual. I will tell you,\nhowever. I left the house a little after eight o'clock this\nmorning in the character of a groom out of work. There is a\nwonderful sympathy and freemasonry among horsey men. Be one of\nthem, and you will know all that there is to know. I soon found\nBriony Lodge. It is a bijou villa, with a garden at the back, but\nbuilt out in front right up to the road, two stories. Chubb lock\nto the door. Large sitting-room on the right side, well\nfurnished, with long windows almost to the floor, and those\npreposterous English window fasteners which a child could open.\nBehind there was nothing remarkable, save that the passage window\ncould be reached from the top of the coach-house. I walked round\nit and examined it closely from every point of view, but without\nnoting anything else of interest.\n\n\"I then lounged down the street and found, as I expected, that\nthere was a mews in a lane which runs down by one wall of the\ngarden. I lent the ostlers a hand in rubbing down their horses,\nand received in exchange twopence, a glass of half and half, two\nfills of shag tobacco, and as much information as I could desire\nabout Miss Adler, to say nothing of half a dozen other people in\nthe neighbourhood in whom I was not in the least interested, but\nwhose biographies I was compelled to listen to.\"\n\n\"And what of Irene Adler?\" I asked.\n\n\"Oh, she has turned all the men's heads down in that part. She is\nthe daintiest thing under a bonnet on this planet. So say the\nSerpentine-mews, to a man. She lives quietly, sings at concerts,\ndrives out at five every day, and returns at seven sharp for\ndinner. Seldom goes out at other times, except when she sings.\nHas only one male visitor, but a good deal of him. He is dark,\nhandsome, and dashing, never calls less than once a day, and\noften twice. He is a Mr. Godfrey Norton, of the Inner Temple. See\nthe advantages of a cabman as a confidant. They had driven him\nhome a dozen times from Serpentine-mews, and knew all about him.\nWhen I had listened to all they had to tell, I began to walk up\nand down near Briony Lodge once more, and to think over my plan\nof campaign.\n\n\"This Godfrey Norton was evidently an important factor in the\nmatter. He was a lawyer. That sounded ominous. What was the\nrelation between them, and what the object of his repeated\nvisits? Was she his client, his friend, or his mistress? If the\nformer, she had probably transferred the photograph to his\nkeeping. If the latter, it was less likely. On the issue of this\nquestion depended whether I should continue my work at Briony\nLodge, or turn my attention to the gentleman's chambers in the\nTemple. It was a delicate point, and it widened the field of my\ninquiry. I fear that I bore you with these details, but I have to\nlet you see my little difficulties, if you are to understand the\nsituation.\"\n\n\"I am following you closely,\" I answered.\n\n\"I was still balancing the matter in my mind when a hansom cab\ndrove up to Briony Lodge, and a gentleman sprang out. He was a\nremarkably handsome man, dark, aquiline, and moustached--evidently\nthe man of whom I had heard. He appeared to be in a\ngreat hurry, shouted to the cabman to wait, and brushed past the\nmaid who opened the door with the air of a man who was thoroughly\nat home.\n\n\"He was in the house about half an hour, and I could catch\nglimpses of him in the windows of the sitting-room, pacing up and\ndown, talking excitedly, and waving his arms. Of her I could see\nnothing. Presently he emerged, looking even more flurried than\nbefore. As he stepped up to the cab, he pulled a gold watch from\nhis pocket and looked at it earnestly, 'Drive like the devil,' he\nshouted, 'first to Gross & Hankey's in Regent Street, and then to\nthe Church of St. Monica in the Edgeware Road. Half a guinea if\nyou do it in twenty minutes!'\n\n\"Away they went, and I was just wondering whether I should not do\nwell to follow them when up the lane came a neat little landau,\nthe coachman with his coat only half-buttoned, and his tie under\nhis ear, while all the tags of his harness were sticking out of\nthe buckles. It hadn't pulled up before she shot out of the hall\ndoor and into it. I only caught a glimpse of her at the moment,\nbut she was a lovely woman, with a face that a man might die for.\n\n\"'The Church of St. Monica, John,' she cried, 'and half a\nsovereign if you reach it in twenty minutes.'\n\n\"This was quite too good to lose, Watson. I was just balancing\nwhether I should run for it, or whether I should perch behind her\nlandau when a cab came through the street. The driver looked\ntwice at such a shabby fare, but I jumped in before he could\nobject. 'The Church of St. Monica,' said I, 'and half a sovereign\nif you reach it in twenty minutes.' It was twenty-five minutes to\ntwelve, and of course it was clear enough what was in the wind.\n\n\"My cabby drove fast. I don't think I ever drove faster, but the\nothers were there before us. The cab and the landau with their\nsteaming horses were in front of the door when I arrived. I paid\nthe man and hurried into the church. There was not a soul there\nsave the two whom I had followed and a surpliced clergyman, who\nseemed to be expostulating with them. They were all three\nstanding in a knot in front of the altar. I lounged up the side\naisle like any other idler who has dropped into a church.\nSuddenly, to my surprise, the three at the altar faced round to\nme, and Godfrey Norton came running as hard as he could towards\nme.\n\n\"'Thank God,' he cried. 'You'll do. Come! Come!'\n\n\"'What then?' I asked.\n\n\"'Come, man, come, only three minutes, or it won't be legal.'\n\n\"I was half-dragged up to the altar, and before I knew where I was\nI found myself mumbling responses which were whispered in my ear,\nand vouching for things of which I knew nothing, and generally\nassisting in the secure tying up of Irene Adler, spinster, to\nGodfrey Norton, bachelor. It was all done in an instant, and\nthere was the gentleman thanking me on the one side and the lady\non the other, while the clergyman beamed on me in front. It was\nthe most preposterous position in which I ever found myself in my\nlife, and it was the thought of it that started me laughing just\nnow. It seems that there had been some informality about their\nlicense, that the clergyman absolutely refused to marry them\nwithout a witness of some sort, and that my lucky appearance\nsaved the bridegroom from having to sally out into the streets in\nsearch of a best man. The bride gave me a sovereign, and I mean\nto wear it on my watch-chain in memory of the occasion.\"\n\n\"This is a very unexpected turn of affairs,\" said I; \"and what\nthen?\"\n\n\"Well, I found my plans very seriously menaced. It looked as if\nthe pair might take an immediate departure, and so necessitate\nvery prompt and energetic measures on my part. At the church\ndoor, however, they separated, he driving back to the Temple, and\nshe to her own house. 'I shall drive out in the park at five as\nusual,' she said as she left him. I heard no more. They drove\naway in different directions, and I went off to make my own\narrangements.\"\n\n\"Which are?\"\n\n\"Some cold beef and a glass of beer,\" he answered, ringing the\nbell. \"I have been too busy to think of food, and I am likely to\nbe busier still this evening. By the way, Doctor, I shall want\nyour co-operation.\"\n\n\"I shall be delighted.\"\n\n\"You don't mind breaking the law?\"\n\n\"Not in the least.\"\n\n\"Nor running a chance of arrest?\"\n\n\"Not in a good cause.\"\n\n\"Oh, the cause is excellent!\"\n\n\"Then I am your man.\"\n\n\"I was sure that I might rely on you.\"\n\n\"But what is it you wish?\"\n\n\"When Mrs. Turner has brought in the tray I will make it clear to\nyou. Now,\" he said as he turned hungrily on the simple fare that\nour landlady had provided, \"I must discuss it while I eat, for I\nhave not much time. It is nearly five now. In two hours we must\nbe on the scene of action. Miss Irene, or Madame, rather, returns\nfrom her drive at seven. We must be at Briony Lodge to meet her.\"\n\n\"And what then?\"\n\n\"You must leave that to me. I have already arranged what is to\noccur. There is only one point on which I must insist. You must\nnot interfere, come what may. You understand?\"\n\n\"I am to be neutral?\"\n\n\"To do nothing whatever. There will probably be some small\nunpleasantness. Do not join in it. It will end in my being\nconveyed into the house. Four or five minutes afterwards the\nsitting-room window will open. You are to station yourself close\nto that open window.\"\n\n\"Yes.\"\n\n\"You are to watch me, for I will be visible to you.\"\n\n\"Yes.\"\n\n\"And when I raise my hand--so--you will throw into the room what\nI give you to throw, and will, at the same time, raise the cry of\nfire. You quite follow me?\"\n\n\"Entirely.\"\n\n\"It is nothing very formidable,\" he said, taking a long cigar-shaped\nroll from his pocket. \"It is an ordinary plumber's smoke-rocket,\nfitted with a cap at either end to make it self-lighting.\nYour task is confined to that. When you raise your cry of fire,\nit will be taken up by quite a number of people. You may then\nwalk to the end of the street, and I will rejoin you in ten\nminutes. I hope that I have made myself clear?\"\n\n\"I am to remain neutral, to get near the window, to watch you,\nand at the signal to throw in this object, then to raise the cry\nof fire, and to wait you at the corner of the street.\"\n\n\"Precisely.\"\n\n\"Then you may entirely rely on me.\"\n\n\"That is excellent. I think, perhaps, it is almost time that I\nprepare for the new role I have to play.\"\n\nHe disappeared into his bedroom and returned in a few minutes in\nthe character of an amiable and simple-minded Nonconformist\nclergyman. His broad black hat, his baggy trousers, his white\ntie, his sympathetic smile, and general look of peering and\nbenevolent curiosity were such as Mr. John Hare alone could have\nequalled. It was not merely that Holmes changed his costume. His\nexpression, his manner, his very soul seemed to vary with every\nfresh part that he assumed. The stage lost a fine actor, even as\nscience lost an acute reasoner, when he became a specialist in\ncrime.\n\nIt was a quarter past six when we left Baker Street, and it still\nwanted ten minutes to the hour when we found ourselves in\nSerpentine Avenue. It was already dusk, and the lamps were just\nbeing lighted as we paced up and down in front of Briony Lodge,\nwaiting for the coming of its occupant. The house was just such\nas I had pictured it from Sherlock Holmes' succinct description,\nbut the locality appeared to be less private than I expected. On\nthe contrary, for a small street in a quiet neighbourhood, it was\nremarkably animated. There was a group of shabbily dressed men\nsmoking and laughing in a corner, a scissors-grinder with his\nwheel, two guardsmen who were flirting with a nurse-girl, and\nseveral well-dressed young men who were lounging up and down with\ncigars in their mouths.\n\n\"You see,\" remarked Holmes, as we paced to and fro in front of\nthe house, \"this marriage rather simplifies matters. The\nphotograph becomes a double-edged weapon now. The chances are\nthat she would be as averse to its being seen by Mr. Godfrey\nNorton, as our client is to its coming to the eyes of his\nprincess. Now the question is, Where are we to find the\nphotograph?\"\n\n\"Where, indeed?\"\n\n\"It is most unlikely that she carries it about with her. It is\ncabinet size. Too large for easy concealment about a woman's\ndress. She knows that the King is capable of having her waylaid\nand searched. Two attempts of the sort have already been made. We\nmay take it, then, that she does not carry it about with her.\"\n\n\"Where, then?\"\n\n\"Her banker or her lawyer. There is that double possibility. But\nI am inclined to think neither. Women are naturally secretive,\nand they like to do their own secreting. Why should she hand it\nover to anyone else? She could trust her own guardianship, but\nshe could not tell what indirect or political influence might be\nbrought to bear upon a business man. Besides, remember that she\nhad resolved to use it within a few days. It must be where she\ncan lay her hands upon it. It must be in her own house.\"\n\n\"But it has twice been burgled.\"\n\n\"Pshaw! They did not know how to look.\"\n\n\"But how will you look?\"\n\n\"I will not look.\"\n\n\"What then?\"\n\n\"I will get her to show me.\"\n\n\"But she will refuse.\"\n\n\"She will not be able to. But I hear the rumble of wheels. It is\nher carriage. Now carry out my orders to the letter.\"\n\nAs he spoke the gleam of the side-lights of a carriage came round\nthe curve of the avenue. It was a smart little landau which\nrattled up to the door of Briony Lodge. As it pulled up, one of\nthe loafing men at the corner dashed forward to open the door in\nthe hope of earning a copper, but was elbowed away by another\nloafer, who had rushed up with the same intention. A fierce\nquarrel broke out, which was increased by the two guardsmen, who\ntook sides with one of the loungers, and by the scissors-grinder,\nwho was equally hot upon the other side. A blow was struck, and\nin an instant the lady, who had stepped from her carriage, was\nthe centre of a little knot of flushed and struggling men, who\nstruck savagely at each other with their fists and sticks. Holmes\ndashed into the crowd to protect the lady; but just as he reached\nher he gave a cry and dropped to the ground, with the blood\nrunning freely down his face. At his fall the guardsmen took to\ntheir heels in one direction and the loungers in the other, while\na number of better-dressed people, who had watched the scuffle\nwithout taking part in it, crowded in to help the lady and to\nattend to the injured man. Irene Adler, as I will still call her,\nhad hurried up the steps; but she stood at the top with her\nsuperb figure outlined against the lights of the hall, looking\nback into the street.\n\n\"Is the poor gentleman much hurt?\" she asked.\n\n\"He is dead,\" cried several voices.\n\n\"No, no, there's life in him!\" shouted another. \"But he'll be\ngone before you can get him to hospital.\"\n\n\"He's a brave fellow,\" said a woman. \"They would have had the\nlady's purse and watch if it hadn't been for him. They were a\ngang, and a rough one, too. Ah, he's breathing now.\"\n\n\"He can't lie in the street. May we bring him in, marm?\"\n\n\"Surely. Bring him into the sitting-room. There is a comfortable\nsofa. This way, please!\"\n\nSlowly and solemnly he was borne into Briony Lodge and laid out\nin the principal room, while I still observed the proceedings\nfrom my post by the window. The lamps had been lit, but the\nblinds had not been drawn, so that I could see Holmes as he lay\nupon the couch. I do not know whether he was seized with\ncompunction at that moment for the part he was playing, but I\nknow that I never felt more heartily ashamed of myself in my life\nthan when I saw the beautiful creature against whom I was\nconspiring, or the grace and kindliness with which she waited\nupon the injured man. And yet it would be the blackest treachery\nto Holmes to draw back now from the part which he had intrusted\nto me. I hardened my heart, and took the smoke-rocket from under\nmy ulster. After all, I thought, we are not injuring her. We are\nbut preventing her from injuring another.\n\nHolmes had sat up upon the couch, and I saw him motion like a man\nwho is in need of air. A maid rushed across and threw open the\nwindow. At the same instant I saw him raise his hand and at the\nsignal I tossed my rocket into the room with a cry of \"Fire!\" The\nword was no sooner out of my mouth than the whole crowd of\nspectators, well dressed and ill--gentlemen, ostlers, and\nservant-maids--joined in a general shriek of \"Fire!\" Thick clouds\nof smoke curled through the room and out at the open window. I\ncaught a glimpse of rushing figures, and a moment later the voice\nof Holmes from within assuring them that it was a false alarm.\nSlipping through the shouting crowd I made my way to the corner\nof the street, and in ten minutes was rejoiced to find my\nfriend's arm in mine, and to get away from the scene of uproar.\nHe walked swiftly and in silence for some few minutes until we\nhad turned down one of the quiet streets which lead towards the\nEdgeware Road.\n\n\"You did it very nicely, Doctor,\" he remarked. \"Nothing could\nhave been better. It is all right.\"\n\n\"You have the photograph?\"\n\n\"I know where it is.\"\n\n\"And how did you find out?\"\n\n\"She showed me, as I told you she would.\"\n\n\"I am still in the dark.\"\n\n\"I do not wish to make a mystery,\" said he, laughing. \"The matter\nwas perfectly simple. You, of course, saw that everyone in the\nstreet was an accomplice. They were all engaged for the evening.\"\n\n\"I guessed as much.\"\n\n\"Then, when the row broke out, I had a little moist red paint in\nthe palm of my hand. I rushed forward, fell down, clapped my hand\nto my face, and became a piteous spectacle. It is an old trick.\"\n\n\"That also I could fathom.\"\n\n\"Then they carried me in. She was bound to have me in. What else\ncould she do? And into her sitting-room, which was the very room\nwhich I suspected. It lay between that and her bedroom, and I was\ndetermined to see which. They laid me on a couch, I motioned for\nair, they were compelled to open the window, and you had your\nchance.\"\n\n\"How did that help you?\"\n\n\"It was all-important. When a woman thinks that her house is on\nfire, her instinct is at once to rush to the thing which she\nvalues most. It is a perfectly overpowering impulse, and I have\nmore than once taken advantage of it. In the case of the\nDarlington substitution scandal it was of use to me, and also in\nthe Arnsworth Castle business. A married woman grabs at her baby;\nan unmarried one reaches for her jewel-box. Now it was clear to\nme that our lady of to-day had nothing in the house more precious\nto her than what we are in quest of. She would rush to secure it.\nThe alarm of fire was admirably done. The smoke and shouting were\nenough to shake nerves of steel. She responded beautifully. The\nphotograph is in a recess behind a sliding panel just above the\nright bell-pull. She was there in an instant, and I caught a\nglimpse of it as she half-drew it out. When I cried out that it\nwas a false alarm, she replaced it, glanced at the rocket, rushed\nfrom the room, and I have not seen her since. I rose, and, making\nmy excuses, escaped from the house. I hesitated whether to\nattempt to secure the photograph at once; but the coachman had\ncome in, and as he was watching me narrowly it seemed safer to\nwait. A little over-precipitance may ruin all.\"\n\n\"And now?\" I asked.\n\n\"Our quest is practically finished. I shall call with the King\nto-morrow, and with you, if you care to come with us. We will be\nshown into the sitting-room to wait for the lady, but it is\nprobable that when she comes she may find neither us nor the\nphotograph. It might be a satisfaction to his Majesty to regain\nit with his own hands.\"\n\n\"And when will you call?\"\n\n\"At eight in the morning. She will not be up, so that we shall\nhave a clear field. Besides, we must be prompt, for this marriage\nmay mean a complete change in her life and habits. I must wire to\nthe King without delay.\"\n\nWe had reached Baker Street and had stopped at the door. He was\nsearching his pockets for the key when someone passing said:\n\n\"Good-night, Mister Sherlock Holmes.\"\n\nThere were several people on the pavement at the time, but the\ngreeting appeared to come from a slim youth in an ulster who had\nhurried by.\n\n\"I've heard that voice before,\" said Holmes, staring down the\ndimly lit street. \"Now, I wonder who the deuce that could have\nbeen.\"\n\n\nIII.\n\nI slept at Baker Street that night, and we were engaged upon our\ntoast and coffee in the morning when the King of Bohemia rushed\ninto the room.\n\n\"You have really got it!\" he cried, grasping Sherlock Holmes by\neither shoulder and looking eagerly into his face.\n\n\"Not yet.\"\n\n\"But you have hopes?\"\n\n\"I have hopes.\"\n\n\"Then, come. I am all impatience to be gone.\"\n\n\"We must have a cab.\"\n\n\"No, my brougham is waiting.\"\n\n\"Then that will simplify matters.\" We descended and started off\nonce more for Briony Lodge.\n\n\"Irene Adler is married,\" remarked Holmes.\n\n\"Married! When?\"\n\n\"Yesterday.\"\n\n\"But to whom?\"\n\n\"To an English lawyer named Norton.\"\n\n\"But she could not love him.\"\n\n\"I am in hopes that she does.\"\n\n\"And why in hopes?\"\n\n\"Because it would spare your Majesty all fear of future\nannoyance. If the lady loves her husband, she does not love your\nMajesty. If she does not love your Majesty, there is no reason\nwhy she should interfere with your Majesty's plan.\"\n\n\"It is true. And yet--Well! I wish she had been of my own\nstation! What a queen she would have made!\" He relapsed into a\nmoody silence, which was not broken until we drew up in\nSerpentine Avenue.\n\nThe door of Briony Lodge was open, and an elderly woman stood\nupon the steps. She watched us with a sardonic eye as we stepped\nfrom the brougham.\n\n\"Mr. Sherlock Holmes, I believe?\" said she.\n\n\"I am Mr. Holmes,\" answered my companion, looking at her with a\nquestioning and rather startled gaze.\n\n\"Indeed! My mistress told me that you were likely to call. She\nleft this morning with her husband by the 5:15 train from Charing\nCross for the Continent.\"\n\n\"What!\" Sherlock Holmes staggered back, white with chagrin and\nsurprise. \"Do you mean that she has left England?\"\n\n\"Never to return.\"\n\n\"And the papers?\" asked the King hoarsely. \"All is lost.\"\n\n\"We shall see.\" He pushed past the servant and rushed into the\ndrawing-room, followed by the King and myself. The furniture was\nscattered about in every direction, with dismantled shelves and\nopen drawers, as if the lady had hurriedly ransacked them before\nher flight. Holmes rushed at the bell-pull, tore back a small\nsliding shutter, and, plunging in his hand, pulled out a\nphotograph and a letter. The photograph was of Irene Adler\nherself in evening dress, the letter was superscribed to\n\"Sherlock Holmes, Esq. To be left till called for.\" My friend\ntore it open and we all three read it together. It was dated at\nmidnight of the preceding night and ran in this way:\n\n\"MY DEAR MR. SHERLOCK HOLMES,--You really did it very well. You\ntook me in completely. Until after the alarm of fire, I had not a\nsuspicion. But then, when I found how I had betrayed myself, I\nbegan to think. I had been warned against you months ago. I had\nbeen told that if the King employed an agent it would certainly\nbe you. And your address had been given me. Yet, with all this,\nyou made me reveal what you wanted to know. Even after I became\nsuspicious, I found it hard to think evil of such a dear, kind\nold clergyman. But, you know, I have been trained as an actress\nmyself. Male costume is nothing new to me. I often take advantage\nof the freedom which it gives. I sent John, the coachman, to\nwatch you, ran up stairs, got into my walking-clothes, as I call\nthem, and came down just as you departed.\n\n\"Well, I followed you to your door, and so made sure that I was\nreally an object of interest to the celebrated Mr. Sherlock\nHolmes. Then I, rather imprudently, wished you good-night, and\nstarted for the Temple to see my husband.\n\n\"We both thought the best resource was flight, when pursued by\nso formidable an antagonist; so you will find the nest empty when\nyou call to-morrow. As to the photograph, your client may rest in\npeace. I love and am loved by a better man than he. The King may\ndo what he will without hindrance from one whom he has cruelly\nwronged. I keep it only to safeguard myself, and to preserve a\nweapon which will always secure me from any steps which he might\ntake in the future. I leave a photograph which he might care to\npossess; and I remain, dear Mr. Sherlock Holmes,\n\n                                      \"Very truly yours,\n                                   \"IRENE NORTON, née ADLER.\"\n\n\"What a woman--oh, what a woman!\" cried the King of Bohemia, when\nwe had all three read this epistle. \"Did I not tell you how quick\nand resolute she was? Would she not have made an admirable queen?\nIs it not a pity that she was not on my level?\"\n\n\"From what I have seen of the lady she seems indeed to be on a\nvery different level to your Majesty,\" said Holmes coldly. \"I am\nsorry that I have not been able to bring your Majesty's business\nto a more successful conclusion.\"\n\n\"On the contrary, my dear sir,\" cried the King; \"nothing could be\nmore successful. I know that her word is inviolate. The\nphotograph is now as safe as if it were in the fire.\"\n\n\"I am glad to hear your Majesty say so.\"\n\n\"I am immensely indebted to you. Pray tell me in what way I can\nreward you. This ring--\" He slipped an emerald snake ring from\nhis finger and held it out upon the palm of his hand.\n\n\"Your Majesty has something which I should value even more\nhighly,\" said Holmes.\n\n\"You have but to name it.\"\n\n\"This photograph!\"\n\nThe King stared at him in amazement.\n\n\"Irene's photograph!\" he cried. \"Certainly, if you wish it.\"\n\n\"I thank your Majesty. Then there is no more to be done in the\nmatter. I have the honour to wish you a very good-morning.\" He\nbowed, and, turning away without observing the hand which the\nKing had stretched out to him, he set off in my company for his\nchambers.\n\nAnd that was how a great scandal threatened to affect the kingdom\nof Bohemia, and how the best plans of Mr. Sherlock Holmes were\nbeaten by a woman's wit. He used to make merry over the\ncleverness of women, but I have not heard him do it of late. And\nwhen he speaks of Irene Adler, or when he refers to her\nphotograph, it is always under the honourable title of the woman.\n\n\n\nADVENTURE II. THE RED-HEADED LEAGUE\n\nI had called upon my friend, Mr. Sherlock Holmes, one day in the\nautumn of last year and found him in deep conversation with a\nvery stout, florid-faced, elderly gentleman with fiery red hair.\nWith an apology for my intrusion, I was about to withdraw when\nHolmes pulled me abruptly into the room and closed the door\nbehind me.\n\n\"You could not possibly have come at a better time, my dear\nWatson,\" he said cordially.\n\n\"I was afraid that you were engaged.\"\n\n\"So I am. Very much so.\"\n\n\"Then I can wait in the next room.\"\n\n\"Not at all. This gentleman, Mr. Wilson, has been my partner and\nhelper in many of my most successful cases, and I have no\ndoubt that he will be of the utmost use to me in yours also.\"\n\nThe stout gentleman half rose from his chair and gave a bob of\ngreeting, with a quick little questioning glance from his small\nfat-encircled eyes.\n\n\"Try the settee,\" said Holmes, relapsing into his armchair and\nputting his fingertips together, as was his custom when in\njudicial moods. \"I know, my dear Watson, that you share my love\nof all that is bizarre and outside the conventions and humdrum\nroutine of everyday life. You have shown your relish for it by\nthe enthusiasm which has prompted you to chronicle, and, if you\nwill excuse my saying so, somewhat to embellish so many of my own\nlittle adventures.\"\n\n\"Your cases have indeed been of the greatest interest to me,\" I\nobserved.\n\n\"You will remember that I remarked the other day, just before we\nwent into the very simple problem presented by Miss Mary\nSutherland, that for strange effects and extraordinary\ncombinations we must go to life itself, which is always far more\ndaring than any effort of the imagination.\"\n\n\"A proposition which I took the liberty of doubting.\"\n\n\"You did, Doctor, but none the less you must come round to my\nview, for otherwise I shall keep on piling fact upon fact on you\nuntil your reason breaks down under them and acknowledges me to\nbe right. Now, Mr. Jabez Wilson here has been good enough to call\nupon me this morning, and to begin a narrative which promises to\nbe one of the most singular which I have listened to for some\ntime. You have heard me remark that the strangest and most unique\nthings are very often connected not with the larger but with the\nsmaller crimes, and occasionally, indeed, where there is room for\ndoubt whether any positive crime has been committed. As far as I\nhave heard it is impossible for me to say whether the present\ncase is an instance of crime or not, but the course of events is\ncertainly among the most singular that I have ever listened to.\nPerhaps, Mr. Wilson, you would have the great kindness to\nrecommence your narrative. I ask you not merely because my friend\nDr. Watson has not heard the opening part but also because the\npeculiar nature of the story makes me anxious to have every\npossible detail from your lips. As a rule, when I have heard some\nslight indication of the course of events, I am able to guide\nmyself by the thousands of other similar cases which occur to my\nmemory. In the present instance I am forced to admit that the\nfacts are, to the best of my belief, unique.\"\n\nThe portly client puffed out his chest with an appearance of some\nlittle pride and pulled a dirty and wrinkled newspaper from the\ninside pocket of his greatcoat. As he glanced down the\nadvertisement column, with his head thrust forward and the paper\nflattened out upon his knee, I took a good look at the man and\nendeavoured, after the fashion of my companion, to read the\nindications which might be presented by his dress or appearance.\n\nI did not gain very much, however, by my inspection. Our visitor\nbore every mark of being an average commonplace British\ntradesman, obese, pompous, and slow. He wore rather baggy grey\nshepherd's check trousers, a not over-clean black frock-coat,\nunbuttoned in the front, and a drab waistcoat with a heavy brassy\nAlbert chain, and a square pierced bit of metal dangling down as\nan ornament. A frayed top-hat and a faded brown overcoat with a\nwrinkled velvet collar lay upon a chair beside him. Altogether,\nlook as I would, there was nothing remarkable about the man save\nhis blazing red head, and the expression of extreme chagrin and\ndiscontent upon his features.\n\nSherlock Holmes' quick eye took in my occupation, and he shook\nhis head with a smile as he noticed my questioning glances.\n\"Beyond the obvious facts that he has at some time done manual\nlabour, that he takes snuff, that he is a Freemason, that he has\nbeen in China, and that he has done a considerable amount of\nwriting lately, I can deduce nothing else.\"\n\nMr. Jabez Wilson started up in his chair, with his forefinger\nupon the paper, but his eyes upon my companion.\n\n\"How, in the name of good-fortune, did you know all that, Mr.\nHolmes?\" he asked. \"How did you know, for example, that I did\nmanual labour. It's as true as gospel, for I began as a ship's\ncarpenter.\"\n\n\"Your hands, my dear sir. Your right hand is quite a size larger\nthan your left. You have worked with it, and the muscles are more\ndeveloped.\"\n\n\"Well, the snuff, then, and the Freemasonry?\"\n\n\"I won't insult your intelligence by telling you how I read that,\nespecially as, rather against the strict rules of your order, you\nuse an arc-and-compass breastpin.\"\n\n\"Ah, of course, I forgot that. But the writing?\"\n\n\"What else can be indicated by that right cuff so very shiny for\nfive inches, and the left one with the smooth patch near the\nelbow where you rest it upon the desk?\"\n\n\"Well, but China?\"\n\n\"The fish that you have tattooed immediately above your right\nwrist could only have been done in China. I have made a small\nstudy of tattoo marks and have even contributed to the literature\nof the subject. That trick of staining the fishes' scales of a\ndelicate pink is quite peculiar to China. When, in addition, I\nsee a Chinese coin hanging from your watch-chain, the matter\nbecomes even more simple.\"\n\nMr. Jabez Wilson laughed heavily. \"Well, I never!\" said he. \"I\nthought at first that you had done something clever, but I see\nthat there was nothing in it, after all.\"\n\n\"I begin to think, Watson,\" said Holmes, \"that I make a mistake\nin explaining. 'Omne ignotum pro magnifico,' you know, and my\npoor little reputation, such as it is, will suffer shipwreck if I\nam so candid. Can you not find the advertisement, Mr. Wilson?\"\n\n\"Yes, I have got it now,\" he answered with his thick red finger\nplanted halfway down the column. \"Here it is. This is what began\nit all. You just read it for yourself, sir.\"\n\nI took the paper from him and read as follows:\n\n\"TO THE RED-HEADED LEAGUE: On account of the bequest of the late\nEzekiah Hopkins, of Lebanon, Pennsylvania, U. S. A., there is now\nanother vacancy open which entitles a member of the League to a\nsalary of 4 pounds a week for purely nominal services. All\nred-headed men who are sound in body and mind and above the age\nof twenty-one years, are eligible. Apply in person on Monday, at\neleven o'clock, to Duncan Ross, at the offices of the League, 7\nPope's Court, Fleet Street.\"\n\n\"What on earth does this mean?\" I ejaculated after I had twice\nread over the extraordinary announcement.\n\nHolmes chuckled and wriggled in his chair, as was his habit when\nin high spirits. \"It is a little off the beaten track, isn't it?\"\nsaid he. \"And now, Mr. Wilson, off you go at scratch and tell us\nall about yourself, your household, and the effect which this\nadvertisement had upon your fortunes. You will first make a note,\nDoctor, of the paper and the date.\"\n\n\"It is The Morning Chronicle of April 27, 1890. Just two months\nago.\"\n\n\"Very good. Now, Mr. Wilson?\"\n\n\"Well, it is just as I have been telling you, Mr. Sherlock\nHolmes,\" said Jabez Wilson, mopping his forehead; \"I have a small\npawnbroker's business at Coburg Square, near the City. It's not a\nvery large affair, and of late years it has not done more than\njust give me a living. I used to be able to keep two assistants,\nbut now I only keep one; and I would have a job to pay him but\nthat he is willing to come for half wages so as to learn the\nbusiness.\"\n\n\"What is the name of this obliging youth?\" asked Sherlock Holmes.\n\n\"His name is Vincent Spaulding, and he's not such a youth,\neither. It's hard to say his age. I should not wish a smarter\nassistant, Mr. Holmes; and I know very well that he could better\nhimself and earn twice what I am able to give him. But, after\nall, if he is satisfied, why should I put ideas in his head?\"\n\n\"Why, indeed? You seem most fortunate in having an employé who\ncomes under the full market price. It is not a common experience\namong employers in this age. I don't know that your assistant is\nnot as remarkable as your advertisement.\"\n\n\"Oh, he has his faults, too,\" said Mr. Wilson. \"Never was such a\nfellow for photography. Snapping away with a camera when he ought\nto be improving his mind, and then diving down into the cellar\nlike a rabbit into its hole to develop his pictures. That is his\nmain fault, but on the whole he's a good worker. There's no vice\nin him.\"\n\n\"He is still with you, I presume?\"\n\n\"Yes, sir. He and a girl of fourteen, who does a bit of simple\ncooking and keeps the place clean--that's all I have in the\nhouse, for I am a widower and never had any family. We live very\nquietly, sir, the three of us; and we keep a roof over our heads\nand pay our debts, if we do nothing more.\n\n\"The first thing that put us out was that advertisement.\nSpaulding, he came down into the office just this day eight\nweeks, with this very paper in his hand, and he says:\n\n\"'I wish to the Lord, Mr. Wilson, that I was a red-headed man.'\n\n\"'Why that?' I asks.\n\n\"'Why,' says he, 'here's another vacancy on the League of the\nRed-headed Men. It's worth quite a little fortune to any man who\ngets it, and I understand that there are more vacancies than\nthere are men, so that the trustees are at their wits' end what\nto do with the money. If my hair would only change colour, here's\na nice little crib all ready for me to step into.'\n\n\"'Why, what is it, then?' I asked. You see, Mr. Holmes, I am a\nvery stay-at-home man, and as my business came to me instead of\nmy having to go to it, I was often weeks on end without putting\nmy foot over the door-mat. In that way I didn't know much of what\nwas going on outside, and I was always glad of a bit of news.\n\n\"'Have you never heard of the League of the Red-headed Men?' he\nasked with his eyes open.\n\n\"'Never.'\n\n\"'Why, I wonder at that, for you are eligible yourself for one\nof the vacancies.'\n\n\"'And what are they worth?' I asked.\n\n\"'Oh, merely a couple of hundred a year, but the work is slight,\nand it need not interfere very much with one's other\noccupations.'\n\n\"Well, you can easily think that that made me prick up my ears,\nfor the business has not been over-good for some years, and an\nextra couple of hundred would have been very handy.\n\n\"'Tell me all about it,' said I.\n\n\"'Well,' said he, showing me the advertisement, 'you can see for\nyourself that the League has a vacancy, and there is the address\nwhere you should apply for particulars. As far as I can make out,\nthe League was founded by an American millionaire, Ezekiah\nHopkins, who was very peculiar in his ways. He was himself\nred-headed, and he had a great sympathy for all red-headed men;\nso when he died it was found that he had left his enormous\nfortune in the hands of trustees, with instructions to apply the\ninterest to the providing of easy berths to men whose hair is of\nthat colour. From all I hear it is splendid pay and very little to\ndo.'\n\n\"'But,' said I, 'there would be millions of red-headed men who\nwould apply.'\n\n\"'Not so many as you might think,' he answered. 'You see it is\nreally confined to Londoners, and to grown men. This American had\nstarted from London when he was young, and he wanted to do the\nold town a good turn. Then, again, I have heard it is no use your\napplying if your hair is light red, or dark red, or anything but\nreal bright, blazing, fiery red. Now, if you cared to apply, Mr.\nWilson, you would just walk in; but perhaps it would hardly be\nworth your while to put yourself out of the way for the sake of a\nfew hundred pounds.'\n\n\"Now, it is a fact, gentlemen, as you may see for yourselves,\nthat my hair is of a very full and rich tint, so that it seemed\nto me that if there was to be any competition in the matter I\nstood as good a chance as any man that I had ever met. Vincent\nSpaulding seemed to know so much about it that I thought he might\nprove useful, so I just ordered him to put up the shutters for\nthe day and to come right away with me. He was very willing to\nhave a holiday, so we shut the business up and started off for\nthe address that was given us in the advertisement.\n\n\"I never hope to see such a sight as that again, Mr. Holmes. From\nnorth, south, east, and west every man who had a shade of red in\nhis hair had tramped into the city to answer the advertisement.\nFleet Street was choked with red-headed folk, and Pope's Court\nlooked like a coster's orange barrow. I should not have thought\nthere were so many in the whole country as were brought together\nby that single advertisement. Every shade of colour they\nwere--straw, lemon, orange, brick, Irish-setter, liver, clay;\nbut, as Spaulding said, there were not many who had the real\nvivid flame-coloured tint. When I saw how many were waiting, I\nwould have given it up in despair; but Spaulding would not hear\nof it. How he did it I could not imagine, but he pushed and\npulled and butted until he got me through the crowd, and right up\nto the steps which led to the office. There was a double stream\nupon the stair, some going up in hope, and some coming back\ndejected; but we wedged in as well as we could and soon found\nourselves in the office.\"\n\n\"Your experience has been a most entertaining one,\" remarked\nHolmes as his client paused and refreshed his memory with a huge\npinch of snuff. \"Pray continue your very interesting statement.\"\n\n\"There was nothing in the office but a couple of wooden chairs\nand a deal table, behind which sat a small man with a head that\nwas even redder than mine. He said a few words to each candidate\nas he came up, and then he always managed to find some fault in\nthem which would disqualify them. Getting a vacancy did not seem\nto be such a very easy matter, after all. However, when our turn\ncame the little man was much more favourable to me than to any of\nthe others, and he closed the door as we entered, so that he\nmight have a private word with us.\n\n\"'This is Mr. Jabez Wilson,' said my assistant, 'and he is\nwilling to fill a vacancy in the League.'\n\n\"'And he is admirably suited for it,' the other answered. 'He has\nevery requirement. I cannot recall when I have seen anything so\nfine.' He took a step backward, cocked his head on one side, and\ngazed at my hair until I felt quite bashful. Then suddenly he\nplunged forward, wrung my hand, and congratulated me warmly on my\nsuccess.\n\n\"'It would be injustice to hesitate,' said he. 'You will,\nhowever, I am sure, excuse me for taking an obvious precaution.'\nWith that he seized my hair in both his hands, and tugged until I\nyelled with the pain. 'There is water in your eyes,' said he as\nhe released me. 'I perceive that all is as it should be. But we\nhave to be careful, for we have twice been deceived by wigs and\nonce by paint. I could tell you tales of cobbler's wax which\nwould disgust you with human nature.' He stepped over to the\nwindow and shouted through it at the top of his voice that the\nvacancy was filled. A groan of disappointment came up from below,\nand the folk all trooped away in different directions until there\nwas not a red-head to be seen except my own and that of the\nmanager.\n\n\"'My name,' said he, 'is Mr. Duncan Ross, and I am myself one of\nthe pensioners upon the fund left by our noble benefactor. Are\nyou a married man, Mr. Wilson? Have you a family?'\n\n\"I answered that I had not.\n\n\"His face fell immediately.\n\n\"'Dear me!' he said gravely, 'that is very serious indeed! I am\nsorry to hear you say that. The fund was, of course, for the\npropagation and spread of the red-heads as well as for their\nmaintenance. It is exceedingly unfortunate that you should be a\nbachelor.'\n\n\"My face lengthened at this, Mr. Holmes, for I thought that I was\nnot to have the vacancy after all; but after thinking it over for\na few minutes he said that it would be all right.\n\n\"'In the case of another,' said he, 'the objection might be\nfatal, but we must stretch a point in favour of a man with such a\nhead of hair as yours. When shall you be able to enter upon your\nnew duties?'\n\n\"'Well, it is a little awkward, for I have a business already,'\nsaid I.\n\n\"'Oh, never mind about that, Mr. Wilson!' said Vincent Spaulding.\n'I should be able to look after that for you.'\n\n\"'What would be the hours?' I asked.\n\n\"'Ten to two.'\n\n\"Now a pawnbroker's business is mostly done of an evening, Mr.\nHolmes, especially Thursday and Friday evening, which is just\nbefore pay-day; so it would suit me very well to earn a little in\nthe mornings. Besides, I knew that my assistant was a good man,\nand that he would see to anything that turned up.\n\n\"'That would suit me very well,' said I. 'And the pay?'\n\n\"'Is 4 pounds a week.'\n\n\"'And the work?'\n\n\"'Is purely nominal.'\n\n\"'What do you call purely nominal?'\n\n\"'Well, you have to be in the office, or at least in the\nbuilding, the whole time. If you leave, you forfeit your whole\nposition forever. The will is very clear upon that point. You\ndon't comply with the conditions if you budge from the office\nduring that time.'\n\n\"'It's only four hours a day, and I should not think of leaving,'\nsaid I.\n\n\"'No excuse will avail,' said Mr. Duncan Ross; 'neither sickness\nnor business nor anything else. There you must stay, or you lose\nyour billet.'\n\n\"'And the work?'\n\n\"'Is to copy out the \"Encyclopaedia Britannica.\" There is the first\nvolume of it in that press. You must find your own ink, pens, and\nblotting-paper, but we provide this table and chair. Will you be\nready to-morrow?'\n\n\"'Certainly,' I answered.\n\n\"'Then, good-bye, Mr. Jabez Wilson, and let me congratulate you\nonce more on the important position which you have been fortunate\nenough to gain.' He bowed me out of the room and I went home with\nmy assistant, hardly knowing what to say or do, I was so pleased\nat my own good fortune.\n\n\"Well, I thought over the matter all day, and by evening I was in\nlow spirits again; for I had quite persuaded myself that the\nwhole affair must be some great hoax or fraud, though what its\nobject might be I could not imagine. It seemed altogether past\nbelief that anyone could make such a will, or that they would pay\nsuch a sum for doing anything so simple as copying out the\n'Encyclopaedia Britannica.' Vincent Spaulding did what he could to\ncheer me up, but by bedtime I had reasoned myself out of the\nwhole thing. However, in the morning I determined to have a look\nat it anyhow, so I bought a penny bottle of ink, and with a\nquill-pen, and seven sheets of foolscap paper, I started off for\nPope's Court.\n\n\"Well, to my surprise and delight, everything was as right as\npossible. The table was set out ready for me, and Mr. Duncan Ross\nwas there to see that I got fairly to work. He started me off\nupon the letter A, and then he left me; but he would drop in from\ntime to time to see that all was right with me. At two o'clock he\nbade me good-day, complimented me upon the amount that I had\nwritten, and locked the door of the office after me.\n\n\"This went on day after day, Mr. Holmes, and on Saturday the\nmanager came in and planked down four golden sovereigns for my\nweek's work. It was the same next week, and the same the week\nafter. Every morning I was there at ten, and every afternoon I\nleft at two. By degrees Mr. Duncan Ross took to coming in only\nonce of a morning, and then, after a time, he did not come in at\nall. Still, of course, I never dared to leave the room for an\ninstant, for I was not sure when he might come, and the billet\nwas such a good one, and suited me so well, that I would not risk\nthe loss of it.\n\n\"Eight weeks passed away like this, and I had written about\nAbbots and Archery and Armour and Architecture and Attica, and\nhoped with diligence that I might get on to the B's before very\nlong. It cost me something in foolscap, and I had pretty nearly\nfilled a shelf with my writings. And then suddenly the whole\nbusiness came to an end.\"\n\n\"To an end?\"\n\n\"Yes, sir. And no later than this morning. I went to my work as\nusual at ten o'clock, but the door was shut and locked, with a\nlittle square of cardboard hammered on to the middle of the\npanel with a tack. Here it is, and you can read for yourself.\"\n\nHe held up a piece of white cardboard about the size of a sheet\nof note-paper. It read in this fashion:\n\n                  THE RED-HEADED LEAGUE\n\n                           IS\n\n                        DISSOLVED.\n\n                     October 9, 1890.\n\nSherlock Holmes and I surveyed this curt announcement and the\nrueful face behind it, until the comical side of the affair so\ncompletely overtopped every other consideration that we both\nburst out into a roar of laughter.\n\n\"I cannot see that there is anything very funny,\" cried our\nclient, flushing up to the roots of his flaming head. \"If you can\ndo nothing better than laugh at me, I can go elsewhere.\"\n\n\"No, no,\" cried Holmes, shoving him back into the chair from\nwhich he had half risen. \"I really wouldn't miss your case for\nthe world. It is most refreshingly unusual. But there is, if you\nwill excuse my saying so, something just a little funny about it.\nPray what steps did you take when you found the card upon the\ndoor?\"\n\n\"I was staggered, sir. I did not know what to do. Then I called\nat the offices round, but none of them seemed to know anything\nabout it. Finally, I went to the landlord, who is an accountant\nliving on the ground-floor, and I asked him if he could tell me\nwhat had become of the Red-headed League. He said that he had\nnever heard of any such body. Then I asked him who Mr. Duncan\nRoss was. He answered that the name was new to him.\n\n\"'Well,' said I, 'the gentleman at No. 4.'\n\n\"'What, the red-headed man?'\n\n\"'Yes.'\n\n\"'Oh,' said he, 'his name was William Morris. He was a solicitor\nand was using my room as a temporary convenience until his new\npremises were ready. He moved out yesterday.'\n\n\"'Where could I find him?'\n\n\"'Oh, at his new offices. He did tell me the address. Yes, 17\nKing Edward Street, near St. Paul's.'\n\n\"I started off, Mr. Holmes, but when I got to that address it was\na manufactory of artificial knee-caps, and no one in it had ever\nheard of either Mr. William Morris or Mr. Duncan Ross.\"\n\n\"And what did you do then?\" asked Holmes.\n\n\"I went home to Saxe-Coburg Square, and I took the advice of my\nassistant. But he could not help me in any way. He could only say\nthat if I waited I should hear by post. But that was not quite\ngood enough, Mr. Holmes. I did not wish to lose such a place\nwithout a struggle, so, as I had heard that you were good enough\nto give advice to poor folk who were in need of it, I came right\naway to you.\"\n\n\"And you did very wisely,\" said Holmes. \"Your case is an\nexceedingly remarkable one, and I shall be happy to look into it.\nFrom what you have told me I think that it is possible that\ngraver issues hang from it than might at first sight appear.\"\n\n\"Grave enough!\" said Mr. Jabez Wilson. \"Why, I have lost four\npound a week.\"\n\n\"As far as you are personally concerned,\" remarked Holmes, \"I do\nnot see that you have any grievance against this extraordinary\nleague. On the contrary, you are, as I understand, richer by some\n30 pounds, to say nothing of the minute knowledge which you have\ngained on every subject which comes under the letter A. You have\nlost nothing by them.\"\n\n\"No, sir. But I want to find out about them, and who they are,\nand what their object was in playing this prank--if it was a\nprank--upon me. It was a pretty expensive joke for them, for it\ncost them two and thirty pounds.\"\n\n\"We shall endeavour to clear up these points for you. And, first,\none or two questions, Mr. Wilson. This assistant of yours who\nfirst called your attention to the advertisement--how long had he\nbeen with you?\"\n\n\"About a month then.\"\n\n\"How did he come?\"\n\n\"In answer to an advertisement.\"\n\n\"Was he the only applicant?\"\n\n\"No, I had a dozen.\"\n\n\"Why did you pick him?\"\n\n\"Because he was handy and would come cheap.\"\n\n\"At half-wages, in fact.\"\n\n\"Yes.\"\n\n\"What is he like, this Vincent Spaulding?\"\n\n\"Small, stout-built, very quick in his ways, no hair on his face,\nthough he's not short of thirty. Has a white splash of acid upon\nhis forehead.\"\n\nHolmes sat up in his chair in considerable excitement. \"I thought\nas much,\" said he. \"Have you ever observed that his ears are\npierced for earrings?\"\n\n\"Yes, sir. He told me that a gipsy had done it for him when he\nwas a lad.\"\n\n\"Hum!\" said Holmes, sinking back in deep thought. \"He is still\nwith you?\"\n\n\"Oh, yes, sir; I have only just left him.\"\n\n\"And has your business been attended to in your absence?\"\n\n\"Nothing to complain of, sir. There's never very much to do of a\nmorning.\"\n\n\"That will do, Mr. Wilson. I shall be happy to give you an\nopinion upon the subject in the course of a day or two. To-day is\nSaturday, and I hope that by Monday we may come to a conclusion.\"\n\n\"Well, Watson,\" said Holmes when our visitor had left us, \"what\ndo you make of it all?\"\n\n\"I make nothing of it,\" I answered frankly. \"It is a most\nmysterious business.\"\n\n\"As a rule,\" said Holmes, \"the more bizarre a thing is the less\nmysterious it proves to be. It is your commonplace, featureless\ncrimes which are really puzzling, just as a commonplace face is\nthe most difficult to identify. But I must be prompt over this\nmatter.\"\n\n\"What are you going to do, then?\" I asked.\n\n\"To smoke,\" he answered. \"It is quite a three pipe problem, and I\nbeg that you won't speak to me for fifty minutes.\" He curled\nhimself up in his chair, with his thin knees drawn up to his\nhawk-like nose, and there he sat with his eyes closed and his\nblack clay pipe thrusting out like the bill of some strange bird.\nI had come to the conclusion that he had dropped asleep, and\nindeed was nodding myself, when he suddenly sprang out of his\nchair with the gesture of a man who has made up his mind and put\nhis pipe down upon the mantelpiece.\n\n\"Sarasate plays at the St. James's Hall this afternoon,\" he\nremarked. \"What do you think, Watson? Could your patients spare\nyou for a few hours?\"\n\n\"I have nothing to do to-day. My practice is never very\nabsorbing.\"\n\n\"Then put on your hat and come. I am going through the City\nfirst, and we can have some lunch on the way. I observe that\nthere is a good deal of German music on the programme, which is\nrather more to my taste than Italian or French. It is\nintrospective, and I want to introspect. Come along!\"\n\nWe travelled by the Underground as far as Aldersgate; and a short\nwalk took us to Saxe-Coburg Square, the scene of the singular\nstory which we had listened to in the morning. It was a poky,\nlittle, shabby-genteel place, where four lines of dingy\ntwo-storied brick houses looked out into a small railed-in\nenclosure, where a lawn of weedy grass and a few clumps of faded\nlaurel-bushes made a hard fight against a smoke-laden and\nuncongenial atmosphere. Three gilt balls and a brown board with\n\"JABEZ WILSON\" in white letters, upon a corner house, announced\nthe place where our red-headed client carried on his business.\nSherlock Holmes stopped in front of it with his head on one side\nand looked it all over, with his eyes shining brightly between\npuckered lids. Then he walked slowly up the street, and then down\nagain to the corner, still looking keenly at the houses. Finally\nhe returned to the pawnbroker's, and, having thumped vigorously\nupon the pavement with his stick two or three times, he went up\nto the door and knocked. It was instantly opened by a\nbright-looking, clean-shaven young fellow, who asked him to step\nin.\n\n\"Thank you,\" said Holmes, \"I only wished to ask you how you would\ngo from here to the Strand.\"\n\n\"Third right, fourth left,\" answered the assistant promptly,\nclosing the door.\n\n\"Smart fellow, that,\" observed Holmes as we walked away. \"He is,\nin my judgment, the fourth smartest man in London, and for daring\nI am not sure that he has not a claim to be third. I have known\nsomething of him before.\"\n\n\"Evidently,\" said I, \"Mr. Wilson's assistant counts for a good\ndeal in this mystery of the Red-headed League. I am sure that you\ninquired your way merely in order that you might see him.\"\n\n\"Not him.\"\n\n\"What then?\"\n\n\"The knees of his trousers.\"\n\n\"And what did you see?\"\n\n\"What I expected to see.\"\n\n\"Why did you beat the pavement?\"\n\n\"My dear doctor, this is a time for observation, not for talk. We\nare spies in an enemy's country. We know something of Saxe-Coburg\nSquare. Let us now explore the parts which lie behind it.\"\n\nThe road in which we found ourselves as we turned round the\ncorner from the retired Saxe-Coburg Square presented as great a\ncontrast to it as the front of a picture does to the back. It was\none of the main arteries which conveyed the traffic of the City\nto the north and west. The roadway was blocked with the immense\nstream of commerce flowing in a double tide inward and outward,\nwhile the footpaths were black with the hurrying swarm of\npedestrians. It was difficult to realise as we looked at the line\nof fine shops and stately business premises that they really\nabutted on the other side upon the faded and stagnant square\nwhich we had just quitted.\n\n\"Let me see,\" said Holmes, standing at the corner and glancing\nalong the line, \"I should like just to remember the order of the\nhouses here. It is a hobby of mine to have an exact knowledge of\nLondon. There is Mortimer's, the tobacconist, the little\nnewspaper shop, the Coburg branch of the City and Suburban Bank,\nthe Vegetarian Restaurant, and McFarlane's carriage-building\ndepot. That carries us right on to the other block. And now,\nDoctor, we've done our work, so it's time we had some play. A\nsandwich and a cup of coffee, and then off to violin-land, where\nall is sweetness and delicacy and harmony, and there are no\nred-headed clients to vex us with their conundrums.\"\n\nMy friend was an enthusiastic musician, being himself not only a\nvery capable performer but a composer of no ordinary merit. All\nthe afternoon he sat in the stalls wrapped in the most perfect\nhappiness, gently waving his long, thin fingers in time to the\nmusic, while his gently smiling face and his languid, dreamy eyes\nwere as unlike those of Holmes the sleuth-hound, Holmes the\nrelentless, keen-witted, ready-handed criminal agent, as it was\npossible to conceive. In his singular character the dual nature\nalternately asserted itself, and his extreme exactness and\nastuteness represented, as I have often thought, the reaction\nagainst the poetic and contemplative mood which occasionally\npredominated in him. The swing of his nature took him from\nextreme languor to devouring energy; and, as I knew well, he was\nnever so truly formidable as when, for days on end, he had been\nlounging in his armchair amid his improvisations and his\nblack-letter editions. Then it was that the lust of the chase\nwould suddenly come upon him, and that his brilliant reasoning\npower would rise to the level of intuition, until those who were\nunacquainted with his methods would look askance at him as on a\nman whose knowledge was not that of other mortals. When I saw him\nthat afternoon so enwrapped in the music at St. James's Hall I\nfelt that an evil time might be coming upon those whom he had set\nhimself to hunt down.\n\n\"You want to go home, no doubt, Doctor,\" he remarked as we\nemerged.\n\n\"Yes, it would be as well.\"\n\n\"And I have some business to do which will take some hours. This\nbusiness at Coburg Square is serious.\"\n\n\"Why serious?\"\n\n\"A considerable crime is in contemplation. I have every reason to\nbelieve that we shall be in time to stop it. But to-day being\nSaturday rather complicates matters. I shall want your help\nto-night.\"\n\n\"At what time?\"\n\n\"Ten will be early enough.\"\n\n\"I shall be at Baker Street at ten.\"\n\n\"Very well. And, I say, Doctor, there may be some little danger,\nso kindly put your army revolver in your pocket.\" He waved his\nhand, turned on his heel, and disappeared in an instant among the\ncrowd.\n\nI trust that I am not more dense than my neighbours, but I was\nalways oppressed with a sense of my own stupidity in my dealings\nwith Sherlock Holmes. Here I had heard what he had heard, I had\nseen what he had seen, and yet from his words it was evident that\nhe saw clearly not only what had happened but what was about to\nhappen, while to me the whole business was still confused and\ngrotesque. As I drove home to my house in Kensington I thought\nover it all, from the extraordinary story of the red-headed\ncopier of the \"Encyclopaedia\" down to the visit to Saxe-Coburg\nSquare, and the ominous words with which he had parted from me.\nWhat was this nocturnal expedition, and why should I go armed?\nWhere were we going, and what were we to do? I had the hint from\nHolmes that this smooth-faced pawnbroker's assistant was a\nformidable man--a man who might play a deep game. I tried to\npuzzle it out, but gave it up in despair and set the matter aside\nuntil night should bring an explanation.\n\nIt was a quarter-past nine when I started from home and made my\nway across the Park, and so through Oxford Street to Baker\nStreet. Two hansoms were standing at the door, and as I entered\nthe passage I heard the sound of voices from above. On entering\nhis room I found Holmes in animated conversation with two men,\none of whom I recognised as Peter Jones, the official police\nagent, while the other was a long, thin, sad-faced man, with a\nvery shiny hat and oppressively respectable frock-coat.\n\n\"Ha! Our party is complete,\" said Holmes, buttoning up his\npea-jacket and taking his heavy hunting crop from the rack.\n\"Watson, I think you know Mr. Jones, of Scotland Yard? Let me\nintroduce you to Mr. Merryweather, who is to be our companion in\nto-night's adventure.\"\n\n\"We're hunting in couples again, Doctor, you see,\" said Jones in\nhis consequential way. \"Our friend here is a wonderful man for\nstarting a chase. All he wants is an old dog to help him to do\nthe running down.\"\n\n\"I hope a wild goose may not prove to be the end of our chase,\"\nobserved Mr. Merryweather gloomily.\n\n\"You may place considerable confidence in Mr. Holmes, sir,\" said\nthe police agent loftily. \"He has his own little methods, which\nare, if he won't mind my saying so, just a little too theoretical\nand fantastic, but he has the makings of a detective in him. It\nis not too much to say that once or twice, as in that business of\nthe Sholto murder and the Agra treasure, he has been more nearly\ncorrect than the official force.\"\n\n\"Oh, if you say so, Mr. Jones, it is all right,\" said the\nstranger with deference. \"Still, I confess that I miss my rubber.\nIt is the first Saturday night for seven-and-twenty years that I\nhave not had my rubber.\"\n\n\"I think you will find,\" said Sherlock Holmes, \"that you will\nplay for a higher stake to-night than you have ever done yet, and\nthat the play will be more exciting. For you, Mr. Merryweather,\nthe stake will be some 30,000 pounds; and for you, Jones, it will\nbe the man upon whom you wish to lay your hands.\"\n\n\"John Clay, the murderer, thief, smasher, and forger. He's a\nyoung man, Mr. Merryweather, but he is at the head of his\nprofession, and I would rather have my bracelets on him than on\nany criminal in London. He's a remarkable man, is young John\nClay. His grandfather was a royal duke, and he himself has been\nto Eton and Oxford. His brain is as cunning as his fingers, and\nthough we meet signs of him at every turn, we never know where to\nfind the man himself. He'll crack a crib in Scotland one week,\nand be raising money to build an orphanage in Cornwall the next.\nI've been on his track for years and have never set eyes on him\nyet.\"\n\n\"I hope that I may have the pleasure of introducing you to-night.\nI've had one or two little turns also with Mr. John Clay, and I\nagree with you that he is at the head of his profession. It is\npast ten, however, and quite time that we started. If you two\nwill take the first hansom, Watson and I will follow in the\nsecond.\"\n\nSherlock Holmes was not very communicative during the long drive\nand lay back in the cab humming the tunes which he had heard in\nthe afternoon. We rattled through an endless labyrinth of gas-lit\nstreets until we emerged into Farrington Street.\n\n\"We are close there now,\" my friend remarked. \"This fellow\nMerryweather is a bank director, and personally interested in the\nmatter. I thought it as well to have Jones with us also. He is\nnot a bad fellow, though an absolute imbecile in his profession.\nHe has one positive virtue. He is as brave as a bulldog and as\ntenacious as a lobster if he gets his claws upon anyone. Here we\nare, and they are waiting for us.\"\n\nWe had reached the same crowded thoroughfare in which we had\nfound ourselves in the morning. Our cabs were dismissed, and,\nfollowing the guidance of Mr. Merryweather, we passed down a\nnarrow passage and through a side door, which he opened for us.\nWithin there was a small corridor, which ended in a very massive\niron gate. This also was opened, and led down a flight of winding\nstone steps, which terminated at another formidable gate. Mr.\nMerryweather stopped to light a lantern, and then conducted us\ndown a dark, earth-smelling passage, and so, after opening a\nthird door, into a huge vault or cellar, which was piled all\nround with crates and massive boxes.\n\n\"You are not very vulnerable from above,\" Holmes remarked as he\nheld up the lantern and gazed about him.\n\n\"Nor from below,\" said Mr. Merryweather, striking his stick upon\nthe flags which lined the floor. \"Why, dear me, it sounds quite\nhollow!\" he remarked, looking up in surprise.\n\n\"I must really ask you to be a little more quiet!\" said Holmes\nseverely. \"You have already imperilled the whole success of our\nexpedition. Might I beg that you would have the goodness to sit\ndown upon one of those boxes, and not to interfere?\"\n\nThe solemn Mr. Merryweather perched himself upon a crate, with a\nvery injured expression upon his face, while Holmes fell upon his\nknees upon the floor and, with the lantern and a magnifying lens,\nbegan to examine minutely the cracks between the stones. A few\nseconds sufficed to satisfy him, for he sprang to his feet again\nand put his glass in his pocket.\n\n\"We have at least an hour before us,\" he remarked, \"for they can\nhardly take any steps until the good pawnbroker is safely in bed.\nThen they will not lose a minute, for the sooner they do their\nwork the longer time they will have for their escape. We are at\npresent, Doctor--as no doubt you have divined--in the cellar of\nthe City branch of one of the principal London banks. Mr.\nMerryweather is the chairman of directors, and he will explain to\nyou that there are reasons why the more daring criminals of\nLondon should take a considerable interest in this cellar at\npresent.\"\n\n\"It is our French gold,\" whispered the director. \"We have had\nseveral warnings that an attempt might be made upon it.\"\n\n\"Your French gold?\"\n\n\"Yes. We had occasion some months ago to strengthen our resources\nand borrowed for that purpose 30,000 napoleons from the Bank of\nFrance. It has become known that we have never had occasion to\nunpack the money, and that it is still lying in our cellar. The\ncrate upon which I sit contains 2,000 napoleons packed between\nlayers of lead foil. Our reserve of bullion is much larger at\npresent than is usually kept in a single branch office, and the\ndirectors have had misgivings upon the subject.\"\n\n\"Which were very well justified,\" observed Holmes. \"And now it is\ntime that we arranged our little plans. I expect that within an\nhour matters will come to a head. In the meantime Mr.\nMerryweather, we must put the screen over that dark lantern.\"\n\n\"And sit in the dark?\"\n\n\"I am afraid so. I had brought a pack of cards in my pocket, and\nI thought that, as we were a partie carrée, you might have your\nrubber after all. But I see that the enemy's preparations have\ngone so far that we cannot risk the presence of a light. And,\nfirst of all, we must choose our positions. These are daring men,\nand though we shall take them at a disadvantage, they may do us\nsome harm unless we are careful. I shall stand behind this crate,\nand do you conceal yourselves behind those. Then, when I flash a\nlight upon them, close in swiftly. If they fire, Watson, have no\ncompunction about shooting them down.\"\n\nI placed my revolver, cocked, upon the top of the wooden case\nbehind which I crouched. Holmes shot the slide across the front\nof his lantern and left us in pitch darkness--such an absolute\ndarkness as I have never before experienced. The smell of hot\nmetal remained to assure us that the light was still there, ready\nto flash out at a moment's notice. To me, with my nerves worked\nup to a pitch of expectancy, there was something depressing and\nsubduing in the sudden gloom, and in the cold dank air of the\nvault.\n\n\"They have but one retreat,\" whispered Holmes. \"That is back\nthrough the house into Saxe-Coburg Square. I hope that you have\ndone what I asked you, Jones?\"\n\n\"I have an inspector and two officers waiting at the front door.\"\n\n\"Then we have stopped all the holes. And now we must be silent\nand wait.\"\n\nWhat a time it seemed! From comparing notes afterwards it was but\nan hour and a quarter, yet it appeared to me that the night must\nhave almost gone and the dawn be breaking above us. My limbs\nwere weary and stiff, for I feared to change my position; yet my\nnerves were worked up to the highest pitch of tension, and my\nhearing was so acute that I could not only hear the gentle\nbreathing of my companions, but I could distinguish the deeper,\nheavier in-breath of the bulky Jones from the thin, sighing note\nof the bank director. From my position I could look over the case\nin the direction of the floor. Suddenly my eyes caught the glint\nof a light.\n\nAt first it was but a lurid spark upon the stone pavement. Then\nit lengthened out until it became a yellow line, and then,\nwithout any warning or sound, a gash seemed to open and a hand\nappeared, a white, almost womanly hand, which felt about in the\ncentre of the little area of light. For a minute or more the\nhand, with its writhing fingers, protruded out of the floor. Then\nit was withdrawn as suddenly as it appeared, and all was dark\nagain save the single lurid spark which marked a chink between\nthe stones.\n\nIts disappearance, however, was but momentary. With a rending,\ntearing sound, one of the broad, white stones turned over upon\nits side and left a square, gaping hole, through which streamed\nthe light of a lantern. Over the edge there peeped a clean-cut,\nboyish face, which looked keenly about it, and then, with a hand\non either side of the aperture, drew itself shoulder-high and\nwaist-high, until one knee rested upon the edge. In another\ninstant he stood at the side of the hole and was hauling after\nhim a companion, lithe and small like himself, with a pale face\nand a shock of very red hair.\n\n\"It's all clear,\" he whispered. \"Have you the chisel and the\nbags? Great Scott! Jump, Archie, jump, and I'll swing for it!\"\n\nSherlock Holmes had sprung out and seized the intruder by the\ncollar. The other dived down the hole, and I heard the sound of\nrending cloth as Jones clutched at his skirts. The light flashed\nupon the barrel of a revolver, but Holmes' hunting crop came\ndown on the man's wrist, and the pistol clinked upon the stone\nfloor.\n\n\"It's no use, John Clay,\" said Holmes blandly. \"You have no\nchance at all.\"\n\n\"So I see,\" the other answered with the utmost coolness. \"I fancy\nthat my pal is all right, though I see you have got his\ncoat-tails.\"\n\n\"There are three men waiting for him at the door,\" said Holmes.\n\n\"Oh, indeed! You seem to have done the thing very completely. I\nmust compliment you.\"\n\n\"And I you,\" Holmes answered. \"Your red-headed idea was very new\nand effective.\"\n\n\"You'll see your pal again presently,\" said Jones. \"He's quicker\nat climbing down holes than I am. Just hold out while I fix the\nderbies.\"\n\n\"I beg that you will not touch me with your filthy hands,\"\nremarked our prisoner as the handcuffs clattered upon his wrists.\n\"You may not be aware that I have royal blood in my veins. Have\nthe goodness, also, when you address me always to say 'sir' and\n'please.'\"\n\n\"All right,\" said Jones with a stare and a snigger. \"Well, would\nyou please, sir, march upstairs, where we can get a cab to carry\nyour Highness to the police-station?\"\n\n\"That is better,\" said John Clay serenely. He made a sweeping bow\nto the three of us and walked quietly off in the custody of the\ndetective.\n\n\"Really, Mr. Holmes,\" said Mr. Merryweather as we followed them\nfrom the cellar, \"I do not know how the bank can thank you or\nrepay you. There is no doubt that you have detected and defeated\nin the most complete manner one of the most determined attempts\nat bank robbery that have ever come within my experience.\"\n\n\"I have had one or two little scores of my own to settle with Mr.\nJohn Clay,\" said Holmes. \"I have been at some small expense over\nthis matter, which I shall expect the bank to refund, but beyond\nthat I am amply repaid by having had an experience which is in\nmany ways unique, and by hearing the very remarkable narrative of\nthe Red-headed League.\"\n\n\n\"You see, Watson,\" he explained in the early hours of the morning\nas we sat over a glass of whisky and soda in Baker Street, \"it\nwas perfectly obvious from the first that the only possible\nobject of this rather fantastic business of the advertisement of\nthe League, and the copying of the 'Encyclopaedia,' must be to get\nthis not over-bright pawnbroker out of the way for a number of\nhours every day. It was a curious way of managing it, but,\nreally, it would be difficult to suggest a better. The method was\nno doubt suggested to Clay's ingenious mind by the colour of his\naccomplice's hair. The 4 pounds a week was a lure which must draw\nhim, and what was it to them, who were playing for thousands?\nThey put in the advertisement, one rogue has the temporary\noffice, the other rogue incites the man to apply for it, and\ntogether they manage to secure his absence every morning in the\nweek. From the time that I heard of the assistant having come for\nhalf wages, it was obvious to me that he had some strong motive\nfor securing the situation.\"\n\n\"But how could you guess what the motive was?\"\n\n\"Had there been women in the house, I should have suspected a\nmere vulgar intrigue. That, however, was out of the question. The\nman's business was a small one, and there was nothing in his\nhouse which could account for such elaborate preparations, and\nsuch an expenditure as they were at. It must, then, be something\nout of the house. What could it be? I thought of the assistant's\nfondness for photography, and his trick of vanishing into the\ncellar. The cellar! There was the end of this tangled clue. Then\nI made inquiries as to this mysterious assistant and found that I\nhad to deal with one of the coolest and most daring criminals in\nLondon. He was doing something in the cellar--something which\ntook many hours a day for months on end. What could it be, once\nmore? I could think of nothing save that he was running a tunnel\nto some other building.\n\n\"So far I had got when we went to visit the scene of action. I\nsurprised you by beating upon the pavement with my stick. I was\nascertaining whether the cellar stretched out in front or behind.\nIt was not in front. Then I rang the bell, and, as I hoped, the\nassistant answered it. We have had some skirmishes, but we had\nnever set eyes upon each other before. I hardly looked at his\nface. His knees were what I wished to see. You must yourself have\nremarked how worn, wrinkled, and stained they were. They spoke of\nthose hours of burrowing. The only remaining point was what they\nwere burrowing for. I walked round the corner, saw the City and\nSuburban Bank abutted on our friend's premises, and felt that I\nhad solved my problem. When you drove home after the concert I\ncalled upon Scotland Yard and upon the chairman of the bank\ndirectors, with the result that you have seen.\"\n\n\"And how could you tell that they would make their attempt\nto-night?\" I asked.\n\n\"Well, when they closed their League offices that was a sign that\nthey cared no longer about Mr. Jabez Wilson's presence--in other\nwords, that they had completed their tunnel. But it was essential\nthat they should use it soon, as it might be discovered, or the\nbullion might be removed. Saturday would suit them better than\nany other day, as it would give them two days for their escape.\nFor all these reasons I expected them to come to-night.\"\n\n\"You reasoned it out beautifully,\" I exclaimed in unfeigned\nadmiration. \"It is so long a chain, and yet every link rings\ntrue.\"\n\n\"It saved me from ennui,\" he answered, yawning. \"Alas! I already\nfeel it closing in upon me. My life is spent in one long effort\nto escape from the commonplaces of existence. These little\nproblems help me to do so.\"\n\n\"And you are a benefactor of the race,\" said I.\n\nHe shrugged his shoulders. \"Well, perhaps, after all, it is of\nsome little use,\" he remarked. \"'L'homme c'est rien--l'oeuvre\nc'est tout,' as Gustave Flaubert wrote to George Sand.\"\n\n\n\nADVENTURE III. A CASE OF IDENTITY\n\n\"My dear fellow,\" said Sherlock Holmes as we sat on either side\nof the fire in his lodgings at Baker Street, \"life is infinitely\nstranger than anything which the mind of man could invent. We\nwould not dare to conceive the things which are really mere\ncommonplaces of existence. If we could fly out of that window\nhand in hand, hover over this great city, gently remove the\nroofs, and peep in at the queer things which are going on, the\nstrange coincidences, the plannings, the cross-purposes, the\nwonderful chains of events, working through generations, and\nleading to the most outré results, it would make all fiction with\nits conventionalities and foreseen conclusions most stale and\nunprofitable.\"\n\n\"And yet I am not convinced of it,\" I answered. \"The cases which\ncome to light in the papers are, as a rule, bald enough, and\nvulgar enough. We have in our police reports realism pushed to\nits extreme limits, and yet the result is, it must be confessed,\nneither fascinating nor artistic.\"\n\n\"A certain selection and discretion must be used in producing a\nrealistic effect,\" remarked Holmes. \"This is wanting in the\npolice report, where more stress is laid, perhaps, upon the\nplatitudes of the magistrate than upon the details, which to an\nobserver contain the vital essence of the whole matter. Depend\nupon it, there is nothing so unnatural as the commonplace.\"\n\nI smiled and shook my head. \"I can quite understand your thinking\nso,\" I said. \"Of course, in your position of unofficial adviser\nand helper to everybody who is absolutely puzzled, throughout\nthree continents, you are brought in contact with all that is\nstrange and bizarre. But here\"--I picked up the morning paper\nfrom the ground--\"let us put it to a practical test. Here is the\nfirst heading upon which I come. 'A husband's cruelty to his\nwife.' There is half a column of print, but I know without\nreading it that it is all perfectly familiar to me. There is, of\ncourse, the other woman, the drink, the push, the blow, the\nbruise, the sympathetic sister or landlady. The crudest of\nwriters could invent nothing more crude.\"\n\n\"Indeed, your example is an unfortunate one for your argument,\"\nsaid Holmes, taking the paper and glancing his eye down it. \"This\nis the Dundas separation case, and, as it happens, I was engaged\nin clearing up some small points in connection with it. The\nhusband was a teetotaler, there was no other woman, and the\nconduct complained of was that he had drifted into the habit of\nwinding up every meal by taking out his false teeth and hurling\nthem at his wife, which, you will allow, is not an action likely\nto occur to the imagination of the average story-teller. Take a\npinch of snuff, Doctor, and acknowledge that I have scored over\nyou in your example.\"\n\nHe held out his snuffbox of old gold, with a great amethyst in\nthe centre of the lid. Its splendour was in such contrast to his\nhomely ways and simple life that I could not help commenting upon\nit.\n\n\"Ah,\" said he, \"I forgot that I had not seen you for some weeks.\nIt is a little souvenir from the King of Bohemia in return for my\nassistance in the case of the Irene Adler papers.\"\n\n\"And the ring?\" I asked, glancing at a remarkable brilliant which\nsparkled upon his finger.\n\n\"It was from the reigning family of Holland, though the matter in\nwhich I served them was of such delicacy that I cannot confide it\neven to you, who have been good enough to chronicle one or two of\nmy little problems.\"\n\n\"And have you any on hand just now?\" I asked with interest.\n\n\"Some ten or twelve, but none which present any feature of\ninterest. They are important, you understand, without being\ninteresting. Indeed, I have found that it is usually in\nunimportant matters that there is a field for the observation,\nand for the quick analysis of cause and effect which gives the\ncharm to an investigation. The larger crimes are apt to be the\nsimpler, for the bigger the crime the more obvious, as a rule, is\nthe motive. In these cases, save for one rather intricate matter\nwhich has been referred to me from Marseilles, there is nothing\nwhich presents any features of interest. It is possible, however,\nthat I may have something better before very many minutes are\nover, for this is one of my clients, or I am much mistaken.\"\n\nHe had risen from his chair and was standing between the parted\nblinds gazing down into the dull neutral-tinted London street.\nLooking over his shoulder, I saw that on the pavement opposite\nthere stood a large woman with a heavy fur boa round her neck,\nand a large curling red feather in a broad-brimmed hat which was\ntilted in a coquettish Duchess of Devonshire fashion over her\near. From under this great panoply she peeped up in a nervous,\nhesitating fashion at our windows, while her body oscillated\nbackward and forward, and her fingers fidgeted with her glove\nbuttons. Suddenly, with a plunge, as of the swimmer who leaves\nthe bank, she hurried across the road, and we heard the sharp\nclang of the bell.\n\n\"I have seen those symptoms before,\" said Holmes, throwing his\ncigarette into the fire. \"Oscillation upon the pavement always\nmeans an affaire de coeur. She would like advice, but is not sure\nthat the matter is not too delicate for communication. And yet\neven here we may discriminate. When a woman has been seriously\nwronged by a man she no longer oscillates, and the usual symptom\nis a broken bell wire. Here we may take it that there is a love\nmatter, but that the maiden is not so much angry as perplexed, or\ngrieved. But here she comes in person to resolve our doubts.\"\n\nAs he spoke there was a tap at the door, and the boy in buttons\nentered to announce Miss Mary Sutherland, while the lady herself\nloomed behind his small black figure like a full-sailed\nmerchant-man behind a tiny pilot boat. Sherlock Holmes welcomed\nher with the easy courtesy for which he was remarkable, and,\nhaving closed the door and bowed her into an armchair, he looked\nher over in the minute and yet abstracted fashion which was\npeculiar to him.\n\n\"Do you not find,\" he said, \"that with your short sight it is a\nlittle trying to do so much typewriting?\"\n\n\"I did at first,\" she answered, \"but now I know where the letters\nare without looking.\" Then, suddenly realising the full purport\nof his words, she gave a violent start and looked up, with fear\nand astonishment upon her broad, good-humoured face. \"You've\nheard about me, Mr. Holmes,\" she cried, \"else how could you know\nall that?\"\n\n\"Never mind,\" said Holmes, laughing; \"it is my business to know\nthings. Perhaps I have trained myself to see what others\noverlook. If not, why should you come to consult me?\"\n\n\"I came to you, sir, because I heard of you from Mrs. Etherege,\nwhose husband you found so easy when the police and everyone had\ngiven him up for dead. Oh, Mr. Holmes, I wish you would do as\nmuch for me. I'm not rich, but still I have a hundred a year in\nmy own right, besides the little that I make by the machine, and\nI would give it all to know what has become of Mr. Hosmer Angel.\"\n\n\"Why did you come away to consult me in such a hurry?\" asked\nSherlock Holmes, with his finger-tips together and his eyes to\nthe ceiling.\n\nAgain a startled look came over the somewhat vacuous face of Miss\nMary Sutherland. \"Yes, I did bang out of the house,\" she said,\n\"for it made me angry to see the easy way in which Mr.\nWindibank--that is, my father--took it all. He would not go to\nthe police, and he would not go to you, and so at last, as he\nwould do nothing and kept on saying that there was no harm done,\nit made me mad, and I just on with my things and came right away\nto you.\"\n\n\"Your father,\" said Holmes, \"your stepfather, surely, since the\nname is different.\"\n\n\"Yes, my stepfather. I call him father, though it sounds funny,\ntoo, for he is only five years and two months older than myself.\"\n\n\"And your mother is alive?\"\n\n\"Oh, yes, mother is alive and well. I wasn't best pleased, Mr.\nHolmes, when she married again so soon after father's death, and\na man who was nearly fifteen years younger than herself. Father\nwas a plumber in the Tottenham Court Road, and he left a tidy\nbusiness behind him, which mother carried on with Mr. Hardy, the\nforeman; but when Mr. Windibank came he made her sell the\nbusiness, for he was very superior, being a traveller in wines.\nThey got 4700 pounds for the goodwill and interest, which wasn't\nnear as much as father could have got if he had been alive.\"\n\nI had expected to see Sherlock Holmes impatient under this\nrambling and inconsequential narrative, but, on the contrary, he\nhad listened with the greatest concentration of attention.\n\n\"Your own little income,\" he asked, \"does it come out of the\nbusiness?\"\n\n\"Oh, no, sir. It is quite separate and was left me by my uncle\nNed in Auckland. It is in New Zealand stock, paying 4 1/2 per\ncent. Two thousand five hundred pounds was the amount, but I can\nonly touch the interest.\"\n\n\"You interest me extremely,\" said Holmes. \"And since you draw so\nlarge a sum as a hundred a year, with what you earn into the\nbargain, you no doubt travel a little and indulge yourself in\nevery way. I believe that a single lady can get on very nicely\nupon an income of about 60 pounds.\"\n\n\"I could do with much less than that, Mr. Holmes, but you\nunderstand that as long as I live at home I don't wish to be a\nburden to them, and so they have the use of the money just while\nI am staying with them. Of course, that is only just for the\ntime. Mr. Windibank draws my interest every quarter and pays it\nover to mother, and I find that I can do pretty well with what I\nearn at typewriting. It brings me twopence a sheet, and I can\noften do from fifteen to twenty sheets in a day.\"\n\n\"You have made your position very clear to me,\" said Holmes.\n\"This is my friend, Dr. Watson, before whom you can speak as\nfreely as before myself. Kindly tell us now all about your\nconnection with Mr. Hosmer Angel.\"\n\nA flush stole over Miss Sutherland's face, and she picked\nnervously at the fringe of her jacket. \"I met him first at the\ngasfitters' ball,\" she said. \"They used to send father tickets\nwhen he was alive, and then afterwards they remembered us, and\nsent them to mother. Mr. Windibank did not wish us to go. He\nnever did wish us to go anywhere. He would get quite mad if I\nwanted so much as to join a Sunday-school treat. But this time I\nwas set on going, and I would go; for what right had he to\nprevent? He said the folk were not fit for us to know, when all\nfather's friends were to be there. And he said that I had nothing\nfit to wear, when I had my purple plush that I had never so much\nas taken out of the drawer. At last, when nothing else would do,\nhe went off to France upon the business of the firm, but we went,\nmother and I, with Mr. Hardy, who used to be our foreman, and it\nwas there I met Mr. Hosmer Angel.\"\n\n\"I suppose,\" said Holmes, \"that when Mr. Windibank came back from\nFrance he was very annoyed at your having gone to the ball.\"\n\n\"Oh, well, he was very good about it. He laughed, I remember, and\nshrugged his shoulders, and said there was no use denying\nanything to a woman, for she would have her way.\"\n\n\"I see. Then at the gasfitters' ball you met, as I understand, a\ngentleman called Mr. Hosmer Angel.\"\n\n\"Yes, sir. I met him that night, and he called next day to ask if\nwe had got home all safe, and after that we met him--that is to\nsay, Mr. Holmes, I met him twice for walks, but after that father\ncame back again, and Mr. Hosmer Angel could not come to the house\nany more.\"\n\n\"No?\"\n\n\"Well, you know father didn't like anything of the sort. He\nwouldn't have any visitors if he could help it, and he used to\nsay that a woman should be happy in her own family circle. But\nthen, as I used to say to mother, a woman wants her own circle to\nbegin with, and I had not got mine yet.\"\n\n\"But how about Mr. Hosmer Angel? Did he make no attempt to see\nyou?\"\n\n\"Well, father was going off to France again in a week, and Hosmer\nwrote and said that it would be safer and better not to see each\nother until he had gone. We could write in the meantime, and he\nused to write every day. I took the letters in in the morning, so\nthere was no need for father to know.\"\n\n\"Were you engaged to the gentleman at this time?\"\n\n\"Oh, yes, Mr. Holmes. We were engaged after the first walk that\nwe took. Hosmer--Mr. Angel--was a cashier in an office in\nLeadenhall Street--and--\"\n\n\"What office?\"\n\n\"That's the worst of it, Mr. Holmes, I don't know.\"\n\n\"Where did he live, then?\"\n\n\"He slept on the premises.\"\n\n\"And you don't know his address?\"\n\n\"No--except that it was Leadenhall Street.\"\n\n\"Where did you address your letters, then?\"\n\n\"To the Leadenhall Street Post Office, to be left till called\nfor. He said that if they were sent to the office he would be\nchaffed by all the other clerks about having letters from a lady,\nso I offered to typewrite them, like he did his, but he wouldn't\nhave that, for he said that when I wrote them they seemed to come\nfrom me, but when they were typewritten he always felt that the\nmachine had come between us. That will just show you how fond he\nwas of me, Mr. Holmes, and the little things that he would think\nof.\"\n\n\"It was most suggestive,\" said Holmes. \"It has long been an axiom\nof mine that the little things are infinitely the most important.\nCan you remember any other little things about Mr. Hosmer Angel?\"\n\n\"He was a very shy man, Mr. Holmes. He would rather walk with me\nin the evening than in the daylight, for he said that he hated to\nbe conspicuous. Very retiring and gentlemanly he was. Even his\nvoice was gentle. He'd had the quinsy and swollen glands when he\nwas young, he told me, and it had left him with a weak throat,\nand a hesitating, whispering fashion of speech. He was always\nwell dressed, very neat and plain, but his eyes were weak, just\nas mine are, and he wore tinted glasses against the glare.\"\n\n\"Well, and what happened when Mr. Windibank, your stepfather,\nreturned to France?\"\n\n\"Mr. Hosmer Angel came to the house again and proposed that we\nshould marry before father came back. He was in dreadful earnest\nand made me swear, with my hands on the Testament, that whatever\nhappened I would always be true to him. Mother said he was quite\nright to make me swear, and that it was a sign of his passion.\nMother was all in his favour from the first and was even fonder\nof him than I was. Then, when they talked of marrying within the\nweek, I began to ask about father; but they both said never to\nmind about father, but just to tell him afterwards, and mother\nsaid she would make it all right with him. I didn't quite like\nthat, Mr. Holmes. It seemed funny that I should ask his leave, as\nhe was only a few years older than me; but I didn't want to do\nanything on the sly, so I wrote to father at Bordeaux, where the\ncompany has its French offices, but the letter came back to me on\nthe very morning of the wedding.\"\n\n\"It missed him, then?\"\n\n\"Yes, sir; for he had started to England just before it arrived.\"\n\n\"Ha! that was unfortunate. Your wedding was arranged, then, for\nthe Friday. Was it to be in church?\"\n\n\"Yes, sir, but very quietly. It was to be at St. Saviour's, near\nKing's Cross, and we were to have breakfast afterwards at the St.\nPancras Hotel. Hosmer came for us in a hansom, but as there were\ntwo of us he put us both into it and stepped himself into a\nfour-wheeler, which happened to be the only other cab in the\nstreet. We got to the church first, and when the four-wheeler\ndrove up we waited for him to step out, but he never did, and\nwhen the cabman got down from the box and looked there was no one\nthere! The cabman said that he could not imagine what had become\nof him, for he had seen him get in with his own eyes. That was\nlast Friday, Mr. Holmes, and I have never seen or heard anything\nsince then to throw any light upon what became of him.\"\n\n\"It seems to me that you have been very shamefully treated,\" said\nHolmes.\n\n\"Oh, no, sir! He was too good and kind to leave me so. Why, all\nthe morning he was saying to me that, whatever happened, I was to\nbe true; and that even if something quite unforeseen occurred to\nseparate us, I was always to remember that I was pledged to him,\nand that he would claim his pledge sooner or later. It seemed\nstrange talk for a wedding-morning, but what has happened since\ngives a meaning to it.\"\n\n\"Most certainly it does. Your own opinion is, then, that some\nunforeseen catastrophe has occurred to him?\"\n\n\"Yes, sir. I believe that he foresaw some danger, or else he\nwould not have talked so. And then I think that what he foresaw\nhappened.\"\n\n\"But you have no notion as to what it could have been?\"\n\n\"None.\"\n\n\"One more question. How did your mother take the matter?\"\n\n\"She was angry, and said that I was never to speak of the matter\nagain.\"\n\n\"And your father? Did you tell him?\"\n\n\"Yes; and he seemed to think, with me, that something had\nhappened, and that I should hear of Hosmer again. As he said,\nwhat interest could anyone have in bringing me to the doors of\nthe church, and then leaving me? Now, if he had borrowed my\nmoney, or if he had married me and got my money settled on him,\nthere might be some reason, but Hosmer was very independent about\nmoney and never would look at a shilling of mine. And yet, what\ncould have happened? And why could he not write? Oh, it drives me\nhalf-mad to think of it, and I can't sleep a wink at night.\" She\npulled a little handkerchief out of her muff and began to sob\nheavily into it.\n\n\"I shall glance into the case for you,\" said Holmes, rising, \"and\nI have no doubt that we shall reach some definite result. Let the\nweight of the matter rest upon me now, and do not let your mind\ndwell upon it further. Above all, try to let Mr. Hosmer Angel\nvanish from your memory, as he has done from your life.\"\n\n\"Then you don't think I'll see him again?\"\n\n\"I fear not.\"\n\n\"Then what has happened to him?\"\n\n\"You will leave that question in my hands. I should like an\naccurate description of him and any letters of his which you can\nspare.\"\n\n\"I advertised for him in last Saturday's Chronicle,\" said she.\n\"Here is the slip and here are four letters from him.\"\n\n\"Thank you. And your address?\"\n\n\"No. 31 Lyon Place, Camberwell.\"\n\n\"Mr. Angel's address you never had, I understand. Where is your\nfather's place of business?\"\n\n\"He travels for Westhouse & Marbank, the great claret importers\nof Fenchurch Street.\"\n\n\"Thank you. You have made your statement very clearly. You will\nleave the papers here, and remember the advice which I have given\nyou. Let the whole incident be a sealed book, and do not allow it\nto affect your life.\"\n\n\"You are very kind, Mr. Holmes, but I cannot do that. I shall be\ntrue to Hosmer. He shall find me ready when he comes back.\"\n\nFor all the preposterous hat and the vacuous face, there was\nsomething noble in the simple faith of our visitor which\ncompelled our respect. She laid her little bundle of papers upon\nthe table and went her way, with a promise to come again whenever\nshe might be summoned.\n\nSherlock Holmes sat silent for a few minutes with his fingertips\nstill pressed together, his legs stretched out in front of him,\nand his gaze directed upward to the ceiling. Then he took down\nfrom the rack the old and oily clay pipe, which was to him as a\ncounsellor, and, having lit it, he leaned back in his chair, with\nthe thick blue cloud-wreaths spinning up from him, and a look of\ninfinite languor in his face.\n\n\"Quite an interesting study, that maiden,\" he observed. \"I found\nher more interesting than her little problem, which, by the way,\nis rather a trite one. You will find parallel cases, if you\nconsult my index, in Andover in '77, and there was something of\nthe sort at The Hague last year. Old as is the idea, however,\nthere were one or two details which were new to me. But the\nmaiden herself was most instructive.\"\n\n\"You appeared to read a good deal upon her which was quite\ninvisible to me,\" I remarked.\n\n\"Not invisible but unnoticed, Watson. You did not know where to\nlook, and so you missed all that was important. I can never bring\nyou to realise the importance of sleeves, the suggestiveness of\nthumb-nails, or the great issues that may hang from a boot-lace.\nNow, what did you gather from that woman's appearance? Describe\nit.\"\n\n\"Well, she had a slate-coloured, broad-brimmed straw hat, with a\nfeather of a brickish red. Her jacket was black, with black beads\nsewn upon it, and a fringe of little black jet ornaments. Her\ndress was brown, rather darker than coffee colour, with a little\npurple plush at the neck and sleeves. Her gloves were greyish and\nwere worn through at the right forefinger. Her boots I didn't\nobserve. She had small round, hanging gold earrings, and a\ngeneral air of being fairly well-to-do in a vulgar, comfortable,\neasy-going way.\"\n\nSherlock Holmes clapped his hands softly together and chuckled.\n\n\"'Pon my word, Watson, you are coming along wonderfully. You have\nreally done very well indeed. It is true that you have missed\neverything of importance, but you have hit upon the method, and\nyou have a quick eye for colour. Never trust to general\nimpressions, my boy, but concentrate yourself upon details. My\nfirst glance is always at a woman's sleeve. In a man it is\nperhaps better first to take the knee of the trouser. As you\nobserve, this woman had plush upon her sleeves, which is a most\nuseful material for showing traces. The double line a little\nabove the wrist, where the typewritist presses against the table,\nwas beautifully defined. The sewing-machine, of the hand type,\nleaves a similar mark, but only on the left arm, and on the side\nof it farthest from the thumb, instead of being right across the\nbroadest part, as this was. I then glanced at her face, and,\nobserving the dint of a pince-nez at either side of her nose, I\nventured a remark upon short sight and typewriting, which seemed\nto surprise her.\"\n\n\"It surprised me.\"\n\n\"But, surely, it was obvious. I was then much surprised and\ninterested on glancing down to observe that, though the boots\nwhich she was wearing were not unlike each other, they were\nreally odd ones; the one having a slightly decorated toe-cap, and\nthe other a plain one. One was buttoned only in the two lower\nbuttons out of five, and the other at the first, third, and\nfifth. Now, when you see that a young lady, otherwise neatly\ndressed, has come away from home with odd boots, half-buttoned,\nit is no great deduction to say that she came away in a hurry.\"\n\n\"And what else?\" I asked, keenly interested, as I always was, by\nmy friend's incisive reasoning.\n\n\"I noted, in passing, that she had written a note before leaving\nhome but after being fully dressed. You observed that her right\nglove was torn at the forefinger, but you did not apparently see\nthat both glove and finger were stained with violet ink. She had\nwritten in a hurry and dipped her pen too deep. It must have been\nthis morning, or the mark would not remain clear upon the finger.\nAll this is amusing, though rather elementary, but I must go back\nto business, Watson. Would you mind reading me the advertised\ndescription of Mr. Hosmer Angel?\"\n\nI held the little printed slip to the light.\n\n\"Missing,\" it said, \"on the morning of the fourteenth, a gentleman\nnamed Hosmer Angel. About five ft. seven in. in height;\nstrongly built, sallow complexion, black hair, a little bald in\nthe centre, bushy, black side-whiskers and moustache; tinted\nglasses, slight infirmity of speech. Was dressed, when last seen,\nin black frock-coat faced with silk, black waistcoat, gold Albert\nchain, and grey Harris tweed trousers, with brown gaiters over\nelastic-sided boots. Known to have been employed in an office in\nLeadenhall Street. Anybody bringing--\"\n\n\"That will do,\" said Holmes. \"As to the letters,\" he continued,\nglancing over them, \"they are very commonplace. Absolutely no\nclue in them to Mr. Angel, save that he quotes Balzac once. There\nis one remarkable point, however, which will no doubt strike\nyou.\"\n\n\"They are typewritten,\" I remarked.\n\n\"Not only that, but the signature is typewritten. Look at the\nneat little 'Hosmer Angel' at the bottom. There is a date, you\nsee, but no superscription except Leadenhall Street, which is\nrather vague. The point about the signature is very suggestive--in\nfact, we may call it conclusive.\"\n\n\"Of what?\"\n\n\"My dear fellow, is it possible you do not see how strongly it\nbears upon the case?\"\n\n\"I cannot say that I do unless it were that he wished to be able\nto deny his signature if an action for breach of promise were\ninstituted.\"\n\n\"No, that was not the point. However, I shall write two letters,\nwhich should settle the matter. One is to a firm in the City, the\nother is to the young lady's stepfather, Mr. Windibank, asking\nhim whether he could meet us here at six o'clock tomorrow\nevening. It is just as well that we should do business with the\nmale relatives. And now, Doctor, we can do nothing until the\nanswers to those letters come, so we may put our little problem\nupon the shelf for the interim.\"\n\nI had had so many reasons to believe in my friend's subtle powers\nof reasoning and extraordinary energy in action that I felt that\nhe must have some solid grounds for the assured and easy\ndemeanour with which he treated the singular mystery which he had\nbeen called upon to fathom. Once only had I known him to fail, in\nthe case of the King of Bohemia and of the Irene Adler\nphotograph; but when I looked back to the weird business of the\nSign of Four, and the extraordinary circumstances connected with\nthe Study in Scarlet, I felt that it would be a strange tangle\nindeed which he could not unravel.\n\nI left him then, still puffing at his black clay pipe, with the\nconviction that when I came again on the next evening I would\nfind that he held in his hands all the clues which would lead up\nto the identity of the disappearing bridegroom of Miss Mary\nSutherland.\n\nA professional case of great gravity was engaging my own\nattention at the time, and the whole of next day I was busy at\nthe bedside of the sufferer. It was not until close upon six\no'clock that I found myself free and was able to spring into a\nhansom and drive to Baker Street, half afraid that I might be too\nlate to assist at the dénouement of the little mystery. I found\nSherlock Holmes alone, however, half asleep, with his long, thin\nform curled up in the recesses of his armchair. A formidable\narray of bottles and test-tubes, with the pungent cleanly smell\nof hydrochloric acid, told me that he had spent his day in the\nchemical work which was so dear to him.\n\n\"Well, have you solved it?\" I asked as I entered.\n\n\"Yes. It was the bisulphate of baryta.\"\n\n\"No, no, the mystery!\" I cried.\n\n\"Oh, that! I thought of the salt that I have been working upon.\nThere was never any mystery in the matter, though, as I said\nyesterday, some of the details are of interest. The only drawback\nis that there is no law, I fear, that can touch the scoundrel.\"\n\n\"Who was he, then, and what was his object in deserting Miss\nSutherland?\"\n\nThe question was hardly out of my mouth, and Holmes had not yet\nopened his lips to reply, when we heard a heavy footfall in the\npassage and a tap at the door.\n\n\"This is the girl's stepfather, Mr. James Windibank,\" said\nHolmes. \"He has written to me to say that he would be here at\nsix. Come in!\"\n\nThe man who entered was a sturdy, middle-sized fellow, some\nthirty years of age, clean-shaven, and sallow-skinned, with a\nbland, insinuating manner, and a pair of wonderfully sharp and\npenetrating grey eyes. He shot a questioning glance at each of\nus, placed his shiny top-hat upon the sideboard, and with a\nslight bow sidled down into the nearest chair.\n\n\"Good-evening, Mr. James Windibank,\" said Holmes. \"I think that\nthis typewritten letter is from you, in which you made an\nappointment with me for six o'clock?\"\n\n\"Yes, sir. I am afraid that I am a little late, but I am not\nquite my own master, you know. I am sorry that Miss Sutherland\nhas troubled you about this little matter, for I think it is far\nbetter not to wash linen of the sort in public. It was quite\nagainst my wishes that she came, but she is a very excitable,\nimpulsive girl, as you may have noticed, and she is not easily\ncontrolled when she has made up her mind on a point. Of course, I\ndid not mind you so much, as you are not connected with the\nofficial police, but it is not pleasant to have a family\nmisfortune like this noised abroad. Besides, it is a useless\nexpense, for how could you possibly find this Hosmer Angel?\"\n\n\"On the contrary,\" said Holmes quietly; \"I have every reason to\nbelieve that I will succeed in discovering Mr. Hosmer Angel.\"\n\nMr. Windibank gave a violent start and dropped his gloves. \"I am\ndelighted to hear it,\" he said.\n\n\"It is a curious thing,\" remarked Holmes, \"that a typewriter has\nreally quite as much individuality as a man's handwriting. Unless\nthey are quite new, no two of them write exactly alike. Some\nletters get more worn than others, and some wear only on one\nside. Now, you remark in this note of yours, Mr. Windibank, that\nin every case there is some little slurring over of the 'e,' and\na slight defect in the tail of the 'r.' There are fourteen other\ncharacteristics, but those are the more obvious.\"\n\n\"We do all our correspondence with this machine at the office,\nand no doubt it is a little worn,\" our visitor answered, glancing\nkeenly at Holmes with his bright little eyes.\n\n\"And now I will show you what is really a very interesting study,\nMr. Windibank,\" Holmes continued. \"I think of writing another\nlittle monograph some of these days on the typewriter and its\nrelation to crime. It is a subject to which I have devoted some\nlittle attention. I have here four letters which purport to come\nfrom the missing man. They are all typewritten. In each case, not\nonly are the 'e's' slurred and the 'r's' tailless, but you will\nobserve, if you care to use my magnifying lens, that the fourteen\nother characteristics to which I have alluded are there as well.\"\n\nMr. Windibank sprang out of his chair and picked up his hat. \"I\ncannot waste time over this sort of fantastic talk, Mr. Holmes,\"\nhe said. \"If you can catch the man, catch him, and let me know\nwhen you have done it.\"\n\n\"Certainly,\" said Holmes, stepping over and turning the key in\nthe door. \"I let you know, then, that I have caught him!\"\n\n\"What! where?\" shouted Mr. Windibank, turning white to his lips\nand glancing about him like a rat in a trap.\n\n\"Oh, it won't do--really it won't,\" said Holmes suavely. \"There\nis no possible getting out of it, Mr. Windibank. It is quite too\ntransparent, and it was a very bad compliment when you said that\nit was impossible for me to solve so simple a question. That's\nright! Sit down and let us talk it over.\"\n\nOur visitor collapsed into a chair, with a ghastly face and a\nglitter of moisture on his brow. \"It--it's not actionable,\" he\nstammered.\n\n\"I am very much afraid that it is not. But between ourselves,\nWindibank, it was as cruel and selfish and heartless a trick in a\npetty way as ever came before me. Now, let me just run over the\ncourse of events, and you will contradict me if I go wrong.\"\n\nThe man sat huddled up in his chair, with his head sunk upon his\nbreast, like one who is utterly crushed. Holmes stuck his feet up\non the corner of the mantelpiece and, leaning back with his hands\nin his pockets, began talking, rather to himself, as it seemed,\nthan to us.\n\n\"The man married a woman very much older than himself for her\nmoney,\" said he, \"and he enjoyed the use of the money of the\ndaughter as long as she lived with them. It was a considerable\nsum, for people in their position, and the loss of it would have\nmade a serious difference. It was worth an effort to preserve it.\nThe daughter was of a good, amiable disposition, but affectionate\nand warm-hearted in her ways, so that it was evident that with\nher fair personal advantages, and her little income, she would\nnot be allowed to remain single long. Now her marriage would\nmean, of course, the loss of a hundred a year, so what does her\nstepfather do to prevent it? He takes the obvious course of\nkeeping her at home and forbidding her to seek the company of\npeople of her own age. But soon he found that that would not\nanswer forever. She became restive, insisted upon her rights, and\nfinally announced her positive intention of going to a certain\nball. What does her clever stepfather do then? He conceives an\nidea more creditable to his head than to his heart. With the\nconnivance and assistance of his wife he disguised himself,\ncovered those keen eyes with tinted glasses, masked the face with\na moustache and a pair of bushy whiskers, sunk that clear voice\ninto an insinuating whisper, and doubly secure on account of the\ngirl's short sight, he appears as Mr. Hosmer Angel, and keeps off\nother lovers by making love himself.\"\n\n\"It was only a joke at first,\" groaned our visitor. \"We never\nthought that she would have been so carried away.\"\n\n\"Very likely not. However that may be, the young lady was very\ndecidedly carried away, and, having quite made up her mind that\nher stepfather was in France, the suspicion of treachery never\nfor an instant entered her mind. She was flattered by the\ngentleman's attentions, and the effect was increased by the\nloudly expressed admiration of her mother. Then Mr. Angel began\nto call, for it was obvious that the matter should be pushed as\nfar as it would go if a real effect were to be produced. There\nwere meetings, and an engagement, which would finally secure the\ngirl's affections from turning towards anyone else. But the\ndeception could not be kept up forever. These pretended journeys\nto France were rather cumbrous. The thing to do was clearly to\nbring the business to an end in such a dramatic manner that it\nwould leave a permanent impression upon the young lady's mind and\nprevent her from looking upon any other suitor for some time to\ncome. Hence those vows of fidelity exacted upon a Testament, and\nhence also the allusions to a possibility of something happening\non the very morning of the wedding. James Windibank wished Miss\nSutherland to be so bound to Hosmer Angel, and so uncertain as to\nhis fate, that for ten years to come, at any rate, she would not\nlisten to another man. As far as the church door he brought her,\nand then, as he could go no farther, he conveniently vanished\naway by the old trick of stepping in at one door of a\nfour-wheeler and out at the other. I think that was the chain of\nevents, Mr. Windibank!\"\n\nOur visitor had recovered something of his assurance while Holmes\nhad been talking, and he rose from his chair now with a cold\nsneer upon his pale face.\n\n\"It may be so, or it may not, Mr. Holmes,\" said he, \"but if you\nare so very sharp you ought to be sharp enough to know that it is\nyou who are breaking the law now, and not me. I have done nothing\nactionable from the first, but as long as you keep that door\nlocked you lay yourself open to an action for assault and illegal\nconstraint.\"\n\n\"The law cannot, as you say, touch you,\" said Holmes, unlocking\nand throwing open the door, \"yet there never was a man who\ndeserved punishment more. If the young lady has a brother or a\nfriend, he ought to lay a whip across your shoulders. By Jove!\"\nhe continued, flushing up at the sight of the bitter sneer upon\nthe man's face, \"it is not part of my duties to my client, but\nhere's a hunting crop handy, and I think I shall just treat\nmyself to--\" He took two swift steps to the whip, but before he\ncould grasp it there was a wild clatter of steps upon the stairs,\nthe heavy hall door banged, and from the window we could see Mr.\nJames Windibank running at the top of his speed down the road.\n\n\"There's a cold-blooded scoundrel!\" said Holmes, laughing, as he\nthrew himself down into his chair once more. \"That fellow will\nrise from crime to crime until he does something very bad, and\nends on a gallows. The case has, in some respects, been not\nentirely devoid of interest.\"\n\n\"I cannot now entirely see all the steps of your reasoning,\" I\nremarked.\n\n\"Well, of course it was obvious from the first that this Mr.\nHosmer Angel must have some strong object for his curious\nconduct, and it was equally clear that the only man who really\nprofited by the incident, as far as we could see, was the\nstepfather. Then the fact that the two men were never together,\nbut that the one always appeared when the other was away, was\nsuggestive. So were the tinted spectacles and the curious voice,\nwhich both hinted at a disguise, as did the bushy whiskers. My\nsuspicions were all confirmed by his peculiar action in\ntypewriting his signature, which, of course, inferred that his\nhandwriting was so familiar to her that she would recognise even\nthe smallest sample of it. You see all these isolated facts,\ntogether with many minor ones, all pointed in the same\ndirection.\"\n\n\"And how did you verify them?\"\n\n\"Having once spotted my man, it was easy to get corroboration. I\nknew the firm for which this man worked. Having taken the printed\ndescription. I eliminated everything from it which could be the\nresult of a disguise--the whiskers, the glasses, the voice, and I\nsent it to the firm, with a request that they would inform me\nwhether it answered to the description of any of their\ntravellers. I had already noticed the peculiarities of the\ntypewriter, and I wrote to the man himself at his business\naddress asking him if he would come here. As I expected, his\nreply was typewritten and revealed the same trivial but\ncharacteristic defects. The same post brought me a letter from\nWesthouse & Marbank, of Fenchurch Street, to say that the\ndescription tallied in every respect with that of their employé,\nJames Windibank. Voilà tout!\"\n\n\"And Miss Sutherland?\"\n\n\"If I tell her she will not believe me. You may remember the old\nPersian saying, 'There is danger for him who taketh the tiger\ncub, and danger also for whoso snatches a delusion from a woman.'\nThere is as much sense in Hafiz as in Horace, and as much\nknowledge of the world.\"\n\n\n\nADVENTURE IV. THE BOSCOMBE VALLEY MYSTERY\n\nWe were seated at breakfast one morning, my wife and I, when the\nmaid brought in a telegram. It was from Sherlock Holmes and ran\nin this way:\n\n\"Have you a couple of days to spare? Have just been wired for from\nthe west of England in connection with Boscombe Valley tragedy.\nShall be glad if you will come with me. Air and scenery perfect.\nLeave Paddington by the 11:15.\"\n\n\"What do you say, dear?\" said my wife, looking across at me.\n\"Will you go?\"\n\n\"I really don't know what to say. I have a fairly long list at\npresent.\"\n\n\"Oh, Anstruther would do your work for you. You have been looking\na little pale lately. I think that the change would do you good,\nand you are always so interested in Mr. Sherlock Holmes' cases.\"\n\n\"I should be ungrateful if I were not, seeing what I gained\nthrough one of them,\" I answered. \"But if I am to go, I must pack\nat once, for I have only half an hour.\"\n\nMy experience of camp life in Afghanistan had at least had the\neffect of making me a prompt and ready traveller. My wants were\nfew and simple, so that in less than the time stated I was in a\ncab with my valise, rattling away to Paddington Station. Sherlock\nHolmes was pacing up and down the platform, his tall, gaunt\nfigure made even gaunter and taller by his long grey\ntravelling-cloak and close-fitting cloth cap.\n\n\"It is really very good of you to come, Watson,\" said he. \"It\nmakes a considerable difference to me, having someone with me on\nwhom I can thoroughly rely. Local aid is always either worthless\nor else biassed. If you will keep the two corner seats I shall\nget the tickets.\"\n\nWe had the carriage to ourselves save for an immense litter of\npapers which Holmes had brought with him. Among these he rummaged\nand read, with intervals of note-taking and of meditation, until\nwe were past Reading. Then he suddenly rolled them all into a\ngigantic ball and tossed them up onto the rack.\n\n\"Have you heard anything of the case?\" he asked.\n\n\"Not a word. I have not seen a paper for some days.\"\n\n\"The London press has not had very full accounts. I have just\nbeen looking through all the recent papers in order to master the\nparticulars. It seems, from what I gather, to be one of those\nsimple cases which are so extremely difficult.\"\n\n\"That sounds a little paradoxical.\"\n\n\"But it is profoundly true. Singularity is almost invariably a\nclue. The more featureless and commonplace a crime is, the more\ndifficult it is to bring it home. In this case, however, they\nhave established a very serious case against the son of the\nmurdered man.\"\n\n\"It is a murder, then?\"\n\n\"Well, it is conjectured to be so. I shall take nothing for\ngranted until I have the opportunity of looking personally into\nit. I will explain the state of things to you, as far as I have\nbeen able to understand it, in a very few words.\n\n\"Boscombe Valley is a country district not very far from Ross, in\nHerefordshire. The largest landed proprietor in that part is a\nMr. John Turner, who made his money in Australia and returned\nsome years ago to the old country. One of the farms which he\nheld, that of Hatherley, was let to Mr. Charles McCarthy, who was\nalso an ex-Australian. The men had known each other in the\ncolonies, so that it was not unnatural that when they came to\nsettle down they should do so as near each other as possible.\nTurner was apparently the richer man, so McCarthy became his\ntenant but still remained, it seems, upon terms of perfect\nequality, as they were frequently together. McCarthy had one son,\na lad of eighteen, and Turner had an only daughter of the same\nage, but neither of them had wives living. They appear to have\navoided the society of the neighbouring English families and to\nhave led retired lives, though both the McCarthys were fond of\nsport and were frequently seen at the race-meetings of the\nneighbourhood. McCarthy kept two servants--a man and a girl.\nTurner had a considerable household, some half-dozen at the\nleast. That is as much as I have been able to gather about the\nfamilies. Now for the facts.\n\n\"On June 3rd, that is, on Monday last, McCarthy left his house at\nHatherley about three in the afternoon and walked down to the\nBoscombe Pool, which is a small lake formed by the spreading out\nof the stream which runs down the Boscombe Valley. He had been\nout with his serving-man in the morning at Ross, and he had told\nthe man that he must hurry, as he had an appointment of\nimportance to keep at three. From that appointment he never came\nback alive.\n\n\"From Hatherley Farm-house to the Boscombe Pool is a quarter of a\nmile, and two people saw him as he passed over this ground. One\nwas an old woman, whose name is not mentioned, and the other was\nWilliam Crowder, a game-keeper in the employ of Mr. Turner. Both\nthese witnesses depose that Mr. McCarthy was walking alone. The\ngame-keeper adds that within a few minutes of his seeing Mr.\nMcCarthy pass he had seen his son, Mr. James McCarthy, going the\nsame way with a gun under his arm. To the best of his belief, the\nfather was actually in sight at the time, and the son was\nfollowing him. He thought no more of the matter until he heard in\nthe evening of the tragedy that had occurred.\n\n\"The two McCarthys were seen after the time when William Crowder,\nthe game-keeper, lost sight of them. The Boscombe Pool is thickly\nwooded round, with just a fringe of grass and of reeds round the\nedge. A girl of fourteen, Patience Moran, who is the daughter of\nthe lodge-keeper of the Boscombe Valley estate, was in one of the\nwoods picking flowers. She states that while she was there she\nsaw, at the border of the wood and close by the lake, Mr.\nMcCarthy and his son, and that they appeared to be having a\nviolent quarrel. She heard Mr. McCarthy the elder using very\nstrong language to his son, and she saw the latter raise up his\nhand as if to strike his father. She was so frightened by their\nviolence that she ran away and told her mother when she reached\nhome that she had left the two McCarthys quarrelling near\nBoscombe Pool, and that she was afraid that they were going to\nfight. She had hardly said the words when young Mr. McCarthy came\nrunning up to the lodge to say that he had found his father dead\nin the wood, and to ask for the help of the lodge-keeper. He was\nmuch excited, without either his gun or his hat, and his right\nhand and sleeve were observed to be stained with fresh blood. On\nfollowing him they found the dead body stretched out upon the\ngrass beside the pool. The head had been beaten in by repeated\nblows of some heavy and blunt weapon. The injuries were such as\nmight very well have been inflicted by the butt-end of his son's\ngun, which was found lying on the grass within a few paces of the\nbody. Under these circumstances the young man was instantly\narrested, and a verdict of 'wilful murder' having been returned\nat the inquest on Tuesday, he was on Wednesday brought before the\nmagistrates at Ross, who have referred the case to the next\nAssizes. Those are the main facts of the case as they came out\nbefore the coroner and the police-court.\"\n\n\"I could hardly imagine a more damning case,\" I remarked. \"If\never circumstantial evidence pointed to a criminal it does so\nhere.\"\n\n\"Circumstantial evidence is a very tricky thing,\" answered Holmes\nthoughtfully. \"It may seem to point very straight to one thing,\nbut if you shift your own point of view a little, you may find it\npointing in an equally uncompromising manner to something\nentirely different. It must be confessed, however, that the case\nlooks exceedingly grave against the young man, and it is very\npossible that he is indeed the culprit. There are several people\nin the neighbourhood, however, and among them Miss Turner, the\ndaughter of the neighbouring landowner, who believe in his\ninnocence, and who have retained Lestrade, whom you may recollect\nin connection with the Study in Scarlet, to work out the case in\nhis interest. Lestrade, being rather puzzled, has referred the\ncase to me, and hence it is that two middle-aged gentlemen are\nflying westward at fifty miles an hour instead of quietly\ndigesting their breakfasts at home.\"\n\n\"I am afraid,\" said I, \"that the facts are so obvious that you\nwill find little credit to be gained out of this case.\"\n\n\"There is nothing more deceptive than an obvious fact,\" he\nanswered, laughing. \"Besides, we may chance to hit upon some\nother obvious facts which may have been by no means obvious to\nMr. Lestrade. You know me too well to think that I am boasting\nwhen I say that I shall either confirm or destroy his theory by\nmeans which he is quite incapable of employing, or even of\nunderstanding. To take the first example to hand, I very clearly\nperceive that in your bedroom the window is upon the right-hand\nside, and yet I question whether Mr. Lestrade would have noted\neven so self-evident a thing as that.\"\n\n\"How on earth--\"\n\n\"My dear fellow, I know you well. I know the military neatness\nwhich characterises you. You shave every morning, and in this\nseason you shave by the sunlight; but since your shaving is less\nand less complete as we get farther back on the left side, until\nit becomes positively slovenly as we get round the angle of the\njaw, it is surely very clear that that side is less illuminated\nthan the other. I could not imagine a man of your habits looking\nat himself in an equal light and being satisfied with such a\nresult. I only quote this as a trivial example of observation and\ninference. Therein lies my métier, and it is just possible that\nit may be of some service in the investigation which lies before\nus. There are one or two minor points which were brought out in\nthe inquest, and which are worth considering.\"\n\n\"What are they?\"\n\n\"It appears that his arrest did not take place at once, but after\nthe return to Hatherley Farm. On the inspector of constabulary\ninforming him that he was a prisoner, he remarked that he was not\nsurprised to hear it, and that it was no more than his deserts.\nThis observation of his had the natural effect of removing any\ntraces of doubt which might have remained in the minds of the\ncoroner's jury.\"\n\n\"It was a confession,\" I ejaculated.\n\n\"No, for it was followed by a protestation of innocence.\"\n\n\"Coming on the top of such a damning series of events, it was at\nleast a most suspicious remark.\"\n\n\"On the contrary,\" said Holmes, \"it is the brightest rift which I\ncan at present see in the clouds. However innocent he might be,\nhe could not be such an absolute imbecile as not to see that the\ncircumstances were very black against him. Had he appeared\nsurprised at his own arrest, or feigned indignation at it, I\nshould have looked upon it as highly suspicious, because such\nsurprise or anger would not be natural under the circumstances,\nand yet might appear to be the best policy to a scheming man. His\nfrank acceptance of the situation marks him as either an innocent\nman, or else as a man of considerable self-restraint and\nfirmness. As to his remark about his deserts, it was also not\nunnatural if you consider that he stood beside the dead body of\nhis father, and that there is no doubt that he had that very day\nso far forgotten his filial duty as to bandy words with him, and\neven, according to the little girl whose evidence is so\nimportant, to raise his hand as if to strike him. The\nself-reproach and contrition which are displayed in his remark\nappear to me to be the signs of a healthy mind rather than of a\nguilty one.\"\n\nI shook my head. \"Many men have been hanged on far slighter\nevidence,\" I remarked.\n\n\"So they have. And many men have been wrongfully hanged.\"\n\n\"What is the young man's own account of the matter?\"\n\n\"It is, I am afraid, not very encouraging to his supporters,\nthough there are one or two points in it which are suggestive.\nYou will find it here, and may read it for yourself.\"\n\nHe picked out from his bundle a copy of the local Herefordshire\npaper, and having turned down the sheet he pointed out the\nparagraph in which the unfortunate young man had given his own\nstatement of what had occurred. I settled myself down in the\ncorner of the carriage and read it very carefully. It ran in this\nway:\n\n\"Mr. James McCarthy, the only son of the deceased, was then called\nand gave evidence as follows: 'I had been away from home for\nthree days at Bristol, and had only just returned upon the\nmorning of last Monday, the 3rd. My father was absent from home at\nthe time of my arrival, and I was informed by the maid that he\nhad driven over to Ross with John Cobb, the groom. Shortly after\nmy return I heard the wheels of his trap in the yard, and,\nlooking out of my window, I saw him get out and walk rapidly out\nof the yard, though I was not aware in which direction he was\ngoing. I then took my gun and strolled out in the direction of\nthe Boscombe Pool, with the intention of visiting the rabbit\nwarren which is upon the other side. On my way I saw William\nCrowder, the game-keeper, as he had stated in his evidence; but\nhe is mistaken in thinking that I was following my father. I had\nno idea that he was in front of me. When about a hundred yards\nfrom the pool I heard a cry of \"Cooee!\" which was a usual signal\nbetween my father and myself. I then hurried forward, and found\nhim standing by the pool. He appeared to be much surprised at\nseeing me and asked me rather roughly what I was doing there. A\nconversation ensued which led to high words and almost to blows,\nfor my father was a man of a very violent temper. Seeing that his\npassion was becoming ungovernable, I left him and returned\ntowards Hatherley Farm. I had not gone more than 150 yards,\nhowever, when I heard a hideous outcry behind me, which caused me\nto run back again. I found my father expiring upon the ground,\nwith his head terribly injured. I dropped my gun and held him in\nmy arms, but he almost instantly expired. I knelt beside him for\nsome minutes, and then made my way to Mr. Turner's lodge-keeper,\nhis house being the nearest, to ask for assistance. I saw no one\nnear my father when I returned, and I have no idea how he came by\nhis injuries. He was not a popular man, being somewhat cold and\nforbidding in his manners, but he had, as far as I know, no\nactive enemies. I know nothing further of the matter.'\n\n\"The Coroner: Did your father make any statement to you before\nhe died?\n\n\"Witness: He mumbled a few words, but I could only catch some\nallusion to a rat.\n\n\"The Coroner: What did you understand by that?\n\n\"Witness: It conveyed no meaning to me. I thought that he was\ndelirious.\n\n\"The Coroner: What was the point upon which you and your father\nhad this final quarrel?\n\n\"Witness: I should prefer not to answer.\n\n\"The Coroner: I am afraid that I must press it.\n\n\"Witness: It is really impossible for me to tell you. I can\nassure you that it has nothing to do with the sad tragedy which\nfollowed.\n\n\"The Coroner: That is for the court to decide. I need not point\nout to you that your refusal to answer will prejudice your case\nconsiderably in any future proceedings which may arise.\n\n\"Witness: I must still refuse.\n\n\"The Coroner: I understand that the cry of 'Cooee' was a common\nsignal between you and your father?\n\n\"Witness: It was.\n\n\"The Coroner: How was it, then, that he uttered it before he saw\nyou, and before he even knew that you had returned from Bristol?\n\n\"Witness (with considerable confusion): I do not know.\n\n\"A Juryman: Did you see nothing which aroused your suspicions\nwhen you returned on hearing the cry and found your father\nfatally injured?\n\n\"Witness: Nothing definite.\n\n\"The Coroner: What do you mean?\n\n\"Witness: I was so disturbed and excited as I rushed out into\nthe open, that I could think of nothing except of my father. Yet\nI have a vague impression that as I ran forward something lay\nupon the ground to the left of me. It seemed to me to be\nsomething grey in colour, a coat of some sort, or a plaid perhaps.\nWhen I rose from my father I looked round for it, but it was\ngone.\n\n\"'Do you mean that it disappeared before you went for help?'\n\n\"'Yes, it was gone.'\n\n\"'You cannot say what it was?'\n\n\"'No, I had a feeling something was there.'\n\n\"'How far from the body?'\n\n\"'A dozen yards or so.'\n\n\"'And how far from the edge of the wood?'\n\n\"'About the same.'\n\n\"'Then if it was removed it was while you were within a dozen\nyards of it?'\n\n\"'Yes, but with my back towards it.'\n\n\"This concluded the examination of the witness.\"\n\n\"I see,\" said I as I glanced down the column, \"that the coroner\nin his concluding remarks was rather severe upon young McCarthy.\nHe calls attention, and with reason, to the discrepancy about his\nfather having signalled to him before seeing him, also to his\nrefusal to give details of his conversation with his father, and\nhis singular account of his father's dying words. They are all,\nas he remarks, very much against the son.\"\n\nHolmes laughed softly to himself and stretched himself out upon\nthe cushioned seat. \"Both you and the coroner have been at some\npains,\" said he, \"to single out the very strongest points in the\nyoung man's favour. Don't you see that you alternately give him\ncredit for having too much imagination and too little? Too\nlittle, if he could not invent a cause of quarrel which would\ngive him the sympathy of the jury; too much, if he evolved from\nhis own inner consciousness anything so outré as a dying\nreference to a rat, and the incident of the vanishing cloth. No,\nsir, I shall approach this case from the point of view that what\nthis young man says is true, and we shall see whither that\nhypothesis will lead us. And now here is my pocket Petrarch, and\nnot another word shall I say of this case until we are on the\nscene of action. We lunch at Swindon, and I see that we shall be\nthere in twenty minutes.\"\n\nIt was nearly four o'clock when we at last, after passing through\nthe beautiful Stroud Valley, and over the broad gleaming Severn,\nfound ourselves at the pretty little country-town of Ross. A\nlean, ferret-like man, furtive and sly-looking, was waiting for\nus upon the platform. In spite of the light brown dustcoat and\nleather-leggings which he wore in deference to his rustic\nsurroundings, I had no difficulty in recognising Lestrade, of\nScotland Yard. With him we drove to the Hereford Arms where a\nroom had already been engaged for us.\n\n\"I have ordered a carriage,\" said Lestrade as we sat over a cup\nof tea. \"I knew your energetic nature, and that you would not be\nhappy until you had been on the scene of the crime.\"\n\n\"It was very nice and complimentary of you,\" Holmes answered. \"It\nis entirely a question of barometric pressure.\"\n\nLestrade looked startled. \"I do not quite follow,\" he said.\n\n\"How is the glass? Twenty-nine, I see. No wind, and not a cloud\nin the sky. I have a caseful of cigarettes here which need\nsmoking, and the sofa is very much superior to the usual country\nhotel abomination. I do not think that it is probable that I\nshall use the carriage to-night.\"\n\nLestrade laughed indulgently. \"You have, no doubt, already formed\nyour conclusions from the newspapers,\" he said. \"The case is as\nplain as a pikestaff, and the more one goes into it the plainer\nit becomes. Still, of course, one can't refuse a lady, and such a\nvery positive one, too. She has heard of you, and would have your\nopinion, though I repeatedly told her that there was nothing\nwhich you could do which I had not already done. Why, bless my\nsoul! here is her carriage at the door.\"\n\nHe had hardly spoken before there rushed into the room one of the\nmost lovely young women that I have ever seen in my life. Her\nviolet eyes shining, her lips parted, a pink flush upon her\ncheeks, all thought of her natural reserve lost in her\noverpowering excitement and concern.\n\n\"Oh, Mr. Sherlock Holmes!\" she cried, glancing from one to the\nother of us, and finally, with a woman's quick intuition,\nfastening upon my companion, \"I am so glad that you have come. I\nhave driven down to tell you so. I know that James didn't do it.\nI know it, and I want you to start upon your work knowing it,\ntoo. Never let yourself doubt upon that point. We have known each\nother since we were little children, and I know his faults as no\none else does; but he is too tender-hearted to hurt a fly. Such a\ncharge is absurd to anyone who really knows him.\"\n\n\"I hope we may clear him, Miss Turner,\" said Sherlock Holmes.\n\"You may rely upon my doing all that I can.\"\n\n\"But you have read the evidence. You have formed some conclusion?\nDo you not see some loophole, some flaw? Do you not yourself\nthink that he is innocent?\"\n\n\"I think that it is very probable.\"\n\n\"There, now!\" she cried, throwing back her head and looking\ndefiantly at Lestrade. \"You hear! He gives me hopes.\"\n\nLestrade shrugged his shoulders. \"I am afraid that my colleague\nhas been a little quick in forming his conclusions,\" he said.\n\n\"But he is right. Oh! I know that he is right. James never did\nit. And about his quarrel with his father, I am sure that the\nreason why he would not speak about it to the coroner was because\nI was concerned in it.\"\n\n\"In what way?\" asked Holmes.\n\n\"It is no time for me to hide anything. James and his father had\nmany disagreements about me. Mr. McCarthy was very anxious that\nthere should be a marriage between us. James and I have always\nloved each other as brother and sister; but of course he is young\nand has seen very little of life yet, and--and--well, he\nnaturally did not wish to do anything like that yet. So there\nwere quarrels, and this, I am sure, was one of them.\"\n\n\"And your father?\" asked Holmes. \"Was he in favour of such a\nunion?\"\n\n\"No, he was averse to it also. No one but Mr. McCarthy was in\nfavour of it.\" A quick blush passed over her fresh young face as\nHolmes shot one of his keen, questioning glances at her.\n\n\"Thank you for this information,\" said he. \"May I see your father\nif I call to-morrow?\"\n\n\"I am afraid the doctor won't allow it.\"\n\n\"The doctor?\"\n\n\"Yes, have you not heard? Poor father has never been strong for\nyears back, but this has broken him down completely. He has taken\nto his bed, and Dr. Willows says that he is a wreck and that his\nnervous system is shattered. Mr. McCarthy was the only man alive\nwho had known dad in the old days in Victoria.\"\n\n\"Ha! In Victoria! That is important.\"\n\n\"Yes, at the mines.\"\n\n\"Quite so; at the gold-mines, where, as I understand, Mr. Turner\nmade his money.\"\n\n\"Yes, certainly.\"\n\n\"Thank you, Miss Turner. You have been of material assistance to\nme.\"\n\n\"You will tell me if you have any news to-morrow. No doubt you\nwill go to the prison to see James. Oh, if you do, Mr. Holmes, do\ntell him that I know him to be innocent.\"\n\n\"I will, Miss Turner.\"\n\n\"I must go home now, for dad is very ill, and he misses me so if\nI leave him. Good-bye, and God help you in your undertaking.\" She\nhurried from the room as impulsively as she had entered, and we\nheard the wheels of her carriage rattle off down the street.\n\n\"I am ashamed of you, Holmes,\" said Lestrade with dignity after a\nfew minutes' silence. \"Why should you raise up hopes which you\nare bound to disappoint? I am not over-tender of heart, but I\ncall it cruel.\"\n\n\"I think that I see my way to clearing James McCarthy,\" said\nHolmes. \"Have you an order to see him in prison?\"\n\n\"Yes, but only for you and me.\"\n\n\"Then I shall reconsider my resolution about going out. We have\nstill time to take a train to Hereford and see him to-night?\"\n\n\"Ample.\"\n\n\"Then let us do so. Watson, I fear that you will find it very\nslow, but I shall only be away a couple of hours.\"\n\nI walked down to the station with them, and then wandered through\nthe streets of the little town, finally returning to the hotel,\nwhere I lay upon the sofa and tried to interest myself in a\nyellow-backed novel. The puny plot of the story was so thin,\nhowever, when compared to the deep mystery through which we were\ngroping, and I found my attention wander so continually from the\naction to the fact, that I at last flung it across the room and\ngave myself up entirely to a consideration of the events of the\nday. Supposing that this unhappy young man's story were\nabsolutely true, then what hellish thing, what absolutely\nunforeseen and extraordinary calamity could have occurred between\nthe time when he parted from his father, and the moment when,\ndrawn back by his screams, he rushed into the glade? It was\nsomething terrible and deadly. What could it be? Might not the\nnature of the injuries reveal something to my medical instincts?\nI rang the bell and called for the weekly county paper, which\ncontained a verbatim account of the inquest. In the surgeon's\ndeposition it was stated that the posterior third of the left\nparietal bone and the left half of the occipital bone had been\nshattered by a heavy blow from a blunt weapon. I marked the spot\nupon my own head. Clearly such a blow must have been struck from\nbehind. That was to some extent in favour of the accused, as when\nseen quarrelling he was face to face with his father. Still, it\ndid not go for very much, for the older man might have turned his\nback before the blow fell. Still, it might be worth while to call\nHolmes' attention to it. Then there was the peculiar dying\nreference to a rat. What could that mean? It could not be\ndelirium. A man dying from a sudden blow does not commonly become\ndelirious. No, it was more likely to be an attempt to explain how\nhe met his fate. But what could it indicate? I cudgelled my\nbrains to find some possible explanation. And then the incident\nof the grey cloth seen by young McCarthy. If that were true the\nmurderer must have dropped some part of his dress, presumably his\novercoat, in his flight, and must have had the hardihood to\nreturn and to carry it away at the instant when the son was\nkneeling with his back turned not a dozen paces off. What a\ntissue of mysteries and improbabilities the whole thing was! I\ndid not wonder at Lestrade's opinion, and yet I had so much faith\nin Sherlock Holmes' insight that I could not lose hope as long\nas every fresh fact seemed to strengthen his conviction of young\nMcCarthy's innocence.\n\nIt was late before Sherlock Holmes returned. He came back alone,\nfor Lestrade was staying in lodgings in the town.\n\n\"The glass still keeps very high,\" he remarked as he sat down.\n\"It is of importance that it should not rain before we are able\nto go over the ground. On the other hand, a man should be at his\nvery best and keenest for such nice work as that, and I did not\nwish to do it when fagged by a long journey. I have seen young\nMcCarthy.\"\n\n\"And what did you learn from him?\"\n\n\"Nothing.\"\n\n\"Could he throw no light?\"\n\n\"None at all. I was inclined to think at one time that he knew\nwho had done it and was screening him or her, but I am convinced\nnow that he is as puzzled as everyone else. He is not a very\nquick-witted youth, though comely to look at and, I should think,\nsound at heart.\"\n\n\"I cannot admire his taste,\" I remarked, \"if it is indeed a fact\nthat he was averse to a marriage with so charming a young lady as\nthis Miss Turner.\"\n\n\"Ah, thereby hangs a rather painful tale. This fellow is madly,\ninsanely, in love with her, but some two years ago, when he was\nonly a lad, and before he really knew her, for she had been away\nfive years at a boarding-school, what does the idiot do but get\ninto the clutches of a barmaid in Bristol and marry her at a\nregistry office? No one knows a word of the matter, but you can\nimagine how maddening it must be to him to be upbraided for not\ndoing what he would give his very eyes to do, but what he knows\nto be absolutely impossible. It was sheer frenzy of this sort\nwhich made him throw his hands up into the air when his father,\nat their last interview, was goading him on to propose to Miss\nTurner. On the other hand, he had no means of supporting himself,\nand his father, who was by all accounts a very hard man, would\nhave thrown him over utterly had he known the truth. It was with\nhis barmaid wife that he had spent the last three days in\nBristol, and his father did not know where he was. Mark that\npoint. It is of importance. Good has come out of evil, however,\nfor the barmaid, finding from the papers that he is in serious\ntrouble and likely to be hanged, has thrown him over utterly and\nhas written to him to say that she has a husband already in the\nBermuda Dockyard, so that there is really no tie between them. I\nthink that that bit of news has consoled young McCarthy for all\nthat he has suffered.\"\n\n\"But if he is innocent, who has done it?\"\n\n\"Ah! who? I would call your attention very particularly to two\npoints. One is that the murdered man had an appointment with\nsomeone at the pool, and that the someone could not have been his\nson, for his son was away, and he did not know when he would\nreturn. The second is that the murdered man was heard to cry\n'Cooee!' before he knew that his son had returned. Those are the\ncrucial points upon which the case depends. And now let us talk\nabout George Meredith, if you please, and we shall leave all\nminor matters until to-morrow.\"\n\nThere was no rain, as Holmes had foretold, and the morning broke\nbright and cloudless. At nine o'clock Lestrade called for us with\nthe carriage, and we set off for Hatherley Farm and the Boscombe\nPool.\n\n\"There is serious news this morning,\" Lestrade observed. \"It is\nsaid that Mr. Turner, of the Hall, is so ill that his life is\ndespaired of.\"\n\n\"An elderly man, I presume?\" said Holmes.\n\n\"About sixty; but his constitution has been shattered by his life\nabroad, and he has been in failing health for some time. This\nbusiness has had a very bad effect upon him. He was an old friend\nof McCarthy's, and, I may add, a great benefactor to him, for I\nhave learned that he gave him Hatherley Farm rent free.\"\n\n\"Indeed! That is interesting,\" said Holmes.\n\n\"Oh, yes! In a hundred other ways he has helped him. Everybody\nabout here speaks of his kindness to him.\"\n\n\"Really! Does it not strike you as a little singular that this\nMcCarthy, who appears to have had little of his own, and to have\nbeen under such obligations to Turner, should still talk of\nmarrying his son to Turner's daughter, who is, presumably,\nheiress to the estate, and that in such a very cocksure manner,\nas if it were merely a case of a proposal and all else would\nfollow? It is the more strange, since we know that Turner himself\nwas averse to the idea. The daughter told us as much. Do you not\ndeduce something from that?\"\n\n\"We have got to the deductions and the inferences,\" said\nLestrade, winking at me. \"I find it hard enough to tackle facts,\nHolmes, without flying away after theories and fancies.\"\n\n\"You are right,\" said Holmes demurely; \"you do find it very hard\nto tackle the facts.\"\n\n\"Anyhow, I have grasped one fact which you seem to find it\ndifficult to get hold of,\" replied Lestrade with some warmth.\n\n\"And that is--\"\n\n\"That McCarthy senior met his death from McCarthy junior and that\nall theories to the contrary are the merest moonshine.\"\n\n\"Well, moonshine is a brighter thing than fog,\" said Holmes,\nlaughing. \"But I am very much mistaken if this is not Hatherley\nFarm upon the left.\"\n\n\"Yes, that is it.\" It was a widespread, comfortable-looking\nbuilding, two-storied, slate-roofed, with great yellow blotches\nof lichen upon the grey walls. The drawn blinds and the smokeless\nchimneys, however, gave it a stricken look, as though the weight\nof this horror still lay heavy upon it. We called at the door,\nwhen the maid, at Holmes' request, showed us the boots which her\nmaster wore at the time of his death, and also a pair of the\nson's, though not the pair which he had then had. Having measured\nthese very carefully from seven or eight different points, Holmes\ndesired to be led to the court-yard, from which we all followed\nthe winding track which led to Boscombe Pool.\n\nSherlock Holmes was transformed when he was hot upon such a scent\nas this. Men who had only known the quiet thinker and logician of\nBaker Street would have failed to recognise him. His face flushed\nand darkened. His brows were drawn into two hard black lines,\nwhile his eyes shone out from beneath them with a steely glitter.\nHis face was bent downward, his shoulders bowed, his lips\ncompressed, and the veins stood out like whipcord in his long,\nsinewy neck. His nostrils seemed to dilate with a purely animal\nlust for the chase, and his mind was so absolutely concentrated\nupon the matter before him that a question or remark fell\nunheeded upon his ears, or, at the most, only provoked a quick,\nimpatient snarl in reply. Swiftly and silently he made his way\nalong the track which ran through the meadows, and so by way of\nthe woods to the Boscombe Pool. It was damp, marshy ground, as is\nall that district, and there were marks of many feet, both upon\nthe path and amid the short grass which bounded it on either\nside. Sometimes Holmes would hurry on, sometimes stop dead, and\nonce he made quite a little detour into the meadow. Lestrade and\nI walked behind him, the detective indifferent and contemptuous,\nwhile I watched my friend with the interest which sprang from the\nconviction that every one of his actions was directed towards a\ndefinite end.\n\nThe Boscombe Pool, which is a little reed-girt sheet of water\nsome fifty yards across, is situated at the boundary between the\nHatherley Farm and the private park of the wealthy Mr. Turner.\nAbove the woods which lined it upon the farther side we could see\nthe red, jutting pinnacles which marked the site of the rich\nlandowner's dwelling. On the Hatherley side of the pool the woods\ngrew very thick, and there was a narrow belt of sodden grass\ntwenty paces across between the edge of the trees and the reeds\nwhich lined the lake. Lestrade showed us the exact spot at which\nthe body had been found, and, indeed, so moist was the ground,\nthat I could plainly see the traces which had been left by the\nfall of the stricken man. To Holmes, as I could see by his eager\nface and peering eyes, very many other things were to be read\nupon the trampled grass. He ran round, like a dog who is picking\nup a scent, and then turned upon my companion.\n\n\"What did you go into the pool for?\" he asked.\n\n\"I fished about with a rake. I thought there might be some weapon\nor other trace. But how on earth--\"\n\n\"Oh, tut, tut! I have no time! That left foot of yours with its\ninward twist is all over the place. A mole could trace it, and\nthere it vanishes among the reeds. Oh, how simple it would all\nhave been had I been here before they came like a herd of buffalo\nand wallowed all over it. Here is where the party with the\nlodge-keeper came, and they have covered all tracks for six or\neight feet round the body. But here are three separate tracks of\nthe same feet.\" He drew out a lens and lay down upon his\nwaterproof to have a better view, talking all the time rather to\nhimself than to us. \"These are young McCarthy's feet. Twice he\nwas walking, and once he ran swiftly, so that the soles are\ndeeply marked and the heels hardly visible. That bears out his\nstory. He ran when he saw his father on the ground. Then here are\nthe father's feet as he paced up and down. What is this, then? It\nis the butt-end of the gun as the son stood listening. And this?\nHa, ha! What have we here? Tiptoes! tiptoes! Square, too, quite\nunusual boots! They come, they go, they come again--of course\nthat was for the cloak. Now where did they come from?\" He ran up\nand down, sometimes losing, sometimes finding the track until we\nwere well within the edge of the wood and under the shadow of a\ngreat beech, the largest tree in the neighbourhood. Holmes traced\nhis way to the farther side of this and lay down once more upon\nhis face with a little cry of satisfaction. For a long time he\nremained there, turning over the leaves and dried sticks,\ngathering up what seemed to me to be dust into an envelope and\nexamining with his lens not only the ground but even the bark of\nthe tree as far as he could reach. A jagged stone was lying among\nthe moss, and this also he carefully examined and retained. Then\nhe followed a pathway through the wood until he came to the\nhighroad, where all traces were lost.\n\n\"It has been a case of considerable interest,\" he remarked,\nreturning to his natural manner. \"I fancy that this grey house on\nthe right must be the lodge. I think that I will go in and have a\nword with Moran, and perhaps write a little note. Having done\nthat, we may drive back to our luncheon. You may walk to the cab,\nand I shall be with you presently.\"\n\nIt was about ten minutes before we regained our cab and drove\nback into Ross, Holmes still carrying with him the stone which he\nhad picked up in the wood.\n\n\"This may interest you, Lestrade,\" he remarked, holding it out.\n\"The murder was done with it.\"\n\n\"I see no marks.\"\n\n\"There are none.\"\n\n\"How do you know, then?\"\n\n\"The grass was growing under it. It had only lain there a few\ndays. There was no sign of a place whence it had been taken. It\ncorresponds with the injuries. There is no sign of any other\nweapon.\"\n\n\"And the murderer?\"\n\n\"Is a tall man, left-handed, limps with the right leg, wears\nthick-soled shooting-boots and a grey cloak, smokes Indian\ncigars, uses a cigar-holder, and carries a blunt pen-knife in his\npocket. There are several other indications, but these may be\nenough to aid us in our search.\"\n\nLestrade laughed. \"I am afraid that I am still a sceptic,\" he\nsaid. \"Theories are all very well, but we have to deal with a\nhard-headed British jury.\"\n\n\"Nous verrons,\" answered Holmes calmly. \"You work your own\nmethod, and I shall work mine. I shall be busy this afternoon,\nand shall probably return to London by the evening train.\"\n\n\"And leave your case unfinished?\"\n\n\"No, finished.\"\n\n\"But the mystery?\"\n\n\"It is solved.\"\n\n\"Who was the criminal, then?\"\n\n\"The gentleman I describe.\"\n\n\"But who is he?\"\n\n\"Surely it would not be difficult to find out. This is not such a\npopulous neighbourhood.\"\n\nLestrade shrugged his shoulders. \"I am a practical man,\" he said,\n\"and I really cannot undertake to go about the country looking\nfor a left-handed gentleman with a game leg. I should become the\nlaughing-stock of Scotland Yard.\"\n\n\"All right,\" said Holmes quietly. \"I have given you the chance.\nHere are your lodgings. Good-bye. I shall drop you a line before\nI leave.\"\n\nHaving left Lestrade at his rooms, we drove to our hotel, where\nwe found lunch upon the table. Holmes was silent and buried in\nthought with a pained expression upon his face, as one who finds\nhimself in a perplexing position.\n\n\"Look here, Watson,\" he said when the cloth was cleared \"just sit\ndown in this chair and let me preach to you for a little. I don't\nknow quite what to do, and I should value your advice. Light a\ncigar and let me expound.\"\n\n \"Pray do so.\"\n\n\"Well, now, in considering this case there are two points about\nyoung McCarthy's narrative which struck us both instantly,\nalthough they impressed me in his favour and you against him. One\nwas the fact that his father should, according to his account,\ncry 'Cooee!' before seeing him. The other was his singular dying\nreference to a rat. He mumbled several words, you understand, but\nthat was all that caught the son's ear. Now from this double\npoint our research must commence, and we will begin it by\npresuming that what the lad says is absolutely true.\"\n\n\"What of this 'Cooee!' then?\"\n\n\"Well, obviously it could not have been meant for the son. The\nson, as far as he knew, was in Bristol. It was mere chance that\nhe was within earshot. The 'Cooee!' was meant to attract the\nattention of whoever it was that he had the appointment with. But\n'Cooee' is a distinctly Australian cry, and one which is used\nbetween Australians. There is a strong presumption that the\nperson whom McCarthy expected to meet him at Boscombe Pool was\nsomeone who had been in Australia.\"\n\n\"What of the rat, then?\"\n\nSherlock Holmes took a folded paper from his pocket and flattened\nit out on the table. \"This is a map of the Colony of Victoria,\"\nhe said. \"I wired to Bristol for it last night.\" He put his hand\nover part of the map. \"What do you read?\"\n\n\"ARAT,\" I read.\n\n\"And now?\" He raised his hand.\n\n\"BALLARAT.\"\n\n\"Quite so. That was the word the man uttered, and of which his\nson only caught the last two syllables. He was trying to utter\nthe name of his murderer. So and so, of Ballarat.\"\n\n\"It is wonderful!\" I exclaimed.\n\n\"It is obvious. And now, you see, I had narrowed the field down\nconsiderably. The possession of a grey garment was a third point\nwhich, granting the son's statement to be correct, was a\ncertainty. We have come now out of mere vagueness to the definite\nconception of an Australian from Ballarat with a grey cloak.\"\n\n\"Certainly.\"\n\n\"And one who was at home in the district, for the pool can only\nbe approached by the farm or by the estate, where strangers could\nhardly wander.\"\n\n\"Quite so.\"\n\n\"Then comes our expedition of to-day. By an examination of the\nground I gained the trifling details which I gave to that\nimbecile Lestrade, as to the personality of the criminal.\"\n\n\"But how did you gain them?\"\n\n\"You know my method. It is founded upon the observation of\ntrifles.\"\n\n\"His height I know that you might roughly judge from the length\nof his stride. His boots, too, might be told from their traces.\"\n\n\"Yes, they were peculiar boots.\"\n\n\"But his lameness?\"\n\n\"The impression of his right foot was always less distinct than\nhis left. He put less weight upon it. Why? Because he limped--he\nwas lame.\"\n\n\"But his left-handedness.\"\n\n\"You were yourself struck by the nature of the injury as recorded\nby the surgeon at the inquest. The blow was struck from\nimmediately behind, and yet was upon the left side. Now, how can\nthat be unless it were by a left-handed man? He had stood behind\nthat tree during the interview between the father and son. He had\neven smoked there. I found the ash of a cigar, which my special\nknowledge of tobacco ashes enables me to pronounce as an Indian\ncigar. I have, as you know, devoted some attention to this, and\nwritten a little monograph on the ashes of 140 different\nvarieties of pipe, cigar, and cigarette tobacco. Having found the\nash, I then looked round and discovered the stump among the moss\nwhere he had tossed it. It was an Indian cigar, of the variety\nwhich are rolled in Rotterdam.\"\n\n\"And the cigar-holder?\"\n\n\"I could see that the end had not been in his mouth. Therefore he\nused a holder. The tip had been cut off, not bitten off, but the\ncut was not a clean one, so I deduced a blunt pen-knife.\"\n\n\"Holmes,\" I said, \"you have drawn a net round this man from which\nhe cannot escape, and you have saved an innocent human life as\ntruly as if you had cut the cord which was hanging him. I see the\ndirection in which all this points. The culprit is--\"\n\n\"Mr. John Turner,\" cried the hotel waiter, opening the door of\nour sitting-room, and ushering in a visitor.\n\nThe man who entered was a strange and impressive figure. His\nslow, limping step and bowed shoulders gave the appearance of\ndecrepitude, and yet his hard, deep-lined, craggy features, and\nhis enormous limbs showed that he was possessed of unusual\nstrength of body and of character. His tangled beard, grizzled\nhair, and outstanding, drooping eyebrows combined to give an air\nof dignity and power to his appearance, but his face was of an\nashen white, while his lips and the corners of his nostrils were\ntinged with a shade of blue. It was clear to me at a glance that\nhe was in the grip of some deadly and chronic disease.\n\n\"Pray sit down on the sofa,\" said Holmes gently. \"You had my\nnote?\"\n\n\"Yes, the lodge-keeper brought it up. You said that you wished to\nsee me here to avoid scandal.\"\n\n\"I thought people would talk if I went to the Hall.\"\n\n\"And why did you wish to see me?\" He looked across at my\ncompanion with despair in his weary eyes, as though his question\nwas already answered.\n\n\"Yes,\" said Holmes, answering the look rather than the words. \"It\nis so. I know all about McCarthy.\"\n\nThe old man sank his face in his hands. \"God help me!\" he cried.\n\"But I would not have let the young man come to harm. I give you\nmy word that I would have spoken out if it went against him at\nthe Assizes.\"\n\n\"I am glad to hear you say so,\" said Holmes gravely.\n\n\"I would have spoken now had it not been for my dear girl. It\nwould break her heart--it will break her heart when she hears\nthat I am arrested.\"\n\n\"It may not come to that,\" said Holmes.\n\n\"What?\"\n\n\"I am no official agent. I understand that it was your daughter\nwho required my presence here, and I am acting in her interests.\nYoung McCarthy must be got off, however.\"\n\n\"I am a dying man,\" said old Turner. \"I have had diabetes for\nyears. My doctor says it is a question whether I shall live a\nmonth. Yet I would rather die under my own roof than in a gaol.\"\n\nHolmes rose and sat down at the table with his pen in his hand\nand a bundle of paper before him. \"Just tell us the truth,\" he\nsaid. \"I shall jot down the facts. You will sign it, and Watson\nhere can witness it. Then I could produce your confession at the\nlast extremity to save young McCarthy. I promise you that I shall\nnot use it unless it is absolutely needed.\"\n\n\"It's as well,\" said the old man; \"it's a question whether I\nshall live to the Assizes, so it matters little to me, but I\nshould wish to spare Alice the shock. And now I will make the\nthing clear to you; it has been a long time in the acting, but\nwill not take me long to tell.\n\n\"You didn't know this dead man, McCarthy. He was a devil\nincarnate. I tell you that. God keep you out of the clutches of\nsuch a man as he. His grip has been upon me these twenty years,\nand he has blasted my life. I'll tell you first how I came to be\nin his power.\n\n\"It was in the early '60's at the diggings. I was a young chap\nthen, hot-blooded and reckless, ready to turn my hand at\nanything; I got among bad companions, took to drink, had no luck\nwith my claim, took to the bush, and in a word became what you\nwould call over here a highway robber. There were six of us, and\nwe had a wild, free life of it, sticking up a station from time\nto time, or stopping the wagons on the road to the diggings.\nBlack Jack of Ballarat was the name I went under, and our party\nis still remembered in the colony as the Ballarat Gang.\n\n\"One day a gold convoy came down from Ballarat to Melbourne, and\nwe lay in wait for it and attacked it. There were six troopers\nand six of us, so it was a close thing, but we emptied four of\ntheir saddles at the first volley. Three of our boys were killed,\nhowever, before we got the swag. I put my pistol to the head of\nthe wagon-driver, who was this very man McCarthy. I wish to the\nLord that I had shot him then, but I spared him, though I saw his\nwicked little eyes fixed on my face, as though to remember every\nfeature. We got away with the gold, became wealthy men, and made\nour way over to England without being suspected. There I parted\nfrom my old pals and determined to settle down to a quiet and\nrespectable life. I bought this estate, which chanced to be in\nthe market, and I set myself to do a little good with my money,\nto make up for the way in which I had earned it. I married, too,\nand though my wife died young she left me my dear little Alice.\nEven when she was just a baby her wee hand seemed to lead me down\nthe right path as nothing else had ever done. In a word, I turned\nover a new leaf and did my best to make up for the past. All was\ngoing well when McCarthy laid his grip upon me.\n\n\"I had gone up to town about an investment, and I met him in\nRegent Street with hardly a coat to his back or a boot to his\nfoot.\n\n\"'Here we are, Jack,' says he, touching me on the arm; 'we'll be\nas good as a family to you. There's two of us, me and my son, and\nyou can have the keeping of us. If you don't--it's a fine,\nlaw-abiding country is England, and there's always a policeman\nwithin hail.'\n\n\"Well, down they came to the west country, there was no shaking\nthem off, and there they have lived rent free on my best land\never since. There was no rest for me, no peace, no forgetfulness;\nturn where I would, there was his cunning, grinning face at my\nelbow. It grew worse as Alice grew up, for he soon saw I was more\nafraid of her knowing my past than of the police. Whatever he\nwanted he must have, and whatever it was I gave him without\nquestion, land, money, houses, until at last he asked a thing\nwhich I could not give. He asked for Alice.\n\n\"His son, you see, had grown up, and so had my girl, and as I was\nknown to be in weak health, it seemed a fine stroke to him that\nhis lad should step into the whole property. But there I was\nfirm. I would not have his cursed stock mixed with mine; not that\nI had any dislike to the lad, but his blood was in him, and that\nwas enough. I stood firm. McCarthy threatened. I braved him to do\nhis worst. We were to meet at the pool midway between our houses\nto talk it over.\n\n\"When I went down there I found him talking with his son, so I\nsmoked a cigar and waited behind a tree until he should be alone.\nBut as I listened to his talk all that was black and bitter in\nme seemed to come uppermost. He was urging his son to marry my\ndaughter with as little regard for what she might think as if she\nwere a slut from off the streets. It drove me mad to think that I\nand all that I held most dear should be in the power of such a\nman as this. Could I not snap the bond? I was already a dying and\na desperate man. Though clear of mind and fairly strong of limb,\nI knew that my own fate was sealed. But my memory and my girl!\nBoth could be saved if I could but silence that foul tongue. I\ndid it, Mr. Holmes. I would do it again. Deeply as I have sinned,\nI have led a life of martyrdom to atone for it. But that my girl\nshould be entangled in the same meshes which held me was more\nthan I could suffer. I struck him down with no more compunction\nthan if he had been some foul and venomous beast. His cry brought\nback his son; but I had gained the cover of the wood, though I\nwas forced to go back to fetch the cloak which I had dropped in\nmy flight. That is the true story, gentlemen, of all that\noccurred.\"\n\n\"Well, it is not for me to judge you,\" said Holmes as the old man\nsigned the statement which had been drawn out. \"I pray that we\nmay never be exposed to such a temptation.\"\n\n\"I pray not, sir. And what do you intend to do?\"\n\n\"In view of your health, nothing. You are yourself aware that you\nwill soon have to answer for your deed at a higher court than the\nAssizes. I will keep your confession, and if McCarthy is\ncondemned I shall be forced to use it. If not, it shall never be\nseen by mortal eye; and your secret, whether you be alive or\ndead, shall be safe with us.\"\n\n\"Farewell, then,\" said the old man solemnly. \"Your own deathbeds,\nwhen they come, will be the easier for the thought of the peace\nwhich you have given to mine.\" Tottering and shaking in all his\ngiant frame, he stumbled slowly from the room.\n\n\"God help us!\" said Holmes after a long silence. \"Why does fate\nplay such tricks with poor, helpless worms? I never hear of such\na case as this that I do not think of Baxter's words, and say,\n'There, but for the grace of God, goes Sherlock Holmes.'\"\n\nJames McCarthy was acquitted at the Assizes on the strength of a\nnumber of objections which had been drawn out by Holmes and\nsubmitted to the defending counsel. Old Turner lived for seven\nmonths after our interview, but he is now dead; and there is\nevery prospect that the son and daughter may come to live happily\ntogether in ignorance of the black cloud which rests upon their\npast.\n\n\n\nADVENTURE V. THE FIVE ORANGE PIPS\n\nWhen I glance over my notes and records of the Sherlock Holmes\ncases between the years '82 and '90, I am faced by so many which\npresent strange and interesting features that it is no easy\nmatter to know which to choose and which to leave. Some, however,\nhave already gained publicity through the papers, and others have\nnot offered a field for those peculiar qualities which my friend\npossessed in so high a degree, and which it is the object of\nthese papers to illustrate. Some, too, have baffled his\nanalytical skill, and would be, as narratives, beginnings without\nan ending, while others have been but partially cleared up, and\nhave their explanations founded rather upon conjecture and\nsurmise than on that absolute logical proof which was so dear to\nhim. There is, however, one of these last which was so remarkable\nin its details and so startling in its results that I am tempted\nto give some account of it in spite of the fact that there are\npoints in connection with it which never have been, and probably\nnever will be, entirely cleared up.\n\nThe year '87 furnished us with a long series of cases of greater\nor less interest, of which I retain the records. Among my\nheadings under this one twelve months I find an account of the\nadventure of the Paradol Chamber, of the Amateur Mendicant\nSociety, who held a luxurious club in the lower vault of a\nfurniture warehouse, of the facts connected with the loss of the\nBritish barque \"Sophy Anderson\", of the singular adventures of the\nGrice Patersons in the island of Uffa, and finally of the\nCamberwell poisoning case. In the latter, as may be remembered,\nSherlock Holmes was able, by winding up the dead man's watch, to\nprove that it had been wound up two hours before, and that\ntherefore the deceased had gone to bed within that time--a\ndeduction which was of the greatest importance in clearing up the\ncase. All these I may sketch out at some future date, but none of\nthem present such singular features as the strange train of\ncircumstances which I have now taken up my pen to describe.\n\nIt was in the latter days of September, and the equinoctial gales\nhad set in with exceptional violence. All day the wind had\nscreamed and the rain had beaten against the windows, so that\neven here in the heart of great, hand-made London we were forced\nto raise our minds for the instant from the routine of life and\nto recognise the presence of those great elemental forces which\nshriek at mankind through the bars of his civilisation, like\nuntamed beasts in a cage. As evening drew in, the storm grew\nhigher and louder, and the wind cried and sobbed like a child in\nthe chimney. Sherlock Holmes sat moodily at one side of the\nfireplace cross-indexing his records of crime, while I at the\nother was deep in one of Clark Russell's fine sea-stories until\nthe howl of the gale from without seemed to blend with the text,\nand the splash of the rain to lengthen out into the long swash of\nthe sea waves. My wife was on a visit to her mother's, and for a\nfew days I was a dweller once more in my old quarters at Baker\nStreet.\n\n\"Why,\" said I, glancing up at my companion, \"that was surely the\nbell. Who could come to-night? Some friend of yours, perhaps?\"\n\n\"Except yourself I have none,\" he answered. \"I do not encourage\nvisitors.\"\n\n\"A client, then?\"\n\n\"If so, it is a serious case. Nothing less would bring a man out\non such a day and at such an hour. But I take it that it is more\nlikely to be some crony of the landlady's.\"\n\nSherlock Holmes was wrong in his conjecture, however, for there\ncame a step in the passage and a tapping at the door. He\nstretched out his long arm to turn the lamp away from himself and\ntowards the vacant chair upon which a newcomer must sit.\n\n\"Come in!\" said he.\n\nThe man who entered was young, some two-and-twenty at the\noutside, well-groomed and trimly clad, with something of\nrefinement and delicacy in his bearing. The streaming umbrella\nwhich he held in his hand, and his long shining waterproof told\nof the fierce weather through which he had come. He looked about\nhim anxiously in the glare of the lamp, and I could see that his\nface was pale and his eyes heavy, like those of a man who is\nweighed down with some great anxiety.\n\n\"I owe you an apology,\" he said, raising his golden pince-nez to\nhis eyes. \"I trust that I am not intruding. I fear that I have\nbrought some traces of the storm and rain into your snug\nchamber.\"\n\n\"Give me your coat and umbrella,\" said Holmes. \"They may rest\nhere on the hook and will be dry presently. You have come up from\nthe south-west, I see.\"\n\n\"Yes, from Horsham.\"\n\n\"That clay and chalk mixture which I see upon your toe caps is\nquite distinctive.\"\n\n\"I have come for advice.\"\n\n\"That is easily got.\"\n\n\"And help.\"\n\n\"That is not always so easy.\"\n\n\"I have heard of you, Mr. Holmes. I heard from Major Prendergast\nhow you saved him in the Tankerville Club scandal.\"\n\n\"Ah, of course. He was wrongfully accused of cheating at cards.\"\n\n\"He said that you could solve anything.\"\n\n\"He said too much.\"\n\n\"That you are never beaten.\"\n\n\"I have been beaten four times--three times by men, and once by a\nwoman.\"\n\n\"But what is that compared with the number of your successes?\"\n\n\"It is true that I have been generally successful.\"\n\n\"Then you may be so with me.\"\n\n\"I beg that you will draw your chair up to the fire and favour me\nwith some details as to your case.\"\n\n\"It is no ordinary one.\"\n\n\"None of those which come to me are. I am the last court of\nappeal.\"\n\n\"And yet I question, sir, whether, in all your experience, you\nhave ever listened to a more mysterious and inexplicable chain of\nevents than those which have happened in my own family.\"\n\n\"You fill me with interest,\" said Holmes. \"Pray give us the\nessential facts from the commencement, and I can afterwards\nquestion you as to those details which seem to me to be most\nimportant.\"\n\nThe young man pulled his chair up and pushed his wet feet out\ntowards the blaze.\n\n\"My name,\" said he, \"is John Openshaw, but my own affairs have,\nas far as I can understand, little to do with this awful\nbusiness. It is a hereditary matter; so in order to give you an\nidea of the facts, I must go back to the commencement of the\naffair.\n\n\"You must know that my grandfather had two sons--my uncle Elias\nand my father Joseph. My father had a small factory at Coventry,\nwhich he enlarged at the time of the invention of bicycling. He\nwas a patentee of the Openshaw unbreakable tire, and his business\nmet with such success that he was able to sell it and to retire\nupon a handsome competence.\n\n\"My uncle Elias emigrated to America when he was a young man and\nbecame a planter in Florida, where he was reported to have done\nvery well. At the time of the war he fought in Jackson's army,\nand afterwards under Hood, where he rose to be a colonel. When\nLee laid down his arms my uncle returned to his plantation, where\nhe remained for three or four years. About 1869 or 1870 he came\nback to Europe and took a small estate in Sussex, near Horsham.\nHe had made a very considerable fortune in the States, and his\nreason for leaving them was his aversion to the negroes, and his\ndislike of the Republican policy in extending the franchise to\nthem. He was a singular man, fierce and quick-tempered, very\nfoul-mouthed when he was angry, and of a most retiring\ndisposition. During all the years that he lived at Horsham, I\ndoubt if ever he set foot in the town. He had a garden and two or\nthree fields round his house, and there he would take his\nexercise, though very often for weeks on end he would never leave\nhis room. He drank a great deal of brandy and smoked very\nheavily, but he would see no society and did not want any\nfriends, not even his own brother.\n\n\"He didn't mind me; in fact, he took a fancy to me, for at the\ntime when he saw me first I was a youngster of twelve or so. This\nwould be in the year 1878, after he had been eight or nine years\nin England. He begged my father to let me live with him and he\nwas very kind to me in his way. When he was sober he used to be\nfond of playing backgammon and draughts with me, and he would\nmake me his representative both with the servants and with the\ntradespeople, so that by the time that I was sixteen I was quite\nmaster of the house. I kept all the keys and could go where I\nliked and do what I liked, so long as I did not disturb him in\nhis privacy. There was one singular exception, however, for he\nhad a single room, a lumber-room up among the attics, which was\ninvariably locked, and which he would never permit either me or\nanyone else to enter. With a boy's curiosity I have peeped\nthrough the keyhole, but I was never able to see more than such a\ncollection of old trunks and bundles as would be expected in such\na room.\n\n\"One day--it was in March, 1883--a letter with a foreign stamp\nlay upon the table in front of the colonel's plate. It was not a\ncommon thing for him to receive letters, for his bills were all\npaid in ready money, and he had no friends of any sort. 'From\nIndia!' said he as he took it up, 'Pondicherry postmark! What can\nthis be?' Opening it hurriedly, out there jumped five little\ndried orange pips, which pattered down upon his plate. I began to\nlaugh at this, but the laugh was struck from my lips at the sight\nof his face. His lip had fallen, his eyes were protruding, his\nskin the colour of putty, and he glared at the envelope which he\nstill held in his trembling hand, 'K. K. K.!' he shrieked, and\nthen, 'My God, my God, my sins have overtaken me!'\n\n\"'What is it, uncle?' I cried.\n\n\"'Death,' said he, and rising from the table he retired to his\nroom, leaving me palpitating with horror. I took up the envelope\nand saw scrawled in red ink upon the inner flap, just above the\ngum, the letter K three times repeated. There was nothing else\nsave the five dried pips. What could be the reason of his\noverpowering terror? I left the breakfast-table, and as I\nascended the stair I met him coming down with an old rusty key,\nwhich must have belonged to the attic, in one hand, and a small\nbrass box, like a cashbox, in the other.\n\n\"'They may do what they like, but I'll checkmate them still,'\nsaid he with an oath. 'Tell Mary that I shall want a fire in my\nroom to-day, and send down to Fordham, the Horsham lawyer.'\n\n\"I did as he ordered, and when the lawyer arrived I was asked to\nstep up to the room. The fire was burning brightly, and in the\ngrate there was a mass of black, fluffy ashes, as of burned\npaper, while the brass box stood open and empty beside it. As I\nglanced at the box I noticed, with a start, that upon the lid was\nprinted the treble K which I had read in the morning upon the\nenvelope.\n\n\"'I wish you, John,' said my uncle, 'to witness my will. I leave\nmy estate, with all its advantages and all its disadvantages, to\nmy brother, your father, whence it will, no doubt, descend to\nyou. If you can enjoy it in peace, well and good! If you find you\ncannot, take my advice, my boy, and leave it to your deadliest\nenemy. I am sorry to give you such a two-edged thing, but I can't\nsay what turn things are going to take. Kindly sign the paper\nwhere Mr. Fordham shows you.'\n\n\"I signed the paper as directed, and the lawyer took it away with\nhim. The singular incident made, as you may think, the deepest\nimpression upon me, and I pondered over it and turned it every\nway in my mind without being able to make anything of it. Yet I\ncould not shake off the vague feeling of dread which it left\nbehind, though the sensation grew less keen as the weeks passed\nand nothing happened to disturb the usual routine of our lives. I\ncould see a change in my uncle, however. He drank more than ever,\nand he was less inclined for any sort of society. Most of his\ntime he would spend in his room, with the door locked upon the\ninside, but sometimes he would emerge in a sort of drunken frenzy\nand would burst out of the house and tear about the garden with a\nrevolver in his hand, screaming out that he was afraid of no man,\nand that he was not to be cooped up, like a sheep in a pen, by\nman or devil. When these hot fits were over, however, he would\nrush tumultuously in at the door and lock and bar it behind him,\nlike a man who can brazen it out no longer against the terror\nwhich lies at the roots of his soul. At such times I have seen\nhis face, even on a cold day, glisten with moisture, as though it\nwere new raised from a basin.\n\n\"Well, to come to an end of the matter, Mr. Holmes, and not to\nabuse your patience, there came a night when he made one of those\ndrunken sallies from which he never came back. We found him, when\nwe went to search for him, face downward in a little\ngreen-scummed pool, which lay at the foot of the garden. There\nwas no sign of any violence, and the water was but two feet deep,\nso that the jury, having regard to his known eccentricity,\nbrought in a verdict of 'suicide.' But I, who knew how he winced\nfrom the very thought of death, had much ado to persuade myself\nthat he had gone out of his way to meet it. The matter passed,\nhowever, and my father entered into possession of the estate, and\nof some 14,000 pounds, which lay to his credit at the bank.\"\n\n\"One moment,\" Holmes interposed, \"your statement is, I foresee,\none of the most remarkable to which I have ever listened. Let me\nhave the date of the reception by your uncle of the letter, and\nthe date of his supposed suicide.\"\n\n\"The letter arrived on March 10, 1883. His death was seven weeks\nlater, upon the night of May 2nd.\"\n\n\"Thank you. Pray proceed.\"\n\n\"When my father took over the Horsham property, he, at my\nrequest, made a careful examination of the attic, which had been\nalways locked up. We found the brass box there, although its\ncontents had been destroyed. On the inside of the cover was a\npaper label, with the initials of K. K. K. repeated upon it, and\n'Letters, memoranda, receipts, and a register' written beneath.\nThese, we presume, indicated the nature of the papers which had\nbeen destroyed by Colonel Openshaw. For the rest, there was\nnothing of much importance in the attic save a great many\nscattered papers and note-books bearing upon my uncle's life in\nAmerica. Some of them were of the war time and showed that he had\ndone his duty well and had borne the repute of a brave soldier.\nOthers were of a date during the reconstruction of the Southern\nstates, and were mostly concerned with politics, for he had\nevidently taken a strong part in opposing the carpet-bag\npoliticians who had been sent down from the North.\n\n\"Well, it was the beginning of '84 when my father came to live at\nHorsham, and all went as well as possible with us until the\nJanuary of '85. On the fourth day after the new year I heard my\nfather give a sharp cry of surprise as we sat together at the\nbreakfast-table. There he was, sitting with a newly opened\nenvelope in one hand and five dried orange pips in the\noutstretched palm of the other one. He had always laughed at what\nhe called my cock-and-bull story about the colonel, but he looked\nvery scared and puzzled now that the same thing had come upon\nhimself.\n\n\"'Why, what on earth does this mean, John?' he stammered.\n\n\"My heart had turned to lead. 'It is K. K. K.,' said I.\n\n\"He looked inside the envelope. 'So it is,' he cried. 'Here are\nthe very letters. But what is this written above them?'\n\n\"'Put the papers on the sundial,' I read, peeping over his\nshoulder.\n\n\"'What papers? What sundial?' he asked.\n\n\"'The sundial in the garden. There is no other,' said I; 'but the\npapers must be those that are destroyed.'\n\n\"'Pooh!' said he, gripping hard at his courage. 'We are in a\ncivilised land here, and we can't have tomfoolery of this kind.\nWhere does the thing come from?'\n\n\"'From Dundee,' I answered, glancing at the postmark.\n\n\"'Some preposterous practical joke,' said he. 'What have I to do\nwith sundials and papers? I shall take no notice of such\nnonsense.'\n\n\"'I should certainly speak to the police,' I said.\n\n\"'And be laughed at for my pains. Nothing of the sort.'\n\n\"'Then let me do so?'\n\n\"'No, I forbid you. I won't have a fuss made about such\nnonsense.'\n\n\"It was in vain to argue with him, for he was a very obstinate\nman. I went about, however, with a heart which was full of\nforebodings.\n\n\"On the third day after the coming of the letter my father went\nfrom home to visit an old friend of his, Major Freebody, who is\nin command of one of the forts upon Portsdown Hill. I was glad\nthat he should go, for it seemed to me that he was farther from\ndanger when he was away from home. In that, however, I was in\nerror. Upon the second day of his absence I received a telegram\nfrom the major, imploring me to come at once. My father had\nfallen over one of the deep chalk-pits which abound in the\nneighbourhood, and was lying senseless, with a shattered skull. I\nhurried to him, but he passed away without having ever recovered\nhis consciousness. He had, as it appears, been returning from\nFareham in the twilight, and as the country was unknown to him,\nand the chalk-pit unfenced, the jury had no hesitation in\nbringing in a verdict of 'death from accidental causes.'\nCarefully as I examined every fact connected with his death, I\nwas unable to find anything which could suggest the idea of\nmurder. There were no signs of violence, no footmarks, no\nrobbery, no record of strangers having been seen upon the roads.\nAnd yet I need not tell you that my mind was far from at ease,\nand that I was well-nigh certain that some foul plot had been\nwoven round him.\n\n\"In this sinister way I came into my inheritance. You will ask me\nwhy I did not dispose of it? I answer, because I was well\nconvinced that our troubles were in some way dependent upon an\nincident in my uncle's life, and that the danger would be as\npressing in one house as in another.\n\n\"It was in January, '85, that my poor father met his end, and two\nyears and eight months have elapsed since then. During that time\nI have lived happily at Horsham, and I had begun to hope that\nthis curse had passed away from the family, and that it had ended\nwith the last generation. I had begun to take comfort too soon,\nhowever; yesterday morning the blow fell in the very shape in\nwhich it had come upon my father.\"\n\nThe young man took from his waistcoat a crumpled envelope, and\nturning to the table he shook out upon it five little dried\norange pips.\n\n\"This is the envelope,\" he continued. \"The postmark is\nLondon--eastern division. Within are the very words which were\nupon my father's last message: 'K. K. K.'; and then 'Put the\npapers on the sundial.'\"\n\n\"What have you done?\" asked Holmes.\n\n\"Nothing.\"\n\n\"Nothing?\"\n\n\"To tell the truth\"--he sank his face into his thin, white\nhands--\"I have felt helpless. I have felt like one of those poor\nrabbits when the snake is writhing towards it. I seem to be in\nthe grasp of some resistless, inexorable evil, which no foresight\nand no precautions can guard against.\"\n\n\"Tut! tut!\" cried Sherlock Holmes. \"You must act, man, or you are\nlost. Nothing but energy can save you. This is no time for\ndespair.\"\n\n\"I have seen the police.\"\n\n\"Ah!\"\n\n\"But they listened to my story with a smile. I am convinced that\nthe inspector has formed the opinion that the letters are all\npractical jokes, and that the deaths of my relations were really\naccidents, as the jury stated, and were not to be connected with\nthe warnings.\"\n\nHolmes shook his clenched hands in the air. \"Incredible\nimbecility!\" he cried.\n\n\"They have, however, allowed me a policeman, who may remain in\nthe house with me.\"\n\n\"Has he come with you to-night?\"\n\n\"No. His orders were to stay in the house.\"\n\nAgain Holmes raved in the air.\n\n\"Why did you come to me,\" he cried, \"and, above all, why did you\nnot come at once?\"\n\n\"I did not know. It was only to-day that I spoke to Major\nPrendergast about my troubles and was advised by him to come to\nyou.\"\n\n\"It is really two days since you had the letter. We should have\nacted before this. You have no further evidence, I suppose, than\nthat which you have placed before us--no suggestive detail which\nmight help us?\"\n\n\"There is one thing,\" said John Openshaw. He rummaged in his coat\npocket, and, drawing out a piece of discoloured, blue-tinted\npaper, he laid it out upon the table. \"I have some remembrance,\"\nsaid he, \"that on the day when my uncle burned the papers I\nobserved that the small, unburned margins which lay amid the\nashes were of this particular colour. I found this single sheet\nupon the floor of his room, and I am inclined to think that it\nmay be one of the papers which has, perhaps, fluttered out from\namong the others, and in that way has escaped destruction. Beyond\nthe mention of pips, I do not see that it helps us much. I think\nmyself that it is a page from some private diary. The writing is\nundoubtedly my uncle's.\"\n\nHolmes moved the lamp, and we both bent over the sheet of paper,\nwhich showed by its ragged edge that it had indeed been torn from\na book. It was headed, \"March, 1869,\" and beneath were the\nfollowing enigmatical notices:\n\n\"4th. Hudson came. Same old platform.\n\n\"7th. Set the pips on McCauley, Paramore, and\n      John Swain, of St. Augustine.\n\n\"9th. McCauley cleared.\n\n\"10th. John Swain cleared.\n\n\"12th. Visited Paramore. All well.\"\n\n\"Thank you!\" said Holmes, folding up the paper and returning it\nto our visitor. \"And now you must on no account lose another\ninstant. We cannot spare time even to discuss what you have told\nme. You must get home instantly and act.\"\n\n\"What shall I do?\"\n\n\"There is but one thing to do. It must be done at once. You must\nput this piece of paper which you have shown us into the brass\nbox which you have described. You must also put in a note to say\nthat all the other papers were burned by your uncle, and that\nthis is the only one which remains. You must assert that in such\nwords as will carry conviction with them. Having done this, you\nmust at once put the box out upon the sundial, as directed. Do\nyou understand?\"\n\n\"Entirely.\"\n\n\"Do not think of revenge, or anything of the sort, at present. I\nthink that we may gain that by means of the law; but we have our\nweb to weave, while theirs is already woven. The first\nconsideration is to remove the pressing danger which threatens\nyou. The second is to clear up the mystery and to punish the\nguilty parties.\"\n\n\"I thank you,\" said the young man, rising and pulling on his\novercoat. \"You have given me fresh life and hope. I shall\ncertainly do as you advise.\"\n\n\"Do not lose an instant. And, above all, take care of yourself in\nthe meanwhile, for I do not think that there can be a doubt that\nyou are threatened by a very real and imminent danger. How do you\ngo back?\"\n\n\"By train from Waterloo.\"\n\n\"It is not yet nine. The streets will be crowded, so I trust that\nyou may be in safety. And yet you cannot guard yourself too\nclosely.\"\n\n\"I am armed.\"\n\n\"That is well. To-morrow I shall set to work upon your case.\"\n\n\"I shall see you at Horsham, then?\"\n\n\"No, your secret lies in London. It is there that I shall seek\nit.\"\n\n\"Then I shall call upon you in a day, or in two days, with news\nas to the box and the papers. I shall take your advice in every\nparticular.\" He shook hands with us and took his leave. Outside\nthe wind still screamed and the rain splashed and pattered\nagainst the windows. This strange, wild story seemed to have come\nto us from amid the mad elements--blown in upon us like a sheet\nof sea-weed in a gale--and now to have been reabsorbed by them\nonce more.\n\nSherlock Holmes sat for some time in silence, with his head sunk\nforward and his eyes bent upon the red glow of the fire. Then he\nlit his pipe, and leaning back in his chair he watched the blue\nsmoke-rings as they chased each other up to the ceiling.\n\n\"I think, Watson,\" he remarked at last, \"that of all our cases we\nhave had none more fantastic than this.\"\n\n\"Save, perhaps, the Sign of Four.\"\n\n\"Well, yes. Save, perhaps, that. And yet this John Openshaw seems\nto me to be walking amid even greater perils than did the\nSholtos.\"\n\n\"But have you,\" I asked, \"formed any definite conception as to\nwhat these perils are?\"\n\n\"There can be no question as to their nature,\" he answered.\n\n\"Then what are they? Who is this K. K. K., and why does he pursue\nthis unhappy family?\"\n\nSherlock Holmes closed his eyes and placed his elbows upon the\narms of his chair, with his finger-tips together. \"The ideal\nreasoner,\" he remarked, \"would, when he had once been shown a\nsingle fact in all its bearings, deduce from it not only all the\nchain of events which led up to it but also all the results which\nwould follow from it. As Cuvier could correctly describe a whole\nanimal by the contemplation of a single bone, so the observer who\nhas thoroughly understood one link in a series of incidents\nshould be able to accurately state all the other ones, both\nbefore and after. We have not yet grasped the results which the\nreason alone can attain to. Problems may be solved in the study\nwhich have baffled all those who have sought a solution by the\naid of their senses. To carry the art, however, to its highest\npitch, it is necessary that the reasoner should be able to\nutilise all the facts which have come to his knowledge; and this\nin itself implies, as you will readily see, a possession of all\nknowledge, which, even in these days of free education and\nencyclopaedias, is a somewhat rare accomplishment. It is not so\nimpossible, however, that a man should possess all knowledge\nwhich is likely to be useful to him in his work, and this I have\nendeavoured in my case to do. If I remember rightly, you on one\noccasion, in the early days of our friendship, defined my limits\nin a very precise fashion.\"\n\n\"Yes,\" I answered, laughing. \"It was a singular document.\nPhilosophy, astronomy, and politics were marked at zero, I\nremember. Botany variable, geology profound as regards the\nmud-stains from any region within fifty miles of town, chemistry\neccentric, anatomy unsystematic, sensational literature and crime\nrecords unique, violin-player, boxer, swordsman, lawyer, and\nself-poisoner by cocaine and tobacco. Those, I think, were the\nmain points of my analysis.\"\n\nHolmes grinned at the last item. \"Well,\" he said, \"I say now, as\nI said then, that a man should keep his little brain-attic\nstocked with all the furniture that he is likely to use, and the\nrest he can put away in the lumber-room of his library, where he\ncan get it if he wants it. Now, for such a case as the one which\nhas been submitted to us to-night, we need certainly to muster\nall our resources. Kindly hand me down the letter K of the\n'American Encyclopaedia' which stands upon the shelf beside you.\nThank you. Now let us consider the situation and see what may be\ndeduced from it. In the first place, we may start with a strong\npresumption that Colonel Openshaw had some very strong reason for\nleaving America. Men at his time of life do not change all their\nhabits and exchange willingly the charming climate of Florida for\nthe lonely life of an English provincial town. His extreme love\nof solitude in England suggests the idea that he was in fear of\nsomeone or something, so we may assume as a working hypothesis\nthat it was fear of someone or something which drove him from\nAmerica. As to what it was he feared, we can only deduce that by\nconsidering the formidable letters which were received by himself\nand his successors. Did you remark the postmarks of those\nletters?\"\n\n\"The first was from Pondicherry, the second from Dundee, and the\nthird from London.\"\n\n\"From East London. What do you deduce from that?\"\n\n\"They are all seaports. That the writer was on board of a ship.\"\n\n\"Excellent. We have already a clue. There can be no doubt that\nthe probability--the strong probability--is that the writer was\non board of a ship. And now let us consider another point. In the\ncase of Pondicherry, seven weeks elapsed between the threat and\nits fulfilment, in Dundee it was only some three or four days.\nDoes that suggest anything?\"\n\n\"A greater distance to travel.\"\n\n\"But the letter had also a greater distance to come.\"\n\n\"Then I do not see the point.\"\n\n\"There is at least a presumption that the vessel in which the man\nor men are is a sailing-ship. It looks as if they always send\ntheir singular warning or token before them when starting upon\ntheir mission. You see how quickly the deed followed the sign\nwhen it came from Dundee. If they had come from Pondicherry in a\nsteamer they would have arrived almost as soon as their letter.\nBut, as a matter of fact, seven weeks elapsed. I think that those\nseven weeks represented the difference between the mail-boat which\nbrought the letter and the sailing vessel which brought the\nwriter.\"\n\n\"It is possible.\"\n\n\"More than that. It is probable. And now you see the deadly\nurgency of this new case, and why I urged young Openshaw to\ncaution. The blow has always fallen at the end of the time which\nit would take the senders to travel the distance. But this one\ncomes from London, and therefore we cannot count upon delay.\"\n\n\"Good God!\" I cried. \"What can it mean, this relentless\npersecution?\"\n\n\"The papers which Openshaw carried are obviously of vital\nimportance to the person or persons in the sailing-ship. I think\nthat it is quite clear that there must be more than one of them.\nA single man could not have carried out two deaths in such a way\nas to deceive a coroner's jury. There must have been several in\nit, and they must have been men of resource and determination.\nTheir papers they mean to have, be the holder of them who it may.\nIn this way you see K. K. K. ceases to be the initials of an\nindividual and becomes the badge of a society.\"\n\n\"But of what society?\"\n\n\"Have you never--\" said Sherlock Holmes, bending forward and\nsinking his voice--\"have you never heard of the Ku Klux Klan?\"\n\n\"I never have.\"\n\nHolmes turned over the leaves of the book upon his knee. \"Here it\nis,\" said he presently:\n\n\"'Ku Klux Klan. A name derived from the fanciful resemblance to\nthe sound produced by cocking a rifle. This terrible secret\nsociety was formed by some ex-Confederate soldiers in the\nSouthern states after the Civil War, and it rapidly formed local\nbranches in different parts of the country, notably in Tennessee,\nLouisiana, the Carolinas, Georgia, and Florida. Its power was\nused for political purposes, principally for the terrorising of\nthe negro voters and the murdering and driving from the country\nof those who were opposed to its views. Its outrages were usually\npreceded by a warning sent to the marked man in some fantastic\nbut generally recognised shape--a sprig of oak-leaves in some\nparts, melon seeds or orange pips in others. On receiving this\nthe victim might either openly abjure his former ways, or might\nfly from the country. If he braved the matter out, death would\nunfailingly come upon him, and usually in some strange and\nunforeseen manner. So perfect was the organisation of the\nsociety, and so systematic its methods, that there is hardly a\ncase upon record where any man succeeded in braving it with\nimpunity, or in which any of its outrages were traced home to the\nperpetrators. For some years the organisation flourished in spite\nof the efforts of the United States government and of the better\nclasses of the community in the South. Eventually, in the year\n1869, the movement rather suddenly collapsed, although there have\nbeen sporadic outbreaks of the same sort since that date.'\n\n\"You will observe,\" said Holmes, laying down the volume, \"that\nthe sudden breaking up of the society was coincident with the\ndisappearance of Openshaw from America with their papers. It may\nwell have been cause and effect. It is no wonder that he and his\nfamily have some of the more implacable spirits upon their track.\nYou can understand that this register and diary may implicate\nsome of the first men in the South, and that there may be many\nwho will not sleep easy at night until it is recovered.\"\n\n\"Then the page we have seen--\"\n\n\"Is such as we might expect. It ran, if I remember right, 'sent\nthe pips to A, B, and C'--that is, sent the society's warning to\nthem. Then there are successive entries that A and B cleared, or\nleft the country, and finally that C was visited, with, I fear, a\nsinister result for C. Well, I think, Doctor, that we may let\nsome light into this dark place, and I believe that the only\nchance young Openshaw has in the meantime is to do what I have\ntold him. There is nothing more to be said or to be done\nto-night, so hand me over my violin and let us try to forget for\nhalf an hour the miserable weather and the still more miserable\nways of our fellow-men.\"\n\n\nIt had cleared in the morning, and the sun was shining with a\nsubdued brightness through the dim veil which hangs over the\ngreat city. Sherlock Holmes was already at breakfast when I came\ndown.\n\n\"You will excuse me for not waiting for you,\" said he; \"I have, I\nforesee, a very busy day before me in looking into this case of\nyoung Openshaw's.\"\n\n\"What steps will you take?\" I asked.\n\n\"It will very much depend upon the results of my first inquiries.\nI may have to go down to Horsham, after all.\"\n\n\"You will not go there first?\"\n\n\"No, I shall commence with the City. Just ring the bell and the\nmaid will bring up your coffee.\"\n\nAs I waited, I lifted the unopened newspaper from the table and\nglanced my eye over it. It rested upon a heading which sent a\nchill to my heart.\n\n\"Holmes,\" I cried, \"you are too late.\"\n\n\"Ah!\" said he, laying down his cup, \"I feared as much. How was it\ndone?\" He spoke calmly, but I could see that he was deeply moved.\n\n\"My eye caught the name of Openshaw, and the heading 'Tragedy\nNear Waterloo Bridge.' Here is the account:\n\n\"Between nine and ten last night Police-Constable Cook, of the H\nDivision, on duty near Waterloo Bridge, heard a cry for help and\na splash in the water. The night, however, was extremely dark and\nstormy, so that, in spite of the help of several passers-by, it\nwas quite impossible to effect a rescue. The alarm, however, was\ngiven, and, by the aid of the water-police, the body was\neventually recovered. It proved to be that of a young gentleman\nwhose name, as it appears from an envelope which was found in his\npocket, was John Openshaw, and whose residence is near Horsham.\nIt is conjectured that he may have been hurrying down to catch\nthe last train from Waterloo Station, and that in his haste and\nthe extreme darkness he missed his path and walked over the edge\nof one of the small landing-places for river steamboats. The body\nexhibited no traces of violence, and there can be no doubt that\nthe deceased had been the victim of an unfortunate accident,\nwhich should have the effect of calling the attention of the\nauthorities to the condition of the riverside landing-stages.\"\n\nWe sat in silence for some minutes, Holmes more depressed and\nshaken than I had ever seen him.\n\n\"That hurts my pride, Watson,\" he said at last. \"It is a petty\nfeeling, no doubt, but it hurts my pride. It becomes a personal\nmatter with me now, and, if God sends me health, I shall set my\nhand upon this gang. That he should come to me for help, and that\nI should send him away to his death--!\" He sprang from his chair\nand paced about the room in uncontrollable agitation, with a\nflush upon his sallow cheeks and a nervous clasping and\nunclasping of his long thin hands.\n\n\"They must be cunning devils,\" he exclaimed at last. \"How could\nthey have decoyed him down there? The Embankment is not on the\ndirect line to the station. The bridge, no doubt, was too\ncrowded, even on such a night, for their purpose. Well, Watson,\nwe shall see who will win in the long run. I am going out now!\"\n\n\"To the police?\"\n\n\"No; I shall be my own police. When I have spun the web they may\ntake the flies, but not before.\"\n\nAll day I was engaged in my professional work, and it was late in\nthe evening before I returned to Baker Street. Sherlock Holmes\nhad not come back yet. It was nearly ten o'clock before he\nentered, looking pale and worn. He walked up to the sideboard,\nand tearing a piece from the loaf he devoured it voraciously,\nwashing it down with a long draught of water.\n\n\"You are hungry,\" I remarked.\n\n\"Starving. It had escaped my memory. I have had nothing since\nbreakfast.\"\n\n\"Nothing?\"\n\n\"Not a bite. I had no time to think of it.\"\n\n\"And how have you succeeded?\"\n\n\"Well.\"\n\n\"You have a clue?\"\n\n\"I have them in the hollow of my hand. Young Openshaw shall not\nlong remain unavenged. Why, Watson, let us put their own devilish\ntrade-mark upon them. It is well thought of!\"\n\n\"What do you mean?\"\n\nHe took an orange from the cupboard, and tearing it to pieces he\nsqueezed out the pips upon the table. Of these he took five and\nthrust them into an envelope. On the inside of the flap he wrote\n\"S. H. for J. O.\" Then he sealed it and addressed it to \"Captain\nJames Calhoun, Barque 'Lone Star,' Savannah, Georgia.\"\n\n\"That will await him when he enters port,\" said he, chuckling.\n\"It may give him a sleepless night. He will find it as sure a\nprecursor of his fate as Openshaw did before him.\"\n\n\"And who is this Captain Calhoun?\"\n\n\"The leader of the gang. I shall have the others, but he first.\"\n\n\"How did you trace it, then?\"\n\nHe took a large sheet of paper from his pocket, all covered with\ndates and names.\n\n\"I have spent the whole day,\" said he, \"over Lloyd's registers\nand files of the old papers, following the future career of every\nvessel which touched at Pondicherry in January and February in\n'83. There were thirty-six ships of fair tonnage which were\nreported there during those months. Of these, one, the 'Lone Star,'\ninstantly attracted my attention, since, although it was reported\nas having cleared from London, the name is that which is given to\none of the states of the Union.\"\n\n\"Texas, I think.\"\n\n\"I was not and am not sure which; but I knew that the ship must\nhave an American origin.\"\n\n\"What then?\"\n\n\"I searched the Dundee records, and when I found that the barque\n'Lone Star' was there in January, '85, my suspicion became a\ncertainty. I then inquired as to the vessels which lay at present\nin the port of London.\"\n\n\"Yes?\"\n\n\"The 'Lone Star' had arrived here last week. I went down to the\nAlbert Dock and found that she had been taken down the river by\nthe early tide this morning, homeward bound to Savannah. I wired\nto Gravesend and learned that she had passed some time ago, and\nas the wind is easterly I have no doubt that she is now past the\nGoodwins and not very far from the Isle of Wight.\"\n\n\"What will you do, then?\"\n\n\"Oh, I have my hand upon him. He and the two mates, are as I\nlearn, the only native-born Americans in the ship. The others are\nFinns and Germans. I know, also, that they were all three away\nfrom the ship last night. I had it from the stevedore who has\nbeen loading their cargo. By the time that their sailing-ship\nreaches Savannah the mail-boat will have carried this letter, and\nthe cable will have informed the police of Savannah that these\nthree gentlemen are badly wanted here upon a charge of murder.\"\n\nThere is ever a flaw, however, in the best laid of human plans,\nand the murderers of John Openshaw were never to receive the\norange pips which would show them that another, as cunning and as\nresolute as themselves, was upon their track. Very long and very\nsevere were the equinoctial gales that year. We waited long for\nnews of the \"Lone Star\" of Savannah, but none ever reached us. We\ndid at last hear that somewhere far out in the Atlantic a\nshattered stern-post of a boat was seen swinging in the trough\nof a wave, with the letters \"L. S.\" carved upon it, and that is\nall which we shall ever know of the fate of the \"Lone Star.\"\n\n\n\nADVENTURE VI. THE MAN WITH THE TWISTED LIP\n\nIsa Whitney, brother of the late Elias Whitney, D.D., Principal\nof the Theological College of St. George's, was much addicted to\nopium. The habit grew upon him, as I understand, from some\nfoolish freak when he was at college; for having read De\nQuincey's description of his dreams and sensations, he had\ndrenched his tobacco with laudanum in an attempt to produce the\nsame effects. He found, as so many more have done, that the\npractice is easier to attain than to get rid of, and for many\nyears he continued to be a slave to the drug, an object of\nmingled horror and pity to his friends and relatives. I can see\nhim now, with yellow, pasty face, drooping lids, and pin-point\npupils, all huddled in a chair, the wreck and ruin of a noble\nman.\n\nOne night--it was in June, '89--there came a ring to my bell,\nabout the hour when a man gives his first yawn and glances at the\nclock. I sat up in my chair, and my wife laid her needle-work\ndown in her lap and made a little face of disappointment.\n\n\"A patient!\" said she. \"You'll have to go out.\"\n\nI groaned, for I was newly come back from a weary day.\n\nWe heard the door open, a few hurried words, and then quick steps\nupon the linoleum. Our own door flew open, and a lady, clad in\nsome dark-coloured stuff, with a black veil, entered the room.\n\n\"You will excuse my calling so late,\" she began, and then,\nsuddenly losing her self-control, she ran forward, threw her arms\nabout my wife's neck, and sobbed upon her shoulder. \"Oh, I'm in\nsuch trouble!\" she cried; \"I do so want a little help.\"\n\n\"Why,\" said my wife, pulling up her veil, \"it is Kate Whitney.\nHow you startled me, Kate! I had not an idea who you were when\nyou came in.\"\n\n\"I didn't know what to do, so I came straight to you.\" That was\nalways the way. Folk who were in grief came to my wife like birds\nto a light-house.\n\n\"It was very sweet of you to come. Now, you must have some wine\nand water, and sit here comfortably and tell us all about it. Or\nshould you rather that I sent James off to bed?\"\n\n\"Oh, no, no! I want the doctor's advice and help, too. It's about\nIsa. He has not been home for two days. I am so frightened about\nhim!\"\n\nIt was not the first time that she had spoken to us of her\nhusband's trouble, to me as a doctor, to my wife as an old friend\nand school companion. We soothed and comforted her by such words\nas we could find. Did she know where her husband was? Was it\npossible that we could bring him back to her?\n\nIt seems that it was. She had the surest information that of late\nhe had, when the fit was on him, made use of an opium den in the\nfarthest east of the City. Hitherto his orgies had always been\nconfined to one day, and he had come back, twitching and\nshattered, in the evening. But now the spell had been upon him\neight-and-forty hours, and he lay there, doubtless among the\ndregs of the docks, breathing in the poison or sleeping off the\neffects. There he was to be found, she was sure of it, at the Bar\nof Gold, in Upper Swandam Lane. But what was she to do? How could\nshe, a young and timid woman, make her way into such a place and\npluck her husband out from among the ruffians who surrounded him?\n\nThere was the case, and of course there was but one way out of\nit. Might I not escort her to this place? And then, as a second\nthought, why should she come at all? I was Isa Whitney's medical\nadviser, and as such I had influence over him. I could manage it\nbetter if I were alone. I promised her on my word that I would\nsend him home in a cab within two hours if he were indeed at the\naddress which she had given me. And so in ten minutes I had left\nmy armchair and cheery sitting-room behind me, and was speeding\neastward in a hansom on a strange errand, as it seemed to me at\nthe time, though the future only could show how strange it was to\nbe.\n\nBut there was no great difficulty in the first stage of my\nadventure. Upper Swandam Lane is a vile alley lurking behind the\nhigh wharves which line the north side of the river to the east\nof London Bridge. Between a slop-shop and a gin-shop, approached\nby a steep flight of steps leading down to a black gap like the\nmouth of a cave, I found the den of which I was in search.\nOrdering my cab to wait, I passed down the steps, worn hollow in\nthe centre by the ceaseless tread of drunken feet; and by the\nlight of a flickering oil-lamp above the door I found the latch\nand made my way into a long, low room, thick and heavy with the\nbrown opium smoke, and terraced with wooden berths, like the\nforecastle of an emigrant ship.\n\nThrough the gloom one could dimly catch a glimpse of bodies lying\nin strange fantastic poses, bowed shoulders, bent knees, heads\nthrown back, and chins pointing upward, with here and there a\ndark, lack-lustre eye turned upon the newcomer. Out of the black\nshadows there glimmered little red circles of light, now bright,\nnow faint, as the burning poison waxed or waned in the bowls of\nthe metal pipes. The most lay silent, but some muttered to\nthemselves, and others talked together in a strange, low,\nmonotonous voice, their conversation coming in gushes, and then\nsuddenly tailing off into silence, each mumbling out his own\nthoughts and paying little heed to the words of his neighbour. At\nthe farther end was a small brazier of burning charcoal, beside\nwhich on a three-legged wooden stool there sat a tall, thin old\nman, with his jaw resting upon his two fists, and his elbows upon\nhis knees, staring into the fire.\n\nAs I entered, a sallow Malay attendant had hurried up with a pipe\nfor me and a supply of the drug, beckoning me to an empty berth.\n\n\"Thank you. I have not come to stay,\" said I. \"There is a friend\nof mine here, Mr. Isa Whitney, and I wish to speak with him.\"\n\nThere was a movement and an exclamation from my right, and\npeering through the gloom, I saw Whitney, pale, haggard, and\nunkempt, staring out at me.\n\n\"My God! It's Watson,\" said he. He was in a pitiable state of\nreaction, with every nerve in a twitter. \"I say, Watson, what\no'clock is it?\"\n\n\"Nearly eleven.\"\n\n\"Of what day?\"\n\n\"Of Friday, June 19th.\"\n\n\"Good heavens! I thought it was Wednesday. It is Wednesday. What\nd'you want to frighten a chap for?\" He sank his face onto his\narms and began to sob in a high treble key.\n\n\"I tell you that it is Friday, man. Your wife has been waiting\nthis two days for you. You should be ashamed of yourself!\"\n\n\"So I am. But you've got mixed, Watson, for I have only been here\na few hours, three pipes, four pipes--I forget how many. But I'll\ngo home with you. I wouldn't frighten Kate--poor little Kate.\nGive me your hand! Have you a cab?\"\n\n\"Yes, I have one waiting.\"\n\n\"Then I shall go in it. But I must owe something. Find what I\nowe, Watson. I am all off colour. I can do nothing for myself.\"\n\nI walked down the narrow passage between the double row of\nsleepers, holding my breath to keep out the vile, stupefying\nfumes of the drug, and looking about for the manager. As I passed\nthe tall man who sat by the brazier I felt a sudden pluck at my\nskirt, and a low voice whispered, \"Walk past me, and then look\nback at me.\" The words fell quite distinctly upon my ear. I\nglanced down. They could only have come from the old man at my\nside, and yet he sat now as absorbed as ever, very thin, very\nwrinkled, bent with age, an opium pipe dangling down from between\nhis knees, as though it had dropped in sheer lassitude from his\nfingers. I took two steps forward and looked back. It took all my\nself-control to prevent me from breaking out into a cry of\nastonishment. He had turned his back so that none could see him\nbut I. His form had filled out, his wrinkles were gone, the dull\neyes had regained their fire, and there, sitting by the fire and\ngrinning at my surprise, was none other than Sherlock Holmes. He\nmade a slight motion to me to approach him, and instantly, as he\nturned his face half round to the company once more, subsided\ninto a doddering, loose-lipped senility.\n\n\"Holmes!\" I whispered, \"what on earth are you doing in this den?\"\n\n\"As low as you can,\" he answered; \"I have excellent ears. If you\nwould have the great kindness to get rid of that sottish friend\nof yours I should be exceedingly glad to have a little talk with\nyou.\"\n\n\"I have a cab outside.\"\n\n\"Then pray send him home in it. You may safely trust him, for he\nappears to be too limp to get into any mischief. I should\nrecommend you also to send a note by the cabman to your wife to\nsay that you have thrown in your lot with me. If you will wait\noutside, I shall be with you in five minutes.\"\n\nIt was difficult to refuse any of Sherlock Holmes' requests, for\nthey were always so exceedingly definite, and put forward with\nsuch a quiet air of mastery. I felt, however, that when Whitney\nwas once confined in the cab my mission was practically\naccomplished; and for the rest, I could not wish anything better\nthan to be associated with my friend in one of those singular\nadventures which were the normal condition of his existence. In a\nfew minutes I had written my note, paid Whitney's bill, led him\nout to the cab, and seen him driven through the darkness. In a\nvery short time a decrepit figure had emerged from the opium den,\nand I was walking down the street with Sherlock Holmes. For two\nstreets he shuffled along with a bent back and an uncertain foot.\nThen, glancing quickly round, he straightened himself out and\nburst into a hearty fit of laughter.\n\n\"I suppose, Watson,\" said he, \"that you imagine that I have added\nopium-smoking to cocaine injections, and all the other little\nweaknesses on which you have favoured me with your medical\nviews.\"\n\n\"I was certainly surprised to find you there.\"\n\n\"But not more so than I to find you.\"\n\n\"I came to find a friend.\"\n\n\"And I to find an enemy.\"\n\n\"An enemy?\"\n\n\"Yes; one of my natural enemies, or, shall I say, my natural\nprey. Briefly, Watson, I am in the midst of a very remarkable\ninquiry, and I have hoped to find a clue in the incoherent\nramblings of these sots, as I have done before now. Had I been\nrecognised in that den my life would not have been worth an\nhour's purchase; for I have used it before now for my own\npurposes, and the rascally Lascar who runs it has sworn to have\nvengeance upon me. There is a trap-door at the back of that\nbuilding, near the corner of Paul's Wharf, which could tell some\nstrange tales of what has passed through it upon the moonless\nnights.\"\n\n\"What! You do not mean bodies?\"\n\n\"Ay, bodies, Watson. We should be rich men if we had 1000 pounds\nfor every poor devil who has been done to death in that den. It\nis the vilest murder-trap on the whole riverside, and I fear that\nNeville St. Clair has entered it never to leave it more. But our\ntrap should be here.\" He put his two forefingers between his\nteeth and whistled shrilly--a signal which was answered by a\nsimilar whistle from the distance, followed shortly by the rattle\nof wheels and the clink of horses' hoofs.\n\n\"Now, Watson,\" said Holmes, as a tall dog-cart dashed up through\nthe gloom, throwing out two golden tunnels of yellow light from\nits side lanterns. \"You'll come with me, won't you?\"\n\n\"If I can be of use.\"\n\n\"Oh, a trusty comrade is always of use; and a chronicler still\nmore so. My room at The Cedars is a double-bedded one.\"\n\n\"The Cedars?\"\n\n\"Yes; that is Mr. St. Clair's house. I am staying there while I\nconduct the inquiry.\"\n\n\"Where is it, then?\"\n\n\"Near Lee, in Kent. We have a seven-mile drive before us.\"\n\n\"But I am all in the dark.\"\n\n\"Of course you are. You'll know all about it presently. Jump up\nhere. All right, John; we shall not need you. Here's half a\ncrown. Look out for me to-morrow, about eleven. Give her her\nhead. So long, then!\"\n\nHe flicked the horse with his whip, and we dashed away through\nthe endless succession of sombre and deserted streets, which\nwidened gradually, until we were flying across a broad\nbalustraded bridge, with the murky river flowing sluggishly\nbeneath us. Beyond lay another dull wilderness of bricks and\nmortar, its silence broken only by the heavy, regular footfall of\nthe policeman, or the songs and shouts of some belated party of\nrevellers. A dull wrack was drifting slowly across the sky, and a\nstar or two twinkled dimly here and there through the rifts of\nthe clouds. Holmes drove in silence, with his head sunk upon his\nbreast, and the air of a man who is lost in thought, while I sat\nbeside him, curious to learn what this new quest might be which\nseemed to tax his powers so sorely, and yet afraid to break in\nupon the current of his thoughts. We had driven several miles,\nand were beginning to get to the fringe of the belt of suburban\nvillas, when he shook himself, shrugged his shoulders, and lit up\nhis pipe with the air of a man who has satisfied himself that he\nis acting for the best.\n\n\"You have a grand gift of silence, Watson,\" said he. \"It makes\nyou quite invaluable as a companion. 'Pon my word, it is a great\nthing for me to have someone to talk to, for my own thoughts are\nnot over-pleasant. I was wondering what I should say to this dear\nlittle woman to-night when she meets me at the door.\"\n\n\"You forget that I know nothing about it.\"\n\n\"I shall just have time to tell you the facts of the case before\nwe get to Lee. It seems absurdly simple, and yet, somehow I can\nget nothing to go upon. There's plenty of thread, no doubt, but I\ncan't get the end of it into my hand. Now, I'll state the case\nclearly and concisely to you, Watson, and maybe you can see a\nspark where all is dark to me.\"\n\n\"Proceed, then.\"\n\n\"Some years ago--to be definite, in May, 1884--there came to Lee\na gentleman, Neville St. Clair by name, who appeared to have\nplenty of money. He took a large villa, laid out the grounds very\nnicely, and lived generally in good style. By degrees he made\nfriends in the neighbourhood, and in 1887 he married the daughter\nof a local brewer, by whom he now has two children. He had no\noccupation, but was interested in several companies and went into\ntown as a rule in the morning, returning by the 5:14 from Cannon\nStreet every night. Mr. St. Clair is now thirty-seven years of\nage, is a man of temperate habits, a good husband, a very\naffectionate father, and a man who is popular with all who know\nhim. I may add that his whole debts at the present moment, as far\nas we have been able to ascertain, amount to 88 pounds 10s., while\nhe has 220 pounds standing to his credit in the Capital and\nCounties Bank. There is no reason, therefore, to think that money\ntroubles have been weighing upon his mind.\n\n\"Last Monday Mr. Neville St. Clair went into town rather earlier\nthan usual, remarking before he started that he had two important\ncommissions to perform, and that he would bring his little boy\nhome a box of bricks. Now, by the merest chance, his wife\nreceived a telegram upon this same Monday, very shortly after his\ndeparture, to the effect that a small parcel of considerable\nvalue which she had been expecting was waiting for her at the\noffices of the Aberdeen Shipping Company. Now, if you are well up\nin your London, you will know that the office of the company is\nin Fresno Street, which branches out of Upper Swandam Lane, where\nyou found me to-night. Mrs. St. Clair had her lunch, started for\nthe City, did some shopping, proceeded to the company's office,\ngot her packet, and found herself at exactly 4:35 walking through\nSwandam Lane on her way back to the station. Have you followed me\nso far?\"\n\n\"It is very clear.\"\n\n\"If you remember, Monday was an exceedingly hot day, and Mrs. St.\nClair walked slowly, glancing about in the hope of seeing a cab,\nas she did not like the neighbourhood in which she found herself.\nWhile she was walking in this way down Swandam Lane, she suddenly\nheard an ejaculation or cry, and was struck cold to see her\nhusband looking down at her and, as it seemed to her, beckoning\nto her from a second-floor window. The window was open, and she\ndistinctly saw his face, which she describes as being terribly\nagitated. He waved his hands frantically to her, and then\nvanished from the window so suddenly that it seemed to her that\nhe had been plucked back by some irresistible force from behind.\nOne singular point which struck her quick feminine eye was that\nalthough he wore some dark coat, such as he had started to town\nin, he had on neither collar nor necktie.\n\n\"Convinced that something was amiss with him, she rushed down the\nsteps--for the house was none other than the opium den in which\nyou found me to-night--and running through the front room she\nattempted to ascend the stairs which led to the first floor. At\nthe foot of the stairs, however, she met this Lascar scoundrel of\nwhom I have spoken, who thrust her back and, aided by a Dane, who\nacts as assistant there, pushed her out into the street. Filled\nwith the most maddening doubts and fears, she rushed down the\nlane and, by rare good-fortune, met in Fresno Street a number of\nconstables with an inspector, all on their way to their beat. The\ninspector and two men accompanied her back, and in spite of the\ncontinued resistance of the proprietor, they made their way to\nthe room in which Mr. St. Clair had last been seen. There was no\nsign of him there. In fact, in the whole of that floor there was\nno one to be found save a crippled wretch of hideous aspect, who,\nit seems, made his home there. Both he and the Lascar stoutly\nswore that no one else had been in the front room during the\nafternoon. So determined was their denial that the inspector was\nstaggered, and had almost come to believe that Mrs. St. Clair had\nbeen deluded when, with a cry, she sprang at a small deal box\nwhich lay upon the table and tore the lid from it. Out there fell\na cascade of children's bricks. It was the toy which he had\npromised to bring home.\n\n\"This discovery, and the evident confusion which the cripple\nshowed, made the inspector realise that the matter was serious.\nThe rooms were carefully examined, and results all pointed to an\nabominable crime. The front room was plainly furnished as a\nsitting-room and led into a small bedroom, which looked out upon\nthe back of one of the wharves. Between the wharf and the bedroom\nwindow is a narrow strip, which is dry at low tide but is covered\nat high tide with at least four and a half feet of water. The\nbedroom window was a broad one and opened from below. On\nexamination traces of blood were to be seen upon the windowsill,\nand several scattered drops were visible upon the wooden floor of\nthe bedroom. Thrust away behind a curtain in the front room were\nall the clothes of Mr. Neville St. Clair, with the exception of\nhis coat. His boots, his socks, his hat, and his watch--all were\nthere. There were no signs of violence upon any of these\ngarments, and there were no other traces of Mr. Neville St.\nClair. Out of the window he must apparently have gone for no\nother exit could be discovered, and the ominous bloodstains upon\nthe sill gave little promise that he could save himself by\nswimming, for the tide was at its very highest at the moment of\nthe tragedy.\n\n\"And now as to the villains who seemed to be immediately\nimplicated in the matter. The Lascar was known to be a man of the\nvilest antecedents, but as, by Mrs. St. Clair's story, he was\nknown to have been at the foot of the stair within a very few\nseconds of her husband's appearance at the window, he could\nhardly have been more than an accessory to the crime. His defence\nwas one of absolute ignorance, and he protested that he had no\nknowledge as to the doings of Hugh Boone, his lodger, and that he\ncould not account in any way for the presence of the missing\ngentleman's clothes.\n\n\"So much for the Lascar manager. Now for the sinister cripple who\nlives upon the second floor of the opium den, and who was\ncertainly the last human being whose eyes rested upon Neville St.\nClair. His name is Hugh Boone, and his hideous face is one which\nis familiar to every man who goes much to the City. He is a\nprofessional beggar, though in order to avoid the police\nregulations he pretends to a small trade in wax vestas. Some\nlittle distance down Threadneedle Street, upon the left-hand\nside, there is, as you may have remarked, a small angle in the\nwall. Here it is that this creature takes his daily seat,\ncross-legged with his tiny stock of matches on his lap, and as he\nis a piteous spectacle a small rain of charity descends into the\ngreasy leather cap which lies upon the pavement beside him. I\nhave watched the fellow more than once before ever I thought of\nmaking his professional acquaintance, and I have been surprised\nat the harvest which he has reaped in a short time. His\nappearance, you see, is so remarkable that no one can pass him\nwithout observing him. A shock of orange hair, a pale face\ndisfigured by a horrible scar, which, by its contraction, has\nturned up the outer edge of his upper lip, a bulldog chin, and a\npair of very penetrating dark eyes, which present a singular\ncontrast to the colour of his hair, all mark him out from amid\nthe common crowd of mendicants and so, too, does his wit, for he\nis ever ready with a reply to any piece of chaff which may be\nthrown at him by the passers-by. This is the man whom we now\nlearn to have been the lodger at the opium den, and to have been\nthe last man to see the gentleman of whom we are in quest.\"\n\n\"But a cripple!\" said I. \"What could he have done single-handed\nagainst a man in the prime of life?\"\n\n\"He is a cripple in the sense that he walks with a limp; but in\nother respects he appears to be a powerful and well-nurtured man.\nSurely your medical experience would tell you, Watson, that\nweakness in one limb is often compensated for by exceptional\nstrength in the others.\"\n\n\"Pray continue your narrative.\"\n\n\"Mrs. St. Clair had fainted at the sight of the blood upon the\nwindow, and she was escorted home in a cab by the police, as her\npresence could be of no help to them in their investigations.\nInspector Barton, who had charge of the case, made a very careful\nexamination of the premises, but without finding anything which\nthrew any light upon the matter. One mistake had been made in not\narresting Boone instantly, as he was allowed some few minutes\nduring which he might have communicated with his friend the\nLascar, but this fault was soon remedied, and he was seized and\nsearched, without anything being found which could incriminate\nhim. There were, it is true, some blood-stains upon his right\nshirt-sleeve, but he pointed to his ring-finger, which had been\ncut near the nail, and explained that the bleeding came from\nthere, adding that he had been to the window not long before, and\nthat the stains which had been observed there came doubtless from\nthe same source. He denied strenuously having ever seen Mr.\nNeville St. Clair and swore that the presence of the clothes in\nhis room was as much a mystery to him as to the police. As to\nMrs. St. Clair's assertion that she had actually seen her husband\nat the window, he declared that she must have been either mad or\ndreaming. He was removed, loudly protesting, to the\npolice-station, while the inspector remained upon the premises in\nthe hope that the ebbing tide might afford some fresh clue.\n\n\"And it did, though they hardly found upon the mud-bank what they\nhad feared to find. It was Neville St. Clair's coat, and not\nNeville St. Clair, which lay uncovered as the tide receded. And\nwhat do you think they found in the pockets?\"\n\n\"I cannot imagine.\"\n\n\"No, I don't think you would guess. Every pocket stuffed with\npennies and half-pennies--421 pennies and 270 half-pennies. It\nwas no wonder that it had not been swept away by the tide. But a\nhuman body is a different matter. There is a fierce eddy between\nthe wharf and the house. It seemed likely enough that the\nweighted coat had remained when the stripped body had been sucked\naway into the river.\"\n\n\"But I understand that all the other clothes were found in the\nroom. Would the body be dressed in a coat alone?\"\n\n\"No, sir, but the facts might be met speciously enough. Suppose\nthat this man Boone had thrust Neville St. Clair through the\nwindow, there is no human eye which could have seen the deed.\nWhat would he do then? It would of course instantly strike him\nthat he must get rid of the tell-tale garments. He would seize\nthe coat, then, and be in the act of throwing it out, when it\nwould occur to him that it would swim and not sink. He has little\ntime, for he has heard the scuffle downstairs when the wife tried\nto force her way up, and perhaps he has already heard from his\nLascar confederate that the police are hurrying up the street.\nThere is not an instant to be lost. He rushes to some secret\nhoard, where he has accumulated the fruits of his beggary, and he\nstuffs all the coins upon which he can lay his hands into the\npockets to make sure of the coat's sinking. He throws it out, and\nwould have done the same with the other garments had not he heard\nthe rush of steps below, and only just had time to close the\nwindow when the police appeared.\"\n\n\"It certainly sounds feasible.\"\n\n\"Well, we will take it as a working hypothesis for want of a\nbetter. Boone, as I have told you, was arrested and taken to the\nstation, but it could not be shown that there had ever before\nbeen anything against him. He had for years been known as a\nprofessional beggar, but his life appeared to have been a very\nquiet and innocent one. There the matter stands at present, and\nthe questions which have to be solved--what Neville St. Clair was\ndoing in the opium den, what happened to him when there, where is\nhe now, and what Hugh Boone had to do with his disappearance--are\nall as far from a solution as ever. I confess that I cannot\nrecall any case within my experience which looked at the first\nglance so simple and yet which presented such difficulties.\"\n\nWhile Sherlock Holmes had been detailing this singular series of\nevents, we had been whirling through the outskirts of the great\ntown until the last straggling houses had been left behind, and\nwe rattled along with a country hedge upon either side of us.\nJust as he finished, however, we drove through two scattered\nvillages, where a few lights still glimmered in the windows.\n\n\"We are on the outskirts of Lee,\" said my companion. \"We have\ntouched on three English counties in our short drive, starting in\nMiddlesex, passing over an angle of Surrey, and ending in Kent.\nSee that light among the trees? That is The Cedars, and beside\nthat lamp sits a woman whose anxious ears have already, I have\nlittle doubt, caught the clink of our horse's feet.\"\n\n\"But why are you not conducting the case from Baker Street?\" I\nasked.\n\n\"Because there are many inquiries which must be made out here.\nMrs. St. Clair has most kindly put two rooms at my disposal, and\nyou may rest assured that she will have nothing but a welcome for\nmy friend and colleague. I hate to meet her, Watson, when I have\nno news of her husband. Here we are. Whoa, there, whoa!\"\n\nWe had pulled up in front of a large villa which stood within its\nown grounds. A stable-boy had run out to the horse's head, and\nspringing down, I followed Holmes up the small, winding\ngravel-drive which led to the house. As we approached, the door\nflew open, and a little blonde woman stood in the opening, clad\nin some sort of light mousseline de soie, with a touch of fluffy\npink chiffon at her neck and wrists. She stood with her figure\noutlined against the flood of light, one hand upon the door, one\nhalf-raised in her eagerness, her body slightly bent, her head\nand face protruded, with eager eyes and parted lips, a standing\nquestion.\n\n\"Well?\" she cried, \"well?\" And then, seeing that there were two\nof us, she gave a cry of hope which sank into a groan as she saw\nthat my companion shook his head and shrugged his shoulders.\n\n\"No good news?\"\n\n\"None.\"\n\n\"No bad?\"\n\n\"No.\"\n\n\"Thank God for that. But come in. You must be weary, for you have\nhad a long day.\"\n\n\"This is my friend, Dr. Watson. He has been of most vital use to\nme in several of my cases, and a lucky chance has made it\npossible for me to bring him out and associate him with this\ninvestigation.\"\n\n\"I am delighted to see you,\" said she, pressing my hand warmly.\n\"You will, I am sure, forgive anything that may be wanting in our\narrangements, when you consider the blow which has come so\nsuddenly upon us.\"\n\n\"My dear madam,\" said I, \"I am an old campaigner, and if I were\nnot I can very well see that no apology is needed. If I can be of\nany assistance, either to you or to my friend here, I shall be\nindeed happy.\"\n\n\"Now, Mr. Sherlock Holmes,\" said the lady as we entered a\nwell-lit dining-room, upon the table of which a cold supper had\nbeen laid out, \"I should very much like to ask you one or two\nplain questions, to which I beg that you will give a plain\nanswer.\"\n\n\"Certainly, madam.\"\n\n\"Do not trouble about my feelings. I am not hysterical, nor given\nto fainting. I simply wish to hear your real, real opinion.\"\n\n\"Upon what point?\"\n\n\"In your heart of hearts, do you think that Neville is alive?\"\n\nSherlock Holmes seemed to be embarrassed by the question.\n\"Frankly, now!\" she repeated, standing upon the rug and looking\nkeenly down at him as he leaned back in a basket-chair.\n\n\"Frankly, then, madam, I do not.\"\n\n\"You think that he is dead?\"\n\n\"I do.\"\n\n\"Murdered?\"\n\n\"I don't say that. Perhaps.\"\n\n\"And on what day did he meet his death?\"\n\n\"On Monday.\"\n\n\"Then perhaps, Mr. Holmes, you will be good enough to explain how\nit is that I have received a letter from him to-day.\"\n\nSherlock Holmes sprang out of his chair as if he had been\ngalvanised.\n\n\"What!\" he roared.\n\n\"Yes, to-day.\" She stood smiling, holding up a little slip of\npaper in the air.\n\n\"May I see it?\"\n\n\"Certainly.\"\n\nHe snatched it from her in his eagerness, and smoothing it out\nupon the table he drew over the lamp and examined it intently. I\nhad left my chair and was gazing at it over his shoulder. The\nenvelope was a very coarse one and was stamped with the Gravesend\npostmark and with the date of that very day, or rather of the day\nbefore, for it was considerably after midnight.\n\n\"Coarse writing,\" murmured Holmes. \"Surely this is not your\nhusband's writing, madam.\"\n\n\"No, but the enclosure is.\"\n\n\"I perceive also that whoever addressed the envelope had to go\nand inquire as to the address.\"\n\n\"How can you tell that?\"\n\n\"The name, you see, is in perfectly black ink, which has dried\nitself. The rest is of the greyish colour, which shows that\nblotting-paper has been used. If it had been written straight\noff, and then blotted, none would be of a deep black shade. This\nman has written the name, and there has then been a pause before\nhe wrote the address, which can only mean that he was not\nfamiliar with it. It is, of course, a trifle, but there is\nnothing so important as trifles. Let us now see the letter. Ha!\nthere has been an enclosure here!\"\n\n\"Yes, there was a ring. His signet-ring.\"\n\n\"And you are sure that this is your husband's hand?\"\n\n\"One of his hands.\"\n\n\"One?\"\n\n\"His hand when he wrote hurriedly. It is very unlike his usual\nwriting, and yet I know it well.\"\n\n\"'Dearest do not be frightened. All will come well. There is a\nhuge error which it may take some little time to rectify.\nWait in patience.--NEVILLE.' Written in pencil upon the fly-leaf\nof a book, octavo size, no water-mark. Hum! Posted to-day in\nGravesend by a man with a dirty thumb. Ha! And the flap has been\ngummed, if I am not very much in error, by a person who had been\nchewing tobacco. And you have no doubt that it is your husband's\nhand, madam?\"\n\n\"None. Neville wrote those words.\"\n\n\"And they were posted to-day at Gravesend. Well, Mrs. St. Clair,\nthe clouds lighten, though I should not venture to say that the\ndanger is over.\"\n\n\"But he must be alive, Mr. Holmes.\"\n\n\"Unless this is a clever forgery to put us on the wrong scent.\nThe ring, after all, proves nothing. It may have been taken from\nhim.\"\n\n\"No, no; it is, it is his very own writing!\"\n\n\"Very well. It may, however, have been written on Monday and only\nposted to-day.\"\n\n\"That is possible.\"\n\n\"If so, much may have happened between.\"\n\n\"Oh, you must not discourage me, Mr. Holmes. I know that all is\nwell with him. There is so keen a sympathy between us that I\nshould know if evil came upon him. On the very day that I saw him\nlast he cut himself in the bedroom, and yet I in the dining-room\nrushed upstairs instantly with the utmost certainty that\nsomething had happened. Do you think that I would respond to such\na trifle and yet be ignorant of his death?\"\n\n\"I have seen too much not to know that the impression of a woman\nmay be more valuable than the conclusion of an analytical\nreasoner. And in this letter you certainly have a very strong\npiece of evidence to corroborate your view. But if your husband\nis alive and able to write letters, why should he remain away\nfrom you?\"\n\n\"I cannot imagine. It is unthinkable.\"\n\n\"And on Monday he made no remarks before leaving you?\"\n\n\"No.\"\n\n\"And you were surprised to see him in Swandam Lane?\"\n\n\"Very much so.\"\n\n\"Was the window open?\"\n\n\"Yes.\"\n\n\"Then he might have called to you?\"\n\n\"He might.\"\n\n\"He only, as I understand, gave an inarticulate cry?\"\n\n\"Yes.\"\n\n\"A call for help, you thought?\"\n\n\"Yes. He waved his hands.\"\n\n\"But it might have been a cry of surprise. Astonishment at the\nunexpected sight of you might cause him to throw up his hands?\"\n\n\"It is possible.\"\n\n\"And you thought he was pulled back?\"\n\n\"He disappeared so suddenly.\"\n\n\"He might have leaped back. You did not see anyone else in the\nroom?\"\n\n\"No, but this horrible man confessed to having been there, and\nthe Lascar was at the foot of the stairs.\"\n\n\"Quite so. Your husband, as far as you could see, had his\nordinary clothes on?\"\n\n\"But without his collar or tie. I distinctly saw his bare\nthroat.\"\n\n\"Had he ever spoken of Swandam Lane?\"\n\n\"Never.\"\n\n\"Had he ever showed any signs of having taken opium?\"\n\n\"Never.\"\n\n\"Thank you, Mrs. St. Clair. Those are the principal points about\nwhich I wished to be absolutely clear. We shall now have a little\nsupper and then retire, for we may have a very busy day\nto-morrow.\"\n\nA large and comfortable double-bedded room had been placed at our\ndisposal, and I was quickly between the sheets, for I was weary\nafter my night of adventure. Sherlock Holmes was a man, however,\nwho, when he had an unsolved problem upon his mind, would go for\ndays, and even for a week, without rest, turning it over,\nrearranging his facts, looking at it from every point of view\nuntil he had either fathomed it or convinced himself that his\ndata were insufficient. It was soon evident to me that he was now\npreparing for an all-night sitting. He took off his coat and\nwaistcoat, put on a large blue dressing-gown, and then wandered\nabout the room collecting pillows from his bed and cushions from\nthe sofa and armchairs. With these he constructed a sort of\nEastern divan, upon which he perched himself cross-legged, with\nan ounce of shag tobacco and a box of matches laid out in front\nof him. In the dim light of the lamp I saw him sitting there, an\nold briar pipe between his lips, his eyes fixed vacantly upon the\ncorner of the ceiling, the blue smoke curling up from him,\nsilent, motionless, with the light shining upon his strong-set\naquiline features. So he sat as I dropped off to sleep, and so he\nsat when a sudden ejaculation caused me to wake up, and I found\nthe summer sun shining into the apartment. The pipe was still\nbetween his lips, the smoke still curled upward, and the room was\nfull of a dense tobacco haze, but nothing remained of the heap of\nshag which I had seen upon the previous night.\n\n\"Awake, Watson?\" he asked.\n\n\"Yes.\"\n\n\"Game for a morning drive?\"\n\n\"Certainly.\"\n\n\"Then dress. No one is stirring yet, but I know where the\nstable-boy sleeps, and we shall soon have the trap out.\" He\nchuckled to himself as he spoke, his eyes twinkled, and he seemed\na different man to the sombre thinker of the previous night.\n\nAs I dressed I glanced at my watch. It was no wonder that no one\nwas stirring. It was twenty-five minutes past four. I had hardly\nfinished when Holmes returned with the news that the boy was\nputting in the horse.\n\n\"I want to test a little theory of mine,\" said he, pulling on his\nboots. \"I think, Watson, that you are now standing in the\npresence of one of the most absolute fools in Europe. I deserve\nto be kicked from here to Charing Cross. But I think I have the\nkey of the affair now.\"\n\n\"And where is it?\" I asked, smiling.\n\n\"In the bathroom,\" he answered. \"Oh, yes, I am not joking,\" he\ncontinued, seeing my look of incredulity. \"I have just been\nthere, and I have taken it out, and I have got it in this\nGladstone bag. Come on, my boy, and we shall see whether it will\nnot fit the lock.\"\n\nWe made our way downstairs as quietly as possible, and out into\nthe bright morning sunshine. In the road stood our horse and\ntrap, with the half-clad stable-boy waiting at the head. We both\nsprang in, and away we dashed down the London Road. A few country\ncarts were stirring, bearing in vegetables to the metropolis, but\nthe lines of villas on either side were as silent and lifeless as\nsome city in a dream.\n\n\"It has been in some points a singular case,\" said Holmes,\nflicking the horse on into a gallop. \"I confess that I have been\nas blind as a mole, but it is better to learn wisdom late than\nnever to learn it at all.\"\n\nIn town the earliest risers were just beginning to look sleepily\nfrom their windows as we drove through the streets of the Surrey\nside. Passing down the Waterloo Bridge Road we crossed over the\nriver, and dashing up Wellington Street wheeled sharply to the\nright and found ourselves in Bow Street. Sherlock Holmes was well\nknown to the force, and the two constables at the door saluted\nhim. One of them held the horse's head while the other led us in.\n\n\"Who is on duty?\" asked Holmes.\n\n\"Inspector Bradstreet, sir.\"\n\n\"Ah, Bradstreet, how are you?\" A tall, stout official had come\ndown the stone-flagged passage, in a peaked cap and frogged\njacket. \"I wish to have a quiet word with you, Bradstreet.\"\n\"Certainly, Mr. Holmes. Step into my room here.\" It was a small,\noffice-like room, with a huge ledger upon the table, and a\ntelephone projecting from the wall. The inspector sat down at his\ndesk.\n\n\"What can I do for you, Mr. Holmes?\"\n\n\"I called about that beggarman, Boone--the one who was charged\nwith being concerned in the disappearance of Mr. Neville St.\nClair, of Lee.\"\n\n\"Yes. He was brought up and remanded for further inquiries.\"\n\n\"So I heard. You have him here?\"\n\n\"In the cells.\"\n\n\"Is he quiet?\"\n\n\"Oh, he gives no trouble. But he is a dirty scoundrel.\"\n\n\"Dirty?\"\n\n\"Yes, it is all we can do to make him wash his hands, and his\nface is as black as a tinker's. Well, when once his case has been\nsettled, he will have a regular prison bath; and I think, if you\nsaw him, you would agree with me that he needed it.\"\n\n\"I should like to see him very much.\"\n\n\"Would you? That is easily done. Come this way. You can leave\nyour bag.\"\n\n\"No, I think that I'll take it.\"\n\n\"Very good. Come this way, if you please.\" He led us down a\npassage, opened a barred door, passed down a winding stair, and\nbrought us to a whitewashed corridor with a line of doors on each\nside.\n\n\"The third on the right is his,\" said the inspector. \"Here it\nis!\" He quietly shot back a panel in the upper part of the door\nand glanced through.\n\n\"He is asleep,\" said he. \"You can see him very well.\"\n\nWe both put our eyes to the grating. The prisoner lay with his\nface towards us, in a very deep sleep, breathing slowly and\nheavily. He was a middle-sized man, coarsely clad as became his\ncalling, with a coloured shirt protruding through the rent in his\ntattered coat. He was, as the inspector had said, extremely\ndirty, but the grime which covered his face could not conceal its\nrepulsive ugliness. A broad wheal from an old scar ran right\nacross it from eye to chin, and by its contraction had turned up\none side of the upper lip, so that three teeth were exposed in a\nperpetual snarl. A shock of very bright red hair grew low over\nhis eyes and forehead.\n\n\"He's a beauty, isn't he?\" said the inspector.\n\n\"He certainly needs a wash,\" remarked Holmes. \"I had an idea that\nhe might, and I took the liberty of bringing the tools with me.\"\nHe opened the Gladstone bag as he spoke, and took out, to my\nastonishment, a very large bath-sponge.\n\n\"He! he! You are a funny one,\" chuckled the inspector.\n\n\"Now, if you will have the great goodness to open that door very\nquietly, we will soon make him cut a much more respectable\nfigure.\"\n\n\"Well, I don't know why not,\" said the inspector. \"He doesn't\nlook a credit to the Bow Street cells, does he?\" He slipped his\nkey into the lock, and we all very quietly entered the cell. The\nsleeper half turned, and then settled down once more into a deep\nslumber. Holmes stooped to the water-jug, moistened his sponge,\nand then rubbed it twice vigorously across and down the\nprisoner's face.\n\n\"Let me introduce you,\" he shouted, \"to Mr. Neville St. Clair, of\nLee, in the county of Kent.\"\n\nNever in my life have I seen such a sight. The man's face peeled\noff under the sponge like the bark from a tree. Gone was the\ncoarse brown tint! Gone, too, was the horrid scar which had\nseamed it across, and the twisted lip which had given the\nrepulsive sneer to the face! A twitch brought away the tangled\nred hair, and there, sitting up in his bed, was a pale,\nsad-faced, refined-looking man, black-haired and smooth-skinned,\nrubbing his eyes and staring about him with sleepy bewilderment.\nThen suddenly realising the exposure, he broke into a scream and\nthrew himself down with his face to the pillow.\n\n\"Great heavens!\" cried the inspector, \"it is, indeed, the missing\nman. I know him from the photograph.\"\n\nThe prisoner turned with the reckless air of a man who abandons\nhimself to his destiny. \"Be it so,\" said he. \"And pray what am I\ncharged with?\"\n\n\"With making away with Mr. Neville St.-- Oh, come, you can't be\ncharged with that unless they make a case of attempted suicide of\nit,\" said the inspector with a grin. \"Well, I have been\ntwenty-seven years in the force, but this really takes the cake.\"\n\n\"If I am Mr. Neville St. Clair, then it is obvious that no crime\nhas been committed, and that, therefore, I am illegally\ndetained.\"\n\n\"No crime, but a very great error has been committed,\" said\nHolmes. \"You would have done better to have trusted your wife.\"\n\n\"It was not the wife; it was the children,\" groaned the prisoner.\n\"God help me, I would not have them ashamed of their father. My\nGod! What an exposure! What can I do?\"\n\nSherlock Holmes sat down beside him on the couch and patted him\nkindly on the shoulder.\n\n\"If you leave it to a court of law to clear the matter up,\" said\nhe, \"of course you can hardly avoid publicity. On the other hand,\nif you convince the police authorities that there is no possible\ncase against you, I do not know that there is any reason that the\ndetails should find their way into the papers. Inspector\nBradstreet would, I am sure, make notes upon anything which you\nmight tell us and submit it to the proper authorities. The case\nwould then never go into court at all.\"\n\n\"God bless you!\" cried the prisoner passionately. \"I would have\nendured imprisonment, ay, even execution, rather than have left\nmy miserable secret as a family blot to my children.\n\n\"You are the first who have ever heard my story. My father was a\nschoolmaster in Chesterfield, where I received an excellent\neducation. I travelled in my youth, took to the stage, and\nfinally became a reporter on an evening paper in London. One day\nmy editor wished to have a series of articles upon begging in the\nmetropolis, and I volunteered to supply them. There was the point\nfrom which all my adventures started. It was only by trying\nbegging as an amateur that I could get the facts upon which to\nbase my articles. When an actor I had, of course, learned all the\nsecrets of making up, and had been famous in the green-room for\nmy skill. I took advantage now of my attainments. I painted my\nface, and to make myself as pitiable as possible I made a good\nscar and fixed one side of my lip in a twist by the aid of a\nsmall slip of flesh-coloured plaster. Then with a red head of\nhair, and an appropriate dress, I took my station in the business\npart of the city, ostensibly as a match-seller but really as a\nbeggar. For seven hours I plied my trade, and when I returned\nhome in the evening I found to my surprise that I had received no\nless than 26s. 4d.\n\n\"I wrote my articles and thought little more of the matter until,\nsome time later, I backed a bill for a friend and had a writ\nserved upon me for 25 pounds. I was at my wit's end where to get\nthe money, but a sudden idea came to me. I begged a fortnight's\ngrace from the creditor, asked for a holiday from my employers,\nand spent the time in begging in the City under my disguise. In\nten days I had the money and had paid the debt.\n\n\"Well, you can imagine how hard it was to settle down to arduous\nwork at 2 pounds a week when I knew that I could earn as much in\na day by smearing my face with a little paint, laying my cap on\nthe ground, and sitting still. It was a long fight between my\npride and the money, but the dollars won at last, and I threw up\nreporting and sat day after day in the corner which I had first\nchosen, inspiring pity by my ghastly face and filling my pockets\nwith coppers. Only one man knew my secret. He was the keeper of a\nlow den in which I used to lodge in Swandam Lane, where I could\nevery morning emerge as a squalid beggar and in the evenings\ntransform myself into a well-dressed man about town. This fellow,\na Lascar, was well paid by me for his rooms, so that I knew that\nmy secret was safe in his possession.\n\n\"Well, very soon I found that I was saving considerable sums of\nmoney. I do not mean that any beggar in the streets of London\ncould earn 700 pounds a year--which is less than my average\ntakings--but I had exceptional advantages in my power of making\nup, and also in a facility of repartee, which improved by\npractice and made me quite a recognised character in the City.\nAll day a stream of pennies, varied by silver, poured in upon me,\nand it was a very bad day in which I failed to take 2 pounds.\n\n\"As I grew richer I grew more ambitious, took a house in the\ncountry, and eventually married, without anyone having a\nsuspicion as to my real occupation. My dear wife knew that I had\nbusiness in the City. She little knew what.\n\n\"Last Monday I had finished for the day and was dressing in my\nroom above the opium den when I looked out of my window and saw,\nto my horror and astonishment, that my wife was standing in the\nstreet, with her eyes fixed full upon me. I gave a cry of\nsurprise, threw up my arms to cover my face, and, rushing to my\nconfidant, the Lascar, entreated him to prevent anyone from\ncoming up to me. I heard her voice downstairs, but I knew that\nshe could not ascend. Swiftly I threw off my clothes, pulled on\nthose of a beggar, and put on my pigments and wig. Even a wife's\neyes could not pierce so complete a disguise. But then it\noccurred to me that there might be a search in the room, and that\nthe clothes might betray me. I threw open the window, reopening\nby my violence a small cut which I had inflicted upon myself in\nthe bedroom that morning. Then I seized my coat, which was\nweighted by the coppers which I had just transferred to it from\nthe leather bag in which I carried my takings. I hurled it out of\nthe window, and it disappeared into the Thames. The other clothes\nwould have followed, but at that moment there was a rush of\nconstables up the stair, and a few minutes after I found, rather,\nI confess, to my relief, that instead of being identified as Mr.\nNeville St. Clair, I was arrested as his murderer.\n\n\"I do not know that there is anything else for me to explain. I\nwas determined to preserve my disguise as long as possible, and\nhence my preference for a dirty face. Knowing that my wife would\nbe terribly anxious, I slipped off my ring and confided it to the\nLascar at a moment when no constable was watching me, together\nwith a hurried scrawl, telling her that she had no cause to\nfear.\"\n\n\"That note only reached her yesterday,\" said Holmes.\n\n\"Good God! What a week she must have spent!\"\n\n\"The police have watched this Lascar,\" said Inspector Bradstreet,\n\"and I can quite understand that he might find it difficult to\npost a letter unobserved. Probably he handed it to some sailor\ncustomer of his, who forgot all about it for some days.\"\n\n\"That was it,\" said Holmes, nodding approvingly; \"I have no doubt\nof it. But have you never been prosecuted for begging?\"\n\n\"Many times; but what was a fine to me?\"\n\n\"It must stop here, however,\" said Bradstreet. \"If the police are\nto hush this thing up, there must be no more of Hugh Boone.\"\n\n\"I have sworn it by the most solemn oaths which a man can take.\"\n\n\"In that case I think that it is probable that no further steps\nmay be taken. But if you are found again, then all must come out.\nI am sure, Mr. Holmes, that we are very much indebted to you for\nhaving cleared the matter up. I wish I knew how you reach your\nresults.\"\n\n\"I reached this one,\" said my friend, \"by sitting upon five\npillows and consuming an ounce of shag. I think, Watson, that if\nwe drive to Baker Street we shall just be in time for breakfast.\"\n\n\n\nVII. THE ADVENTURE OF THE BLUE CARBUNCLE\n\nI had called upon my friend Sherlock Holmes upon the second\nmorning after Christmas, with the intention of wishing him the\ncompliments of the season. He was lounging upon the sofa in a\npurple dressing-gown, a pipe-rack within his reach upon the\nright, and a pile of crumpled morning papers, evidently newly\nstudied, near at hand. Beside the couch was a wooden chair, and\non the angle of the back hung a very seedy and disreputable\nhard-felt hat, much the worse for wear, and cracked in several\nplaces. A lens and a forceps lying upon the seat of the chair\nsuggested that the hat had been suspended in this manner for the\npurpose of examination.\n\n\"You are engaged,\" said I; \"perhaps I interrupt you.\"\n\n\"Not at all. I am glad to have a friend with whom I can discuss\nmy results. The matter is a perfectly trivial one\"--he jerked his\nthumb in the direction of the old hat--\"but there are points in\nconnection with it which are not entirely devoid of interest and\neven of instruction.\"\n\nI seated myself in his armchair and warmed my hands before his\ncrackling fire, for a sharp frost had set in, and the windows\nwere thick with the ice crystals. \"I suppose,\" I remarked, \"that,\nhomely as it looks, this thing has some deadly story linked on to\nit--that it is the clue which will guide you in the solution of\nsome mystery and the punishment of some crime.\"\n\n\"No, no. No crime,\" said Sherlock Holmes, laughing. \"Only one of\nthose whimsical little incidents which will happen when you have\nfour million human beings all jostling each other within the\nspace of a few square miles. Amid the action and reaction of so\ndense a swarm of humanity, every possible combination of events\nmay be expected to take place, and many a little problem will be\npresented which may be striking and bizarre without being\ncriminal. We have already had experience of such.\"\n\n\"So much so,\" I remarked, \"that of the last six cases which I\nhave added to my notes, three have been entirely free of any\nlegal crime.\"\n\n\"Precisely. You allude to my attempt to recover the Irene Adler\npapers, to the singular case of Miss Mary Sutherland, and to the\nadventure of the man with the twisted lip. Well, I have no doubt\nthat this small matter will fall into the same innocent category.\nYou know Peterson, the commissionaire?\"\n\n\"Yes.\"\n\n\"It is to him that this trophy belongs.\"\n\n\"It is his hat.\"\n\n\"No, no, he found it. Its owner is unknown. I beg that you will\nlook upon it not as a battered billycock but as an intellectual\nproblem. And, first, as to how it came here. It arrived upon\nChristmas morning, in company with a good fat goose, which is, I\nhave no doubt, roasting at this moment in front of Peterson's\nfire. The facts are these: about four o'clock on Christmas\nmorning, Peterson, who, as you know, is a very honest fellow, was\nreturning from some small jollification and was making his way\nhomeward down Tottenham Court Road. In front of him he saw, in\nthe gaslight, a tallish man, walking with a slight stagger, and\ncarrying a white goose slung over his shoulder. As he reached the\ncorner of Goodge Street, a row broke out between this stranger\nand a little knot of roughs. One of the latter knocked off the\nman's hat, on which he raised his stick to defend himself and,\nswinging it over his head, smashed the shop window behind him.\nPeterson had rushed forward to protect the stranger from his\nassailants; but the man, shocked at having broken the window, and\nseeing an official-looking person in uniform rushing towards him,\ndropped his goose, took to his heels, and vanished amid the\nlabyrinth of small streets which lie at the back of Tottenham\nCourt Road. The roughs had also fled at the appearance of\nPeterson, so that he was left in possession of the field of\nbattle, and also of the spoils of victory in the shape of this\nbattered hat and a most unimpeachable Christmas goose.\"\n\n\"Which surely he restored to their owner?\"\n\n\"My dear fellow, there lies the problem. It is true that 'For\nMrs. Henry Baker' was printed upon a small card which was tied to\nthe bird's left leg, and it is also true that the initials 'H.\nB.' are legible upon the lining of this hat, but as there are\nsome thousands of Bakers, and some hundreds of Henry Bakers in\nthis city of ours, it is not easy to restore lost property to any\none of them.\"\n\n\"What, then, did Peterson do?\"\n\n\"He brought round both hat and goose to me on Christmas morning,\nknowing that even the smallest problems are of interest to me.\nThe goose we retained until this morning, when there were signs\nthat, in spite of the slight frost, it would be well that it\nshould be eaten without unnecessary delay. Its finder has carried\nit off, therefore, to fulfil the ultimate destiny of a goose,\nwhile I continue to retain the hat of the unknown gentleman who\nlost his Christmas dinner.\"\n\n\"Did he not advertise?\"\n\n\"No.\"\n\n\"Then, what clue could you have as to his identity?\"\n\n\"Only as much as we can deduce.\"\n\n\"From his hat?\"\n\n\"Precisely.\"\n\n\"But you are joking. What can you gather from this old battered\nfelt?\"\n\n\"Here is my lens. You know my methods. What can you gather\nyourself as to the individuality of the man who has worn this\narticle?\"\n\nI took the tattered object in my hands and turned it over rather\nruefully. It was a very ordinary black hat of the usual round\nshape, hard and much the worse for wear. The lining had been of\nred silk, but was a good deal discoloured. There was no maker's\nname; but, as Holmes had remarked, the initials \"H. B.\" were\nscrawled upon one side. It was pierced in the brim for a\nhat-securer, but the elastic was missing. For the rest, it was\ncracked, exceedingly dusty, and spotted in several places,\nalthough there seemed to have been some attempt to hide the\ndiscoloured patches by smearing them with ink.\n\n\"I can see nothing,\" said I, handing it back to my friend.\n\n\"On the contrary, Watson, you can see everything. You fail,\nhowever, to reason from what you see. You are too timid in\ndrawing your inferences.\"\n\n\"Then, pray tell me what it is that you can infer from this hat?\"\n\nHe picked it up and gazed at it in the peculiar introspective\nfashion which was characteristic of him. \"It is perhaps less\nsuggestive than it might have been,\" he remarked, \"and yet there\nare a few inferences which are very distinct, and a few others\nwhich represent at least a strong balance of probability. That\nthe man was highly intellectual is of course obvious upon the\nface of it, and also that he was fairly well-to-do within the\nlast three years, although he has now fallen upon evil days. He\nhad foresight, but has less now than formerly, pointing to a\nmoral retrogression, which, when taken with the decline of his\nfortunes, seems to indicate some evil influence, probably drink,\nat work upon him. This may account also for the obvious fact that\nhis wife has ceased to love him.\"\n\n\"My dear Holmes!\"\n\n\"He has, however, retained some degree of self-respect,\" he\ncontinued, disregarding my remonstrance. \"He is a man who leads a\nsedentary life, goes out little, is out of training entirely, is\nmiddle-aged, has grizzled hair which he has had cut within the\nlast few days, and which he anoints with lime-cream. These are\nthe more patent facts which are to be deduced from his hat. Also,\nby the way, that it is extremely improbable that he has gas laid\non in his house.\"\n\n\"You are certainly joking, Holmes.\"\n\n\"Not in the least. Is it possible that even now, when I give you\nthese results, you are unable to see how they are attained?\"\n\n\"I have no doubt that I am very stupid, but I must confess that I\nam unable to follow you. For example, how did you deduce that\nthis man was intellectual?\"\n\nFor answer Holmes clapped the hat upon his head. It came right\nover the forehead and settled upon the bridge of his nose. \"It is\na question of cubic capacity,\" said he; \"a man with so large a\nbrain must have something in it.\"\n\n\"The decline of his fortunes, then?\"\n\n\"This hat is three years old. These flat brims curled at the edge\ncame in then. It is a hat of the very best quality. Look at the\nband of ribbed silk and the excellent lining. If this man could\nafford to buy so expensive a hat three years ago, and has had no\nhat since, then he has assuredly gone down in the world.\"\n\n\"Well, that is clear enough, certainly. But how about the\nforesight and the moral retrogression?\"\n\nSherlock Holmes laughed. \"Here is the foresight,\" said he putting\nhis finger upon the little disc and loop of the hat-securer.\n\"They are never sold upon hats. If this man ordered one, it is a\nsign of a certain amount of foresight, since he went out of his\nway to take this precaution against the wind. But since we see\nthat he has broken the elastic and has not troubled to replace\nit, it is obvious that he has less foresight now than formerly,\nwhich is a distinct proof of a weakening nature. On the other\nhand, he has endeavoured to conceal some of these stains upon the\nfelt by daubing them with ink, which is a sign that he has not\nentirely lost his self-respect.\"\n\n\"Your reasoning is certainly plausible.\"\n\n\"The further points, that he is middle-aged, that his hair is\ngrizzled, that it has been recently cut, and that he uses\nlime-cream, are all to be gathered from a close examination of the\nlower part of the lining. The lens discloses a large number of\nhair-ends, clean cut by the scissors of the barber. They all\nappear to be adhesive, and there is a distinct odour of\nlime-cream. This dust, you will observe, is not the gritty, grey\ndust of the street but the fluffy brown dust of the house,\nshowing that it has been hung up indoors most of the time, while\nthe marks of moisture upon the inside are proof positive that the\nwearer perspired very freely, and could therefore, hardly be in\nthe best of training.\"\n\n\"But his wife--you said that she had ceased to love him.\"\n\n\"This hat has not been brushed for weeks. When I see you, my dear\nWatson, with a week's accumulation of dust upon your hat, and\nwhen your wife allows you to go out in such a state, I shall fear\nthat you also have been unfortunate enough to lose your wife's\naffection.\"\n\n\"But he might be a bachelor.\"\n\n\"Nay, he was bringing home the goose as a peace-offering to his\nwife. Remember the card upon the bird's leg.\"\n\n\"You have an answer to everything. But how on earth do you deduce\nthat the gas is not laid on in his house?\"\n\n\"One tallow stain, or even two, might come by chance; but when I\nsee no less than five, I think that there can be little doubt\nthat the individual must be brought into frequent contact with\nburning tallow--walks upstairs at night probably with his hat in\none hand and a guttering candle in the other. Anyhow, he never\ngot tallow-stains from a gas-jet. Are you satisfied?\"\n\n\"Well, it is very ingenious,\" said I, laughing; \"but since, as\nyou said just now, there has been no crime committed, and no harm\ndone save the loss of a goose, all this seems to be rather a\nwaste of energy.\"\n\nSherlock Holmes had opened his mouth to reply, when the door flew\nopen, and Peterson, the commissionaire, rushed into the apartment\nwith flushed cheeks and the face of a man who is dazed with\nastonishment.\n\n\"The goose, Mr. Holmes! The goose, sir!\" he gasped.\n\n\"Eh? What of it, then? Has it returned to life and flapped off\nthrough the kitchen window?\" Holmes twisted himself round upon\nthe sofa to get a fairer view of the man's excited face.\n\n\"See here, sir! See what my wife found in its crop!\" He held out\nhis hand and displayed upon the centre of the palm a brilliantly\nscintillating blue stone, rather smaller than a bean in size, but\nof such purity and radiance that it twinkled like an electric\npoint in the dark hollow of his hand.\n\nSherlock Holmes sat up with a whistle. \"By Jove, Peterson!\" said\nhe, \"this is treasure trove indeed. I suppose you know what you\nhave got?\"\n\n\"A diamond, sir? A precious stone. It cuts into glass as though\nit were putty.\"\n\n\"It's more than a precious stone. It is the precious stone.\"\n\n\"Not the Countess of Morcar's blue carbuncle!\" I ejaculated.\n\n\"Precisely so. I ought to know its size and shape, seeing that I\nhave read the advertisement about it in The Times every day\nlately. It is absolutely unique, and its value can only be\nconjectured, but the reward offered of 1000 pounds is certainly\nnot within a twentieth part of the market price.\"\n\n\"A thousand pounds! Great Lord of mercy!\" The commissionaire\nplumped down into a chair and stared from one to the other of us.\n\n\"That is the reward, and I have reason to know that there are\nsentimental considerations in the background which would induce\nthe Countess to part with half her fortune if she could but\nrecover the gem.\"\n\n\"It was lost, if I remember aright, at the Hotel Cosmopolitan,\" I\nremarked.\n\n\"Precisely so, on December 22nd, just five days ago. John Horner,\na plumber, was accused of having abstracted it from the lady's\njewel-case. The evidence against him was so strong that the case\nhas been referred to the Assizes. I have some account of the\nmatter here, I believe.\" He rummaged amid his newspapers,\nglancing over the dates, until at last he smoothed one out,\ndoubled it over, and read the following paragraph:\n\n\"Hotel Cosmopolitan Jewel Robbery. John Horner, 26, plumber, was\nbrought up upon the charge of having upon the 22nd inst.,\nabstracted from the jewel-case of the Countess of Morcar the\nvaluable gem known as the blue carbuncle. James Ryder,\nupper-attendant at the hotel, gave his evidence to the effect\nthat he had shown Horner up to the dressing-room of the Countess\nof Morcar upon the day of the robbery in order that he might\nsolder the second bar of the grate, which was loose. He had\nremained with Horner some little time, but had finally been\ncalled away. On returning, he found that Horner had disappeared,\nthat the bureau had been forced open, and that the small morocco\ncasket in which, as it afterwards transpired, the Countess was\naccustomed to keep her jewel, was lying empty upon the\ndressing-table. Ryder instantly gave the alarm, and Horner was\narrested the same evening; but the stone could not be found\neither upon his person or in his rooms. Catherine Cusack, maid to\nthe Countess, deposed to having heard Ryder's cry of dismay on\ndiscovering the robbery, and to having rushed into the room,\nwhere she found matters as described by the last witness.\nInspector Bradstreet, B division, gave evidence as to the arrest\nof Horner, who struggled frantically, and protested his innocence\nin the strongest terms. Evidence of a previous conviction for\nrobbery having been given against the prisoner, the magistrate\nrefused to deal summarily with the offence, but referred it to\nthe Assizes. Horner, who had shown signs of intense emotion\nduring the proceedings, fainted away at the conclusion and was\ncarried out of court.\"\n\n\"Hum! So much for the police-court,\" said Holmes thoughtfully,\ntossing aside the paper. \"The question for us now to solve is the\nsequence of events leading from a rifled jewel-case at one end to\nthe crop of a goose in Tottenham Court Road at the other. You\nsee, Watson, our little deductions have suddenly assumed a much\nmore important and less innocent aspect. Here is the stone; the\nstone came from the goose, and the goose came from Mr. Henry\nBaker, the gentleman with the bad hat and all the other\ncharacteristics with which I have bored you. So now we must set\nourselves very seriously to finding this gentleman and\nascertaining what part he has played in this little mystery. To\ndo this, we must try the simplest means first, and these lie\nundoubtedly in an advertisement in all the evening papers. If\nthis fail, I shall have recourse to other methods.\"\n\n\"What will you say?\"\n\n\"Give me a pencil and that slip of paper. Now, then: 'Found at\nthe corner of Goodge Street, a goose and a black felt hat. Mr.\nHenry Baker can have the same by applying at 6:30 this evening at\n221B, Baker Street.' That is clear and concise.\"\n\n\"Very. But will he see it?\"\n\n\"Well, he is sure to keep an eye on the papers, since, to a poor\nman, the loss was a heavy one. He was clearly so scared by his\nmischance in breaking the window and by the approach of Peterson\nthat he thought of nothing but flight, but since then he must\nhave bitterly regretted the impulse which caused him to drop his\nbird. Then, again, the introduction of his name will cause him to\nsee it, for everyone who knows him will direct his attention to\nit. Here you are, Peterson, run down to the advertising agency\nand have this put in the evening papers.\"\n\n\"In which, sir?\"\n\n\"Oh, in the Globe, Star, Pall Mall, St. James's, Evening News,\nStandard, Echo, and any others that occur to you.\"\n\n\"Very well, sir. And this stone?\"\n\n\"Ah, yes, I shall keep the stone. Thank you. And, I say,\nPeterson, just buy a goose on your way back and leave it here\nwith me, for we must have one to give to this gentleman in place\nof the one which your family is now devouring.\"\n\nWhen the commissionaire had gone, Holmes took up the stone and\nheld it against the light. \"It's a bonny thing,\" said he. \"Just\nsee how it glints and sparkles. Of course it is a nucleus and\nfocus of crime. Every good stone is. They are the devil's pet\nbaits. In the larger and older jewels every facet may stand for a\nbloody deed. This stone is not yet twenty years old. It was found\nin the banks of the Amoy River in southern China and is remarkable\nin having every characteristic of the carbuncle, save that it is\nblue in shade instead of ruby red. In spite of its youth, it has\nalready a sinister history. There have been two murders, a\nvitriol-throwing, a suicide, and several robberies brought about\nfor the sake of this forty-grain weight of crystallised charcoal.\nWho would think that so pretty a toy would be a purveyor to the\ngallows and the prison? I'll lock it up in my strong box now and\ndrop a line to the Countess to say that we have it.\"\n\n\"Do you think that this man Horner is innocent?\"\n\n\"I cannot tell.\"\n\n\"Well, then, do you imagine that this other one, Henry Baker, had\nanything to do with the matter?\"\n\n\"It is, I think, much more likely that Henry Baker is an\nabsolutely innocent man, who had no idea that the bird which he\nwas carrying was of considerably more value than if it were made\nof solid gold. That, however, I shall determine by a very simple\ntest if we have an answer to our advertisement.\"\n\n\"And you can do nothing until then?\"\n\n\"Nothing.\"\n\n\"In that case I shall continue my professional round. But I shall\ncome back in the evening at the hour you have mentioned, for I\nshould like to see the solution of so tangled a business.\"\n\n\"Very glad to see you. I dine at seven. There is a woodcock, I\nbelieve. By the way, in view of recent occurrences, perhaps I\nought to ask Mrs. Hudson to examine its crop.\"\n\nI had been delayed at a case, and it was a little after half-past\nsix when I found myself in Baker Street once more. As I\napproached the house I saw a tall man in a Scotch bonnet with a\ncoat which was buttoned up to his chin waiting outside in the\nbright semicircle which was thrown from the fanlight. Just as I\narrived the door was opened, and we were shown up together to\nHolmes' room.\n\n\"Mr. Henry Baker, I believe,\" said he, rising from his armchair\nand greeting his visitor with the easy air of geniality which he\ncould so readily assume. \"Pray take this chair by the fire, Mr.\nBaker. It is a cold night, and I observe that your circulation is\nmore adapted for summer than for winter. Ah, Watson, you have\njust come at the right time. Is that your hat, Mr. Baker?\"\n\n\"Yes, sir, that is undoubtedly my hat.\"\n\nHe was a large man with rounded shoulders, a massive head, and a\nbroad, intelligent face, sloping down to a pointed beard of\ngrizzled brown. A touch of red in nose and cheeks, with a slight\ntremor of his extended hand, recalled Holmes' surmise as to his\nhabits. His rusty black frock-coat was buttoned right up in\nfront, with the collar turned up, and his lank wrists protruded\nfrom his sleeves without a sign of cuff or shirt. He spoke in a\nslow staccato fashion, choosing his words with care, and gave the\nimpression generally of a man of learning and letters who had had\nill-usage at the hands of fortune.\n\n\"We have retained these things for some days,\" said Holmes,\n\"because we expected to see an advertisement from you giving your\naddress. I am at a loss to know now why you did not advertise.\"\n\nOur visitor gave a rather shamefaced laugh. \"Shillings have not\nbeen so plentiful with me as they once were,\" he remarked. \"I had\nno doubt that the gang of roughs who assaulted me had carried off\nboth my hat and the bird. I did not care to spend more money in a\nhopeless attempt at recovering them.\"\n\n\"Very naturally. By the way, about the bird, we were compelled to\neat it.\"\n\n\"To eat it!\" Our visitor half rose from his chair in his\nexcitement.\n\n\"Yes, it would have been of no use to anyone had we not done so.\nBut I presume that this other goose upon the sideboard, which is\nabout the same weight and perfectly fresh, will answer your\npurpose equally well?\"\n\n\"Oh, certainly, certainly,\" answered Mr. Baker with a sigh of\nrelief.\n\n\"Of course, we still have the feathers, legs, crop, and so on of\nyour own bird, so if you wish--\"\n\nThe man burst into a hearty laugh. \"They might be useful to me as\nrelics of my adventure,\" said he, \"but beyond that I can hardly\nsee what use the disjecta membra of my late acquaintance are\ngoing to be to me. No, sir, I think that, with your permission, I\nwill confine my attentions to the excellent bird which I perceive\nupon the sideboard.\"\n\nSherlock Holmes glanced sharply across at me with a slight shrug\nof his shoulders.\n\n\"There is your hat, then, and there your bird,\" said he. \"By the\nway, would it bore you to tell me where you got the other one\nfrom? I am somewhat of a fowl fancier, and I have seldom seen a\nbetter grown goose.\"\n\n\"Certainly, sir,\" said Baker, who had risen and tucked his newly\ngained property under his arm. \"There are a few of us who\nfrequent the Alpha Inn, near the Museum--we are to be found in\nthe Museum itself during the day, you understand. This year our\ngood host, Windigate by name, instituted a goose club, by which,\non consideration of some few pence every week, we were each to\nreceive a bird at Christmas. My pence were duly paid, and the\nrest is familiar to you. I am much indebted to you, sir, for a\nScotch bonnet is fitted neither to my years nor my gravity.\" With\na comical pomposity of manner he bowed solemnly to both of us and\nstrode off upon his way.\n\n\"So much for Mr. Henry Baker,\" said Holmes when he had closed the\ndoor behind him. \"It is quite certain that he knows nothing\nwhatever about the matter. Are you hungry, Watson?\"\n\n\"Not particularly.\"\n\n\"Then I suggest that we turn our dinner into a supper and follow\nup this clue while it is still hot.\"\n\n\"By all means.\"\n\nIt was a bitter night, so we drew on our ulsters and wrapped\ncravats about our throats. Outside, the stars were shining coldly\nin a cloudless sky, and the breath of the passers-by blew out\ninto smoke like so many pistol shots. Our footfalls rang out\ncrisply and loudly as we swung through the doctors' quarter,\nWimpole Street, Harley Street, and so through Wigmore Street into\nOxford Street. In a quarter of an hour we were in Bloomsbury at\nthe Alpha Inn, which is a small public-house at the corner of one\nof the streets which runs down into Holborn. Holmes pushed open\nthe door of the private bar and ordered two glasses of beer from\nthe ruddy-faced, white-aproned landlord.\n\n\"Your beer should be excellent if it is as good as your geese,\"\nsaid he.\n\n\"My geese!\" The man seemed surprised.\n\n\"Yes. I was speaking only half an hour ago to Mr. Henry Baker,\nwho was a member of your goose club.\"\n\n\"Ah! yes, I see. But you see, sir, them's not our geese.\"\n\n\"Indeed! Whose, then?\"\n\n\"Well, I got the two dozen from a salesman in Covent Garden.\"\n\n\"Indeed? I know some of them. Which was it?\"\n\n\"Breckinridge is his name.\"\n\n\"Ah! I don't know him. Well, here's your good health landlord,\nand prosperity to your house. Good-night.\"\n\n\"Now for Mr. Breckinridge,\" he continued, buttoning up his coat\nas we came out into the frosty air. \"Remember, Watson that though\nwe have so homely a thing as a goose at one end of this chain, we\nhave at the other a man who will certainly get seven years' penal\nservitude unless we can establish his innocence. It is possible\nthat our inquiry may but confirm his guilt; but, in any case, we\nhave a line of investigation which has been missed by the police,\nand which a singular chance has placed in our hands. Let us\nfollow it out to the bitter end. Faces to the south, then, and\nquick march!\"\n\nWe passed across Holborn, down Endell Street, and so through a\nzigzag of slums to Covent Garden Market. One of the largest\nstalls bore the name of Breckinridge upon it, and the proprietor\na horsey-looking man, with a sharp face and trim side-whiskers was\nhelping a boy to put up the shutters.\n\n\"Good-evening. It's a cold night,\" said Holmes.\n\nThe salesman nodded and shot a questioning glance at my\ncompanion.\n\n\"Sold out of geese, I see,\" continued Holmes, pointing at the\nbare slabs of marble.\n\n\"Let you have five hundred to-morrow morning.\"\n\n\"That's no good.\"\n\n\"Well, there are some on the stall with the gas-flare.\"\n\n\"Ah, but I was recommended to you.\"\n\n\"Who by?\"\n\n\"The landlord of the Alpha.\"\n\n\"Oh, yes; I sent him a couple of dozen.\"\n\n\"Fine birds they were, too. Now where did you get them from?\"\n\nTo my surprise the question provoked a burst of anger from the\nsalesman.\n\n\"Now, then, mister,\" said he, with his head cocked and his arms\nakimbo, \"what are you driving at? Let's have it straight, now.\"\n\n\"It is straight enough. I should like to know who sold you the\ngeese which you supplied to the Alpha.\"\n\n\"Well then, I shan't tell you. So now!\"\n\n\"Oh, it is a matter of no importance; but I don't know why you\nshould be so warm over such a trifle.\"\n\n\"Warm! You'd be as warm, maybe, if you were as pestered as I am.\nWhen I pay good money for a good article there should be an end\nof the business; but it's 'Where are the geese?' and 'Who did you\nsell the geese to?' and 'What will you take for the geese?' One\nwould think they were the only geese in the world, to hear the\nfuss that is made over them.\"\n\n\"Well, I have no connection with any other people who have been\nmaking inquiries,\" said Holmes carelessly. \"If you won't tell us\nthe bet is off, that is all. But I'm always ready to back my\nopinion on a matter of fowls, and I have a fiver on it that the\nbird I ate is country bred.\"\n\n\"Well, then, you've lost your fiver, for it's town bred,\" snapped\nthe salesman.\n\n\"It's nothing of the kind.\"\n\n\"I say it is.\"\n\n\"I don't believe it.\"\n\n\"D'you think you know more about fowls than I, who have handled\nthem ever since I was a nipper? I tell you, all those birds that\nwent to the Alpha were town bred.\"\n\n\"You'll never persuade me to believe that.\"\n\n\"Will you bet, then?\"\n\n\"It's merely taking your money, for I know that I am right. But\nI'll have a sovereign on with you, just to teach you not to be\nobstinate.\"\n\nThe salesman chuckled grimly. \"Bring me the books, Bill,\" said\nhe.\n\nThe small boy brought round a small thin volume and a great\ngreasy-backed one, laying them out together beneath the hanging\nlamp.\n\n\"Now then, Mr. Cocksure,\" said the salesman, \"I thought that I\nwas out of geese, but before I finish you'll find that there is\nstill one left in my shop. You see this little book?\"\n\n\"Well?\"\n\n\"That's the list of the folk from whom I buy. D'you see? Well,\nthen, here on this page are the country folk, and the numbers\nafter their names are where their accounts are in the big ledger.\nNow, then! You see this other page in red ink? Well, that is a\nlist of my town suppliers. Now, look at that third name. Just\nread it out to me.\"\n\n\"Mrs. Oakshott, 117, Brixton Road--249,\" read Holmes.\n\n\"Quite so. Now turn that up in the ledger.\"\n\nHolmes turned to the page indicated. \"Here you are, 'Mrs.\nOakshott, 117, Brixton Road, egg and poultry supplier.'\"\n\n\"Now, then, what's the last entry?\"\n\n\"'December 22nd. Twenty-four geese at 7s. 6d.'\"\n\n\"Quite so. There you are. And underneath?\"\n\n\"'Sold to Mr. Windigate of the Alpha, at 12s.'\"\n\n\"What have you to say now?\"\n\nSherlock Holmes looked deeply chagrined. He drew a sovereign from\nhis pocket and threw it down upon the slab, turning away with the\nair of a man whose disgust is too deep for words. A few yards off\nhe stopped under a lamp-post and laughed in the hearty, noiseless\nfashion which was peculiar to him.\n\n\"When you see a man with whiskers of that cut and the 'Pink 'un'\nprotruding out of his pocket, you can always draw him by a bet,\"\nsaid he. \"I daresay that if I had put 100 pounds down in front of\nhim, that man would not have given me such complete information\nas was drawn from him by the idea that he was doing me on a\nwager. Well, Watson, we are, I fancy, nearing the end of our\nquest, and the only point which remains to be determined is\nwhether we should go on to this Mrs. Oakshott to-night, or\nwhether we should reserve it for to-morrow. It is clear from what\nthat surly fellow said that there are others besides ourselves\nwho are anxious about the matter, and I should--\"\n\nHis remarks were suddenly cut short by a loud hubbub which broke\nout from the stall which we had just left. Turning round we saw a\nlittle rat-faced fellow standing in the centre of the circle of\nyellow light which was thrown by the swinging lamp, while\nBreckinridge, the salesman, framed in the door of his stall, was\nshaking his fists fiercely at the cringing figure.\n\n\"I've had enough of you and your geese,\" he shouted. \"I wish you\nwere all at the devil together. If you come pestering me any more\nwith your silly talk I'll set the dog at you. You bring Mrs.\nOakshott here and I'll answer her, but what have you to do with\nit? Did I buy the geese off you?\"\n\n\"No; but one of them was mine all the same,\" whined the little\nman.\n\n\"Well, then, ask Mrs. Oakshott for it.\"\n\n\"She told me to ask you.\"\n\n\"Well, you can ask the King of Proosia, for all I care. I've had\nenough of it. Get out of this!\" He rushed fiercely forward, and\nthe inquirer flitted away into the darkness.\n\n\"Ha! this may save us a visit to Brixton Road,\" whispered Holmes.\n\"Come with me, and we will see what is to be made of this\nfellow.\" Striding through the scattered knots of people who\nlounged round the flaring stalls, my companion speedily overtook\nthe little man and touched him upon the shoulder. He sprang\nround, and I could see in the gas-light that every vestige of\ncolour had been driven from his face.\n\n\"Who are you, then? What do you want?\" he asked in a quavering\nvoice.\n\n\"You will excuse me,\" said Holmes blandly, \"but I could not help\noverhearing the questions which you put to the salesman just now.\nI think that I could be of assistance to you.\"\n\n\"You? Who are you? How could you know anything of the matter?\"\n\n\"My name is Sherlock Holmes. It is my business to know what other\npeople don't know.\"\n\n\"But you can know nothing of this?\"\n\n\"Excuse me, I know everything of it. You are endeavouring to\ntrace some geese which were sold by Mrs. Oakshott, of Brixton\nRoad, to a salesman named Breckinridge, by him in turn to Mr.\nWindigate, of the Alpha, and by him to his club, of which Mr.\nHenry Baker is a member.\"\n\n\"Oh, sir, you are the very man whom I have longed to meet,\" cried\nthe little fellow with outstretched hands and quivering fingers.\n\"I can hardly explain to you how interested I am in this matter.\"\n\nSherlock Holmes hailed a four-wheeler which was passing. \"In that\ncase we had better discuss it in a cosy room rather than in this\nwind-swept market-place,\" said he. \"But pray tell me, before we\ngo farther, who it is that I have the pleasure of assisting.\"\n\nThe man hesitated for an instant. \"My name is John Robinson,\" he\nanswered with a sidelong glance.\n\n\"No, no; the real name,\" said Holmes sweetly. \"It is always\nawkward doing business with an alias.\"\n\nA flush sprang to the white cheeks of the stranger. \"Well then,\"\nsaid he, \"my real name is James Ryder.\"\n\n\"Precisely so. Head attendant at the Hotel Cosmopolitan. Pray\nstep into the cab, and I shall soon be able to tell you\neverything which you would wish to know.\"\n\nThe little man stood glancing from one to the other of us with\nhalf-frightened, half-hopeful eyes, as one who is not sure\nwhether he is on the verge of a windfall or of a catastrophe.\nThen he stepped into the cab, and in half an hour we were back in\nthe sitting-room at Baker Street. Nothing had been said during\nour drive, but the high, thin breathing of our new companion, and\nthe claspings and unclaspings of his hands, spoke of the nervous\ntension within him.\n\n\"Here we are!\" said Holmes cheerily as we filed into the room.\n\"The fire looks very seasonable in this weather. You look cold,\nMr. Ryder. Pray take the basket-chair. I will just put on my\nslippers before we settle this little matter of yours. Now, then!\nYou want to know what became of those geese?\"\n\n\"Yes, sir.\"\n\n\"Or rather, I fancy, of that goose. It was one bird, I imagine in\nwhich you were interested--white, with a black bar across the\ntail.\"\n\nRyder quivered with emotion. \"Oh, sir,\" he cried, \"can you tell\nme where it went to?\"\n\n\"It came here.\"\n\n\"Here?\"\n\n\"Yes, and a most remarkable bird it proved. I don't wonder that\nyou should take an interest in it. It laid an egg after it was\ndead--the bonniest, brightest little blue egg that ever was seen.\nI have it here in my museum.\"\n\nOur visitor staggered to his feet and clutched the mantelpiece\nwith his right hand. Holmes unlocked his strong-box and held up\nthe blue carbuncle, which shone out like a star, with a cold,\nbrilliant, many-pointed radiance. Ryder stood glaring with a\ndrawn face, uncertain whether to claim or to disown it.\n\n\"The game's up, Ryder,\" said Holmes quietly. \"Hold up, man, or\nyou'll be into the fire! Give him an arm back into his chair,\nWatson. He's not got blood enough to go in for felony with\nimpunity. Give him a dash of brandy. So! Now he looks a little\nmore human. What a shrimp it is, to be sure!\"\n\nFor a moment he had staggered and nearly fallen, but the brandy\nbrought a tinge of colour into his cheeks, and he sat staring\nwith frightened eyes at his accuser.\n\n\"I have almost every link in my hands, and all the proofs which I\ncould possibly need, so there is little which you need tell me.\nStill, that little may as well be cleared up to make the case\ncomplete. You had heard, Ryder, of this blue stone of the\nCountess of Morcar's?\"\n\n\"It was Catherine Cusack who told me of it,\" said he in a\ncrackling voice.\n\n\"I see--her ladyship's waiting-maid. Well, the temptation of\nsudden wealth so easily acquired was too much for you, as it has\nbeen for better men before you; but you were not very scrupulous\nin the means you used. It seems to me, Ryder, that there is the\nmaking of a very pretty villain in you. You knew that this man\nHorner, the plumber, had been concerned in some such matter\nbefore, and that suspicion would rest the more readily upon him.\nWhat did you do, then? You made some small job in my lady's\nroom--you and your confederate Cusack--and you managed that he\nshould be the man sent for. Then, when he had left, you rifled\nthe jewel-case, raised the alarm, and had this unfortunate man\narrested. You then--\"\n\nRyder threw himself down suddenly upon the rug and clutched at my\ncompanion's knees. \"For God's sake, have mercy!\" he shrieked.\n\"Think of my father! Of my mother! It would break their hearts. I\nnever went wrong before! I never will again. I swear it. I'll\nswear it on a Bible. Oh, don't bring it into court! For Christ's\nsake, don't!\"\n\n\"Get back into your chair!\" said Holmes sternly. \"It is very well\nto cringe and crawl now, but you thought little enough of this\npoor Horner in the dock for a crime of which he knew nothing.\"\n\n\"I will fly, Mr. Holmes. I will leave the country, sir. Then the\ncharge against him will break down.\"\n\n\"Hum! We will talk about that. And now let us hear a true account\nof the next act. How came the stone into the goose, and how came\nthe goose into the open market? Tell us the truth, for there lies\nyour only hope of safety.\"\n\nRyder passed his tongue over his parched lips. \"I will tell you\nit just as it happened, sir,\" said he. \"When Horner had been\narrested, it seemed to me that it would be best for me to get\naway with the stone at once, for I did not know at what moment\nthe police might not take it into their heads to search me and my\nroom. There was no place about the hotel where it would be safe.\nI went out, as if on some commission, and I made for my sister's\nhouse. She had married a man named Oakshott, and lived in Brixton\nRoad, where she fattened fowls for the market. All the way there\nevery man I met seemed to me to be a policeman or a detective;\nand, for all that it was a cold night, the sweat was pouring down\nmy face before I came to the Brixton Road. My sister asked me\nwhat was the matter, and why I was so pale; but I told her that I\nhad been upset by the jewel robbery at the hotel. Then I went\ninto the back yard and smoked a pipe and wondered what it would\nbe best to do.\n\n\"I had a friend once called Maudsley, who went to the bad, and\nhas just been serving his time in Pentonville. One day he had met\nme, and fell into talk about the ways of thieves, and how they\ncould get rid of what they stole. I knew that he would be true to\nme, for I knew one or two things about him; so I made up my mind\nto go right on to Kilburn, where he lived, and take him into my\nconfidence. He would show me how to turn the stone into money.\nBut how to get to him in safety? I thought of the agonies I had\ngone through in coming from the hotel. I might at any moment be\nseized and searched, and there would be the stone in my waistcoat\npocket. I was leaning against the wall at the time and looking at\nthe geese which were waddling about round my feet, and suddenly\nan idea came into my head which showed me how I could beat the\nbest detective that ever lived.\n\n\"My sister had told me some weeks before that I might have the\npick of her geese for a Christmas present, and I knew that she\nwas always as good as her word. I would take my goose now, and in\nit I would carry my stone to Kilburn. There was a little shed in\nthe yard, and behind this I drove one of the birds--a fine big\none, white, with a barred tail. I caught it, and prying its bill\nopen, I thrust the stone down its throat as far as my finger\ncould reach. The bird gave a gulp, and I felt the stone pass\nalong its gullet and down into its crop. But the creature flapped\nand struggled, and out came my sister to know what was the\nmatter. As I turned to speak to her the brute broke loose and\nfluttered off among the others.\n\n\"'Whatever were you doing with that bird, Jem?' says she.\n\n\"'Well,' said I, 'you said you'd give me one for Christmas, and I\nwas feeling which was the fattest.'\n\n\"'Oh,' says she, 'we've set yours aside for you--Jem's bird, we\ncall it. It's the big white one over yonder. There's twenty-six\nof them, which makes one for you, and one for us, and two dozen\nfor the market.'\n\n\"'Thank you, Maggie,' says I; 'but if it is all the same to you,\nI'd rather have that one I was handling just now.'\n\n\"'The other is a good three pound heavier,' said she, 'and we\nfattened it expressly for you.'\n\n\"'Never mind. I'll have the other, and I'll take it now,' said I.\n\n\"'Oh, just as you like,' said she, a little huffed. 'Which is it\nyou want, then?'\n\n\"'That white one with the barred tail, right in the middle of the\nflock.'\n\n\"'Oh, very well. Kill it and take it with you.'\n\n\"Well, I did what she said, Mr. Holmes, and I carried the bird\nall the way to Kilburn. I told my pal what I had done, for he was\na man that it was easy to tell a thing like that to. He laughed\nuntil he choked, and we got a knife and opened the goose. My\nheart turned to water, for there was no sign of the stone, and I\nknew that some terrible mistake had occurred. I left the bird,\nrushed back to my sister's, and hurried into the back yard. There\nwas not a bird to be seen there.\n\n\"'Where are they all, Maggie?' I cried.\n\n\"'Gone to the dealer's, Jem.'\n\n\"'Which dealer's?'\n\n\"'Breckinridge, of Covent Garden.'\n\n\"'But was there another with a barred tail?' I asked, 'the same\nas the one I chose?'\n\n\"'Yes, Jem; there were two barred-tailed ones, and I could never\ntell them apart.'\n\n\"Well, then, of course I saw it all, and I ran off as hard as my\nfeet would carry me to this man Breckinridge; but he had sold the\nlot at once, and not one word would he tell me as to where they\nhad gone. You heard him yourselves to-night. Well, he has always\nanswered me like that. My sister thinks that I am going mad.\nSometimes I think that I am myself. And now--and now I am myself\na branded thief, without ever having touched the wealth for which\nI sold my character. God help me! God help me!\" He burst into\nconvulsive sobbing, with his face buried in his hands.\n\nThere was a long silence, broken only by his heavy breathing and\nby the measured tapping of Sherlock Holmes' finger-tips upon the\nedge of the table. Then my friend rose and threw open the door.\n\n\"Get out!\" said he.\n\n\"What, sir! Oh, Heaven bless you!\"\n\n\"No more words. Get out!\"\n\nAnd no more words were needed. There was a rush, a clatter upon\nthe stairs, the bang of a door, and the crisp rattle of running\nfootfalls from the street.\n\n\"After all, Watson,\" said Holmes, reaching up his hand for his\nclay pipe, \"I am not retained by the police to supply their\ndeficiencies. If Horner were in danger it would be another thing;\nbut this fellow will not appear against him, and the case must\ncollapse. I suppose that I am commuting a felony, but it is just\npossible that I am saving a soul. This fellow will not go wrong\nagain; he is too terribly frightened. Send him to gaol now, and\nyou make him a gaol-bird for life. Besides, it is the season of\nforgiveness. Chance has put in our way a most singular and\nwhimsical problem, and its solution is its own reward. If you\nwill have the goodness to touch the bell, Doctor, we will begin\nanother investigation, in which, also a bird will be the chief\nfeature.\"\n\n\n\nVIII. THE ADVENTURE OF THE SPECKLED BAND\n\nOn glancing over my notes of the seventy odd cases in which I\nhave during the last eight years studied the methods of my friend\nSherlock Holmes, I find many tragic, some comic, a large number\nmerely strange, but none commonplace; for, working as he did\nrather for the love of his art than for the acquirement of\nwealth, he refused to associate himself with any investigation\nwhich did not tend towards the unusual, and even the fantastic.\nOf all these varied cases, however, I cannot recall any which\npresented more singular features than that which was associated\nwith the well-known Surrey family of the Roylotts of Stoke Moran.\nThe events in question occurred in the early days of my\nassociation with Holmes, when we were sharing rooms as bachelors\nin Baker Street. It is possible that I might have placed them\nupon record before, but a promise of secrecy was made at the\ntime, from which I have only been freed during the last month by\nthe untimely death of the lady to whom the pledge was given. It\nis perhaps as well that the facts should now come to light, for I\nhave reasons to know that there are widespread rumours as to the\ndeath of Dr. Grimesby Roylott which tend to make the matter even\nmore terrible than the truth.\n\nIt was early in April in the year '83 that I woke one morning to\nfind Sherlock Holmes standing, fully dressed, by the side of my\nbed. He was a late riser, as a rule, and as the clock on the\nmantelpiece showed me that it was only a quarter-past seven, I\nblinked up at him in some surprise, and perhaps just a little\nresentment, for I was myself regular in my habits.\n\n\"Very sorry to knock you up, Watson,\" said he, \"but it's the\ncommon lot this morning. Mrs. Hudson has been knocked up, she\nretorted upon me, and I on you.\"\n\n\"What is it, then--a fire?\"\n\n\"No; a client. It seems that a young lady has arrived in a\nconsiderable state of excitement, who insists upon seeing me. She\nis waiting now in the sitting-room. Now, when young ladies wander\nabout the metropolis at this hour of the morning, and knock\nsleepy people up out of their beds, I presume that it is\nsomething very pressing which they have to communicate. Should it\nprove to be an interesting case, you would, I am sure, wish to\nfollow it from the outset. I thought, at any rate, that I should\ncall you and give you the chance.\"\n\n\"My dear fellow, I would not miss it for anything.\"\n\nI had no keener pleasure than in following Holmes in his\nprofessional investigations, and in admiring the rapid\ndeductions, as swift as intuitions, and yet always founded on a\nlogical basis with which he unravelled the problems which were\nsubmitted to him. I rapidly threw on my clothes and was ready in\na few minutes to accompany my friend down to the sitting-room. A\nlady dressed in black and heavily veiled, who had been sitting in\nthe window, rose as we entered.\n\n\"Good-morning, madam,\" said Holmes cheerily. \"My name is Sherlock\nHolmes. This is my intimate friend and associate, Dr. Watson,\nbefore whom you can speak as freely as before myself. Ha! I am\nglad to see that Mrs. Hudson has had the good sense to light the\nfire. Pray draw up to it, and I shall order you a cup of hot\ncoffee, for I observe that you are shivering.\"\n\n\"It is not cold which makes me shiver,\" said the woman in a low\nvoice, changing her seat as requested.\n\n\"What, then?\"\n\n\"It is fear, Mr. Holmes. It is terror.\" She raised her veil as\nshe spoke, and we could see that she was indeed in a pitiable\nstate of agitation, her face all drawn and grey, with restless\nfrightened eyes, like those of some hunted animal. Her features\nand figure were those of a woman of thirty, but her hair was shot\nwith premature grey, and her expression was weary and haggard.\nSherlock Holmes ran her over with one of his quick,\nall-comprehensive glances.\n\n\"You must not fear,\" said he soothingly, bending forward and\npatting her forearm. \"We shall soon set matters right, I have no\ndoubt. You have come in by train this morning, I see.\"\n\n\"You know me, then?\"\n\n\"No, but I observe the second half of a return ticket in the palm\nof your left glove. You must have started early, and yet you had\na good drive in a dog-cart, along heavy roads, before you reached\nthe station.\"\n\nThe lady gave a violent start and stared in bewilderment at my\ncompanion.\n\n\"There is no mystery, my dear madam,\" said he, smiling. \"The left\narm of your jacket is spattered with mud in no less than seven\nplaces. The marks are perfectly fresh. There is no vehicle save a\ndog-cart which throws up mud in that way, and then only when you\nsit on the left-hand side of the driver.\"\n\n\"Whatever your reasons may be, you are perfectly correct,\" said\nshe. \"I started from home before six, reached Leatherhead at\ntwenty past, and came in by the first train to Waterloo. Sir, I\ncan stand this strain no longer; I shall go mad if it continues.\nI have no one to turn to--none, save only one, who cares for me,\nand he, poor fellow, can be of little aid. I have heard of you,\nMr. Holmes; I have heard of you from Mrs. Farintosh, whom you\nhelped in the hour of her sore need. It was from her that I had\nyour address. Oh, sir, do you not think that you could help me,\ntoo, and at least throw a little light through the dense darkness\nwhich surrounds me? At present it is out of my power to reward\nyou for your services, but in a month or six weeks I shall be\nmarried, with the control of my own income, and then at least you\nshall not find me ungrateful.\"\n\nHolmes turned to his desk and, unlocking it, drew out a small\ncase-book, which he consulted.\n\n\"Farintosh,\" said he. \"Ah yes, I recall the case; it was\nconcerned with an opal tiara. I think it was before your time,\nWatson. I can only say, madam, that I shall be happy to devote\nthe same care to your case as I did to that of your friend. As to\nreward, my profession is its own reward; but you are at liberty\nto defray whatever expenses I may be put to, at the time which\nsuits you best. And now I beg that you will lay before us\neverything that may help us in forming an opinion upon the\nmatter.\"\n\n\"Alas!\" replied our visitor, \"the very horror of my situation\nlies in the fact that my fears are so vague, and my suspicions\ndepend so entirely upon small points, which might seem trivial to\nanother, that even he to whom of all others I have a right to\nlook for help and advice looks upon all that I tell him about it\nas the fancies of a nervous woman. He does not say so, but I can\nread it from his soothing answers and averted eyes. But I have\nheard, Mr. Holmes, that you can see deeply into the manifold\nwickedness of the human heart. You may advise me how to walk amid\nthe dangers which encompass me.\"\n\n\"I am all attention, madam.\"\n\n\"My name is Helen Stoner, and I am living with my stepfather, who\nis the last survivor of one of the oldest Saxon families in\nEngland, the Roylotts of Stoke Moran, on the western border of\nSurrey.\"\n\nHolmes nodded his head. \"The name is familiar to me,\" said he.\n\n\"The family was at one time among the richest in England, and the\nestates extended over the borders into Berkshire in the north,\nand Hampshire in the west. In the last century, however, four\nsuccessive heirs were of a dissolute and wasteful disposition,\nand the family ruin was eventually completed by a gambler in the\ndays of the Regency. Nothing was left save a few acres of ground,\nand the two-hundred-year-old house, which is itself crushed under\na heavy mortgage. The last squire dragged out his existence\nthere, living the horrible life of an aristocratic pauper; but\nhis only son, my stepfather, seeing that he must adapt himself to\nthe new conditions, obtained an advance from a relative, which\nenabled him to take a medical degree and went out to Calcutta,\nwhere, by his professional skill and his force of character, he\nestablished a large practice. In a fit of anger, however, caused\nby some robberies which had been perpetrated in the house, he\nbeat his native butler to death and narrowly escaped a capital\nsentence. As it was, he suffered a long term of imprisonment and\nafterwards returned to England a morose and disappointed man.\n\n\"When Dr. Roylott was in India he married my mother, Mrs. Stoner,\nthe young widow of Major-General Stoner, of the Bengal Artillery.\nMy sister Julia and I were twins, and we were only two years old\nat the time of my mother's re-marriage. She had a considerable\nsum of money--not less than 1000 pounds a year--and this she\nbequeathed to Dr. Roylott entirely while we resided with him,\nwith a provision that a certain annual sum should be allowed to\neach of us in the event of our marriage. Shortly after our return\nto England my mother died--she was killed eight years ago in a\nrailway accident near Crewe. Dr. Roylott then abandoned his\nattempts to establish himself in practice in London and took us\nto live with him in the old ancestral house at Stoke Moran. The\nmoney which my mother had left was enough for all our wants, and\nthere seemed to be no obstacle to our happiness.\n\n\"But a terrible change came over our stepfather about this time.\nInstead of making friends and exchanging visits with our\nneighbours, who had at first been overjoyed to see a Roylott of\nStoke Moran back in the old family seat, he shut himself up in\nhis house and seldom came out save to indulge in ferocious\nquarrels with whoever might cross his path. Violence of temper\napproaching to mania has been hereditary in the men of the\nfamily, and in my stepfather's case it had, I believe, been\nintensified by his long residence in the tropics. A series of\ndisgraceful brawls took place, two of which ended in the\npolice-court, until at last he became the terror of the village,\nand the folks would fly at his approach, for he is a man of\nimmense strength, and absolutely uncontrollable in his anger.\n\n\"Last week he hurled the local blacksmith over a parapet into a\nstream, and it was only by paying over all the money which I\ncould gather together that I was able to avert another public\nexposure. He had no friends at all save the wandering gipsies,\nand he would give these vagabonds leave to encamp upon the few\nacres of bramble-covered land which represent the family estate,\nand would accept in return the hospitality of their tents,\nwandering away with them sometimes for weeks on end. He has a\npassion also for Indian animals, which are sent over to him by a\ncorrespondent, and he has at this moment a cheetah and a baboon,\nwhich wander freely over his grounds and are feared by the\nvillagers almost as much as their master.\n\n\"You can imagine from what I say that my poor sister Julia and I\nhad no great pleasure in our lives. No servant would stay with\nus, and for a long time we did all the work of the house. She was\nbut thirty at the time of her death, and yet her hair had already\nbegun to whiten, even as mine has.\"\n\n\"Your sister is dead, then?\"\n\n\"She died just two years ago, and it is of her death that I wish\nto speak to you. You can understand that, living the life which I\nhave described, we were little likely to see anyone of our own\nage and position. We had, however, an aunt, my mother's maiden\nsister, Miss Honoria Westphail, who lives near Harrow, and we\nwere occasionally allowed to pay short visits at this lady's\nhouse. Julia went there at Christmas two years ago, and met there\na half-pay major of marines, to whom she became engaged. My\nstepfather learned of the engagement when my sister returned and\noffered no objection to the marriage; but within a fortnight of\nthe day which had been fixed for the wedding, the terrible event\noccurred which has deprived me of my only companion.\"\n\nSherlock Holmes had been leaning back in his chair with his eyes\nclosed and his head sunk in a cushion, but he half opened his\nlids now and glanced across at his visitor.\n\n\"Pray be precise as to details,\" said he.\n\n\"It is easy for me to be so, for every event of that dreadful\ntime is seared into my memory. The manor-house is, as I have\nalready said, very old, and only one wing is now inhabited. The\nbedrooms in this wing are on the ground floor, the sitting-rooms\nbeing in the central block of the buildings. Of these bedrooms\nthe first is Dr. Roylott's, the second my sister's, and the third\nmy own. There is no communication between them, but they all open\nout into the same corridor. Do I make myself plain?\"\n\n\"Perfectly so.\"\n\n\"The windows of the three rooms open out upon the lawn. That\nfatal night Dr. Roylott had gone to his room early, though we\nknew that he had not retired to rest, for my sister was troubled\nby the smell of the strong Indian cigars which it was his custom\nto smoke. She left her room, therefore, and came into mine, where\nshe sat for some time, chatting about her approaching wedding. At\neleven o'clock she rose to leave me, but she paused at the door\nand looked back.\n\n\"'Tell me, Helen,' said she, 'have you ever heard anyone whistle\nin the dead of the night?'\n\n\"'Never,' said I.\n\n\"'I suppose that you could not possibly whistle, yourself, in\nyour sleep?'\n\n\"'Certainly not. But why?'\n\n\"'Because during the last few nights I have always, about three\nin the morning, heard a low, clear whistle. I am a light sleeper,\nand it has awakened me. I cannot tell where it came from--perhaps\nfrom the next room, perhaps from the lawn. I thought that I would\njust ask you whether you had heard it.'\n\n\"'No, I have not. It must be those wretched gipsies in the\nplantation.'\n\n\"'Very likely. And yet if it were on the lawn, I wonder that you\ndid not hear it also.'\n\n\"'Ah, but I sleep more heavily than you.'\n\n\"'Well, it is of no great consequence, at any rate.' She smiled\nback at me, closed my door, and a few moments later I heard her\nkey turn in the lock.\"\n\n\"Indeed,\" said Holmes. \"Was it your custom always to lock\nyourselves in at night?\"\n\n\"Always.\"\n\n\"And why?\"\n\n\"I think that I mentioned to you that the doctor kept a cheetah\nand a baboon. We had no feeling of security unless our doors were\nlocked.\"\n\n\"Quite so. Pray proceed with your statement.\"\n\n\"I could not sleep that night. A vague feeling of impending\nmisfortune impressed me. My sister and I, you will recollect,\nwere twins, and you know how subtle are the links which bind two\nsouls which are so closely allied. It was a wild night. The wind\nwas howling outside, and the rain was beating and splashing\nagainst the windows. Suddenly, amid all the hubbub of the gale,\nthere burst forth the wild scream of a terrified woman. I knew\nthat it was my sister's voice. I sprang from my bed, wrapped a\nshawl round me, and rushed into the corridor. As I opened my door\nI seemed to hear a low whistle, such as my sister described, and\na few moments later a clanging sound, as if a mass of metal had\nfallen. As I ran down the passage, my sister's door was unlocked,\nand revolved slowly upon its hinges. I stared at it\nhorror-stricken, not knowing what was about to issue from it. By\nthe light of the corridor-lamp I saw my sister appear at the\nopening, her face blanched with terror, her hands groping for\nhelp, her whole figure swaying to and fro like that of a\ndrunkard. I ran to her and threw my arms round her, but at that\nmoment her knees seemed to give way and she fell to the ground.\nShe writhed as one who is in terrible pain, and her limbs were\ndreadfully convulsed. At first I thought that she had not\nrecognised me, but as I bent over her she suddenly shrieked out\nin a voice which I shall never forget, 'Oh, my God! Helen! It was\nthe band! The speckled band!' There was something else which she\nwould fain have said, and she stabbed with her finger into the\nair in the direction of the doctor's room, but a fresh convulsion\nseized her and choked her words. I rushed out, calling loudly for\nmy stepfather, and I met him hastening from his room in his\ndressing-gown. When he reached my sister's side she was\nunconscious, and though he poured brandy down her throat and sent\nfor medical aid from the village, all efforts were in vain, for\nshe slowly sank and died without having recovered her\nconsciousness. Such was the dreadful end of my beloved sister.\"\n\n\"One moment,\" said Holmes, \"are you sure about this whistle and\nmetallic sound? Could you swear to it?\"\n\n\"That was what the county coroner asked me at the inquiry. It is\nmy strong impression that I heard it, and yet, among the crash of\nthe gale and the creaking of an old house, I may possibly have\nbeen deceived.\"\n\n\"Was your sister dressed?\"\n\n\"No, she was in her night-dress. In her right hand was found the\ncharred stump of a match, and in her left a match-box.\"\n\n\"Showing that she had struck a light and looked about her when\nthe alarm took place. That is important. And what conclusions did\nthe coroner come to?\"\n\n\"He investigated the case with great care, for Dr. Roylott's\nconduct had long been notorious in the county, but he was unable\nto find any satisfactory cause of death. My evidence showed that\nthe door had been fastened upon the inner side, and the windows\nwere blocked by old-fashioned shutters with broad iron bars,\nwhich were secured every night. The walls were carefully sounded,\nand were shown to be quite solid all round, and the flooring was\nalso thoroughly examined, with the same result. The chimney is\nwide, but is barred up by four large staples. It is certain,\ntherefore, that my sister was quite alone when she met her end.\nBesides, there were no marks of any violence upon her.\"\n\n\"How about poison?\"\n\n\"The doctors examined her for it, but without success.\"\n\n\"What do you think that this unfortunate lady died of, then?\"\n\n\"It is my belief that she died of pure fear and nervous shock,\nthough what it was that frightened her I cannot imagine.\"\n\n\"Were there gipsies in the plantation at the time?\"\n\n\"Yes, there are nearly always some there.\"\n\n\"Ah, and what did you gather from this allusion to a band--a\nspeckled band?\"\n\n\"Sometimes I have thought that it was merely the wild talk of\ndelirium, sometimes that it may have referred to some band of\npeople, perhaps to these very gipsies in the plantation. I do not\nknow whether the spotted handkerchiefs which so many of them wear\nover their heads might have suggested the strange adjective which\nshe used.\"\n\nHolmes shook his head like a man who is far from being satisfied.\n\n\"These are very deep waters,\" said he; \"pray go on with your\nnarrative.\"\n\n\"Two years have passed since then, and my life has been until\nlately lonelier than ever. A month ago, however, a dear friend,\nwhom I have known for many years, has done me the honour to ask\nmy hand in marriage. His name is Armitage--Percy Armitage--the\nsecond son of Mr. Armitage, of Crane Water, near Reading. My\nstepfather has offered no opposition to the match, and we are to\nbe married in the course of the spring. Two days ago some repairs\nwere started in the west wing of the building, and my bedroom\nwall has been pierced, so that I have had to move into the\nchamber in which my sister died, and to sleep in the very bed in\nwhich she slept. Imagine, then, my thrill of terror when last\nnight, as I lay awake, thinking over her terrible fate, I\nsuddenly heard in the silence of the night the low whistle which\nhad been the herald of her own death. I sprang up and lit the\nlamp, but nothing was to be seen in the room. I was too shaken to\ngo to bed again, however, so I dressed, and as soon as it was\ndaylight I slipped down, got a dog-cart at the Crown Inn, which\nis opposite, and drove to Leatherhead, from whence I have come on\nthis morning with the one object of seeing you and asking your\nadvice.\"\n\n\"You have done wisely,\" said my friend. \"But have you told me\nall?\"\n\n\"Yes, all.\"\n\n\"Miss Roylott, you have not. You are screening your stepfather.\"\n\n\"Why, what do you mean?\"\n\nFor answer Holmes pushed back the frill of black lace which\nfringed the hand that lay upon our visitor's knee. Five little\nlivid spots, the marks of four fingers and a thumb, were printed\nupon the white wrist.\n\n\"You have been cruelly used,\" said Holmes.\n\nThe lady coloured deeply and covered over her injured wrist. \"He\nis a hard man,\" she said, \"and perhaps he hardly knows his own\nstrength.\"\n\nThere was a long silence, during which Holmes leaned his chin\nupon his hands and stared into the crackling fire.\n\n\"This is a very deep business,\" he said at last. \"There are a\nthousand details which I should desire to know before I decide\nupon our course of action. Yet we have not a moment to lose. If\nwe were to come to Stoke Moran to-day, would it be possible for\nus to see over these rooms without the knowledge of your\nstepfather?\"\n\n\"As it happens, he spoke of coming into town to-day upon some\nmost important business. It is probable that he will be away all\nday, and that there would be nothing to disturb you. We have a\nhousekeeper now, but she is old and foolish, and I could easily\nget her out of the way.\"\n\n\"Excellent. You are not averse to this trip, Watson?\"\n\n\"By no means.\"\n\n\"Then we shall both come. What are you going to do yourself?\"\n\n\"I have one or two things which I would wish to do now that I am\nin town. But I shall return by the twelve o'clock train, so as to\nbe there in time for your coming.\"\n\n\"And you may expect us early in the afternoon. I have myself some\nsmall business matters to attend to. Will you not wait and\nbreakfast?\"\n\n\"No, I must go. My heart is lightened already since I have\nconfided my trouble to you. I shall look forward to seeing you\nagain this afternoon.\" She dropped her thick black veil over her\nface and glided from the room.\n\n\"And what do you think of it all, Watson?\" asked Sherlock Holmes,\nleaning back in his chair.\n\n\"It seems to me to be a most dark and sinister business.\"\n\n\"Dark enough and sinister enough.\"\n\n\"Yet if the lady is correct in saying that the flooring and walls\nare sound, and that the door, window, and chimney are impassable,\nthen her sister must have been undoubtedly alone when she met her\nmysterious end.\"\n\n\"What becomes, then, of these nocturnal whistles, and what of the\nvery peculiar words of the dying woman?\"\n\n\"I cannot think.\"\n\n\"When you combine the ideas of whistles at night, the presence of\na band of gipsies who are on intimate terms with this old doctor,\nthe fact that we have every reason to believe that the doctor has\nan interest in preventing his stepdaughter's marriage, the dying\nallusion to a band, and, finally, the fact that Miss Helen Stoner\nheard a metallic clang, which might have been caused by one of\nthose metal bars that secured the shutters falling back into its\nplace, I think that there is good ground to think that the\nmystery may be cleared along those lines.\"\n\n\"But what, then, did the gipsies do?\"\n\n\"I cannot imagine.\"\n\n\"I see many objections to any such theory.\"\n\n\"And so do I. It is precisely for that reason that we are going\nto Stoke Moran this day. I want to see whether the objections are\nfatal, or if they may be explained away. But what in the name of\nthe devil!\"\n\nThe ejaculation had been drawn from my companion by the fact that\nour door had been suddenly dashed open, and that a huge man had\nframed himself in the aperture. His costume was a peculiar\nmixture of the professional and of the agricultural, having a\nblack top-hat, a long frock-coat, and a pair of high gaiters,\nwith a hunting-crop swinging in his hand. So tall was he that his\nhat actually brushed the cross bar of the doorway, and his\nbreadth seemed to span it across from side to side. A large face,\nseared with a thousand wrinkles, burned yellow with the sun, and\nmarked with every evil passion, was turned from one to the other\nof us, while his deep-set, bile-shot eyes, and his high, thin,\nfleshless nose, gave him somewhat the resemblance to a fierce old\nbird of prey.\n\n\"Which of you is Holmes?\" asked this apparition.\n\n\"My name, sir; but you have the advantage of me,\" said my\ncompanion quietly.\n\n\"I am Dr. Grimesby Roylott, of Stoke Moran.\"\n\n\"Indeed, Doctor,\" said Holmes blandly. \"Pray take a seat.\"\n\n\"I will do nothing of the kind. My stepdaughter has been here. I\nhave traced her. What has she been saying to you?\"\n\n\"It is a little cold for the time of the year,\" said Holmes.\n\n\"What has she been saying to you?\" screamed the old man\nfuriously.\n\n\"But I have heard that the crocuses promise well,\" continued my\ncompanion imperturbably.\n\n\"Ha! You put me off, do you?\" said our new visitor, taking a step\nforward and shaking his hunting-crop. \"I know you, you scoundrel!\nI have heard of you before. You are Holmes, the meddler.\"\n\nMy friend smiled.\n\n\"Holmes, the busybody!\"\n\nHis smile broadened.\n\n\"Holmes, the Scotland Yard Jack-in-office!\"\n\nHolmes chuckled heartily. \"Your conversation is most\nentertaining,\" said he. \"When you go out close the door, for\nthere is a decided draught.\"\n\n\"I will go when I have said my say. Don't you dare to meddle with\nmy affairs. I know that Miss Stoner has been here. I traced her!\nI am a dangerous man to fall foul of! See here.\" He stepped\nswiftly forward, seized the poker, and bent it into a curve with\nhis huge brown hands.\n\n\"See that you keep yourself out of my grip,\" he snarled, and\nhurling the twisted poker into the fireplace he strode out of the\nroom.\n\n\"He seems a very amiable person,\" said Holmes, laughing. \"I am\nnot quite so bulky, but if he had remained I might have shown him\nthat my grip was not much more feeble than his own.\" As he spoke\nhe picked up the steel poker and, with a sudden effort,\nstraightened it out again.\n\n\"Fancy his having the insolence to confound me with the official\ndetective force! This incident gives zest to our investigation,\nhowever, and I only trust that our little friend will not suffer\nfrom her imprudence in allowing this brute to trace her. And now,\nWatson, we shall order breakfast, and afterwards I shall walk\ndown to Doctors' Commons, where I hope to get some data which may\nhelp us in this matter.\"\n\n\nIt was nearly one o'clock when Sherlock Holmes returned from his\nexcursion. He held in his hand a sheet of blue paper, scrawled\nover with notes and figures.\n\n\"I have seen the will of the deceased wife,\" said he. \"To\ndetermine its exact meaning I have been obliged to work out the\npresent prices of the investments with which it is concerned. The\ntotal income, which at the time of the wife's death was little\nshort of 1100 pounds, is now, through the fall in agricultural\nprices, not more than 750 pounds. Each daughter can claim an\nincome of 250 pounds, in case of marriage. It is evident,\ntherefore, that if both girls had married, this beauty would have\nhad a mere pittance, while even one of them would cripple him to\na very serious extent. My morning's work has not been wasted,\nsince it has proved that he has the very strongest motives for\nstanding in the way of anything of the sort. And now, Watson,\nthis is too serious for dawdling, especially as the old man is\naware that we are interesting ourselves in his affairs; so if you\nare ready, we shall call a cab and drive to Waterloo. I should be\nvery much obliged if you would slip your revolver into your\npocket. An Eley's No. 2 is an excellent argument with gentlemen\nwho can twist steel pokers into knots. That and a tooth-brush\nare, I think, all that we need.\"\n\nAt Waterloo we were fortunate in catching a train for\nLeatherhead, where we hired a trap at the station inn and drove\nfor four or five miles through the lovely Surrey lanes. It was a\nperfect day, with a bright sun and a few fleecy clouds in the\nheavens. The trees and wayside hedges were just throwing out\ntheir first green shoots, and the air was full of the pleasant\nsmell of the moist earth. To me at least there was a strange\ncontrast between the sweet promise of the spring and this\nsinister quest upon which we were engaged. My companion sat in\nthe front of the trap, his arms folded, his hat pulled down over\nhis eyes, and his chin sunk upon his breast, buried in the\ndeepest thought. Suddenly, however, he started, tapped me on the\nshoulder, and pointed over the meadows.\n\n\"Look there!\" said he.\n\nA heavily timbered park stretched up in a gentle slope,\nthickening into a grove at the highest point. From amid the\nbranches there jutted out the grey gables and high roof-tree of a\nvery old mansion.\n\n\"Stoke Moran?\" said he.\n\n\"Yes, sir, that be the house of Dr. Grimesby Roylott,\" remarked\nthe driver.\n\n\"There is some building going on there,\" said Holmes; \"that is\nwhere we are going.\"\n\n\"There's the village,\" said the driver, pointing to a cluster of\nroofs some distance to the left; \"but if you want to get to the\nhouse, you'll find it shorter to get over this stile, and so by\nthe foot-path over the fields. There it is, where the lady is\nwalking.\"\n\n\"And the lady, I fancy, is Miss Stoner,\" observed Holmes, shading\nhis eyes. \"Yes, I think we had better do as you suggest.\"\n\nWe got off, paid our fare, and the trap rattled back on its way\nto Leatherhead.\n\n\"I thought it as well,\" said Holmes as we climbed the stile,\n\"that this fellow should think we had come here as architects, or\non some definite business. It may stop his gossip.\nGood-afternoon, Miss Stoner. You see that we have been as good as\nour word.\"\n\nOur client of the morning had hurried forward to meet us with a\nface which spoke her joy. \"I have been waiting so eagerly for\nyou,\" she cried, shaking hands with us warmly. \"All has turned\nout splendidly. Dr. Roylott has gone to town, and it is unlikely\nthat he will be back before evening.\"\n\n\"We have had the pleasure of making the doctor's acquaintance,\"\nsaid Holmes, and in a few words he sketched out what had\noccurred. Miss Stoner turned white to the lips as she listened.\n\n\"Good heavens!\" she cried, \"he has followed me, then.\"\n\n\"So it appears.\"\n\n\"He is so cunning that I never know when I am safe from him. What\nwill he say when he returns?\"\n\n\"He must guard himself, for he may find that there is someone\nmore cunning than himself upon his track. You must lock yourself\nup from him to-night. If he is violent, we shall take you away to\nyour aunt's at Harrow. Now, we must make the best use of our\ntime, so kindly take us at once to the rooms which we are to\nexamine.\"\n\nThe building was of grey, lichen-blotched stone, with a high\ncentral portion and two curving wings, like the claws of a crab,\nthrown out on each side. In one of these wings the windows were\nbroken and blocked with wooden boards, while the roof was partly\ncaved in, a picture of ruin. The central portion was in little\nbetter repair, but the right-hand block was comparatively modern,\nand the blinds in the windows, with the blue smoke curling up\nfrom the chimneys, showed that this was where the family resided.\nSome scaffolding had been erected against the end wall, and the\nstone-work had been broken into, but there were no signs of any\nworkmen at the moment of our visit. Holmes walked slowly up and\ndown the ill-trimmed lawn and examined with deep attention the\noutsides of the windows.\n\n\"This, I take it, belongs to the room in which you used to sleep,\nthe centre one to your sister's, and the one next to the main\nbuilding to Dr. Roylott's chamber?\"\n\n\"Exactly so. But I am now sleeping in the middle one.\"\n\n\"Pending the alterations, as I understand. By the way, there does\nnot seem to be any very pressing need for repairs at that end\nwall.\"\n\n\"There were none. I believe that it was an excuse to move me from\nmy room.\"\n\n\"Ah! that is suggestive. Now, on the other side of this narrow\nwing runs the corridor from which these three rooms open. There\nare windows in it, of course?\"\n\n\"Yes, but very small ones. Too narrow for anyone to pass\nthrough.\"\n\n\"As you both locked your doors at night, your rooms were\nunapproachable from that side. Now, would you have the kindness\nto go into your room and bar your shutters?\"\n\nMiss Stoner did so, and Holmes, after a careful examination\nthrough the open window, endeavoured in every way to force the\nshutter open, but without success. There was no slit through\nwhich a knife could be passed to raise the bar. Then with his\nlens he tested the hinges, but they were of solid iron, built\nfirmly into the massive masonry. \"Hum!\" said he, scratching his\nchin in some perplexity, \"my theory certainly presents some\ndifficulties. No one could pass these shutters if they were\nbolted. Well, we shall see if the inside throws any light upon\nthe matter.\"\n\nA small side door led into the whitewashed corridor from which\nthe three bedrooms opened. Holmes refused to examine the third\nchamber, so we passed at once to the second, that in which Miss\nStoner was now sleeping, and in which her sister had met with her\nfate. It was a homely little room, with a low ceiling and a\ngaping fireplace, after the fashion of old country-houses. A\nbrown chest of drawers stood in one corner, a narrow\nwhite-counterpaned bed in another, and a dressing-table on the\nleft-hand side of the window. These articles, with two small\nwicker-work chairs, made up all the furniture in the room save\nfor a square of Wilton carpet in the centre. The boards round and\nthe panelling of the walls were of brown, worm-eaten oak, so old\nand discoloured that it may have dated from the original building\nof the house. Holmes drew one of the chairs into a corner and sat\nsilent, while his eyes travelled round and round and up and down,\ntaking in every detail of the apartment.\n\n\"Where does that bell communicate with?\" he asked at last\npointing to a thick bell-rope which hung down beside the bed, the\ntassel actually lying upon the pillow.\n\n\"It goes to the housekeeper's room.\"\n\n\"It looks newer than the other things?\"\n\n\"Yes, it was only put there a couple of years ago.\"\n\n\"Your sister asked for it, I suppose?\"\n\n\"No, I never heard of her using it. We used always to get what we\nwanted for ourselves.\"\n\n\"Indeed, it seemed unnecessary to put so nice a bell-pull there.\nYou will excuse me for a few minutes while I satisfy myself as to\nthis floor.\" He threw himself down upon his face with his lens in\nhis hand and crawled swiftly backward and forward, examining\nminutely the cracks between the boards. Then he did the same with\nthe wood-work with which the chamber was panelled. Finally he\nwalked over to the bed and spent some time in staring at it and\nin running his eye up and down the wall. Finally he took the\nbell-rope in his hand and gave it a brisk tug.\n\n\"Why, it's a dummy,\" said he.\n\n\"Won't it ring?\"\n\n\"No, it is not even attached to a wire. This is very interesting.\nYou can see now that it is fastened to a hook just above where\nthe little opening for the ventilator is.\"\n\n\"How very absurd! I never noticed that before.\"\n\n\"Very strange!\" muttered Holmes, pulling at the rope. \"There are\none or two very singular points about this room. For example,\nwhat a fool a builder must be to open a ventilator into another\nroom, when, with the same trouble, he might have communicated\nwith the outside air!\"\n\n\"That is also quite modern,\" said the lady.\n\n\"Done about the same time as the bell-rope?\" remarked Holmes.\n\n\"Yes, there were several little changes carried out about that\ntime.\"\n\n\"They seem to have been of a most interesting character--dummy\nbell-ropes, and ventilators which do not ventilate. With your\npermission, Miss Stoner, we shall now carry our researches into\nthe inner apartment.\"\n\nDr. Grimesby Roylott's chamber was larger than that of his\nstep-daughter, but was as plainly furnished. A camp-bed, a small\nwooden shelf full of books, mostly of a technical character, an\narmchair beside the bed, a plain wooden chair against the wall, a\nround table, and a large iron safe were the principal things\nwhich met the eye. Holmes walked slowly round and examined each\nand all of them with the keenest interest.\n\n\"What's in here?\" he asked, tapping the safe.\n\n\"My stepfather's business papers.\"\n\n\"Oh! you have seen inside, then?\"\n\n\"Only once, some years ago. I remember that it was full of\npapers.\"\n\n\"There isn't a cat in it, for example?\"\n\n\"No. What a strange idea!\"\n\n\"Well, look at this!\" He took up a small saucer of milk which\nstood on the top of it.\n\n\"No; we don't keep a cat. But there is a cheetah and a baboon.\"\n\n\"Ah, yes, of course! Well, a cheetah is just a big cat, and yet a\nsaucer of milk does not go very far in satisfying its wants, I\ndaresay. There is one point which I should wish to determine.\" He\nsquatted down in front of the wooden chair and examined the seat\nof it with the greatest attention.\n\n\"Thank you. That is quite settled,\" said he, rising and putting\nhis lens in his pocket. \"Hullo! Here is something interesting!\"\n\nThe object which had caught his eye was a small dog lash hung on\none corner of the bed. The lash, however, was curled upon itself\nand tied so as to make a loop of whipcord.\n\n\"What do you make of that, Watson?\"\n\n\"It's a common enough lash. But I don't know why it should be\ntied.\"\n\n\"That is not quite so common, is it? Ah, me! it's a wicked world,\nand when a clever man turns his brains to crime it is the worst\nof all. I think that I have seen enough now, Miss Stoner, and\nwith your permission we shall walk out upon the lawn.\"\n\nI had never seen my friend's face so grim or his brow so dark as\nit was when we turned from the scene of this investigation. We\nhad walked several times up and down the lawn, neither Miss\nStoner nor myself liking to break in upon his thoughts before he\nroused himself from his reverie.\n\n\"It is very essential, Miss Stoner,\" said he, \"that you should\nabsolutely follow my advice in every respect.\"\n\n\"I shall most certainly do so.\"\n\n\"The matter is too serious for any hesitation. Your life may\ndepend upon your compliance.\"\n\n\"I assure you that I am in your hands.\"\n\n\"In the first place, both my friend and I must spend the night in\nyour room.\"\n\nBoth Miss Stoner and I gazed at him in astonishment.\n\n\"Yes, it must be so. Let me explain. I believe that that is the\nvillage inn over there?\"\n\n\"Yes, that is the Crown.\"\n\n\"Very good. Your windows would be visible from there?\"\n\n\"Certainly.\"\n\n\"You must confine yourself to your room, on pretence of a\nheadache, when your stepfather comes back. Then when you hear him\nretire for the night, you must open the shutters of your window,\nundo the hasp, put your lamp there as a signal to us, and then\nwithdraw quietly with everything which you are likely to want\ninto the room which you used to occupy. I have no doubt that, in\nspite of the repairs, you could manage there for one night.\"\n\n\"Oh, yes, easily.\"\n\n\"The rest you will leave in our hands.\"\n\n\"But what will you do?\"\n\n\"We shall spend the night in your room, and we shall investigate\nthe cause of this noise which has disturbed you.\"\n\n\"I believe, Mr. Holmes, that you have already made up your mind,\"\nsaid Miss Stoner, laying her hand upon my companion's sleeve.\n\n\"Perhaps I have.\"\n\n\"Then, for pity's sake, tell me what was the cause of my sister's\ndeath.\"\n\n\"I should prefer to have clearer proofs before I speak.\"\n\n\"You can at least tell me whether my own thought is correct, and\nif she died from some sudden fright.\"\n\n\"No, I do not think so. I think that there was probably some more\ntangible cause. And now, Miss Stoner, we must leave you for if\nDr. Roylott returned and saw us our journey would be in vain.\nGood-bye, and be brave, for if you will do what I have told you,\nyou may rest assured that we shall soon drive away the dangers\nthat threaten you.\"\n\nSherlock Holmes and I had no difficulty in engaging a bedroom and\nsitting-room at the Crown Inn. They were on the upper floor, and\nfrom our window we could command a view of the avenue gate, and\nof the inhabited wing of Stoke Moran Manor House. At dusk we saw\nDr. Grimesby Roylott drive past, his huge form looming up beside\nthe little figure of the lad who drove him. The boy had some\nslight difficulty in undoing the heavy iron gates, and we heard\nthe hoarse roar of the doctor's voice and saw the fury with which\nhe shook his clinched fists at him. The trap drove on, and a few\nminutes later we saw a sudden light spring up among the trees as\nthe lamp was lit in one of the sitting-rooms.\n\n\"Do you know, Watson,\" said Holmes as we sat together in the\ngathering darkness, \"I have really some scruples as to taking you\nto-night. There is a distinct element of danger.\"\n\n\"Can I be of assistance?\"\n\n\"Your presence might be invaluable.\"\n\n\"Then I shall certainly come.\"\n\n\"It is very kind of you.\"\n\n\"You speak of danger. You have evidently seen more in these rooms\nthan was visible to me.\"\n\n\"No, but I fancy that I may have deduced a little more. I imagine\nthat you saw all that I did.\"\n\n\"I saw nothing remarkable save the bell-rope, and what purpose\nthat could answer I confess is more than I can imagine.\"\n\n\"You saw the ventilator, too?\"\n\n\"Yes, but I do not think that it is such a very unusual thing to\nhave a small opening between two rooms. It was so small that a\nrat could hardly pass through.\"\n\n\"I knew that we should find a ventilator before ever we came to\nStoke Moran.\"\n\n\"My dear Holmes!\"\n\n\"Oh, yes, I did. You remember in her statement she said that her\nsister could smell Dr. Roylott's cigar. Now, of course that\nsuggested at once that there must be a communication between the\ntwo rooms. It could only be a small one, or it would have been\nremarked upon at the coroner's inquiry. I deduced a ventilator.\"\n\n\"But what harm can there be in that?\"\n\n\"Well, there is at least a curious coincidence of dates. A\nventilator is made, a cord is hung, and a lady who sleeps in the\nbed dies. Does not that strike you?\"\n\n\"I cannot as yet see any connection.\"\n\n\"Did you observe anything very peculiar about that bed?\"\n\n\"No.\"\n\n\"It was clamped to the floor. Did you ever see a bed fastened\nlike that before?\"\n\n\"I cannot say that I have.\"\n\n\"The lady could not move her bed. It must always be in the same\nrelative position to the ventilator and to the rope--or so we may\ncall it, since it was clearly never meant for a bell-pull.\"\n\n\"Holmes,\" I cried, \"I seem to see dimly what you are hinting at.\nWe are only just in time to prevent some subtle and horrible\ncrime.\"\n\n\"Subtle enough and horrible enough. When a doctor does go wrong\nhe is the first of criminals. He has nerve and he has knowledge.\nPalmer and Pritchard were among the heads of their profession.\nThis man strikes even deeper, but I think, Watson, that we shall\nbe able to strike deeper still. But we shall have horrors enough\nbefore the night is over; for goodness' sake let us have a quiet\npipe and turn our minds for a few hours to something more\ncheerful.\"\n\n\nAbout nine o'clock the light among the trees was extinguished,\nand all was dark in the direction of the Manor House. Two hours\npassed slowly away, and then, suddenly, just at the stroke of\neleven, a single bright light shone out right in front of us.\n\n\"That is our signal,\" said Holmes, springing to his feet; \"it\ncomes from the middle window.\"\n\nAs we passed out he exchanged a few words with the landlord,\nexplaining that we were going on a late visit to an acquaintance,\nand that it was possible that we might spend the night there. A\nmoment later we were out on the dark road, a chill wind blowing\nin our faces, and one yellow light twinkling in front of us\nthrough the gloom to guide us on our sombre errand.\n\nThere was little difficulty in entering the grounds, for\nunrepaired breaches gaped in the old park wall. Making our way\namong the trees, we reached the lawn, crossed it, and were about\nto enter through the window when out from a clump of laurel\nbushes there darted what seemed to be a hideous and distorted\nchild, who threw itself upon the grass with writhing limbs and\nthen ran swiftly across the lawn into the darkness.\n\n\"My God!\" I whispered; \"did you see it?\"\n\nHolmes was for the moment as startled as I. His hand closed like\na vice upon my wrist in his agitation. Then he broke into a low\nlaugh and put his lips to my ear.\n\n\"It is a nice household,\" he murmured. \"That is the baboon.\"\n\nI had forgotten the strange pets which the doctor affected. There\nwas a cheetah, too; perhaps we might find it upon our shoulders\nat any moment. I confess that I felt easier in my mind when,\nafter following Holmes' example and slipping off my shoes, I\nfound myself inside the bedroom. My companion noiselessly closed\nthe shutters, moved the lamp onto the table, and cast his eyes\nround the room. All was as we had seen it in the daytime. Then\ncreeping up to me and making a trumpet of his hand, he whispered\ninto my ear again so gently that it was all that I could do to\ndistinguish the words:\n\n\"The least sound would be fatal to our plans.\"\n\nI nodded to show that I had heard.\n\n\"We must sit without light. He would see it through the\nventilator.\"\n\nI nodded again.\n\n\"Do not go asleep; your very life may depend upon it. Have your\npistol ready in case we should need it. I will sit on the side of\nthe bed, and you in that chair.\"\n\nI took out my revolver and laid it on the corner of the table.\n\nHolmes had brought up a long thin cane, and this he placed upon\nthe bed beside him. By it he laid the box of matches and the\nstump of a candle. Then he turned down the lamp, and we were left\nin darkness.\n\nHow shall I ever forget that dreadful vigil? I could not hear a\nsound, not even the drawing of a breath, and yet I knew that my\ncompanion sat open-eyed, within a few feet of me, in the same\nstate of nervous tension in which I was myself. The shutters cut\noff the least ray of light, and we waited in absolute darkness.\n\nFrom outside came the occasional cry of a night-bird, and once at\nour very window a long drawn catlike whine, which told us that\nthe cheetah was indeed at liberty. Far away we could hear the\ndeep tones of the parish clock, which boomed out every quarter of\nan hour. How long they seemed, those quarters! Twelve struck, and\none and two and three, and still we sat waiting silently for\nwhatever might befall.\n\nSuddenly there was the momentary gleam of a light up in the\ndirection of the ventilator, which vanished immediately, but was\nsucceeded by a strong smell of burning oil and heated metal.\nSomeone in the next room had lit a dark-lantern. I heard a gentle\nsound of movement, and then all was silent once more, though the\nsmell grew stronger. For half an hour I sat with straining ears.\nThen suddenly another sound became audible--a very gentle,\nsoothing sound, like that of a small jet of steam escaping\ncontinually from a kettle. The instant that we heard it, Holmes\nsprang from the bed, struck a match, and lashed furiously with\nhis cane at the bell-pull.\n\n\"You see it, Watson?\" he yelled. \"You see it?\"\n\nBut I saw nothing. At the moment when Holmes struck the light I\nheard a low, clear whistle, but the sudden glare flashing into my\nweary eyes made it impossible for me to tell what it was at which\nmy friend lashed so savagely. I could, however, see that his face\nwas deadly pale and filled with horror and loathing. He had\nceased to strike and was gazing up at the ventilator when\nsuddenly there broke from the silence of the night the most\nhorrible cry to which I have ever listened. It swelled up louder\nand louder, a hoarse yell of pain and fear and anger all mingled\nin the one dreadful shriek. They say that away down in the\nvillage, and even in the distant parsonage, that cry raised the\nsleepers from their beds. It struck cold to our hearts, and I\nstood gazing at Holmes, and he at me, until the last echoes of it\nhad died away into the silence from which it rose.\n\n\"What can it mean?\" I gasped.\n\n\"It means that it is all over,\" Holmes answered. \"And perhaps,\nafter all, it is for the best. Take your pistol, and we will\nenter Dr. Roylott's room.\"\n\nWith a grave face he lit the lamp and led the way down the\ncorridor. Twice he struck at the chamber door without any reply\nfrom within. Then he turned the handle and entered, I at his\nheels, with the cocked pistol in my hand.\n\nIt was a singular sight which met our eyes. On the table stood a\ndark-lantern with the shutter half open, throwing a brilliant\nbeam of light upon the iron safe, the door of which was ajar.\nBeside this table, on the wooden chair, sat Dr. Grimesby Roylott\nclad in a long grey dressing-gown, his bare ankles protruding\nbeneath, and his feet thrust into red heelless Turkish slippers.\nAcross his lap lay the short stock with the long lash which we\nhad noticed during the day. His chin was cocked upward and his\neyes were fixed in a dreadful, rigid stare at the corner of the\nceiling. Round his brow he had a peculiar yellow band, with\nbrownish speckles, which seemed to be bound tightly round his\nhead. As we entered he made neither sound nor motion.\n\n\"The band! the speckled band!\" whispered Holmes.\n\nI took a step forward. In an instant his strange headgear began\nto move, and there reared itself from among his hair the squat\ndiamond-shaped head and puffed neck of a loathsome serpent.\n\n\"It is a swamp adder!\" cried Holmes; \"the deadliest snake in\nIndia. He has died within ten seconds of being bitten. Violence\ndoes, in truth, recoil upon the violent, and the schemer falls\ninto the pit which he digs for another. Let us thrust this\ncreature back into its den, and we can then remove Miss Stoner to\nsome place of shelter and let the county police know what has\nhappened.\"\n\nAs he spoke he drew the dog-whip swiftly from the dead man's lap,\nand throwing the noose round the reptile's neck he drew it from\nits horrid perch and, carrying it at arm's length, threw it into\nthe iron safe, which he closed upon it.\n\nSuch are the true facts of the death of Dr. Grimesby Roylott, of\nStoke Moran. It is not necessary that I should prolong a\nnarrative which has already run to too great a length by telling\nhow we broke the sad news to the terrified girl, how we conveyed\nher by the morning train to the care of her good aunt at Harrow,\nof how the slow process of official inquiry came to the\nconclusion that the doctor met his fate while indiscreetly\nplaying with a dangerous pet. The little which I had yet to learn\nof the case was told me by Sherlock Holmes as we travelled back\nnext day.\n\n\"I had,\" said he, \"come to an entirely erroneous conclusion which\nshows, my dear Watson, how dangerous it always is to reason from\ninsufficient data. The presence of the gipsies, and the use of\nthe word 'band,' which was used by the poor girl, no doubt, to\nexplain the appearance which she had caught a hurried glimpse of\nby the light of her match, were sufficient to put me upon an\nentirely wrong scent. I can only claim the merit that I instantly\nreconsidered my position when, however, it became clear to me\nthat whatever danger threatened an occupant of the room could not\ncome either from the window or the door. My attention was\nspeedily drawn, as I have already remarked to you, to this\nventilator, and to the bell-rope which hung down to the bed. The\ndiscovery that this was a dummy, and that the bed was clamped to\nthe floor, instantly gave rise to the suspicion that the rope was\nthere as a bridge for something passing through the hole and\ncoming to the bed. The idea of a snake instantly occurred to me,\nand when I coupled it with my knowledge that the doctor was\nfurnished with a supply of creatures from India, I felt that I\nwas probably on the right track. The idea of using a form of\npoison which could not possibly be discovered by any chemical\ntest was just such a one as would occur to a clever and ruthless\nman who had had an Eastern training. The rapidity with which such\na poison would take effect would also, from his point of view, be\nan advantage. It would be a sharp-eyed coroner, indeed, who could\ndistinguish the two little dark punctures which would show where\nthe poison fangs had done their work. Then I thought of the\nwhistle. Of course he must recall the snake before the morning\nlight revealed it to the victim. He had trained it, probably by\nthe use of the milk which we saw, to return to him when summoned.\nHe would put it through this ventilator at the hour that he\nthought best, with the certainty that it would crawl down the\nrope and land on the bed. It might or might not bite the\noccupant, perhaps she might escape every night for a week, but\nsooner or later she must fall a victim.\n\n\"I had come to these conclusions before ever I had entered his\nroom. An inspection of his chair showed me that he had been in\nthe habit of standing on it, which of course would be necessary\nin order that he should reach the ventilator. The sight of the\nsafe, the saucer of milk, and the loop of whipcord were enough to\nfinally dispel any doubts which may have remained. The metallic\nclang heard by Miss Stoner was obviously caused by her stepfather\nhastily closing the door of his safe upon its terrible occupant.\nHaving once made up my mind, you know the steps which I took in\norder to put the matter to the proof. I heard the creature hiss\nas I have no doubt that you did also, and I instantly lit the\nlight and attacked it.\"\n\n\"With the result of driving it through the ventilator.\"\n\n\"And also with the result of causing it to turn upon its master\nat the other side. Some of the blows of my cane came home and\nroused its snakish temper, so that it flew upon the first person\nit saw. In this way I am no doubt indirectly responsible for Dr.\nGrimesby Roylott's death, and I cannot say that it is likely to\nweigh very heavily upon my conscience.\"\n\n\n\nIX. THE ADVENTURE OF THE ENGINEER'S THUMB\n\nOf all the problems which have been submitted to my friend, Mr.\nSherlock Holmes, for solution during the years of our intimacy,\nthere were only two which I was the means of introducing to his\nnotice--that of Mr. Hatherley's thumb, and that of Colonel\nWarburton's madness. Of these the latter may have afforded a\nfiner field for an acute and original observer, but the other was\nso strange in its inception and so dramatic in its details that\nit may be the more worthy of being placed upon record, even if it\ngave my friend fewer openings for those deductive methods of\nreasoning by which he achieved such remarkable results. The story\nhas, I believe, been told more than once in the newspapers, but,\nlike all such narratives, its effect is much less striking when\nset forth en bloc in a single half-column of print than when the\nfacts slowly evolve before your own eyes, and the mystery clears\ngradually away as each new discovery furnishes a step which leads\non to the complete truth. At the time the circumstances made a\ndeep impression upon me, and the lapse of two years has hardly\nserved to weaken the effect.\n\nIt was in the summer of '89, not long after my marriage, that the\nevents occurred which I am now about to summarise. I had returned\nto civil practice and had finally abandoned Holmes in his Baker\nStreet rooms, although I continually visited him and occasionally\neven persuaded him to forgo his Bohemian habits so far as to come\nand visit us. My practice had steadily increased, and as I\nhappened to live at no very great distance from Paddington\nStation, I got a few patients from among the officials. One of\nthese, whom I had cured of a painful and lingering disease, was\nnever weary of advertising my virtues and of endeavouring to send\nme on every sufferer over whom he might have any influence.\n\nOne morning, at a little before seven o'clock, I was awakened by\nthe maid tapping at the door to announce that two men had come\nfrom Paddington and were waiting in the consulting-room. I\ndressed hurriedly, for I knew by experience that railway cases\nwere seldom trivial, and hastened downstairs. As I descended, my\nold ally, the guard, came out of the room and closed the door\ntightly behind him.\n\n\"I've got him here,\" he whispered, jerking his thumb over his\nshoulder; \"he's all right.\"\n\n\"What is it, then?\" I asked, for his manner suggested that it was\nsome strange creature which he had caged up in my room.\n\n\"It's a new patient,\" he whispered. \"I thought I'd bring him\nround myself; then he couldn't slip away. There he is, all safe\nand sound. I must go now, Doctor; I have my dooties, just the\nsame as you.\" And off he went, this trusty tout, without even\ngiving me time to thank him.\n\nI entered my consulting-room and found a gentleman seated by the\ntable. He was quietly dressed in a suit of heather tweed with a\nsoft cloth cap which he had laid down upon my books. Round one of\nhis hands he had a handkerchief wrapped, which was mottled all\nover with bloodstains. He was young, not more than\nfive-and-twenty, I should say, with a strong, masculine face; but\nhe was exceedingly pale and gave me the impression of a man who\nwas suffering from some strong agitation, which it took all his\nstrength of mind to control.\n\n\"I am sorry to knock you up so early, Doctor,\" said he, \"but I\nhave had a very serious accident during the night. I came in by\ntrain this morning, and on inquiring at Paddington as to where I\nmight find a doctor, a worthy fellow very kindly escorted me\nhere. I gave the maid a card, but I see that she has left it upon\nthe side-table.\"\n\nI took it up and glanced at it. \"Mr. Victor Hatherley, hydraulic\nengineer, 16A, Victoria Street (3rd floor).\" That was the name,\nstyle, and abode of my morning visitor. \"I regret that I have\nkept you waiting,\" said I, sitting down in my library-chair. \"You\nare fresh from a night journey, I understand, which is in itself\na monotonous occupation.\"\n\n\"Oh, my night could not be called monotonous,\" said he, and\nlaughed. He laughed very heartily, with a high, ringing note,\nleaning back in his chair and shaking his sides. All my medical\ninstincts rose up against that laugh.\n\n\"Stop it!\" I cried; \"pull yourself together!\" and I poured out\nsome water from a caraffe.\n\nIt was useless, however. He was off in one of those hysterical\noutbursts which come upon a strong nature when some great crisis\nis over and gone. Presently he came to himself once more, very\nweary and pale-looking.\n\n\"I have been making a fool of myself,\" he gasped.\n\n\"Not at all. Drink this.\" I dashed some brandy into the water,\nand the colour began to come back to his bloodless cheeks.\n\n\"That's better!\" said he. \"And now, Doctor, perhaps you would\nkindly attend to my thumb, or rather to the place where my thumb\nused to be.\"\n\nHe unwound the handkerchief and held out his hand. It gave even\nmy hardened nerves a shudder to look at it. There were four\nprotruding fingers and a horrid red, spongy surface where the\nthumb should have been. It had been hacked or torn right out from\nthe roots.\n\n\"Good heavens!\" I cried, \"this is a terrible injury. It must have\nbled considerably.\"\n\n\"Yes, it did. I fainted when it was done, and I think that I must\nhave been senseless for a long time. When I came to I found that\nit was still bleeding, so I tied one end of my handkerchief very\ntightly round the wrist and braced it up with a twig.\"\n\n\"Excellent! You should have been a surgeon.\"\n\n\"It is a question of hydraulics, you see, and came within my own\nprovince.\"\n\n\"This has been done,\" said I, examining the wound, \"by a very\nheavy and sharp instrument.\"\n\n\"A thing like a cleaver,\" said he.\n\n\"An accident, I presume?\"\n\n\"By no means.\"\n\n\"What! a murderous attack?\"\n\n\"Very murderous indeed.\"\n\n\"You horrify me.\"\n\nI sponged the wound, cleaned it, dressed it, and finally covered\nit over with cotton wadding and carbolised bandages. He lay back\nwithout wincing, though he bit his lip from time to time.\n\n\"How is that?\" I asked when I had finished.\n\n\"Capital! Between your brandy and your bandage, I feel a new man.\nI was very weak, but I have had a good deal to go through.\"\n\n\"Perhaps you had better not speak of the matter. It is evidently\ntrying to your nerves.\"\n\n\"Oh, no, not now. I shall have to tell my tale to the police;\nbut, between ourselves, if it were not for the convincing\nevidence of this wound of mine, I should be surprised if they\nbelieved my statement, for it is a very extraordinary one, and I\nhave not much in the way of proof with which to back it up; and,\neven if they believe me, the clues which I can give them are so\nvague that it is a question whether justice will be done.\"\n\n\"Ha!\" cried I, \"if it is anything in the nature of a problem\nwhich you desire to see solved, I should strongly recommend you\nto come to my friend, Mr. Sherlock Holmes, before you go to the\nofficial police.\"\n\n\"Oh, I have heard of that fellow,\" answered my visitor, \"and I\nshould be very glad if he would take the matter up, though of\ncourse I must use the official police as well. Would you give me\nan introduction to him?\"\n\n\"I'll do better. I'll take you round to him myself.\"\n\n\"I should be immensely obliged to you.\"\n\n\"We'll call a cab and go together. We shall just be in time to\nhave a little breakfast with him. Do you feel equal to it?\"\n\n\"Yes; I shall not feel easy until I have told my story.\"\n\n\"Then my servant will call a cab, and I shall be with you in an\ninstant.\" I rushed upstairs, explained the matter shortly to my\nwife, and in five minutes was inside a hansom, driving with my\nnew acquaintance to Baker Street.\n\nSherlock Holmes was, as I expected, lounging about his\nsitting-room in his dressing-gown, reading the agony column of The\nTimes and smoking his before-breakfast pipe, which was composed\nof all the plugs and dottles left from his smokes of the day\nbefore, all carefully dried and collected on the corner of the\nmantelpiece. He received us in his quietly genial fashion,\nordered fresh rashers and eggs, and joined us in a hearty meal.\nWhen it was concluded he settled our new acquaintance upon the\nsofa, placed a pillow beneath his head, and laid a glass of\nbrandy and water within his reach.\n\n\"It is easy to see that your experience has been no common one,\nMr. Hatherley,\" said he. \"Pray, lie down there and make yourself\nabsolutely at home. Tell us what you can, but stop when you are\ntired and keep up your strength with a little stimulant.\"\n\n\"Thank you,\" said my patient, \"but I have felt another man since\nthe doctor bandaged me, and I think that your breakfast has\ncompleted the cure. I shall take up as little of your valuable\ntime as possible, so I shall start at once upon my peculiar\nexperiences.\"\n\nHolmes sat in his big armchair with the weary, heavy-lidded\nexpression which veiled his keen and eager nature, while I sat\nopposite to him, and we listened in silence to the strange story\nwhich our visitor detailed to us.\n\n\"You must know,\" said he, \"that I am an orphan and a bachelor,\nresiding alone in lodgings in London. By profession I am a\nhydraulic engineer, and I have had considerable experience of my\nwork during the seven years that I was apprenticed to Venner &\nMatheson, the well-known firm, of Greenwich. Two years ago,\nhaving served my time, and having also come into a fair sum of\nmoney through my poor father's death, I determined to start in\nbusiness for myself and took professional chambers in Victoria\nStreet.\n\n\"I suppose that everyone finds his first independent start in\nbusiness a dreary experience. To me it has been exceptionally so.\nDuring two years I have had three consultations and one small\njob, and that is absolutely all that my profession has brought\nme. My gross takings amount to 27 pounds 10s. Every day, from\nnine in the morning until four in the afternoon, I waited in my\nlittle den, until at last my heart began to sink, and I came to\nbelieve that I should never have any practice at all.\n\n\"Yesterday, however, just as I was thinking of leaving the\noffice, my clerk entered to say there was a gentleman waiting who\nwished to see me upon business. He brought up a card, too, with\nthe name of 'Colonel Lysander Stark' engraved upon it. Close at\nhis heels came the colonel himself, a man rather over the middle\nsize, but of an exceeding thinness. I do not think that I have\never seen so thin a man. His whole face sharpened away into nose\nand chin, and the skin of his cheeks was drawn quite tense over\nhis outstanding bones. Yet this emaciation seemed to be his\nnatural habit, and due to no disease, for his eye was bright, his\nstep brisk, and his bearing assured. He was plainly but neatly\ndressed, and his age, I should judge, would be nearer forty than\nthirty.\n\n\"'Mr. Hatherley?' said he, with something of a German accent.\n'You have been recommended to me, Mr. Hatherley, as being a man\nwho is not only proficient in his profession but is also discreet\nand capable of preserving a secret.'\n\n\"I bowed, feeling as flattered as any young man would at such an\naddress. 'May I ask who it was who gave me so good a character?'\n\n\"'Well, perhaps it is better that I should not tell you that just\nat this moment. I have it from the same source that you are both\nan orphan and a bachelor and are residing alone in London.'\n\n\"'That is quite correct,' I answered; 'but you will excuse me if\nI say that I cannot see how all this bears upon my professional\nqualifications. I understand that it was on a professional matter\nthat you wished to speak to me?'\n\n\"'Undoubtedly so. But you will find that all I say is really to\nthe point. I have a professional commission for you, but absolute\nsecrecy is quite essential--absolute secrecy, you understand, and\nof course we may expect that more from a man who is alone than\nfrom one who lives in the bosom of his family.'\n\n\"'If I promise to keep a secret,' said I, 'you may absolutely\ndepend upon my doing so.'\n\n\"He looked very hard at me as I spoke, and it seemed to me that I\nhad never seen so suspicious and questioning an eye.\n\n\"'Do you promise, then?' said he at last.\n\n\"'Yes, I promise.'\n\n\"'Absolute and complete silence before, during, and after? No\nreference to the matter at all, either in word or writing?'\n\n\"'I have already given you my word.'\n\n\"'Very good.' He suddenly sprang up, and darting like lightning\nacross the room he flung open the door. The passage outside was\nempty.\n\n\"'That's all right,' said he, coming back. 'I know that clerks are\nsometimes curious as to their master's affairs. Now we can talk\nin safety.' He drew up his chair very close to mine and began to\nstare at me again with the same questioning and thoughtful look.\n\n\"A feeling of repulsion, and of something akin to fear had begun\nto rise within me at the strange antics of this fleshless man.\nEven my dread of losing a client could not restrain me from\nshowing my impatience.\n\n\"'I beg that you will state your business, sir,' said I; 'my time\nis of value.' Heaven forgive me for that last sentence, but the\nwords came to my lips.\n\n\"'How would fifty guineas for a night's work suit you?' he asked.\n\n\"'Most admirably.'\n\n\"'I say a night's work, but an hour's would be nearer the mark. I\nsimply want your opinion about a hydraulic stamping machine which\nhas got out of gear. If you show us what is wrong we shall soon\nset it right ourselves. What do you think of such a commission as\nthat?'\n\n\"'The work appears to be light and the pay munificent.'\n\n\"'Precisely so. We shall want you to come to-night by the last\ntrain.'\n\n\"'Where to?'\n\n\"'To Eyford, in Berkshire. It is a little place near the borders\nof Oxfordshire, and within seven miles of Reading. There is a\ntrain from Paddington which would bring you there at about\n11:15.'\n\n\"'Very good.'\n\n\"'I shall come down in a carriage to meet you.'\n\n\"'There is a drive, then?'\n\n\"'Yes, our little place is quite out in the country. It is a good\nseven miles from Eyford Station.'\n\n\"'Then we can hardly get there before midnight. I suppose there\nwould be no chance of a train back. I should be compelled to stop\nthe night.'\n\n\"'Yes, we could easily give you a shake-down.'\n\n\"'That is very awkward. Could I not come at some more convenient\nhour?'\n\n\"'We have judged it best that you should come late. It is to\nrecompense you for any inconvenience that we are paying to you, a\nyoung and unknown man, a fee which would buy an opinion from the\nvery heads of your profession. Still, of course, if you would\nlike to draw out of the business, there is plenty of time to do\nso.'\n\n\"I thought of the fifty guineas, and of how very useful they\nwould be to me. 'Not at all,' said I, 'I shall be very happy to\naccommodate myself to your wishes. I should like, however, to\nunderstand a little more clearly what it is that you wish me to\ndo.'\n\n\"'Quite so. It is very natural that the pledge of secrecy which\nwe have exacted from you should have aroused your curiosity. I\nhave no wish to commit you to anything without your having it all\nlaid before you. I suppose that we are absolutely safe from\neavesdroppers?'\n\n\"'Entirely.'\n\n\"'Then the matter stands thus. You are probably aware that\nfuller's-earth is a valuable product, and that it is only found\nin one or two places in England?'\n\n\"'I have heard so.'\n\n\"'Some little time ago I bought a small place--a very small\nplace--within ten miles of Reading. I was fortunate enough to\ndiscover that there was a deposit of fuller's-earth in one of my\nfields. On examining it, however, I found that this deposit was a\ncomparatively small one, and that it formed a link between two\nvery much larger ones upon the right and left--both of them,\nhowever, in the grounds of my neighbours. These good people were\nabsolutely ignorant that their land contained that which was\nquite as valuable as a gold-mine. Naturally, it was to my\ninterest to buy their land before they discovered its true value,\nbut unfortunately I had no capital by which I could do this. I\ntook a few of my friends into the secret, however, and they\nsuggested that we should quietly and secretly work our own little\ndeposit and that in this way we should earn the money which would\nenable us to buy the neighbouring fields. This we have now been\ndoing for some time, and in order to help us in our operations we\nerected a hydraulic press. This press, as I have already\nexplained, has got out of order, and we wish your advice upon the\nsubject. We guard our secret very jealously, however, and if it\nonce became known that we had hydraulic engineers coming to our\nlittle house, it would soon rouse inquiry, and then, if the facts\ncame out, it would be good-bye to any chance of getting these\nfields and carrying out our plans. That is why I have made you\npromise me that you will not tell a human being that you are\ngoing to Eyford to-night. I hope that I make it all plain?'\n\n\"'I quite follow you,' said I. 'The only point which I could not\nquite understand was what use you could make of a hydraulic press\nin excavating fuller's-earth, which, as I understand, is dug out\nlike gravel from a pit.'\n\n\"'Ah!' said he carelessly, 'we have our own process. We compress\nthe earth into bricks, so as to remove them without revealing\nwhat they are. But that is a mere detail. I have taken you fully\ninto my confidence now, Mr. Hatherley, and I have shown you how I\ntrust you.' He rose as he spoke. 'I shall expect you, then, at\nEyford at 11:15.'\n\n\"'I shall certainly be there.'\n\n\"'And not a word to a soul.' He looked at me with a last long,\nquestioning gaze, and then, pressing my hand in a cold, dank\ngrasp, he hurried from the room.\n\n\"Well, when I came to think it all over in cool blood I was very\nmuch astonished, as you may both think, at this sudden commission\nwhich had been intrusted to me. On the one hand, of course, I was\nglad, for the fee was at least tenfold what I should have asked\nhad I set a price upon my own services, and it was possible that\nthis order might lead to other ones. On the other hand, the face\nand manner of my patron had made an unpleasant impression upon\nme, and I could not think that his explanation of the\nfuller's-earth was sufficient to explain the necessity for my\ncoming at midnight, and his extreme anxiety lest I should tell\nanyone of my errand. However, I threw all fears to the winds, ate\na hearty supper, drove to Paddington, and started off, having\nobeyed to the letter the injunction as to holding my tongue.\n\n\"At Reading I had to change not only my carriage but my station.\nHowever, I was in time for the last train to Eyford, and I\nreached the little dim-lit station after eleven o'clock. I was the\nonly passenger who got out there, and there was no one upon the\nplatform save a single sleepy porter with a lantern. As I passed\nout through the wicket gate, however, I found my acquaintance of\nthe morning waiting in the shadow upon the other side. Without a\nword he grasped my arm and hurried me into a carriage, the door\nof which was standing open. He drew up the windows on either\nside, tapped on the wood-work, and away we went as fast as the\nhorse could go.\"\n\n\"One horse?\" interjected Holmes.\n\n\"Yes, only one.\"\n\n\"Did you observe the colour?\"\n\n\"Yes, I saw it by the side-lights when I was stepping into the\ncarriage. It was a chestnut.\"\n\n\"Tired-looking or fresh?\"\n\n\"Oh, fresh and glossy.\"\n\n\"Thank you. I am sorry to have interrupted you. Pray continue\nyour most interesting statement.\"\n\n\"Away we went then, and we drove for at least an hour. Colonel\nLysander Stark had said that it was only seven miles, but I\nshould think, from the rate that we seemed to go, and from the\ntime that we took, that it must have been nearer twelve. He sat\nat my side in silence all the time, and I was aware, more than\nonce when I glanced in his direction, that he was looking at me\nwith great intensity. The country roads seem to be not very good\nin that part of the world, for we lurched and jolted terribly. I\ntried to look out of the windows to see something of where we\nwere, but they were made of frosted glass, and I could make out\nnothing save the occasional bright blur of a passing light. Now\nand then I hazarded some remark to break the monotony of the\njourney, but the colonel answered only in monosyllables, and the\nconversation soon flagged. At last, however, the bumping of the\nroad was exchanged for the crisp smoothness of a gravel-drive,\nand the carriage came to a stand. Colonel Lysander Stark sprang\nout, and, as I followed after him, pulled me swiftly into a porch\nwhich gaped in front of us. We stepped, as it were, right out of\nthe carriage and into the hall, so that I failed to catch the\nmost fleeting glance of the front of the house. The instant that\nI had crossed the threshold the door slammed heavily behind us,\nand I heard faintly the rattle of the wheels as the carriage\ndrove away.\n\n\"It was pitch dark inside the house, and the colonel fumbled\nabout looking for matches and muttering under his breath.\nSuddenly a door opened at the other end of the passage, and a\nlong, golden bar of light shot out in our direction. It grew\nbroader, and a woman appeared with a lamp in her hand, which she\nheld above her head, pushing her face forward and peering at us.\nI could see that she was pretty, and from the gloss with which\nthe light shone upon her dark dress I knew that it was a rich\nmaterial. She spoke a few words in a foreign tongue in a tone as\nthough asking a question, and when my companion answered in a\ngruff monosyllable she gave such a start that the lamp nearly\nfell from her hand. Colonel Stark went up to her, whispered\nsomething in her ear, and then, pushing her back into the room\nfrom whence she had come, he walked towards me again with the\nlamp in his hand.\n\n\"'Perhaps you will have the kindness to wait in this room for a\nfew minutes,' said he, throwing open another door. It was a\nquiet, little, plainly furnished room, with a round table in the\ncentre, on which several German books were scattered. Colonel\nStark laid down the lamp on the top of a harmonium beside the\ndoor. 'I shall not keep you waiting an instant,' said he, and\nvanished into the darkness.\n\n\"I glanced at the books upon the table, and in spite of my\nignorance of German I could see that two of them were treatises\non science, the others being volumes of poetry. Then I walked\nacross to the window, hoping that I might catch some glimpse of\nthe country-side, but an oak shutter, heavily barred, was folded\nacross it. It was a wonderfully silent house. There was an old\nclock ticking loudly somewhere in the passage, but otherwise\neverything was deadly still. A vague feeling of uneasiness began\nto steal over me. Who were these German people, and what were\nthey doing living in this strange, out-of-the-way place? And\nwhere was the place? I was ten miles or so from Eyford, that was\nall I knew, but whether north, south, east, or west I had no\nidea. For that matter, Reading, and possibly other large towns,\nwere within that radius, so the place might not be so secluded,\nafter all. Yet it was quite certain, from the absolute stillness,\nthat we were in the country. I paced up and down the room,\nhumming a tune under my breath to keep up my spirits and feeling\nthat I was thoroughly earning my fifty-guinea fee.\n\n\"Suddenly, without any preliminary sound in the midst of the\nutter stillness, the door of my room swung slowly open. The woman\nwas standing in the aperture, the darkness of the hall behind\nher, the yellow light from my lamp beating upon her eager and\nbeautiful face. I could see at a glance that she was sick with\nfear, and the sight sent a chill to my own heart. She held up one\nshaking finger to warn me to be silent, and she shot a few\nwhispered words of broken English at me, her eyes glancing back,\nlike those of a frightened horse, into the gloom behind her.\n\n\"'I would go,' said she, trying hard, as it seemed to me, to\nspeak calmly; 'I would go. I should not stay here. There is no\ngood for you to do.'\n\n\"'But, madam,' said I, 'I have not yet done what I came for. I\ncannot possibly leave until I have seen the machine.'\n\n\"'It is not worth your while to wait,' she went on. 'You can pass\nthrough the door; no one hinders.' And then, seeing that I smiled\nand shook my head, she suddenly threw aside her constraint and\nmade a step forward, with her hands wrung together. 'For the love\nof Heaven!' she whispered, 'get away from here before it is too\nlate!'\n\n\"But I am somewhat headstrong by nature, and the more ready to\nengage in an affair when there is some obstacle in the way. I\nthought of my fifty-guinea fee, of my wearisome journey, and of\nthe unpleasant night which seemed to be before me. Was it all to\ngo for nothing? Why should I slink away without having carried\nout my commission, and without the payment which was my due? This\nwoman might, for all I knew, be a monomaniac. With a stout\nbearing, therefore, though her manner had shaken me more than I\ncared to confess, I still shook my head and declared my intention\nof remaining where I was. She was about to renew her entreaties\nwhen a door slammed overhead, and the sound of several footsteps\nwas heard upon the stairs. She listened for an instant, threw up\nher hands with a despairing gesture, and vanished as suddenly and\nas noiselessly as she had come.\n\n\"The newcomers were Colonel Lysander Stark and a short thick man\nwith a chinchilla beard growing out of the creases of his double\nchin, who was introduced to me as Mr. Ferguson.\n\n\"'This is my secretary and manager,' said the colonel. 'By the\nway, I was under the impression that I left this door shut just\nnow. I fear that you have felt the draught.'\n\n\"'On the contrary,' said I, 'I opened the door myself because I\nfelt the room to be a little close.'\n\n\"He shot one of his suspicious looks at me. 'Perhaps we had\nbetter proceed to business, then,' said he. 'Mr. Ferguson and I\nwill take you up to see the machine.'\n\n\"'I had better put my hat on, I suppose.'\n\n\"'Oh, no, it is in the house.'\n\n\"'What, you dig fuller's-earth in the house?'\n\n\"'No, no. This is only where we compress it. But never mind that.\nAll we wish you to do is to examine the machine and to let us\nknow what is wrong with it.'\n\n\"We went upstairs together, the colonel first with the lamp, the\nfat manager and I behind him. It was a labyrinth of an old house,\nwith corridors, passages, narrow winding staircases, and little\nlow doors, the thresholds of which were hollowed out by the\ngenerations who had crossed them. There were no carpets and no\nsigns of any furniture above the ground floor, while the plaster\nwas peeling off the walls, and the damp was breaking through in\ngreen, unhealthy blotches. I tried to put on as unconcerned an\nair as possible, but I had not forgotten the warnings of the\nlady, even though I disregarded them, and I kept a keen eye upon\nmy two companions. Ferguson appeared to be a morose and silent\nman, but I could see from the little that he said that he was at\nleast a fellow-countryman.\n\n\"Colonel Lysander Stark stopped at last before a low door, which\nhe unlocked. Within was a small, square room, in which the three\nof us could hardly get at one time. Ferguson remained outside,\nand the colonel ushered me in.\n\n\"'We are now,' said he, 'actually within the hydraulic press, and\nit would be a particularly unpleasant thing for us if anyone were\nto turn it on. The ceiling of this small chamber is really the\nend of the descending piston, and it comes down with the force of\nmany tons upon this metal floor. There are small lateral columns\nof water outside which receive the force, and which transmit and\nmultiply it in the manner which is familiar to you. The machine\ngoes readily enough, but there is some stiffness in the working\nof it, and it has lost a little of its force. Perhaps you will\nhave the goodness to look it over and to show us how we can set\nit right.'\n\n\"I took the lamp from him, and I examined the machine very\nthoroughly. It was indeed a gigantic one, and capable of\nexercising enormous pressure. When I passed outside, however, and\npressed down the levers which controlled it, I knew at once by\nthe whishing sound that there was a slight leakage, which allowed\na regurgitation of water through one of the side cylinders. An\nexamination showed that one of the india-rubber bands which was\nround the head of a driving-rod had shrunk so as not quite to\nfill the socket along which it worked. This was clearly the cause\nof the loss of power, and I pointed it out to my companions, who\nfollowed my remarks very carefully and asked several practical\nquestions as to how they should proceed to set it right. When I\nhad made it clear to them, I returned to the main chamber of the\nmachine and took a good look at it to satisfy my own curiosity.\nIt was obvious at a glance that the story of the fuller's-earth\nwas the merest fabrication, for it would be absurd to suppose\nthat so powerful an engine could be designed for so inadequate a\npurpose. The walls were of wood, but the floor consisted of a\nlarge iron trough, and when I came to examine it I could see a\ncrust of metallic deposit all over it. I had stooped and was\nscraping at this to see exactly what it was when I heard a\nmuttered exclamation in German and saw the cadaverous face of the\ncolonel looking down at me.\n\n\"'What are you doing there?' he asked.\n\n\"I felt angry at having been tricked by so elaborate a story as\nthat which he had told me. 'I was admiring your fuller's-earth,'\nsaid I; 'I think that I should be better able to advise you as to\nyour machine if I knew what the exact purpose was for which it\nwas used.'\n\n\"The instant that I uttered the words I regretted the rashness of\nmy speech. His face set hard, and a baleful light sprang up in\nhis grey eyes.\n\n\"'Very well,' said he, 'you shall know all about the machine.' He\ntook a step backward, slammed the little door, and turned the key\nin the lock. I rushed towards it and pulled at the handle, but it\nwas quite secure, and did not give in the least to my kicks and\nshoves. 'Hullo!' I yelled. 'Hullo! Colonel! Let me out!'\n\n\"And then suddenly in the silence I heard a sound which sent my\nheart into my mouth. It was the clank of the levers and the swish\nof the leaking cylinder. He had set the engine at work. The lamp\nstill stood upon the floor where I had placed it when examining\nthe trough. By its light I saw that the black ceiling was coming\ndown upon me, slowly, jerkily, but, as none knew better than\nmyself, with a force which must within a minute grind me to a\nshapeless pulp. I threw myself, screaming, against the door, and\ndragged with my nails at the lock. I implored the colonel to let\nme out, but the remorseless clanking of the levers drowned my\ncries. The ceiling was only a foot or two above my head, and with\nmy hand upraised I could feel its hard, rough surface. Then it\nflashed through my mind that the pain of my death would depend\nvery much upon the position in which I met it. If I lay on my\nface the weight would come upon my spine, and I shuddered to\nthink of that dreadful snap. Easier the other way, perhaps; and\nyet, had I the nerve to lie and look up at that deadly black\nshadow wavering down upon me? Already I was unable to stand\nerect, when my eye caught something which brought a gush of hope\nback to my heart.\n\n\"I have said that though the floor and ceiling were of iron, the\nwalls were of wood. As I gave a last hurried glance around, I saw\na thin line of yellow light between two of the boards, which\nbroadened and broadened as a small panel was pushed backward. For\nan instant I could hardly believe that here was indeed a door\nwhich led away from death. The next instant I threw myself\nthrough, and lay half-fainting upon the other side. The panel had\nclosed again behind me, but the crash of the lamp, and a few\nmoments afterwards the clang of the two slabs of metal, told me\nhow narrow had been my escape.\n\n\"I was recalled to myself by a frantic plucking at my wrist, and\nI found myself lying upon the stone floor of a narrow corridor,\nwhile a woman bent over me and tugged at me with her left hand,\nwhile she held a candle in her right. It was the same good friend\nwhose warning I had so foolishly rejected.\n\n\"'Come! come!' she cried breathlessly. 'They will be here in a\nmoment. They will see that you are not there. Oh, do not waste\nthe so-precious time, but come!'\n\n\"This time, at least, I did not scorn her advice. I staggered to\nmy feet and ran with her along the corridor and down a winding\nstair. The latter led to another broad passage, and just as we\nreached it we heard the sound of running feet and the shouting of\ntwo voices, one answering the other from the floor on which  we\nwere and from the one beneath. My guide stopped and looked about\nher like one  who is at her wit's end. Then she threw open a door\nwhich led into a bedroom, through the window of which the moon\nwas shining brightly.\n\n\"'It is your only chance,' said she. 'It is high, but it may be\nthat you can jump it.'\n\n\"As she spoke a light sprang into view at the further end of the\npassage, and I saw the lean figure of Colonel Lysander Stark\nrushing forward with a lantern in one hand and a weapon like a\nbutcher's cleaver in the other. I rushed across the bedroom,\nflung open the window, and looked out. How quiet and sweet and\nwholesome the garden looked in the moonlight, and it could not be\nmore than thirty feet down. I clambered out upon the sill, but I\nhesitated to jump until I should have heard what passed between\nmy saviour and the ruffian who pursued me. If she were ill-used,\nthen at any risks I was determined to go back to her assistance.\nThe thought had hardly flashed through my mind before he was at\nthe door, pushing his way past her; but she threw her arms round\nhim and tried to hold him back.\n\n\"'Fritz! Fritz!' she cried in English, 'remember your promise\nafter the last time. You said it should not be again. He will be\nsilent! Oh, he will be silent!'\n\n\"'You are mad, Elise!' he shouted, struggling to break away from\nher. 'You will be the ruin of us. He has seen too much. Let me\npass, I say!' He dashed her to one side, and, rushing to the\nwindow, cut at me with his heavy weapon. I had let myself go, and\nwas hanging by the hands to the sill, when his blow fell. I was\nconscious of a dull pain, my grip loosened, and I fell into the\ngarden below.\n\n\"I was shaken but not hurt by the fall; so I picked myself up and\nrushed off among the bushes as hard as I could run, for I\nunderstood that I was far from being out of danger yet. Suddenly,\nhowever, as I ran, a deadly dizziness and sickness came over me.\nI glanced down at my hand, which was throbbing painfully, and\nthen, for the first time, saw that my thumb had been cut off and\nthat the blood was pouring from my wound. I endeavoured to tie my\nhandkerchief round it, but there came a sudden buzzing in my\nears, and next moment I fell in a dead faint among the\nrose-bushes.\n\n\"How long I remained unconscious I cannot tell. It must have been\na very long time, for the moon had sunk, and a bright morning was\nbreaking when I came to myself. My clothes were all sodden with\ndew, and my coat-sleeve was drenched with blood from my wounded\nthumb. The smarting of it recalled in an instant all the\nparticulars of my night's adventure, and I sprang to my feet with\nthe feeling that I might hardly yet be safe from my pursuers. But\nto my astonishment, when I came to look round me, neither house\nnor garden were to be seen. I had been lying in an angle of the\nhedge close by the highroad, and just a little lower down was a\nlong building, which proved, upon my approaching it, to be the\nvery station at which I had arrived upon the previous night. Were\nit not for the ugly wound upon my hand, all that had passed\nduring those dreadful hours might have been an evil dream.\n\n\"Half dazed, I went into the station and asked about the morning\ntrain. There would be one to Reading in less than an hour. The\nsame porter was on duty, I found, as had been there when I\narrived. I inquired of him whether he had ever heard of Colonel\nLysander Stark. The name was strange to him. Had he observed a\ncarriage the night before waiting for me? No, he had not. Was\nthere a police-station anywhere near? There was one about three\nmiles off.\n\n\"It was too far for me to go, weak and ill as I was. I determined\nto wait until I got back to town before telling my story to the\npolice. It was a little past six when I arrived, so I went first\nto have my wound dressed, and then the doctor was kind enough to\nbring me along here. I put the case into your hands and shall do\nexactly what you advise.\"\n\nWe both sat in silence for some little time after listening to\nthis extraordinary narrative. Then Sherlock Holmes pulled down\nfrom the shelf one of the ponderous commonplace books in which he\nplaced his cuttings.\n\n\"Here is an advertisement which will interest you,\" said he. \"It\nappeared in all the papers about a year ago. Listen to this:\n'Lost, on the 9th inst., Mr. Jeremiah Hayling, aged\ntwenty-six, a hydraulic engineer. Left his lodgings at ten\no'clock at night, and has not been heard of since. Was\ndressed in,' etc., etc. Ha! That represents the last time that\nthe colonel needed to have his machine overhauled, I fancy.\"\n\n\"Good heavens!\" cried my patient. \"Then that explains what the\ngirl said.\"\n\n\"Undoubtedly. It is quite clear that the colonel was a cool and\ndesperate man, who was absolutely determined that nothing should\nstand in the way of his little game, like those out-and-out\npirates who will leave no survivor from a captured ship. Well,\nevery moment now is precious, so if you feel equal to it we shall\ngo down to Scotland Yard at once as a preliminary to starting for\nEyford.\"\n\nSome three hours or so afterwards we were all in the train\ntogether, bound from Reading to the little Berkshire village.\nThere were Sherlock Holmes, the hydraulic engineer, Inspector\nBradstreet, of Scotland Yard, a plain-clothes man, and myself.\nBradstreet had spread an ordnance map of the county out upon the\nseat and was busy with his compasses drawing a circle with Eyford\nfor its centre.\n\n\"There you are,\" said he. \"That circle is drawn at a radius of\nten miles from the village. The place we want must be somewhere\nnear that line. You said ten miles, I think, sir.\"\n\n\"It was an hour's good drive.\"\n\n\"And you think that they brought you back all that way when you\nwere unconscious?\"\n\n\"They must have done so. I have a confused memory, too, of having\nbeen lifted and conveyed somewhere.\"\n\n\"What I cannot understand,\" said I, \"is why they should have\nspared you when they found you lying fainting in the garden.\nPerhaps the villain was softened by the woman's entreaties.\"\n\n\"I hardly think that likely. I never saw a more inexorable face\nin my life.\"\n\n\"Oh, we shall soon clear up all that,\" said Bradstreet. \"Well, I\nhave drawn my circle, and I only wish I knew at what point upon\nit the folk that we are in search of are to be found.\"\n\n\"I think I could lay my finger on it,\" said Holmes quietly.\n\n\"Really, now!\" cried the inspector, \"you have formed your\nopinion! Come, now, we shall see who agrees with you. I say it is\nsouth, for the country is more deserted there.\"\n\n\"And I say east,\" said my patient.\n\n\"I am for west,\" remarked the plain-clothes man. \"There are\nseveral quiet little villages up there.\"\n\n\"And I am for north,\" said I, \"because there are no hills there,\nand our friend says that he did not notice the carriage go up\nany.\"\n\n\"Come,\" cried the inspector, laughing; \"it's a very pretty\ndiversity of opinion. We have boxed the compass among us. Who do\nyou give your casting vote to?\"\n\n\"You are all wrong.\"\n\n\"But we can't all be.\"\n\n\"Oh, yes, you can. This is my point.\" He placed his finger in the\ncentre of the circle. \"This is where we shall find them.\"\n\n\"But the twelve-mile drive?\" gasped Hatherley.\n\n\"Six out and six back. Nothing simpler. You say yourself that the\nhorse was fresh and glossy when you got in. How could it be that\nif it had gone twelve miles over heavy roads?\"\n\n\"Indeed, it is a likely ruse enough,\" observed Bradstreet\nthoughtfully. \"Of course there can be no doubt as to the nature\nof this gang.\"\n\n\"None at all,\" said Holmes. \"They are coiners on a large scale,\nand have used the machine to form the amalgam which has taken the\nplace of silver.\"\n\n\"We have known for some time that a clever gang was at work,\"\nsaid the inspector. \"They have been turning out half-crowns by\nthe thousand. We even traced them as far as Reading, but could\nget no farther, for they had covered their traces in a way that\nshowed that they were very old hands. But now, thanks to this\nlucky chance, I think that we have got them right enough.\"\n\nBut the inspector was mistaken, for those criminals were not\ndestined to fall into the hands of justice. As we rolled into\nEyford Station we saw a gigantic column of smoke which streamed\nup from behind a small clump of trees in the neighbourhood and\nhung like an immense ostrich feather over the landscape.\n\n\"A house on fire?\" asked Bradstreet as the train steamed off\nagain on its way.\n\n\"Yes, sir!\" said the station-master.\n\n\"When did it break out?\"\n\n\"I hear that it was during the night, sir, but it has got worse,\nand the whole place is in a blaze.\"\n\n\"Whose house is it?\"\n\n\"Dr. Becher's.\"\n\n\"Tell me,\" broke in the engineer, \"is Dr. Becher a German, very\nthin, with a long, sharp nose?\"\n\nThe station-master laughed heartily. \"No, sir, Dr. Becher is an\nEnglishman, and there isn't a man in the parish who has a\nbetter-lined waistcoat. But he has a gentleman staying with him,\na patient, as I understand, who is a foreigner, and he looks as\nif a little good Berkshire beef would do him no harm.\"\n\nThe station-master had not finished his speech before we were all\nhastening in the direction of the fire. The road topped a low\nhill, and there was a great widespread whitewashed building in\nfront of us, spouting fire at every chink and window, while in\nthe garden in front three fire-engines were vainly striving to\nkeep the flames under.\n\n\"That's it!\" cried Hatherley, in intense excitement. \"There is\nthe gravel-drive, and there are the rose-bushes where I lay. That\nsecond window is the one that I jumped from.\"\n\n\"Well, at least,\" said Holmes, \"you have had your revenge upon\nthem. There can be no question that it was your oil-lamp which,\nwhen it was crushed in the press, set fire to the wooden walls,\nthough no doubt they were too excited in the chase after you to\nobserve it at the time. Now keep your eyes open in this crowd for\nyour friends of last night, though I very much fear that they are\na good hundred miles off by now.\"\n\nAnd Holmes' fears came to be realised, for from that day to this\nno word has ever been heard either of the beautiful woman, the\nsinister German, or the morose Englishman. Early that morning a\npeasant had met a cart containing several people and some very\nbulky boxes driving rapidly in the direction of Reading, but\nthere all traces of the fugitives disappeared, and even Holmes'\ningenuity failed ever to discover the least clue as to their\nwhereabouts.\n\nThe firemen had been much perturbed at the strange arrangements\nwhich they had found within, and still more so by discovering a\nnewly severed human thumb upon a window-sill of the second floor.\nAbout sunset, however, their efforts were at last successful, and\nthey subdued the flames, but not before the roof had fallen in,\nand the whole place been reduced to such absolute ruin that, save\nsome twisted cylinders and iron piping, not a trace remained of\nthe machinery which had cost our unfortunate acquaintance so\ndearly. Large masses of nickel and of tin were discovered stored\nin an out-house, but no coins were to be found, which may have\nexplained the presence of those bulky boxes which have been\nalready referred to.\n\nHow our hydraulic engineer had been conveyed from the garden to\nthe spot where he recovered his senses might have remained\nforever a mystery were it not for the soft mould, which told us a\nvery plain tale. He had evidently been carried down by two\npersons, one of whom had remarkably small feet and the other\nunusually large ones. On the whole, it was most probable that the\nsilent Englishman, being less bold or less murderous than his\ncompanion, had assisted the woman to bear the unconscious man out\nof the way of danger.\n\n\"Well,\" said our engineer ruefully as we took our seats to return\nonce more to London, \"it has been a pretty business for me! I\nhave lost my thumb and I have lost a fifty-guinea fee, and what\nhave I gained?\"\n\n\"Experience,\" said Holmes, laughing. \"Indirectly it may be of\nvalue, you know; you have only to put it into words to gain the\nreputation of being excellent company for the remainder of your\nexistence.\"\n\n\n\nX. THE ADVENTURE OF THE NOBLE BACHELOR\n\nThe Lord St. Simon marriage, and its curious termination, have\nlong ceased to be a subject of interest in those exalted circles\nin which the unfortunate bridegroom moves. Fresh scandals have\neclipsed it, and their more piquant details have drawn the\ngossips away from this four-year-old drama. As I have reason to\nbelieve, however, that the full facts have never been revealed to\nthe general public, and as my friend Sherlock Holmes had a\nconsiderable share in clearing the matter up, I feel that no\nmemoir of him would be complete without some little sketch of\nthis remarkable episode.\n\nIt was a few weeks before my own marriage, during the days when I\nwas still sharing rooms with Holmes in Baker Street, that he came\nhome from an afternoon stroll to find a letter on the table\nwaiting for him. I had remained indoors all day, for the weather\nhad taken a sudden turn to rain, with high autumnal winds, and\nthe Jezail bullet which I had brought back in one of my limbs as\na relic of my Afghan campaign throbbed with dull persistence.\nWith my body in one easy-chair and my legs upon another, I had\nsurrounded myself with a cloud of newspapers until at last,\nsaturated with the news of the day, I tossed them all aside and\nlay listless, watching the huge crest and monogram upon the\nenvelope upon the table and wondering lazily who my friend's\nnoble correspondent could be.\n\n\"Here is a very fashionable epistle,\" I remarked as he entered.\n\"Your morning letters, if I remember right, were from a\nfish-monger and a tide-waiter.\"\n\n\"Yes, my correspondence has certainly the charm of variety,\" he\nanswered, smiling, \"and the humbler are usually the more\ninteresting. This looks like one of those unwelcome social\nsummonses which call upon a man either to be bored or to lie.\"\n\nHe broke the seal and glanced over the contents.\n\n\"Oh, come, it may prove to be something of interest, after all.\"\n\n\"Not social, then?\"\n\n\"No, distinctly professional.\"\n\n\"And from a noble client?\"\n\n\"One of the highest in England.\"\n\n\"My dear fellow, I congratulate you.\"\n\n\"I assure you, Watson, without affectation, that the status of my\nclient is a matter of less moment to me than the interest of his\ncase. It is just possible, however, that that also may not be\nwanting in this new investigation. You have been reading the\npapers diligently of late, have you not?\"\n\n\"It looks like it,\" said I ruefully, pointing to a huge bundle in\nthe corner. \"I have had nothing else to do.\"\n\n\"It is fortunate, for you will perhaps be able to post me up. I\nread nothing except the criminal news and the agony column. The\nlatter is always instructive. But if you have followed recent\nevents so closely you must have read about Lord St. Simon and his\nwedding?\"\n\n\"Oh, yes, with the deepest interest.\"\n\n\"That is well. The letter which I hold in my hand is from Lord\nSt. Simon. I will read it to you, and in return you must turn\nover these papers and let me have whatever bears upon the matter.\nThis is what he says:\n\n\"'MY DEAR MR. SHERLOCK HOLMES:--Lord Backwater tells me that I\nmay place implicit reliance upon your judgment and discretion. I\nhave determined, therefore, to call upon you and to consult you\nin reference to the very painful event which has occurred in\nconnection with my wedding. Mr. Lestrade, of Scotland Yard, is\nacting already in the matter, but he assures me that he sees no\nobjection to your co-operation, and that he even thinks that\nit might be of some assistance. I will call at four o'clock in\nthe afternoon, and, should you have any other engagement at that\ntime, I hope that you will postpone it, as this matter is of\nparamount importance. Yours faithfully, ST. SIMON.'\n\n\"It is dated from Grosvenor Mansions, written with a quill pen,\nand the noble lord has had the misfortune to get a smear of ink\nupon the outer side of his right little finger,\" remarked Holmes\nas he folded up the epistle.\n\n\"He says four o'clock. It is three now. He will be here in an\nhour.\"\n\n\"Then I have just time, with your assistance, to get clear upon\nthe subject. Turn over those papers and arrange the extracts in\ntheir order of time, while I take a glance as to who our client\nis.\" He picked a red-covered volume from a line of books of\nreference beside the mantelpiece. \"Here he is,\" said he, sitting\ndown and flattening it out upon his knee. \"'Lord Robert Walsingham\nde Vere St. Simon, second son of the Duke of Balmoral.' Hum! 'Arms:\nAzure, three caltrops in chief over a fess sable. Born in 1846.'\nHe's forty-one years of age, which is mature for marriage. Was\nUnder-Secretary for the colonies in a late administration. The\nDuke, his father, was at one time Secretary for Foreign Affairs.\nThey inherit Plantagenet blood by direct descent, and Tudor on\nthe distaff side. Ha! Well, there is nothing very instructive in\nall this. I think that I must turn to you Watson, for something\nmore solid.\"\n\n\"I have very little difficulty in finding what I want,\" said I,\n\"for the facts are quite recent, and the matter struck me as\nremarkable. I feared to refer them to you, however, as I knew\nthat you had an inquiry on hand and that you disliked the\nintrusion of other matters.\"\n\n\"Oh, you mean the little problem of the Grosvenor Square\nfurniture van. That is quite cleared up now--though, indeed, it\nwas obvious from the first. Pray give me the results of your\nnewspaper selections.\"\n\n\"Here is the first notice which I can find. It is in the personal\ncolumn of the Morning Post, and dates, as you see, some weeks\nback: 'A marriage has been arranged,' it says, 'and will, if\nrumour is correct, very shortly take place, between Lord Robert\nSt. Simon, second son of the Duke of Balmoral, and Miss Hatty\nDoran, the only daughter of Aloysius Doran. Esq., of San\nFrancisco, Cal., U.S.A.' That is all.\"\n\n\"Terse and to the point,\" remarked Holmes, stretching his long,\nthin legs towards the fire.\n\n\"There was a paragraph amplifying this in one of the society\npapers of the same week. Ah, here it is: 'There will soon be a\ncall for protection in the marriage market, for the present\nfree-trade principle appears to tell heavily against our home\nproduct. One by one the management of the noble houses of Great\nBritain is passing into the hands of our fair cousins from across\nthe Atlantic. An important addition has been made during the last\nweek to the list of the prizes which have been borne away by\nthese charming invaders. Lord St. Simon, who has shown himself\nfor over twenty years proof against the little god's arrows, has\nnow definitely announced his approaching marriage with Miss Hatty\nDoran, the fascinating daughter of a California millionaire. Miss\nDoran, whose graceful figure and striking face attracted much\nattention at the Westbury House festivities, is an only child,\nand it is currently reported that her dowry will run to\nconsiderably over the six figures, with expectancies for the\nfuture. As it is an open secret that the Duke of Balmoral has\nbeen compelled to sell his pictures within the last few years,\nand as Lord St. Simon has no property of his own save the small\nestate of Birchmoor, it is obvious that the Californian heiress\nis not the only gainer by an alliance which will enable her to\nmake the easy and common transition from a Republican lady to a\nBritish peeress.'\"\n\n\"Anything else?\" asked Holmes, yawning.\n\n\"Oh, yes; plenty. Then there is another note in the Morning Post\nto say that the marriage would be an absolutely quiet one, that it\nwould be at St. George's, Hanover Square, that only half a dozen\nintimate friends would be invited, and that the party would\nreturn to the furnished house at Lancaster Gate which has been\ntaken by Mr. Aloysius Doran. Two days later--that is, on\nWednesday last--there is a curt announcement that the wedding had\ntaken place, and that the honeymoon would be passed at Lord\nBackwater's place, near Petersfield. Those are all the notices\nwhich appeared before the disappearance of the bride.\"\n\n\"Before the what?\" asked Holmes with a start.\n\n\"The vanishing of the lady.\"\n\n\"When did she vanish, then?\"\n\n\"At the wedding breakfast.\"\n\n\"Indeed. This is more interesting than it promised to be; quite\ndramatic, in fact.\"\n\n\"Yes; it struck me as being a little out of the common.\"\n\n\"They often vanish before the ceremony, and occasionally during\nthe honeymoon; but I cannot call to mind anything quite so prompt\nas this. Pray let me have the details.\"\n\n\"I warn you that they are very incomplete.\"\n\n\"Perhaps we may make them less so.\"\n\n\"Such as they are, they are set forth in a single article of a\nmorning paper of yesterday, which I will read to you. It is\nheaded, 'Singular Occurrence at a Fashionable Wedding':\n\n\"'The family of Lord Robert St. Simon has been thrown into the\ngreatest consternation by the strange and painful episodes which\nhave taken place in connection with his wedding. The ceremony, as\nshortly announced in the papers of yesterday, occurred on the\nprevious morning; but it is only now that it has been possible to\nconfirm the strange rumours which have been so persistently\nfloating about. In spite of the attempts of the friends to hush\nthe matter up, so much public attention has now been drawn to it\nthat no good purpose can be served by affecting to disregard what\nis a common subject for conversation.\n\n\"'The ceremony, which was performed at St. George's, Hanover\nSquare, was a very quiet one, no one being present save the\nfather of the bride, Mr. Aloysius Doran, the Duchess of Balmoral,\nLord Backwater, Lord Eustace and Lady Clara St. Simon (the\nyounger brother and sister of the bridegroom), and Lady Alicia\nWhittington. The whole party proceeded afterwards to the house of\nMr. Aloysius Doran, at Lancaster Gate, where breakfast had been\nprepared. It appears that some little trouble was caused by a\nwoman, whose name has not been ascertained, who endeavoured to\nforce her way into the house after the bridal party, alleging\nthat she had some claim upon Lord St. Simon. It was only after a\npainful and prolonged scene that she was ejected by the butler\nand the footman. The bride, who had fortunately entered the house\nbefore this unpleasant interruption, had sat down to breakfast\nwith the rest, when she complained of a sudden indisposition and\nretired to her room. Her prolonged absence having caused some\ncomment, her father followed her, but learned from her maid that\nshe had only come up to her chamber for an instant, caught up an\nulster and bonnet, and hurried down to the passage. One of the\nfootmen declared that he had seen a lady leave the house thus\napparelled, but had refused to credit that it was his mistress,\nbelieving her to be with the company. On ascertaining that his\ndaughter had disappeared, Mr. Aloysius Doran, in conjunction with\nthe bridegroom, instantly put themselves in communication with\nthe police, and very energetic inquiries are being made, which\nwill probably result in a speedy clearing up of this very\nsingular business. Up to a late hour last night, however, nothing\nhad transpired as to the whereabouts of the missing lady. There\nare rumours of foul play in the matter, and it is said that the\npolice have caused the arrest of the woman who had caused the\noriginal disturbance, in the belief that, from jealousy or some\nother motive, she may have been concerned in the strange\ndisappearance of the bride.'\"\n\n\"And is that all?\"\n\n\"Only one little item in another of the morning papers, but it is\na suggestive one.\"\n\n\"And it is--\"\n\n\"That Miss Flora Millar, the lady who had caused the disturbance,\nhas actually been arrested. It appears that she was formerly a\ndanseuse at the Allegro, and that she has known the bridegroom\nfor some years. There are no further particulars, and the whole\ncase is in your hands now--so far as it has been set forth in the\npublic press.\"\n\n\"And an exceedingly interesting case it appears to be. I would\nnot have missed it for worlds. But there is a ring at the bell,\nWatson, and as the clock makes it a few minutes after four, I\nhave no doubt that this will prove to be our noble client. Do not\ndream of going, Watson, for I very much prefer having a witness,\nif only as a check to my own memory.\"\n\n\"Lord Robert St. Simon,\" announced our page-boy, throwing open\nthe door. A gentleman entered, with a pleasant, cultured face,\nhigh-nosed and pale, with something perhaps of petulance about\nthe mouth, and with the steady, well-opened eye of a man whose\npleasant lot it had ever been to command and to be obeyed. His\nmanner was brisk, and yet his general appearance gave an undue\nimpression of age, for he had a slight forward stoop and a little\nbend of the knees as he walked. His hair, too, as he swept off\nhis very curly-brimmed hat, was grizzled round the edges and thin\nupon the top. As to his dress, it was careful to the verge of\nfoppishness, with high collar, black frock-coat, white waistcoat,\nyellow gloves, patent-leather shoes, and light-coloured gaiters.\nHe advanced slowly into the room, turning his head from left to\nright, and swinging in his right hand the cord which held his\ngolden eyeglasses.\n\n\"Good-day, Lord St. Simon,\" said Holmes, rising and bowing. \"Pray\ntake the basket-chair. This is my friend and colleague, Dr.\nWatson. Draw up a little to the fire, and we will talk this\nmatter over.\"\n\n\"A most painful matter to me, as you can most readily imagine,\nMr. Holmes. I have been cut to the quick. I understand that you\nhave already managed several delicate cases of this sort, sir,\nthough I presume that they were hardly from the same class of\nsociety.\"\n\n\"No, I am descending.\"\n\n\"I beg pardon.\"\n\n\"My last client of the sort was a king.\"\n\n\"Oh, really! I had no idea. And which king?\"\n\n\"The King of Scandinavia.\"\n\n\"What! Had he lost his wife?\"\n\n\"You can understand,\" said Holmes suavely, \"that I extend to the\naffairs of my other clients the same secrecy which I promise to\nyou in yours.\"\n\n\"Of course! Very right! very right! I'm sure I beg pardon. As to\nmy own case, I am ready to give you any information which may\nassist you in forming an opinion.\"\n\n\"Thank you. I have already learned all that is in the public\nprints, nothing more. I presume that I may take it as correct--this\narticle, for example, as to the disappearance of the bride.\"\n\nLord St. Simon glanced over it. \"Yes, it is correct, as far as it\ngoes.\"\n\n\"But it needs a great deal of supplementing before anyone could\noffer an opinion. I think that I may arrive at my facts most\ndirectly by questioning you.\"\n\n\"Pray do so.\"\n\n\"When did you first meet Miss Hatty Doran?\"\n\n\"In San Francisco, a year ago.\"\n\n\"You were travelling in the States?\"\n\n\"Yes.\"\n\n\"Did you become engaged then?\"\n\n\"No.\"\n\n\"But you were on a friendly footing?\"\n\n\"I was amused by her society, and she could see that I was\namused.\"\n\n\"Her father is very rich?\"\n\n\"He is said to be the richest man on the Pacific slope.\"\n\n\"And how did he make his money?\"\n\n\"In mining. He had nothing a few years ago. Then he struck gold,\ninvested it, and came up by leaps and bounds.\"\n\n\"Now, what is your own impression as to the young lady's--your\nwife's character?\"\n\nThe nobleman swung his glasses a little faster and stared down\ninto the fire. \"You see, Mr. Holmes,\" said he, \"my wife was\ntwenty before her father became a rich man. During that time she\nran free in a mining camp and wandered through woods or\nmountains, so that her education has come from Nature rather than\nfrom the schoolmaster. She is what we call in England a tomboy,\nwith a strong nature, wild and free, unfettered by any sort of\ntraditions. She is impetuous--volcanic, I was about to say. She\nis swift in making up her mind and fearless in carrying out her\nresolutions. On the other hand, I would not have given her the\nname which I have the honour to bear\"--he gave a little stately\ncough--\"had not I thought her to be at bottom a noble woman. I\nbelieve that she is capable of heroic self-sacrifice and that\nanything dishonourable would be repugnant to her.\"\n\n\"Have you her photograph?\"\n\n\"I brought this with me.\" He opened a locket and showed us the\nfull face of a very lovely woman. It was not a photograph but an\nivory miniature, and the artist had brought out the full effect\nof the lustrous black hair, the large dark eyes, and the\nexquisite mouth. Holmes gazed long and earnestly at it. Then he\nclosed the locket and handed it back to Lord St. Simon.\n\n\"The young lady came to London, then, and you renewed your\nacquaintance?\"\n\n\"Yes, her father brought her over for this last London season. I\nmet her several times, became engaged to her, and have now\nmarried her.\"\n\n\"She brought, I understand, a considerable dowry?\"\n\n\"A fair dowry. Not more than is usual in my family.\"\n\n\"And this, of course, remains to you, since the marriage is a\nfait accompli?\"\n\n\"I really have made no inquiries on the subject.\"\n\n\"Very naturally not. Did you see Miss Doran on the day before the\nwedding?\"\n\n\"Yes.\"\n\n\"Was she in good spirits?\"\n\n\"Never better. She kept talking of what we should do in our\nfuture lives.\"\n\n\"Indeed! That is very interesting. And on the morning of the\nwedding?\"\n\n\"She was as bright as possible--at least until after the\nceremony.\"\n\n\"And did you observe any change in her then?\"\n\n\"Well, to tell the truth, I saw then the first signs that I had\never seen that her temper was just a little sharp. The incident\nhowever, was too trivial to relate and can have no possible\nbearing upon the case.\"\n\n\"Pray let us have it, for all that.\"\n\n\"Oh, it is childish. She dropped her bouquet as we went towards\nthe vestry. She was passing the front pew at the time, and it\nfell over into the pew. There was a moment's delay, but the\ngentleman in the pew handed it up to her again, and it did not\nappear to be the worse for the fall. Yet when I spoke to her of\nthe matter, she answered me abruptly; and in the carriage, on our\nway home, she seemed absurdly agitated over this trifling cause.\"\n\n\"Indeed! You say that there was a gentleman in the pew. Some of\nthe general public were present, then?\"\n\n\"Oh, yes. It is impossible to exclude them when the church is\nopen.\"\n\n\"This gentleman was not one of your wife's friends?\"\n\n\"No, no; I call him a gentleman by courtesy, but he was quite a\ncommon-looking person. I hardly noticed his appearance. But\nreally I think that we are wandering rather far from the point.\"\n\n\"Lady St. Simon, then, returned from the wedding in a less\ncheerful frame of mind than she had gone to it. What did she do\non re-entering her father's house?\"\n\n\"I saw her in conversation with her maid.\"\n\n\"And who is her maid?\"\n\n\"Alice is her name. She is an American and came from California\nwith her.\"\n\n\"A confidential servant?\"\n\n\"A little too much so. It seemed to me that her mistress allowed\nher to take great liberties. Still, of course, in America they\nlook upon these things in a different way.\"\n\n\"How long did she speak to this Alice?\"\n\n\"Oh, a few minutes. I had something else to think of.\"\n\n\"You did not overhear what they said?\"\n\n\"Lady St. Simon said something about 'jumping a claim.' She was\naccustomed to use slang of the kind. I have no idea what she\nmeant.\"\n\n\"American slang is very expressive sometimes. And what did your\nwife do when she finished speaking to her maid?\"\n\n\"She walked into the breakfast-room.\"\n\n\"On your arm?\"\n\n\"No, alone. She was very independent in little matters like that.\nThen, after we had sat down for ten minutes or so, she rose\nhurriedly, muttered some words of apology, and left the room. She\nnever came back.\"\n\n\"But this maid, Alice, as I understand, deposes that she went to\nher room, covered her bride's dress with a long ulster, put on a\nbonnet, and went out.\"\n\n\"Quite so. And she was afterwards seen walking into Hyde Park in\ncompany with Flora Millar, a woman who is now in custody, and who\nhad already made a disturbance at Mr. Doran's house that\nmorning.\"\n\n\"Ah, yes. I should like a few particulars as to this young lady,\nand your relations to her.\"\n\nLord St. Simon shrugged his shoulders and raised his eyebrows.\n\"We have been on a friendly footing for some years--I may say on\na very friendly footing. She used to be at the Allegro. I have\nnot treated her ungenerously, and she had no just cause of\ncomplaint against me, but you know what women are, Mr. Holmes.\nFlora was a dear little thing, but exceedingly hot-headed and\ndevotedly attached to me. She wrote me dreadful letters when she\nheard that I was about to be married, and, to tell the truth, the\nreason why I had the marriage celebrated so quietly was that I\nfeared lest there might be a scandal in the church. She came to\nMr. Doran's door just after we returned, and she endeavoured to\npush her way in, uttering very abusive expressions towards my\nwife, and even threatening her, but I had foreseen the\npossibility of something of the sort, and I had two police\nfellows there in private clothes, who soon pushed her out again.\nShe was quiet when she saw that there was no good in making a\nrow.\"\n\n\"Did your wife hear all this?\"\n\n\"No, thank goodness, she did not.\"\n\n\"And she was seen walking with this very woman afterwards?\"\n\n\"Yes. That is what Mr. Lestrade, of Scotland Yard, looks upon as\nso serious. It is thought that Flora decoyed my wife out and laid\nsome terrible trap for her.\"\n\n\"Well, it is a possible supposition.\"\n\n\"You think so, too?\"\n\n\"I did not say a probable one. But you do not yourself look upon\nthis as likely?\"\n\n\"I do not think Flora would hurt a fly.\"\n\n\"Still, jealousy is a strange transformer of characters. Pray\nwhat is your own theory as to what took place?\"\n\n\"Well, really, I came to seek a theory, not to propound one. I\nhave given you all the facts. Since you ask me, however, I may\nsay that it has occurred to me as possible that the excitement of\nthis affair, the consciousness that she had made so immense a\nsocial stride, had the effect of causing some little nervous\ndisturbance in my wife.\"\n\n\"In short, that she had become suddenly deranged?\"\n\n\"Well, really, when I consider that she has turned her back--I\nwill not say upon me, but upon so much that many have aspired to\nwithout success--I can hardly explain it in any other fashion.\"\n\n\"Well, certainly that is also a conceivable hypothesis,\" said\nHolmes, smiling. \"And now, Lord St. Simon, I think that I have\nnearly all my data. May I ask whether you were seated at the\nbreakfast-table so that you could see out of the window?\"\n\n\"We could see the other side of the road and the Park.\"\n\n\"Quite so. Then I do not think that I need to detain you longer.\nI shall communicate with you.\"\n\n\"Should you be fortunate enough to solve this problem,\" said our\nclient, rising.\n\n\"I have solved it.\"\n\n\"Eh? What was that?\"\n\n\"I say that I have solved it.\"\n\n\"Where, then, is my wife?\"\n\n\"That is a detail which I shall speedily supply.\"\n\nLord St. Simon shook his head. \"I am afraid that it will take\nwiser heads than yours or mine,\" he remarked, and bowing in a\nstately, old-fashioned manner he departed.\n\n\"It is very good of Lord St. Simon to honour my head by putting\nit on a level with his own,\" said Sherlock Holmes, laughing. \"I\nthink that I shall have a whisky and soda and a cigar after all\nthis cross-questioning. I had formed my conclusions as to the\ncase before our client came into the room.\"\n\n\"My dear Holmes!\"\n\n\"I have notes of several similar cases, though none, as I\nremarked before, which were quite as prompt. My whole examination\nserved to turn my conjecture into a certainty. Circumstantial\nevidence is occasionally very convincing, as when you find a\ntrout in the milk, to quote Thoreau's example.\"\n\n\"But I have heard all that you have heard.\"\n\n\"Without, however, the knowledge of pre-existing cases which\nserves me so well. There was a parallel instance in Aberdeen some\nyears back, and something on very much the same lines at Munich\nthe year after the Franco-Prussian War. It is one of these\ncases--but, hullo, here is Lestrade! Good-afternoon, Lestrade!\nYou will find an extra tumbler upon the sideboard, and there are\ncigars in the box.\"\n\nThe official detective was attired in a pea-jacket and cravat,\nwhich gave him a decidedly nautical appearance, and he carried a\nblack canvas bag in his hand. With a short greeting he seated\nhimself and lit the cigar which had been offered to him.\n\n\"What's up, then?\" asked Holmes with a twinkle in his eye. \"You\nlook dissatisfied.\"\n\n\"And I feel dissatisfied. It is this infernal St. Simon marriage\ncase. I can make neither head nor tail of the business.\"\n\n\"Really! You surprise me.\"\n\n\"Who ever heard of such a mixed affair? Every clue seems to slip\nthrough my fingers. I have been at work upon it all day.\"\n\n\"And very wet it seems to have made you,\" said Holmes laying his\nhand upon the arm of the pea-jacket.\n\n\"Yes, I have been dragging the Serpentine.\"\n\n\"In heaven's name, what for?\"\n\n\"In search of the body of Lady St. Simon.\"\n\nSherlock Holmes leaned back in his chair and laughed heartily.\n\n\"Have you dragged the basin of Trafalgar Square fountain?\" he\nasked.\n\n\"Why? What do you mean?\"\n\n\"Because you have just as good a chance of finding this lady in\nthe one as in the other.\"\n\nLestrade shot an angry glance at my companion. \"I suppose you\nknow all about it,\" he snarled.\n\n\"Well, I have only just heard the facts, but my mind is made up.\"\n\n\"Oh, indeed! Then you think that the Serpentine plays no part in\nthe matter?\"\n\n\"I think it very unlikely.\"\n\n\"Then perhaps you will kindly explain how it is that we found\nthis in it?\" He opened his bag as he spoke, and tumbled onto the\nfloor a wedding-dress of watered silk, a pair of white satin\nshoes and a bride's wreath and veil, all discoloured and soaked\nin water. \"There,\" said he, putting a new wedding-ring upon the\ntop of the pile. \"There is a little nut for you to crack, Master\nHolmes.\"\n\n\"Oh, indeed!\" said my friend, blowing blue rings into the air.\n\"You dragged them from the Serpentine?\"\n\n\"No. They were found floating near the margin by a park-keeper.\nThey have been identified as her clothes, and it seemed to me\nthat if the clothes were there the body would not be far off.\"\n\n\"By the same brilliant reasoning, every man's body is to be found\nin the neighbourhood of his wardrobe. And pray what did you hope\nto arrive at through this?\"\n\n\"At some evidence implicating Flora Millar in the disappearance.\"\n\n\"I am afraid that you will find it difficult.\"\n\n\"Are you, indeed, now?\" cried Lestrade with some bitterness. \"I\nam afraid, Holmes, that you are not very practical with your\ndeductions and your inferences. You have made two blunders in as\nmany minutes. This dress does implicate Miss Flora Millar.\"\n\n\"And how?\"\n\n\"In the dress is a pocket. In the pocket is a card-case. In the\ncard-case is a note. And here is the very note.\" He slapped it\ndown upon the table in front of him. \"Listen to this: 'You will\nsee me when all is ready. Come at once. F.H.M.' Now my theory all\nalong has been that Lady St. Simon was decoyed away by Flora\nMillar, and that she, with confederates, no doubt, was\nresponsible for her disappearance. Here, signed with her\ninitials, is the very note which was no doubt quietly slipped\ninto her hand at the door and which lured her within their\nreach.\"\n\n\"Very good, Lestrade,\" said Holmes, laughing. \"You really are\nvery fine indeed. Let me see it.\" He took up the paper in a\nlistless way, but his attention instantly became riveted, and he\ngave a little cry of satisfaction. \"This is indeed important,\"\nsaid he.\n\n\"Ha! you find it so?\"\n\n\"Extremely so. I congratulate you warmly.\"\n\nLestrade rose in his triumph and bent his head to look. \"Why,\" he\nshrieked, \"you're looking at the wrong side!\"\n\n\"On the contrary, this is the right side.\"\n\n\"The right side? You're mad! Here is the note written in pencil\nover here.\"\n\n\"And over here is what appears to be the fragment of a hotel\nbill, which interests me deeply.\"\n\n\"There's nothing in it. I looked at it before,\" said Lestrade.\n\"'Oct. 4th, rooms 8s., breakfast 2s. 6d., cocktail 1s., lunch 2s.\n6d., glass sherry, 8d.' I see nothing in that.\"\n\n\"Very likely not. It is most important, all the same. As to the\nnote, it is important also, or at least the initials are, so I\ncongratulate you again.\"\n\n\"I've wasted time enough,\" said Lestrade, rising. \"I believe in\nhard work and not in sitting by the fire spinning fine theories.\nGood-day, Mr. Holmes, and we shall see which gets to the bottom\nof the matter first.\" He gathered up the garments, thrust them\ninto the bag, and made for the door.\n\n\"Just one hint to you, Lestrade,\" drawled Holmes before his rival\nvanished; \"I will tell you the true solution of the matter. Lady\nSt. Simon is a myth. There is not, and there never has been, any\nsuch person.\"\n\nLestrade looked sadly at my companion. Then he turned to me,\ntapped his forehead three times, shook his head solemnly, and\nhurried away.\n\nHe had hardly shut the door behind him when Holmes rose to put on\nhis overcoat. \"There is something in what the fellow says about\noutdoor work,\" he remarked, \"so I think, Watson, that I must\nleave you to your papers for a little.\"\n\nIt was after five o'clock when Sherlock Holmes left me, but I had\nno time to be lonely, for within an hour there arrived a\nconfectioner's man with a very large flat box. This he unpacked\nwith the help of a youth whom he had brought with him, and\npresently, to my very great astonishment, a quite epicurean\nlittle cold supper began to be laid out upon our humble\nlodging-house mahogany. There were a couple of brace of cold\nwoodcock, a pheasant, a pâté de foie gras pie with a group of\nancient and cobwebby bottles. Having laid out all these luxuries,\nmy two visitors vanished away, like the genii of the Arabian\nNights, with no explanation save that the things had been paid\nfor and were ordered to this address.\n\nJust before nine o'clock Sherlock Holmes stepped briskly into the\nroom. His features were gravely set, but there was a light in his\neye which made me think that he had not been disappointed in his\nconclusions.\n\n\"They have laid the supper, then,\" he said, rubbing his hands.\n\n\"You seem to expect company. They have laid for five.\"\n\n\"Yes, I fancy we may have some company dropping in,\" said he. \"I\nam surprised that Lord St. Simon has not already arrived. Ha! I\nfancy that I hear his step now upon the stairs.\"\n\nIt was indeed our visitor of the afternoon who came bustling in,\ndangling his glasses more vigorously than ever, and with a very\nperturbed expression upon his aristocratic features.\n\n\"My messenger reached you, then?\" asked Holmes.\n\n\"Yes, and I confess that the contents startled me beyond measure.\nHave you good authority for what you say?\"\n\n\"The best possible.\"\n\nLord St. Simon sank into a chair and passed his hand over his\nforehead.\n\n\"What will the Duke say,\" he murmured, \"when he hears that one of\nthe family has been subjected to such humiliation?\"\n\n\"It is the purest accident. I cannot allow that there is any\nhumiliation.\"\n\n\"Ah, you look on these things from another standpoint.\"\n\n\"I fail to see that anyone is to blame. I can hardly see how the\nlady could have acted otherwise, though her abrupt method of\ndoing it was undoubtedly to be regretted. Having no mother, she\nhad no one to advise her at such a crisis.\"\n\n\"It was a slight, sir, a public slight,\" said Lord St. Simon,\ntapping his fingers upon the table.\n\n\"You must make allowance for this poor girl, placed in so\nunprecedented a position.\"\n\n\"I will make no allowance. I am very angry indeed, and I have\nbeen shamefully used.\"\n\n\"I think that I heard a ring,\" said Holmes. \"Yes, there are steps\non the landing. If I cannot persuade you to take a lenient view\nof the matter, Lord St. Simon, I have brought an advocate here\nwho may be more successful.\" He opened the door and ushered in a\nlady and gentleman. \"Lord St. Simon,\" said he \"allow me to\nintroduce you to Mr. and Mrs. Francis Hay Moulton. The lady, I\nthink, you have already met.\"\n\nAt the sight of these newcomers our client had sprung from his\nseat and stood very erect, with his eyes cast down and his hand\nthrust into the breast of his frock-coat, a picture of offended\ndignity. The lady had taken a quick step forward and had held out\nher hand to him, but he still refused to raise his eyes. It was\nas well for his resolution, perhaps, for her pleading face was\none which it was hard to resist.\n\n\"You're angry, Robert,\" said she. \"Well, I guess you have every\ncause to be.\"\n\n\"Pray make no apology to me,\" said Lord St. Simon bitterly.\n\n\"Oh, yes, I know that I have treated you real bad and that I\nshould have spoken to you before I went; but I was kind of\nrattled, and from the time when I saw Frank here again I just\ndidn't know what I was doing or saying. I only wonder I didn't\nfall down and do a faint right there before the altar.\"\n\n\"Perhaps, Mrs. Moulton, you would like my friend and me to leave\nthe room while you explain this matter?\"\n\n\"If I may give an opinion,\" remarked the strange gentleman,\n\"we've had just a little too much secrecy over this business\nalready. For my part, I should like all Europe and America to\nhear the rights of it.\" He was a small, wiry, sunburnt man,\nclean-shaven, with a sharp face and alert manner.\n\n\"Then I'll tell our story right away,\" said the lady. \"Frank here\nand I met in '84, in McQuire's camp, near the Rockies, where pa\nwas working a claim. We were engaged to each other, Frank and I;\nbut then one day father struck a rich pocket and made a pile,\nwhile poor Frank here had a claim that petered out and came to\nnothing. The richer pa grew the poorer was Frank; so at last pa\nwouldn't hear of our engagement lasting any longer, and he took\nme away to 'Frisco. Frank wouldn't throw up his hand, though; so\nhe followed me there, and he saw me without pa knowing anything\nabout it. It would only have made him mad to know, so we just\nfixed it all up for ourselves. Frank said that he would go and\nmake his pile, too, and never come back to claim me until he had\nas much as pa. So then I promised to wait for him to the end of\ntime and pledged myself not to marry anyone else while he lived.\n'Why shouldn't we be married right away, then,' said he, 'and\nthen I will feel sure of you; and I won't claim to be your\nhusband until I come back?' Well, we talked it over, and he had\nfixed it all up so nicely, with a clergyman all ready in waiting,\nthat we just did it right there; and then Frank went off to seek\nhis fortune, and I went back to pa.\n\n\"The next I heard of Frank was that he was in Montana, and then\nhe went prospecting in Arizona, and then I heard of him from New\nMexico. After that came a long newspaper story about how a\nminers' camp had been attacked by Apache Indians, and there was\nmy Frank's name among the killed. I fainted dead away, and I was\nvery sick for months after. Pa thought I had a decline and took\nme to half the doctors in 'Frisco. Not a word of news came for a\nyear and more, so that I never doubted that Frank was really\ndead. Then Lord St. Simon came to 'Frisco, and we came to London,\nand a marriage was arranged, and pa was very pleased, but I felt\nall the time that no man on this earth would ever take the place\nin my heart that had been given to my poor Frank.\n\n\"Still, if I had married Lord St. Simon, of course I'd have done\nmy duty by him. We can't command our love, but we can our\nactions. I went to the altar with him with the intention to make\nhim just as good a wife as it was in me to be. But you may\nimagine what I felt when, just as I came to the altar rails, I\nglanced back and saw Frank standing and looking at me out of the\nfirst pew. I thought it was his ghost at first; but when I looked\nagain there he was still, with a kind of question in his eyes, as\nif to ask me whether I were glad or sorry to see him. I wonder I\ndidn't drop. I know that everything was turning round, and the\nwords of the clergyman were just like the buzz of a bee in my\near. I didn't know what to do. Should I stop the service and make\na scene in the church? I glanced at him again, and he seemed to\nknow what I was thinking, for he raised his finger to his lips to\ntell me to be still. Then I saw him scribble on a piece of paper,\nand I knew that he was writing me a note. As I passed his pew on\nthe way out I dropped my bouquet over to him, and he slipped the\nnote into my hand when he returned me the flowers. It was only a\nline asking me to join him when he made the sign to me to do so.\nOf course I never doubted for a moment that my first duty was now\nto him, and I determined to do just whatever he might direct.\n\n\"When I got back I told my maid, who had known him in California,\nand had always been his friend. I ordered her to say nothing, but\nto get a few things packed and my ulster ready. I know I ought to\nhave spoken to Lord St. Simon, but it was dreadful hard before\nhis mother and all those great people. I just made up my mind to\nrun away and explain afterwards. I hadn't been at the table ten\nminutes before I saw Frank out of the window at the other side of\nthe road. He beckoned to me and then began walking into the Park.\nI slipped out, put on my things, and followed him. Some woman\ncame talking something or other about Lord St. Simon to\nme--seemed to me from the little I heard as if he had a little\nsecret of his own before marriage also--but I managed to get away\nfrom her and soon overtook Frank. We got into a cab together, and\naway we drove to some lodgings he had taken in Gordon Square, and\nthat was my true wedding after all those years of waiting. Frank\nhad been a prisoner among the Apaches, had escaped, came on to\n'Frisco, found that I had given him up for dead and had gone to\nEngland, followed me there, and had come upon me at last on the\nvery morning of my second wedding.\"\n\n\"I saw it in a paper,\" explained the American. \"It gave the name\nand the church but not where the lady lived.\"\n\n\"Then we had a talk as to what we should do, and Frank was all\nfor openness, but I was so ashamed of it all that I felt as if I\nshould like to vanish away and never see any of them again--just\nsending a line to pa, perhaps, to show him that I was alive. It\nwas awful to me to think of all those lords and ladies sitting\nround that breakfast-table and waiting for me to come back. So\nFrank took my wedding-clothes and things and made a bundle of\nthem, so that I should not be traced, and dropped them away\nsomewhere where no one could find them. It is likely that we\nshould have gone on to Paris to-morrow, only that this good\ngentleman, Mr. Holmes, came round to us this evening, though how\nhe found us is more than I can think, and he showed us very\nclearly and kindly that I was wrong and that Frank was right, and\nthat we should be putting ourselves in the wrong if we were so\nsecret. Then he offered to give us a chance of talking to Lord\nSt. Simon alone, and so we came right away round to his rooms at\nonce. Now, Robert, you have heard it all, and I am very sorry if\nI have given you pain, and I hope that you do not think very\nmeanly of me.\"\n\nLord St. Simon had by no means relaxed his rigid attitude, but\nhad listened with a frowning brow and a compressed lip to this\nlong narrative.\n\n\"Excuse me,\" he said, \"but it is not my custom to discuss my most\nintimate personal affairs in this public manner.\"\n\n\"Then you won't forgive me? You won't shake hands before I go?\"\n\n\"Oh, certainly, if it would give you any pleasure.\" He put out\nhis hand and coldly grasped that which she extended to him.\n\n\"I had hoped,\" suggested Holmes, \"that you would have joined us\nin a friendly supper.\"\n\n\"I think that there you ask a little too much,\" responded his\nLordship. \"I may be forced to acquiesce in these recent\ndevelopments, but I can hardly be expected to make merry over\nthem. I think that with your permission I will now wish you all a\nvery good-night.\" He included us all in a sweeping bow and\nstalked out of the room.\n\n\"Then I trust that you at least will honour me with your\ncompany,\" said Sherlock Holmes. \"It is always a joy to meet an\nAmerican, Mr. Moulton, for I am one of those who believe that the\nfolly of a monarch and the blundering of a minister in far-gone\nyears will not prevent our children from being some day citizens\nof the same world-wide country under a flag which shall be a\nquartering of the Union Jack with the Stars and Stripes.\"\n\n\"The case has been an interesting one,\" remarked Holmes when our\nvisitors had left us, \"because it serves to show very clearly how\nsimple the explanation may be of an affair which at first sight\nseems to be almost inexplicable. Nothing could be more natural\nthan the sequence of events as narrated by this lady, and nothing\nstranger than the result when viewed, for instance, by Mr.\nLestrade of Scotland Yard.\"\n\n\"You were not yourself at fault at all, then?\"\n\n\"From the first, two facts were very obvious to me, the one that\nthe lady had been quite willing to undergo the wedding ceremony,\nthe other that she had repented of it within a few minutes of\nreturning home. Obviously something had occurred during the\nmorning, then, to cause her to change her mind. What could that\nsomething be? She could not have spoken to anyone when she was\nout, for she had been in the company of the bridegroom. Had she\nseen someone, then? If she had, it must be someone from America\nbecause she had spent so short a time in this country that she\ncould hardly have allowed anyone to acquire so deep an influence\nover her that the mere sight of him would induce her to change\nher plans so completely. You see we have already arrived, by a\nprocess of exclusion, at the idea that she might have seen an\nAmerican. Then who could this American be, and why should he\npossess so much influence over her? It might be a lover; it might\nbe a husband. Her young womanhood had, I knew, been spent in\nrough scenes and under strange conditions. So far I had got\nbefore I ever heard Lord St. Simon's narrative. When he told us\nof a man in a pew, of the change in the bride's manner, of so\ntransparent a device for obtaining a note as the dropping of a\nbouquet, of her resort to her confidential maid, and of her very\nsignificant allusion to claim-jumping--which in miners' parlance\nmeans taking possession of that which another person has a prior\nclaim to--the whole situation became absolutely clear. She had\ngone off with a man, and the man was either a lover or was a\nprevious husband--the chances being in favour of the latter.\"\n\n\"And how in the world did you find them?\"\n\n\"It might have been difficult, but friend Lestrade held\ninformation in his hands the value of which he did not himself\nknow. The initials were, of course, of the highest importance,\nbut more valuable still was it to know that within a week he had\nsettled his bill at one of the most select London hotels.\"\n\n\"How did you deduce the select?\"\n\n\"By the select prices. Eight shillings for a bed and eightpence\nfor a glass of sherry pointed to one of the most expensive\nhotels. There are not many in London which charge at that rate.\nIn the second one which I visited in Northumberland Avenue, I\nlearned by an inspection of the book that Francis H. Moulton, an\nAmerican gentleman, had left only the day before, and on looking\nover the entries against him, I came upon the very items which I\nhad seen in the duplicate bill. His letters were to be forwarded\nto 226 Gordon Square; so thither I travelled, and being fortunate\nenough to find the loving couple at home, I ventured to give them\nsome paternal advice and to point out to them that it would be\nbetter in every way that they should make their position a little\nclearer both to the general public and to Lord St. Simon in\nparticular. I invited them to meet him here, and, as you see, I\nmade him keep the appointment.\"\n\n\"But with no very good result,\" I remarked. \"His conduct was\ncertainly not very gracious.\"\n\n\"Ah, Watson,\" said Holmes, smiling, \"perhaps you would not be\nvery gracious either, if, after all the trouble of wooing and\nwedding, you found yourself deprived in an instant of wife and of\nfortune. I think that we may judge Lord St. Simon very mercifully\nand thank our stars that we are never likely to find ourselves in\nthe same position. Draw your chair up and hand me my violin, for\nthe only problem we have still to solve is how to while away\nthese bleak autumnal evenings.\"\n\n\n\nXI. THE ADVENTURE OF THE BERYL CORONET\n\n\"Holmes,\" said I as I stood one morning in our bow-window looking\ndown the street, \"here is a madman coming along. It seems rather\nsad that his relatives should allow him to come out alone.\"\n\nMy friend rose lazily from his armchair and stood with his hands\nin the pockets of his dressing-gown, looking over my shoulder. It\nwas a bright, crisp February morning, and the snow of the day\nbefore still lay deep upon the ground, shimmering brightly in the\nwintry sun. Down the centre of Baker Street it had been ploughed\ninto a brown crumbly band by the traffic, but at either side and\non the heaped-up edges of the foot-paths it still lay as white as\nwhen it fell. The grey pavement had been cleaned and scraped, but\nwas still dangerously slippery, so that there were fewer\npassengers than usual. Indeed, from the direction of the\nMetropolitan Station no one was coming save the single gentleman\nwhose eccentric conduct had drawn my attention.\n\nHe was a man of about fifty, tall, portly, and imposing, with a\nmassive, strongly marked face and a commanding figure. He was\ndressed in a sombre yet rich style, in black frock-coat, shining\nhat, neat brown gaiters, and well-cut pearl-grey trousers. Yet\nhis actions were in absurd contrast to the dignity of his dress\nand features, for he was running hard, with occasional little\nsprings, such as a weary man gives who is little accustomed to\nset any tax upon his legs. As he ran he jerked his hands up and\ndown, waggled his head, and writhed his face into the most\nextraordinary contortions.\n\n\"What on earth can be the matter with him?\" I asked. \"He is\nlooking up at the numbers of the houses.\"\n\n\"I believe that he is coming here,\" said Holmes, rubbing his\nhands.\n\n\"Here?\"\n\n\"Yes; I rather think he is coming to consult me professionally. I\nthink that I recognise the symptoms. Ha! did I not tell you?\" As\nhe spoke, the man, puffing and blowing, rushed at our door and\npulled at our bell until the whole house resounded with the\nclanging.\n\nA few moments later he was in our room, still puffing, still\ngesticulating, but with so fixed a look of grief and despair in\nhis eyes that our smiles were turned in an instant to horror and\npity. For a while he could not get his words out, but swayed his\nbody and plucked at his hair like one who has been driven to the\nextreme limits of his reason. Then, suddenly springing to his\nfeet, he beat his head against the wall with such force that we\nboth rushed upon him and tore him away to the centre of the room.\nSherlock Holmes pushed him down into the easy-chair and, sitting\nbeside him, patted his hand and chatted with him in the easy,\nsoothing tones which he knew so well how to employ.\n\n\"You have come to me to tell your story, have you not?\" said he.\n\"You are fatigued with your haste. Pray wait until you have\nrecovered yourself, and then I shall be most happy to look into\nany little problem which you may submit to me.\"\n\nThe man sat for a minute or more with a heaving chest, fighting\nagainst his emotion. Then he passed his handkerchief over his\nbrow, set his lips tight, and turned his face towards us.\n\n\"No doubt you think me mad?\" said he.\n\n\"I see that you have had some great trouble,\" responded Holmes.\n\n\"God knows I have!--a trouble which is enough to unseat my\nreason, so sudden and so terrible is it. Public disgrace I might\nhave faced, although I am a man whose character has never yet\nborne a stain. Private affliction also is the lot of every man;\nbut the two coming together, and in so frightful a form, have\nbeen enough to shake my very soul. Besides, it is not I alone.\nThe very noblest in the land may suffer unless some way be found\nout of this horrible affair.\"\n\n\"Pray compose yourself, sir,\" said Holmes, \"and let me have a\nclear account of who you are and what it is that has befallen\nyou.\"\n\n\"My name,\" answered our visitor, \"is probably familiar to your\nears. I am Alexander Holder, of the banking firm of Holder &\nStevenson, of Threadneedle Street.\"\n\nThe name was indeed well known to us as belonging to the senior\npartner in the second largest private banking concern in the City\nof London. What could have happened, then, to bring one of the\nforemost citizens of London to this most pitiable pass? We\nwaited, all curiosity, until with another effort he braced\nhimself to tell his story.\n\n\"I feel that time is of value,\" said he; \"that is why I hastened\nhere when the police inspector suggested that I should secure\nyour co-operation. I came to Baker Street by the Underground and\nhurried from there on foot, for the cabs go slowly through this\nsnow. That is why I was so out of breath, for I am a man who\ntakes very little exercise. I feel better now, and I will put the\nfacts before you as shortly and yet as clearly as I can.\n\n\"It is, of course, well known to you that in a successful banking\nbusiness as much depends upon our being able to find remunerative\ninvestments for our funds as upon our increasing our connection\nand the number of our depositors. One of our most lucrative means\nof laying out money is in the shape of loans, where the security\nis unimpeachable. We have done a good deal in this direction\nduring the last few years, and there are many noble families to\nwhom we have advanced large sums upon the security of their\npictures, libraries, or plate.\n\n\"Yesterday morning I was seated in my office at the bank when a\ncard was brought in to me by one of the clerks. I started when I\nsaw the name, for it was that of none other than--well, perhaps\neven to you I had better say no more than that it was a name\nwhich is a household word all over the earth--one of the highest,\nnoblest, most exalted names in England. I was overwhelmed by the\nhonour and attempted, when he entered, to say so, but he plunged\nat once into business with the air of a man who wishes to hurry\nquickly through a disagreeable task.\n\n\"'Mr. Holder,' said he, 'I have been informed that you are in the\nhabit of advancing money.'\n\n\"'The firm does so when the security is good.' I answered.\n\n\"'It is absolutely essential to me,' said he, 'that I should have\n50,000 pounds at once. I could, of course, borrow so trifling a\nsum ten times over from my friends, but I much prefer to make it\na matter of business and to carry out that business myself. In my\nposition you can readily understand that it is unwise to place\none's self under obligations.'\n\n\"'For how long, may I ask, do you want this sum?' I asked.\n\n\"'Next Monday I have a large sum due to me, and I shall then most\ncertainly repay what you advance, with whatever interest you\nthink it right to charge. But it is very essential to me that the\nmoney should be paid at once.'\n\n\"'I should be happy to advance it without further parley from my\nown private purse,' said I, 'were it not that the strain would be\nrather more than it could bear. If, on the other hand, I am to do\nit in the name of the firm, then in justice to my partner I must\ninsist that, even in your case, every businesslike precaution\nshould be taken.'\n\n\"'I should much prefer to have it so,' said he, raising up a\nsquare, black morocco case which he had laid beside his chair.\n'You have doubtless heard of the Beryl Coronet?'\n\n\"'One of the most precious public possessions of the empire,'\nsaid I.\n\n\"'Precisely.' He opened the case, and there, imbedded in soft,\nflesh-coloured velvet, lay the magnificent piece of jewellery\nwhich he had named. 'There are thirty-nine enormous beryls,' said\nhe, 'and the price of the gold chasing is incalculable. The\nlowest estimate would put the worth of the coronet at double the\nsum which I have asked. I am prepared to leave it with you as my\nsecurity.'\n\n\"I took the precious case into my hands and looked in some\nperplexity from it to my illustrious client.\n\n\"'You doubt its value?' he asked.\n\n\"'Not at all. I only doubt--'\n\n\"'The propriety of my leaving it. You may set your mind at rest\nabout that. I should not dream of doing so were it not absolutely\ncertain that I should be able in four days to reclaim it. It is a\npure matter of form. Is the security sufficient?'\n\n\"'Ample.'\n\n\"'You understand, Mr. Holder, that I am giving you a strong proof\nof the confidence which I have in you, founded upon all that I\nhave heard of you. I rely upon you not only to be discreet and to\nrefrain from all gossip upon the matter but, above all, to\npreserve this coronet with every possible precaution because I\nneed not say that a great public scandal would be caused if any\nharm were to befall it. Any injury to it would be almost as\nserious as its complete loss, for there are no beryls in the\nworld to match these, and it would be impossible to replace them.\nI leave it with you, however, with every confidence, and I shall\ncall for it in person on Monday morning.'\n\n\"Seeing that my client was anxious to leave, I said no more but,\ncalling for my cashier, I ordered him to pay over fifty 1000\npound notes. When I was alone once more, however, with the\nprecious case lying upon the table in front of me, I could not\nbut think with some misgivings of the immense responsibility\nwhich it entailed upon me. There could be no doubt that, as it\nwas a national possession, a horrible scandal would ensue if any\nmisfortune should occur to it. I already regretted having ever\nconsented to take charge of it. However, it was too late to alter\nthe matter now, so I locked it up in my private safe and turned\nonce more to my work.\n\n\"When evening came I felt that it would be an imprudence to leave\nso precious a thing in the office behind me. Bankers' safes had\nbeen forced before now, and why should not mine be? If so, how\nterrible would be the position in which I should find myself! I\ndetermined, therefore, that for the next few days I would always\ncarry the case backward and forward with me, so that it might\nnever be really out of my reach. With this intention, I called a\ncab and drove out to my house at Streatham, carrying the jewel\nwith me. I did not breathe freely until I had taken it upstairs\nand locked it in the bureau of my dressing-room.\n\n\"And now a word as to my household, Mr. Holmes, for I wish you to\nthoroughly understand the situation. My groom and my page sleep\nout of the house, and may be set aside altogether. I have three\nmaid-servants who have been with me a number of years and whose\nabsolute reliability is quite above suspicion. Another, Lucy\nParr, the second waiting-maid, has only been in my service a few\nmonths. She came with an excellent character, however, and has\nalways given me satisfaction. She is a very pretty girl and has\nattracted admirers who have occasionally hung about the place.\nThat is the only drawback which we have found to her, but we\nbelieve her to be a thoroughly good girl in every way.\n\n\"So much for the servants. My family itself is so small that it\nwill not take me long to describe it. I am a widower and have an\nonly son, Arthur. He has been a disappointment to me, Mr.\nHolmes--a grievous disappointment. I have no doubt that I am\nmyself to blame. People tell me that I have spoiled him. Very\nlikely I have. When my dear wife died I felt that he was all I\nhad to love. I could not bear to see the smile fade even for a\nmoment from his face. I have never denied him a wish. Perhaps it\nwould have been better for both of us had I been sterner, but I\nmeant it for the best.\n\n\"It was naturally my intention that he should succeed me in my\nbusiness, but he was not of a business turn. He was wild,\nwayward, and, to speak the truth, I could not trust him in the\nhandling of large sums of money. When he was young he became a\nmember of an aristocratic club, and there, having charming\nmanners, he was soon the intimate of a number of men with long\npurses and expensive habits. He learned to play heavily at cards\nand to squander money on the turf, until he had again and again\nto come to me and implore me to give him an advance upon his\nallowance, that he might settle his debts of honour. He tried\nmore than once to break away from the dangerous company which he\nwas keeping, but each time the influence of his friend, Sir\nGeorge Burnwell, was enough to draw him back again.\n\n\"And, indeed, I could not wonder that such a man as Sir George\nBurnwell should gain an influence over him, for he has frequently\nbrought him to my house, and I have found myself that I could\nhardly resist the fascination of his manner. He is older than\nArthur, a man of the world to his finger-tips, one who had been\neverywhere, seen everything, a brilliant talker, and a man of\ngreat personal beauty. Yet when I think of him in cold blood, far\naway from the glamour of his presence, I am convinced from his\ncynical speech and the look which I have caught in his eyes that\nhe is one who should be deeply distrusted. So I think, and so,\ntoo, thinks my little Mary, who has a woman's quick insight into\ncharacter.\n\n\"And now there is only she to be described. She is my niece; but\nwhen my brother died five years ago and left her alone in the\nworld I adopted her, and have looked upon her ever since as my\ndaughter. She is a sunbeam in my house--sweet, loving, beautiful,\na wonderful manager and housekeeper, yet as tender and quiet and\ngentle as a woman could be. She is my right hand. I do not know\nwhat I could do without her. In only one matter has she ever gone\nagainst my wishes. Twice my boy has asked her to marry him, for\nhe loves her devotedly, but each time she has refused him. I\nthink that if anyone could have drawn him into the right path it\nwould have been she, and that his marriage might have changed his\nwhole life; but now, alas! it is too late--forever too late!\n\n\"Now, Mr. Holmes, you know the people who live under my roof, and\nI shall continue with my miserable story.\n\n\"When we were taking coffee in the drawing-room that night after\ndinner, I told Arthur and Mary my experience, and of the precious\ntreasure which we had under our roof, suppressing only the name\nof my client. Lucy Parr, who had brought in the coffee, had, I am\nsure, left the room; but I cannot swear that the door was closed.\nMary and Arthur were much interested and wished to see the famous\ncoronet, but I thought it better not to disturb it.\n\n\"'Where have you put it?' asked Arthur.\n\n\"'In my own bureau.'\n\n\"'Well, I hope to goodness the house won't be burgled during the\nnight.' said he.\n\n\"'It is locked up,' I answered.\n\n\"'Oh, any old key will fit that bureau. When I was a youngster I\nhave opened it myself with the key of the box-room cupboard.'\n\n\"He often had a wild way of talking, so that I thought little of\nwhat he said. He followed me to my room, however, that night with\na very grave face.\n\n\"'Look here, dad,' said he with his eyes cast down, 'can you let\nme have 200 pounds?'\n\n\"'No, I cannot!' I answered sharply. 'I have been far too\ngenerous with you in money matters.'\n\n\"'You have been very kind,' said he, 'but I must have this money,\nor else I can never show my face inside the club again.'\n\n\"'And a very good thing, too!' I cried.\n\n\"'Yes, but you would not have me leave it a dishonoured man,'\nsaid he. 'I could not bear the disgrace. I must raise the money\nin some way, and if you will not let me have it, then I must try\nother means.'\n\n\"I was very angry, for this was the third demand during the\nmonth. 'You shall not have a farthing from me,' I cried, on which\nhe bowed and left the room without another word.\n\n\"When he was gone I unlocked my bureau, made sure that my\ntreasure was safe, and locked it again. Then I started to go\nround the house to see that all was secure--a duty which I\nusually leave to Mary but which I thought it well to perform\nmyself that night. As I came down the stairs I saw Mary herself\nat the side window of the hall, which she closed and fastened as\nI approached.\n\n\"'Tell me, dad,' said she, looking, I thought, a little\ndisturbed, 'did you give Lucy, the maid, leave to go out\nto-night?'\n\n\"'Certainly not.'\n\n\"'She came in just now by the back door. I have no doubt that she\nhas only been to the side gate to see someone, but I think that\nit is hardly safe and should be stopped.'\n\n\"'You must speak to her in the morning, or I will if you prefer\nit. Are you sure that everything is fastened?'\n\n\"'Quite sure, dad.'\n\n\"'Then, good-night.' I kissed her and went up to my bedroom\nagain, where I was soon asleep.\n\n\"I am endeavouring to tell you everything, Mr. Holmes, which may\nhave any bearing upon the case, but I beg that you will question\nme upon any point which I do not make clear.\"\n\n\"On the contrary, your statement is singularly lucid.\"\n\n\"I come to a part of my story now in which I should wish to be\nparticularly so. I am not a very heavy sleeper, and the anxiety\nin my mind tended, no doubt, to make me even less so than usual.\nAbout two in the morning, then, I was awakened by some sound in\nthe house. It had ceased ere I was wide awake, but it had left an\nimpression behind it as though a window had gently closed\nsomewhere. I lay listening with all my ears. Suddenly, to my\nhorror, there was a distinct sound of footsteps moving softly in\nthe next room. I slipped out of bed, all palpitating with fear,\nand peeped round the corner of my dressing-room door.\n\n\"'Arthur!' I screamed, 'you villain! you thief! How dare you\ntouch that coronet?'\n\n\"The gas was half up, as I had left it, and my unhappy boy,\ndressed only in his shirt and trousers, was standing beside the\nlight, holding the coronet in his hands. He appeared to be\nwrenching at it, or bending it with all his strength. At my cry\nhe dropped it from his grasp and turned as pale as death. I\nsnatched it up and examined it. One of the gold corners, with\nthree of the beryls in it, was missing.\n\n\"'You blackguard!' I shouted, beside myself with rage. 'You have\ndestroyed it! You have dishonoured me forever! Where are the\njewels which you have stolen?'\n\n\"'Stolen!' he cried.\n\n\"'Yes, thief!' I roared, shaking him by the shoulder.\n\n\"'There are none missing. There cannot be any missing,' said he.\n\n\"'There are three missing. And you know where they are. Must I\ncall you a liar as well as a thief? Did I not see you trying to\ntear off another piece?'\n\n\"'You have called me names enough,' said he, 'I will not stand it\nany longer. I shall not say another word about this business,\nsince you have chosen to insult me. I will leave your house in\nthe morning and make my own way in the world.'\n\n\"'You shall leave it in the hands of the police!' I cried\nhalf-mad with grief and rage. 'I shall have this matter probed to\nthe bottom.'\n\n\"'You shall learn nothing from me,' said he with a passion such\nas I should not have thought was in his nature. 'If you choose to\ncall the police, let the police find what they can.'\n\n\"By this time the whole house was astir, for I had raised my\nvoice in my anger. Mary was the first to rush into my room, and,\nat the sight of the coronet and of Arthur's face, she read the\nwhole story and, with a scream, fell down senseless on the\nground. I sent the house-maid for the police and put the\ninvestigation into their hands at once. When the inspector and a\nconstable entered the house, Arthur, who had stood sullenly with\nhis arms folded, asked me whether it was my intention to charge\nhim with theft. I answered that it had ceased to be a private\nmatter, but had become a public one, since the ruined coronet was\nnational property. I was determined that the law should have its\nway in everything.\n\n\"'At least,' said he, 'you will not have me arrested at once. It\nwould be to your advantage as well as mine if I might leave the\nhouse for five minutes.'\n\n\"'That you may get away, or perhaps that you may conceal what you\nhave stolen,' said I. And then, realising the dreadful position\nin which I was placed, I implored him to remember that not only\nmy honour but that of one who was far greater than I was at\nstake; and that he threatened to raise a scandal which would\nconvulse the nation. He might avert it all if he would but tell\nme what he had done with the three missing stones.\n\n\"'You may as well face the matter,' said I; 'you have been caught\nin the act, and no confession could make your guilt more heinous.\nIf you but make such reparation as is in your power, by telling\nus where the beryls are, all shall be forgiven and forgotten.'\n\n\"'Keep your forgiveness for those who ask for it,' he answered,\nturning away from me with a sneer. I saw that he was too hardened\nfor any words of mine to influence him. There was but one way for\nit. I called in the inspector and gave him into custody. A search\nwas made at once not only of his person but of his room and of\nevery portion of the house where he could possibly have concealed\nthe gems; but no trace of them could be found, nor would the\nwretched boy open his mouth for all our persuasions and our\nthreats. This morning he was removed to a cell, and I, after\ngoing through all the police formalities, have hurried round to\nyou to implore you to use your skill in unravelling the matter.\nThe police have openly confessed that they can at present make\nnothing of it. You may go to any expense which you think\nnecessary. I have already offered a reward of 1000 pounds. My\nGod, what shall I do! I have lost my honour, my gems, and my son\nin one night. Oh, what shall I do!\"\n\nHe put a hand on either side of his head and rocked himself to\nand fro, droning to himself like a child whose grief has got\nbeyond words.\n\nSherlock Holmes sat silent for some few minutes, with his brows\nknitted and his eyes fixed upon the fire.\n\n\"Do you receive much company?\" he asked.\n\n\"None save my partner with his family and an occasional friend of\nArthur's. Sir George Burnwell has been several times lately. No\none else, I think.\"\n\n\"Do you go out much in society?\"\n\n\"Arthur does. Mary and I stay at home. We neither of us care for\nit.\"\n\n\"That is unusual in a young girl.\"\n\n\"She is of a quiet nature. Besides, she is not so very young. She\nis four-and-twenty.\"\n\n\"This matter, from what you say, seems to have been a shock to\nher also.\"\n\n\"Terrible! She is even more affected than I.\"\n\n\"You have neither of you any doubt as to your son's guilt?\"\n\n\"How can we have when I saw him with my own eyes with the coronet\nin his hands.\"\n\n\"I hardly consider that a conclusive proof. Was the remainder of\nthe coronet at all injured?\"\n\n\"Yes, it was twisted.\"\n\n\"Do you not think, then, that he might have been trying to\nstraighten it?\"\n\n\"God bless you! You are doing what you can for him and for me.\nBut it is too heavy a task. What was he doing there at all? If\nhis purpose were innocent, why did he not say so?\"\n\n\"Precisely. And if it were guilty, why did he not invent a lie?\nHis silence appears to me to cut both ways. There are several\nsingular points about the case. What did the police think of the\nnoise which awoke you from your sleep?\"\n\n\"They considered that it might be caused by Arthur's closing his\nbedroom door.\"\n\n\"A likely story! As if a man bent on felony would slam his door\nso as to wake a household. What did they say, then, of the\ndisappearance of these gems?\"\n\n\"They are still sounding the planking and probing the furniture\nin the hope of finding them.\"\n\n\"Have they thought of looking outside the house?\"\n\n\"Yes, they have shown extraordinary energy. The whole garden has\nalready been minutely examined.\"\n\n\"Now, my dear sir,\" said Holmes, \"is it not obvious to you now\nthat this matter really strikes very much deeper than either you\nor the police were at first inclined to think? It appeared to you\nto be a simple case; to me it seems exceedingly complex. Consider\nwhat is involved by your theory. You suppose that your son came\ndown from his bed, went, at great risk, to your dressing-room,\nopened your bureau, took out your coronet, broke off by main\nforce a small portion of it, went off to some other place,\nconcealed three gems out of the thirty-nine, with such skill that\nnobody can find them, and then returned with the other thirty-six\ninto the room in which he exposed himself to the greatest danger\nof being discovered. I ask you now, is such a theory tenable?\"\n\n\"But what other is there?\" cried the banker with a gesture of\ndespair. \"If his motives were innocent, why does he not explain\nthem?\"\n\n\"It is our task to find that out,\" replied Holmes; \"so now, if\nyou please, Mr. Holder, we will set off for Streatham together,\nand devote an hour to glancing a little more closely into\ndetails.\"\n\nMy friend insisted upon my accompanying them in their expedition,\nwhich I was eager enough to do, for my curiosity and sympathy\nwere deeply stirred by the story to which we had listened. I\nconfess that the guilt of the banker's son appeared to me to be\nas obvious as it did to his unhappy father, but still I had such\nfaith in Holmes' judgment that I felt that there must be some\ngrounds for hope as long as he was dissatisfied with the accepted\nexplanation. He hardly spoke a word the whole way out to the\nsouthern suburb, but sat with his chin upon his breast and his\nhat drawn over his eyes, sunk in the deepest thought. Our client\nappeared to have taken fresh heart at the little glimpse of hope\nwhich had been presented to him, and he even broke into a\ndesultory chat with me over his business affairs. A short railway\njourney and a shorter walk brought us to Fairbank, the modest\nresidence of the great financier.\n\nFairbank was a good-sized square house of white stone, standing\nback a little from the road. A double carriage-sweep, with a\nsnow-clad lawn, stretched down in front to two large iron gates\nwhich closed the entrance. On the right side was a small wooden\nthicket, which led into a narrow path between two neat hedges\nstretching from the road to the kitchen door, and forming the\ntradesmen's entrance. On the left ran a lane which led to the\nstables, and was not itself within the grounds at all, being a\npublic, though little used, thoroughfare. Holmes left us standing\nat the door and walked slowly all round the house, across the\nfront, down the tradesmen's path, and so round by the garden\nbehind into the stable lane. So long was he that Mr. Holder and I\nwent into the dining-room and waited by the fire until he should\nreturn. We were sitting there in silence when the door opened and\na young lady came in. She was rather above the middle height,\nslim, with dark hair and eyes, which seemed the darker against\nthe absolute pallor of her skin. I do not think that I have ever\nseen such deadly paleness in a woman's face. Her lips, too, were\nbloodless, but her eyes were flushed with crying. As she swept\nsilently into the room she impressed me with a greater sense of\ngrief than the banker had done in the morning, and it was the\nmore striking in her as she was evidently a woman of strong\ncharacter, with immense capacity for self-restraint. Disregarding\nmy presence, she went straight to her uncle and passed her hand\nover his head with a sweet womanly caress.\n\n\"You have given orders that Arthur should be liberated, have you\nnot, dad?\" she asked.\n\n\"No, no, my girl, the matter must be probed to the bottom.\"\n\n\"But I am so sure that he is innocent. You know what woman's\ninstincts are. I know that he has done no harm and that you will\nbe sorry for having acted so harshly.\"\n\n\"Why is he silent, then, if he is innocent?\"\n\n\"Who knows? Perhaps because he was so angry that you should\nsuspect him.\"\n\n\"How could I help suspecting him, when I actually saw him with\nthe coronet in his hand?\"\n\n\"Oh, but he had only picked it up to look at it. Oh, do, do take\nmy word for it that he is innocent. Let the matter drop and say\nno more. It is so dreadful to think of our dear Arthur in\nprison!\"\n\n\"I shall never let it drop until the gems are found--never, Mary!\nYour affection for Arthur blinds you as to the awful consequences\nto me. Far from hushing the thing up, I have brought a gentleman\ndown from London to inquire more deeply into it.\"\n\n\"This gentleman?\" she asked, facing round to me.\n\n\"No, his friend. He wished us to leave him alone. He is round in\nthe stable lane now.\"\n\n\"The stable lane?\" She raised her dark eyebrows. \"What can he\nhope to find there? Ah! this, I suppose, is he. I trust, sir,\nthat you will succeed in proving, what I feel sure is the truth,\nthat my cousin Arthur is innocent of this crime.\"\n\n\"I fully share your opinion, and I trust, with you, that we may\nprove it,\" returned Holmes, going back to the mat to knock the\nsnow from his shoes. \"I believe I have the honour of addressing\nMiss Mary Holder. Might I ask you a question or two?\"\n\n\"Pray do, sir, if it may help to clear this horrible affair up.\"\n\n\"You heard nothing yourself last night?\"\n\n\"Nothing, until my uncle here began to speak loudly. I heard\nthat, and I came down.\"\n\n\"You shut up the windows and doors the night before. Did you\nfasten all the windows?\"\n\n\"Yes.\"\n\n\"Were they all fastened this morning?\"\n\n\"Yes.\"\n\n\"You have a maid who has a sweetheart? I think that you remarked\nto your uncle last night that she had been out to see him?\"\n\n\"Yes, and she was the girl who waited in the drawing-room, and\nwho may have heard uncle's remarks about the coronet.\"\n\n\"I see. You infer that she may have gone out to tell her\nsweetheart, and that the two may have planned the robbery.\"\n\n\"But what is the good of all these vague theories,\" cried the\nbanker impatiently, \"when I have told you that I saw Arthur with\nthe coronet in his hands?\"\n\n\"Wait a little, Mr. Holder. We must come back to that. About this\ngirl, Miss Holder. You saw her return by the kitchen door, I\npresume?\"\n\n\"Yes; when I went to see if the door was fastened for the night I\nmet her slipping in. I saw the man, too, in the gloom.\"\n\n\"Do you know him?\"\n\n\"Oh, yes! he is the green-grocer who brings our vegetables round.\nHis name is Francis Prosper.\"\n\n\"He stood,\" said Holmes, \"to the left of the door--that is to\nsay, farther up the path than is necessary to reach the door?\"\n\n\"Yes, he did.\"\n\n\"And he is a man with a wooden leg?\"\n\nSomething like fear sprang up in the young lady's expressive\nblack eyes. \"Why, you are like a magician,\" said she. \"How do you\nknow that?\" She smiled, but there was no answering smile in\nHolmes' thin, eager face.\n\n\"I should be very glad now to go upstairs,\" said he. \"I shall\nprobably wish to go over the outside of the house again. Perhaps\nI had better take a look at the lower windows before I go up.\"\n\nHe walked swiftly round from one to the other, pausing only at\nthe large one which looked from the hall onto the stable lane.\nThis he opened and made a very careful examination of the sill\nwith his powerful magnifying lens. \"Now we shall go upstairs,\"\nsaid he at last.\n\nThe banker's dressing-room was a plainly furnished little\nchamber, with a grey carpet, a large bureau, and a long mirror.\nHolmes went to the bureau first and looked hard at the lock.\n\n\"Which key was used to open it?\" he asked.\n\n\"That which my son himself indicated--that of the cupboard of the\nlumber-room.\"\n\n\"Have you it here?\"\n\n\"That is it on the dressing-table.\"\n\nSherlock Holmes took it up and opened the bureau.\n\n\"It is a noiseless lock,\" said he. \"It is no wonder that it did\nnot wake you. This case, I presume, contains the coronet. We must\nhave a look at it.\" He opened the case, and taking out the diadem\nhe laid it upon the table. It was a magnificent specimen of the\njeweller's art, and the thirty-six stones were the finest that I\nhave ever seen. At one side of the coronet was a cracked edge,\nwhere a corner holding three gems had been torn away.\n\n\"Now, Mr. Holder,\" said Holmes, \"here is the corner which\ncorresponds to that which has been so unfortunately lost. Might I\nbeg that you will break it off.\"\n\nThe banker recoiled in horror. \"I should not dream of trying,\"\nsaid he.\n\n\"Then I will.\" Holmes suddenly bent his strength upon it, but\nwithout result. \"I feel it give a little,\" said he; \"but, though\nI am exceptionally strong in the fingers, it would take me all my\ntime to break it. An ordinary man could not do it. Now, what do\nyou think would happen if I did break it, Mr. Holder? There would\nbe a noise like a pistol shot. Do you tell me that all this\nhappened within a few yards of your bed and that you heard\nnothing of it?\"\n\n\"I do not know what to think. It is all dark to me.\"\n\n\"But perhaps it may grow lighter as we go. What do you think,\nMiss Holder?\"\n\n\"I confess that I still share my uncle's perplexity.\"\n\n\"Your son had no shoes or slippers on when you saw him?\"\n\n\"He had nothing on save only his trousers and shirt.\"\n\n\"Thank you. We have certainly been favoured with extraordinary\nluck during this inquiry, and it will be entirely our own fault\nif we do not succeed in clearing the matter up. With your\npermission, Mr. Holder, I shall now continue my investigations\noutside.\"\n\nHe went alone, at his own request, for he explained that any\nunnecessary footmarks might make his task more difficult. For an\nhour or more he was at work, returning at last with his feet\nheavy with snow and his features as inscrutable as ever.\n\n\"I think that I have seen now all that there is to see, Mr.\nHolder,\" said he; \"I can serve you best by returning to my\nrooms.\"\n\n\"But the gems, Mr. Holmes. Where are they?\"\n\n\"I cannot tell.\"\n\nThe banker wrung his hands. \"I shall never see them again!\" he\ncried. \"And my son? You give me hopes?\"\n\n\"My opinion is in no way altered.\"\n\n\"Then, for God's sake, what was this dark business which was\nacted in my house last night?\"\n\n\"If you can call upon me at my Baker Street rooms to-morrow\nmorning between nine and ten I shall be happy to do what I can to\nmake it clearer. I understand that you give me carte blanche to\nact for you, provided only that I get back the gems, and that you\nplace no limit on the sum I may draw.\"\n\n\"I would give my fortune to have them back.\"\n\n\"Very good. I shall look into the matter between this and then.\nGood-bye; it is just possible that I may have to come over here\nagain before evening.\"\n\nIt was obvious to me that my companion's mind was now made up\nabout the case, although what his conclusions were was more than\nI could even dimly imagine. Several times during our homeward\njourney I endeavoured to sound him upon the point, but he always\nglided away to some other topic, until at last I gave it over in\ndespair. It was not yet three when we found ourselves in our\nrooms once more. He hurried to his chamber and was down again in\na few minutes dressed as a common loafer. With his collar turned\nup, his shiny, seedy coat, his red cravat, and his worn boots, he\nwas a perfect sample of the class.\n\n\"I think that this should do,\" said he, glancing into the glass\nabove the fireplace. \"I only wish that you could come with me,\nWatson, but I fear that it won't do. I may be on the trail in\nthis matter, or I may be following a will-o'-the-wisp, but I\nshall soon know which it is. I hope that I may be back in a few\nhours.\" He cut a slice of beef from the joint upon the sideboard,\nsandwiched it between two rounds of bread, and thrusting this\nrude meal into his pocket he started off upon his expedition.\n\nI had just finished my tea when he returned, evidently in\nexcellent spirits, swinging an old elastic-sided boot in his\nhand. He chucked it down into a corner and helped himself to a\ncup of tea.\n\n\"I only looked in as I passed,\" said he. \"I am going right on.\"\n\n\"Where to?\"\n\n\"Oh, to the other side of the West End. It may be some time\nbefore I get back. Don't wait up for me in case I should be\nlate.\"\n\n\"How are you getting on?\"\n\n\"Oh, so so. Nothing to complain of. I have been out to Streatham\nsince I saw you last, but I did not call at the house. It is a\nvery sweet little problem, and I would not have missed it for a\ngood deal. However, I must not sit gossiping here, but must get\nthese disreputable clothes off and return to my highly\nrespectable self.\"\n\nI could see by his manner that he had stronger reasons for\nsatisfaction than his words alone would imply. His eyes twinkled,\nand there was even a touch of colour upon his sallow cheeks. He\nhastened upstairs, and a few minutes later I heard the slam of\nthe hall door, which told me that he was off once more upon his\ncongenial hunt.\n\nI waited until midnight, but there was no sign of his return, so\nI retired to my room. It was no uncommon thing for him to be away\nfor days and nights on end when he was hot upon a scent, so that\nhis lateness caused me no surprise. I do not know at what hour he\ncame in, but when I came down to breakfast in the morning there\nhe was with a cup of coffee in one hand and the paper in the\nother, as fresh and trim as possible.\n\n\"You will excuse my beginning without you, Watson,\" said he, \"but\nyou remember that our client has rather an early appointment this\nmorning.\"\n\n\"Why, it is after nine now,\" I answered. \"I should not be\nsurprised if that were he. I thought I heard a ring.\"\n\nIt was, indeed, our friend the financier. I was shocked by the\nchange which had come over him, for his face which was naturally\nof a broad and massive mould, was now pinched and fallen in,\nwhile his hair seemed to me at least a shade whiter. He entered\nwith a weariness and lethargy which was even more painful than\nhis violence of the morning before, and he dropped heavily into\nthe armchair which I pushed forward for him.\n\n\"I do not know what I have done to be so severely tried,\" said\nhe. \"Only two days ago I was a happy and prosperous man, without\na care in the world. Now I am left to a lonely and dishonoured\nage. One sorrow comes close upon the heels of another. My niece,\nMary, has deserted me.\"\n\n\"Deserted you?\"\n\n\"Yes. Her bed this morning had not been slept in, her room was\nempty, and a note for me lay upon the hall table. I had said to\nher last night, in sorrow and not in anger, that if she had\nmarried my boy all might have been well with him. Perhaps it was\nthoughtless of me to say so. It is to that remark that she refers\nin this note:\n\n\"'MY DEAREST UNCLE:--I feel that I have brought trouble upon you,\nand that if I had acted differently this terrible misfortune\nmight never have occurred. I cannot, with this thought in my\nmind, ever again be happy under your roof, and I feel that I must\nleave you forever. Do not worry about my future, for that is\nprovided for; and, above all, do not search for me, for it will\nbe fruitless labour and an ill-service to me. In life or in\ndeath, I am ever your loving,--MARY.'\n\n\"What could she mean by that note, Mr. Holmes? Do you think it\npoints to suicide?\"\n\n\"No, no, nothing of the kind. It is perhaps the best possible\nsolution. I trust, Mr. Holder, that you are nearing the end of\nyour troubles.\"\n\n\"Ha! You say so! You have heard something, Mr. Holmes; you have\nlearned something! Where are the gems?\"\n\n\"You would not think 1000 pounds apiece an excessive sum for\nthem?\"\n\n\"I would pay ten.\"\n\n\"That would be unnecessary. Three thousand will cover the matter.\nAnd there is a little reward, I fancy. Have you your check-book?\nHere is a pen. Better make it out for 4000 pounds.\"\n\nWith a dazed face the banker made out the required check. Holmes\nwalked over to his desk, took out a little triangular piece of\ngold with three gems in it, and threw it down upon the table.\n\nWith a shriek of joy our client clutched it up.\n\n\"You have it!\" he gasped. \"I am saved! I am saved!\"\n\nThe reaction of joy was as passionate as his grief had been, and\nhe hugged his recovered gems to his bosom.\n\n\"There is one other thing you owe, Mr. Holder,\" said Sherlock\nHolmes rather sternly.\n\n\"Owe!\" He caught up a pen. \"Name the sum, and I will pay it.\"\n\n\"No, the debt is not to me. You owe a very humble apology to that\nnoble lad, your son, who has carried himself in this matter as I\nshould be proud to see my own son do, should I ever chance to\nhave one.\"\n\n\"Then it was not Arthur who took them?\"\n\n\"I told you yesterday, and I repeat to-day, that it was not.\"\n\n\"You are sure of it! Then let us hurry to him at once to let him\nknow that the truth is known.\"\n\n\"He knows it already. When I had cleared it all up I had an\ninterview with him, and finding that he would not tell me the\nstory, I told it to him, on which he had to confess that I was\nright and to add the very few details which were not yet quite\nclear to me. Your news of this morning, however, may open his\nlips.\"\n\n\"For heaven's sake, tell me, then, what is this extraordinary\nmystery!\"\n\n\"I will do so, and I will show you the steps by which I reached\nit. And let me say to you, first, that which it is hardest for me\nto say and for you to hear: there has been an understanding\nbetween Sir George Burnwell and your niece Mary. They have now\nfled together.\"\n\n\"My Mary? Impossible!\"\n\n\"It is unfortunately more than possible; it is certain. Neither\nyou nor your son knew the true character of this man when you\nadmitted him into your family circle. He is one of the most\ndangerous men in England--a ruined gambler, an absolutely\ndesperate villain, a man without heart or conscience. Your niece\nknew nothing of such men. When he breathed his vows to her, as he\nhad done to a hundred before her, she flattered herself that she\nalone had touched his heart. The devil knows best what he said,\nbut at least she became his tool and was in the habit of seeing\nhim nearly every evening.\"\n\n\"I cannot, and I will not, believe it!\" cried the banker with an\nashen face.\n\n\"I will tell you, then, what occurred in your house last night.\nYour niece, when you had, as she thought, gone to your room,\nslipped down and talked to her lover through the window which\nleads into the stable lane. His footmarks had pressed right\nthrough the snow, so long had he stood there. She told him of the\ncoronet. His wicked lust for gold kindled at the news, and he\nbent her to his will. I have no doubt that she loved you, but\nthere are women in whom the love of a lover extinguishes all\nother loves, and I think that she must have been one. She had\nhardly listened to his instructions when she saw you coming\ndownstairs, on which she closed the window rapidly and told you\nabout one of the servants' escapade with her wooden-legged lover,\nwhich was all perfectly true.\n\n\"Your boy, Arthur, went to bed after his interview with you but\nhe slept badly on account of his uneasiness about his club debts.\nIn the middle of the night he heard a soft tread pass his door,\nso he rose and, looking out, was surprised to see his cousin\nwalking very stealthily along the passage until she disappeared\ninto your dressing-room. Petrified with astonishment, the lad\nslipped on some clothes and waited there in the dark to see what\nwould come of this strange affair. Presently she emerged from the\nroom again, and in the light of the passage-lamp your son saw\nthat she carried the precious coronet in her hands. She passed\ndown the stairs, and he, thrilling with horror, ran along and\nslipped behind the curtain near your door, whence he could see\nwhat passed in the hall beneath. He saw her stealthily open the\nwindow, hand out the coronet to someone in the gloom, and then\nclosing it once more hurry back to her room, passing quite close\nto where he stood hid behind the curtain.\n\n\"As long as she was on the scene he could not take any action\nwithout a horrible exposure of the woman whom he loved. But the\ninstant that she was gone he realised how crushing a misfortune\nthis would be for you, and how all-important it was to set it\nright. He rushed down, just as he was, in his bare feet, opened\nthe window, sprang out into the snow, and ran down the lane,\nwhere he could see a dark figure in the moonlight. Sir George\nBurnwell tried to get away, but Arthur caught him, and there was\na struggle between them, your lad tugging at one side of the\ncoronet, and his opponent at the other. In the scuffle, your son\nstruck Sir George and cut him over the eye. Then something\nsuddenly snapped, and your son, finding that he had the coronet\nin his hands, rushed back, closed the window, ascended to your\nroom, and had just observed that the coronet had been twisted in\nthe struggle and was endeavouring to straighten it when you\nappeared upon the scene.\"\n\n\"Is it possible?\" gasped the banker.\n\n\"You then roused his anger by calling him names at a moment when\nhe felt that he had deserved your warmest thanks. He could not\nexplain the true state of affairs without betraying one who\ncertainly deserved little enough consideration at his hands. He\ntook the more chivalrous view, however, and preserved her\nsecret.\"\n\n\"And that was why she shrieked and fainted when she saw the\ncoronet,\" cried Mr. Holder. \"Oh, my God! what a blind fool I have\nbeen! And his asking to be allowed to go out for five minutes!\nThe dear fellow wanted to see if the missing piece were at the\nscene of the struggle. How cruelly I have misjudged him!\"\n\n\"When I arrived at the house,\" continued Holmes, \"I at once went\nvery carefully round it to observe if there were any traces in\nthe snow which might help me. I knew that none had fallen since\nthe evening before, and also that there had been a strong frost\nto preserve impressions. I passed along the tradesmen's path, but\nfound it all trampled down and indistinguishable. Just beyond it,\nhowever, at the far side of the kitchen door, a woman had stood\nand talked with a man, whose round impressions on one side showed\nthat he had a wooden leg. I could even tell that they had been\ndisturbed, for the woman had run back swiftly to the door, as was\nshown by the deep toe and light heel marks, while Wooden-leg had\nwaited a little, and then had gone away. I thought at the time\nthat this might be the maid and her sweetheart, of whom you had\nalready spoken to me, and inquiry showed it was so. I passed\nround the garden without seeing anything more than random tracks,\nwhich I took to be the police; but when I got into the stable\nlane a very long and complex story was written in the snow in\nfront of me.\n\n\"There was a double line of tracks of a booted man, and a second\ndouble line which I saw with delight belonged to a man with naked\nfeet. I was at once convinced from what you had told me that the\nlatter was your son. The first had walked both ways, but the\nother had run swiftly, and as his tread was marked in places over\nthe depression of the boot, it was obvious that he had passed\nafter the other. I followed them up and found they led to the\nhall window, where Boots had worn all the snow away while\nwaiting. Then I walked to the other end, which was a hundred\nyards or more down the lane. I saw where Boots had faced round,\nwhere the snow was cut up as though there had been a struggle,\nand, finally, where a few drops of blood had fallen, to show me\nthat I was not mistaken. Boots had then run down the lane, and\nanother little smudge of blood showed that it was he who had been\nhurt. When he came to the highroad at the other end, I found that\nthe pavement had been cleared, so there was an end to that clue.\n\n\"On entering the house, however, I examined, as you remember, the\nsill and framework of the hall window with my lens, and I could\nat once see that someone had passed out. I could distinguish the\noutline of an instep where the wet foot had been placed in coming\nin. I was then beginning to be able to form an opinion as to what\nhad occurred. A man had waited outside the window; someone had\nbrought the gems; the deed had been overseen by your son; he had\npursued the thief; had struggled with him; they had each tugged\nat the coronet, their united strength causing injuries which\nneither alone could have effected. He had returned with the\nprize, but had left a fragment in the grasp of his opponent. So\nfar I was clear. The question now was, who was the man and who\nwas it brought him the coronet?\n\n\"It is an old maxim of mine that when you have excluded the\nimpossible, whatever remains, however improbable, must be the\ntruth. Now, I knew that it was not you who had brought it down,\nso there only remained your niece and the maids. But if it were\nthe maids, why should your son allow himself to be accused in\ntheir place? There could be no possible reason. As he loved his\ncousin, however, there was an excellent explanation why he should\nretain her secret--the more so as the secret was a disgraceful\none. When I remembered that you had seen her at that window, and\nhow she had fainted on seeing the coronet again, my conjecture\nbecame a certainty.\n\n\"And who could it be who was her confederate? A lover evidently,\nfor who else could outweigh the love and gratitude which she must\nfeel to you? I knew that you went out little, and that your\ncircle of friends was a very limited one. But among them was Sir\nGeorge Burnwell. I had heard of him before as being a man of evil\nreputation among women. It must have been he who wore those boots\nand retained the missing gems. Even though he knew that Arthur\nhad discovered him, he might still flatter himself that he was\nsafe, for the lad could not say a word without compromising his\nown family.\n\n\"Well, your own good sense will suggest what measures I took\nnext. I went in the shape of a loafer to Sir George's house,\nmanaged to pick up an acquaintance with his valet, learned that\nhis master had cut his head the night before, and, finally, at\nthe expense of six shillings, made all sure by buying a pair of\nhis cast-off shoes. With these I journeyed down to Streatham and\nsaw that they exactly fitted the tracks.\"\n\n\"I saw an ill-dressed vagabond in the lane yesterday evening,\"\nsaid Mr. Holder.\n\n\"Precisely. It was I. I found that I had my man, so I came home\nand changed my clothes. It was a delicate part which I had to\nplay then, for I saw that a prosecution must be avoided to avert\nscandal, and I knew that so astute a villain would see that our\nhands were tied in the matter. I went and saw him. At first, of\ncourse, he denied everything. But when I gave him every\nparticular that had occurred, he tried to bluster and took down a\nlife-preserver from the wall. I knew my man, however, and I\nclapped a pistol to his head before he could strike. Then he\nbecame a little more reasonable. I told him that we would give\nhim a price for the stones he held--1000 pounds apiece. That\nbrought out the first signs of grief that he had shown. 'Why,\ndash it all!' said he, 'I've let them go at six hundred for the\nthree!' I soon managed to get the address of the receiver who had\nthem, on promising him that there would be no prosecution. Off I\nset to him, and after much chaffering I got our stones at 1000\npounds apiece. Then I looked in upon your son, told him that all\nwas right, and eventually got to my bed about two o'clock, after\nwhat I may call a really hard day's work.\"\n\n\"A day which has saved England from a great public scandal,\" said\nthe banker, rising. \"Sir, I cannot find words to thank you, but\nyou shall not find me ungrateful for what you have done. Your\nskill has indeed exceeded all that I have heard of it. And now I\nmust fly to my dear boy to apologise to him for the wrong which I\nhave done him. As to what you tell me of poor Mary, it goes to my\nvery heart. Not even your skill can inform me where she is now.\"\n\n\"I think that we may safely say,\" returned Holmes, \"that she is\nwherever Sir George Burnwell is. It is equally certain, too, that\nwhatever her sins are, they will soon receive a more than\nsufficient punishment.\"\n\n\n\nXII. THE ADVENTURE OF THE COPPER BEECHES\n\n\"To the man who loves art for its own sake,\" remarked Sherlock\nHolmes, tossing aside the advertisement sheet of the Daily\nTelegraph, \"it is frequently in its least important and lowliest\nmanifestations that the keenest pleasure is to be derived. It is\npleasant to me to observe, Watson, that you have so far grasped\nthis truth that in these little records of our cases which you\nhave been good enough to draw up, and, I am bound to say,\noccasionally to embellish, you have given prominence not so much\nto the many causes célèbres and sensational trials in which I\nhave figured but rather to those incidents which may have been\ntrivial in themselves, but which have given room for those\nfaculties of deduction and of logical synthesis which I have made\nmy special province.\"\n\n\"And yet,\" said I, smiling, \"I cannot quite hold myself absolved\nfrom the charge of sensationalism which has been urged against my\nrecords.\"\n\n\"You have erred, perhaps,\" he observed, taking up a glowing\ncinder with the tongs and lighting with it the long cherry-wood\npipe which was wont to replace his clay when he was in a\ndisputatious rather than a meditative mood--\"you have erred\nperhaps in attempting to put colour and life into each of your\nstatements instead of confining yourself to the task of placing\nupon record that severe reasoning from cause to effect which is\nreally the only notable feature about the thing.\"\n\n\"It seems to me that I have done you full justice in the matter,\"\nI remarked with some coldness, for I was repelled by the egotism\nwhich I had more than once observed to be a strong factor in my\nfriend's singular character.\n\n\"No, it is not selfishness or conceit,\" said he, answering, as\nwas his wont, my thoughts rather than my words. \"If I claim full\njustice for my art, it is because it is an impersonal thing--a\nthing beyond myself. Crime is common. Logic is rare. Therefore it\nis upon the logic rather than upon the crime that you should\ndwell. You have degraded what should have been a course of\nlectures into a series of tales.\"\n\nIt was a cold morning of the early spring, and we sat after\nbreakfast on either side of a cheery fire in the old room at\nBaker Street. A thick fog rolled down between the lines of\ndun-coloured houses, and the opposing windows loomed like dark,\nshapeless blurs through the heavy yellow wreaths. Our gas was lit\nand shone on the white cloth and glimmer of china and metal, for\nthe table had not been cleared yet. Sherlock Holmes had been\nsilent all the morning, dipping continuously into the\nadvertisement columns of a succession of papers until at last,\nhaving apparently given up his search, he had emerged in no very\nsweet temper to lecture me upon my literary shortcomings.\n\n\"At the same time,\" he remarked after a pause, during which he\nhad sat puffing at his long pipe and gazing down into the fire,\n\"you can hardly be open to a charge of sensationalism, for out of\nthese cases which you have been so kind as to interest yourself\nin, a fair proportion do not treat of crime, in its legal sense,\nat all. The small matter in which I endeavoured to help the King\nof Bohemia, the singular experience of Miss Mary Sutherland, the\nproblem connected with the man with the twisted lip, and the\nincident of the noble bachelor, were all matters which are\noutside the pale of the law. But in avoiding the sensational, I\nfear that you may have bordered on the trivial.\"\n\n\"The end may have been so,\" I answered, \"but the methods I hold\nto have been novel and of interest.\"\n\n\"Pshaw, my dear fellow, what do the public, the great unobservant\npublic, who could hardly tell a weaver by his tooth or a\ncompositor by his left thumb, care about the finer shades of\nanalysis and deduction! But, indeed, if you are trivial, I cannot\nblame you, for the days of the great cases are past. Man, or at\nleast criminal man, has lost all enterprise and originality. As\nto my own little practice, it seems to be degenerating into an\nagency for recovering lost lead pencils and giving advice to\nyoung ladies from boarding-schools. I think that I have touched\nbottom at last, however. This note I had this morning marks my\nzero-point, I fancy. Read it!\" He tossed a crumpled letter across\nto me.\n\nIt was dated from Montague Place upon the preceding evening, and\nran thus:\n\n\"DEAR MR. HOLMES:--I am very anxious to consult you as to whether\nI should or should not accept a situation which has been offered\nto me as governess. I shall call at half-past ten to-morrow if I\ndo not inconvenience you. Yours faithfully,\n                                               \"VIOLET HUNTER.\"\n\n\"Do you know the young lady?\" I asked.\n\n\"Not I.\"\n\n\"It is half-past ten now.\"\n\n\"Yes, and I have no doubt that is her ring.\"\n\n\"It may turn out to be of more interest than you think. You\nremember that the affair of the blue carbuncle, which appeared to\nbe a mere whim at first, developed into a serious investigation.\nIt may be so in this case, also.\"\n\n\"Well, let us hope so. But our doubts will very soon be solved,\nfor here, unless I am much mistaken, is the person in question.\"\n\nAs he spoke the door opened and a young lady entered the room.\nShe was plainly but neatly dressed, with a bright, quick face,\nfreckled like a plover's egg, and with the brisk manner of a\nwoman who has had her own way to make in the world.\n\n\"You will excuse my troubling you, I am sure,\" said she, as my\ncompanion rose to greet her, \"but I have had a very strange\nexperience, and as I have no parents or relations of any sort\nfrom whom I could ask advice, I thought that perhaps you would be\nkind enough to tell me what I should do.\"\n\n\"Pray take a seat, Miss Hunter. I shall be happy to do anything\nthat I can to serve you.\"\n\nI could see that Holmes was favourably impressed by the manner\nand speech of his new client. He looked her over in his searching\nfashion, and then composed himself, with his lids drooping and\nhis finger-tips together, to listen to her story.\n\n\"I have been a governess for five years,\" said she, \"in the\nfamily of Colonel Spence Munro, but two months ago the colonel\nreceived an appointment at Halifax, in Nova Scotia, and took his\nchildren over to America with him, so that I found myself without\na situation. I advertised, and I answered advertisements, but\nwithout success. At last the little money which I had saved began\nto run short, and I was at my wit's end as to what I should do.\n\n\"There is a well-known agency for governesses in the West End\ncalled Westaway's, and there I used to call about once a week in\norder to see whether anything had turned up which might suit me.\nWestaway was the name of the founder of the business, but it is\nreally managed by Miss Stoper. She sits in her own little office,\nand the ladies who are seeking employment wait in an anteroom,\nand are then shown in one by one, when she consults her ledgers\nand sees whether she has anything which would suit them.\n\n\"Well, when I called last week I was shown into the little office\nas usual, but I found that Miss Stoper was not alone. A\nprodigiously stout man with a very smiling face and a great heavy\nchin which rolled down in fold upon fold over his throat sat at\nher elbow with a pair of glasses on his nose, looking very\nearnestly at the ladies who entered. As I came in he gave quite a\njump in his chair and turned quickly to Miss Stoper.\n\n\"'That will do,' said he; 'I could not ask for anything better.\nCapital! capital!' He seemed quite enthusiastic and rubbed his\nhands together in the most genial fashion. He was such a\ncomfortable-looking man that it was quite a pleasure to look at\nhim.\n\n\"'You are looking for a situation, miss?' he asked.\n\n\"'Yes, sir.'\n\n\"'As governess?'\n\n\"'Yes, sir.'\n\n\"'And what salary do you ask?'\n\n\"'I had 4 pounds a month in my last place with Colonel Spence\nMunro.'\n\n\"'Oh, tut, tut! sweating--rank sweating!' he cried, throwing his\nfat hands out into the air like a man who is in a boiling\npassion. 'How could anyone offer so pitiful a sum to a lady with\nsuch attractions and accomplishments?'\n\n\"'My accomplishments, sir, may be less than you imagine,' said I.\n'A little French, a little German, music, and drawing--'\n\n\"'Tut, tut!' he cried. 'This is all quite beside the question.\nThe point is, have you or have you not the bearing and deportment\nof a lady? There it is in a nutshell. If you have not, you are\nnot fitted for the rearing of a child who may some day play a\nconsiderable part in the history of the country. But if you have\nwhy, then, how could any gentleman ask you to condescend to\naccept anything under the three figures? Your salary with me,\nmadam, would commence at 100 pounds a year.'\n\n\"You may imagine, Mr. Holmes, that to me, destitute as I was,\nsuch an offer seemed almost too good to be true. The gentleman,\nhowever, seeing perhaps the look of incredulity upon my face,\nopened a pocket-book and took out a note.\n\n\"'It is also my custom,' said he, smiling in the most pleasant\nfashion until his eyes were just two little shining slits amid\nthe white creases of his face, 'to advance to my young ladies\nhalf their salary beforehand, so that they may meet any little\nexpenses of their journey and their wardrobe.'\n\n\"It seemed to me that I had never met so fascinating and so\nthoughtful a man. As I was already in debt to my tradesmen, the\nadvance was a great convenience, and yet there was something\nunnatural about the whole transaction which made me wish to know\na little more before I quite committed myself.\n\n\"'May I ask where you live, sir?' said I.\n\n\"'Hampshire. Charming rural place. The Copper Beeches, five miles\non the far side of Winchester. It is the most lovely country, my\ndear young lady, and the dearest old country-house.'\n\n\"'And my duties, sir? I should be glad to know what they would\nbe.'\n\n\"'One child--one dear little romper just six years old. Oh, if\nyou could see him killing cockroaches with a slipper! Smack!\nsmack! smack! Three gone before you could wink!' He leaned back\nin his chair and laughed his eyes into his head again.\n\n\"I was a little startled at the nature of the child's amusement,\nbut the father's laughter made me think that perhaps he was\njoking.\n\n\"'My sole duties, then,' I asked, 'are to take charge of a single\nchild?'\n\n\"'No, no, not the sole, not the sole, my dear young lady,' he\ncried. 'Your duty would be, as I am sure your good sense would\nsuggest, to obey any little commands my wife might give, provided\nalways that they were such commands as a lady might with\npropriety obey. You see no difficulty, heh?'\n\n\"'I should be happy to make myself useful.'\n\n\"'Quite so. In dress now, for example. We are faddy people, you\nknow--faddy but kind-hearted. If you were asked to wear any dress\nwhich we might give you, you would not object to our little whim.\nHeh?'\n\n\"'No,' said I, considerably astonished at his words.\n\n\"'Or to sit here, or sit there, that would not be offensive to\nyou?'\n\n\"'Oh, no.'\n\n\"'Or to cut your hair quite short before you come to us?'\n\n\"I could hardly believe my ears. As you may observe, Mr. Holmes,\nmy hair is somewhat luxuriant, and of a rather peculiar tint of\nchestnut. It has been considered artistic. I could not dream of\nsacrificing it in this offhand fashion.\n\n\"'I am afraid that that is quite impossible,' said I. He had been\nwatching me eagerly out of his small eyes, and I could see a\nshadow pass over his face as I spoke.\n\n\"'I am afraid that it is quite essential,' said he. 'It is a\nlittle fancy of my wife's, and ladies' fancies, you know, madam,\nladies' fancies must be consulted. And so you won't cut your\nhair?'\n\n\"'No, sir, I really could not,' I answered firmly.\n\n\"'Ah, very well; then that quite settles the matter. It is a\npity, because in other respects you would really have done very\nnicely. In that case, Miss Stoper, I had best inspect a few more\nof your young ladies.'\n\n\"The manageress had sat all this while busy with her papers\nwithout a word to either of us, but she glanced at me now with so\nmuch annoyance upon her face that I could not help suspecting\nthat she had lost a handsome commission through my refusal.\n\n\"'Do you desire your name to be kept upon the books?' she asked.\n\n\"'If you please, Miss Stoper.'\n\n\"'Well, really, it seems rather useless, since you refuse the\nmost excellent offers in this fashion,' said she sharply. 'You\ncan hardly expect us to exert ourselves to find another such\nopening for you. Good-day to you, Miss Hunter.' She struck a gong\nupon the table, and I was shown out by the page.\n\n\"Well, Mr. Holmes, when I got back to my lodgings and found\nlittle enough in the cupboard, and two or three bills upon the\ntable, I began to ask myself whether I had not done a very\nfoolish thing. After all, if these people had strange fads and\nexpected obedience on the most extraordinary matters, they were\nat least ready to pay for their eccentricity. Very few\ngovernesses in England are getting 100 pounds a year. Besides,\nwhat use was my hair to me? Many people are improved by wearing\nit short and perhaps I should be among the number. Next day I was\ninclined to think that I had made a mistake, and by the day after\nI was sure of it. I had almost overcome my pride so far as to go\nback to the agency and inquire whether the place was still open\nwhen I received this letter from the gentleman himself. I have it\nhere and I will read it to you:\n\n                       \"'The Copper Beeches, near Winchester.\n\"'DEAR MISS HUNTER:--Miss Stoper has very kindly given me your\naddress, and I write from here to ask you whether you have\nreconsidered your decision. My wife is very anxious that you\nshould come, for she has been much attracted by my description of\nyou. We are willing to give 30 pounds a quarter, or 120 pounds a\nyear, so as to recompense you for any little inconvenience which\nour fads may cause you. They are not very exacting, after all. My\nwife is fond of a particular shade of electric blue and would\nlike you to wear such a dress indoors in the morning. You need\nnot, however, go to the expense of purchasing one, as we have one\nbelonging to my dear daughter Alice (now in Philadelphia), which\nwould, I should think, fit you very well. Then, as to sitting\nhere or there, or amusing yourself in any manner indicated, that\nneed cause you no inconvenience. As regards your hair, it is no\ndoubt a pity, especially as I could not help remarking its beauty\nduring our short interview, but I am afraid that I must remain\nfirm upon this point, and I only hope that the increased salary\nmay recompense you for the loss. Your duties, as far as the child\nis concerned, are very light. Now do try to come, and I shall\nmeet you with the dog-cart at Winchester. Let me know your train.\nYours faithfully, JEPHRO RUCASTLE.'\n\n\"That is the letter which I have just received, Mr. Holmes, and\nmy mind is made up that I will accept it. I thought, however,\nthat before taking the final step I should like to submit the\nwhole matter to your consideration.\"\n\n\"Well, Miss Hunter, if your mind is made up, that settles the\nquestion,\" said Holmes, smiling.\n\n\"But you would not advise me to refuse?\"\n\n\"I confess that it is not the situation which I should like to\nsee a sister of mine apply for.\"\n\n\"What is the meaning of it all, Mr. Holmes?\"\n\n\"Ah, I have no data. I cannot tell. Perhaps you have yourself\nformed some opinion?\"\n\n\"Well, there seems to me to be only one possible solution. Mr.\nRucastle seemed to be a very kind, good-natured man. Is it not\npossible that his wife is a lunatic, that he desires to keep the\nmatter quiet for fear she should be taken to an asylum, and that\nhe humours her fancies in every way in order to prevent an\noutbreak?\"\n\n\"That is a possible solution--in fact, as matters stand, it is\nthe most probable one. But in any case it does not seem to be a\nnice household for a young lady.\"\n\n\"But the money, Mr. Holmes, the money!\"\n\n\"Well, yes, of course the pay is good--too good. That is what\nmakes me uneasy. Why should they give you 120 pounds a year, when\nthey could have their pick for 40 pounds? There must be some\nstrong reason behind.\"\n\n\"I thought that if I told you the circumstances you would\nunderstand afterwards if I wanted your help. I should feel so\nmuch stronger if I felt that you were at the back of me.\"\n\n\"Oh, you may carry that feeling away with you. I assure you that\nyour little problem promises to be the most interesting which has\ncome my way for some months. There is something distinctly novel\nabout some of the features. If you should find yourself in doubt\nor in danger--\"\n\n\"Danger! What danger do you foresee?\"\n\nHolmes shook his head gravely. \"It would cease to be a danger if\nwe could define it,\" said he. \"But at any time, day or night, a\ntelegram would bring me down to your help.\"\n\n\"That is enough.\" She rose briskly from her chair with the\nanxiety all swept from her face. \"I shall go down to Hampshire\nquite easy in my mind now. I shall write to Mr. Rucastle at once,\nsacrifice my poor hair to-night, and start for Winchester\nto-morrow.\" With a few grateful words to Holmes she bade us both\ngood-night and bustled off upon her way.\n\n\"At least,\" said I as we heard her quick, firm steps descending\nthe stairs, \"she seems to be a young lady who is very well able\nto take care of herself.\"\n\n\"And she would need to be,\" said Holmes gravely. \"I am much\nmistaken if we do not hear from her before many days are past.\"\n\nIt was not very long before my friend's prediction was fulfilled.\nA fortnight went by, during which I frequently found my thoughts\nturning in her direction and wondering what strange side-alley of\nhuman experience this lonely woman had strayed into. The unusual\nsalary, the curious conditions, the light duties, all pointed to\nsomething abnormal, though whether a fad or a plot, or whether\nthe man were a philanthropist or a villain, it was quite beyond\nmy powers to determine. As to Holmes, I observed that he sat\nfrequently for half an hour on end, with knitted brows and an\nabstracted air, but he swept the matter away with a wave of his\nhand when I mentioned it. \"Data! data! data!\" he cried\nimpatiently. \"I can't make bricks without clay.\" And yet he would\nalways wind up by muttering that no sister of his should ever\nhave accepted such a situation.\n\nThe telegram which we eventually received came late one night\njust as I was thinking of turning in and Holmes was settling down\nto one of those all-night chemical researches which he frequently\nindulged in, when I would leave him stooping over a retort and a\ntest-tube at night and find him in the same position when I came\ndown to breakfast in the morning. He opened the yellow envelope,\nand then, glancing at the message, threw it across to me.\n\n\"Just look up the trains in Bradshaw,\" said he, and turned back\nto his chemical studies.\n\nThe summons was a brief and urgent one.\n\n\"Please be at the Black Swan Hotel at Winchester at midday\nto-morrow,\" it said. \"Do come! I am at my wit's end.  HUNTER.\"\n\n\"Will you come with me?\" asked Holmes, glancing up.\n\n\"I should wish to.\"\n\n\"Just look it up, then.\"\n\n\"There is a train at half-past nine,\" said I, glancing over my\nBradshaw. \"It is due at Winchester at 11:30.\"\n\n\"That will do very nicely. Then perhaps I had better postpone my\nanalysis of the acetones, as we may need to be at our best in the\nmorning.\"\n\nBy eleven o'clock the next day we were well upon our way to the\nold English capital. Holmes had been buried in the morning papers\nall the way down, but after we had passed the Hampshire border he\nthrew them down and began to admire the scenery. It was an ideal\nspring day, a light blue sky, flecked with little fleecy white\nclouds drifting across from west to east. The sun was shining\nvery brightly, and yet there was an exhilarating nip in the air,\nwhich set an edge to a man's energy. All over the countryside,\naway to the rolling hills around Aldershot, the little red and\ngrey roofs of the farm-steadings peeped out from amid the light\ngreen of the new foliage.\n\n\"Are they not fresh and beautiful?\" I cried with all the\nenthusiasm of a man fresh from the fogs of Baker Street.\n\nBut Holmes shook his head gravely.\n\n\"Do you know, Watson,\" said he, \"that it is one of the curses of\na mind with a turn like mine that I must look at everything with\nreference to my own special subject. You look at these scattered\nhouses, and you are impressed by their beauty. I look at them,\nand the only thought which comes to me is a feeling of their\nisolation and of the impunity with which crime may be committed\nthere.\"\n\n\"Good heavens!\" I cried. \"Who would associate crime with these\ndear old homesteads?\"\n\n\"They always fill me with a certain horror. It is my belief,\nWatson, founded upon my experience, that the lowest and vilest\nalleys in London do not present a more dreadful record of sin\nthan does the smiling and beautiful countryside.\"\n\n\"You horrify me!\"\n\n\"But the reason is very obvious. The pressure of public opinion\ncan do in the town what the law cannot accomplish. There is no\nlane so vile that the scream of a tortured child, or the thud of\na drunkard's blow, does not beget sympathy and indignation among\nthe neighbours, and then the whole machinery of justice is ever\nso close that a word of complaint can set it going, and there is\nbut a step between the crime and the dock. But look at these\nlonely houses, each in its own fields, filled for the most part\nwith poor ignorant folk who know little of the law. Think of the\ndeeds of hellish cruelty, the hidden wickedness which may go on,\nyear in, year out, in such places, and none the wiser. Had this\nlady who appeals to us for help gone to live in Winchester, I\nshould never have had a fear for her. It is the five miles of\ncountry which makes the danger. Still, it is clear that she is\nnot personally threatened.\"\n\n\"No. If she can come to Winchester to meet us she can get away.\"\n\n\"Quite so. She has her freedom.\"\n\n\"What CAN be the matter, then? Can you suggest no explanation?\"\n\n\"I have devised seven separate explanations, each of which would\ncover the facts as far as we know them. But which of these is\ncorrect can only be determined by the fresh information which we\nshall no doubt find waiting for us. Well, there is the tower of\nthe cathedral, and we shall soon learn all that Miss Hunter has\nto tell.\"\n\nThe Black Swan is an inn of repute in the High Street, at no\ndistance from the station, and there we found the young lady\nwaiting for us. She had engaged a sitting-room, and our lunch\nawaited us upon the table.\n\n\"I am so delighted that you have come,\" she said earnestly. \"It\nis so very kind of you both; but indeed I do not know what I\nshould do. Your advice will be altogether invaluable to me.\"\n\n\"Pray tell us what has happened to you.\"\n\n\"I will do so, and I must be quick, for I have promised Mr.\nRucastle to be back before three. I got his leave to come into\ntown this morning, though he little knew for what purpose.\"\n\n\"Let us have everything in its due order.\" Holmes thrust his long\nthin legs out towards the fire and composed himself to listen.\n\n\"In the first place, I may say that I have met, on the whole,\nwith no actual ill-treatment from Mr. and Mrs. Rucastle. It is\nonly fair to them to say that. But I cannot understand them, and\nI am not easy in my mind about them.\"\n\n\"What can you not understand?\"\n\n\"Their reasons for their conduct. But you shall have it all just\nas it occurred. When I came down, Mr. Rucastle met me here and\ndrove me in his dog-cart to the Copper Beeches. It is, as he\nsaid, beautifully situated, but it is not beautiful in itself,\nfor it is a large square block of a house, whitewashed, but all\nstained and streaked with damp and bad weather. There are grounds\nround it, woods on three sides, and on the fourth a field which\nslopes down to the Southampton highroad, which curves past about\na hundred yards from the front door. This ground in front belongs\nto the house, but the woods all round are part of Lord\nSoutherton's preserves. A clump of copper beeches immediately in\nfront of the hall door has given its name to the place.\n\n\"I was driven over by my employer, who was as amiable as ever,\nand was introduced by him that evening to his wife and the child.\nThere was no truth, Mr. Holmes, in the conjecture which seemed to\nus to be probable in your rooms at Baker Street. Mrs. Rucastle is\nnot mad. I found her to be a silent, pale-faced woman, much\nyounger than her husband, not more than thirty, I should think,\nwhile he can hardly be less than forty-five. From their\nconversation I have gathered that they have been married about\nseven years, that he was a widower, and that his only child by\nthe first wife was the daughter who has gone to Philadelphia. Mr.\nRucastle told me in private that the reason why she had left them\nwas that she had an unreasoning aversion to her stepmother. As\nthe daughter could not have been less than twenty, I can quite\nimagine that her position must have been uncomfortable with her\nfather's young wife.\n\n\"Mrs. Rucastle seemed to me to be colourless in mind as well as\nin feature. She impressed me neither favourably nor the reverse.\nShe was a nonentity. It was easy to see that she was passionately\ndevoted both to her husband and to her little son. Her light grey\neyes wandered continually from one to the other, noting every\nlittle want and forestalling it if possible. He was kind to her\nalso in his bluff, boisterous fashion, and on the whole they\nseemed to be a happy couple. And yet she had some secret sorrow,\nthis woman. She would often be lost in deep thought, with the\nsaddest look upon her face. More than once I have surprised her\nin tears. I have thought sometimes that it was the disposition of\nher child which weighed upon her mind, for I have never met so\nutterly spoiled and so ill-natured a little creature. He is small\nfor his age, with a head which is quite disproportionately large.\nHis whole life appears to be spent in an alternation between\nsavage fits of passion and gloomy intervals of sulking. Giving\npain to any creature weaker than himself seems to be his one idea\nof amusement, and he shows quite remarkable talent in planning\nthe capture of mice, little birds, and insects. But I would\nrather not talk about the creature, Mr. Holmes, and, indeed, he\nhas little to do with my story.\"\n\n\"I am glad of all details,\" remarked my friend, \"whether they\nseem to you to be relevant or not.\"\n\n\"I shall try not to miss anything of importance. The one\nunpleasant thing about the house, which struck me at once, was\nthe appearance and conduct of the servants. There are only two, a\nman and his wife. Toller, for that is his name, is a rough,\nuncouth man, with grizzled hair and whiskers, and a perpetual\nsmell of drink. Twice since I have been with them he has been\nquite drunk, and yet Mr. Rucastle seemed to take no notice of it.\nHis wife is a very tall and strong woman with a sour face, as\nsilent as Mrs. Rucastle and much less amiable. They are a most\nunpleasant couple, but fortunately I spend most of my time in the\nnursery and my own room, which are next to each other in one\ncorner of the building.\n\n\"For two days after my arrival at the Copper Beeches my life was\nvery quiet; on the third, Mrs. Rucastle came down just after\nbreakfast and whispered something to her husband.\n\n\"'Oh, yes,' said he, turning to me, 'we are very much obliged to\nyou, Miss Hunter, for falling in with our whims so far as to cut\nyour hair. I assure you that it has not detracted in the tiniest\niota from your appearance. We shall now see how the electric-blue\ndress will become you. You will find it laid out upon the bed in\nyour room, and if you would be so good as to put it on we should\nboth be extremely obliged.'\n\n\"The dress which I found waiting for me was of a peculiar shade\nof blue. It was of excellent material, a sort of beige, but it\nbore unmistakable signs of having been worn before. It could not\nhave been a better fit if I had been measured for it. Both Mr.\nand Mrs. Rucastle expressed a delight at the look of it, which\nseemed quite exaggerated in its vehemence. They were waiting for\nme in the drawing-room, which is a very large room, stretching\nalong the entire front of the house, with three long windows\nreaching down to the floor. A chair had been placed close to the\ncentral window, with its back turned towards it. In this I was\nasked to sit, and then Mr. Rucastle, walking up and down on the\nother side of the room, began to tell me a series of the funniest\nstories that I have ever listened to. You cannot imagine how\ncomical he was, and I laughed until I was quite weary. Mrs.\nRucastle, however, who has evidently no sense of humour, never so\nmuch as smiled, but sat with her hands in her lap, and a sad,\nanxious look upon her face. After an hour or so, Mr. Rucastle\nsuddenly remarked that it was time to commence the duties of the\nday, and that I might change my dress and go to little Edward in\nthe nursery.\n\n\"Two days later this same performance was gone through under\nexactly similar circumstances. Again I changed my dress, again I\nsat in the window, and again I laughed very heartily at the funny\nstories of which my employer had an immense répertoire, and which\nhe told inimitably. Then he handed me a yellow-backed novel, and\nmoving my chair a little sideways, that my own shadow might not\nfall upon the page, he begged me to read aloud to him. I read for\nabout ten minutes, beginning in the heart of a chapter, and then\nsuddenly, in the middle of a sentence, he ordered me to cease and\nto change my dress.\n\n\"You can easily imagine, Mr. Holmes, how curious I became as to\nwhat the meaning of this extraordinary performance could possibly\nbe. They were always very careful, I observed, to turn my face\naway from the window, so that I became consumed with the desire\nto see what was going on behind my back. At first it seemed to be\nimpossible, but I soon devised a means. My hand-mirror had been\nbroken, so a happy thought seized me, and I concealed a piece of\nthe glass in my handkerchief. On the next occasion, in the midst\nof my laughter, I put my handkerchief up to my eyes, and was able\nwith a little management to see all that there was behind me. I\nconfess that I was disappointed. There was nothing. At least that\nwas my first impression. At the second glance, however, I\nperceived that there was a man standing in the Southampton Road,\na small bearded man in a grey suit, who seemed to be looking in\nmy direction. The road is an important highway, and there are\nusually people there. This man, however, was leaning against the\nrailings which bordered our field and was looking earnestly up. I\nlowered my handkerchief and glanced at Mrs. Rucastle to find her\neyes fixed upon me with a most searching gaze. She said nothing,\nbut I am convinced that she had divined that I had a mirror in my\nhand and had seen what was behind me. She rose at once.\n\n\"'Jephro,' said she, 'there is an impertinent fellow upon the\nroad there who stares up at Miss Hunter.'\n\n\"'No friend of yours, Miss Hunter?' he asked.\n\n\"'No, I know no one in these parts.'\n\n\"'Dear me! How very impertinent! Kindly turn round and motion to\nhim to go away.'\n\n\"'Surely it would be better to take no notice.'\n\n\"'No, no, we should have him loitering here always. Kindly turn\nround and wave him away like that.'\n\n\"I did as I was told, and at the same instant Mrs. Rucastle drew\ndown the blind. That was a week ago, and from that time I have\nnot sat again in the window, nor have I worn the blue dress, nor\nseen the man in the road.\"\n\n\"Pray continue,\" said Holmes. \"Your narrative promises to be a\nmost interesting one.\"\n\n\"You will find it rather disconnected, I fear, and there may\nprove to be little relation between the different incidents of\nwhich I speak. On the very first day that I was at the Copper\nBeeches, Mr. Rucastle took me to a small outhouse which stands\nnear the kitchen door. As we approached it I heard the sharp\nrattling of a chain, and the sound as of a large animal moving\nabout.\n\n\"'Look in here!' said Mr. Rucastle, showing me a slit between two\nplanks. 'Is he not a beauty?'\n\n\"I looked through and was conscious of two glowing eyes, and of a\nvague figure huddled up in the darkness.\n\n\"'Don't be frightened,' said my employer, laughing at the start\nwhich I had given. 'It's only Carlo, my mastiff. I call him mine,\nbut really old Toller, my groom, is the only man who can do\nanything with him. We feed him once a day, and not too much then,\nso that he is always as keen as mustard. Toller lets him loose\nevery night, and God help the trespasser whom he lays his fangs\nupon. For goodness' sake don't you ever on any pretext set your\nfoot over the threshold at night, for it's as much as your life\nis worth.'\n\n\"The warning was no idle one, for two nights later I happened to\nlook out of my bedroom window about two o'clock in the morning.\nIt was a beautiful moonlight night, and the lawn in front of the\nhouse was silvered over and almost as bright as day. I was\nstanding, rapt in the peaceful beauty of the scene, when I was\naware that something was moving under the shadow of the copper\nbeeches. As it emerged into the moonshine I saw what it was. It\nwas a giant dog, as large as a calf, tawny tinted, with hanging\njowl, black muzzle, and huge projecting bones. It walked slowly\nacross the lawn and vanished into the shadow upon the other side.\nThat dreadful sentinel sent a chill to my heart which I do not\nthink that any burglar could have done.\n\n\"And now I have a very strange experience to tell you. I had, as\nyou know, cut off my hair in London, and I had placed it in a\ngreat coil at the bottom of my trunk. One evening, after the\nchild was in bed, I began to amuse myself by examining the\nfurniture of my room and by rearranging my own little things.\nThere was an old chest of drawers in the room, the two upper ones\nempty and open, the lower one locked. I had filled the first two\nwith my linen, and as I had still much to pack away I was\nnaturally annoyed at not having the use of the third drawer. It\nstruck me that it might have been fastened by a mere oversight,\nso I took out my bunch of keys and tried to open it. The very\nfirst key fitted to perfection, and I drew the drawer open. There\nwas only one thing in it, but I am sure that you would never\nguess what it was. It was my coil of hair.\n\n\"I took it up and examined it. It was of the same peculiar tint,\nand the same thickness. But then the impossibility of the thing\nobtruded itself upon me. How could my hair have been locked in\nthe drawer? With trembling hands I undid my trunk, turned out the\ncontents, and drew from the bottom my own hair. I laid the two\ntresses together, and I assure you that they were identical. Was\nit not extraordinary? Puzzle as I would, I could make nothing at\nall of what it meant. I returned the strange hair to the drawer,\nand I said nothing of the matter to the Rucastles as I felt that\nI had put myself in the wrong by opening a drawer which they had\nlocked.\n\n\"I am naturally observant, as you may have remarked, Mr. Holmes,\nand I soon had a pretty good plan of the whole house in my head.\nThere was one wing, however, which appeared not to be inhabited\nat all. A door which faced that which led into the quarters of\nthe Tollers opened into this suite, but it was invariably locked.\nOne day, however, as I ascended the stair, I met Mr. Rucastle\ncoming out through this door, his keys in his hand, and a look on\nhis face which made him a very different person to the round,\njovial man to whom I was accustomed. His cheeks were red, his\nbrow was all crinkled with anger, and the veins stood out at his\ntemples with passion. He locked the door and hurried past me\nwithout a word or a look.\n\n\"This aroused my curiosity, so when I went out for a walk in the\ngrounds with my charge, I strolled round to the side from which I\ncould see the windows of this part of the house. There were four\nof them in a row, three of which were simply dirty, while the\nfourth was shuttered up. They were evidently all deserted. As I\nstrolled up and down, glancing at them occasionally, Mr. Rucastle\ncame out to me, looking as merry and jovial as ever.\n\n\"'Ah!' said he, 'you must not think me rude if I passed you\nwithout a word, my dear young lady. I was preoccupied with\nbusiness matters.'\n\n\"I assured him that I was not offended. 'By the way,' said I,\n'you seem to have quite a suite of spare rooms up there, and one\nof them has the shutters up.'\n\n\"He looked surprised and, as it seemed to me, a little startled\nat my remark.\n\n\"'Photography is one of my hobbies,' said he. 'I have made my\ndark room up there. But, dear me! what an observant young lady we\nhave come upon. Who would have believed it? Who would have ever\nbelieved it?' He spoke in a jesting tone, but there was no jest\nin his eyes as he looked at me. I read suspicion there and\nannoyance, but no jest.\n\n\"Well, Mr. Holmes, from the moment that I understood that there\nwas something about that suite of rooms which I was not to know,\nI was all on fire to go over them. It was not mere curiosity,\nthough I have my share of that. It was more a feeling of duty--a\nfeeling that some good might come from my penetrating to this\nplace. They talk of woman's instinct; perhaps it was woman's\ninstinct which gave me that feeling. At any rate, it was there,\nand I was keenly on the lookout for any chance to pass the\nforbidden door.\n\n\"It was only yesterday that the chance came. I may tell you that,\nbesides Mr. Rucastle, both Toller and his wife find something to\ndo in these deserted rooms, and I once saw him carrying a large\nblack linen bag with him through the door. Recently he has been\ndrinking hard, and yesterday evening he was very drunk; and when\nI came upstairs there was the key in the door. I have no doubt at\nall that he had left it there. Mr. and Mrs. Rucastle were both\ndownstairs, and the child was with them, so that I had an\nadmirable opportunity. I turned the key gently in the lock,\nopened the door, and slipped through.\n\n\"There was a little passage in front of me, unpapered and\nuncarpeted, which turned at a right angle at the farther end.\nRound this corner were three doors in a line, the first and third\nof which were open. They each led into an empty room, dusty and\ncheerless, with two windows in the one and one in the other, so\nthick with dirt that the evening light glimmered dimly through\nthem. The centre door was closed, and across the outside of it\nhad been fastened one of the broad bars of an iron bed, padlocked\nat one end to a ring in the wall, and fastened at the other with\nstout cord. The door itself was locked as well, and the key was\nnot there. This barricaded door corresponded clearly with the\nshuttered window outside, and yet I could see by the glimmer from\nbeneath it that the room was not in darkness. Evidently there was\na skylight which let in light from above. As I stood in the\npassage gazing at the sinister door and wondering what secret it\nmight veil, I suddenly heard the sound of steps within the room\nand saw a shadow pass backward and forward against the little\nslit of dim light which shone out from under the door. A mad,\nunreasoning terror rose up in me at the sight, Mr. Holmes. My\noverstrung nerves failed me suddenly, and I turned and ran--ran\nas though some dreadful hand were behind me clutching at the\nskirt of my dress. I rushed down the passage, through the door,\nand straight into the arms of Mr. Rucastle, who was waiting\noutside.\n\n\"'So,' said he, smiling, 'it was you, then. I thought that it\nmust be when I saw the door open.'\n\n\"'Oh, I am so frightened!' I panted.\n\n\"'My dear young lady! my dear young lady!'--you cannot think how\ncaressing and soothing his manner was--'and what has frightened\nyou, my dear young lady?'\n\n\"But his voice was just a little too coaxing. He overdid it. I\nwas keenly on my guard against him.\n\n\"'I was foolish enough to go into the empty wing,' I answered.\n'But it is so lonely and eerie in this dim light that I was\nfrightened and ran out again. Oh, it is so dreadfully still in\nthere!'\n\n\"'Only that?' said he, looking at me keenly.\n\n\"'Why, what did you think?' I asked.\n\n\"'Why do you think that I lock this door?'\n\n\"'I am sure that I do not know.'\n\n\"'It is to keep people out who have no business there. Do you\nsee?' He was still smiling in the most amiable manner.\n\n\"'I am sure if I had known--'\n\n\"'Well, then, you know now. And if you ever put your foot over\nthat threshold again'--here in an instant the smile hardened into\na grin of rage, and he glared down at me with the face of a\ndemon--'I'll throw you to the mastiff.'\n\n\"I was so terrified that I do not know what I did. I suppose that\nI must have rushed past him into my room. I remember nothing\nuntil I found myself lying on my bed trembling all over. Then I\nthought of you, Mr. Holmes. I could not live there longer without\nsome advice. I was frightened of the house, of the man, of the\nwoman, of the servants, even of the child. They were all horrible\nto me. If I could only bring you down all would be well. Of\ncourse I might have fled from the house, but my curiosity was\nalmost as strong as my fears. My mind was soon made up. I would\nsend you a wire. I put on my hat and cloak, went down to the\noffice, which is about half a mile from the house, and then\nreturned, feeling very much easier. A horrible doubt came into my\nmind as I approached the door lest the dog might be loose, but I\nremembered that Toller had drunk himself into a state of\ninsensibility that evening, and I knew that he was the only one\nin the household who had any influence with the savage creature,\nor who would venture to set him free. I slipped in in safety and\nlay awake half the night in my joy at the thought of seeing you.\nI had no difficulty in getting leave to come into Winchester this\nmorning, but I must be back before three o'clock, for Mr. and\nMrs. Rucastle are going on a visit, and will be away all the\nevening, so that I must look after the child. Now I have told you\nall my adventures, Mr. Holmes, and I should be very glad if you\ncould tell me what it all means, and, above all, what I should\ndo.\"\n\nHolmes and I had listened spellbound to this extraordinary story.\nMy friend rose now and paced up and down the room, his hands in\nhis pockets, and an expression of the most profound gravity upon\nhis face.\n\n\"Is Toller still drunk?\" he asked.\n\n\"Yes. I heard his wife tell Mrs. Rucastle that she could do\nnothing with him.\"\n\n\"That is well. And the Rucastles go out to-night?\"\n\n\"Yes.\"\n\n\"Is there a cellar with a good strong lock?\"\n\n\"Yes, the wine-cellar.\"\n\n\"You seem to me to have acted all through this matter like a very\nbrave and sensible girl, Miss Hunter. Do you think that you could\nperform one more feat? I should not ask it of you if I did not\nthink you a quite exceptional woman.\"\n\n\"I will try. What is it?\"\n\n\"We shall be at the Copper Beeches by seven o'clock, my friend\nand I. The Rucastles will be gone by that time, and Toller will,\nwe hope, be incapable. There only remains Mrs. Toller, who might\ngive the alarm. If you could send her into the cellar on some\nerrand, and then turn the key upon her, you would facilitate\nmatters immensely.\"\n\n\"I will do it.\"\n\n\"Excellent! We shall then look thoroughly into the affair. Of\ncourse there is only one feasible explanation. You have been\nbrought there to personate someone, and the real person is\nimprisoned in this chamber. That is obvious. As to who this\nprisoner is, I have no doubt that it is the daughter, Miss Alice\nRucastle, if I remember right, who was said to have gone to\nAmerica. You were chosen, doubtless, as resembling her in height,\nfigure, and the colour of your hair. Hers had been cut off, very\npossibly in some illness through which she has passed, and so, of\ncourse, yours had to be sacrificed also. By a curious chance you\ncame upon her tresses. The man in the road was undoubtedly some\nfriend of hers--possibly her fiancé--and no doubt, as you wore\nthe girl's dress and were so like her, he was convinced from your\nlaughter, whenever he saw you, and afterwards from your gesture,\nthat Miss Rucastle was perfectly happy, and that she no longer\ndesired his attentions. The dog is let loose at night to prevent\nhim from endeavouring to communicate with her. So much is fairly\nclear. The most serious point in the case is the disposition of\nthe child.\"\n\n\"What on earth has that to do with it?\" I ejaculated.\n\n\"My dear Watson, you as a medical man are continually gaining\nlight as to the tendencies of a child by the study of the\nparents. Don't you see that the converse is equally valid. I have\nfrequently gained my first real insight into the character of\nparents by studying their children. This child's disposition is\nabnormally cruel, merely for cruelty's sake, and whether he\nderives this from his smiling father, as I should suspect, or\nfrom his mother, it bodes evil for the poor girl who is in their\npower.\"\n\n\"I am sure that you are right, Mr. Holmes,\" cried our client. \"A\nthousand things come back to me which make me certain that you\nhave hit it. Oh, let us lose not an instant in bringing help to\nthis poor creature.\"\n\n\"We must be circumspect, for we are dealing with a very cunning\nman. We can do nothing until seven o'clock. At that hour we shall\nbe with you, and it will not be long before we solve the\nmystery.\"\n\nWe were as good as our word, for it was just seven when we\nreached the Copper Beeches, having put up our trap at a wayside\npublic-house. The group of trees, with their dark leaves shining\nlike burnished metal in the light of the setting sun, were\nsufficient to mark the house even had Miss Hunter not been\nstanding smiling on the door-step.\n\n\"Have you managed it?\" asked Holmes.\n\nA loud thudding noise came from somewhere downstairs. \"That is\nMrs. Toller in the cellar,\" said she. \"Her husband lies snoring\non the kitchen rug. Here are his keys, which are the duplicates\nof Mr. Rucastle's.\"\n\n\"You have done well indeed!\" cried Holmes with enthusiasm. \"Now\nlead the way, and we shall soon see the end of this black\nbusiness.\"\n\nWe passed up the stair, unlocked the door, followed on down a\npassage, and found ourselves in front of the barricade which Miss\nHunter had described. Holmes cut the cord and removed the\ntransverse bar. Then he tried the various keys in the lock, but\nwithout success. No sound came from within, and at the silence\nHolmes' face clouded over.\n\n\"I trust that we are not too late,\" said he. \"I think, Miss\nHunter, that we had better go in without you. Now, Watson, put\nyour shoulder to it, and we shall see whether we cannot make our\nway in.\"\n\nIt was an old rickety door and gave at once before our united\nstrength. Together we rushed into the room. It was empty. There\nwas no furniture save a little pallet bed, a small table, and a\nbasketful of linen. The skylight above was open, and the prisoner\ngone.\n\n\"There has been some villainy here,\" said Holmes; \"this beauty\nhas guessed Miss Hunter's intentions and has carried his victim\noff.\"\n\n\"But how?\"\n\n\"Through the skylight. We shall soon see how he managed it.\" He\nswung himself up onto the roof. \"Ah, yes,\" he cried, \"here's the\nend of a long light ladder against the eaves. That is how he did\nit.\"\n\n\"But it is impossible,\" said Miss Hunter; \"the ladder was not\nthere when the Rucastles went away.\"\n\n\"He has come back and done it. I tell you that he is a clever and\ndangerous man. I should not be very much surprised if this were\nhe whose step I hear now upon the stair. I think, Watson, that it\nwould be as well for you to have your pistol ready.\"\n\nThe words were hardly out of his mouth before a man appeared at\nthe door of the room, a very fat and burly man, with a heavy\nstick in his hand. Miss Hunter screamed and shrunk against the\nwall at the sight of him, but Sherlock Holmes sprang forward and\nconfronted him.\n\n\"You villain!\" said he, \"where's your daughter?\"\n\nThe fat man cast his eyes round, and then up at the open\nskylight.\n\n\"It is for me to ask you that,\" he shrieked, \"you thieves! Spies\nand thieves! I have caught you, have I? You are in my power. I'll\nserve you!\" He turned and clattered down the stairs as hard as he\ncould go.\n\n\"He's gone for the dog!\" cried Miss Hunter.\n\n\"I have my revolver,\" said I.\n\n\"Better close the front door,\" cried Holmes, and we all rushed\ndown the stairs together. We had hardly reached the hall when we\nheard the baying of a hound, and then a scream of agony, with a\nhorrible worrying sound which it was dreadful to listen to. An\nelderly man with a red face and shaking limbs came staggering out\nat a side door.\n\n\"My God!\" he cried. \"Someone has loosed the dog. It's not been\nfed for two days. Quick, quick, or it'll be too late!\"\n\nHolmes and I rushed out and round the angle of the house, with\nToller hurrying behind us. There was the huge famished brute, its\nblack muzzle buried in Rucastle's throat, while he writhed and\nscreamed upon the ground. Running up, I blew its brains out, and\nit fell over with its keen white teeth still meeting in the great\ncreases of his neck. With much labour we separated them and\ncarried him, living but horribly mangled, into the house. We laid\nhim upon the drawing-room sofa, and having dispatched the sobered\nToller to bear the news to his wife, I did what I could to\nrelieve his pain. We were all assembled round him when the door\nopened, and a tall, gaunt woman entered the room.\n\n\"Mrs. Toller!\" cried Miss Hunter.\n\n\"Yes, miss. Mr. Rucastle let me out when he came back before he\nwent up to you. Ah, miss, it is a pity you didn't let me know\nwhat you were planning, for I would have told you that your pains\nwere wasted.\"\n\n\"Ha!\" said Holmes, looking keenly at her. \"It is clear that Mrs.\nToller knows more about this matter than anyone else.\"\n\n\"Yes, sir, I do, and I am ready enough to tell what I know.\"\n\n\"Then, pray, sit down, and let us hear it for there are several\npoints on which I must confess that I am still in the dark.\"\n\n\"I will soon make it clear to you,\" said she; \"and I'd have done\nso before now if I could ha' got out from the cellar. If there's\npolice-court business over this, you'll remember that I was the\none that stood your friend, and that I was Miss Alice's friend\ntoo.\n\n\"She was never happy at home, Miss Alice wasn't, from the time\nthat her father married again. She was slighted like and had no\nsay in anything, but it never really became bad for her until\nafter she met Mr. Fowler at a friend's house. As well as I could\nlearn, Miss Alice had rights of her own by will, but she was so\nquiet and patient, she was, that she never said a word about them\nbut just left everything in Mr. Rucastle's hands. He knew he was\nsafe with her; but when there was a chance of a husband coming\nforward, who would ask for all that the law would give him, then\nher father thought it time to put a stop on it. He wanted her to\nsign a paper, so that whether she married or not, he could use\nher money. When she wouldn't do it, he kept on worrying her until\nshe got brain-fever, and for six weeks was at death's door. Then\nshe got better at last, all worn to a shadow, and with her\nbeautiful hair cut off; but that didn't make no change in her\nyoung man, and he stuck to her as true as man could be.\"\n\n\"Ah,\" said Holmes, \"I think that what you have been good enough\nto tell us makes the matter fairly clear, and that I can deduce\nall that remains. Mr. Rucastle then, I presume, took to this\nsystem of imprisonment?\"\n\n\"Yes, sir.\"\n\n\"And brought Miss Hunter down from London in order to get rid of\nthe disagreeable persistence of Mr. Fowler.\"\n\n\"That was it, sir.\"\n\n\"But Mr. Fowler being a persevering man, as a good seaman should\nbe, blockaded the house, and having met you succeeded by certain\narguments, metallic or otherwise, in convincing you that your\ninterests were the same as his.\"\n\n\"Mr. Fowler was a very kind-spoken, free-handed gentleman,\" said\nMrs. Toller serenely.\n\n\"And in this way he managed that your good man should have no\nwant of drink, and that a ladder should be ready at the moment\nwhen your master had gone out.\"\n\n\"You have it, sir, just as it happened.\"\n\n\"I am sure we owe you an apology, Mrs. Toller,\" said Holmes, \"for\nyou have certainly cleared up everything which puzzled us. And\nhere comes the country surgeon and Mrs. Rucastle, so I think,\nWatson, that we had best escort Miss Hunter back to Winchester,\nas it seems to me that our locus standi now is rather a\nquestionable one.\"\n\nAnd thus was solved the mystery of the sinister house with the\ncopper beeches in front of the door. Mr. Rucastle survived, but\nwas always a broken man, kept alive solely through the care of\nhis devoted wife. They still live with their old servants, who\nprobably know so much of Rucastle's past life that he finds it\ndifficult to part from them. Mr. Fowler and Miss Rucastle were\nmarried, by special license, in Southampton the day after their\nflight, and he is now the holder of a government appointment in\nthe island of Mauritius. As to Miss Violet Hunter, my friend\nHolmes, rather to my disappointment, manifested no further\ninterest in her when once she had ceased to be the centre of one\nof his problems, and she is now the head of a private school at\nWalsall, where I believe that she has met with considerable success.\n\n\n\n\n\n\n\n\n\nEnd of the Project Gutenberg EBook of The Adventures of Sherlock Holmes, by \nArthur Conan Doyle\n\n*** END OF THIS PROJECT GUTENBERG EBOOK THE ADVENTURES OF SHERLOCK HOLMES ***\n\n***** This file should be named 1661-8.txt or 1661-8.zip *****\nThis and all associated files of various formats will be found in:\n        http://www.gutenberg.org/1/6/6/1661/\n\nProduced by an anonymous Project Gutenberg volunteer and Jose Menendez\n\nUpdated editions will replace the previous one--the old editions\nwill be renamed.\n\nCreating the works from public domain print editions means that no\none owns a United States copyright in these works, so the Foundation\n(and you!) can copy and distribute it in the United States without\npermission and without paying copyright royalties.  Special rules,\nset forth in the General Terms of Use part of this license, apply to\ncopying and distributing Project Gutenberg-tm electronic works to\nprotect the PROJECT GUTENBERG-tm concept and trademark.  Project\nGutenberg is a registered trademark, and may not be used if you\ncharge for the eBooks, unless you receive specific permission.  If you\ndo not charge anything for copies of this eBook, complying with the\nrules is very easy.  You may use this eBook for nearly any purpose\nsuch as creation of derivative works, reports, performances and\nresearch.  They may be modified and printed and given away--you may do\npractically ANYTHING with public domain eBooks.  Redistribution is\nsubject to the trademark license, especially commercial\nredistribution.\n\n\n\n*** START: FULL LICENSE ***\n\nTHE FULL PROJECT GUTENBERG LICENSE\nPLEASE READ THIS BEFORE YOU DISTRIBUTE OR USE THIS WORK\n\nTo protect the Project Gutenberg-tm mission of promoting the free\ndistribution of electronic works, by using or distributing this work\n(or any other work associated in any way with the phrase \"Project\nGutenberg\"), you agree to comply with all the terms of the Full Project\nGutenberg-tm License (available with this file or online at\nhttp://gutenberg.net/license).\n\n\nSection 1.  General Terms of Use and Redistributing Project Gutenberg-tm\nelectronic works\n\n1.A.  By reading or using any part of this Project Gutenberg-tm\nelectronic work, you indicate that you have read, understand, agree to\nand accept all the terms of this license and intellectual property\n(trademark/copyright) agreement.  If you do not agree to abide by all\nthe terms of this agreement, you must cease using and return or destroy\nall copies of Project Gutenberg-tm electronic works in your possession.\nIf you paid a fee for obtaining a copy of or access to a Project\nGutenberg-tm electronic work and you do not agree to be bound by the\nterms of this agreement, you may obtain a refund from the person or\nentity to whom you paid the fee as set forth in paragraph 1.E.8.\n\n1.B.  \"Project Gutenberg\" is a registered trademark.  It may only be\nused on or associated in any way with an electronic work by people who\nagree to be bound by the terms of this agreement.  There are a few\nthings that you can do with most Project Gutenberg-tm electronic works\neven without complying with the full terms of this agreement.  See\nparagraph 1.C below.  There are a lot of things you can do with Project\nGutenberg-tm electronic works if you follow the terms of this agreement\nand help preserve free future access to Project Gutenberg-tm electronic\nworks.  See paragraph 1.E below.\n\n1.C.  The Project Gutenberg Literary Archive Foundation (\"the Foundation\"\nor PGLAF), owns a compilation copyright in the collection of Project\nGutenberg-tm electronic works.  Nearly all the individual works in the\ncollection are in the public domain in the United States.  If an\nindividual work is in the public domain in the United States and you are\nlocated in the United States, we do not claim a right to prevent you from\ncopying, distributing, performing, displaying or creating derivative\nworks based on the work as long as all references to Project Gutenberg\nare removed.  Of course, we hope that you will support the Project\nGutenberg-tm mission of promoting free access to electronic works by\nfreely sharing Project Gutenberg-tm works in compliance with the terms of\nthis agreement for keeping the Project Gutenberg-tm name associated with\nthe work.  You can easily comply with the terms of this agreement by\nkeeping this work in the same format with its attached full Project\nGutenberg-tm License when you share it without charge with others.\n\n1.D.  The copyright laws of the place where you are located also govern\nwhat you can do with this work.  Copyright laws in most countries are in\na constant state of change.  If you are outside the United States, check\nthe laws of your country in addition to the terms of this agreement\nbefore downloading, copying, displaying, performing, distributing or\ncreating derivative works based on this work or any other Project\nGutenberg-tm work.  The Foundation makes no representations concerning\nthe copyright status of any work in any country outside the United\nStates.\n\n1.E.  Unless you have removed all references to Project Gutenberg:\n\n1.E.1.  The following sentence, with active links to, or other immediate\naccess to, the full Project Gutenberg-tm License must appear prominently\nwhenever any copy of a Project Gutenberg-tm work (any work on which the\nphrase \"Project Gutenberg\" appears, or with which the phrase \"Project\nGutenberg\" is associated) is accessed, displayed, performed, viewed,\ncopied or distributed:\n\nThis eBook is for the use of anyone anywhere at no cost and with\nalmost no restrictions whatsoever.  You may copy it, give it away or\nre-use it under the terms of the Project Gutenberg License included\nwith this eBook or online at www.gutenberg.net\n\n1.E.2.  If an individual Project Gutenberg-tm electronic work is derived\nfrom the public domain (does not contain a notice indicating that it is\nposted with permission of the copyright holder), the work can be copied\nand distributed to anyone in the United States without paying any fees\nor charges.  If you are redistributing or providing access to a work\nwith the phrase \"Project Gutenberg\" associated with or appearing on the\nwork, you must comply either with the requirements of paragraphs 1.E.1\nthrough 1.E.7 or obtain permission for the use of the work and the\nProject Gutenberg-tm trademark as set forth in paragraphs 1.E.8 or\n1.E.9.\n\n1.E.3.  If an individual Project Gutenberg-tm electronic work is posted\nwith the permission of the copyright holder, your use and distribution\nmust comply with both paragraphs 1.E.1 through 1.E.7 and any additional\nterms imposed by the copyright holder.  Additional terms will be linked\nto the Project Gutenberg-tm License for all works posted with the\npermission of the copyright holder found at the beginning of this work.\n\n1.E.4.  Do not unlink or detach or remove the full Project Gutenberg-tm\nLicense terms from this work, or any files containing a part of this\nwork or any other work associated with Project Gutenberg-tm.\n\n1.E.5.  Do not copy, display, perform, distribute or redistribute this\nelectronic work, or any part of this electronic work, without\nprominently displaying the sentence set forth in paragraph 1.E.1 with\nactive links or immediate access to the full terms of the Project\nGutenberg-tm License.\n\n1.E.6.  You may convert to and distribute this work in any binary,\ncompressed, marked up, nonproprietary or proprietary form, including any\nword processing or hypertext form.  However, if you provide access to or\ndistribute copies of a Project Gutenberg-tm work in a format other than\n\"Plain Vanilla ASCII\" or other format used in the official version\nposted on the official Project Gutenberg-tm web site (www.gutenberg.net),\nyou must, at no additional cost, fee or expense to the user, provide a\ncopy, a means of exporting a copy, or a means of obtaining a copy upon\nrequest, of the work in its original \"Plain Vanilla ASCII\" or other\nform.  Any alternate format must include the full Project Gutenberg-tm\nLicense as specified in paragraph 1.E.1.\n\n1.E.7.  Do not charge a fee for access to, viewing, displaying,\nperforming, copying or distributing any Project Gutenberg-tm works\nunless you comply with paragraph 1.E.8 or 1.E.9.\n\n1.E.8.  You may charge a reasonable fee for copies of or providing\naccess to or distributing Project Gutenberg-tm electronic works provided\nthat\n\n- You pay a royalty fee of 20% of the gross profits you derive from\n     the use of Project Gutenberg-tm works calculated using the method\n     you already use to calculate your applicable taxes.  The fee is\n     owed to the owner of the Project Gutenberg-tm trademark, but he\n     has agreed to donate royalties under this paragraph to the\n     Project Gutenberg Literary Archive Foundation.  Royalty payments\n     must be paid within 60 days following each date on which you\n     prepare (or are legally required to prepare) your periodic tax\n     returns.  Royalty payments should be clearly marked as such and\n     sent to the Project Gutenberg Literary Archive Foundation at the\n     address specified in Section 4, \"Information about donations to\n     the Project Gutenberg Literary Archive Foundation.\"\n\n- You provide a full refund of any money paid by a user who notifies\n     you in writing (or by e-mail) within 30 days of receipt that s/he\n     does not agree to the terms of the full Project Gutenberg-tm\n     License.  You must require such a user to return or\n     destroy all copies of the works possessed in a physical medium\n     and discontinue all use of and all access to other copies of\n     Project Gutenberg-tm works.\n\n- You provide, in accordance with paragraph 1.F.3, a full refund of any\n     money paid for a work or a replacement copy, if a defect in the\n     electronic work is discovered and reported to you within 90 days\n     of receipt of the work.\n\n- You comply with all other terms of this agreement for free\n     distribution of Project Gutenberg-tm works.\n\n1.E.9.  If you wish to charge a fee or distribute a Project Gutenberg-tm\nelectronic work or group of works on different terms than are set\nforth in this agreement, you must obtain permission in writing from\nboth the Project Gutenberg Literary Archive Foundation and Michael\nHart, the owner of the Project Gutenberg-tm trademark.  Contact the\nFoundation as set forth in Section 3 below.\n\n1.F.\n\n1.F.1.  Project Gutenberg volunteers and employees expend considerable\neffort to identify, do copyright research on, transcribe and proofread\npublic domain works in creating the Project Gutenberg-tm\ncollection.  Despite these efforts, Project Gutenberg-tm electronic\nworks, and the medium on which they may be stored, may contain\n\"Defects,\" such as, but not limited to, incomplete, inaccurate or\ncorrupt data, transcription errors, a copyright or other intellectual\nproperty infringement, a defective or damaged disk or other medium, a\ncomputer virus, or computer codes that damage or cannot be read by\nyour equipment.\n\n1.F.2.  LIMITED WARRANTY, DISCLAIMER OF DAMAGES - Except for the \"Right\nof Replacement or Refund\" described in paragraph 1.F.3, the Project\nGutenberg Literary Archive Foundation, the owner of the Project\nGutenberg-tm trademark, and any other party distributing a Project\nGutenberg-tm electronic work under this agreement, disclaim all\nliability to you for damages, costs and expenses, including legal\nfees.  YOU AGREE THAT YOU HAVE NO REMEDIES FOR NEGLIGENCE, STRICT\nLIABILITY, BREACH OF WARRANTY OR BREACH OF CONTRACT EXCEPT THOSE\nPROVIDED IN PARAGRAPH 1.F.3.  YOU AGREE THAT THE FOUNDATION, THE\nTRADEMARK OWNER, AND ANY DISTRIBUTOR UNDER THIS AGREEMENT WILL NOT BE\nLIABLE TO YOU FOR ACTUAL, DIRECT, INDIRECT, CONSEQUENTIAL, PUNITIVE OR\nINCIDENTAL DAMAGES EVEN IF YOU GIVE NOTICE OF THE POSSIBILITY OF SUCH\nDAMAGE.\n\n1.F.3.  LIMITED RIGHT OF REPLACEMENT OR REFUND - If you discover a\ndefect in this electronic work within 90 days of receiving it, you can\nreceive a refund of the money (if any) you paid for it by sending a\nwritten explanation to the person you received the work from.  If you\nreceived the work on a physical medium, you must return the medium with\nyour written explanation.  The person or entity that provided you with\nthe defective work may elect to provide a replacement copy in lieu of a\nrefund.  If you received the work electronically, the person or entity\nproviding it to you may choose to give you a second opportunity to\nreceive the work electronically in lieu of a refund.  If the second copy\nis also defective, you may demand a refund in writing without further\nopportunities to fix the problem.\n\n1.F.4.  Except for the limited right of replacement or refund set forth\nin paragraph 1.F.3, this work is provided to you 'AS-IS' WITH NO OTHER\nWARRANTIES OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\nWARRANTIES OF MERCHANTIBILITY OR FITNESS FOR ANY PURPOSE.\n\n1.F.5.  Some states do not allow disclaimers of certain implied\nwarranties or the exclusion or limitation of certain types of damages.\nIf any disclaimer or limitation set forth in this agreement violates the\nlaw of the state applicable to this agreement, the agreement shall be\ninterpreted to make the maximum disclaimer or limitation permitted by\nthe applicable state law.  The invalidity or unenforceability of any\nprovision of this agreement shall not void the remaining provisions.\n\n1.F.6.  INDEMNITY - You agree to indemnify and hold the Foundation, the\ntrademark owner, any agent or employee of the Foundation, anyone\nproviding copies of Project Gutenberg-tm electronic works in accordance\nwith this agreement, and any volunteers associated with the production,\npromotion and distribution of Project Gutenberg-tm electronic works,\nharmless from all liability, costs and expenses, including legal fees,\nthat arise directly or indirectly from any of the following which you do\nor cause to occur: (a) distribution of this or any Project Gutenberg-tm\nwork, (b) alteration, modification, or additions or deletions to any\nProject Gutenberg-tm work, and (c) any Defect you cause.\n\n\nSection  2.  Information about the Mission of Project Gutenberg-tm\n\nProject Gutenberg-tm is synonymous with the free distribution of\nelectronic works in formats readable by the widest variety of computers\nincluding obsolete, old, middle-aged and new computers.  It exists\nbecause of the efforts of hundreds of volunteers and donations from\npeople in all walks of life.\n\nVolunteers and financial support to provide volunteers with the\nassistance they need are critical to reaching Project Gutenberg-tm's\ngoals and ensuring that the Project Gutenberg-tm collection will\nremain freely available for generations to come.  In 2001, the Project\nGutenberg Literary Archive Foundation was created to provide a secure\nand permanent future for Project Gutenberg-tm and future generations.\nTo learn more about the Project Gutenberg Literary Archive Foundation\nand how your efforts and donations can help, see Sections 3 and 4\nand the Foundation web page at http://www.pglaf.org.\n\n\nSection 3.  Information about the Project Gutenberg Literary Archive\nFoundation\n\nThe Project Gutenberg Literary Archive Foundation is a non profit\n501(c)(3) educational corporation organized under the laws of the\nstate of Mississippi and granted tax exempt status by the Internal\nRevenue Service.  The Foundation's EIN or federal tax identification\nnumber is 64-6221541.  Its 501(c)(3) letter is posted at\nhttp://pglaf.org/fundraising.  Contributions to the Project Gutenberg\nLiterary Archive Foundation are tax deductible to the full extent\npermitted by U.S. federal laws and your state's laws.\n\nThe Foundation's principal office is located at 4557 Melan Dr. S.\nFairbanks, AK, 99712., but its volunteers and employees are scattered\nthroughout numerous locations.  Its business office is located at\n809 North 1500 West, Salt Lake City, UT 84116, (801) 596-1887, email\nbusiness@pglaf.org.  Email contact links and up to date contact\ninformation can be found at the Foundation's web site and official\npage at http://pglaf.org\n\nFor additional contact information:\n     Dr. Gregory B. Newby\n     Chief Executive and Director\n     gbnewby@pglaf.org\n\n\nSection 4.  Information about Donations to the Project Gutenberg\nLiterary Archive Foundation\n\nProject Gutenberg-tm depends upon and cannot survive without wide\nspread public support and donations to carry out its mission of\nincreasing the number of public domain and licensed works that can be\nfreely distributed in machine readable form accessible by the widest\narray of equipment including outdated equipment.  Many small donations\n($1 to $5,000) are particularly important to maintaining tax exempt\nstatus with the IRS.\n\nThe Foundation is committed to complying with the laws regulating\ncharities and charitable donations in all 50 states of the United\nStates.  Compliance requirements are not uniform and it takes a\nconsiderable effort, much paperwork and many fees to meet and keep up\nwith these requirements.  We do not solicit donations in locations\nwhere we have not received written confirmation of compliance.  To\nSEND DONATIONS or determine the status of compliance for any\nparticular state visit http://pglaf.org\n\nWhile we cannot and do not solicit contributions from states where we\nhave not met the solicitation requirements, we know of no prohibition\nagainst accepting unsolicited donations from donors in such states who\napproach us with offers to donate.\n\nInternational donations are gratefully accepted, but we cannot make\nany statements concerning tax treatment of donations received from\noutside the United States.  U.S. laws alone swamp our small staff.\n\nPlease check the Project Gutenberg Web pages for current donation\nmethods and addresses.  Donations are accepted in a number of other\nways including including checks, online payments and credit card\ndonations.  To donate, please visit: http://pglaf.org/donate\n\n\nSection 5.  General Information About Project Gutenberg-tm electronic\nworks.\n\nProfessor Michael S. Hart is the originator of the Project Gutenberg-tm\nconcept of a library of electronic works that could be freely shared\nwith anyone.  For thirty years, he produced and distributed Project\nGutenberg-tm eBooks with only a loose network of volunteer support.\n\n\nProject Gutenberg-tm eBooks are often created from several printed\neditions, all of which are confirmed as Public Domain in the U.S.\nunless a copyright notice is included.  Thus, we do not necessarily\nkeep eBooks in compliance with any particular paper edition.\n\n\nMost people start at our Web site which has the main PG search facility:\n\n     http://www.gutenberg.net\n\nThis Web site includes information about Project Gutenberg-tm,\nincluding how to make donations to the Project Gutenberg Literary\nArchive Foundation, how to help produce our new eBooks, and how to\nsubscribe to our email newsletter to hear about new eBooks.\n"
  },
  {
    "path": "packages/ipfs-http-response/test/fixtures/test-folder/pp.txt",
    "content": "PRIDE AND PREJUDICE\n\nBy Jane Austen\n\n\n\nChapter 1\n\n\nIt is a truth universally acknowledged, that a single man in possession\nof a good fortune, must be in want of a wife.\n\nHowever little known the feelings or views of such a man may be on his\nfirst entering a neighbourhood, this truth is so well fixed in the minds\nof the surrounding families, that he is considered the rightful property\nof some one or other of their daughters.\n\n\"My dear Mr. Bennet,\" said his lady to him one day, \"have you heard that\nNetherfield Park is let at last?\"\n\nMr. Bennet replied that he had not.\n\n\"But it is,\" returned she; \"for Mrs. Long has just been here, and she\ntold me all about it.\"\n\nMr. Bennet made no answer.\n\n\"Do you not want to know who has taken it?\" cried his wife impatiently.\n\n\"_You_ want to tell me, and I have no objection to hearing it.\"\n\nThis was invitation enough.\n\n\"Why, my dear, you must know, Mrs. Long says that Netherfield is taken\nby a young man of large fortune from the north of England; that he came\ndown on Monday in a chaise and four to see the place, and was so much\ndelighted with it, that he agreed with Mr. Morris immediately; that he\nis to take possession before Michaelmas, and some of his servants are to\nbe in the house by the end of next week.\"\n\n\"What is his name?\"\n\n\"Bingley.\"\n\n\"Is he married or single?\"\n\n\"Oh! Single, my dear, to be sure! A single man of large fortune; four or\nfive thousand a year. What a fine thing for our girls!\"\n\n\"How so? How can it affect them?\"\n\n\"My dear Mr. Bennet,\" replied his wife, \"how can you be so tiresome! You\nmust know that I am thinking of his marrying one of them.\"\n\n\"Is that his design in settling here?\"\n\n\"Design! Nonsense, how can you talk so! But it is very likely that he\n_may_ fall in love with one of them, and therefore you must visit him as\nsoon as he comes.\"\n\n\"I see no occasion for that. You and the girls may go, or you may send\nthem by themselves, which perhaps will be still better, for as you are\nas handsome as any of them, Mr. Bingley may like you the best of the\nparty.\"\n\n\"My dear, you flatter me. I certainly _have_ had my share of beauty, but\nI do not pretend to be anything extraordinary now. When a woman has five\ngrown-up daughters, she ought to give over thinking of her own beauty.\"\n\n\"In such cases, a woman has not often much beauty to think of.\"\n\n\"But, my dear, you must indeed go and see Mr. Bingley when he comes into\nthe neighbourhood.\"\n\n\"It is more than I engage for, I assure you.\"\n\n\"But consider your daughters. Only think what an establishment it would\nbe for one of them. Sir William and Lady Lucas are determined to\ngo, merely on that account, for in general, you know, they visit no\nnewcomers. Indeed you must go, for it will be impossible for _us_ to\nvisit him if you do not.\"\n\n\"You are over-scrupulous, surely. I dare say Mr. Bingley will be very\nglad to see you; and I will send a few lines by you to assure him of my\nhearty consent to his marrying whichever he chooses of the girls; though\nI must throw in a good word for my little Lizzy.\"\n\n\"I desire you will do no such thing. Lizzy is not a bit better than the\nothers; and I am sure she is not half so handsome as Jane, nor half so\ngood-humoured as Lydia. But you are always giving _her_ the preference.\"\n\n\"They have none of them much to recommend them,\" replied he; \"they are\nall silly and ignorant like other girls; but Lizzy has something more of\nquickness than her sisters.\"\n\n\"Mr. Bennet, how _can_ you abuse your own children in such a way? You\ntake delight in vexing me. You have no compassion for my poor nerves.\"\n\n\"You mistake me, my dear. I have a high respect for your nerves. They\nare my old friends. I have heard you mention them with consideration\nthese last twenty years at least.\"\n\n\"Ah, you do not know what I suffer.\"\n\n\"But I hope you will get over it, and live to see many young men of four\nthousand a year come into the neighbourhood.\"\n\n\"It will be no use to us, if twenty such should come, since you will not\nvisit them.\"\n\n\"Depend upon it, my dear, that when there are twenty, I will visit them\nall.\"\n\nMr. Bennet was so odd a mixture of quick parts, sarcastic humour,\nreserve, and caprice, that the experience of three-and-twenty years had\nbeen insufficient to make his wife understand his character. _Her_ mind\nwas less difficult to develop. She was a woman of mean understanding,\nlittle information, and uncertain temper. When she was discontented,\nshe fancied herself nervous. The business of her life was to get her\ndaughters married; its solace was visiting and news.\n"
  },
  {
    "path": "packages/ipfs-http-response/test/fixtures/test-mime-types/index.html",
    "content": "<html>\n  <body>\n    Website\n  </body>\n</html>\n"
  },
  {
    "path": "packages/ipfs-http-response/test/fixtures/test-mime-types/pp.txt",
    "content": "PRIDE AND PREJUDICE\n\nBy Jane Austen\n\n\n\nChapter 1\n\n\nIt is a truth universally acknowledged, that a single man in possession\nof a good fortune, must be in want of a wife.\n\nHowever little known the feelings or views of such a man may be on his\nfirst entering a neighbourhood, this truth is so well fixed in the minds\nof the surrounding families, that he is considered the rightful property\nof some one or other of their daughters.\n\n\"My dear Mr. Bennet,\" said his lady to him one day, \"have you heard that\nNetherfield Park is let at last?\"\n\nMr. Bennet replied that he had not.\n\n\"But it is,\" returned she; \"for Mrs. Long has just been here, and she\ntold me all about it.\"\n\nMr. Bennet made no answer.\n\n\"Do you not want to know who has taken it?\" cried his wife impatiently.\n\n\"_You_ want to tell me, and I have no objection to hearing it.\"\n\nThis was invitation enough.\n\n\"Why, my dear, you must know, Mrs. Long says that Netherfield is taken\nby a young man of large fortune from the north of England; that he came\ndown on Monday in a chaise and four to see the place, and was so much\ndelighted with it, that he agreed with Mr. Morris immediately; that he\nis to take possession before Michaelmas, and some of his servants are to\nbe in the house by the end of next week.\"\n\n\"What is his name?\"\n\n\"Bingley.\"\n\n\"Is he married or single?\"\n\n\"Oh! Single, my dear, to be sure! A single man of large fortune; four or\nfive thousand a year. What a fine thing for our girls!\"\n\n\"How so? How can it affect them?\"\n\n\"My dear Mr. Bennet,\" replied his wife, \"how can you be so tiresome! You\nmust know that I am thinking of his marrying one of them.\"\n\n\"Is that his design in settling here?\"\n\n\"Design! Nonsense, how can you talk so! But it is very likely that he\n_may_ fall in love with one of them, and therefore you must visit him as\nsoon as he comes.\"\n\n\"I see no occasion for that. You and the girls may go, or you may send\nthem by themselves, which perhaps will be still better, for as you are\nas handsome as any of them, Mr. Bingley may like you the best of the\nparty.\"\n\n\"My dear, you flatter me. I certainly _have_ had my share of beauty, but\nI do not pretend to be anything extraordinary now. When a woman has five\ngrown-up daughters, she ought to give over thinking of her own beauty.\"\n\n\"In such cases, a woman has not often much beauty to think of.\"\n\n\"But, my dear, you must indeed go and see Mr. Bingley when he comes into\nthe neighbourhood.\"\n\n\"It is more than I engage for, I assure you.\"\n\n\"But consider your daughters. Only think what an establishment it would\nbe for one of them. Sir William and Lady Lucas are determined to\ngo, merely on that account, for in general, you know, they visit no\nnewcomers. Indeed you must go, for it will be impossible for _us_ to\nvisit him if you do not.\"\n\n\"You are over-scrupulous, surely. I dare say Mr. Bingley will be very\nglad to see you; and I will send a few lines by you to assure him of my\nhearty consent to his marrying whichever he chooses of the girls; though\nI must throw in a good word for my little Lizzy.\"\n\n\"I desire you will do no such thing. Lizzy is not a bit better than the\nothers; and I am sure she is not half so handsome as Jane, nor half so\ngood-humoured as Lydia. But you are always giving _her_ the preference.\"\n\n\"They have none of them much to recommend them,\" replied he; \"they are\nall silly and ignorant like other girls; but Lizzy has something more of\nquickness than her sisters.\"\n\n\"Mr. Bennet, how _can_ you abuse your own children in such a way? You\ntake delight in vexing me. You have no compassion for my poor nerves.\"\n\n\"You mistake me, my dear. I have a high respect for your nerves. They\nare my old friends. I have heard you mention them with consideration\nthese last twenty years at least.\"\n\n\"Ah, you do not know what I suffer.\"\n\n\"But I hope you will get over it, and live to see many young men of four\nthousand a year come into the neighbourhood.\"\n\n\"It will be no use to us, if twenty such should come, since you will not\nvisit them.\"\n\n\"Depend upon it, my dear, that when there are twenty, I will visit them\nall.\"\n\nMr. Bennet was so odd a mixture of quick parts, sarcastic humour,\nreserve, and caprice, that the experience of three-and-twenty years had\nbeen insufficient to make his wife understand his character. _Her_ mind\nwas less difficult to develop. She was a woman of mean understanding,\nlittle information, and uncertain temper. When she was discontented,\nshe fancied herself nervous. The business of her life was to get her\ndaughters married; its solace was visiting and news.\n"
  },
  {
    "path": "packages/ipfs-http-response/test/fixtures/test-site/holmes.txt",
    "content": "Project Gutenberg's The Adventures of Sherlock Holmes, by Arthur Conan Doyle\n\nThis eBook is for the use of anyone anywhere at no cost and with\nalmost no restrictions whatsoever.  You may copy it, give it away or\nre-use it under the terms of the Project Gutenberg License included\nwith this eBook or online at www.gutenberg.net\n\n\nTitle: The Adventures of Sherlock Holmes\n\nAuthor: Arthur Conan Doyle\n\nPosting Date: April 18, 2011 [EBook #1661]\nFirst Posted: November 29, 2002\n\nLanguage: English\n\n\n*** START OF THIS PROJECT GUTENBERG EBOOK THE ADVENTURES OF SHERLOCK HOLMES ***\n\n\n\n\nProduced by an anonymous Project Gutenberg volunteer and Jose Menendez\n\n\n\n\n\n\n\n\n\nTHE ADVENTURES OF SHERLOCK HOLMES\n\nby\n\nSIR ARTHUR CONAN DOYLE\n\n\n\n   I. A Scandal in Bohemia\n  II. The Red-headed League\n III. A Case of Identity\n  IV. The Boscombe Valley Mystery\n   V. The Five Orange Pips\n  VI. The Man with the Twisted Lip\n VII. The Adventure of the Blue Carbuncle\nVIII. The Adventure of the Speckled Band\n  IX. The Adventure of the Engineer's Thumb\n   X. The Adventure of the Noble Bachelor\n  XI. The Adventure of the Beryl Coronet\n XII. The Adventure of the Copper Beeches\n\n\n\n\nADVENTURE I. A SCANDAL IN BOHEMIA\n\nI.\n\nTo Sherlock Holmes she is always THE woman. I have seldom heard\nhim mention her under any other name. In his eyes she eclipses\nand predominates the whole of her sex. It was not that he felt\nany emotion akin to love for Irene Adler. All emotions, and that\none particularly, were abhorrent to his cold, precise but\nadmirably balanced mind. He was, I take it, the most perfect\nreasoning and observing machine that the world has seen, but as a\nlover he would have placed himself in a false position. He never\nspoke of the softer passions, save with a gibe and a sneer. They\nwere admirable things for the observer--excellent for drawing the\nveil from men's motives and actions. But for the trained reasoner\nto admit such intrusions into his own delicate and finely\nadjusted temperament was to introduce a distracting factor which\nmight throw a doubt upon all his mental results. Grit in a\nsensitive instrument, or a crack in one of his own high-power\nlenses, would not be more disturbing than a strong emotion in a\nnature such as his. And yet there was but one woman to him, and\nthat woman was the late Irene Adler, of dubious and questionable\nmemory.\n\nI had seen little of Holmes lately. My marriage had drifted us\naway from each other. My own complete happiness, and the\nhome-centred interests which rise up around the man who first\nfinds himself master of his own establishment, were sufficient to\nabsorb all my attention, while Holmes, who loathed every form of\nsociety with his whole Bohemian soul, remained in our lodgings in\nBaker Street, buried among his old books, and alternating from\nweek to week between cocaine and ambition, the drowsiness of the\ndrug, and the fierce energy of his own keen nature. He was still,\nas ever, deeply attracted by the study of crime, and occupied his\nimmense faculties and extraordinary powers of observation in\nfollowing out those clues, and clearing up those mysteries which\nhad been abandoned as hopeless by the official police. From time\nto time I heard some vague account of his doings: of his summons\nto Odessa in the case of the Trepoff murder, of his clearing up\nof the singular tragedy of the Atkinson brothers at Trincomalee,\nand finally of the mission which he had accomplished so\ndelicately and successfully for the reigning family of Holland.\nBeyond these signs of his activity, however, which I merely\nshared with all the readers of the daily press, I knew little of\nmy former friend and companion.\n\nOne night--it was on the twentieth of March, 1888--I was\nreturning from a journey to a patient (for I had now returned to\ncivil practice), when my way led me through Baker Street. As I\npassed the well-remembered door, which must always be associated\nin my mind with my wooing, and with the dark incidents of the\nStudy in Scarlet, I was seized with a keen desire to see Holmes\nagain, and to know how he was employing his extraordinary powers.\nHis rooms were brilliantly lit, and, even as I looked up, I saw\nhis tall, spare figure pass twice in a dark silhouette against\nthe blind. He was pacing the room swiftly, eagerly, with his head\nsunk upon his chest and his hands clasped behind him. To me, who\nknew his every mood and habit, his attitude and manner told their\nown story. He was at work again. He had risen out of his\ndrug-created dreams and was hot upon the scent of some new\nproblem. I rang the bell and was shown up to the chamber which\nhad formerly been in part my own.\n\nHis manner was not effusive. It seldom was; but he was glad, I\nthink, to see me. With hardly a word spoken, but with a kindly\neye, he waved me to an armchair, threw across his case of cigars,\nand indicated a spirit case and a gasogene in the corner. Then he\nstood before the fire and looked me over in his singular\nintrospective fashion.\n\n\"Wedlock suits you,\" he remarked. \"I think, Watson, that you have\nput on seven and a half pounds since I saw you.\"\n\n\"Seven!\" I answered.\n\n\"Indeed, I should have thought a little more. Just a trifle more,\nI fancy, Watson. And in practice again, I observe. You did not\ntell me that you intended to go into harness.\"\n\n\"Then, how do you know?\"\n\n\"I see it, I deduce it. How do I know that you have been getting\nyourself very wet lately, and that you have a most clumsy and\ncareless servant girl?\"\n\n\"My dear Holmes,\" said I, \"this is too much. You would certainly\nhave been burned, had you lived a few centuries ago. It is true\nthat I had a country walk on Thursday and came home in a dreadful\nmess, but as I have changed my clothes I can't imagine how you\ndeduce it. As to Mary Jane, she is incorrigible, and my wife has\ngiven her notice, but there, again, I fail to see how you work it\nout.\"\n\nHe chuckled to himself and rubbed his long, nervous hands\ntogether.\n\n\"It is simplicity itself,\" said he; \"my eyes tell me that on the\ninside of your left shoe, just where the firelight strikes it,\nthe leather is scored by six almost parallel cuts. Obviously they\nhave been caused by someone who has very carelessly scraped round\nthe edges of the sole in order to remove crusted mud from it.\nHence, you see, my double deduction that you had been out in vile\nweather, and that you had a particularly malignant boot-slitting\nspecimen of the London slavey. As to your practice, if a\ngentleman walks into my rooms smelling of iodoform, with a black\nmark of nitrate of silver upon his right forefinger, and a bulge\non the right side of his top-hat to show where he has secreted\nhis stethoscope, I must be dull, indeed, if I do not pronounce\nhim to be an active member of the medical profession.\"\n\nI could not help laughing at the ease with which he explained his\nprocess of deduction. \"When I hear you give your reasons,\" I\nremarked, \"the thing always appears to me to be so ridiculously\nsimple that I could easily do it myself, though at each\nsuccessive instance of your reasoning I am baffled until you\nexplain your process. And yet I believe that my eyes are as good\nas yours.\"\n\n\"Quite so,\" he answered, lighting a cigarette, and throwing\nhimself down into an armchair. \"You see, but you do not observe.\nThe distinction is clear. For example, you have frequently seen\nthe steps which lead up from the hall to this room.\"\n\n\"Frequently.\"\n\n\"How often?\"\n\n\"Well, some hundreds of times.\"\n\n\"Then how many are there?\"\n\n\"How many? I don't know.\"\n\n\"Quite so! You have not observed. And yet you have seen. That is\njust my point. Now, I know that there are seventeen steps,\nbecause I have both seen and observed. By-the-way, since you are\ninterested in these little problems, and since you are good\nenough to chronicle one or two of my trifling experiences, you\nmay be interested in this.\" He threw over a sheet of thick,\npink-tinted note-paper which had been lying open upon the table.\n\"It came by the last post,\" said he. \"Read it aloud.\"\n\nThe note was undated, and without either signature or address.\n\n\"There will call upon you to-night, at a quarter to eight\no'clock,\" it said, \"a gentleman who desires to consult you upon a\nmatter of the very deepest moment. Your recent services to one of\nthe royal houses of Europe have shown that you are one who may\nsafely be trusted with matters which are of an importance which\ncan hardly be exaggerated. This account of you we have from all\nquarters received. Be in your chamber then at that hour, and do\nnot take it amiss if your visitor wear a mask.\"\n\n\"This is indeed a mystery,\" I remarked. \"What do you imagine that\nit means?\"\n\n\"I have no data yet. It is a capital mistake to theorize before\none has data. Insensibly one begins to twist facts to suit\ntheories, instead of theories to suit facts. But the note itself.\nWhat do you deduce from it?\"\n\nI carefully examined the writing, and the paper upon which it was\nwritten.\n\n\"The man who wrote it was presumably well to do,\" I remarked,\nendeavouring to imitate my companion's processes. \"Such paper\ncould not be bought under half a crown a packet. It is peculiarly\nstrong and stiff.\"\n\n\"Peculiar--that is the very word,\" said Holmes. \"It is not an\nEnglish paper at all. Hold it up to the light.\"\n\nI did so, and saw a large \"E\" with a small \"g,\" a \"P,\" and a\nlarge \"G\" with a small \"t\" woven into the texture of the paper.\n\n\"What do you make of that?\" asked Holmes.\n\n\"The name of the maker, no doubt; or his monogram, rather.\"\n\n\"Not at all. The 'G' with the small 't' stands for\n'Gesellschaft,' which is the German for 'Company.' It is a\ncustomary contraction like our 'Co.' 'P,' of course, stands for\n'Papier.' Now for the 'Eg.' Let us glance at our Continental\nGazetteer.\" He took down a heavy brown volume from his shelves.\n\"Eglow, Eglonitz--here we are, Egria. It is in a German-speaking\ncountry--in Bohemia, not far from Carlsbad. 'Remarkable as being\nthe scene of the death of Wallenstein, and for its numerous\nglass-factories and paper-mills.' Ha, ha, my boy, what do you\nmake of that?\" His eyes sparkled, and he sent up a great blue\ntriumphant cloud from his cigarette.\n\n\"The paper was made in Bohemia,\" I said.\n\n\"Precisely. And the man who wrote the note is a German. Do you\nnote the peculiar construction of the sentence--'This account of\nyou we have from all quarters received.' A Frenchman or Russian\ncould not have written that. It is the German who is so\nuncourteous to his verbs. It only remains, therefore, to discover\nwhat is wanted by this German who writes upon Bohemian paper and\nprefers wearing a mask to showing his face. And here he comes, if\nI am not mistaken, to resolve all our doubts.\"\n\nAs he spoke there was the sharp sound of horses' hoofs and\ngrating wheels against the curb, followed by a sharp pull at the\nbell. Holmes whistled.\n\n\"A pair, by the sound,\" said he. \"Yes,\" he continued, glancing\nout of the window. \"A nice little brougham and a pair of\nbeauties. A hundred and fifty guineas apiece. There's money in\nthis case, Watson, if there is nothing else.\"\n\n\"I think that I had better go, Holmes.\"\n\n\"Not a bit, Doctor. Stay where you are. I am lost without my\nBoswell. And this promises to be interesting. It would be a pity\nto miss it.\"\n\n\"But your client--\"\n\n\"Never mind him. I may want your help, and so may he. Here he\ncomes. Sit down in that armchair, Doctor, and give us your best\nattention.\"\n\nA slow and heavy step, which had been heard upon the stairs and\nin the passage, paused immediately outside the door. Then there\nwas a loud and authoritative tap.\n\n\"Come in!\" said Holmes.\n\nA man entered who could hardly have been less than six feet six\ninches in height, with the chest and limbs of a Hercules. His\ndress was rich with a richness which would, in England, be looked\nupon as akin to bad taste. Heavy bands of astrakhan were slashed\nacross the sleeves and fronts of his double-breasted coat, while\nthe deep blue cloak which was thrown over his shoulders was lined\nwith flame-coloured silk and secured at the neck with a brooch\nwhich consisted of a single flaming beryl. Boots which extended\nhalfway up his calves, and which were trimmed at the tops with\nrich brown fur, completed the impression of barbaric opulence\nwhich was suggested by his whole appearance. He carried a\nbroad-brimmed hat in his hand, while he wore across the upper\npart of his face, extending down past the cheekbones, a black\nvizard mask, which he had apparently adjusted that very moment,\nfor his hand was still raised to it as he entered. From the lower\npart of the face he appeared to be a man of strong character,\nwith a thick, hanging lip, and a long, straight chin suggestive\nof resolution pushed to the length of obstinacy.\n\n\"You had my note?\" he asked with a deep harsh voice and a\nstrongly marked German accent. \"I told you that I would call.\" He\nlooked from one to the other of us, as if uncertain which to\naddress.\n\n\"Pray take a seat,\" said Holmes. \"This is my friend and\ncolleague, Dr. Watson, who is occasionally good enough to help me\nin my cases. Whom have I the honour to address?\"\n\n\"You may address me as the Count Von Kramm, a Bohemian nobleman.\nI understand that this gentleman, your friend, is a man of honour\nand discretion, whom I may trust with a matter of the most\nextreme importance. If not, I should much prefer to communicate\nwith you alone.\"\n\nI rose to go, but Holmes caught me by the wrist and pushed me\nback into my chair. \"It is both, or none,\" said he. \"You may say\nbefore this gentleman anything which you may say to me.\"\n\nThe Count shrugged his broad shoulders. \"Then I must begin,\" said\nhe, \"by binding you both to absolute secrecy for two years; at\nthe end of that time the matter will be of no importance. At\npresent it is not too much to say that it is of such weight it\nmay have an influence upon European history.\"\n\n\"I promise,\" said Holmes.\n\n\"And I.\"\n\n\"You will excuse this mask,\" continued our strange visitor. \"The\naugust person who employs me wishes his agent to be unknown to\nyou, and I may confess at once that the title by which I have\njust called myself is not exactly my own.\"\n\n\"I was aware of it,\" said Holmes dryly.\n\n\"The circumstances are of great delicacy, and every precaution\nhas to be taken to quench what might grow to be an immense\nscandal and seriously compromise one of the reigning families of\nEurope. To speak plainly, the matter implicates the great House\nof Ormstein, hereditary kings of Bohemia.\"\n\n\"I was also aware of that,\" murmured Holmes, settling himself\ndown in his armchair and closing his eyes.\n\nOur visitor glanced with some apparent surprise at the languid,\nlounging figure of the man who had been no doubt depicted to him\nas the most incisive reasoner and most energetic agent in Europe.\nHolmes slowly reopened his eyes and looked impatiently at his\ngigantic client.\n\n\"If your Majesty would condescend to state your case,\" he\nremarked, \"I should be better able to advise you.\"\n\nThe man sprang from his chair and paced up and down the room in\nuncontrollable agitation. Then, with a gesture of desperation, he\ntore the mask from his face and hurled it upon the ground. \"You\nare right,\" he cried; \"I am the King. Why should I attempt to\nconceal it?\"\n\n\"Why, indeed?\" murmured Holmes. \"Your Majesty had not spoken\nbefore I was aware that I was addressing Wilhelm Gottsreich\nSigismond von Ormstein, Grand Duke of Cassel-Felstein, and\nhereditary King of Bohemia.\"\n\n\"But you can understand,\" said our strange visitor, sitting down\nonce more and passing his hand over his high white forehead, \"you\ncan understand that I am not accustomed to doing such business in\nmy own person. Yet the matter was so delicate that I could not\nconfide it to an agent without putting myself in his power. I\nhave come incognito from Prague for the purpose of consulting\nyou.\"\n\n\"Then, pray consult,\" said Holmes, shutting his eyes once more.\n\n\"The facts are briefly these: Some five years ago, during a\nlengthy visit to Warsaw, I made the acquaintance of the well-known\nadventuress, Irene Adler. The name is no doubt familiar to you.\"\n\n\"Kindly look her up in my index, Doctor,\" murmured Holmes without\nopening his eyes. For many years he had adopted a system of\ndocketing all paragraphs concerning men and things, so that it\nwas difficult to name a subject or a person on which he could not\nat once furnish information. In this case I found her biography\nsandwiched in between that of a Hebrew rabbi and that of a\nstaff-commander who had written a monograph upon the deep-sea\nfishes.\n\n\"Let me see!\" said Holmes. \"Hum! Born in New Jersey in the year\n1858. Contralto--hum! La Scala, hum! Prima donna Imperial Opera\nof Warsaw--yes! Retired from operatic stage--ha! Living in\nLondon--quite so! Your Majesty, as I understand, became entangled\nwith this young person, wrote her some compromising letters, and\nis now desirous of getting those letters back.\"\n\n\"Precisely so. But how--\"\n\n\"Was there a secret marriage?\"\n\n\"None.\"\n\n\"No legal papers or certificates?\"\n\n\"None.\"\n\n\"Then I fail to follow your Majesty. If this young person should\nproduce her letters for blackmailing or other purposes, how is\nshe to prove their authenticity?\"\n\n\"There is the writing.\"\n\n\"Pooh, pooh! Forgery.\"\n\n\"My private note-paper.\"\n\n\"Stolen.\"\n\n\"My own seal.\"\n\n\"Imitated.\"\n\n\"My photograph.\"\n\n\"Bought.\"\n\n\"We were both in the photograph.\"\n\n\"Oh, dear! That is very bad! Your Majesty has indeed committed an\nindiscretion.\"\n\n\"I was mad--insane.\"\n\n\"You have compromised yourself seriously.\"\n\n\"I was only Crown Prince then. I was young. I am but thirty now.\"\n\n\"It must be recovered.\"\n\n\"We have tried and failed.\"\n\n\"Your Majesty must pay. It must be bought.\"\n\n\"She will not sell.\"\n\n\"Stolen, then.\"\n\n\"Five attempts have been made. Twice burglars in my pay ransacked\nher house. Once we diverted her luggage when she travelled. Twice\nshe has been waylaid. There has been no result.\"\n\n\"No sign of it?\"\n\n\"Absolutely none.\"\n\nHolmes laughed. \"It is quite a pretty little problem,\" said he.\n\n\"But a very serious one to me,\" returned the King reproachfully.\n\n\"Very, indeed. And what does she propose to do with the\nphotograph?\"\n\n\"To ruin me.\"\n\n\"But how?\"\n\n\"I am about to be married.\"\n\n\"So I have heard.\"\n\n\"To Clotilde Lothman von Saxe-Meningen, second daughter of the\nKing of Scandinavia. You may know the strict principles of her\nfamily. She is herself the very soul of delicacy. A shadow of a\ndoubt as to my conduct would bring the matter to an end.\"\n\n\"And Irene Adler?\"\n\n\"Threatens to send them the photograph. And she will do it. I\nknow that she will do it. You do not know her, but she has a soul\nof steel. She has the face of the most beautiful of women, and\nthe mind of the most resolute of men. Rather than I should marry\nanother woman, there are no lengths to which she would not\ngo--none.\"\n\n\"You are sure that she has not sent it yet?\"\n\n\"I am sure.\"\n\n\"And why?\"\n\n\"Because she has said that she would send it on the day when the\nbetrothal was publicly proclaimed. That will be next Monday.\"\n\n\"Oh, then we have three days yet,\" said Holmes with a yawn. \"That\nis very fortunate, as I have one or two matters of importance to\nlook into just at present. Your Majesty will, of course, stay in\nLondon for the present?\"\n\n\"Certainly. You will find me at the Langham under the name of the\nCount Von Kramm.\"\n\n\"Then I shall drop you a line to let you know how we progress.\"\n\n\"Pray do so. I shall be all anxiety.\"\n\n\"Then, as to money?\"\n\n\"You have carte blanche.\"\n\n\"Absolutely?\"\n\n\"I tell you that I would give one of the provinces of my kingdom\nto have that photograph.\"\n\n\"And for present expenses?\"\n\nThe King took a heavy chamois leather bag from under his cloak\nand laid it on the table.\n\n\"There are three hundred pounds in gold and seven hundred in\nnotes,\" he said.\n\nHolmes scribbled a receipt upon a sheet of his note-book and\nhanded it to him.\n\n\"And Mademoiselle's address?\" he asked.\n\n\"Is Briony Lodge, Serpentine Avenue, St. John's Wood.\"\n\nHolmes took a note of it. \"One other question,\" said he. \"Was the\nphotograph a cabinet?\"\n\n\"It was.\"\n\n\"Then, good-night, your Majesty, and I trust that we shall soon\nhave some good news for you. And good-night, Watson,\" he added,\nas the wheels of the royal brougham rolled down the street. \"If\nyou will be good enough to call to-morrow afternoon at three\no'clock I should like to chat this little matter over with you.\"\n\n\nII.\n\nAt three o'clock precisely I was at Baker Street, but Holmes had\nnot yet returned. The landlady informed me that he had left the\nhouse shortly after eight o'clock in the morning. I sat down\nbeside the fire, however, with the intention of awaiting him,\nhowever long he might be. I was already deeply interested in his\ninquiry, for, though it was surrounded by none of the grim and\nstrange features which were associated with the two crimes which\nI have already recorded, still, the nature of the case and the\nexalted station of his client gave it a character of its own.\nIndeed, apart from the nature of the investigation which my\nfriend had on hand, there was something in his masterly grasp of\na situation, and his keen, incisive reasoning, which made it a\npleasure to me to study his system of work, and to follow the\nquick, subtle methods by which he disentangled the most\ninextricable mysteries. So accustomed was I to his invariable\nsuccess that the very possibility of his failing had ceased to\nenter into my head.\n\nIt was close upon four before the door opened, and a\ndrunken-looking groom, ill-kempt and side-whiskered, with an\ninflamed face and disreputable clothes, walked into the room.\nAccustomed as I was to my friend's amazing powers in the use of\ndisguises, I had to look three times before I was certain that it\nwas indeed he. With a nod he vanished into the bedroom, whence he\nemerged in five minutes tweed-suited and respectable, as of old.\nPutting his hands into his pockets, he stretched out his legs in\nfront of the fire and laughed heartily for some minutes.\n\n\"Well, really!\" he cried, and then he choked and laughed again\nuntil he was obliged to lie back, limp and helpless, in the\nchair.\n\n\"What is it?\"\n\n\"It's quite too funny. I am sure you could never guess how I\nemployed my morning, or what I ended by doing.\"\n\n\"I can't imagine. I suppose that you have been watching the\nhabits, and perhaps the house, of Miss Irene Adler.\"\n\n\"Quite so; but the sequel was rather unusual. I will tell you,\nhowever. I left the house a little after eight o'clock this\nmorning in the character of a groom out of work. There is a\nwonderful sympathy and freemasonry among horsey men. Be one of\nthem, and you will know all that there is to know. I soon found\nBriony Lodge. It is a bijou villa, with a garden at the back, but\nbuilt out in front right up to the road, two stories. Chubb lock\nto the door. Large sitting-room on the right side, well\nfurnished, with long windows almost to the floor, and those\npreposterous English window fasteners which a child could open.\nBehind there was nothing remarkable, save that the passage window\ncould be reached from the top of the coach-house. I walked round\nit and examined it closely from every point of view, but without\nnoting anything else of interest.\n\n\"I then lounged down the street and found, as I expected, that\nthere was a mews in a lane which runs down by one wall of the\ngarden. I lent the ostlers a hand in rubbing down their horses,\nand received in exchange twopence, a glass of half and half, two\nfills of shag tobacco, and as much information as I could desire\nabout Miss Adler, to say nothing of half a dozen other people in\nthe neighbourhood in whom I was not in the least interested, but\nwhose biographies I was compelled to listen to.\"\n\n\"And what of Irene Adler?\" I asked.\n\n\"Oh, she has turned all the men's heads down in that part. She is\nthe daintiest thing under a bonnet on this planet. So say the\nSerpentine-mews, to a man. She lives quietly, sings at concerts,\ndrives out at five every day, and returns at seven sharp for\ndinner. Seldom goes out at other times, except when she sings.\nHas only one male visitor, but a good deal of him. He is dark,\nhandsome, and dashing, never calls less than once a day, and\noften twice. He is a Mr. Godfrey Norton, of the Inner Temple. See\nthe advantages of a cabman as a confidant. They had driven him\nhome a dozen times from Serpentine-mews, and knew all about him.\nWhen I had listened to all they had to tell, I began to walk up\nand down near Briony Lodge once more, and to think over my plan\nof campaign.\n\n\"This Godfrey Norton was evidently an important factor in the\nmatter. He was a lawyer. That sounded ominous. What was the\nrelation between them, and what the object of his repeated\nvisits? Was she his client, his friend, or his mistress? If the\nformer, she had probably transferred the photograph to his\nkeeping. If the latter, it was less likely. On the issue of this\nquestion depended whether I should continue my work at Briony\nLodge, or turn my attention to the gentleman's chambers in the\nTemple. It was a delicate point, and it widened the field of my\ninquiry. I fear that I bore you with these details, but I have to\nlet you see my little difficulties, if you are to understand the\nsituation.\"\n\n\"I am following you closely,\" I answered.\n\n\"I was still balancing the matter in my mind when a hansom cab\ndrove up to Briony Lodge, and a gentleman sprang out. He was a\nremarkably handsome man, dark, aquiline, and moustached--evidently\nthe man of whom I had heard. He appeared to be in a\ngreat hurry, shouted to the cabman to wait, and brushed past the\nmaid who opened the door with the air of a man who was thoroughly\nat home.\n\n\"He was in the house about half an hour, and I could catch\nglimpses of him in the windows of the sitting-room, pacing up and\ndown, talking excitedly, and waving his arms. Of her I could see\nnothing. Presently he emerged, looking even more flurried than\nbefore. As he stepped up to the cab, he pulled a gold watch from\nhis pocket and looked at it earnestly, 'Drive like the devil,' he\nshouted, 'first to Gross & Hankey's in Regent Street, and then to\nthe Church of St. Monica in the Edgeware Road. Half a guinea if\nyou do it in twenty minutes!'\n\n\"Away they went, and I was just wondering whether I should not do\nwell to follow them when up the lane came a neat little landau,\nthe coachman with his coat only half-buttoned, and his tie under\nhis ear, while all the tags of his harness were sticking out of\nthe buckles. It hadn't pulled up before she shot out of the hall\ndoor and into it. I only caught a glimpse of her at the moment,\nbut she was a lovely woman, with a face that a man might die for.\n\n\"'The Church of St. Monica, John,' she cried, 'and half a\nsovereign if you reach it in twenty minutes.'\n\n\"This was quite too good to lose, Watson. I was just balancing\nwhether I should run for it, or whether I should perch behind her\nlandau when a cab came through the street. The driver looked\ntwice at such a shabby fare, but I jumped in before he could\nobject. 'The Church of St. Monica,' said I, 'and half a sovereign\nif you reach it in twenty minutes.' It was twenty-five minutes to\ntwelve, and of course it was clear enough what was in the wind.\n\n\"My cabby drove fast. I don't think I ever drove faster, but the\nothers were there before us. The cab and the landau with their\nsteaming horses were in front of the door when I arrived. I paid\nthe man and hurried into the church. There was not a soul there\nsave the two whom I had followed and a surpliced clergyman, who\nseemed to be expostulating with them. They were all three\nstanding in a knot in front of the altar. I lounged up the side\naisle like any other idler who has dropped into a church.\nSuddenly, to my surprise, the three at the altar faced round to\nme, and Godfrey Norton came running as hard as he could towards\nme.\n\n\"'Thank God,' he cried. 'You'll do. Come! Come!'\n\n\"'What then?' I asked.\n\n\"'Come, man, come, only three minutes, or it won't be legal.'\n\n\"I was half-dragged up to the altar, and before I knew where I was\nI found myself mumbling responses which were whispered in my ear,\nand vouching for things of which I knew nothing, and generally\nassisting in the secure tying up of Irene Adler, spinster, to\nGodfrey Norton, bachelor. It was all done in an instant, and\nthere was the gentleman thanking me on the one side and the lady\non the other, while the clergyman beamed on me in front. It was\nthe most preposterous position in which I ever found myself in my\nlife, and it was the thought of it that started me laughing just\nnow. It seems that there had been some informality about their\nlicense, that the clergyman absolutely refused to marry them\nwithout a witness of some sort, and that my lucky appearance\nsaved the bridegroom from having to sally out into the streets in\nsearch of a best man. The bride gave me a sovereign, and I mean\nto wear it on my watch-chain in memory of the occasion.\"\n\n\"This is a very unexpected turn of affairs,\" said I; \"and what\nthen?\"\n\n\"Well, I found my plans very seriously menaced. It looked as if\nthe pair might take an immediate departure, and so necessitate\nvery prompt and energetic measures on my part. At the church\ndoor, however, they separated, he driving back to the Temple, and\nshe to her own house. 'I shall drive out in the park at five as\nusual,' she said as she left him. I heard no more. They drove\naway in different directions, and I went off to make my own\narrangements.\"\n\n\"Which are?\"\n\n\"Some cold beef and a glass of beer,\" he answered, ringing the\nbell. \"I have been too busy to think of food, and I am likely to\nbe busier still this evening. By the way, Doctor, I shall want\nyour co-operation.\"\n\n\"I shall be delighted.\"\n\n\"You don't mind breaking the law?\"\n\n\"Not in the least.\"\n\n\"Nor running a chance of arrest?\"\n\n\"Not in a good cause.\"\n\n\"Oh, the cause is excellent!\"\n\n\"Then I am your man.\"\n\n\"I was sure that I might rely on you.\"\n\n\"But what is it you wish?\"\n\n\"When Mrs. Turner has brought in the tray I will make it clear to\nyou. Now,\" he said as he turned hungrily on the simple fare that\nour landlady had provided, \"I must discuss it while I eat, for I\nhave not much time. It is nearly five now. In two hours we must\nbe on the scene of action. Miss Irene, or Madame, rather, returns\nfrom her drive at seven. We must be at Briony Lodge to meet her.\"\n\n\"And what then?\"\n\n\"You must leave that to me. I have already arranged what is to\noccur. There is only one point on which I must insist. You must\nnot interfere, come what may. You understand?\"\n\n\"I am to be neutral?\"\n\n\"To do nothing whatever. There will probably be some small\nunpleasantness. Do not join in it. It will end in my being\nconveyed into the house. Four or five minutes afterwards the\nsitting-room window will open. You are to station yourself close\nto that open window.\"\n\n\"Yes.\"\n\n\"You are to watch me, for I will be visible to you.\"\n\n\"Yes.\"\n\n\"And when I raise my hand--so--you will throw into the room what\nI give you to throw, and will, at the same time, raise the cry of\nfire. You quite follow me?\"\n\n\"Entirely.\"\n\n\"It is nothing very formidable,\" he said, taking a long cigar-shaped\nroll from his pocket. \"It is an ordinary plumber's smoke-rocket,\nfitted with a cap at either end to make it self-lighting.\nYour task is confined to that. When you raise your cry of fire,\nit will be taken up by quite a number of people. You may then\nwalk to the end of the street, and I will rejoin you in ten\nminutes. I hope that I have made myself clear?\"\n\n\"I am to remain neutral, to get near the window, to watch you,\nand at the signal to throw in this object, then to raise the cry\nof fire, and to wait you at the corner of the street.\"\n\n\"Precisely.\"\n\n\"Then you may entirely rely on me.\"\n\n\"That is excellent. I think, perhaps, it is almost time that I\nprepare for the new role I have to play.\"\n\nHe disappeared into his bedroom and returned in a few minutes in\nthe character of an amiable and simple-minded Nonconformist\nclergyman. His broad black hat, his baggy trousers, his white\ntie, his sympathetic smile, and general look of peering and\nbenevolent curiosity were such as Mr. John Hare alone could have\nequalled. It was not merely that Holmes changed his costume. His\nexpression, his manner, his very soul seemed to vary with every\nfresh part that he assumed. The stage lost a fine actor, even as\nscience lost an acute reasoner, when he became a specialist in\ncrime.\n\nIt was a quarter past six when we left Baker Street, and it still\nwanted ten minutes to the hour when we found ourselves in\nSerpentine Avenue. It was already dusk, and the lamps were just\nbeing lighted as we paced up and down in front of Briony Lodge,\nwaiting for the coming of its occupant. The house was just such\nas I had pictured it from Sherlock Holmes' succinct description,\nbut the locality appeared to be less private than I expected. On\nthe contrary, for a small street in a quiet neighbourhood, it was\nremarkably animated. There was a group of shabbily dressed men\nsmoking and laughing in a corner, a scissors-grinder with his\nwheel, two guardsmen who were flirting with a nurse-girl, and\nseveral well-dressed young men who were lounging up and down with\ncigars in their mouths.\n\n\"You see,\" remarked Holmes, as we paced to and fro in front of\nthe house, \"this marriage rather simplifies matters. The\nphotograph becomes a double-edged weapon now. The chances are\nthat she would be as averse to its being seen by Mr. Godfrey\nNorton, as our client is to its coming to the eyes of his\nprincess. Now the question is, Where are we to find the\nphotograph?\"\n\n\"Where, indeed?\"\n\n\"It is most unlikely that she carries it about with her. It is\ncabinet size. Too large for easy concealment about a woman's\ndress. She knows that the King is capable of having her waylaid\nand searched. Two attempts of the sort have already been made. We\nmay take it, then, that she does not carry it about with her.\"\n\n\"Where, then?\"\n\n\"Her banker or her lawyer. There is that double possibility. But\nI am inclined to think neither. Women are naturally secretive,\nand they like to do their own secreting. Why should she hand it\nover to anyone else? She could trust her own guardianship, but\nshe could not tell what indirect or political influence might be\nbrought to bear upon a business man. Besides, remember that she\nhad resolved to use it within a few days. It must be where she\ncan lay her hands upon it. It must be in her own house.\"\n\n\"But it has twice been burgled.\"\n\n\"Pshaw! They did not know how to look.\"\n\n\"But how will you look?\"\n\n\"I will not look.\"\n\n\"What then?\"\n\n\"I will get her to show me.\"\n\n\"But she will refuse.\"\n\n\"She will not be able to. But I hear the rumble of wheels. It is\nher carriage. Now carry out my orders to the letter.\"\n\nAs he spoke the gleam of the side-lights of a carriage came round\nthe curve of the avenue. It was a smart little landau which\nrattled up to the door of Briony Lodge. As it pulled up, one of\nthe loafing men at the corner dashed forward to open the door in\nthe hope of earning a copper, but was elbowed away by another\nloafer, who had rushed up with the same intention. A fierce\nquarrel broke out, which was increased by the two guardsmen, who\ntook sides with one of the loungers, and by the scissors-grinder,\nwho was equally hot upon the other side. A blow was struck, and\nin an instant the lady, who had stepped from her carriage, was\nthe centre of a little knot of flushed and struggling men, who\nstruck savagely at each other with their fists and sticks. Holmes\ndashed into the crowd to protect the lady; but just as he reached\nher he gave a cry and dropped to the ground, with the blood\nrunning freely down his face. At his fall the guardsmen took to\ntheir heels in one direction and the loungers in the other, while\na number of better-dressed people, who had watched the scuffle\nwithout taking part in it, crowded in to help the lady and to\nattend to the injured man. Irene Adler, as I will still call her,\nhad hurried up the steps; but she stood at the top with her\nsuperb figure outlined against the lights of the hall, looking\nback into the street.\n\n\"Is the poor gentleman much hurt?\" she asked.\n\n\"He is dead,\" cried several voices.\n\n\"No, no, there's life in him!\" shouted another. \"But he'll be\ngone before you can get him to hospital.\"\n\n\"He's a brave fellow,\" said a woman. \"They would have had the\nlady's purse and watch if it hadn't been for him. They were a\ngang, and a rough one, too. Ah, he's breathing now.\"\n\n\"He can't lie in the street. May we bring him in, marm?\"\n\n\"Surely. Bring him into the sitting-room. There is a comfortable\nsofa. This way, please!\"\n\nSlowly and solemnly he was borne into Briony Lodge and laid out\nin the principal room, while I still observed the proceedings\nfrom my post by the window. The lamps had been lit, but the\nblinds had not been drawn, so that I could see Holmes as he lay\nupon the couch. I do not know whether he was seized with\ncompunction at that moment for the part he was playing, but I\nknow that I never felt more heartily ashamed of myself in my life\nthan when I saw the beautiful creature against whom I was\nconspiring, or the grace and kindliness with which she waited\nupon the injured man. And yet it would be the blackest treachery\nto Holmes to draw back now from the part which he had intrusted\nto me. I hardened my heart, and took the smoke-rocket from under\nmy ulster. After all, I thought, we are not injuring her. We are\nbut preventing her from injuring another.\n\nHolmes had sat up upon the couch, and I saw him motion like a man\nwho is in need of air. A maid rushed across and threw open the\nwindow. At the same instant I saw him raise his hand and at the\nsignal I tossed my rocket into the room with a cry of \"Fire!\" The\nword was no sooner out of my mouth than the whole crowd of\nspectators, well dressed and ill--gentlemen, ostlers, and\nservant-maids--joined in a general shriek of \"Fire!\" Thick clouds\nof smoke curled through the room and out at the open window. I\ncaught a glimpse of rushing figures, and a moment later the voice\nof Holmes from within assuring them that it was a false alarm.\nSlipping through the shouting crowd I made my way to the corner\nof the street, and in ten minutes was rejoiced to find my\nfriend's arm in mine, and to get away from the scene of uproar.\nHe walked swiftly and in silence for some few minutes until we\nhad turned down one of the quiet streets which lead towards the\nEdgeware Road.\n\n\"You did it very nicely, Doctor,\" he remarked. \"Nothing could\nhave been better. It is all right.\"\n\n\"You have the photograph?\"\n\n\"I know where it is.\"\n\n\"And how did you find out?\"\n\n\"She showed me, as I told you she would.\"\n\n\"I am still in the dark.\"\n\n\"I do not wish to make a mystery,\" said he, laughing. \"The matter\nwas perfectly simple. You, of course, saw that everyone in the\nstreet was an accomplice. They were all engaged for the evening.\"\n\n\"I guessed as much.\"\n\n\"Then, when the row broke out, I had a little moist red paint in\nthe palm of my hand. I rushed forward, fell down, clapped my hand\nto my face, and became a piteous spectacle. It is an old trick.\"\n\n\"That also I could fathom.\"\n\n\"Then they carried me in. She was bound to have me in. What else\ncould she do? And into her sitting-room, which was the very room\nwhich I suspected. It lay between that and her bedroom, and I was\ndetermined to see which. They laid me on a couch, I motioned for\nair, they were compelled to open the window, and you had your\nchance.\"\n\n\"How did that help you?\"\n\n\"It was all-important. When a woman thinks that her house is on\nfire, her instinct is at once to rush to the thing which she\nvalues most. It is a perfectly overpowering impulse, and I have\nmore than once taken advantage of it. In the case of the\nDarlington substitution scandal it was of use to me, and also in\nthe Arnsworth Castle business. A married woman grabs at her baby;\nan unmarried one reaches for her jewel-box. Now it was clear to\nme that our lady of to-day had nothing in the house more precious\nto her than what we are in quest of. She would rush to secure it.\nThe alarm of fire was admirably done. The smoke and shouting were\nenough to shake nerves of steel. She responded beautifully. The\nphotograph is in a recess behind a sliding panel just above the\nright bell-pull. She was there in an instant, and I caught a\nglimpse of it as she half-drew it out. When I cried out that it\nwas a false alarm, she replaced it, glanced at the rocket, rushed\nfrom the room, and I have not seen her since. I rose, and, making\nmy excuses, escaped from the house. I hesitated whether to\nattempt to secure the photograph at once; but the coachman had\ncome in, and as he was watching me narrowly it seemed safer to\nwait. A little over-precipitance may ruin all.\"\n\n\"And now?\" I asked.\n\n\"Our quest is practically finished. I shall call with the King\nto-morrow, and with you, if you care to come with us. We will be\nshown into the sitting-room to wait for the lady, but it is\nprobable that when she comes she may find neither us nor the\nphotograph. It might be a satisfaction to his Majesty to regain\nit with his own hands.\"\n\n\"And when will you call?\"\n\n\"At eight in the morning. She will not be up, so that we shall\nhave a clear field. Besides, we must be prompt, for this marriage\nmay mean a complete change in her life and habits. I must wire to\nthe King without delay.\"\n\nWe had reached Baker Street and had stopped at the door. He was\nsearching his pockets for the key when someone passing said:\n\n\"Good-night, Mister Sherlock Holmes.\"\n\nThere were several people on the pavement at the time, but the\ngreeting appeared to come from a slim youth in an ulster who had\nhurried by.\n\n\"I've heard that voice before,\" said Holmes, staring down the\ndimly lit street. \"Now, I wonder who the deuce that could have\nbeen.\"\n\n\nIII.\n\nI slept at Baker Street that night, and we were engaged upon our\ntoast and coffee in the morning when the King of Bohemia rushed\ninto the room.\n\n\"You have really got it!\" he cried, grasping Sherlock Holmes by\neither shoulder and looking eagerly into his face.\n\n\"Not yet.\"\n\n\"But you have hopes?\"\n\n\"I have hopes.\"\n\n\"Then, come. I am all impatience to be gone.\"\n\n\"We must have a cab.\"\n\n\"No, my brougham is waiting.\"\n\n\"Then that will simplify matters.\" We descended and started off\nonce more for Briony Lodge.\n\n\"Irene Adler is married,\" remarked Holmes.\n\n\"Married! When?\"\n\n\"Yesterday.\"\n\n\"But to whom?\"\n\n\"To an English lawyer named Norton.\"\n\n\"But she could not love him.\"\n\n\"I am in hopes that she does.\"\n\n\"And why in hopes?\"\n\n\"Because it would spare your Majesty all fear of future\nannoyance. If the lady loves her husband, she does not love your\nMajesty. If she does not love your Majesty, there is no reason\nwhy she should interfere with your Majesty's plan.\"\n\n\"It is true. And yet--Well! I wish she had been of my own\nstation! What a queen she would have made!\" He relapsed into a\nmoody silence, which was not broken until we drew up in\nSerpentine Avenue.\n\nThe door of Briony Lodge was open, and an elderly woman stood\nupon the steps. She watched us with a sardonic eye as we stepped\nfrom the brougham.\n\n\"Mr. Sherlock Holmes, I believe?\" said she.\n\n\"I am Mr. Holmes,\" answered my companion, looking at her with a\nquestioning and rather startled gaze.\n\n\"Indeed! My mistress told me that you were likely to call. She\nleft this morning with her husband by the 5:15 train from Charing\nCross for the Continent.\"\n\n\"What!\" Sherlock Holmes staggered back, white with chagrin and\nsurprise. \"Do you mean that she has left England?\"\n\n\"Never to return.\"\n\n\"And the papers?\" asked the King hoarsely. \"All is lost.\"\n\n\"We shall see.\" He pushed past the servant and rushed into the\ndrawing-room, followed by the King and myself. The furniture was\nscattered about in every direction, with dismantled shelves and\nopen drawers, as if the lady had hurriedly ransacked them before\nher flight. Holmes rushed at the bell-pull, tore back a small\nsliding shutter, and, plunging in his hand, pulled out a\nphotograph and a letter. The photograph was of Irene Adler\nherself in evening dress, the letter was superscribed to\n\"Sherlock Holmes, Esq. To be left till called for.\" My friend\ntore it open and we all three read it together. It was dated at\nmidnight of the preceding night and ran in this way:\n\n\"MY DEAR MR. SHERLOCK HOLMES,--You really did it very well. You\ntook me in completely. Until after the alarm of fire, I had not a\nsuspicion. But then, when I found how I had betrayed myself, I\nbegan to think. I had been warned against you months ago. I had\nbeen told that if the King employed an agent it would certainly\nbe you. And your address had been given me. Yet, with all this,\nyou made me reveal what you wanted to know. Even after I became\nsuspicious, I found it hard to think evil of such a dear, kind\nold clergyman. But, you know, I have been trained as an actress\nmyself. Male costume is nothing new to me. I often take advantage\nof the freedom which it gives. I sent John, the coachman, to\nwatch you, ran up stairs, got into my walking-clothes, as I call\nthem, and came down just as you departed.\n\n\"Well, I followed you to your door, and so made sure that I was\nreally an object of interest to the celebrated Mr. Sherlock\nHolmes. Then I, rather imprudently, wished you good-night, and\nstarted for the Temple to see my husband.\n\n\"We both thought the best resource was flight, when pursued by\nso formidable an antagonist; so you will find the nest empty when\nyou call to-morrow. As to the photograph, your client may rest in\npeace. I love and am loved by a better man than he. The King may\ndo what he will without hindrance from one whom he has cruelly\nwronged. I keep it only to safeguard myself, and to preserve a\nweapon which will always secure me from any steps which he might\ntake in the future. I leave a photograph which he might care to\npossess; and I remain, dear Mr. Sherlock Holmes,\n\n                                      \"Very truly yours,\n                                   \"IRENE NORTON, née ADLER.\"\n\n\"What a woman--oh, what a woman!\" cried the King of Bohemia, when\nwe had all three read this epistle. \"Did I not tell you how quick\nand resolute she was? Would she not have made an admirable queen?\nIs it not a pity that she was not on my level?\"\n\n\"From what I have seen of the lady she seems indeed to be on a\nvery different level to your Majesty,\" said Holmes coldly. \"I am\nsorry that I have not been able to bring your Majesty's business\nto a more successful conclusion.\"\n\n\"On the contrary, my dear sir,\" cried the King; \"nothing could be\nmore successful. I know that her word is inviolate. The\nphotograph is now as safe as if it were in the fire.\"\n\n\"I am glad to hear your Majesty say so.\"\n\n\"I am immensely indebted to you. Pray tell me in what way I can\nreward you. This ring--\" He slipped an emerald snake ring from\nhis finger and held it out upon the palm of his hand.\n\n\"Your Majesty has something which I should value even more\nhighly,\" said Holmes.\n\n\"You have but to name it.\"\n\n\"This photograph!\"\n\nThe King stared at him in amazement.\n\n\"Irene's photograph!\" he cried. \"Certainly, if you wish it.\"\n\n\"I thank your Majesty. Then there is no more to be done in the\nmatter. I have the honour to wish you a very good-morning.\" He\nbowed, and, turning away without observing the hand which the\nKing had stretched out to him, he set off in my company for his\nchambers.\n\nAnd that was how a great scandal threatened to affect the kingdom\nof Bohemia, and how the best plans of Mr. Sherlock Holmes were\nbeaten by a woman's wit. He used to make merry over the\ncleverness of women, but I have not heard him do it of late. And\nwhen he speaks of Irene Adler, or when he refers to her\nphotograph, it is always under the honourable title of the woman.\n\n\n\nADVENTURE II. THE RED-HEADED LEAGUE\n\nI had called upon my friend, Mr. Sherlock Holmes, one day in the\nautumn of last year and found him in deep conversation with a\nvery stout, florid-faced, elderly gentleman with fiery red hair.\nWith an apology for my intrusion, I was about to withdraw when\nHolmes pulled me abruptly into the room and closed the door\nbehind me.\n\n\"You could not possibly have come at a better time, my dear\nWatson,\" he said cordially.\n\n\"I was afraid that you were engaged.\"\n\n\"So I am. Very much so.\"\n\n\"Then I can wait in the next room.\"\n\n\"Not at all. This gentleman, Mr. Wilson, has been my partner and\nhelper in many of my most successful cases, and I have no\ndoubt that he will be of the utmost use to me in yours also.\"\n\nThe stout gentleman half rose from his chair and gave a bob of\ngreeting, with a quick little questioning glance from his small\nfat-encircled eyes.\n\n\"Try the settee,\" said Holmes, relapsing into his armchair and\nputting his fingertips together, as was his custom when in\njudicial moods. \"I know, my dear Watson, that you share my love\nof all that is bizarre and outside the conventions and humdrum\nroutine of everyday life. You have shown your relish for it by\nthe enthusiasm which has prompted you to chronicle, and, if you\nwill excuse my saying so, somewhat to embellish so many of my own\nlittle adventures.\"\n\n\"Your cases have indeed been of the greatest interest to me,\" I\nobserved.\n\n\"You will remember that I remarked the other day, just before we\nwent into the very simple problem presented by Miss Mary\nSutherland, that for strange effects and extraordinary\ncombinations we must go to life itself, which is always far more\ndaring than any effort of the imagination.\"\n\n\"A proposition which I took the liberty of doubting.\"\n\n\"You did, Doctor, but none the less you must come round to my\nview, for otherwise I shall keep on piling fact upon fact on you\nuntil your reason breaks down under them and acknowledges me to\nbe right. Now, Mr. Jabez Wilson here has been good enough to call\nupon me this morning, and to begin a narrative which promises to\nbe one of the most singular which I have listened to for some\ntime. You have heard me remark that the strangest and most unique\nthings are very often connected not with the larger but with the\nsmaller crimes, and occasionally, indeed, where there is room for\ndoubt whether any positive crime has been committed. As far as I\nhave heard it is impossible for me to say whether the present\ncase is an instance of crime or not, but the course of events is\ncertainly among the most singular that I have ever listened to.\nPerhaps, Mr. Wilson, you would have the great kindness to\nrecommence your narrative. I ask you not merely because my friend\nDr. Watson has not heard the opening part but also because the\npeculiar nature of the story makes me anxious to have every\npossible detail from your lips. As a rule, when I have heard some\nslight indication of the course of events, I am able to guide\nmyself by the thousands of other similar cases which occur to my\nmemory. In the present instance I am forced to admit that the\nfacts are, to the best of my belief, unique.\"\n\nThe portly client puffed out his chest with an appearance of some\nlittle pride and pulled a dirty and wrinkled newspaper from the\ninside pocket of his greatcoat. As he glanced down the\nadvertisement column, with his head thrust forward and the paper\nflattened out upon his knee, I took a good look at the man and\nendeavoured, after the fashion of my companion, to read the\nindications which might be presented by his dress or appearance.\n\nI did not gain very much, however, by my inspection. Our visitor\nbore every mark of being an average commonplace British\ntradesman, obese, pompous, and slow. He wore rather baggy grey\nshepherd's check trousers, a not over-clean black frock-coat,\nunbuttoned in the front, and a drab waistcoat with a heavy brassy\nAlbert chain, and a square pierced bit of metal dangling down as\nan ornament. A frayed top-hat and a faded brown overcoat with a\nwrinkled velvet collar lay upon a chair beside him. Altogether,\nlook as I would, there was nothing remarkable about the man save\nhis blazing red head, and the expression of extreme chagrin and\ndiscontent upon his features.\n\nSherlock Holmes' quick eye took in my occupation, and he shook\nhis head with a smile as he noticed my questioning glances.\n\"Beyond the obvious facts that he has at some time done manual\nlabour, that he takes snuff, that he is a Freemason, that he has\nbeen in China, and that he has done a considerable amount of\nwriting lately, I can deduce nothing else.\"\n\nMr. Jabez Wilson started up in his chair, with his forefinger\nupon the paper, but his eyes upon my companion.\n\n\"How, in the name of good-fortune, did you know all that, Mr.\nHolmes?\" he asked. \"How did you know, for example, that I did\nmanual labour. It's as true as gospel, for I began as a ship's\ncarpenter.\"\n\n\"Your hands, my dear sir. Your right hand is quite a size larger\nthan your left. You have worked with it, and the muscles are more\ndeveloped.\"\n\n\"Well, the snuff, then, and the Freemasonry?\"\n\n\"I won't insult your intelligence by telling you how I read that,\nespecially as, rather against the strict rules of your order, you\nuse an arc-and-compass breastpin.\"\n\n\"Ah, of course, I forgot that. But the writing?\"\n\n\"What else can be indicated by that right cuff so very shiny for\nfive inches, and the left one with the smooth patch near the\nelbow where you rest it upon the desk?\"\n\n\"Well, but China?\"\n\n\"The fish that you have tattooed immediately above your right\nwrist could only have been done in China. I have made a small\nstudy of tattoo marks and have even contributed to the literature\nof the subject. That trick of staining the fishes' scales of a\ndelicate pink is quite peculiar to China. When, in addition, I\nsee a Chinese coin hanging from your watch-chain, the matter\nbecomes even more simple.\"\n\nMr. Jabez Wilson laughed heavily. \"Well, I never!\" said he. \"I\nthought at first that you had done something clever, but I see\nthat there was nothing in it, after all.\"\n\n\"I begin to think, Watson,\" said Holmes, \"that I make a mistake\nin explaining. 'Omne ignotum pro magnifico,' you know, and my\npoor little reputation, such as it is, will suffer shipwreck if I\nam so candid. Can you not find the advertisement, Mr. Wilson?\"\n\n\"Yes, I have got it now,\" he answered with his thick red finger\nplanted halfway down the column. \"Here it is. This is what began\nit all. You just read it for yourself, sir.\"\n\nI took the paper from him and read as follows:\n\n\"TO THE RED-HEADED LEAGUE: On account of the bequest of the late\nEzekiah Hopkins, of Lebanon, Pennsylvania, U. S. A., there is now\nanother vacancy open which entitles a member of the League to a\nsalary of 4 pounds a week for purely nominal services. All\nred-headed men who are sound in body and mind and above the age\nof twenty-one years, are eligible. Apply in person on Monday, at\neleven o'clock, to Duncan Ross, at the offices of the League, 7\nPope's Court, Fleet Street.\"\n\n\"What on earth does this mean?\" I ejaculated after I had twice\nread over the extraordinary announcement.\n\nHolmes chuckled and wriggled in his chair, as was his habit when\nin high spirits. \"It is a little off the beaten track, isn't it?\"\nsaid he. \"And now, Mr. Wilson, off you go at scratch and tell us\nall about yourself, your household, and the effect which this\nadvertisement had upon your fortunes. You will first make a note,\nDoctor, of the paper and the date.\"\n\n\"It is The Morning Chronicle of April 27, 1890. Just two months\nago.\"\n\n\"Very good. Now, Mr. Wilson?\"\n\n\"Well, it is just as I have been telling you, Mr. Sherlock\nHolmes,\" said Jabez Wilson, mopping his forehead; \"I have a small\npawnbroker's business at Coburg Square, near the City. It's not a\nvery large affair, and of late years it has not done more than\njust give me a living. I used to be able to keep two assistants,\nbut now I only keep one; and I would have a job to pay him but\nthat he is willing to come for half wages so as to learn the\nbusiness.\"\n\n\"What is the name of this obliging youth?\" asked Sherlock Holmes.\n\n\"His name is Vincent Spaulding, and he's not such a youth,\neither. It's hard to say his age. I should not wish a smarter\nassistant, Mr. Holmes; and I know very well that he could better\nhimself and earn twice what I am able to give him. But, after\nall, if he is satisfied, why should I put ideas in his head?\"\n\n\"Why, indeed? You seem most fortunate in having an employé who\ncomes under the full market price. It is not a common experience\namong employers in this age. I don't know that your assistant is\nnot as remarkable as your advertisement.\"\n\n\"Oh, he has his faults, too,\" said Mr. Wilson. \"Never was such a\nfellow for photography. Snapping away with a camera when he ought\nto be improving his mind, and then diving down into the cellar\nlike a rabbit into its hole to develop his pictures. That is his\nmain fault, but on the whole he's a good worker. There's no vice\nin him.\"\n\n\"He is still with you, I presume?\"\n\n\"Yes, sir. He and a girl of fourteen, who does a bit of simple\ncooking and keeps the place clean--that's all I have in the\nhouse, for I am a widower and never had any family. We live very\nquietly, sir, the three of us; and we keep a roof over our heads\nand pay our debts, if we do nothing more.\n\n\"The first thing that put us out was that advertisement.\nSpaulding, he came down into the office just this day eight\nweeks, with this very paper in his hand, and he says:\n\n\"'I wish to the Lord, Mr. Wilson, that I was a red-headed man.'\n\n\"'Why that?' I asks.\n\n\"'Why,' says he, 'here's another vacancy on the League of the\nRed-headed Men. It's worth quite a little fortune to any man who\ngets it, and I understand that there are more vacancies than\nthere are men, so that the trustees are at their wits' end what\nto do with the money. If my hair would only change colour, here's\na nice little crib all ready for me to step into.'\n\n\"'Why, what is it, then?' I asked. You see, Mr. Holmes, I am a\nvery stay-at-home man, and as my business came to me instead of\nmy having to go to it, I was often weeks on end without putting\nmy foot over the door-mat. In that way I didn't know much of what\nwas going on outside, and I was always glad of a bit of news.\n\n\"'Have you never heard of the League of the Red-headed Men?' he\nasked with his eyes open.\n\n\"'Never.'\n\n\"'Why, I wonder at that, for you are eligible yourself for one\nof the vacancies.'\n\n\"'And what are they worth?' I asked.\n\n\"'Oh, merely a couple of hundred a year, but the work is slight,\nand it need not interfere very much with one's other\noccupations.'\n\n\"Well, you can easily think that that made me prick up my ears,\nfor the business has not been over-good for some years, and an\nextra couple of hundred would have been very handy.\n\n\"'Tell me all about it,' said I.\n\n\"'Well,' said he, showing me the advertisement, 'you can see for\nyourself that the League has a vacancy, and there is the address\nwhere you should apply for particulars. As far as I can make out,\nthe League was founded by an American millionaire, Ezekiah\nHopkins, who was very peculiar in his ways. He was himself\nred-headed, and he had a great sympathy for all red-headed men;\nso when he died it was found that he had left his enormous\nfortune in the hands of trustees, with instructions to apply the\ninterest to the providing of easy berths to men whose hair is of\nthat colour. From all I hear it is splendid pay and very little to\ndo.'\n\n\"'But,' said I, 'there would be millions of red-headed men who\nwould apply.'\n\n\"'Not so many as you might think,' he answered. 'You see it is\nreally confined to Londoners, and to grown men. This American had\nstarted from London when he was young, and he wanted to do the\nold town a good turn. Then, again, I have heard it is no use your\napplying if your hair is light red, or dark red, or anything but\nreal bright, blazing, fiery red. Now, if you cared to apply, Mr.\nWilson, you would just walk in; but perhaps it would hardly be\nworth your while to put yourself out of the way for the sake of a\nfew hundred pounds.'\n\n\"Now, it is a fact, gentlemen, as you may see for yourselves,\nthat my hair is of a very full and rich tint, so that it seemed\nto me that if there was to be any competition in the matter I\nstood as good a chance as any man that I had ever met. Vincent\nSpaulding seemed to know so much about it that I thought he might\nprove useful, so I just ordered him to put up the shutters for\nthe day and to come right away with me. He was very willing to\nhave a holiday, so we shut the business up and started off for\nthe address that was given us in the advertisement.\n\n\"I never hope to see such a sight as that again, Mr. Holmes. From\nnorth, south, east, and west every man who had a shade of red in\nhis hair had tramped into the city to answer the advertisement.\nFleet Street was choked with red-headed folk, and Pope's Court\nlooked like a coster's orange barrow. I should not have thought\nthere were so many in the whole country as were brought together\nby that single advertisement. Every shade of colour they\nwere--straw, lemon, orange, brick, Irish-setter, liver, clay;\nbut, as Spaulding said, there were not many who had the real\nvivid flame-coloured tint. When I saw how many were waiting, I\nwould have given it up in despair; but Spaulding would not hear\nof it. How he did it I could not imagine, but he pushed and\npulled and butted until he got me through the crowd, and right up\nto the steps which led to the office. There was a double stream\nupon the stair, some going up in hope, and some coming back\ndejected; but we wedged in as well as we could and soon found\nourselves in the office.\"\n\n\"Your experience has been a most entertaining one,\" remarked\nHolmes as his client paused and refreshed his memory with a huge\npinch of snuff. \"Pray continue your very interesting statement.\"\n\n\"There was nothing in the office but a couple of wooden chairs\nand a deal table, behind which sat a small man with a head that\nwas even redder than mine. He said a few words to each candidate\nas he came up, and then he always managed to find some fault in\nthem which would disqualify them. Getting a vacancy did not seem\nto be such a very easy matter, after all. However, when our turn\ncame the little man was much more favourable to me than to any of\nthe others, and he closed the door as we entered, so that he\nmight have a private word with us.\n\n\"'This is Mr. Jabez Wilson,' said my assistant, 'and he is\nwilling to fill a vacancy in the League.'\n\n\"'And he is admirably suited for it,' the other answered. 'He has\nevery requirement. I cannot recall when I have seen anything so\nfine.' He took a step backward, cocked his head on one side, and\ngazed at my hair until I felt quite bashful. Then suddenly he\nplunged forward, wrung my hand, and congratulated me warmly on my\nsuccess.\n\n\"'It would be injustice to hesitate,' said he. 'You will,\nhowever, I am sure, excuse me for taking an obvious precaution.'\nWith that he seized my hair in both his hands, and tugged until I\nyelled with the pain. 'There is water in your eyes,' said he as\nhe released me. 'I perceive that all is as it should be. But we\nhave to be careful, for we have twice been deceived by wigs and\nonce by paint. I could tell you tales of cobbler's wax which\nwould disgust you with human nature.' He stepped over to the\nwindow and shouted through it at the top of his voice that the\nvacancy was filled. A groan of disappointment came up from below,\nand the folk all trooped away in different directions until there\nwas not a red-head to be seen except my own and that of the\nmanager.\n\n\"'My name,' said he, 'is Mr. Duncan Ross, and I am myself one of\nthe pensioners upon the fund left by our noble benefactor. Are\nyou a married man, Mr. Wilson? Have you a family?'\n\n\"I answered that I had not.\n\n\"His face fell immediately.\n\n\"'Dear me!' he said gravely, 'that is very serious indeed! I am\nsorry to hear you say that. The fund was, of course, for the\npropagation and spread of the red-heads as well as for their\nmaintenance. It is exceedingly unfortunate that you should be a\nbachelor.'\n\n\"My face lengthened at this, Mr. Holmes, for I thought that I was\nnot to have the vacancy after all; but after thinking it over for\na few minutes he said that it would be all right.\n\n\"'In the case of another,' said he, 'the objection might be\nfatal, but we must stretch a point in favour of a man with such a\nhead of hair as yours. When shall you be able to enter upon your\nnew duties?'\n\n\"'Well, it is a little awkward, for I have a business already,'\nsaid I.\n\n\"'Oh, never mind about that, Mr. Wilson!' said Vincent Spaulding.\n'I should be able to look after that for you.'\n\n\"'What would be the hours?' I asked.\n\n\"'Ten to two.'\n\n\"Now a pawnbroker's business is mostly done of an evening, Mr.\nHolmes, especially Thursday and Friday evening, which is just\nbefore pay-day; so it would suit me very well to earn a little in\nthe mornings. Besides, I knew that my assistant was a good man,\nand that he would see to anything that turned up.\n\n\"'That would suit me very well,' said I. 'And the pay?'\n\n\"'Is 4 pounds a week.'\n\n\"'And the work?'\n\n\"'Is purely nominal.'\n\n\"'What do you call purely nominal?'\n\n\"'Well, you have to be in the office, or at least in the\nbuilding, the whole time. If you leave, you forfeit your whole\nposition forever. The will is very clear upon that point. You\ndon't comply with the conditions if you budge from the office\nduring that time.'\n\n\"'It's only four hours a day, and I should not think of leaving,'\nsaid I.\n\n\"'No excuse will avail,' said Mr. Duncan Ross; 'neither sickness\nnor business nor anything else. There you must stay, or you lose\nyour billet.'\n\n\"'And the work?'\n\n\"'Is to copy out the \"Encyclopaedia Britannica.\" There is the first\nvolume of it in that press. You must find your own ink, pens, and\nblotting-paper, but we provide this table and chair. Will you be\nready to-morrow?'\n\n\"'Certainly,' I answered.\n\n\"'Then, good-bye, Mr. Jabez Wilson, and let me congratulate you\nonce more on the important position which you have been fortunate\nenough to gain.' He bowed me out of the room and I went home with\nmy assistant, hardly knowing what to say or do, I was so pleased\nat my own good fortune.\n\n\"Well, I thought over the matter all day, and by evening I was in\nlow spirits again; for I had quite persuaded myself that the\nwhole affair must be some great hoax or fraud, though what its\nobject might be I could not imagine. It seemed altogether past\nbelief that anyone could make such a will, or that they would pay\nsuch a sum for doing anything so simple as copying out the\n'Encyclopaedia Britannica.' Vincent Spaulding did what he could to\ncheer me up, but by bedtime I had reasoned myself out of the\nwhole thing. However, in the morning I determined to have a look\nat it anyhow, so I bought a penny bottle of ink, and with a\nquill-pen, and seven sheets of foolscap paper, I started off for\nPope's Court.\n\n\"Well, to my surprise and delight, everything was as right as\npossible. The table was set out ready for me, and Mr. Duncan Ross\nwas there to see that I got fairly to work. He started me off\nupon the letter A, and then he left me; but he would drop in from\ntime to time to see that all was right with me. At two o'clock he\nbade me good-day, complimented me upon the amount that I had\nwritten, and locked the door of the office after me.\n\n\"This went on day after day, Mr. Holmes, and on Saturday the\nmanager came in and planked down four golden sovereigns for my\nweek's work. It was the same next week, and the same the week\nafter. Every morning I was there at ten, and every afternoon I\nleft at two. By degrees Mr. Duncan Ross took to coming in only\nonce of a morning, and then, after a time, he did not come in at\nall. Still, of course, I never dared to leave the room for an\ninstant, for I was not sure when he might come, and the billet\nwas such a good one, and suited me so well, that I would not risk\nthe loss of it.\n\n\"Eight weeks passed away like this, and I had written about\nAbbots and Archery and Armour and Architecture and Attica, and\nhoped with diligence that I might get on to the B's before very\nlong. It cost me something in foolscap, and I had pretty nearly\nfilled a shelf with my writings. And then suddenly the whole\nbusiness came to an end.\"\n\n\"To an end?\"\n\n\"Yes, sir. And no later than this morning. I went to my work as\nusual at ten o'clock, but the door was shut and locked, with a\nlittle square of cardboard hammered on to the middle of the\npanel with a tack. Here it is, and you can read for yourself.\"\n\nHe held up a piece of white cardboard about the size of a sheet\nof note-paper. It read in this fashion:\n\n                  THE RED-HEADED LEAGUE\n\n                           IS\n\n                        DISSOLVED.\n\n                     October 9, 1890.\n\nSherlock Holmes and I surveyed this curt announcement and the\nrueful face behind it, until the comical side of the affair so\ncompletely overtopped every other consideration that we both\nburst out into a roar of laughter.\n\n\"I cannot see that there is anything very funny,\" cried our\nclient, flushing up to the roots of his flaming head. \"If you can\ndo nothing better than laugh at me, I can go elsewhere.\"\n\n\"No, no,\" cried Holmes, shoving him back into the chair from\nwhich he had half risen. \"I really wouldn't miss your case for\nthe world. It is most refreshingly unusual. But there is, if you\nwill excuse my saying so, something just a little funny about it.\nPray what steps did you take when you found the card upon the\ndoor?\"\n\n\"I was staggered, sir. I did not know what to do. Then I called\nat the offices round, but none of them seemed to know anything\nabout it. Finally, I went to the landlord, who is an accountant\nliving on the ground-floor, and I asked him if he could tell me\nwhat had become of the Red-headed League. He said that he had\nnever heard of any such body. Then I asked him who Mr. Duncan\nRoss was. He answered that the name was new to him.\n\n\"'Well,' said I, 'the gentleman at No. 4.'\n\n\"'What, the red-headed man?'\n\n\"'Yes.'\n\n\"'Oh,' said he, 'his name was William Morris. He was a solicitor\nand was using my room as a temporary convenience until his new\npremises were ready. He moved out yesterday.'\n\n\"'Where could I find him?'\n\n\"'Oh, at his new offices. He did tell me the address. Yes, 17\nKing Edward Street, near St. Paul's.'\n\n\"I started off, Mr. Holmes, but when I got to that address it was\na manufactory of artificial knee-caps, and no one in it had ever\nheard of either Mr. William Morris or Mr. Duncan Ross.\"\n\n\"And what did you do then?\" asked Holmes.\n\n\"I went home to Saxe-Coburg Square, and I took the advice of my\nassistant. But he could not help me in any way. He could only say\nthat if I waited I should hear by post. But that was not quite\ngood enough, Mr. Holmes. I did not wish to lose such a place\nwithout a struggle, so, as I had heard that you were good enough\nto give advice to poor folk who were in need of it, I came right\naway to you.\"\n\n\"And you did very wisely,\" said Holmes. \"Your case is an\nexceedingly remarkable one, and I shall be happy to look into it.\nFrom what you have told me I think that it is possible that\ngraver issues hang from it than might at first sight appear.\"\n\n\"Grave enough!\" said Mr. Jabez Wilson. \"Why, I have lost four\npound a week.\"\n\n\"As far as you are personally concerned,\" remarked Holmes, \"I do\nnot see that you have any grievance against this extraordinary\nleague. On the contrary, you are, as I understand, richer by some\n30 pounds, to say nothing of the minute knowledge which you have\ngained on every subject which comes under the letter A. You have\nlost nothing by them.\"\n\n\"No, sir. But I want to find out about them, and who they are,\nand what their object was in playing this prank--if it was a\nprank--upon me. It was a pretty expensive joke for them, for it\ncost them two and thirty pounds.\"\n\n\"We shall endeavour to clear up these points for you. And, first,\none or two questions, Mr. Wilson. This assistant of yours who\nfirst called your attention to the advertisement--how long had he\nbeen with you?\"\n\n\"About a month then.\"\n\n\"How did he come?\"\n\n\"In answer to an advertisement.\"\n\n\"Was he the only applicant?\"\n\n\"No, I had a dozen.\"\n\n\"Why did you pick him?\"\n\n\"Because he was handy and would come cheap.\"\n\n\"At half-wages, in fact.\"\n\n\"Yes.\"\n\n\"What is he like, this Vincent Spaulding?\"\n\n\"Small, stout-built, very quick in his ways, no hair on his face,\nthough he's not short of thirty. Has a white splash of acid upon\nhis forehead.\"\n\nHolmes sat up in his chair in considerable excitement. \"I thought\nas much,\" said he. \"Have you ever observed that his ears are\npierced for earrings?\"\n\n\"Yes, sir. He told me that a gipsy had done it for him when he\nwas a lad.\"\n\n\"Hum!\" said Holmes, sinking back in deep thought. \"He is still\nwith you?\"\n\n\"Oh, yes, sir; I have only just left him.\"\n\n\"And has your business been attended to in your absence?\"\n\n\"Nothing to complain of, sir. There's never very much to do of a\nmorning.\"\n\n\"That will do, Mr. Wilson. I shall be happy to give you an\nopinion upon the subject in the course of a day or two. To-day is\nSaturday, and I hope that by Monday we may come to a conclusion.\"\n\n\"Well, Watson,\" said Holmes when our visitor had left us, \"what\ndo you make of it all?\"\n\n\"I make nothing of it,\" I answered frankly. \"It is a most\nmysterious business.\"\n\n\"As a rule,\" said Holmes, \"the more bizarre a thing is the less\nmysterious it proves to be. It is your commonplace, featureless\ncrimes which are really puzzling, just as a commonplace face is\nthe most difficult to identify. But I must be prompt over this\nmatter.\"\n\n\"What are you going to do, then?\" I asked.\n\n\"To smoke,\" he answered. \"It is quite a three pipe problem, and I\nbeg that you won't speak to me for fifty minutes.\" He curled\nhimself up in his chair, with his thin knees drawn up to his\nhawk-like nose, and there he sat with his eyes closed and his\nblack clay pipe thrusting out like the bill of some strange bird.\nI had come to the conclusion that he had dropped asleep, and\nindeed was nodding myself, when he suddenly sprang out of his\nchair with the gesture of a man who has made up his mind and put\nhis pipe down upon the mantelpiece.\n\n\"Sarasate plays at the St. James's Hall this afternoon,\" he\nremarked. \"What do you think, Watson? Could your patients spare\nyou for a few hours?\"\n\n\"I have nothing to do to-day. My practice is never very\nabsorbing.\"\n\n\"Then put on your hat and come. I am going through the City\nfirst, and we can have some lunch on the way. I observe that\nthere is a good deal of German music on the programme, which is\nrather more to my taste than Italian or French. It is\nintrospective, and I want to introspect. Come along!\"\n\nWe travelled by the Underground as far as Aldersgate; and a short\nwalk took us to Saxe-Coburg Square, the scene of the singular\nstory which we had listened to in the morning. It was a poky,\nlittle, shabby-genteel place, where four lines of dingy\ntwo-storied brick houses looked out into a small railed-in\nenclosure, where a lawn of weedy grass and a few clumps of faded\nlaurel-bushes made a hard fight against a smoke-laden and\nuncongenial atmosphere. Three gilt balls and a brown board with\n\"JABEZ WILSON\" in white letters, upon a corner house, announced\nthe place where our red-headed client carried on his business.\nSherlock Holmes stopped in front of it with his head on one side\nand looked it all over, with his eyes shining brightly between\npuckered lids. Then he walked slowly up the street, and then down\nagain to the corner, still looking keenly at the houses. Finally\nhe returned to the pawnbroker's, and, having thumped vigorously\nupon the pavement with his stick two or three times, he went up\nto the door and knocked. It was instantly opened by a\nbright-looking, clean-shaven young fellow, who asked him to step\nin.\n\n\"Thank you,\" said Holmes, \"I only wished to ask you how you would\ngo from here to the Strand.\"\n\n\"Third right, fourth left,\" answered the assistant promptly,\nclosing the door.\n\n\"Smart fellow, that,\" observed Holmes as we walked away. \"He is,\nin my judgment, the fourth smartest man in London, and for daring\nI am not sure that he has not a claim to be third. I have known\nsomething of him before.\"\n\n\"Evidently,\" said I, \"Mr. Wilson's assistant counts for a good\ndeal in this mystery of the Red-headed League. I am sure that you\ninquired your way merely in order that you might see him.\"\n\n\"Not him.\"\n\n\"What then?\"\n\n\"The knees of his trousers.\"\n\n\"And what did you see?\"\n\n\"What I expected to see.\"\n\n\"Why did you beat the pavement?\"\n\n\"My dear doctor, this is a time for observation, not for talk. We\nare spies in an enemy's country. We know something of Saxe-Coburg\nSquare. Let us now explore the parts which lie behind it.\"\n\nThe road in which we found ourselves as we turned round the\ncorner from the retired Saxe-Coburg Square presented as great a\ncontrast to it as the front of a picture does to the back. It was\none of the main arteries which conveyed the traffic of the City\nto the north and west. The roadway was blocked with the immense\nstream of commerce flowing in a double tide inward and outward,\nwhile the footpaths were black with the hurrying swarm of\npedestrians. It was difficult to realise as we looked at the line\nof fine shops and stately business premises that they really\nabutted on the other side upon the faded and stagnant square\nwhich we had just quitted.\n\n\"Let me see,\" said Holmes, standing at the corner and glancing\nalong the line, \"I should like just to remember the order of the\nhouses here. It is a hobby of mine to have an exact knowledge of\nLondon. There is Mortimer's, the tobacconist, the little\nnewspaper shop, the Coburg branch of the City and Suburban Bank,\nthe Vegetarian Restaurant, and McFarlane's carriage-building\ndepot. That carries us right on to the other block. And now,\nDoctor, we've done our work, so it's time we had some play. A\nsandwich and a cup of coffee, and then off to violin-land, where\nall is sweetness and delicacy and harmony, and there are no\nred-headed clients to vex us with their conundrums.\"\n\nMy friend was an enthusiastic musician, being himself not only a\nvery capable performer but a composer of no ordinary merit. All\nthe afternoon he sat in the stalls wrapped in the most perfect\nhappiness, gently waving his long, thin fingers in time to the\nmusic, while his gently smiling face and his languid, dreamy eyes\nwere as unlike those of Holmes the sleuth-hound, Holmes the\nrelentless, keen-witted, ready-handed criminal agent, as it was\npossible to conceive. In his singular character the dual nature\nalternately asserted itself, and his extreme exactness and\nastuteness represented, as I have often thought, the reaction\nagainst the poetic and contemplative mood which occasionally\npredominated in him. The swing of his nature took him from\nextreme languor to devouring energy; and, as I knew well, he was\nnever so truly formidable as when, for days on end, he had been\nlounging in his armchair amid his improvisations and his\nblack-letter editions. Then it was that the lust of the chase\nwould suddenly come upon him, and that his brilliant reasoning\npower would rise to the level of intuition, until those who were\nunacquainted with his methods would look askance at him as on a\nman whose knowledge was not that of other mortals. When I saw him\nthat afternoon so enwrapped in the music at St. James's Hall I\nfelt that an evil time might be coming upon those whom he had set\nhimself to hunt down.\n\n\"You want to go home, no doubt, Doctor,\" he remarked as we\nemerged.\n\n\"Yes, it would be as well.\"\n\n\"And I have some business to do which will take some hours. This\nbusiness at Coburg Square is serious.\"\n\n\"Why serious?\"\n\n\"A considerable crime is in contemplation. I have every reason to\nbelieve that we shall be in time to stop it. But to-day being\nSaturday rather complicates matters. I shall want your help\nto-night.\"\n\n\"At what time?\"\n\n\"Ten will be early enough.\"\n\n\"I shall be at Baker Street at ten.\"\n\n\"Very well. And, I say, Doctor, there may be some little danger,\nso kindly put your army revolver in your pocket.\" He waved his\nhand, turned on his heel, and disappeared in an instant among the\ncrowd.\n\nI trust that I am not more dense than my neighbours, but I was\nalways oppressed with a sense of my own stupidity in my dealings\nwith Sherlock Holmes. Here I had heard what he had heard, I had\nseen what he had seen, and yet from his words it was evident that\nhe saw clearly not only what had happened but what was about to\nhappen, while to me the whole business was still confused and\ngrotesque. As I drove home to my house in Kensington I thought\nover it all, from the extraordinary story of the red-headed\ncopier of the \"Encyclopaedia\" down to the visit to Saxe-Coburg\nSquare, and the ominous words with which he had parted from me.\nWhat was this nocturnal expedition, and why should I go armed?\nWhere were we going, and what were we to do? I had the hint from\nHolmes that this smooth-faced pawnbroker's assistant was a\nformidable man--a man who might play a deep game. I tried to\npuzzle it out, but gave it up in despair and set the matter aside\nuntil night should bring an explanation.\n\nIt was a quarter-past nine when I started from home and made my\nway across the Park, and so through Oxford Street to Baker\nStreet. Two hansoms were standing at the door, and as I entered\nthe passage I heard the sound of voices from above. On entering\nhis room I found Holmes in animated conversation with two men,\none of whom I recognised as Peter Jones, the official police\nagent, while the other was a long, thin, sad-faced man, with a\nvery shiny hat and oppressively respectable frock-coat.\n\n\"Ha! Our party is complete,\" said Holmes, buttoning up his\npea-jacket and taking his heavy hunting crop from the rack.\n\"Watson, I think you know Mr. Jones, of Scotland Yard? Let me\nintroduce you to Mr. Merryweather, who is to be our companion in\nto-night's adventure.\"\n\n\"We're hunting in couples again, Doctor, you see,\" said Jones in\nhis consequential way. \"Our friend here is a wonderful man for\nstarting a chase. All he wants is an old dog to help him to do\nthe running down.\"\n\n\"I hope a wild goose may not prove to be the end of our chase,\"\nobserved Mr. Merryweather gloomily.\n\n\"You may place considerable confidence in Mr. Holmes, sir,\" said\nthe police agent loftily. \"He has his own little methods, which\nare, if he won't mind my saying so, just a little too theoretical\nand fantastic, but he has the makings of a detective in him. It\nis not too much to say that once or twice, as in that business of\nthe Sholto murder and the Agra treasure, he has been more nearly\ncorrect than the official force.\"\n\n\"Oh, if you say so, Mr. Jones, it is all right,\" said the\nstranger with deference. \"Still, I confess that I miss my rubber.\nIt is the first Saturday night for seven-and-twenty years that I\nhave not had my rubber.\"\n\n\"I think you will find,\" said Sherlock Holmes, \"that you will\nplay for a higher stake to-night than you have ever done yet, and\nthat the play will be more exciting. For you, Mr. Merryweather,\nthe stake will be some 30,000 pounds; and for you, Jones, it will\nbe the man upon whom you wish to lay your hands.\"\n\n\"John Clay, the murderer, thief, smasher, and forger. He's a\nyoung man, Mr. Merryweather, but he is at the head of his\nprofession, and I would rather have my bracelets on him than on\nany criminal in London. He's a remarkable man, is young John\nClay. His grandfather was a royal duke, and he himself has been\nto Eton and Oxford. His brain is as cunning as his fingers, and\nthough we meet signs of him at every turn, we never know where to\nfind the man himself. He'll crack a crib in Scotland one week,\nand be raising money to build an orphanage in Cornwall the next.\nI've been on his track for years and have never set eyes on him\nyet.\"\n\n\"I hope that I may have the pleasure of introducing you to-night.\nI've had one or two little turns also with Mr. John Clay, and I\nagree with you that he is at the head of his profession. It is\npast ten, however, and quite time that we started. If you two\nwill take the first hansom, Watson and I will follow in the\nsecond.\"\n\nSherlock Holmes was not very communicative during the long drive\nand lay back in the cab humming the tunes which he had heard in\nthe afternoon. We rattled through an endless labyrinth of gas-lit\nstreets until we emerged into Farrington Street.\n\n\"We are close there now,\" my friend remarked. \"This fellow\nMerryweather is a bank director, and personally interested in the\nmatter. I thought it as well to have Jones with us also. He is\nnot a bad fellow, though an absolute imbecile in his profession.\nHe has one positive virtue. He is as brave as a bulldog and as\ntenacious as a lobster if he gets his claws upon anyone. Here we\nare, and they are waiting for us.\"\n\nWe had reached the same crowded thoroughfare in which we had\nfound ourselves in the morning. Our cabs were dismissed, and,\nfollowing the guidance of Mr. Merryweather, we passed down a\nnarrow passage and through a side door, which he opened for us.\nWithin there was a small corridor, which ended in a very massive\niron gate. This also was opened, and led down a flight of winding\nstone steps, which terminated at another formidable gate. Mr.\nMerryweather stopped to light a lantern, and then conducted us\ndown a dark, earth-smelling passage, and so, after opening a\nthird door, into a huge vault or cellar, which was piled all\nround with crates and massive boxes.\n\n\"You are not very vulnerable from above,\" Holmes remarked as he\nheld up the lantern and gazed about him.\n\n\"Nor from below,\" said Mr. Merryweather, striking his stick upon\nthe flags which lined the floor. \"Why, dear me, it sounds quite\nhollow!\" he remarked, looking up in surprise.\n\n\"I must really ask you to be a little more quiet!\" said Holmes\nseverely. \"You have already imperilled the whole success of our\nexpedition. Might I beg that you would have the goodness to sit\ndown upon one of those boxes, and not to interfere?\"\n\nThe solemn Mr. Merryweather perched himself upon a crate, with a\nvery injured expression upon his face, while Holmes fell upon his\nknees upon the floor and, with the lantern and a magnifying lens,\nbegan to examine minutely the cracks between the stones. A few\nseconds sufficed to satisfy him, for he sprang to his feet again\nand put his glass in his pocket.\n\n\"We have at least an hour before us,\" he remarked, \"for they can\nhardly take any steps until the good pawnbroker is safely in bed.\nThen they will not lose a minute, for the sooner they do their\nwork the longer time they will have for their escape. We are at\npresent, Doctor--as no doubt you have divined--in the cellar of\nthe City branch of one of the principal London banks. Mr.\nMerryweather is the chairman of directors, and he will explain to\nyou that there are reasons why the more daring criminals of\nLondon should take a considerable interest in this cellar at\npresent.\"\n\n\"It is our French gold,\" whispered the director. \"We have had\nseveral warnings that an attempt might be made upon it.\"\n\n\"Your French gold?\"\n\n\"Yes. We had occasion some months ago to strengthen our resources\nand borrowed for that purpose 30,000 napoleons from the Bank of\nFrance. It has become known that we have never had occasion to\nunpack the money, and that it is still lying in our cellar. The\ncrate upon which I sit contains 2,000 napoleons packed between\nlayers of lead foil. Our reserve of bullion is much larger at\npresent than is usually kept in a single branch office, and the\ndirectors have had misgivings upon the subject.\"\n\n\"Which were very well justified,\" observed Holmes. \"And now it is\ntime that we arranged our little plans. I expect that within an\nhour matters will come to a head. In the meantime Mr.\nMerryweather, we must put the screen over that dark lantern.\"\n\n\"And sit in the dark?\"\n\n\"I am afraid so. I had brought a pack of cards in my pocket, and\nI thought that, as we were a partie carrée, you might have your\nrubber after all. But I see that the enemy's preparations have\ngone so far that we cannot risk the presence of a light. And,\nfirst of all, we must choose our positions. These are daring men,\nand though we shall take them at a disadvantage, they may do us\nsome harm unless we are careful. I shall stand behind this crate,\nand do you conceal yourselves behind those. Then, when I flash a\nlight upon them, close in swiftly. If they fire, Watson, have no\ncompunction about shooting them down.\"\n\nI placed my revolver, cocked, upon the top of the wooden case\nbehind which I crouched. Holmes shot the slide across the front\nof his lantern and left us in pitch darkness--such an absolute\ndarkness as I have never before experienced. The smell of hot\nmetal remained to assure us that the light was still there, ready\nto flash out at a moment's notice. To me, with my nerves worked\nup to a pitch of expectancy, there was something depressing and\nsubduing in the sudden gloom, and in the cold dank air of the\nvault.\n\n\"They have but one retreat,\" whispered Holmes. \"That is back\nthrough the house into Saxe-Coburg Square. I hope that you have\ndone what I asked you, Jones?\"\n\n\"I have an inspector and two officers waiting at the front door.\"\n\n\"Then we have stopped all the holes. And now we must be silent\nand wait.\"\n\nWhat a time it seemed! From comparing notes afterwards it was but\nan hour and a quarter, yet it appeared to me that the night must\nhave almost gone and the dawn be breaking above us. My limbs\nwere weary and stiff, for I feared to change my position; yet my\nnerves were worked up to the highest pitch of tension, and my\nhearing was so acute that I could not only hear the gentle\nbreathing of my companions, but I could distinguish the deeper,\nheavier in-breath of the bulky Jones from the thin, sighing note\nof the bank director. From my position I could look over the case\nin the direction of the floor. Suddenly my eyes caught the glint\nof a light.\n\nAt first it was but a lurid spark upon the stone pavement. Then\nit lengthened out until it became a yellow line, and then,\nwithout any warning or sound, a gash seemed to open and a hand\nappeared, a white, almost womanly hand, which felt about in the\ncentre of the little area of light. For a minute or more the\nhand, with its writhing fingers, protruded out of the floor. Then\nit was withdrawn as suddenly as it appeared, and all was dark\nagain save the single lurid spark which marked a chink between\nthe stones.\n\nIts disappearance, however, was but momentary. With a rending,\ntearing sound, one of the broad, white stones turned over upon\nits side and left a square, gaping hole, through which streamed\nthe light of a lantern. Over the edge there peeped a clean-cut,\nboyish face, which looked keenly about it, and then, with a hand\non either side of the aperture, drew itself shoulder-high and\nwaist-high, until one knee rested upon the edge. In another\ninstant he stood at the side of the hole and was hauling after\nhim a companion, lithe and small like himself, with a pale face\nand a shock of very red hair.\n\n\"It's all clear,\" he whispered. \"Have you the chisel and the\nbags? Great Scott! Jump, Archie, jump, and I'll swing for it!\"\n\nSherlock Holmes had sprung out and seized the intruder by the\ncollar. The other dived down the hole, and I heard the sound of\nrending cloth as Jones clutched at his skirts. The light flashed\nupon the barrel of a revolver, but Holmes' hunting crop came\ndown on the man's wrist, and the pistol clinked upon the stone\nfloor.\n\n\"It's no use, John Clay,\" said Holmes blandly. \"You have no\nchance at all.\"\n\n\"So I see,\" the other answered with the utmost coolness. \"I fancy\nthat my pal is all right, though I see you have got his\ncoat-tails.\"\n\n\"There are three men waiting for him at the door,\" said Holmes.\n\n\"Oh, indeed! You seem to have done the thing very completely. I\nmust compliment you.\"\n\n\"And I you,\" Holmes answered. \"Your red-headed idea was very new\nand effective.\"\n\n\"You'll see your pal again presently,\" said Jones. \"He's quicker\nat climbing down holes than I am. Just hold out while I fix the\nderbies.\"\n\n\"I beg that you will not touch me with your filthy hands,\"\nremarked our prisoner as the handcuffs clattered upon his wrists.\n\"You may not be aware that I have royal blood in my veins. Have\nthe goodness, also, when you address me always to say 'sir' and\n'please.'\"\n\n\"All right,\" said Jones with a stare and a snigger. \"Well, would\nyou please, sir, march upstairs, where we can get a cab to carry\nyour Highness to the police-station?\"\n\n\"That is better,\" said John Clay serenely. He made a sweeping bow\nto the three of us and walked quietly off in the custody of the\ndetective.\n\n\"Really, Mr. Holmes,\" said Mr. Merryweather as we followed them\nfrom the cellar, \"I do not know how the bank can thank you or\nrepay you. There is no doubt that you have detected and defeated\nin the most complete manner one of the most determined attempts\nat bank robbery that have ever come within my experience.\"\n\n\"I have had one or two little scores of my own to settle with Mr.\nJohn Clay,\" said Holmes. \"I have been at some small expense over\nthis matter, which I shall expect the bank to refund, but beyond\nthat I am amply repaid by having had an experience which is in\nmany ways unique, and by hearing the very remarkable narrative of\nthe Red-headed League.\"\n\n\n\"You see, Watson,\" he explained in the early hours of the morning\nas we sat over a glass of whisky and soda in Baker Street, \"it\nwas perfectly obvious from the first that the only possible\nobject of this rather fantastic business of the advertisement of\nthe League, and the copying of the 'Encyclopaedia,' must be to get\nthis not over-bright pawnbroker out of the way for a number of\nhours every day. It was a curious way of managing it, but,\nreally, it would be difficult to suggest a better. The method was\nno doubt suggested to Clay's ingenious mind by the colour of his\naccomplice's hair. The 4 pounds a week was a lure which must draw\nhim, and what was it to them, who were playing for thousands?\nThey put in the advertisement, one rogue has the temporary\noffice, the other rogue incites the man to apply for it, and\ntogether they manage to secure his absence every morning in the\nweek. From the time that I heard of the assistant having come for\nhalf wages, it was obvious to me that he had some strong motive\nfor securing the situation.\"\n\n\"But how could you guess what the motive was?\"\n\n\"Had there been women in the house, I should have suspected a\nmere vulgar intrigue. That, however, was out of the question. The\nman's business was a small one, and there was nothing in his\nhouse which could account for such elaborate preparations, and\nsuch an expenditure as they were at. It must, then, be something\nout of the house. What could it be? I thought of the assistant's\nfondness for photography, and his trick of vanishing into the\ncellar. The cellar! There was the end of this tangled clue. Then\nI made inquiries as to this mysterious assistant and found that I\nhad to deal with one of the coolest and most daring criminals in\nLondon. He was doing something in the cellar--something which\ntook many hours a day for months on end. What could it be, once\nmore? I could think of nothing save that he was running a tunnel\nto some other building.\n\n\"So far I had got when we went to visit the scene of action. I\nsurprised you by beating upon the pavement with my stick. I was\nascertaining whether the cellar stretched out in front or behind.\nIt was not in front. Then I rang the bell, and, as I hoped, the\nassistant answered it. We have had some skirmishes, but we had\nnever set eyes upon each other before. I hardly looked at his\nface. His knees were what I wished to see. You must yourself have\nremarked how worn, wrinkled, and stained they were. They spoke of\nthose hours of burrowing. The only remaining point was what they\nwere burrowing for. I walked round the corner, saw the City and\nSuburban Bank abutted on our friend's premises, and felt that I\nhad solved my problem. When you drove home after the concert I\ncalled upon Scotland Yard and upon the chairman of the bank\ndirectors, with the result that you have seen.\"\n\n\"And how could you tell that they would make their attempt\nto-night?\" I asked.\n\n\"Well, when they closed their League offices that was a sign that\nthey cared no longer about Mr. Jabez Wilson's presence--in other\nwords, that they had completed their tunnel. But it was essential\nthat they should use it soon, as it might be discovered, or the\nbullion might be removed. Saturday would suit them better than\nany other day, as it would give them two days for their escape.\nFor all these reasons I expected them to come to-night.\"\n\n\"You reasoned it out beautifully,\" I exclaimed in unfeigned\nadmiration. \"It is so long a chain, and yet every link rings\ntrue.\"\n\n\"It saved me from ennui,\" he answered, yawning. \"Alas! I already\nfeel it closing in upon me. My life is spent in one long effort\nto escape from the commonplaces of existence. These little\nproblems help me to do so.\"\n\n\"And you are a benefactor of the race,\" said I.\n\nHe shrugged his shoulders. \"Well, perhaps, after all, it is of\nsome little use,\" he remarked. \"'L'homme c'est rien--l'oeuvre\nc'est tout,' as Gustave Flaubert wrote to George Sand.\"\n\n\n\nADVENTURE III. A CASE OF IDENTITY\n\n\"My dear fellow,\" said Sherlock Holmes as we sat on either side\nof the fire in his lodgings at Baker Street, \"life is infinitely\nstranger than anything which the mind of man could invent. We\nwould not dare to conceive the things which are really mere\ncommonplaces of existence. If we could fly out of that window\nhand in hand, hover over this great city, gently remove the\nroofs, and peep in at the queer things which are going on, the\nstrange coincidences, the plannings, the cross-purposes, the\nwonderful chains of events, working through generations, and\nleading to the most outré results, it would make all fiction with\nits conventionalities and foreseen conclusions most stale and\nunprofitable.\"\n\n\"And yet I am not convinced of it,\" I answered. \"The cases which\ncome to light in the papers are, as a rule, bald enough, and\nvulgar enough. We have in our police reports realism pushed to\nits extreme limits, and yet the result is, it must be confessed,\nneither fascinating nor artistic.\"\n\n\"A certain selection and discretion must be used in producing a\nrealistic effect,\" remarked Holmes. \"This is wanting in the\npolice report, where more stress is laid, perhaps, upon the\nplatitudes of the magistrate than upon the details, which to an\nobserver contain the vital essence of the whole matter. Depend\nupon it, there is nothing so unnatural as the commonplace.\"\n\nI smiled and shook my head. \"I can quite understand your thinking\nso,\" I said. \"Of course, in your position of unofficial adviser\nand helper to everybody who is absolutely puzzled, throughout\nthree continents, you are brought in contact with all that is\nstrange and bizarre. But here\"--I picked up the morning paper\nfrom the ground--\"let us put it to a practical test. Here is the\nfirst heading upon which I come. 'A husband's cruelty to his\nwife.' There is half a column of print, but I know without\nreading it that it is all perfectly familiar to me. There is, of\ncourse, the other woman, the drink, the push, the blow, the\nbruise, the sympathetic sister or landlady. The crudest of\nwriters could invent nothing more crude.\"\n\n\"Indeed, your example is an unfortunate one for your argument,\"\nsaid Holmes, taking the paper and glancing his eye down it. \"This\nis the Dundas separation case, and, as it happens, I was engaged\nin clearing up some small points in connection with it. The\nhusband was a teetotaler, there was no other woman, and the\nconduct complained of was that he had drifted into the habit of\nwinding up every meal by taking out his false teeth and hurling\nthem at his wife, which, you will allow, is not an action likely\nto occur to the imagination of the average story-teller. Take a\npinch of snuff, Doctor, and acknowledge that I have scored over\nyou in your example.\"\n\nHe held out his snuffbox of old gold, with a great amethyst in\nthe centre of the lid. Its splendour was in such contrast to his\nhomely ways and simple life that I could not help commenting upon\nit.\n\n\"Ah,\" said he, \"I forgot that I had not seen you for some weeks.\nIt is a little souvenir from the King of Bohemia in return for my\nassistance in the case of the Irene Adler papers.\"\n\n\"And the ring?\" I asked, glancing at a remarkable brilliant which\nsparkled upon his finger.\n\n\"It was from the reigning family of Holland, though the matter in\nwhich I served them was of such delicacy that I cannot confide it\neven to you, who have been good enough to chronicle one or two of\nmy little problems.\"\n\n\"And have you any on hand just now?\" I asked with interest.\n\n\"Some ten or twelve, but none which present any feature of\ninterest. They are important, you understand, without being\ninteresting. Indeed, I have found that it is usually in\nunimportant matters that there is a field for the observation,\nand for the quick analysis of cause and effect which gives the\ncharm to an investigation. The larger crimes are apt to be the\nsimpler, for the bigger the crime the more obvious, as a rule, is\nthe motive. In these cases, save for one rather intricate matter\nwhich has been referred to me from Marseilles, there is nothing\nwhich presents any features of interest. It is possible, however,\nthat I may have something better before very many minutes are\nover, for this is one of my clients, or I am much mistaken.\"\n\nHe had risen from his chair and was standing between the parted\nblinds gazing down into the dull neutral-tinted London street.\nLooking over his shoulder, I saw that on the pavement opposite\nthere stood a large woman with a heavy fur boa round her neck,\nand a large curling red feather in a broad-brimmed hat which was\ntilted in a coquettish Duchess of Devonshire fashion over her\near. From under this great panoply she peeped up in a nervous,\nhesitating fashion at our windows, while her body oscillated\nbackward and forward, and her fingers fidgeted with her glove\nbuttons. Suddenly, with a plunge, as of the swimmer who leaves\nthe bank, she hurried across the road, and we heard the sharp\nclang of the bell.\n\n\"I have seen those symptoms before,\" said Holmes, throwing his\ncigarette into the fire. \"Oscillation upon the pavement always\nmeans an affaire de coeur. She would like advice, but is not sure\nthat the matter is not too delicate for communication. And yet\neven here we may discriminate. When a woman has been seriously\nwronged by a man she no longer oscillates, and the usual symptom\nis a broken bell wire. Here we may take it that there is a love\nmatter, but that the maiden is not so much angry as perplexed, or\ngrieved. But here she comes in person to resolve our doubts.\"\n\nAs he spoke there was a tap at the door, and the boy in buttons\nentered to announce Miss Mary Sutherland, while the lady herself\nloomed behind his small black figure like a full-sailed\nmerchant-man behind a tiny pilot boat. Sherlock Holmes welcomed\nher with the easy courtesy for which he was remarkable, and,\nhaving closed the door and bowed her into an armchair, he looked\nher over in the minute and yet abstracted fashion which was\npeculiar to him.\n\n\"Do you not find,\" he said, \"that with your short sight it is a\nlittle trying to do so much typewriting?\"\n\n\"I did at first,\" she answered, \"but now I know where the letters\nare without looking.\" Then, suddenly realising the full purport\nof his words, she gave a violent start and looked up, with fear\nand astonishment upon her broad, good-humoured face. \"You've\nheard about me, Mr. Holmes,\" she cried, \"else how could you know\nall that?\"\n\n\"Never mind,\" said Holmes, laughing; \"it is my business to know\nthings. Perhaps I have trained myself to see what others\noverlook. If not, why should you come to consult me?\"\n\n\"I came to you, sir, because I heard of you from Mrs. Etherege,\nwhose husband you found so easy when the police and everyone had\ngiven him up for dead. Oh, Mr. Holmes, I wish you would do as\nmuch for me. I'm not rich, but still I have a hundred a year in\nmy own right, besides the little that I make by the machine, and\nI would give it all to know what has become of Mr. Hosmer Angel.\"\n\n\"Why did you come away to consult me in such a hurry?\" asked\nSherlock Holmes, with his finger-tips together and his eyes to\nthe ceiling.\n\nAgain a startled look came over the somewhat vacuous face of Miss\nMary Sutherland. \"Yes, I did bang out of the house,\" she said,\n\"for it made me angry to see the easy way in which Mr.\nWindibank--that is, my father--took it all. He would not go to\nthe police, and he would not go to you, and so at last, as he\nwould do nothing and kept on saying that there was no harm done,\nit made me mad, and I just on with my things and came right away\nto you.\"\n\n\"Your father,\" said Holmes, \"your stepfather, surely, since the\nname is different.\"\n\n\"Yes, my stepfather. I call him father, though it sounds funny,\ntoo, for he is only five years and two months older than myself.\"\n\n\"And your mother is alive?\"\n\n\"Oh, yes, mother is alive and well. I wasn't best pleased, Mr.\nHolmes, when she married again so soon after father's death, and\na man who was nearly fifteen years younger than herself. Father\nwas a plumber in the Tottenham Court Road, and he left a tidy\nbusiness behind him, which mother carried on with Mr. Hardy, the\nforeman; but when Mr. Windibank came he made her sell the\nbusiness, for he was very superior, being a traveller in wines.\nThey got 4700 pounds for the goodwill and interest, which wasn't\nnear as much as father could have got if he had been alive.\"\n\nI had expected to see Sherlock Holmes impatient under this\nrambling and inconsequential narrative, but, on the contrary, he\nhad listened with the greatest concentration of attention.\n\n\"Your own little income,\" he asked, \"does it come out of the\nbusiness?\"\n\n\"Oh, no, sir. It is quite separate and was left me by my uncle\nNed in Auckland. It is in New Zealand stock, paying 4 1/2 per\ncent. Two thousand five hundred pounds was the amount, but I can\nonly touch the interest.\"\n\n\"You interest me extremely,\" said Holmes. \"And since you draw so\nlarge a sum as a hundred a year, with what you earn into the\nbargain, you no doubt travel a little and indulge yourself in\nevery way. I believe that a single lady can get on very nicely\nupon an income of about 60 pounds.\"\n\n\"I could do with much less than that, Mr. Holmes, but you\nunderstand that as long as I live at home I don't wish to be a\nburden to them, and so they have the use of the money just while\nI am staying with them. Of course, that is only just for the\ntime. Mr. Windibank draws my interest every quarter and pays it\nover to mother, and I find that I can do pretty well with what I\nearn at typewriting. It brings me twopence a sheet, and I can\noften do from fifteen to twenty sheets in a day.\"\n\n\"You have made your position very clear to me,\" said Holmes.\n\"This is my friend, Dr. Watson, before whom you can speak as\nfreely as before myself. Kindly tell us now all about your\nconnection with Mr. Hosmer Angel.\"\n\nA flush stole over Miss Sutherland's face, and she picked\nnervously at the fringe of her jacket. \"I met him first at the\ngasfitters' ball,\" she said. \"They used to send father tickets\nwhen he was alive, and then afterwards they remembered us, and\nsent them to mother. Mr. Windibank did not wish us to go. He\nnever did wish us to go anywhere. He would get quite mad if I\nwanted so much as to join a Sunday-school treat. But this time I\nwas set on going, and I would go; for what right had he to\nprevent? He said the folk were not fit for us to know, when all\nfather's friends were to be there. And he said that I had nothing\nfit to wear, when I had my purple plush that I had never so much\nas taken out of the drawer. At last, when nothing else would do,\nhe went off to France upon the business of the firm, but we went,\nmother and I, with Mr. Hardy, who used to be our foreman, and it\nwas there I met Mr. Hosmer Angel.\"\n\n\"I suppose,\" said Holmes, \"that when Mr. Windibank came back from\nFrance he was very annoyed at your having gone to the ball.\"\n\n\"Oh, well, he was very good about it. He laughed, I remember, and\nshrugged his shoulders, and said there was no use denying\nanything to a woman, for she would have her way.\"\n\n\"I see. Then at the gasfitters' ball you met, as I understand, a\ngentleman called Mr. Hosmer Angel.\"\n\n\"Yes, sir. I met him that night, and he called next day to ask if\nwe had got home all safe, and after that we met him--that is to\nsay, Mr. Holmes, I met him twice for walks, but after that father\ncame back again, and Mr. Hosmer Angel could not come to the house\nany more.\"\n\n\"No?\"\n\n\"Well, you know father didn't like anything of the sort. He\nwouldn't have any visitors if he could help it, and he used to\nsay that a woman should be happy in her own family circle. But\nthen, as I used to say to mother, a woman wants her own circle to\nbegin with, and I had not got mine yet.\"\n\n\"But how about Mr. Hosmer Angel? Did he make no attempt to see\nyou?\"\n\n\"Well, father was going off to France again in a week, and Hosmer\nwrote and said that it would be safer and better not to see each\nother until he had gone. We could write in the meantime, and he\nused to write every day. I took the letters in in the morning, so\nthere was no need for father to know.\"\n\n\"Were you engaged to the gentleman at this time?\"\n\n\"Oh, yes, Mr. Holmes. We were engaged after the first walk that\nwe took. Hosmer--Mr. Angel--was a cashier in an office in\nLeadenhall Street--and--\"\n\n\"What office?\"\n\n\"That's the worst of it, Mr. Holmes, I don't know.\"\n\n\"Where did he live, then?\"\n\n\"He slept on the premises.\"\n\n\"And you don't know his address?\"\n\n\"No--except that it was Leadenhall Street.\"\n\n\"Where did you address your letters, then?\"\n\n\"To the Leadenhall Street Post Office, to be left till called\nfor. He said that if they were sent to the office he would be\nchaffed by all the other clerks about having letters from a lady,\nso I offered to typewrite them, like he did his, but he wouldn't\nhave that, for he said that when I wrote them they seemed to come\nfrom me, but when they were typewritten he always felt that the\nmachine had come between us. That will just show you how fond he\nwas of me, Mr. Holmes, and the little things that he would think\nof.\"\n\n\"It was most suggestive,\" said Holmes. \"It has long been an axiom\nof mine that the little things are infinitely the most important.\nCan you remember any other little things about Mr. Hosmer Angel?\"\n\n\"He was a very shy man, Mr. Holmes. He would rather walk with me\nin the evening than in the daylight, for he said that he hated to\nbe conspicuous. Very retiring and gentlemanly he was. Even his\nvoice was gentle. He'd had the quinsy and swollen glands when he\nwas young, he told me, and it had left him with a weak throat,\nand a hesitating, whispering fashion of speech. He was always\nwell dressed, very neat and plain, but his eyes were weak, just\nas mine are, and he wore tinted glasses against the glare.\"\n\n\"Well, and what happened when Mr. Windibank, your stepfather,\nreturned to France?\"\n\n\"Mr. Hosmer Angel came to the house again and proposed that we\nshould marry before father came back. He was in dreadful earnest\nand made me swear, with my hands on the Testament, that whatever\nhappened I would always be true to him. Mother said he was quite\nright to make me swear, and that it was a sign of his passion.\nMother was all in his favour from the first and was even fonder\nof him than I was. Then, when they talked of marrying within the\nweek, I began to ask about father; but they both said never to\nmind about father, but just to tell him afterwards, and mother\nsaid she would make it all right with him. I didn't quite like\nthat, Mr. Holmes. It seemed funny that I should ask his leave, as\nhe was only a few years older than me; but I didn't want to do\nanything on the sly, so I wrote to father at Bordeaux, where the\ncompany has its French offices, but the letter came back to me on\nthe very morning of the wedding.\"\n\n\"It missed him, then?\"\n\n\"Yes, sir; for he had started to England just before it arrived.\"\n\n\"Ha! that was unfortunate. Your wedding was arranged, then, for\nthe Friday. Was it to be in church?\"\n\n\"Yes, sir, but very quietly. It was to be at St. Saviour's, near\nKing's Cross, and we were to have breakfast afterwards at the St.\nPancras Hotel. Hosmer came for us in a hansom, but as there were\ntwo of us he put us both into it and stepped himself into a\nfour-wheeler, which happened to be the only other cab in the\nstreet. We got to the church first, and when the four-wheeler\ndrove up we waited for him to step out, but he never did, and\nwhen the cabman got down from the box and looked there was no one\nthere! The cabman said that he could not imagine what had become\nof him, for he had seen him get in with his own eyes. That was\nlast Friday, Mr. Holmes, and I have never seen or heard anything\nsince then to throw any light upon what became of him.\"\n\n\"It seems to me that you have been very shamefully treated,\" said\nHolmes.\n\n\"Oh, no, sir! He was too good and kind to leave me so. Why, all\nthe morning he was saying to me that, whatever happened, I was to\nbe true; and that even if something quite unforeseen occurred to\nseparate us, I was always to remember that I was pledged to him,\nand that he would claim his pledge sooner or later. It seemed\nstrange talk for a wedding-morning, but what has happened since\ngives a meaning to it.\"\n\n\"Most certainly it does. Your own opinion is, then, that some\nunforeseen catastrophe has occurred to him?\"\n\n\"Yes, sir. I believe that he foresaw some danger, or else he\nwould not have talked so. And then I think that what he foresaw\nhappened.\"\n\n\"But you have no notion as to what it could have been?\"\n\n\"None.\"\n\n\"One more question. How did your mother take the matter?\"\n\n\"She was angry, and said that I was never to speak of the matter\nagain.\"\n\n\"And your father? Did you tell him?\"\n\n\"Yes; and he seemed to think, with me, that something had\nhappened, and that I should hear of Hosmer again. As he said,\nwhat interest could anyone have in bringing me to the doors of\nthe church, and then leaving me? Now, if he had borrowed my\nmoney, or if he had married me and got my money settled on him,\nthere might be some reason, but Hosmer was very independent about\nmoney and never would look at a shilling of mine. And yet, what\ncould have happened? And why could he not write? Oh, it drives me\nhalf-mad to think of it, and I can't sleep a wink at night.\" She\npulled a little handkerchief out of her muff and began to sob\nheavily into it.\n\n\"I shall glance into the case for you,\" said Holmes, rising, \"and\nI have no doubt that we shall reach some definite result. Let the\nweight of the matter rest upon me now, and do not let your mind\ndwell upon it further. Above all, try to let Mr. Hosmer Angel\nvanish from your memory, as he has done from your life.\"\n\n\"Then you don't think I'll see him again?\"\n\n\"I fear not.\"\n\n\"Then what has happened to him?\"\n\n\"You will leave that question in my hands. I should like an\naccurate description of him and any letters of his which you can\nspare.\"\n\n\"I advertised for him in last Saturday's Chronicle,\" said she.\n\"Here is the slip and here are four letters from him.\"\n\n\"Thank you. And your address?\"\n\n\"No. 31 Lyon Place, Camberwell.\"\n\n\"Mr. Angel's address you never had, I understand. Where is your\nfather's place of business?\"\n\n\"He travels for Westhouse & Marbank, the great claret importers\nof Fenchurch Street.\"\n\n\"Thank you. You have made your statement very clearly. You will\nleave the papers here, and remember the advice which I have given\nyou. Let the whole incident be a sealed book, and do not allow it\nto affect your life.\"\n\n\"You are very kind, Mr. Holmes, but I cannot do that. I shall be\ntrue to Hosmer. He shall find me ready when he comes back.\"\n\nFor all the preposterous hat and the vacuous face, there was\nsomething noble in the simple faith of our visitor which\ncompelled our respect. She laid her little bundle of papers upon\nthe table and went her way, with a promise to come again whenever\nshe might be summoned.\n\nSherlock Holmes sat silent for a few minutes with his fingertips\nstill pressed together, his legs stretched out in front of him,\nand his gaze directed upward to the ceiling. Then he took down\nfrom the rack the old and oily clay pipe, which was to him as a\ncounsellor, and, having lit it, he leaned back in his chair, with\nthe thick blue cloud-wreaths spinning up from him, and a look of\ninfinite languor in his face.\n\n\"Quite an interesting study, that maiden,\" he observed. \"I found\nher more interesting than her little problem, which, by the way,\nis rather a trite one. You will find parallel cases, if you\nconsult my index, in Andover in '77, and there was something of\nthe sort at The Hague last year. Old as is the idea, however,\nthere were one or two details which were new to me. But the\nmaiden herself was most instructive.\"\n\n\"You appeared to read a good deal upon her which was quite\ninvisible to me,\" I remarked.\n\n\"Not invisible but unnoticed, Watson. You did not know where to\nlook, and so you missed all that was important. I can never bring\nyou to realise the importance of sleeves, the suggestiveness of\nthumb-nails, or the great issues that may hang from a boot-lace.\nNow, what did you gather from that woman's appearance? Describe\nit.\"\n\n\"Well, she had a slate-coloured, broad-brimmed straw hat, with a\nfeather of a brickish red. Her jacket was black, with black beads\nsewn upon it, and a fringe of little black jet ornaments. Her\ndress was brown, rather darker than coffee colour, with a little\npurple plush at the neck and sleeves. Her gloves were greyish and\nwere worn through at the right forefinger. Her boots I didn't\nobserve. She had small round, hanging gold earrings, and a\ngeneral air of being fairly well-to-do in a vulgar, comfortable,\neasy-going way.\"\n\nSherlock Holmes clapped his hands softly together and chuckled.\n\n\"'Pon my word, Watson, you are coming along wonderfully. You have\nreally done very well indeed. It is true that you have missed\neverything of importance, but you have hit upon the method, and\nyou have a quick eye for colour. Never trust to general\nimpressions, my boy, but concentrate yourself upon details. My\nfirst glance is always at a woman's sleeve. In a man it is\nperhaps better first to take the knee of the trouser. As you\nobserve, this woman had plush upon her sleeves, which is a most\nuseful material for showing traces. The double line a little\nabove the wrist, where the typewritist presses against the table,\nwas beautifully defined. The sewing-machine, of the hand type,\nleaves a similar mark, but only on the left arm, and on the side\nof it farthest from the thumb, instead of being right across the\nbroadest part, as this was. I then glanced at her face, and,\nobserving the dint of a pince-nez at either side of her nose, I\nventured a remark upon short sight and typewriting, which seemed\nto surprise her.\"\n\n\"It surprised me.\"\n\n\"But, surely, it was obvious. I was then much surprised and\ninterested on glancing down to observe that, though the boots\nwhich she was wearing were not unlike each other, they were\nreally odd ones; the one having a slightly decorated toe-cap, and\nthe other a plain one. One was buttoned only in the two lower\nbuttons out of five, and the other at the first, third, and\nfifth. Now, when you see that a young lady, otherwise neatly\ndressed, has come away from home with odd boots, half-buttoned,\nit is no great deduction to say that she came away in a hurry.\"\n\n\"And what else?\" I asked, keenly interested, as I always was, by\nmy friend's incisive reasoning.\n\n\"I noted, in passing, that she had written a note before leaving\nhome but after being fully dressed. You observed that her right\nglove was torn at the forefinger, but you did not apparently see\nthat both glove and finger were stained with violet ink. She had\nwritten in a hurry and dipped her pen too deep. It must have been\nthis morning, or the mark would not remain clear upon the finger.\nAll this is amusing, though rather elementary, but I must go back\nto business, Watson. Would you mind reading me the advertised\ndescription of Mr. Hosmer Angel?\"\n\nI held the little printed slip to the light.\n\n\"Missing,\" it said, \"on the morning of the fourteenth, a gentleman\nnamed Hosmer Angel. About five ft. seven in. in height;\nstrongly built, sallow complexion, black hair, a little bald in\nthe centre, bushy, black side-whiskers and moustache; tinted\nglasses, slight infirmity of speech. Was dressed, when last seen,\nin black frock-coat faced with silk, black waistcoat, gold Albert\nchain, and grey Harris tweed trousers, with brown gaiters over\nelastic-sided boots. Known to have been employed in an office in\nLeadenhall Street. Anybody bringing--\"\n\n\"That will do,\" said Holmes. \"As to the letters,\" he continued,\nglancing over them, \"they are very commonplace. Absolutely no\nclue in them to Mr. Angel, save that he quotes Balzac once. There\nis one remarkable point, however, which will no doubt strike\nyou.\"\n\n\"They are typewritten,\" I remarked.\n\n\"Not only that, but the signature is typewritten. Look at the\nneat little 'Hosmer Angel' at the bottom. There is a date, you\nsee, but no superscription except Leadenhall Street, which is\nrather vague. The point about the signature is very suggestive--in\nfact, we may call it conclusive.\"\n\n\"Of what?\"\n\n\"My dear fellow, is it possible you do not see how strongly it\nbears upon the case?\"\n\n\"I cannot say that I do unless it were that he wished to be able\nto deny his signature if an action for breach of promise were\ninstituted.\"\n\n\"No, that was not the point. However, I shall write two letters,\nwhich should settle the matter. One is to a firm in the City, the\nother is to the young lady's stepfather, Mr. Windibank, asking\nhim whether he could meet us here at six o'clock tomorrow\nevening. It is just as well that we should do business with the\nmale relatives. And now, Doctor, we can do nothing until the\nanswers to those letters come, so we may put our little problem\nupon the shelf for the interim.\"\n\nI had had so many reasons to believe in my friend's subtle powers\nof reasoning and extraordinary energy in action that I felt that\nhe must have some solid grounds for the assured and easy\ndemeanour with which he treated the singular mystery which he had\nbeen called upon to fathom. Once only had I known him to fail, in\nthe case of the King of Bohemia and of the Irene Adler\nphotograph; but when I looked back to the weird business of the\nSign of Four, and the extraordinary circumstances connected with\nthe Study in Scarlet, I felt that it would be a strange tangle\nindeed which he could not unravel.\n\nI left him then, still puffing at his black clay pipe, with the\nconviction that when I came again on the next evening I would\nfind that he held in his hands all the clues which would lead up\nto the identity of the disappearing bridegroom of Miss Mary\nSutherland.\n\nA professional case of great gravity was engaging my own\nattention at the time, and the whole of next day I was busy at\nthe bedside of the sufferer. It was not until close upon six\no'clock that I found myself free and was able to spring into a\nhansom and drive to Baker Street, half afraid that I might be too\nlate to assist at the dénouement of the little mystery. I found\nSherlock Holmes alone, however, half asleep, with his long, thin\nform curled up in the recesses of his armchair. A formidable\narray of bottles and test-tubes, with the pungent cleanly smell\nof hydrochloric acid, told me that he had spent his day in the\nchemical work which was so dear to him.\n\n\"Well, have you solved it?\" I asked as I entered.\n\n\"Yes. It was the bisulphate of baryta.\"\n\n\"No, no, the mystery!\" I cried.\n\n\"Oh, that! I thought of the salt that I have been working upon.\nThere was never any mystery in the matter, though, as I said\nyesterday, some of the details are of interest. The only drawback\nis that there is no law, I fear, that can touch the scoundrel.\"\n\n\"Who was he, then, and what was his object in deserting Miss\nSutherland?\"\n\nThe question was hardly out of my mouth, and Holmes had not yet\nopened his lips to reply, when we heard a heavy footfall in the\npassage and a tap at the door.\n\n\"This is the girl's stepfather, Mr. James Windibank,\" said\nHolmes. \"He has written to me to say that he would be here at\nsix. Come in!\"\n\nThe man who entered was a sturdy, middle-sized fellow, some\nthirty years of age, clean-shaven, and sallow-skinned, with a\nbland, insinuating manner, and a pair of wonderfully sharp and\npenetrating grey eyes. He shot a questioning glance at each of\nus, placed his shiny top-hat upon the sideboard, and with a\nslight bow sidled down into the nearest chair.\n\n\"Good-evening, Mr. James Windibank,\" said Holmes. \"I think that\nthis typewritten letter is from you, in which you made an\nappointment with me for six o'clock?\"\n\n\"Yes, sir. I am afraid that I am a little late, but I am not\nquite my own master, you know. I am sorry that Miss Sutherland\nhas troubled you about this little matter, for I think it is far\nbetter not to wash linen of the sort in public. It was quite\nagainst my wishes that she came, but she is a very excitable,\nimpulsive girl, as you may have noticed, and she is not easily\ncontrolled when she has made up her mind on a point. Of course, I\ndid not mind you so much, as you are not connected with the\nofficial police, but it is not pleasant to have a family\nmisfortune like this noised abroad. Besides, it is a useless\nexpense, for how could you possibly find this Hosmer Angel?\"\n\n\"On the contrary,\" said Holmes quietly; \"I have every reason to\nbelieve that I will succeed in discovering Mr. Hosmer Angel.\"\n\nMr. Windibank gave a violent start and dropped his gloves. \"I am\ndelighted to hear it,\" he said.\n\n\"It is a curious thing,\" remarked Holmes, \"that a typewriter has\nreally quite as much individuality as a man's handwriting. Unless\nthey are quite new, no two of them write exactly alike. Some\nletters get more worn than others, and some wear only on one\nside. Now, you remark in this note of yours, Mr. Windibank, that\nin every case there is some little slurring over of the 'e,' and\na slight defect in the tail of the 'r.' There are fourteen other\ncharacteristics, but those are the more obvious.\"\n\n\"We do all our correspondence with this machine at the office,\nand no doubt it is a little worn,\" our visitor answered, glancing\nkeenly at Holmes with his bright little eyes.\n\n\"And now I will show you what is really a very interesting study,\nMr. Windibank,\" Holmes continued. \"I think of writing another\nlittle monograph some of these days on the typewriter and its\nrelation to crime. It is a subject to which I have devoted some\nlittle attention. I have here four letters which purport to come\nfrom the missing man. They are all typewritten. In each case, not\nonly are the 'e's' slurred and the 'r's' tailless, but you will\nobserve, if you care to use my magnifying lens, that the fourteen\nother characteristics to which I have alluded are there as well.\"\n\nMr. Windibank sprang out of his chair and picked up his hat. \"I\ncannot waste time over this sort of fantastic talk, Mr. Holmes,\"\nhe said. \"If you can catch the man, catch him, and let me know\nwhen you have done it.\"\n\n\"Certainly,\" said Holmes, stepping over and turning the key in\nthe door. \"I let you know, then, that I have caught him!\"\n\n\"What! where?\" shouted Mr. Windibank, turning white to his lips\nand glancing about him like a rat in a trap.\n\n\"Oh, it won't do--really it won't,\" said Holmes suavely. \"There\nis no possible getting out of it, Mr. Windibank. It is quite too\ntransparent, and it was a very bad compliment when you said that\nit was impossible for me to solve so simple a question. That's\nright! Sit down and let us talk it over.\"\n\nOur visitor collapsed into a chair, with a ghastly face and a\nglitter of moisture on his brow. \"It--it's not actionable,\" he\nstammered.\n\n\"I am very much afraid that it is not. But between ourselves,\nWindibank, it was as cruel and selfish and heartless a trick in a\npetty way as ever came before me. Now, let me just run over the\ncourse of events, and you will contradict me if I go wrong.\"\n\nThe man sat huddled up in his chair, with his head sunk upon his\nbreast, like one who is utterly crushed. Holmes stuck his feet up\non the corner of the mantelpiece and, leaning back with his hands\nin his pockets, began talking, rather to himself, as it seemed,\nthan to us.\n\n\"The man married a woman very much older than himself for her\nmoney,\" said he, \"and he enjoyed the use of the money of the\ndaughter as long as she lived with them. It was a considerable\nsum, for people in their position, and the loss of it would have\nmade a serious difference. It was worth an effort to preserve it.\nThe daughter was of a good, amiable disposition, but affectionate\nand warm-hearted in her ways, so that it was evident that with\nher fair personal advantages, and her little income, she would\nnot be allowed to remain single long. Now her marriage would\nmean, of course, the loss of a hundred a year, so what does her\nstepfather do to prevent it? He takes the obvious course of\nkeeping her at home and forbidding her to seek the company of\npeople of her own age. But soon he found that that would not\nanswer forever. She became restive, insisted upon her rights, and\nfinally announced her positive intention of going to a certain\nball. What does her clever stepfather do then? He conceives an\nidea more creditable to his head than to his heart. With the\nconnivance and assistance of his wife he disguised himself,\ncovered those keen eyes with tinted glasses, masked the face with\na moustache and a pair of bushy whiskers, sunk that clear voice\ninto an insinuating whisper, and doubly secure on account of the\ngirl's short sight, he appears as Mr. Hosmer Angel, and keeps off\nother lovers by making love himself.\"\n\n\"It was only a joke at first,\" groaned our visitor. \"We never\nthought that she would have been so carried away.\"\n\n\"Very likely not. However that may be, the young lady was very\ndecidedly carried away, and, having quite made up her mind that\nher stepfather was in France, the suspicion of treachery never\nfor an instant entered her mind. She was flattered by the\ngentleman's attentions, and the effect was increased by the\nloudly expressed admiration of her mother. Then Mr. Angel began\nto call, for it was obvious that the matter should be pushed as\nfar as it would go if a real effect were to be produced. There\nwere meetings, and an engagement, which would finally secure the\ngirl's affections from turning towards anyone else. But the\ndeception could not be kept up forever. These pretended journeys\nto France were rather cumbrous. The thing to do was clearly to\nbring the business to an end in such a dramatic manner that it\nwould leave a permanent impression upon the young lady's mind and\nprevent her from looking upon any other suitor for some time to\ncome. Hence those vows of fidelity exacted upon a Testament, and\nhence also the allusions to a possibility of something happening\non the very morning of the wedding. James Windibank wished Miss\nSutherland to be so bound to Hosmer Angel, and so uncertain as to\nhis fate, that for ten years to come, at any rate, she would not\nlisten to another man. As far as the church door he brought her,\nand then, as he could go no farther, he conveniently vanished\naway by the old trick of stepping in at one door of a\nfour-wheeler and out at the other. I think that was the chain of\nevents, Mr. Windibank!\"\n\nOur visitor had recovered something of his assurance while Holmes\nhad been talking, and he rose from his chair now with a cold\nsneer upon his pale face.\n\n\"It may be so, or it may not, Mr. Holmes,\" said he, \"but if you\nare so very sharp you ought to be sharp enough to know that it is\nyou who are breaking the law now, and not me. I have done nothing\nactionable from the first, but as long as you keep that door\nlocked you lay yourself open to an action for assault and illegal\nconstraint.\"\n\n\"The law cannot, as you say, touch you,\" said Holmes, unlocking\nand throwing open the door, \"yet there never was a man who\ndeserved punishment more. If the young lady has a brother or a\nfriend, he ought to lay a whip across your shoulders. By Jove!\"\nhe continued, flushing up at the sight of the bitter sneer upon\nthe man's face, \"it is not part of my duties to my client, but\nhere's a hunting crop handy, and I think I shall just treat\nmyself to--\" He took two swift steps to the whip, but before he\ncould grasp it there was a wild clatter of steps upon the stairs,\nthe heavy hall door banged, and from the window we could see Mr.\nJames Windibank running at the top of his speed down the road.\n\n\"There's a cold-blooded scoundrel!\" said Holmes, laughing, as he\nthrew himself down into his chair once more. \"That fellow will\nrise from crime to crime until he does something very bad, and\nends on a gallows. The case has, in some respects, been not\nentirely devoid of interest.\"\n\n\"I cannot now entirely see all the steps of your reasoning,\" I\nremarked.\n\n\"Well, of course it was obvious from the first that this Mr.\nHosmer Angel must have some strong object for his curious\nconduct, and it was equally clear that the only man who really\nprofited by the incident, as far as we could see, was the\nstepfather. Then the fact that the two men were never together,\nbut that the one always appeared when the other was away, was\nsuggestive. So were the tinted spectacles and the curious voice,\nwhich both hinted at a disguise, as did the bushy whiskers. My\nsuspicions were all confirmed by his peculiar action in\ntypewriting his signature, which, of course, inferred that his\nhandwriting was so familiar to her that she would recognise even\nthe smallest sample of it. You see all these isolated facts,\ntogether with many minor ones, all pointed in the same\ndirection.\"\n\n\"And how did you verify them?\"\n\n\"Having once spotted my man, it was easy to get corroboration. I\nknew the firm for which this man worked. Having taken the printed\ndescription. I eliminated everything from it which could be the\nresult of a disguise--the whiskers, the glasses, the voice, and I\nsent it to the firm, with a request that they would inform me\nwhether it answered to the description of any of their\ntravellers. I had already noticed the peculiarities of the\ntypewriter, and I wrote to the man himself at his business\naddress asking him if he would come here. As I expected, his\nreply was typewritten and revealed the same trivial but\ncharacteristic defects. The same post brought me a letter from\nWesthouse & Marbank, of Fenchurch Street, to say that the\ndescription tallied in every respect with that of their employé,\nJames Windibank. Voilà tout!\"\n\n\"And Miss Sutherland?\"\n\n\"If I tell her she will not believe me. You may remember the old\nPersian saying, 'There is danger for him who taketh the tiger\ncub, and danger also for whoso snatches a delusion from a woman.'\nThere is as much sense in Hafiz as in Horace, and as much\nknowledge of the world.\"\n\n\n\nADVENTURE IV. THE BOSCOMBE VALLEY MYSTERY\n\nWe were seated at breakfast one morning, my wife and I, when the\nmaid brought in a telegram. It was from Sherlock Holmes and ran\nin this way:\n\n\"Have you a couple of days to spare? Have just been wired for from\nthe west of England in connection with Boscombe Valley tragedy.\nShall be glad if you will come with me. Air and scenery perfect.\nLeave Paddington by the 11:15.\"\n\n\"What do you say, dear?\" said my wife, looking across at me.\n\"Will you go?\"\n\n\"I really don't know what to say. I have a fairly long list at\npresent.\"\n\n\"Oh, Anstruther would do your work for you. You have been looking\na little pale lately. I think that the change would do you good,\nand you are always so interested in Mr. Sherlock Holmes' cases.\"\n\n\"I should be ungrateful if I were not, seeing what I gained\nthrough one of them,\" I answered. \"But if I am to go, I must pack\nat once, for I have only half an hour.\"\n\nMy experience of camp life in Afghanistan had at least had the\neffect of making me a prompt and ready traveller. My wants were\nfew and simple, so that in less than the time stated I was in a\ncab with my valise, rattling away to Paddington Station. Sherlock\nHolmes was pacing up and down the platform, his tall, gaunt\nfigure made even gaunter and taller by his long grey\ntravelling-cloak and close-fitting cloth cap.\n\n\"It is really very good of you to come, Watson,\" said he. \"It\nmakes a considerable difference to me, having someone with me on\nwhom I can thoroughly rely. Local aid is always either worthless\nor else biassed. If you will keep the two corner seats I shall\nget the tickets.\"\n\nWe had the carriage to ourselves save for an immense litter of\npapers which Holmes had brought with him. Among these he rummaged\nand read, with intervals of note-taking and of meditation, until\nwe were past Reading. Then he suddenly rolled them all into a\ngigantic ball and tossed them up onto the rack.\n\n\"Have you heard anything of the case?\" he asked.\n\n\"Not a word. I have not seen a paper for some days.\"\n\n\"The London press has not had very full accounts. I have just\nbeen looking through all the recent papers in order to master the\nparticulars. It seems, from what I gather, to be one of those\nsimple cases which are so extremely difficult.\"\n\n\"That sounds a little paradoxical.\"\n\n\"But it is profoundly true. Singularity is almost invariably a\nclue. The more featureless and commonplace a crime is, the more\ndifficult it is to bring it home. In this case, however, they\nhave established a very serious case against the son of the\nmurdered man.\"\n\n\"It is a murder, then?\"\n\n\"Well, it is conjectured to be so. I shall take nothing for\ngranted until I have the opportunity of looking personally into\nit. I will explain the state of things to you, as far as I have\nbeen able to understand it, in a very few words.\n\n\"Boscombe Valley is a country district not very far from Ross, in\nHerefordshire. The largest landed proprietor in that part is a\nMr. John Turner, who made his money in Australia and returned\nsome years ago to the old country. One of the farms which he\nheld, that of Hatherley, was let to Mr. Charles McCarthy, who was\nalso an ex-Australian. The men had known each other in the\ncolonies, so that it was not unnatural that when they came to\nsettle down they should do so as near each other as possible.\nTurner was apparently the richer man, so McCarthy became his\ntenant but still remained, it seems, upon terms of perfect\nequality, as they were frequently together. McCarthy had one son,\na lad of eighteen, and Turner had an only daughter of the same\nage, but neither of them had wives living. They appear to have\navoided the society of the neighbouring English families and to\nhave led retired lives, though both the McCarthys were fond of\nsport and were frequently seen at the race-meetings of the\nneighbourhood. McCarthy kept two servants--a man and a girl.\nTurner had a considerable household, some half-dozen at the\nleast. That is as much as I have been able to gather about the\nfamilies. Now for the facts.\n\n\"On June 3rd, that is, on Monday last, McCarthy left his house at\nHatherley about three in the afternoon and walked down to the\nBoscombe Pool, which is a small lake formed by the spreading out\nof the stream which runs down the Boscombe Valley. He had been\nout with his serving-man in the morning at Ross, and he had told\nthe man that he must hurry, as he had an appointment of\nimportance to keep at three. From that appointment he never came\nback alive.\n\n\"From Hatherley Farm-house to the Boscombe Pool is a quarter of a\nmile, and two people saw him as he passed over this ground. One\nwas an old woman, whose name is not mentioned, and the other was\nWilliam Crowder, a game-keeper in the employ of Mr. Turner. Both\nthese witnesses depose that Mr. McCarthy was walking alone. The\ngame-keeper adds that within a few minutes of his seeing Mr.\nMcCarthy pass he had seen his son, Mr. James McCarthy, going the\nsame way with a gun under his arm. To the best of his belief, the\nfather was actually in sight at the time, and the son was\nfollowing him. He thought no more of the matter until he heard in\nthe evening of the tragedy that had occurred.\n\n\"The two McCarthys were seen after the time when William Crowder,\nthe game-keeper, lost sight of them. The Boscombe Pool is thickly\nwooded round, with just a fringe of grass and of reeds round the\nedge. A girl of fourteen, Patience Moran, who is the daughter of\nthe lodge-keeper of the Boscombe Valley estate, was in one of the\nwoods picking flowers. She states that while she was there she\nsaw, at the border of the wood and close by the lake, Mr.\nMcCarthy and his son, and that they appeared to be having a\nviolent quarrel. She heard Mr. McCarthy the elder using very\nstrong language to his son, and she saw the latter raise up his\nhand as if to strike his father. She was so frightened by their\nviolence that she ran away and told her mother when she reached\nhome that she had left the two McCarthys quarrelling near\nBoscombe Pool, and that she was afraid that they were going to\nfight. She had hardly said the words when young Mr. McCarthy came\nrunning up to the lodge to say that he had found his father dead\nin the wood, and to ask for the help of the lodge-keeper. He was\nmuch excited, without either his gun or his hat, and his right\nhand and sleeve were observed to be stained with fresh blood. On\nfollowing him they found the dead body stretched out upon the\ngrass beside the pool. The head had been beaten in by repeated\nblows of some heavy and blunt weapon. The injuries were such as\nmight very well have been inflicted by the butt-end of his son's\ngun, which was found lying on the grass within a few paces of the\nbody. Under these circumstances the young man was instantly\narrested, and a verdict of 'wilful murder' having been returned\nat the inquest on Tuesday, he was on Wednesday brought before the\nmagistrates at Ross, who have referred the case to the next\nAssizes. Those are the main facts of the case as they came out\nbefore the coroner and the police-court.\"\n\n\"I could hardly imagine a more damning case,\" I remarked. \"If\never circumstantial evidence pointed to a criminal it does so\nhere.\"\n\n\"Circumstantial evidence is a very tricky thing,\" answered Holmes\nthoughtfully. \"It may seem to point very straight to one thing,\nbut if you shift your own point of view a little, you may find it\npointing in an equally uncompromising manner to something\nentirely different. It must be confessed, however, that the case\nlooks exceedingly grave against the young man, and it is very\npossible that he is indeed the culprit. There are several people\nin the neighbourhood, however, and among them Miss Turner, the\ndaughter of the neighbouring landowner, who believe in his\ninnocence, and who have retained Lestrade, whom you may recollect\nin connection with the Study in Scarlet, to work out the case in\nhis interest. Lestrade, being rather puzzled, has referred the\ncase to me, and hence it is that two middle-aged gentlemen are\nflying westward at fifty miles an hour instead of quietly\ndigesting their breakfasts at home.\"\n\n\"I am afraid,\" said I, \"that the facts are so obvious that you\nwill find little credit to be gained out of this case.\"\n\n\"There is nothing more deceptive than an obvious fact,\" he\nanswered, laughing. \"Besides, we may chance to hit upon some\nother obvious facts which may have been by no means obvious to\nMr. Lestrade. You know me too well to think that I am boasting\nwhen I say that I shall either confirm or destroy his theory by\nmeans which he is quite incapable of employing, or even of\nunderstanding. To take the first example to hand, I very clearly\nperceive that in your bedroom the window is upon the right-hand\nside, and yet I question whether Mr. Lestrade would have noted\neven so self-evident a thing as that.\"\n\n\"How on earth--\"\n\n\"My dear fellow, I know you well. I know the military neatness\nwhich characterises you. You shave every morning, and in this\nseason you shave by the sunlight; but since your shaving is less\nand less complete as we get farther back on the left side, until\nit becomes positively slovenly as we get round the angle of the\njaw, it is surely very clear that that side is less illuminated\nthan the other. I could not imagine a man of your habits looking\nat himself in an equal light and being satisfied with such a\nresult. I only quote this as a trivial example of observation and\ninference. Therein lies my métier, and it is just possible that\nit may be of some service in the investigation which lies before\nus. There are one or two minor points which were brought out in\nthe inquest, and which are worth considering.\"\n\n\"What are they?\"\n\n\"It appears that his arrest did not take place at once, but after\nthe return to Hatherley Farm. On the inspector of constabulary\ninforming him that he was a prisoner, he remarked that he was not\nsurprised to hear it, and that it was no more than his deserts.\nThis observation of his had the natural effect of removing any\ntraces of doubt which might have remained in the minds of the\ncoroner's jury.\"\n\n\"It was a confession,\" I ejaculated.\n\n\"No, for it was followed by a protestation of innocence.\"\n\n\"Coming on the top of such a damning series of events, it was at\nleast a most suspicious remark.\"\n\n\"On the contrary,\" said Holmes, \"it is the brightest rift which I\ncan at present see in the clouds. However innocent he might be,\nhe could not be such an absolute imbecile as not to see that the\ncircumstances were very black against him. Had he appeared\nsurprised at his own arrest, or feigned indignation at it, I\nshould have looked upon it as highly suspicious, because such\nsurprise or anger would not be natural under the circumstances,\nand yet might appear to be the best policy to a scheming man. His\nfrank acceptance of the situation marks him as either an innocent\nman, or else as a man of considerable self-restraint and\nfirmness. As to his remark about his deserts, it was also not\nunnatural if you consider that he stood beside the dead body of\nhis father, and that there is no doubt that he had that very day\nso far forgotten his filial duty as to bandy words with him, and\neven, according to the little girl whose evidence is so\nimportant, to raise his hand as if to strike him. The\nself-reproach and contrition which are displayed in his remark\nappear to me to be the signs of a healthy mind rather than of a\nguilty one.\"\n\nI shook my head. \"Many men have been hanged on far slighter\nevidence,\" I remarked.\n\n\"So they have. And many men have been wrongfully hanged.\"\n\n\"What is the young man's own account of the matter?\"\n\n\"It is, I am afraid, not very encouraging to his supporters,\nthough there are one or two points in it which are suggestive.\nYou will find it here, and may read it for yourself.\"\n\nHe picked out from his bundle a copy of the local Herefordshire\npaper, and having turned down the sheet he pointed out the\nparagraph in which the unfortunate young man had given his own\nstatement of what had occurred. I settled myself down in the\ncorner of the carriage and read it very carefully. It ran in this\nway:\n\n\"Mr. James McCarthy, the only son of the deceased, was then called\nand gave evidence as follows: 'I had been away from home for\nthree days at Bristol, and had only just returned upon the\nmorning of last Monday, the 3rd. My father was absent from home at\nthe time of my arrival, and I was informed by the maid that he\nhad driven over to Ross with John Cobb, the groom. Shortly after\nmy return I heard the wheels of his trap in the yard, and,\nlooking out of my window, I saw him get out and walk rapidly out\nof the yard, though I was not aware in which direction he was\ngoing. I then took my gun and strolled out in the direction of\nthe Boscombe Pool, with the intention of visiting the rabbit\nwarren which is upon the other side. On my way I saw William\nCrowder, the game-keeper, as he had stated in his evidence; but\nhe is mistaken in thinking that I was following my father. I had\nno idea that he was in front of me. When about a hundred yards\nfrom the pool I heard a cry of \"Cooee!\" which was a usual signal\nbetween my father and myself. I then hurried forward, and found\nhim standing by the pool. He appeared to be much surprised at\nseeing me and asked me rather roughly what I was doing there. A\nconversation ensued which led to high words and almost to blows,\nfor my father was a man of a very violent temper. Seeing that his\npassion was becoming ungovernable, I left him and returned\ntowards Hatherley Farm. I had not gone more than 150 yards,\nhowever, when I heard a hideous outcry behind me, which caused me\nto run back again. I found my father expiring upon the ground,\nwith his head terribly injured. I dropped my gun and held him in\nmy arms, but he almost instantly expired. I knelt beside him for\nsome minutes, and then made my way to Mr. Turner's lodge-keeper,\nhis house being the nearest, to ask for assistance. I saw no one\nnear my father when I returned, and I have no idea how he came by\nhis injuries. He was not a popular man, being somewhat cold and\nforbidding in his manners, but he had, as far as I know, no\nactive enemies. I know nothing further of the matter.'\n\n\"The Coroner: Did your father make any statement to you before\nhe died?\n\n\"Witness: He mumbled a few words, but I could only catch some\nallusion to a rat.\n\n\"The Coroner: What did you understand by that?\n\n\"Witness: It conveyed no meaning to me. I thought that he was\ndelirious.\n\n\"The Coroner: What was the point upon which you and your father\nhad this final quarrel?\n\n\"Witness: I should prefer not to answer.\n\n\"The Coroner: I am afraid that I must press it.\n\n\"Witness: It is really impossible for me to tell you. I can\nassure you that it has nothing to do with the sad tragedy which\nfollowed.\n\n\"The Coroner: That is for the court to decide. I need not point\nout to you that your refusal to answer will prejudice your case\nconsiderably in any future proceedings which may arise.\n\n\"Witness: I must still refuse.\n\n\"The Coroner: I understand that the cry of 'Cooee' was a common\nsignal between you and your father?\n\n\"Witness: It was.\n\n\"The Coroner: How was it, then, that he uttered it before he saw\nyou, and before he even knew that you had returned from Bristol?\n\n\"Witness (with considerable confusion): I do not know.\n\n\"A Juryman: Did you see nothing which aroused your suspicions\nwhen you returned on hearing the cry and found your father\nfatally injured?\n\n\"Witness: Nothing definite.\n\n\"The Coroner: What do you mean?\n\n\"Witness: I was so disturbed and excited as I rushed out into\nthe open, that I could think of nothing except of my father. Yet\nI have a vague impression that as I ran forward something lay\nupon the ground to the left of me. It seemed to me to be\nsomething grey in colour, a coat of some sort, or a plaid perhaps.\nWhen I rose from my father I looked round for it, but it was\ngone.\n\n\"'Do you mean that it disappeared before you went for help?'\n\n\"'Yes, it was gone.'\n\n\"'You cannot say what it was?'\n\n\"'No, I had a feeling something was there.'\n\n\"'How far from the body?'\n\n\"'A dozen yards or so.'\n\n\"'And how far from the edge of the wood?'\n\n\"'About the same.'\n\n\"'Then if it was removed it was while you were within a dozen\nyards of it?'\n\n\"'Yes, but with my back towards it.'\n\n\"This concluded the examination of the witness.\"\n\n\"I see,\" said I as I glanced down the column, \"that the coroner\nin his concluding remarks was rather severe upon young McCarthy.\nHe calls attention, and with reason, to the discrepancy about his\nfather having signalled to him before seeing him, also to his\nrefusal to give details of his conversation with his father, and\nhis singular account of his father's dying words. They are all,\nas he remarks, very much against the son.\"\n\nHolmes laughed softly to himself and stretched himself out upon\nthe cushioned seat. \"Both you and the coroner have been at some\npains,\" said he, \"to single out the very strongest points in the\nyoung man's favour. Don't you see that you alternately give him\ncredit for having too much imagination and too little? Too\nlittle, if he could not invent a cause of quarrel which would\ngive him the sympathy of the jury; too much, if he evolved from\nhis own inner consciousness anything so outré as a dying\nreference to a rat, and the incident of the vanishing cloth. No,\nsir, I shall approach this case from the point of view that what\nthis young man says is true, and we shall see whither that\nhypothesis will lead us. And now here is my pocket Petrarch, and\nnot another word shall I say of this case until we are on the\nscene of action. We lunch at Swindon, and I see that we shall be\nthere in twenty minutes.\"\n\nIt was nearly four o'clock when we at last, after passing through\nthe beautiful Stroud Valley, and over the broad gleaming Severn,\nfound ourselves at the pretty little country-town of Ross. A\nlean, ferret-like man, furtive and sly-looking, was waiting for\nus upon the platform. In spite of the light brown dustcoat and\nleather-leggings which he wore in deference to his rustic\nsurroundings, I had no difficulty in recognising Lestrade, of\nScotland Yard. With him we drove to the Hereford Arms where a\nroom had already been engaged for us.\n\n\"I have ordered a carriage,\" said Lestrade as we sat over a cup\nof tea. \"I knew your energetic nature, and that you would not be\nhappy until you had been on the scene of the crime.\"\n\n\"It was very nice and complimentary of you,\" Holmes answered. \"It\nis entirely a question of barometric pressure.\"\n\nLestrade looked startled. \"I do not quite follow,\" he said.\n\n\"How is the glass? Twenty-nine, I see. No wind, and not a cloud\nin the sky. I have a caseful of cigarettes here which need\nsmoking, and the sofa is very much superior to the usual country\nhotel abomination. I do not think that it is probable that I\nshall use the carriage to-night.\"\n\nLestrade laughed indulgently. \"You have, no doubt, already formed\nyour conclusions from the newspapers,\" he said. \"The case is as\nplain as a pikestaff, and the more one goes into it the plainer\nit becomes. Still, of course, one can't refuse a lady, and such a\nvery positive one, too. She has heard of you, and would have your\nopinion, though I repeatedly told her that there was nothing\nwhich you could do which I had not already done. Why, bless my\nsoul! here is her carriage at the door.\"\n\nHe had hardly spoken before there rushed into the room one of the\nmost lovely young women that I have ever seen in my life. Her\nviolet eyes shining, her lips parted, a pink flush upon her\ncheeks, all thought of her natural reserve lost in her\noverpowering excitement and concern.\n\n\"Oh, Mr. Sherlock Holmes!\" she cried, glancing from one to the\nother of us, and finally, with a woman's quick intuition,\nfastening upon my companion, \"I am so glad that you have come. I\nhave driven down to tell you so. I know that James didn't do it.\nI know it, and I want you to start upon your work knowing it,\ntoo. Never let yourself doubt upon that point. We have known each\nother since we were little children, and I know his faults as no\none else does; but he is too tender-hearted to hurt a fly. Such a\ncharge is absurd to anyone who really knows him.\"\n\n\"I hope we may clear him, Miss Turner,\" said Sherlock Holmes.\n\"You may rely upon my doing all that I can.\"\n\n\"But you have read the evidence. You have formed some conclusion?\nDo you not see some loophole, some flaw? Do you not yourself\nthink that he is innocent?\"\n\n\"I think that it is very probable.\"\n\n\"There, now!\" she cried, throwing back her head and looking\ndefiantly at Lestrade. \"You hear! He gives me hopes.\"\n\nLestrade shrugged his shoulders. \"I am afraid that my colleague\nhas been a little quick in forming his conclusions,\" he said.\n\n\"But he is right. Oh! I know that he is right. James never did\nit. And about his quarrel with his father, I am sure that the\nreason why he would not speak about it to the coroner was because\nI was concerned in it.\"\n\n\"In what way?\" asked Holmes.\n\n\"It is no time for me to hide anything. James and his father had\nmany disagreements about me. Mr. McCarthy was very anxious that\nthere should be a marriage between us. James and I have always\nloved each other as brother and sister; but of course he is young\nand has seen very little of life yet, and--and--well, he\nnaturally did not wish to do anything like that yet. So there\nwere quarrels, and this, I am sure, was one of them.\"\n\n\"And your father?\" asked Holmes. \"Was he in favour of such a\nunion?\"\n\n\"No, he was averse to it also. No one but Mr. McCarthy was in\nfavour of it.\" A quick blush passed over her fresh young face as\nHolmes shot one of his keen, questioning glances at her.\n\n\"Thank you for this information,\" said he. \"May I see your father\nif I call to-morrow?\"\n\n\"I am afraid the doctor won't allow it.\"\n\n\"The doctor?\"\n\n\"Yes, have you not heard? Poor father has never been strong for\nyears back, but this has broken him down completely. He has taken\nto his bed, and Dr. Willows says that he is a wreck and that his\nnervous system is shattered. Mr. McCarthy was the only man alive\nwho had known dad in the old days in Victoria.\"\n\n\"Ha! In Victoria! That is important.\"\n\n\"Yes, at the mines.\"\n\n\"Quite so; at the gold-mines, where, as I understand, Mr. Turner\nmade his money.\"\n\n\"Yes, certainly.\"\n\n\"Thank you, Miss Turner. You have been of material assistance to\nme.\"\n\n\"You will tell me if you have any news to-morrow. No doubt you\nwill go to the prison to see James. Oh, if you do, Mr. Holmes, do\ntell him that I know him to be innocent.\"\n\n\"I will, Miss Turner.\"\n\n\"I must go home now, for dad is very ill, and he misses me so if\nI leave him. Good-bye, and God help you in your undertaking.\" She\nhurried from the room as impulsively as she had entered, and we\nheard the wheels of her carriage rattle off down the street.\n\n\"I am ashamed of you, Holmes,\" said Lestrade with dignity after a\nfew minutes' silence. \"Why should you raise up hopes which you\nare bound to disappoint? I am not over-tender of heart, but I\ncall it cruel.\"\n\n\"I think that I see my way to clearing James McCarthy,\" said\nHolmes. \"Have you an order to see him in prison?\"\n\n\"Yes, but only for you and me.\"\n\n\"Then I shall reconsider my resolution about going out. We have\nstill time to take a train to Hereford and see him to-night?\"\n\n\"Ample.\"\n\n\"Then let us do so. Watson, I fear that you will find it very\nslow, but I shall only be away a couple of hours.\"\n\nI walked down to the station with them, and then wandered through\nthe streets of the little town, finally returning to the hotel,\nwhere I lay upon the sofa and tried to interest myself in a\nyellow-backed novel. The puny plot of the story was so thin,\nhowever, when compared to the deep mystery through which we were\ngroping, and I found my attention wander so continually from the\naction to the fact, that I at last flung it across the room and\ngave myself up entirely to a consideration of the events of the\nday. Supposing that this unhappy young man's story were\nabsolutely true, then what hellish thing, what absolutely\nunforeseen and extraordinary calamity could have occurred between\nthe time when he parted from his father, and the moment when,\ndrawn back by his screams, he rushed into the glade? It was\nsomething terrible and deadly. What could it be? Might not the\nnature of the injuries reveal something to my medical instincts?\nI rang the bell and called for the weekly county paper, which\ncontained a verbatim account of the inquest. In the surgeon's\ndeposition it was stated that the posterior third of the left\nparietal bone and the left half of the occipital bone had been\nshattered by a heavy blow from a blunt weapon. I marked the spot\nupon my own head. Clearly such a blow must have been struck from\nbehind. That was to some extent in favour of the accused, as when\nseen quarrelling he was face to face with his father. Still, it\ndid not go for very much, for the older man might have turned his\nback before the blow fell. Still, it might be worth while to call\nHolmes' attention to it. Then there was the peculiar dying\nreference to a rat. What could that mean? It could not be\ndelirium. A man dying from a sudden blow does not commonly become\ndelirious. No, it was more likely to be an attempt to explain how\nhe met his fate. But what could it indicate? I cudgelled my\nbrains to find some possible explanation. And then the incident\nof the grey cloth seen by young McCarthy. If that were true the\nmurderer must have dropped some part of his dress, presumably his\novercoat, in his flight, and must have had the hardihood to\nreturn and to carry it away at the instant when the son was\nkneeling with his back turned not a dozen paces off. What a\ntissue of mysteries and improbabilities the whole thing was! I\ndid not wonder at Lestrade's opinion, and yet I had so much faith\nin Sherlock Holmes' insight that I could not lose hope as long\nas every fresh fact seemed to strengthen his conviction of young\nMcCarthy's innocence.\n\nIt was late before Sherlock Holmes returned. He came back alone,\nfor Lestrade was staying in lodgings in the town.\n\n\"The glass still keeps very high,\" he remarked as he sat down.\n\"It is of importance that it should not rain before we are able\nto go over the ground. On the other hand, a man should be at his\nvery best and keenest for such nice work as that, and I did not\nwish to do it when fagged by a long journey. I have seen young\nMcCarthy.\"\n\n\"And what did you learn from him?\"\n\n\"Nothing.\"\n\n\"Could he throw no light?\"\n\n\"None at all. I was inclined to think at one time that he knew\nwho had done it and was screening him or her, but I am convinced\nnow that he is as puzzled as everyone else. He is not a very\nquick-witted youth, though comely to look at and, I should think,\nsound at heart.\"\n\n\"I cannot admire his taste,\" I remarked, \"if it is indeed a fact\nthat he was averse to a marriage with so charming a young lady as\nthis Miss Turner.\"\n\n\"Ah, thereby hangs a rather painful tale. This fellow is madly,\ninsanely, in love with her, but some two years ago, when he was\nonly a lad, and before he really knew her, for she had been away\nfive years at a boarding-school, what does the idiot do but get\ninto the clutches of a barmaid in Bristol and marry her at a\nregistry office? No one knows a word of the matter, but you can\nimagine how maddening it must be to him to be upbraided for not\ndoing what he would give his very eyes to do, but what he knows\nto be absolutely impossible. It was sheer frenzy of this sort\nwhich made him throw his hands up into the air when his father,\nat their last interview, was goading him on to propose to Miss\nTurner. On the other hand, he had no means of supporting himself,\nand his father, who was by all accounts a very hard man, would\nhave thrown him over utterly had he known the truth. It was with\nhis barmaid wife that he had spent the last three days in\nBristol, and his father did not know where he was. Mark that\npoint. It is of importance. Good has come out of evil, however,\nfor the barmaid, finding from the papers that he is in serious\ntrouble and likely to be hanged, has thrown him over utterly and\nhas written to him to say that she has a husband already in the\nBermuda Dockyard, so that there is really no tie between them. I\nthink that that bit of news has consoled young McCarthy for all\nthat he has suffered.\"\n\n\"But if he is innocent, who has done it?\"\n\n\"Ah! who? I would call your attention very particularly to two\npoints. One is that the murdered man had an appointment with\nsomeone at the pool, and that the someone could not have been his\nson, for his son was away, and he did not know when he would\nreturn. The second is that the murdered man was heard to cry\n'Cooee!' before he knew that his son had returned. Those are the\ncrucial points upon which the case depends. And now let us talk\nabout George Meredith, if you please, and we shall leave all\nminor matters until to-morrow.\"\n\nThere was no rain, as Holmes had foretold, and the morning broke\nbright and cloudless. At nine o'clock Lestrade called for us with\nthe carriage, and we set off for Hatherley Farm and the Boscombe\nPool.\n\n\"There is serious news this morning,\" Lestrade observed. \"It is\nsaid that Mr. Turner, of the Hall, is so ill that his life is\ndespaired of.\"\n\n\"An elderly man, I presume?\" said Holmes.\n\n\"About sixty; but his constitution has been shattered by his life\nabroad, and he has been in failing health for some time. This\nbusiness has had a very bad effect upon him. He was an old friend\nof McCarthy's, and, I may add, a great benefactor to him, for I\nhave learned that he gave him Hatherley Farm rent free.\"\n\n\"Indeed! That is interesting,\" said Holmes.\n\n\"Oh, yes! In a hundred other ways he has helped him. Everybody\nabout here speaks of his kindness to him.\"\n\n\"Really! Does it not strike you as a little singular that this\nMcCarthy, who appears to have had little of his own, and to have\nbeen under such obligations to Turner, should still talk of\nmarrying his son to Turner's daughter, who is, presumably,\nheiress to the estate, and that in such a very cocksure manner,\nas if it were merely a case of a proposal and all else would\nfollow? It is the more strange, since we know that Turner himself\nwas averse to the idea. The daughter told us as much. Do you not\ndeduce something from that?\"\n\n\"We have got to the deductions and the inferences,\" said\nLestrade, winking at me. \"I find it hard enough to tackle facts,\nHolmes, without flying away after theories and fancies.\"\n\n\"You are right,\" said Holmes demurely; \"you do find it very hard\nto tackle the facts.\"\n\n\"Anyhow, I have grasped one fact which you seem to find it\ndifficult to get hold of,\" replied Lestrade with some warmth.\n\n\"And that is--\"\n\n\"That McCarthy senior met his death from McCarthy junior and that\nall theories to the contrary are the merest moonshine.\"\n\n\"Well, moonshine is a brighter thing than fog,\" said Holmes,\nlaughing. \"But I am very much mistaken if this is not Hatherley\nFarm upon the left.\"\n\n\"Yes, that is it.\" It was a widespread, comfortable-looking\nbuilding, two-storied, slate-roofed, with great yellow blotches\nof lichen upon the grey walls. The drawn blinds and the smokeless\nchimneys, however, gave it a stricken look, as though the weight\nof this horror still lay heavy upon it. We called at the door,\nwhen the maid, at Holmes' request, showed us the boots which her\nmaster wore at the time of his death, and also a pair of the\nson's, though not the pair which he had then had. Having measured\nthese very carefully from seven or eight different points, Holmes\ndesired to be led to the court-yard, from which we all followed\nthe winding track which led to Boscombe Pool.\n\nSherlock Holmes was transformed when he was hot upon such a scent\nas this. Men who had only known the quiet thinker and logician of\nBaker Street would have failed to recognise him. His face flushed\nand darkened. His brows were drawn into two hard black lines,\nwhile his eyes shone out from beneath them with a steely glitter.\nHis face was bent downward, his shoulders bowed, his lips\ncompressed, and the veins stood out like whipcord in his long,\nsinewy neck. His nostrils seemed to dilate with a purely animal\nlust for the chase, and his mind was so absolutely concentrated\nupon the matter before him that a question or remark fell\nunheeded upon his ears, or, at the most, only provoked a quick,\nimpatient snarl in reply. Swiftly and silently he made his way\nalong the track which ran through the meadows, and so by way of\nthe woods to the Boscombe Pool. It was damp, marshy ground, as is\nall that district, and there were marks of many feet, both upon\nthe path and amid the short grass which bounded it on either\nside. Sometimes Holmes would hurry on, sometimes stop dead, and\nonce he made quite a little detour into the meadow. Lestrade and\nI walked behind him, the detective indifferent and contemptuous,\nwhile I watched my friend with the interest which sprang from the\nconviction that every one of his actions was directed towards a\ndefinite end.\n\nThe Boscombe Pool, which is a little reed-girt sheet of water\nsome fifty yards across, is situated at the boundary between the\nHatherley Farm and the private park of the wealthy Mr. Turner.\nAbove the woods which lined it upon the farther side we could see\nthe red, jutting pinnacles which marked the site of the rich\nlandowner's dwelling. On the Hatherley side of the pool the woods\ngrew very thick, and there was a narrow belt of sodden grass\ntwenty paces across between the edge of the trees and the reeds\nwhich lined the lake. Lestrade showed us the exact spot at which\nthe body had been found, and, indeed, so moist was the ground,\nthat I could plainly see the traces which had been left by the\nfall of the stricken man. To Holmes, as I could see by his eager\nface and peering eyes, very many other things were to be read\nupon the trampled grass. He ran round, like a dog who is picking\nup a scent, and then turned upon my companion.\n\n\"What did you go into the pool for?\" he asked.\n\n\"I fished about with a rake. I thought there might be some weapon\nor other trace. But how on earth--\"\n\n\"Oh, tut, tut! I have no time! That left foot of yours with its\ninward twist is all over the place. A mole could trace it, and\nthere it vanishes among the reeds. Oh, how simple it would all\nhave been had I been here before they came like a herd of buffalo\nand wallowed all over it. Here is where the party with the\nlodge-keeper came, and they have covered all tracks for six or\neight feet round the body. But here are three separate tracks of\nthe same feet.\" He drew out a lens and lay down upon his\nwaterproof to have a better view, talking all the time rather to\nhimself than to us. \"These are young McCarthy's feet. Twice he\nwas walking, and once he ran swiftly, so that the soles are\ndeeply marked and the heels hardly visible. That bears out his\nstory. He ran when he saw his father on the ground. Then here are\nthe father's feet as he paced up and down. What is this, then? It\nis the butt-end of the gun as the son stood listening. And this?\nHa, ha! What have we here? Tiptoes! tiptoes! Square, too, quite\nunusual boots! They come, they go, they come again--of course\nthat was for the cloak. Now where did they come from?\" He ran up\nand down, sometimes losing, sometimes finding the track until we\nwere well within the edge of the wood and under the shadow of a\ngreat beech, the largest tree in the neighbourhood. Holmes traced\nhis way to the farther side of this and lay down once more upon\nhis face with a little cry of satisfaction. For a long time he\nremained there, turning over the leaves and dried sticks,\ngathering up what seemed to me to be dust into an envelope and\nexamining with his lens not only the ground but even the bark of\nthe tree as far as he could reach. A jagged stone was lying among\nthe moss, and this also he carefully examined and retained. Then\nhe followed a pathway through the wood until he came to the\nhighroad, where all traces were lost.\n\n\"It has been a case of considerable interest,\" he remarked,\nreturning to his natural manner. \"I fancy that this grey house on\nthe right must be the lodge. I think that I will go in and have a\nword with Moran, and perhaps write a little note. Having done\nthat, we may drive back to our luncheon. You may walk to the cab,\nand I shall be with you presently.\"\n\nIt was about ten minutes before we regained our cab and drove\nback into Ross, Holmes still carrying with him the stone which he\nhad picked up in the wood.\n\n\"This may interest you, Lestrade,\" he remarked, holding it out.\n\"The murder was done with it.\"\n\n\"I see no marks.\"\n\n\"There are none.\"\n\n\"How do you know, then?\"\n\n\"The grass was growing under it. It had only lain there a few\ndays. There was no sign of a place whence it had been taken. It\ncorresponds with the injuries. There is no sign of any other\nweapon.\"\n\n\"And the murderer?\"\n\n\"Is a tall man, left-handed, limps with the right leg, wears\nthick-soled shooting-boots and a grey cloak, smokes Indian\ncigars, uses a cigar-holder, and carries a blunt pen-knife in his\npocket. There are several other indications, but these may be\nenough to aid us in our search.\"\n\nLestrade laughed. \"I am afraid that I am still a sceptic,\" he\nsaid. \"Theories are all very well, but we have to deal with a\nhard-headed British jury.\"\n\n\"Nous verrons,\" answered Holmes calmly. \"You work your own\nmethod, and I shall work mine. I shall be busy this afternoon,\nand shall probably return to London by the evening train.\"\n\n\"And leave your case unfinished?\"\n\n\"No, finished.\"\n\n\"But the mystery?\"\n\n\"It is solved.\"\n\n\"Who was the criminal, then?\"\n\n\"The gentleman I describe.\"\n\n\"But who is he?\"\n\n\"Surely it would not be difficult to find out. This is not such a\npopulous neighbourhood.\"\n\nLestrade shrugged his shoulders. \"I am a practical man,\" he said,\n\"and I really cannot undertake to go about the country looking\nfor a left-handed gentleman with a game leg. I should become the\nlaughing-stock of Scotland Yard.\"\n\n\"All right,\" said Holmes quietly. \"I have given you the chance.\nHere are your lodgings. Good-bye. I shall drop you a line before\nI leave.\"\n\nHaving left Lestrade at his rooms, we drove to our hotel, where\nwe found lunch upon the table. Holmes was silent and buried in\nthought with a pained expression upon his face, as one who finds\nhimself in a perplexing position.\n\n\"Look here, Watson,\" he said when the cloth was cleared \"just sit\ndown in this chair and let me preach to you for a little. I don't\nknow quite what to do, and I should value your advice. Light a\ncigar and let me expound.\"\n\n \"Pray do so.\"\n\n\"Well, now, in considering this case there are two points about\nyoung McCarthy's narrative which struck us both instantly,\nalthough they impressed me in his favour and you against him. One\nwas the fact that his father should, according to his account,\ncry 'Cooee!' before seeing him. The other was his singular dying\nreference to a rat. He mumbled several words, you understand, but\nthat was all that caught the son's ear. Now from this double\npoint our research must commence, and we will begin it by\npresuming that what the lad says is absolutely true.\"\n\n\"What of this 'Cooee!' then?\"\n\n\"Well, obviously it could not have been meant for the son. The\nson, as far as he knew, was in Bristol. It was mere chance that\nhe was within earshot. The 'Cooee!' was meant to attract the\nattention of whoever it was that he had the appointment with. But\n'Cooee' is a distinctly Australian cry, and one which is used\nbetween Australians. There is a strong presumption that the\nperson whom McCarthy expected to meet him at Boscombe Pool was\nsomeone who had been in Australia.\"\n\n\"What of the rat, then?\"\n\nSherlock Holmes took a folded paper from his pocket and flattened\nit out on the table. \"This is a map of the Colony of Victoria,\"\nhe said. \"I wired to Bristol for it last night.\" He put his hand\nover part of the map. \"What do you read?\"\n\n\"ARAT,\" I read.\n\n\"And now?\" He raised his hand.\n\n\"BALLARAT.\"\n\n\"Quite so. That was the word the man uttered, and of which his\nson only caught the last two syllables. He was trying to utter\nthe name of his murderer. So and so, of Ballarat.\"\n\n\"It is wonderful!\" I exclaimed.\n\n\"It is obvious. And now, you see, I had narrowed the field down\nconsiderably. The possession of a grey garment was a third point\nwhich, granting the son's statement to be correct, was a\ncertainty. We have come now out of mere vagueness to the definite\nconception of an Australian from Ballarat with a grey cloak.\"\n\n\"Certainly.\"\n\n\"And one who was at home in the district, for the pool can only\nbe approached by the farm or by the estate, where strangers could\nhardly wander.\"\n\n\"Quite so.\"\n\n\"Then comes our expedition of to-day. By an examination of the\nground I gained the trifling details which I gave to that\nimbecile Lestrade, as to the personality of the criminal.\"\n\n\"But how did you gain them?\"\n\n\"You know my method. It is founded upon the observation of\ntrifles.\"\n\n\"His height I know that you might roughly judge from the length\nof his stride. His boots, too, might be told from their traces.\"\n\n\"Yes, they were peculiar boots.\"\n\n\"But his lameness?\"\n\n\"The impression of his right foot was always less distinct than\nhis left. He put less weight upon it. Why? Because he limped--he\nwas lame.\"\n\n\"But his left-handedness.\"\n\n\"You were yourself struck by the nature of the injury as recorded\nby the surgeon at the inquest. The blow was struck from\nimmediately behind, and yet was upon the left side. Now, how can\nthat be unless it were by a left-handed man? He had stood behind\nthat tree during the interview between the father and son. He had\neven smoked there. I found the ash of a cigar, which my special\nknowledge of tobacco ashes enables me to pronounce as an Indian\ncigar. I have, as you know, devoted some attention to this, and\nwritten a little monograph on the ashes of 140 different\nvarieties of pipe, cigar, and cigarette tobacco. Having found the\nash, I then looked round and discovered the stump among the moss\nwhere he had tossed it. It was an Indian cigar, of the variety\nwhich are rolled in Rotterdam.\"\n\n\"And the cigar-holder?\"\n\n\"I could see that the end had not been in his mouth. Therefore he\nused a holder. The tip had been cut off, not bitten off, but the\ncut was not a clean one, so I deduced a blunt pen-knife.\"\n\n\"Holmes,\" I said, \"you have drawn a net round this man from which\nhe cannot escape, and you have saved an innocent human life as\ntruly as if you had cut the cord which was hanging him. I see the\ndirection in which all this points. The culprit is--\"\n\n\"Mr. John Turner,\" cried the hotel waiter, opening the door of\nour sitting-room, and ushering in a visitor.\n\nThe man who entered was a strange and impressive figure. His\nslow, limping step and bowed shoulders gave the appearance of\ndecrepitude, and yet his hard, deep-lined, craggy features, and\nhis enormous limbs showed that he was possessed of unusual\nstrength of body and of character. His tangled beard, grizzled\nhair, and outstanding, drooping eyebrows combined to give an air\nof dignity and power to his appearance, but his face was of an\nashen white, while his lips and the corners of his nostrils were\ntinged with a shade of blue. It was clear to me at a glance that\nhe was in the grip of some deadly and chronic disease.\n\n\"Pray sit down on the sofa,\" said Holmes gently. \"You had my\nnote?\"\n\n\"Yes, the lodge-keeper brought it up. You said that you wished to\nsee me here to avoid scandal.\"\n\n\"I thought people would talk if I went to the Hall.\"\n\n\"And why did you wish to see me?\" He looked across at my\ncompanion with despair in his weary eyes, as though his question\nwas already answered.\n\n\"Yes,\" said Holmes, answering the look rather than the words. \"It\nis so. I know all about McCarthy.\"\n\nThe old man sank his face in his hands. \"God help me!\" he cried.\n\"But I would not have let the young man come to harm. I give you\nmy word that I would have spoken out if it went against him at\nthe Assizes.\"\n\n\"I am glad to hear you say so,\" said Holmes gravely.\n\n\"I would have spoken now had it not been for my dear girl. It\nwould break her heart--it will break her heart when she hears\nthat I am arrested.\"\n\n\"It may not come to that,\" said Holmes.\n\n\"What?\"\n\n\"I am no official agent. I understand that it was your daughter\nwho required my presence here, and I am acting in her interests.\nYoung McCarthy must be got off, however.\"\n\n\"I am a dying man,\" said old Turner. \"I have had diabetes for\nyears. My doctor says it is a question whether I shall live a\nmonth. Yet I would rather die under my own roof than in a gaol.\"\n\nHolmes rose and sat down at the table with his pen in his hand\nand a bundle of paper before him. \"Just tell us the truth,\" he\nsaid. \"I shall jot down the facts. You will sign it, and Watson\nhere can witness it. Then I could produce your confession at the\nlast extremity to save young McCarthy. I promise you that I shall\nnot use it unless it is absolutely needed.\"\n\n\"It's as well,\" said the old man; \"it's a question whether I\nshall live to the Assizes, so it matters little to me, but I\nshould wish to spare Alice the shock. And now I will make the\nthing clear to you; it has been a long time in the acting, but\nwill not take me long to tell.\n\n\"You didn't know this dead man, McCarthy. He was a devil\nincarnate. I tell you that. God keep you out of the clutches of\nsuch a man as he. His grip has been upon me these twenty years,\nand he has blasted my life. I'll tell you first how I came to be\nin his power.\n\n\"It was in the early '60's at the diggings. I was a young chap\nthen, hot-blooded and reckless, ready to turn my hand at\nanything; I got among bad companions, took to drink, had no luck\nwith my claim, took to the bush, and in a word became what you\nwould call over here a highway robber. There were six of us, and\nwe had a wild, free life of it, sticking up a station from time\nto time, or stopping the wagons on the road to the diggings.\nBlack Jack of Ballarat was the name I went under, and our party\nis still remembered in the colony as the Ballarat Gang.\n\n\"One day a gold convoy came down from Ballarat to Melbourne, and\nwe lay in wait for it and attacked it. There were six troopers\nand six of us, so it was a close thing, but we emptied four of\ntheir saddles at the first volley. Three of our boys were killed,\nhowever, before we got the swag. I put my pistol to the head of\nthe wagon-driver, who was this very man McCarthy. I wish to the\nLord that I had shot him then, but I spared him, though I saw his\nwicked little eyes fixed on my face, as though to remember every\nfeature. We got away with the gold, became wealthy men, and made\nour way over to England without being suspected. There I parted\nfrom my old pals and determined to settle down to a quiet and\nrespectable life. I bought this estate, which chanced to be in\nthe market, and I set myself to do a little good with my money,\nto make up for the way in which I had earned it. I married, too,\nand though my wife died young she left me my dear little Alice.\nEven when she was just a baby her wee hand seemed to lead me down\nthe right path as nothing else had ever done. In a word, I turned\nover a new leaf and did my best to make up for the past. All was\ngoing well when McCarthy laid his grip upon me.\n\n\"I had gone up to town about an investment, and I met him in\nRegent Street with hardly a coat to his back or a boot to his\nfoot.\n\n\"'Here we are, Jack,' says he, touching me on the arm; 'we'll be\nas good as a family to you. There's two of us, me and my son, and\nyou can have the keeping of us. If you don't--it's a fine,\nlaw-abiding country is England, and there's always a policeman\nwithin hail.'\n\n\"Well, down they came to the west country, there was no shaking\nthem off, and there they have lived rent free on my best land\never since. There was no rest for me, no peace, no forgetfulness;\nturn where I would, there was his cunning, grinning face at my\nelbow. It grew worse as Alice grew up, for he soon saw I was more\nafraid of her knowing my past than of the police. Whatever he\nwanted he must have, and whatever it was I gave him without\nquestion, land, money, houses, until at last he asked a thing\nwhich I could not give. He asked for Alice.\n\n\"His son, you see, had grown up, and so had my girl, and as I was\nknown to be in weak health, it seemed a fine stroke to him that\nhis lad should step into the whole property. But there I was\nfirm. I would not have his cursed stock mixed with mine; not that\nI had any dislike to the lad, but his blood was in him, and that\nwas enough. I stood firm. McCarthy threatened. I braved him to do\nhis worst. We were to meet at the pool midway between our houses\nto talk it over.\n\n\"When I went down there I found him talking with his son, so I\nsmoked a cigar and waited behind a tree until he should be alone.\nBut as I listened to his talk all that was black and bitter in\nme seemed to come uppermost. He was urging his son to marry my\ndaughter with as little regard for what she might think as if she\nwere a slut from off the streets. It drove me mad to think that I\nand all that I held most dear should be in the power of such a\nman as this. Could I not snap the bond? I was already a dying and\na desperate man. Though clear of mind and fairly strong of limb,\nI knew that my own fate was sealed. But my memory and my girl!\nBoth could be saved if I could but silence that foul tongue. I\ndid it, Mr. Holmes. I would do it again. Deeply as I have sinned,\nI have led a life of martyrdom to atone for it. But that my girl\nshould be entangled in the same meshes which held me was more\nthan I could suffer. I struck him down with no more compunction\nthan if he had been some foul and venomous beast. His cry brought\nback his son; but I had gained the cover of the wood, though I\nwas forced to go back to fetch the cloak which I had dropped in\nmy flight. That is the true story, gentlemen, of all that\noccurred.\"\n\n\"Well, it is not for me to judge you,\" said Holmes as the old man\nsigned the statement which had been drawn out. \"I pray that we\nmay never be exposed to such a temptation.\"\n\n\"I pray not, sir. And what do you intend to do?\"\n\n\"In view of your health, nothing. You are yourself aware that you\nwill soon have to answer for your deed at a higher court than the\nAssizes. I will keep your confession, and if McCarthy is\ncondemned I shall be forced to use it. If not, it shall never be\nseen by mortal eye; and your secret, whether you be alive or\ndead, shall be safe with us.\"\n\n\"Farewell, then,\" said the old man solemnly. \"Your own deathbeds,\nwhen they come, will be the easier for the thought of the peace\nwhich you have given to mine.\" Tottering and shaking in all his\ngiant frame, he stumbled slowly from the room.\n\n\"God help us!\" said Holmes after a long silence. \"Why does fate\nplay such tricks with poor, helpless worms? I never hear of such\na case as this that I do not think of Baxter's words, and say,\n'There, but for the grace of God, goes Sherlock Holmes.'\"\n\nJames McCarthy was acquitted at the Assizes on the strength of a\nnumber of objections which had been drawn out by Holmes and\nsubmitted to the defending counsel. Old Turner lived for seven\nmonths after our interview, but he is now dead; and there is\nevery prospect that the son and daughter may come to live happily\ntogether in ignorance of the black cloud which rests upon their\npast.\n\n\n\nADVENTURE V. THE FIVE ORANGE PIPS\n\nWhen I glance over my notes and records of the Sherlock Holmes\ncases between the years '82 and '90, I am faced by so many which\npresent strange and interesting features that it is no easy\nmatter to know which to choose and which to leave. Some, however,\nhave already gained publicity through the papers, and others have\nnot offered a field for those peculiar qualities which my friend\npossessed in so high a degree, and which it is the object of\nthese papers to illustrate. Some, too, have baffled his\nanalytical skill, and would be, as narratives, beginnings without\nan ending, while others have been but partially cleared up, and\nhave their explanations founded rather upon conjecture and\nsurmise than on that absolute logical proof which was so dear to\nhim. There is, however, one of these last which was so remarkable\nin its details and so startling in its results that I am tempted\nto give some account of it in spite of the fact that there are\npoints in connection with it which never have been, and probably\nnever will be, entirely cleared up.\n\nThe year '87 furnished us with a long series of cases of greater\nor less interest, of which I retain the records. Among my\nheadings under this one twelve months I find an account of the\nadventure of the Paradol Chamber, of the Amateur Mendicant\nSociety, who held a luxurious club in the lower vault of a\nfurniture warehouse, of the facts connected with the loss of the\nBritish barque \"Sophy Anderson\", of the singular adventures of the\nGrice Patersons in the island of Uffa, and finally of the\nCamberwell poisoning case. In the latter, as may be remembered,\nSherlock Holmes was able, by winding up the dead man's watch, to\nprove that it had been wound up two hours before, and that\ntherefore the deceased had gone to bed within that time--a\ndeduction which was of the greatest importance in clearing up the\ncase. All these I may sketch out at some future date, but none of\nthem present such singular features as the strange train of\ncircumstances which I have now taken up my pen to describe.\n\nIt was in the latter days of September, and the equinoctial gales\nhad set in with exceptional violence. All day the wind had\nscreamed and the rain had beaten against the windows, so that\neven here in the heart of great, hand-made London we were forced\nto raise our minds for the instant from the routine of life and\nto recognise the presence of those great elemental forces which\nshriek at mankind through the bars of his civilisation, like\nuntamed beasts in a cage. As evening drew in, the storm grew\nhigher and louder, and the wind cried and sobbed like a child in\nthe chimney. Sherlock Holmes sat moodily at one side of the\nfireplace cross-indexing his records of crime, while I at the\nother was deep in one of Clark Russell's fine sea-stories until\nthe howl of the gale from without seemed to blend with the text,\nand the splash of the rain to lengthen out into the long swash of\nthe sea waves. My wife was on a visit to her mother's, and for a\nfew days I was a dweller once more in my old quarters at Baker\nStreet.\n\n\"Why,\" said I, glancing up at my companion, \"that was surely the\nbell. Who could come to-night? Some friend of yours, perhaps?\"\n\n\"Except yourself I have none,\" he answered. \"I do not encourage\nvisitors.\"\n\n\"A client, then?\"\n\n\"If so, it is a serious case. Nothing less would bring a man out\non such a day and at such an hour. But I take it that it is more\nlikely to be some crony of the landlady's.\"\n\nSherlock Holmes was wrong in his conjecture, however, for there\ncame a step in the passage and a tapping at the door. He\nstretched out his long arm to turn the lamp away from himself and\ntowards the vacant chair upon which a newcomer must sit.\n\n\"Come in!\" said he.\n\nThe man who entered was young, some two-and-twenty at the\noutside, well-groomed and trimly clad, with something of\nrefinement and delicacy in his bearing. The streaming umbrella\nwhich he held in his hand, and his long shining waterproof told\nof the fierce weather through which he had come. He looked about\nhim anxiously in the glare of the lamp, and I could see that his\nface was pale and his eyes heavy, like those of a man who is\nweighed down with some great anxiety.\n\n\"I owe you an apology,\" he said, raising his golden pince-nez to\nhis eyes. \"I trust that I am not intruding. I fear that I have\nbrought some traces of the storm and rain into your snug\nchamber.\"\n\n\"Give me your coat and umbrella,\" said Holmes. \"They may rest\nhere on the hook and will be dry presently. You have come up from\nthe south-west, I see.\"\n\n\"Yes, from Horsham.\"\n\n\"That clay and chalk mixture which I see upon your toe caps is\nquite distinctive.\"\n\n\"I have come for advice.\"\n\n\"That is easily got.\"\n\n\"And help.\"\n\n\"That is not always so easy.\"\n\n\"I have heard of you, Mr. Holmes. I heard from Major Prendergast\nhow you saved him in the Tankerville Club scandal.\"\n\n\"Ah, of course. He was wrongfully accused of cheating at cards.\"\n\n\"He said that you could solve anything.\"\n\n\"He said too much.\"\n\n\"That you are never beaten.\"\n\n\"I have been beaten four times--three times by men, and once by a\nwoman.\"\n\n\"But what is that compared with the number of your successes?\"\n\n\"It is true that I have been generally successful.\"\n\n\"Then you may be so with me.\"\n\n\"I beg that you will draw your chair up to the fire and favour me\nwith some details as to your case.\"\n\n\"It is no ordinary one.\"\n\n\"None of those which come to me are. I am the last court of\nappeal.\"\n\n\"And yet I question, sir, whether, in all your experience, you\nhave ever listened to a more mysterious and inexplicable chain of\nevents than those which have happened in my own family.\"\n\n\"You fill me with interest,\" said Holmes. \"Pray give us the\nessential facts from the commencement, and I can afterwards\nquestion you as to those details which seem to me to be most\nimportant.\"\n\nThe young man pulled his chair up and pushed his wet feet out\ntowards the blaze.\n\n\"My name,\" said he, \"is John Openshaw, but my own affairs have,\nas far as I can understand, little to do with this awful\nbusiness. It is a hereditary matter; so in order to give you an\nidea of the facts, I must go back to the commencement of the\naffair.\n\n\"You must know that my grandfather had two sons--my uncle Elias\nand my father Joseph. My father had a small factory at Coventry,\nwhich he enlarged at the time of the invention of bicycling. He\nwas a patentee of the Openshaw unbreakable tire, and his business\nmet with such success that he was able to sell it and to retire\nupon a handsome competence.\n\n\"My uncle Elias emigrated to America when he was a young man and\nbecame a planter in Florida, where he was reported to have done\nvery well. At the time of the war he fought in Jackson's army,\nand afterwards under Hood, where he rose to be a colonel. When\nLee laid down his arms my uncle returned to his plantation, where\nhe remained for three or four years. About 1869 or 1870 he came\nback to Europe and took a small estate in Sussex, near Horsham.\nHe had made a very considerable fortune in the States, and his\nreason for leaving them was his aversion to the negroes, and his\ndislike of the Republican policy in extending the franchise to\nthem. He was a singular man, fierce and quick-tempered, very\nfoul-mouthed when he was angry, and of a most retiring\ndisposition. During all the years that he lived at Horsham, I\ndoubt if ever he set foot in the town. He had a garden and two or\nthree fields round his house, and there he would take his\nexercise, though very often for weeks on end he would never leave\nhis room. He drank a great deal of brandy and smoked very\nheavily, but he would see no society and did not want any\nfriends, not even his own brother.\n\n\"He didn't mind me; in fact, he took a fancy to me, for at the\ntime when he saw me first I was a youngster of twelve or so. This\nwould be in the year 1878, after he had been eight or nine years\nin England. He begged my father to let me live with him and he\nwas very kind to me in his way. When he was sober he used to be\nfond of playing backgammon and draughts with me, and he would\nmake me his representative both with the servants and with the\ntradespeople, so that by the time that I was sixteen I was quite\nmaster of the house. I kept all the keys and could go where I\nliked and do what I liked, so long as I did not disturb him in\nhis privacy. There was one singular exception, however, for he\nhad a single room, a lumber-room up among the attics, which was\ninvariably locked, and which he would never permit either me or\nanyone else to enter. With a boy's curiosity I have peeped\nthrough the keyhole, but I was never able to see more than such a\ncollection of old trunks and bundles as would be expected in such\na room.\n\n\"One day--it was in March, 1883--a letter with a foreign stamp\nlay upon the table in front of the colonel's plate. It was not a\ncommon thing for him to receive letters, for his bills were all\npaid in ready money, and he had no friends of any sort. 'From\nIndia!' said he as he took it up, 'Pondicherry postmark! What can\nthis be?' Opening it hurriedly, out there jumped five little\ndried orange pips, which pattered down upon his plate. I began to\nlaugh at this, but the laugh was struck from my lips at the sight\nof his face. His lip had fallen, his eyes were protruding, his\nskin the colour of putty, and he glared at the envelope which he\nstill held in his trembling hand, 'K. K. K.!' he shrieked, and\nthen, 'My God, my God, my sins have overtaken me!'\n\n\"'What is it, uncle?' I cried.\n\n\"'Death,' said he, and rising from the table he retired to his\nroom, leaving me palpitating with horror. I took up the envelope\nand saw scrawled in red ink upon the inner flap, just above the\ngum, the letter K three times repeated. There was nothing else\nsave the five dried pips. What could be the reason of his\noverpowering terror? I left the breakfast-table, and as I\nascended the stair I met him coming down with an old rusty key,\nwhich must have belonged to the attic, in one hand, and a small\nbrass box, like a cashbox, in the other.\n\n\"'They may do what they like, but I'll checkmate them still,'\nsaid he with an oath. 'Tell Mary that I shall want a fire in my\nroom to-day, and send down to Fordham, the Horsham lawyer.'\n\n\"I did as he ordered, and when the lawyer arrived I was asked to\nstep up to the room. The fire was burning brightly, and in the\ngrate there was a mass of black, fluffy ashes, as of burned\npaper, while the brass box stood open and empty beside it. As I\nglanced at the box I noticed, with a start, that upon the lid was\nprinted the treble K which I had read in the morning upon the\nenvelope.\n\n\"'I wish you, John,' said my uncle, 'to witness my will. I leave\nmy estate, with all its advantages and all its disadvantages, to\nmy brother, your father, whence it will, no doubt, descend to\nyou. If you can enjoy it in peace, well and good! If you find you\ncannot, take my advice, my boy, and leave it to your deadliest\nenemy. I am sorry to give you such a two-edged thing, but I can't\nsay what turn things are going to take. Kindly sign the paper\nwhere Mr. Fordham shows you.'\n\n\"I signed the paper as directed, and the lawyer took it away with\nhim. The singular incident made, as you may think, the deepest\nimpression upon me, and I pondered over it and turned it every\nway in my mind without being able to make anything of it. Yet I\ncould not shake off the vague feeling of dread which it left\nbehind, though the sensation grew less keen as the weeks passed\nand nothing happened to disturb the usual routine of our lives. I\ncould see a change in my uncle, however. He drank more than ever,\nand he was less inclined for any sort of society. Most of his\ntime he would spend in his room, with the door locked upon the\ninside, but sometimes he would emerge in a sort of drunken frenzy\nand would burst out of the house and tear about the garden with a\nrevolver in his hand, screaming out that he was afraid of no man,\nand that he was not to be cooped up, like a sheep in a pen, by\nman or devil. When these hot fits were over, however, he would\nrush tumultuously in at the door and lock and bar it behind him,\nlike a man who can brazen it out no longer against the terror\nwhich lies at the roots of his soul. At such times I have seen\nhis face, even on a cold day, glisten with moisture, as though it\nwere new raised from a basin.\n\n\"Well, to come to an end of the matter, Mr. Holmes, and not to\nabuse your patience, there came a night when he made one of those\ndrunken sallies from which he never came back. We found him, when\nwe went to search for him, face downward in a little\ngreen-scummed pool, which lay at the foot of the garden. There\nwas no sign of any violence, and the water was but two feet deep,\nso that the jury, having regard to his known eccentricity,\nbrought in a verdict of 'suicide.' But I, who knew how he winced\nfrom the very thought of death, had much ado to persuade myself\nthat he had gone out of his way to meet it. The matter passed,\nhowever, and my father entered into possession of the estate, and\nof some 14,000 pounds, which lay to his credit at the bank.\"\n\n\"One moment,\" Holmes interposed, \"your statement is, I foresee,\none of the most remarkable to which I have ever listened. Let me\nhave the date of the reception by your uncle of the letter, and\nthe date of his supposed suicide.\"\n\n\"The letter arrived on March 10, 1883. His death was seven weeks\nlater, upon the night of May 2nd.\"\n\n\"Thank you. Pray proceed.\"\n\n\"When my father took over the Horsham property, he, at my\nrequest, made a careful examination of the attic, which had been\nalways locked up. We found the brass box there, although its\ncontents had been destroyed. On the inside of the cover was a\npaper label, with the initials of K. K. K. repeated upon it, and\n'Letters, memoranda, receipts, and a register' written beneath.\nThese, we presume, indicated the nature of the papers which had\nbeen destroyed by Colonel Openshaw. For the rest, there was\nnothing of much importance in the attic save a great many\nscattered papers and note-books bearing upon my uncle's life in\nAmerica. Some of them were of the war time and showed that he had\ndone his duty well and had borne the repute of a brave soldier.\nOthers were of a date during the reconstruction of the Southern\nstates, and were mostly concerned with politics, for he had\nevidently taken a strong part in opposing the carpet-bag\npoliticians who had been sent down from the North.\n\n\"Well, it was the beginning of '84 when my father came to live at\nHorsham, and all went as well as possible with us until the\nJanuary of '85. On the fourth day after the new year I heard my\nfather give a sharp cry of surprise as we sat together at the\nbreakfast-table. There he was, sitting with a newly opened\nenvelope in one hand and five dried orange pips in the\noutstretched palm of the other one. He had always laughed at what\nhe called my cock-and-bull story about the colonel, but he looked\nvery scared and puzzled now that the same thing had come upon\nhimself.\n\n\"'Why, what on earth does this mean, John?' he stammered.\n\n\"My heart had turned to lead. 'It is K. K. K.,' said I.\n\n\"He looked inside the envelope. 'So it is,' he cried. 'Here are\nthe very letters. But what is this written above them?'\n\n\"'Put the papers on the sundial,' I read, peeping over his\nshoulder.\n\n\"'What papers? What sundial?' he asked.\n\n\"'The sundial in the garden. There is no other,' said I; 'but the\npapers must be those that are destroyed.'\n\n\"'Pooh!' said he, gripping hard at his courage. 'We are in a\ncivilised land here, and we can't have tomfoolery of this kind.\nWhere does the thing come from?'\n\n\"'From Dundee,' I answered, glancing at the postmark.\n\n\"'Some preposterous practical joke,' said he. 'What have I to do\nwith sundials and papers? I shall take no notice of such\nnonsense.'\n\n\"'I should certainly speak to the police,' I said.\n\n\"'And be laughed at for my pains. Nothing of the sort.'\n\n\"'Then let me do so?'\n\n\"'No, I forbid you. I won't have a fuss made about such\nnonsense.'\n\n\"It was in vain to argue with him, for he was a very obstinate\nman. I went about, however, with a heart which was full of\nforebodings.\n\n\"On the third day after the coming of the letter my father went\nfrom home to visit an old friend of his, Major Freebody, who is\nin command of one of the forts upon Portsdown Hill. I was glad\nthat he should go, for it seemed to me that he was farther from\ndanger when he was away from home. In that, however, I was in\nerror. Upon the second day of his absence I received a telegram\nfrom the major, imploring me to come at once. My father had\nfallen over one of the deep chalk-pits which abound in the\nneighbourhood, and was lying senseless, with a shattered skull. I\nhurried to him, but he passed away without having ever recovered\nhis consciousness. He had, as it appears, been returning from\nFareham in the twilight, and as the country was unknown to him,\nand the chalk-pit unfenced, the jury had no hesitation in\nbringing in a verdict of 'death from accidental causes.'\nCarefully as I examined every fact connected with his death, I\nwas unable to find anything which could suggest the idea of\nmurder. There were no signs of violence, no footmarks, no\nrobbery, no record of strangers having been seen upon the roads.\nAnd yet I need not tell you that my mind was far from at ease,\nand that I was well-nigh certain that some foul plot had been\nwoven round him.\n\n\"In this sinister way I came into my inheritance. You will ask me\nwhy I did not dispose of it? I answer, because I was well\nconvinced that our troubles were in some way dependent upon an\nincident in my uncle's life, and that the danger would be as\npressing in one house as in another.\n\n\"It was in January, '85, that my poor father met his end, and two\nyears and eight months have elapsed since then. During that time\nI have lived happily at Horsham, and I had begun to hope that\nthis curse had passed away from the family, and that it had ended\nwith the last generation. I had begun to take comfort too soon,\nhowever; yesterday morning the blow fell in the very shape in\nwhich it had come upon my father.\"\n\nThe young man took from his waistcoat a crumpled envelope, and\nturning to the table he shook out upon it five little dried\norange pips.\n\n\"This is the envelope,\" he continued. \"The postmark is\nLondon--eastern division. Within are the very words which were\nupon my father's last message: 'K. K. K.'; and then 'Put the\npapers on the sundial.'\"\n\n\"What have you done?\" asked Holmes.\n\n\"Nothing.\"\n\n\"Nothing?\"\n\n\"To tell the truth\"--he sank his face into his thin, white\nhands--\"I have felt helpless. I have felt like one of those poor\nrabbits when the snake is writhing towards it. I seem to be in\nthe grasp of some resistless, inexorable evil, which no foresight\nand no precautions can guard against.\"\n\n\"Tut! tut!\" cried Sherlock Holmes. \"You must act, man, or you are\nlost. Nothing but energy can save you. This is no time for\ndespair.\"\n\n\"I have seen the police.\"\n\n\"Ah!\"\n\n\"But they listened to my story with a smile. I am convinced that\nthe inspector has formed the opinion that the letters are all\npractical jokes, and that the deaths of my relations were really\naccidents, as the jury stated, and were not to be connected with\nthe warnings.\"\n\nHolmes shook his clenched hands in the air. \"Incredible\nimbecility!\" he cried.\n\n\"They have, however, allowed me a policeman, who may remain in\nthe house with me.\"\n\n\"Has he come with you to-night?\"\n\n\"No. His orders were to stay in the house.\"\n\nAgain Holmes raved in the air.\n\n\"Why did you come to me,\" he cried, \"and, above all, why did you\nnot come at once?\"\n\n\"I did not know. It was only to-day that I spoke to Major\nPrendergast about my troubles and was advised by him to come to\nyou.\"\n\n\"It is really two days since you had the letter. We should have\nacted before this. You have no further evidence, I suppose, than\nthat which you have placed before us--no suggestive detail which\nmight help us?\"\n\n\"There is one thing,\" said John Openshaw. He rummaged in his coat\npocket, and, drawing out a piece of discoloured, blue-tinted\npaper, he laid it out upon the table. \"I have some remembrance,\"\nsaid he, \"that on the day when my uncle burned the papers I\nobserved that the small, unburned margins which lay amid the\nashes were of this particular colour. I found this single sheet\nupon the floor of his room, and I am inclined to think that it\nmay be one of the papers which has, perhaps, fluttered out from\namong the others, and in that way has escaped destruction. Beyond\nthe mention of pips, I do not see that it helps us much. I think\nmyself that it is a page from some private diary. The writing is\nundoubtedly my uncle's.\"\n\nHolmes moved the lamp, and we both bent over the sheet of paper,\nwhich showed by its ragged edge that it had indeed been torn from\na book. It was headed, \"March, 1869,\" and beneath were the\nfollowing enigmatical notices:\n\n\"4th. Hudson came. Same old platform.\n\n\"7th. Set the pips on McCauley, Paramore, and\n      John Swain, of St. Augustine.\n\n\"9th. McCauley cleared.\n\n\"10th. John Swain cleared.\n\n\"12th. Visited Paramore. All well.\"\n\n\"Thank you!\" said Holmes, folding up the paper and returning it\nto our visitor. \"And now you must on no account lose another\ninstant. We cannot spare time even to discuss what you have told\nme. You must get home instantly and act.\"\n\n\"What shall I do?\"\n\n\"There is but one thing to do. It must be done at once. You must\nput this piece of paper which you have shown us into the brass\nbox which you have described. You must also put in a note to say\nthat all the other papers were burned by your uncle, and that\nthis is the only one which remains. You must assert that in such\nwords as will carry conviction with them. Having done this, you\nmust at once put the box out upon the sundial, as directed. Do\nyou understand?\"\n\n\"Entirely.\"\n\n\"Do not think of revenge, or anything of the sort, at present. I\nthink that we may gain that by means of the law; but we have our\nweb to weave, while theirs is already woven. The first\nconsideration is to remove the pressing danger which threatens\nyou. The second is to clear up the mystery and to punish the\nguilty parties.\"\n\n\"I thank you,\" said the young man, rising and pulling on his\novercoat. \"You have given me fresh life and hope. I shall\ncertainly do as you advise.\"\n\n\"Do not lose an instant. And, above all, take care of yourself in\nthe meanwhile, for I do not think that there can be a doubt that\nyou are threatened by a very real and imminent danger. How do you\ngo back?\"\n\n\"By train from Waterloo.\"\n\n\"It is not yet nine. The streets will be crowded, so I trust that\nyou may be in safety. And yet you cannot guard yourself too\nclosely.\"\n\n\"I am armed.\"\n\n\"That is well. To-morrow I shall set to work upon your case.\"\n\n\"I shall see you at Horsham, then?\"\n\n\"No, your secret lies in London. It is there that I shall seek\nit.\"\n\n\"Then I shall call upon you in a day, or in two days, with news\nas to the box and the papers. I shall take your advice in every\nparticular.\" He shook hands with us and took his leave. Outside\nthe wind still screamed and the rain splashed and pattered\nagainst the windows. This strange, wild story seemed to have come\nto us from amid the mad elements--blown in upon us like a sheet\nof sea-weed in a gale--and now to have been reabsorbed by them\nonce more.\n\nSherlock Holmes sat for some time in silence, with his head sunk\nforward and his eyes bent upon the red glow of the fire. Then he\nlit his pipe, and leaning back in his chair he watched the blue\nsmoke-rings as they chased each other up to the ceiling.\n\n\"I think, Watson,\" he remarked at last, \"that of all our cases we\nhave had none more fantastic than this.\"\n\n\"Save, perhaps, the Sign of Four.\"\n\n\"Well, yes. Save, perhaps, that. And yet this John Openshaw seems\nto me to be walking amid even greater perils than did the\nSholtos.\"\n\n\"But have you,\" I asked, \"formed any definite conception as to\nwhat these perils are?\"\n\n\"There can be no question as to their nature,\" he answered.\n\n\"Then what are they? Who is this K. K. K., and why does he pursue\nthis unhappy family?\"\n\nSherlock Holmes closed his eyes and placed his elbows upon the\narms of his chair, with his finger-tips together. \"The ideal\nreasoner,\" he remarked, \"would, when he had once been shown a\nsingle fact in all its bearings, deduce from it not only all the\nchain of events which led up to it but also all the results which\nwould follow from it. As Cuvier could correctly describe a whole\nanimal by the contemplation of a single bone, so the observer who\nhas thoroughly understood one link in a series of incidents\nshould be able to accurately state all the other ones, both\nbefore and after. We have not yet grasped the results which the\nreason alone can attain to. Problems may be solved in the study\nwhich have baffled all those who have sought a solution by the\naid of their senses. To carry the art, however, to its highest\npitch, it is necessary that the reasoner should be able to\nutilise all the facts which have come to his knowledge; and this\nin itself implies, as you will readily see, a possession of all\nknowledge, which, even in these days of free education and\nencyclopaedias, is a somewhat rare accomplishment. It is not so\nimpossible, however, that a man should possess all knowledge\nwhich is likely to be useful to him in his work, and this I have\nendeavoured in my case to do. If I remember rightly, you on one\noccasion, in the early days of our friendship, defined my limits\nin a very precise fashion.\"\n\n\"Yes,\" I answered, laughing. \"It was a singular document.\nPhilosophy, astronomy, and politics were marked at zero, I\nremember. Botany variable, geology profound as regards the\nmud-stains from any region within fifty miles of town, chemistry\neccentric, anatomy unsystematic, sensational literature and crime\nrecords unique, violin-player, boxer, swordsman, lawyer, and\nself-poisoner by cocaine and tobacco. Those, I think, were the\nmain points of my analysis.\"\n\nHolmes grinned at the last item. \"Well,\" he said, \"I say now, as\nI said then, that a man should keep his little brain-attic\nstocked with all the furniture that he is likely to use, and the\nrest he can put away in the lumber-room of his library, where he\ncan get it if he wants it. Now, for such a case as the one which\nhas been submitted to us to-night, we need certainly to muster\nall our resources. Kindly hand me down the letter K of the\n'American Encyclopaedia' which stands upon the shelf beside you.\nThank you. Now let us consider the situation and see what may be\ndeduced from it. In the first place, we may start with a strong\npresumption that Colonel Openshaw had some very strong reason for\nleaving America. Men at his time of life do not change all their\nhabits and exchange willingly the charming climate of Florida for\nthe lonely life of an English provincial town. His extreme love\nof solitude in England suggests the idea that he was in fear of\nsomeone or something, so we may assume as a working hypothesis\nthat it was fear of someone or something which drove him from\nAmerica. As to what it was he feared, we can only deduce that by\nconsidering the formidable letters which were received by himself\nand his successors. Did you remark the postmarks of those\nletters?\"\n\n\"The first was from Pondicherry, the second from Dundee, and the\nthird from London.\"\n\n\"From East London. What do you deduce from that?\"\n\n\"They are all seaports. That the writer was on board of a ship.\"\n\n\"Excellent. We have already a clue. There can be no doubt that\nthe probability--the strong probability--is that the writer was\non board of a ship. And now let us consider another point. In the\ncase of Pondicherry, seven weeks elapsed between the threat and\nits fulfilment, in Dundee it was only some three or four days.\nDoes that suggest anything?\"\n\n\"A greater distance to travel.\"\n\n\"But the letter had also a greater distance to come.\"\n\n\"Then I do not see the point.\"\n\n\"There is at least a presumption that the vessel in which the man\nor men are is a sailing-ship. It looks as if they always send\ntheir singular warning or token before them when starting upon\ntheir mission. You see how quickly the deed followed the sign\nwhen it came from Dundee. If they had come from Pondicherry in a\nsteamer they would have arrived almost as soon as their letter.\nBut, as a matter of fact, seven weeks elapsed. I think that those\nseven weeks represented the difference between the mail-boat which\nbrought the letter and the sailing vessel which brought the\nwriter.\"\n\n\"It is possible.\"\n\n\"More than that. It is probable. And now you see the deadly\nurgency of this new case, and why I urged young Openshaw to\ncaution. The blow has always fallen at the end of the time which\nit would take the senders to travel the distance. But this one\ncomes from London, and therefore we cannot count upon delay.\"\n\n\"Good God!\" I cried. \"What can it mean, this relentless\npersecution?\"\n\n\"The papers which Openshaw carried are obviously of vital\nimportance to the person or persons in the sailing-ship. I think\nthat it is quite clear that there must be more than one of them.\nA single man could not have carried out two deaths in such a way\nas to deceive a coroner's jury. There must have been several in\nit, and they must have been men of resource and determination.\nTheir papers they mean to have, be the holder of them who it may.\nIn this way you see K. K. K. ceases to be the initials of an\nindividual and becomes the badge of a society.\"\n\n\"But of what society?\"\n\n\"Have you never--\" said Sherlock Holmes, bending forward and\nsinking his voice--\"have you never heard of the Ku Klux Klan?\"\n\n\"I never have.\"\n\nHolmes turned over the leaves of the book upon his knee. \"Here it\nis,\" said he presently:\n\n\"'Ku Klux Klan. A name derived from the fanciful resemblance to\nthe sound produced by cocking a rifle. This terrible secret\nsociety was formed by some ex-Confederate soldiers in the\nSouthern states after the Civil War, and it rapidly formed local\nbranches in different parts of the country, notably in Tennessee,\nLouisiana, the Carolinas, Georgia, and Florida. Its power was\nused for political purposes, principally for the terrorising of\nthe negro voters and the murdering and driving from the country\nof those who were opposed to its views. Its outrages were usually\npreceded by a warning sent to the marked man in some fantastic\nbut generally recognised shape--a sprig of oak-leaves in some\nparts, melon seeds or orange pips in others. On receiving this\nthe victim might either openly abjure his former ways, or might\nfly from the country. If he braved the matter out, death would\nunfailingly come upon him, and usually in some strange and\nunforeseen manner. So perfect was the organisation of the\nsociety, and so systematic its methods, that there is hardly a\ncase upon record where any man succeeded in braving it with\nimpunity, or in which any of its outrages were traced home to the\nperpetrators. For some years the organisation flourished in spite\nof the efforts of the United States government and of the better\nclasses of the community in the South. Eventually, in the year\n1869, the movement rather suddenly collapsed, although there have\nbeen sporadic outbreaks of the same sort since that date.'\n\n\"You will observe,\" said Holmes, laying down the volume, \"that\nthe sudden breaking up of the society was coincident with the\ndisappearance of Openshaw from America with their papers. It may\nwell have been cause and effect. It is no wonder that he and his\nfamily have some of the more implacable spirits upon their track.\nYou can understand that this register and diary may implicate\nsome of the first men in the South, and that there may be many\nwho will not sleep easy at night until it is recovered.\"\n\n\"Then the page we have seen--\"\n\n\"Is such as we might expect. It ran, if I remember right, 'sent\nthe pips to A, B, and C'--that is, sent the society's warning to\nthem. Then there are successive entries that A and B cleared, or\nleft the country, and finally that C was visited, with, I fear, a\nsinister result for C. Well, I think, Doctor, that we may let\nsome light into this dark place, and I believe that the only\nchance young Openshaw has in the meantime is to do what I have\ntold him. There is nothing more to be said or to be done\nto-night, so hand me over my violin and let us try to forget for\nhalf an hour the miserable weather and the still more miserable\nways of our fellow-men.\"\n\n\nIt had cleared in the morning, and the sun was shining with a\nsubdued brightness through the dim veil which hangs over the\ngreat city. Sherlock Holmes was already at breakfast when I came\ndown.\n\n\"You will excuse me for not waiting for you,\" said he; \"I have, I\nforesee, a very busy day before me in looking into this case of\nyoung Openshaw's.\"\n\n\"What steps will you take?\" I asked.\n\n\"It will very much depend upon the results of my first inquiries.\nI may have to go down to Horsham, after all.\"\n\n\"You will not go there first?\"\n\n\"No, I shall commence with the City. Just ring the bell and the\nmaid will bring up your coffee.\"\n\nAs I waited, I lifted the unopened newspaper from the table and\nglanced my eye over it. It rested upon a heading which sent a\nchill to my heart.\n\n\"Holmes,\" I cried, \"you are too late.\"\n\n\"Ah!\" said he, laying down his cup, \"I feared as much. How was it\ndone?\" He spoke calmly, but I could see that he was deeply moved.\n\n\"My eye caught the name of Openshaw, and the heading 'Tragedy\nNear Waterloo Bridge.' Here is the account:\n\n\"Between nine and ten last night Police-Constable Cook, of the H\nDivision, on duty near Waterloo Bridge, heard a cry for help and\na splash in the water. The night, however, was extremely dark and\nstormy, so that, in spite of the help of several passers-by, it\nwas quite impossible to effect a rescue. The alarm, however, was\ngiven, and, by the aid of the water-police, the body was\neventually recovered. It proved to be that of a young gentleman\nwhose name, as it appears from an envelope which was found in his\npocket, was John Openshaw, and whose residence is near Horsham.\nIt is conjectured that he may have been hurrying down to catch\nthe last train from Waterloo Station, and that in his haste and\nthe extreme darkness he missed his path and walked over the edge\nof one of the small landing-places for river steamboats. The body\nexhibited no traces of violence, and there can be no doubt that\nthe deceased had been the victim of an unfortunate accident,\nwhich should have the effect of calling the attention of the\nauthorities to the condition of the riverside landing-stages.\"\n\nWe sat in silence for some minutes, Holmes more depressed and\nshaken than I had ever seen him.\n\n\"That hurts my pride, Watson,\" he said at last. \"It is a petty\nfeeling, no doubt, but it hurts my pride. It becomes a personal\nmatter with me now, and, if God sends me health, I shall set my\nhand upon this gang. That he should come to me for help, and that\nI should send him away to his death--!\" He sprang from his chair\nand paced about the room in uncontrollable agitation, with a\nflush upon his sallow cheeks and a nervous clasping and\nunclasping of his long thin hands.\n\n\"They must be cunning devils,\" he exclaimed at last. \"How could\nthey have decoyed him down there? The Embankment is not on the\ndirect line to the station. The bridge, no doubt, was too\ncrowded, even on such a night, for their purpose. Well, Watson,\nwe shall see who will win in the long run. I am going out now!\"\n\n\"To the police?\"\n\n\"No; I shall be my own police. When I have spun the web they may\ntake the flies, but not before.\"\n\nAll day I was engaged in my professional work, and it was late in\nthe evening before I returned to Baker Street. Sherlock Holmes\nhad not come back yet. It was nearly ten o'clock before he\nentered, looking pale and worn. He walked up to the sideboard,\nand tearing a piece from the loaf he devoured it voraciously,\nwashing it down with a long draught of water.\n\n\"You are hungry,\" I remarked.\n\n\"Starving. It had escaped my memory. I have had nothing since\nbreakfast.\"\n\n\"Nothing?\"\n\n\"Not a bite. I had no time to think of it.\"\n\n\"And how have you succeeded?\"\n\n\"Well.\"\n\n\"You have a clue?\"\n\n\"I have them in the hollow of my hand. Young Openshaw shall not\nlong remain unavenged. Why, Watson, let us put their own devilish\ntrade-mark upon them. It is well thought of!\"\n\n\"What do you mean?\"\n\nHe took an orange from the cupboard, and tearing it to pieces he\nsqueezed out the pips upon the table. Of these he took five and\nthrust them into an envelope. On the inside of the flap he wrote\n\"S. H. for J. O.\" Then he sealed it and addressed it to \"Captain\nJames Calhoun, Barque 'Lone Star,' Savannah, Georgia.\"\n\n\"That will await him when he enters port,\" said he, chuckling.\n\"It may give him a sleepless night. He will find it as sure a\nprecursor of his fate as Openshaw did before him.\"\n\n\"And who is this Captain Calhoun?\"\n\n\"The leader of the gang. I shall have the others, but he first.\"\n\n\"How did you trace it, then?\"\n\nHe took a large sheet of paper from his pocket, all covered with\ndates and names.\n\n\"I have spent the whole day,\" said he, \"over Lloyd's registers\nand files of the old papers, following the future career of every\nvessel which touched at Pondicherry in January and February in\n'83. There were thirty-six ships of fair tonnage which were\nreported there during those months. Of these, one, the 'Lone Star,'\ninstantly attracted my attention, since, although it was reported\nas having cleared from London, the name is that which is given to\none of the states of the Union.\"\n\n\"Texas, I think.\"\n\n\"I was not and am not sure which; but I knew that the ship must\nhave an American origin.\"\n\n\"What then?\"\n\n\"I searched the Dundee records, and when I found that the barque\n'Lone Star' was there in January, '85, my suspicion became a\ncertainty. I then inquired as to the vessels which lay at present\nin the port of London.\"\n\n\"Yes?\"\n\n\"The 'Lone Star' had arrived here last week. I went down to the\nAlbert Dock and found that she had been taken down the river by\nthe early tide this morning, homeward bound to Savannah. I wired\nto Gravesend and learned that she had passed some time ago, and\nas the wind is easterly I have no doubt that she is now past the\nGoodwins and not very far from the Isle of Wight.\"\n\n\"What will you do, then?\"\n\n\"Oh, I have my hand upon him. He and the two mates, are as I\nlearn, the only native-born Americans in the ship. The others are\nFinns and Germans. I know, also, that they were all three away\nfrom the ship last night. I had it from the stevedore who has\nbeen loading their cargo. By the time that their sailing-ship\nreaches Savannah the mail-boat will have carried this letter, and\nthe cable will have informed the police of Savannah that these\nthree gentlemen are badly wanted here upon a charge of murder.\"\n\nThere is ever a flaw, however, in the best laid of human plans,\nand the murderers of John Openshaw were never to receive the\norange pips which would show them that another, as cunning and as\nresolute as themselves, was upon their track. Very long and very\nsevere were the equinoctial gales that year. We waited long for\nnews of the \"Lone Star\" of Savannah, but none ever reached us. We\ndid at last hear that somewhere far out in the Atlantic a\nshattered stern-post of a boat was seen swinging in the trough\nof a wave, with the letters \"L. S.\" carved upon it, and that is\nall which we shall ever know of the fate of the \"Lone Star.\"\n\n\n\nADVENTURE VI. THE MAN WITH THE TWISTED LIP\n\nIsa Whitney, brother of the late Elias Whitney, D.D., Principal\nof the Theological College of St. George's, was much addicted to\nopium. The habit grew upon him, as I understand, from some\nfoolish freak when he was at college; for having read De\nQuincey's description of his dreams and sensations, he had\ndrenched his tobacco with laudanum in an attempt to produce the\nsame effects. He found, as so many more have done, that the\npractice is easier to attain than to get rid of, and for many\nyears he continued to be a slave to the drug, an object of\nmingled horror and pity to his friends and relatives. I can see\nhim now, with yellow, pasty face, drooping lids, and pin-point\npupils, all huddled in a chair, the wreck and ruin of a noble\nman.\n\nOne night--it was in June, '89--there came a ring to my bell,\nabout the hour when a man gives his first yawn and glances at the\nclock. I sat up in my chair, and my wife laid her needle-work\ndown in her lap and made a little face of disappointment.\n\n\"A patient!\" said she. \"You'll have to go out.\"\n\nI groaned, for I was newly come back from a weary day.\n\nWe heard the door open, a few hurried words, and then quick steps\nupon the linoleum. Our own door flew open, and a lady, clad in\nsome dark-coloured stuff, with a black veil, entered the room.\n\n\"You will excuse my calling so late,\" she began, and then,\nsuddenly losing her self-control, she ran forward, threw her arms\nabout my wife's neck, and sobbed upon her shoulder. \"Oh, I'm in\nsuch trouble!\" she cried; \"I do so want a little help.\"\n\n\"Why,\" said my wife, pulling up her veil, \"it is Kate Whitney.\nHow you startled me, Kate! I had not an idea who you were when\nyou came in.\"\n\n\"I didn't know what to do, so I came straight to you.\" That was\nalways the way. Folk who were in grief came to my wife like birds\nto a light-house.\n\n\"It was very sweet of you to come. Now, you must have some wine\nand water, and sit here comfortably and tell us all about it. Or\nshould you rather that I sent James off to bed?\"\n\n\"Oh, no, no! I want the doctor's advice and help, too. It's about\nIsa. He has not been home for two days. I am so frightened about\nhim!\"\n\nIt was not the first time that she had spoken to us of her\nhusband's trouble, to me as a doctor, to my wife as an old friend\nand school companion. We soothed and comforted her by such words\nas we could find. Did she know where her husband was? Was it\npossible that we could bring him back to her?\n\nIt seems that it was. She had the surest information that of late\nhe had, when the fit was on him, made use of an opium den in the\nfarthest east of the City. Hitherto his orgies had always been\nconfined to one day, and he had come back, twitching and\nshattered, in the evening. But now the spell had been upon him\neight-and-forty hours, and he lay there, doubtless among the\ndregs of the docks, breathing in the poison or sleeping off the\neffects. There he was to be found, she was sure of it, at the Bar\nof Gold, in Upper Swandam Lane. But what was she to do? How could\nshe, a young and timid woman, make her way into such a place and\npluck her husband out from among the ruffians who surrounded him?\n\nThere was the case, and of course there was but one way out of\nit. Might I not escort her to this place? And then, as a second\nthought, why should she come at all? I was Isa Whitney's medical\nadviser, and as such I had influence over him. I could manage it\nbetter if I were alone. I promised her on my word that I would\nsend him home in a cab within two hours if he were indeed at the\naddress which she had given me. And so in ten minutes I had left\nmy armchair and cheery sitting-room behind me, and was speeding\neastward in a hansom on a strange errand, as it seemed to me at\nthe time, though the future only could show how strange it was to\nbe.\n\nBut there was no great difficulty in the first stage of my\nadventure. Upper Swandam Lane is a vile alley lurking behind the\nhigh wharves which line the north side of the river to the east\nof London Bridge. Between a slop-shop and a gin-shop, approached\nby a steep flight of steps leading down to a black gap like the\nmouth of a cave, I found the den of which I was in search.\nOrdering my cab to wait, I passed down the steps, worn hollow in\nthe centre by the ceaseless tread of drunken feet; and by the\nlight of a flickering oil-lamp above the door I found the latch\nand made my way into a long, low room, thick and heavy with the\nbrown opium smoke, and terraced with wooden berths, like the\nforecastle of an emigrant ship.\n\nThrough the gloom one could dimly catch a glimpse of bodies lying\nin strange fantastic poses, bowed shoulders, bent knees, heads\nthrown back, and chins pointing upward, with here and there a\ndark, lack-lustre eye turned upon the newcomer. Out of the black\nshadows there glimmered little red circles of light, now bright,\nnow faint, as the burning poison waxed or waned in the bowls of\nthe metal pipes. The most lay silent, but some muttered to\nthemselves, and others talked together in a strange, low,\nmonotonous voice, their conversation coming in gushes, and then\nsuddenly tailing off into silence, each mumbling out his own\nthoughts and paying little heed to the words of his neighbour. At\nthe farther end was a small brazier of burning charcoal, beside\nwhich on a three-legged wooden stool there sat a tall, thin old\nman, with his jaw resting upon his two fists, and his elbows upon\nhis knees, staring into the fire.\n\nAs I entered, a sallow Malay attendant had hurried up with a pipe\nfor me and a supply of the drug, beckoning me to an empty berth.\n\n\"Thank you. I have not come to stay,\" said I. \"There is a friend\nof mine here, Mr. Isa Whitney, and I wish to speak with him.\"\n\nThere was a movement and an exclamation from my right, and\npeering through the gloom, I saw Whitney, pale, haggard, and\nunkempt, staring out at me.\n\n\"My God! It's Watson,\" said he. He was in a pitiable state of\nreaction, with every nerve in a twitter. \"I say, Watson, what\no'clock is it?\"\n\n\"Nearly eleven.\"\n\n\"Of what day?\"\n\n\"Of Friday, June 19th.\"\n\n\"Good heavens! I thought it was Wednesday. It is Wednesday. What\nd'you want to frighten a chap for?\" He sank his face onto his\narms and began to sob in a high treble key.\n\n\"I tell you that it is Friday, man. Your wife has been waiting\nthis two days for you. You should be ashamed of yourself!\"\n\n\"So I am. But you've got mixed, Watson, for I have only been here\na few hours, three pipes, four pipes--I forget how many. But I'll\ngo home with you. I wouldn't frighten Kate--poor little Kate.\nGive me your hand! Have you a cab?\"\n\n\"Yes, I have one waiting.\"\n\n\"Then I shall go in it. But I must owe something. Find what I\nowe, Watson. I am all off colour. I can do nothing for myself.\"\n\nI walked down the narrow passage between the double row of\nsleepers, holding my breath to keep out the vile, stupefying\nfumes of the drug, and looking about for the manager. As I passed\nthe tall man who sat by the brazier I felt a sudden pluck at my\nskirt, and a low voice whispered, \"Walk past me, and then look\nback at me.\" The words fell quite distinctly upon my ear. I\nglanced down. They could only have come from the old man at my\nside, and yet he sat now as absorbed as ever, very thin, very\nwrinkled, bent with age, an opium pipe dangling down from between\nhis knees, as though it had dropped in sheer lassitude from his\nfingers. I took two steps forward and looked back. It took all my\nself-control to prevent me from breaking out into a cry of\nastonishment. He had turned his back so that none could see him\nbut I. His form had filled out, his wrinkles were gone, the dull\neyes had regained their fire, and there, sitting by the fire and\ngrinning at my surprise, was none other than Sherlock Holmes. He\nmade a slight motion to me to approach him, and instantly, as he\nturned his face half round to the company once more, subsided\ninto a doddering, loose-lipped senility.\n\n\"Holmes!\" I whispered, \"what on earth are you doing in this den?\"\n\n\"As low as you can,\" he answered; \"I have excellent ears. If you\nwould have the great kindness to get rid of that sottish friend\nof yours I should be exceedingly glad to have a little talk with\nyou.\"\n\n\"I have a cab outside.\"\n\n\"Then pray send him home in it. You may safely trust him, for he\nappears to be too limp to get into any mischief. I should\nrecommend you also to send a note by the cabman to your wife to\nsay that you have thrown in your lot with me. If you will wait\noutside, I shall be with you in five minutes.\"\n\nIt was difficult to refuse any of Sherlock Holmes' requests, for\nthey were always so exceedingly definite, and put forward with\nsuch a quiet air of mastery. I felt, however, that when Whitney\nwas once confined in the cab my mission was practically\naccomplished; and for the rest, I could not wish anything better\nthan to be associated with my friend in one of those singular\nadventures which were the normal condition of his existence. In a\nfew minutes I had written my note, paid Whitney's bill, led him\nout to the cab, and seen him driven through the darkness. In a\nvery short time a decrepit figure had emerged from the opium den,\nand I was walking down the street with Sherlock Holmes. For two\nstreets he shuffled along with a bent back and an uncertain foot.\nThen, glancing quickly round, he straightened himself out and\nburst into a hearty fit of laughter.\n\n\"I suppose, Watson,\" said he, \"that you imagine that I have added\nopium-smoking to cocaine injections, and all the other little\nweaknesses on which you have favoured me with your medical\nviews.\"\n\n\"I was certainly surprised to find you there.\"\n\n\"But not more so than I to find you.\"\n\n\"I came to find a friend.\"\n\n\"And I to find an enemy.\"\n\n\"An enemy?\"\n\n\"Yes; one of my natural enemies, or, shall I say, my natural\nprey. Briefly, Watson, I am in the midst of a very remarkable\ninquiry, and I have hoped to find a clue in the incoherent\nramblings of these sots, as I have done before now. Had I been\nrecognised in that den my life would not have been worth an\nhour's purchase; for I have used it before now for my own\npurposes, and the rascally Lascar who runs it has sworn to have\nvengeance upon me. There is a trap-door at the back of that\nbuilding, near the corner of Paul's Wharf, which could tell some\nstrange tales of what has passed through it upon the moonless\nnights.\"\n\n\"What! You do not mean bodies?\"\n\n\"Ay, bodies, Watson. We should be rich men if we had 1000 pounds\nfor every poor devil who has been done to death in that den. It\nis the vilest murder-trap on the whole riverside, and I fear that\nNeville St. Clair has entered it never to leave it more. But our\ntrap should be here.\" He put his two forefingers between his\nteeth and whistled shrilly--a signal which was answered by a\nsimilar whistle from the distance, followed shortly by the rattle\nof wheels and the clink of horses' hoofs.\n\n\"Now, Watson,\" said Holmes, as a tall dog-cart dashed up through\nthe gloom, throwing out two golden tunnels of yellow light from\nits side lanterns. \"You'll come with me, won't you?\"\n\n\"If I can be of use.\"\n\n\"Oh, a trusty comrade is always of use; and a chronicler still\nmore so. My room at The Cedars is a double-bedded one.\"\n\n\"The Cedars?\"\n\n\"Yes; that is Mr. St. Clair's house. I am staying there while I\nconduct the inquiry.\"\n\n\"Where is it, then?\"\n\n\"Near Lee, in Kent. We have a seven-mile drive before us.\"\n\n\"But I am all in the dark.\"\n\n\"Of course you are. You'll know all about it presently. Jump up\nhere. All right, John; we shall not need you. Here's half a\ncrown. Look out for me to-morrow, about eleven. Give her her\nhead. So long, then!\"\n\nHe flicked the horse with his whip, and we dashed away through\nthe endless succession of sombre and deserted streets, which\nwidened gradually, until we were flying across a broad\nbalustraded bridge, with the murky river flowing sluggishly\nbeneath us. Beyond lay another dull wilderness of bricks and\nmortar, its silence broken only by the heavy, regular footfall of\nthe policeman, or the songs and shouts of some belated party of\nrevellers. A dull wrack was drifting slowly across the sky, and a\nstar or two twinkled dimly here and there through the rifts of\nthe clouds. Holmes drove in silence, with his head sunk upon his\nbreast, and the air of a man who is lost in thought, while I sat\nbeside him, curious to learn what this new quest might be which\nseemed to tax his powers so sorely, and yet afraid to break in\nupon the current of his thoughts. We had driven several miles,\nand were beginning to get to the fringe of the belt of suburban\nvillas, when he shook himself, shrugged his shoulders, and lit up\nhis pipe with the air of a man who has satisfied himself that he\nis acting for the best.\n\n\"You have a grand gift of silence, Watson,\" said he. \"It makes\nyou quite invaluable as a companion. 'Pon my word, it is a great\nthing for me to have someone to talk to, for my own thoughts are\nnot over-pleasant. I was wondering what I should say to this dear\nlittle woman to-night when she meets me at the door.\"\n\n\"You forget that I know nothing about it.\"\n\n\"I shall just have time to tell you the facts of the case before\nwe get to Lee. It seems absurdly simple, and yet, somehow I can\nget nothing to go upon. There's plenty of thread, no doubt, but I\ncan't get the end of it into my hand. Now, I'll state the case\nclearly and concisely to you, Watson, and maybe you can see a\nspark where all is dark to me.\"\n\n\"Proceed, then.\"\n\n\"Some years ago--to be definite, in May, 1884--there came to Lee\na gentleman, Neville St. Clair by name, who appeared to have\nplenty of money. He took a large villa, laid out the grounds very\nnicely, and lived generally in good style. By degrees he made\nfriends in the neighbourhood, and in 1887 he married the daughter\nof a local brewer, by whom he now has two children. He had no\noccupation, but was interested in several companies and went into\ntown as a rule in the morning, returning by the 5:14 from Cannon\nStreet every night. Mr. St. Clair is now thirty-seven years of\nage, is a man of temperate habits, a good husband, a very\naffectionate father, and a man who is popular with all who know\nhim. I may add that his whole debts at the present moment, as far\nas we have been able to ascertain, amount to 88 pounds 10s., while\nhe has 220 pounds standing to his credit in the Capital and\nCounties Bank. There is no reason, therefore, to think that money\ntroubles have been weighing upon his mind.\n\n\"Last Monday Mr. Neville St. Clair went into town rather earlier\nthan usual, remarking before he started that he had two important\ncommissions to perform, and that he would bring his little boy\nhome a box of bricks. Now, by the merest chance, his wife\nreceived a telegram upon this same Monday, very shortly after his\ndeparture, to the effect that a small parcel of considerable\nvalue which she had been expecting was waiting for her at the\noffices of the Aberdeen Shipping Company. Now, if you are well up\nin your London, you will know that the office of the company is\nin Fresno Street, which branches out of Upper Swandam Lane, where\nyou found me to-night. Mrs. St. Clair had her lunch, started for\nthe City, did some shopping, proceeded to the company's office,\ngot her packet, and found herself at exactly 4:35 walking through\nSwandam Lane on her way back to the station. Have you followed me\nso far?\"\n\n\"It is very clear.\"\n\n\"If you remember, Monday was an exceedingly hot day, and Mrs. St.\nClair walked slowly, glancing about in the hope of seeing a cab,\nas she did not like the neighbourhood in which she found herself.\nWhile she was walking in this way down Swandam Lane, she suddenly\nheard an ejaculation or cry, and was struck cold to see her\nhusband looking down at her and, as it seemed to her, beckoning\nto her from a second-floor window. The window was open, and she\ndistinctly saw his face, which she describes as being terribly\nagitated. He waved his hands frantically to her, and then\nvanished from the window so suddenly that it seemed to her that\nhe had been plucked back by some irresistible force from behind.\nOne singular point which struck her quick feminine eye was that\nalthough he wore some dark coat, such as he had started to town\nin, he had on neither collar nor necktie.\n\n\"Convinced that something was amiss with him, she rushed down the\nsteps--for the house was none other than the opium den in which\nyou found me to-night--and running through the front room she\nattempted to ascend the stairs which led to the first floor. At\nthe foot of the stairs, however, she met this Lascar scoundrel of\nwhom I have spoken, who thrust her back and, aided by a Dane, who\nacts as assistant there, pushed her out into the street. Filled\nwith the most maddening doubts and fears, she rushed down the\nlane and, by rare good-fortune, met in Fresno Street a number of\nconstables with an inspector, all on their way to their beat. The\ninspector and two men accompanied her back, and in spite of the\ncontinued resistance of the proprietor, they made their way to\nthe room in which Mr. St. Clair had last been seen. There was no\nsign of him there. In fact, in the whole of that floor there was\nno one to be found save a crippled wretch of hideous aspect, who,\nit seems, made his home there. Both he and the Lascar stoutly\nswore that no one else had been in the front room during the\nafternoon. So determined was their denial that the inspector was\nstaggered, and had almost come to believe that Mrs. St. Clair had\nbeen deluded when, with a cry, she sprang at a small deal box\nwhich lay upon the table and tore the lid from it. Out there fell\na cascade of children's bricks. It was the toy which he had\npromised to bring home.\n\n\"This discovery, and the evident confusion which the cripple\nshowed, made the inspector realise that the matter was serious.\nThe rooms were carefully examined, and results all pointed to an\nabominable crime. The front room was plainly furnished as a\nsitting-room and led into a small bedroom, which looked out upon\nthe back of one of the wharves. Between the wharf and the bedroom\nwindow is a narrow strip, which is dry at low tide but is covered\nat high tide with at least four and a half feet of water. The\nbedroom window was a broad one and opened from below. On\nexamination traces of blood were to be seen upon the windowsill,\nand several scattered drops were visible upon the wooden floor of\nthe bedroom. Thrust away behind a curtain in the front room were\nall the clothes of Mr. Neville St. Clair, with the exception of\nhis coat. His boots, his socks, his hat, and his watch--all were\nthere. There were no signs of violence upon any of these\ngarments, and there were no other traces of Mr. Neville St.\nClair. Out of the window he must apparently have gone for no\nother exit could be discovered, and the ominous bloodstains upon\nthe sill gave little promise that he could save himself by\nswimming, for the tide was at its very highest at the moment of\nthe tragedy.\n\n\"And now as to the villains who seemed to be immediately\nimplicated in the matter. The Lascar was known to be a man of the\nvilest antecedents, but as, by Mrs. St. Clair's story, he was\nknown to have been at the foot of the stair within a very few\nseconds of her husband's appearance at the window, he could\nhardly have been more than an accessory to the crime. His defence\nwas one of absolute ignorance, and he protested that he had no\nknowledge as to the doings of Hugh Boone, his lodger, and that he\ncould not account in any way for the presence of the missing\ngentleman's clothes.\n\n\"So much for the Lascar manager. Now for the sinister cripple who\nlives upon the second floor of the opium den, and who was\ncertainly the last human being whose eyes rested upon Neville St.\nClair. His name is Hugh Boone, and his hideous face is one which\nis familiar to every man who goes much to the City. He is a\nprofessional beggar, though in order to avoid the police\nregulations he pretends to a small trade in wax vestas. Some\nlittle distance down Threadneedle Street, upon the left-hand\nside, there is, as you may have remarked, a small angle in the\nwall. Here it is that this creature takes his daily seat,\ncross-legged with his tiny stock of matches on his lap, and as he\nis a piteous spectacle a small rain of charity descends into the\ngreasy leather cap which lies upon the pavement beside him. I\nhave watched the fellow more than once before ever I thought of\nmaking his professional acquaintance, and I have been surprised\nat the harvest which he has reaped in a short time. His\nappearance, you see, is so remarkable that no one can pass him\nwithout observing him. A shock of orange hair, a pale face\ndisfigured by a horrible scar, which, by its contraction, has\nturned up the outer edge of his upper lip, a bulldog chin, and a\npair of very penetrating dark eyes, which present a singular\ncontrast to the colour of his hair, all mark him out from amid\nthe common crowd of mendicants and so, too, does his wit, for he\nis ever ready with a reply to any piece of chaff which may be\nthrown at him by the passers-by. This is the man whom we now\nlearn to have been the lodger at the opium den, and to have been\nthe last man to see the gentleman of whom we are in quest.\"\n\n\"But a cripple!\" said I. \"What could he have done single-handed\nagainst a man in the prime of life?\"\n\n\"He is a cripple in the sense that he walks with a limp; but in\nother respects he appears to be a powerful and well-nurtured man.\nSurely your medical experience would tell you, Watson, that\nweakness in one limb is often compensated for by exceptional\nstrength in the others.\"\n\n\"Pray continue your narrative.\"\n\n\"Mrs. St. Clair had fainted at the sight of the blood upon the\nwindow, and she was escorted home in a cab by the police, as her\npresence could be of no help to them in their investigations.\nInspector Barton, who had charge of the case, made a very careful\nexamination of the premises, but without finding anything which\nthrew any light upon the matter. One mistake had been made in not\narresting Boone instantly, as he was allowed some few minutes\nduring which he might have communicated with his friend the\nLascar, but this fault was soon remedied, and he was seized and\nsearched, without anything being found which could incriminate\nhim. There were, it is true, some blood-stains upon his right\nshirt-sleeve, but he pointed to his ring-finger, which had been\ncut near the nail, and explained that the bleeding came from\nthere, adding that he had been to the window not long before, and\nthat the stains which had been observed there came doubtless from\nthe same source. He denied strenuously having ever seen Mr.\nNeville St. Clair and swore that the presence of the clothes in\nhis room was as much a mystery to him as to the police. As to\nMrs. St. Clair's assertion that she had actually seen her husband\nat the window, he declared that she must have been either mad or\ndreaming. He was removed, loudly protesting, to the\npolice-station, while the inspector remained upon the premises in\nthe hope that the ebbing tide might afford some fresh clue.\n\n\"And it did, though they hardly found upon the mud-bank what they\nhad feared to find. It was Neville St. Clair's coat, and not\nNeville St. Clair, which lay uncovered as the tide receded. And\nwhat do you think they found in the pockets?\"\n\n\"I cannot imagine.\"\n\n\"No, I don't think you would guess. Every pocket stuffed with\npennies and half-pennies--421 pennies and 270 half-pennies. It\nwas no wonder that it had not been swept away by the tide. But a\nhuman body is a different matter. There is a fierce eddy between\nthe wharf and the house. It seemed likely enough that the\nweighted coat had remained when the stripped body had been sucked\naway into the river.\"\n\n\"But I understand that all the other clothes were found in the\nroom. Would the body be dressed in a coat alone?\"\n\n\"No, sir, but the facts might be met speciously enough. Suppose\nthat this man Boone had thrust Neville St. Clair through the\nwindow, there is no human eye which could have seen the deed.\nWhat would he do then? It would of course instantly strike him\nthat he must get rid of the tell-tale garments. He would seize\nthe coat, then, and be in the act of throwing it out, when it\nwould occur to him that it would swim and not sink. He has little\ntime, for he has heard the scuffle downstairs when the wife tried\nto force her way up, and perhaps he has already heard from his\nLascar confederate that the police are hurrying up the street.\nThere is not an instant to be lost. He rushes to some secret\nhoard, where he has accumulated the fruits of his beggary, and he\nstuffs all the coins upon which he can lay his hands into the\npockets to make sure of the coat's sinking. He throws it out, and\nwould have done the same with the other garments had not he heard\nthe rush of steps below, and only just had time to close the\nwindow when the police appeared.\"\n\n\"It certainly sounds feasible.\"\n\n\"Well, we will take it as a working hypothesis for want of a\nbetter. Boone, as I have told you, was arrested and taken to the\nstation, but it could not be shown that there had ever before\nbeen anything against him. He had for years been known as a\nprofessional beggar, but his life appeared to have been a very\nquiet and innocent one. There the matter stands at present, and\nthe questions which have to be solved--what Neville St. Clair was\ndoing in the opium den, what happened to him when there, where is\nhe now, and what Hugh Boone had to do with his disappearance--are\nall as far from a solution as ever. I confess that I cannot\nrecall any case within my experience which looked at the first\nglance so simple and yet which presented such difficulties.\"\n\nWhile Sherlock Holmes had been detailing this singular series of\nevents, we had been whirling through the outskirts of the great\ntown until the last straggling houses had been left behind, and\nwe rattled along with a country hedge upon either side of us.\nJust as he finished, however, we drove through two scattered\nvillages, where a few lights still glimmered in the windows.\n\n\"We are on the outskirts of Lee,\" said my companion. \"We have\ntouched on three English counties in our short drive, starting in\nMiddlesex, passing over an angle of Surrey, and ending in Kent.\nSee that light among the trees? That is The Cedars, and beside\nthat lamp sits a woman whose anxious ears have already, I have\nlittle doubt, caught the clink of our horse's feet.\"\n\n\"But why are you not conducting the case from Baker Street?\" I\nasked.\n\n\"Because there are many inquiries which must be made out here.\nMrs. St. Clair has most kindly put two rooms at my disposal, and\nyou may rest assured that she will have nothing but a welcome for\nmy friend and colleague. I hate to meet her, Watson, when I have\nno news of her husband. Here we are. Whoa, there, whoa!\"\n\nWe had pulled up in front of a large villa which stood within its\nown grounds. A stable-boy had run out to the horse's head, and\nspringing down, I followed Holmes up the small, winding\ngravel-drive which led to the house. As we approached, the door\nflew open, and a little blonde woman stood in the opening, clad\nin some sort of light mousseline de soie, with a touch of fluffy\npink chiffon at her neck and wrists. She stood with her figure\noutlined against the flood of light, one hand upon the door, one\nhalf-raised in her eagerness, her body slightly bent, her head\nand face protruded, with eager eyes and parted lips, a standing\nquestion.\n\n\"Well?\" she cried, \"well?\" And then, seeing that there were two\nof us, she gave a cry of hope which sank into a groan as she saw\nthat my companion shook his head and shrugged his shoulders.\n\n\"No good news?\"\n\n\"None.\"\n\n\"No bad?\"\n\n\"No.\"\n\n\"Thank God for that. But come in. You must be weary, for you have\nhad a long day.\"\n\n\"This is my friend, Dr. Watson. He has been of most vital use to\nme in several of my cases, and a lucky chance has made it\npossible for me to bring him out and associate him with this\ninvestigation.\"\n\n\"I am delighted to see you,\" said she, pressing my hand warmly.\n\"You will, I am sure, forgive anything that may be wanting in our\narrangements, when you consider the blow which has come so\nsuddenly upon us.\"\n\n\"My dear madam,\" said I, \"I am an old campaigner, and if I were\nnot I can very well see that no apology is needed. If I can be of\nany assistance, either to you or to my friend here, I shall be\nindeed happy.\"\n\n\"Now, Mr. Sherlock Holmes,\" said the lady as we entered a\nwell-lit dining-room, upon the table of which a cold supper had\nbeen laid out, \"I should very much like to ask you one or two\nplain questions, to which I beg that you will give a plain\nanswer.\"\n\n\"Certainly, madam.\"\n\n\"Do not trouble about my feelings. I am not hysterical, nor given\nto fainting. I simply wish to hear your real, real opinion.\"\n\n\"Upon what point?\"\n\n\"In your heart of hearts, do you think that Neville is alive?\"\n\nSherlock Holmes seemed to be embarrassed by the question.\n\"Frankly, now!\" she repeated, standing upon the rug and looking\nkeenly down at him as he leaned back in a basket-chair.\n\n\"Frankly, then, madam, I do not.\"\n\n\"You think that he is dead?\"\n\n\"I do.\"\n\n\"Murdered?\"\n\n\"I don't say that. Perhaps.\"\n\n\"And on what day did he meet his death?\"\n\n\"On Monday.\"\n\n\"Then perhaps, Mr. Holmes, you will be good enough to explain how\nit is that I have received a letter from him to-day.\"\n\nSherlock Holmes sprang out of his chair as if he had been\ngalvanised.\n\n\"What!\" he roared.\n\n\"Yes, to-day.\" She stood smiling, holding up a little slip of\npaper in the air.\n\n\"May I see it?\"\n\n\"Certainly.\"\n\nHe snatched it from her in his eagerness, and smoothing it out\nupon the table he drew over the lamp and examined it intently. I\nhad left my chair and was gazing at it over his shoulder. The\nenvelope was a very coarse one and was stamped with the Gravesend\npostmark and with the date of that very day, or rather of the day\nbefore, for it was considerably after midnight.\n\n\"Coarse writing,\" murmured Holmes. \"Surely this is not your\nhusband's writing, madam.\"\n\n\"No, but the enclosure is.\"\n\n\"I perceive also that whoever addressed the envelope had to go\nand inquire as to the address.\"\n\n\"How can you tell that?\"\n\n\"The name, you see, is in perfectly black ink, which has dried\nitself. The rest is of the greyish colour, which shows that\nblotting-paper has been used. If it had been written straight\noff, and then blotted, none would be of a deep black shade. This\nman has written the name, and there has then been a pause before\nhe wrote the address, which can only mean that he was not\nfamiliar with it. It is, of course, a trifle, but there is\nnothing so important as trifles. Let us now see the letter. Ha!\nthere has been an enclosure here!\"\n\n\"Yes, there was a ring. His signet-ring.\"\n\n\"And you are sure that this is your husband's hand?\"\n\n\"One of his hands.\"\n\n\"One?\"\n\n\"His hand when he wrote hurriedly. It is very unlike his usual\nwriting, and yet I know it well.\"\n\n\"'Dearest do not be frightened. All will come well. There is a\nhuge error which it may take some little time to rectify.\nWait in patience.--NEVILLE.' Written in pencil upon the fly-leaf\nof a book, octavo size, no water-mark. Hum! Posted to-day in\nGravesend by a man with a dirty thumb. Ha! And the flap has been\ngummed, if I am not very much in error, by a person who had been\nchewing tobacco. And you have no doubt that it is your husband's\nhand, madam?\"\n\n\"None. Neville wrote those words.\"\n\n\"And they were posted to-day at Gravesend. Well, Mrs. St. Clair,\nthe clouds lighten, though I should not venture to say that the\ndanger is over.\"\n\n\"But he must be alive, Mr. Holmes.\"\n\n\"Unless this is a clever forgery to put us on the wrong scent.\nThe ring, after all, proves nothing. It may have been taken from\nhim.\"\n\n\"No, no; it is, it is his very own writing!\"\n\n\"Very well. It may, however, have been written on Monday and only\nposted to-day.\"\n\n\"That is possible.\"\n\n\"If so, much may have happened between.\"\n\n\"Oh, you must not discourage me, Mr. Holmes. I know that all is\nwell with him. There is so keen a sympathy between us that I\nshould know if evil came upon him. On the very day that I saw him\nlast he cut himself in the bedroom, and yet I in the dining-room\nrushed upstairs instantly with the utmost certainty that\nsomething had happened. Do you think that I would respond to such\na trifle and yet be ignorant of his death?\"\n\n\"I have seen too much not to know that the impression of a woman\nmay be more valuable than the conclusion of an analytical\nreasoner. And in this letter you certainly have a very strong\npiece of evidence to corroborate your view. But if your husband\nis alive and able to write letters, why should he remain away\nfrom you?\"\n\n\"I cannot imagine. It is unthinkable.\"\n\n\"And on Monday he made no remarks before leaving you?\"\n\n\"No.\"\n\n\"And you were surprised to see him in Swandam Lane?\"\n\n\"Very much so.\"\n\n\"Was the window open?\"\n\n\"Yes.\"\n\n\"Then he might have called to you?\"\n\n\"He might.\"\n\n\"He only, as I understand, gave an inarticulate cry?\"\n\n\"Yes.\"\n\n\"A call for help, you thought?\"\n\n\"Yes. He waved his hands.\"\n\n\"But it might have been a cry of surprise. Astonishment at the\nunexpected sight of you might cause him to throw up his hands?\"\n\n\"It is possible.\"\n\n\"And you thought he was pulled back?\"\n\n\"He disappeared so suddenly.\"\n\n\"He might have leaped back. You did not see anyone else in the\nroom?\"\n\n\"No, but this horrible man confessed to having been there, and\nthe Lascar was at the foot of the stairs.\"\n\n\"Quite so. Your husband, as far as you could see, had his\nordinary clothes on?\"\n\n\"But without his collar or tie. I distinctly saw his bare\nthroat.\"\n\n\"Had he ever spoken of Swandam Lane?\"\n\n\"Never.\"\n\n\"Had he ever showed any signs of having taken opium?\"\n\n\"Never.\"\n\n\"Thank you, Mrs. St. Clair. Those are the principal points about\nwhich I wished to be absolutely clear. We shall now have a little\nsupper and then retire, for we may have a very busy day\nto-morrow.\"\n\nA large and comfortable double-bedded room had been placed at our\ndisposal, and I was quickly between the sheets, for I was weary\nafter my night of adventure. Sherlock Holmes was a man, however,\nwho, when he had an unsolved problem upon his mind, would go for\ndays, and even for a week, without rest, turning it over,\nrearranging his facts, looking at it from every point of view\nuntil he had either fathomed it or convinced himself that his\ndata were insufficient. It was soon evident to me that he was now\npreparing for an all-night sitting. He took off his coat and\nwaistcoat, put on a large blue dressing-gown, and then wandered\nabout the room collecting pillows from his bed and cushions from\nthe sofa and armchairs. With these he constructed a sort of\nEastern divan, upon which he perched himself cross-legged, with\nan ounce of shag tobacco and a box of matches laid out in front\nof him. In the dim light of the lamp I saw him sitting there, an\nold briar pipe between his lips, his eyes fixed vacantly upon the\ncorner of the ceiling, the blue smoke curling up from him,\nsilent, motionless, with the light shining upon his strong-set\naquiline features. So he sat as I dropped off to sleep, and so he\nsat when a sudden ejaculation caused me to wake up, and I found\nthe summer sun shining into the apartment. The pipe was still\nbetween his lips, the smoke still curled upward, and the room was\nfull of a dense tobacco haze, but nothing remained of the heap of\nshag which I had seen upon the previous night.\n\n\"Awake, Watson?\" he asked.\n\n\"Yes.\"\n\n\"Game for a morning drive?\"\n\n\"Certainly.\"\n\n\"Then dress. No one is stirring yet, but I know where the\nstable-boy sleeps, and we shall soon have the trap out.\" He\nchuckled to himself as he spoke, his eyes twinkled, and he seemed\na different man to the sombre thinker of the previous night.\n\nAs I dressed I glanced at my watch. It was no wonder that no one\nwas stirring. It was twenty-five minutes past four. I had hardly\nfinished when Holmes returned with the news that the boy was\nputting in the horse.\n\n\"I want to test a little theory of mine,\" said he, pulling on his\nboots. \"I think, Watson, that you are now standing in the\npresence of one of the most absolute fools in Europe. I deserve\nto be kicked from here to Charing Cross. But I think I have the\nkey of the affair now.\"\n\n\"And where is it?\" I asked, smiling.\n\n\"In the bathroom,\" he answered. \"Oh, yes, I am not joking,\" he\ncontinued, seeing my look of incredulity. \"I have just been\nthere, and I have taken it out, and I have got it in this\nGladstone bag. Come on, my boy, and we shall see whether it will\nnot fit the lock.\"\n\nWe made our way downstairs as quietly as possible, and out into\nthe bright morning sunshine. In the road stood our horse and\ntrap, with the half-clad stable-boy waiting at the head. We both\nsprang in, and away we dashed down the London Road. A few country\ncarts were stirring, bearing in vegetables to the metropolis, but\nthe lines of villas on either side were as silent and lifeless as\nsome city in a dream.\n\n\"It has been in some points a singular case,\" said Holmes,\nflicking the horse on into a gallop. \"I confess that I have been\nas blind as a mole, but it is better to learn wisdom late than\nnever to learn it at all.\"\n\nIn town the earliest risers were just beginning to look sleepily\nfrom their windows as we drove through the streets of the Surrey\nside. Passing down the Waterloo Bridge Road we crossed over the\nriver, and dashing up Wellington Street wheeled sharply to the\nright and found ourselves in Bow Street. Sherlock Holmes was well\nknown to the force, and the two constables at the door saluted\nhim. One of them held the horse's head while the other led us in.\n\n\"Who is on duty?\" asked Holmes.\n\n\"Inspector Bradstreet, sir.\"\n\n\"Ah, Bradstreet, how are you?\" A tall, stout official had come\ndown the stone-flagged passage, in a peaked cap and frogged\njacket. \"I wish to have a quiet word with you, Bradstreet.\"\n\"Certainly, Mr. Holmes. Step into my room here.\" It was a small,\noffice-like room, with a huge ledger upon the table, and a\ntelephone projecting from the wall. The inspector sat down at his\ndesk.\n\n\"What can I do for you, Mr. Holmes?\"\n\n\"I called about that beggarman, Boone--the one who was charged\nwith being concerned in the disappearance of Mr. Neville St.\nClair, of Lee.\"\n\n\"Yes. He was brought up and remanded for further inquiries.\"\n\n\"So I heard. You have him here?\"\n\n\"In the cells.\"\n\n\"Is he quiet?\"\n\n\"Oh, he gives no trouble. But he is a dirty scoundrel.\"\n\n\"Dirty?\"\n\n\"Yes, it is all we can do to make him wash his hands, and his\nface is as black as a tinker's. Well, when once his case has been\nsettled, he will have a regular prison bath; and I think, if you\nsaw him, you would agree with me that he needed it.\"\n\n\"I should like to see him very much.\"\n\n\"Would you? That is easily done. Come this way. You can leave\nyour bag.\"\n\n\"No, I think that I'll take it.\"\n\n\"Very good. Come this way, if you please.\" He led us down a\npassage, opened a barred door, passed down a winding stair, and\nbrought us to a whitewashed corridor with a line of doors on each\nside.\n\n\"The third on the right is his,\" said the inspector. \"Here it\nis!\" He quietly shot back a panel in the upper part of the door\nand glanced through.\n\n\"He is asleep,\" said he. \"You can see him very well.\"\n\nWe both put our eyes to the grating. The prisoner lay with his\nface towards us, in a very deep sleep, breathing slowly and\nheavily. He was a middle-sized man, coarsely clad as became his\ncalling, with a coloured shirt protruding through the rent in his\ntattered coat. He was, as the inspector had said, extremely\ndirty, but the grime which covered his face could not conceal its\nrepulsive ugliness. A broad wheal from an old scar ran right\nacross it from eye to chin, and by its contraction had turned up\none side of the upper lip, so that three teeth were exposed in a\nperpetual snarl. A shock of very bright red hair grew low over\nhis eyes and forehead.\n\n\"He's a beauty, isn't he?\" said the inspector.\n\n\"He certainly needs a wash,\" remarked Holmes. \"I had an idea that\nhe might, and I took the liberty of bringing the tools with me.\"\nHe opened the Gladstone bag as he spoke, and took out, to my\nastonishment, a very large bath-sponge.\n\n\"He! he! You are a funny one,\" chuckled the inspector.\n\n\"Now, if you will have the great goodness to open that door very\nquietly, we will soon make him cut a much more respectable\nfigure.\"\n\n\"Well, I don't know why not,\" said the inspector. \"He doesn't\nlook a credit to the Bow Street cells, does he?\" He slipped his\nkey into the lock, and we all very quietly entered the cell. The\nsleeper half turned, and then settled down once more into a deep\nslumber. Holmes stooped to the water-jug, moistened his sponge,\nand then rubbed it twice vigorously across and down the\nprisoner's face.\n\n\"Let me introduce you,\" he shouted, \"to Mr. Neville St. Clair, of\nLee, in the county of Kent.\"\n\nNever in my life have I seen such a sight. The man's face peeled\noff under the sponge like the bark from a tree. Gone was the\ncoarse brown tint! Gone, too, was the horrid scar which had\nseamed it across, and the twisted lip which had given the\nrepulsive sneer to the face! A twitch brought away the tangled\nred hair, and there, sitting up in his bed, was a pale,\nsad-faced, refined-looking man, black-haired and smooth-skinned,\nrubbing his eyes and staring about him with sleepy bewilderment.\nThen suddenly realising the exposure, he broke into a scream and\nthrew himself down with his face to the pillow.\n\n\"Great heavens!\" cried the inspector, \"it is, indeed, the missing\nman. I know him from the photograph.\"\n\nThe prisoner turned with the reckless air of a man who abandons\nhimself to his destiny. \"Be it so,\" said he. \"And pray what am I\ncharged with?\"\n\n\"With making away with Mr. Neville St.-- Oh, come, you can't be\ncharged with that unless they make a case of attempted suicide of\nit,\" said the inspector with a grin. \"Well, I have been\ntwenty-seven years in the force, but this really takes the cake.\"\n\n\"If I am Mr. Neville St. Clair, then it is obvious that no crime\nhas been committed, and that, therefore, I am illegally\ndetained.\"\n\n\"No crime, but a very great error has been committed,\" said\nHolmes. \"You would have done better to have trusted your wife.\"\n\n\"It was not the wife; it was the children,\" groaned the prisoner.\n\"God help me, I would not have them ashamed of their father. My\nGod! What an exposure! What can I do?\"\n\nSherlock Holmes sat down beside him on the couch and patted him\nkindly on the shoulder.\n\n\"If you leave it to a court of law to clear the matter up,\" said\nhe, \"of course you can hardly avoid publicity. On the other hand,\nif you convince the police authorities that there is no possible\ncase against you, I do not know that there is any reason that the\ndetails should find their way into the papers. Inspector\nBradstreet would, I am sure, make notes upon anything which you\nmight tell us and submit it to the proper authorities. The case\nwould then never go into court at all.\"\n\n\"God bless you!\" cried the prisoner passionately. \"I would have\nendured imprisonment, ay, even execution, rather than have left\nmy miserable secret as a family blot to my children.\n\n\"You are the first who have ever heard my story. My father was a\nschoolmaster in Chesterfield, where I received an excellent\neducation. I travelled in my youth, took to the stage, and\nfinally became a reporter on an evening paper in London. One day\nmy editor wished to have a series of articles upon begging in the\nmetropolis, and I volunteered to supply them. There was the point\nfrom which all my adventures started. It was only by trying\nbegging as an amateur that I could get the facts upon which to\nbase my articles. When an actor I had, of course, learned all the\nsecrets of making up, and had been famous in the green-room for\nmy skill. I took advantage now of my attainments. I painted my\nface, and to make myself as pitiable as possible I made a good\nscar and fixed one side of my lip in a twist by the aid of a\nsmall slip of flesh-coloured plaster. Then with a red head of\nhair, and an appropriate dress, I took my station in the business\npart of the city, ostensibly as a match-seller but really as a\nbeggar. For seven hours I plied my trade, and when I returned\nhome in the evening I found to my surprise that I had received no\nless than 26s. 4d.\n\n\"I wrote my articles and thought little more of the matter until,\nsome time later, I backed a bill for a friend and had a writ\nserved upon me for 25 pounds. I was at my wit's end where to get\nthe money, but a sudden idea came to me. I begged a fortnight's\ngrace from the creditor, asked for a holiday from my employers,\nand spent the time in begging in the City under my disguise. In\nten days I had the money and had paid the debt.\n\n\"Well, you can imagine how hard it was to settle down to arduous\nwork at 2 pounds a week when I knew that I could earn as much in\na day by smearing my face with a little paint, laying my cap on\nthe ground, and sitting still. It was a long fight between my\npride and the money, but the dollars won at last, and I threw up\nreporting and sat day after day in the corner which I had first\nchosen, inspiring pity by my ghastly face and filling my pockets\nwith coppers. Only one man knew my secret. He was the keeper of a\nlow den in which I used to lodge in Swandam Lane, where I could\nevery morning emerge as a squalid beggar and in the evenings\ntransform myself into a well-dressed man about town. This fellow,\na Lascar, was well paid by me for his rooms, so that I knew that\nmy secret was safe in his possession.\n\n\"Well, very soon I found that I was saving considerable sums of\nmoney. I do not mean that any beggar in the streets of London\ncould earn 700 pounds a year--which is less than my average\ntakings--but I had exceptional advantages in my power of making\nup, and also in a facility of repartee, which improved by\npractice and made me quite a recognised character in the City.\nAll day a stream of pennies, varied by silver, poured in upon me,\nand it was a very bad day in which I failed to take 2 pounds.\n\n\"As I grew richer I grew more ambitious, took a house in the\ncountry, and eventually married, without anyone having a\nsuspicion as to my real occupation. My dear wife knew that I had\nbusiness in the City. She little knew what.\n\n\"Last Monday I had finished for the day and was dressing in my\nroom above the opium den when I looked out of my window and saw,\nto my horror and astonishment, that my wife was standing in the\nstreet, with her eyes fixed full upon me. I gave a cry of\nsurprise, threw up my arms to cover my face, and, rushing to my\nconfidant, the Lascar, entreated him to prevent anyone from\ncoming up to me. I heard her voice downstairs, but I knew that\nshe could not ascend. Swiftly I threw off my clothes, pulled on\nthose of a beggar, and put on my pigments and wig. Even a wife's\neyes could not pierce so complete a disguise. But then it\noccurred to me that there might be a search in the room, and that\nthe clothes might betray me. I threw open the window, reopening\nby my violence a small cut which I had inflicted upon myself in\nthe bedroom that morning. Then I seized my coat, which was\nweighted by the coppers which I had just transferred to it from\nthe leather bag in which I carried my takings. I hurled it out of\nthe window, and it disappeared into the Thames. The other clothes\nwould have followed, but at that moment there was a rush of\nconstables up the stair, and a few minutes after I found, rather,\nI confess, to my relief, that instead of being identified as Mr.\nNeville St. Clair, I was arrested as his murderer.\n\n\"I do not know that there is anything else for me to explain. I\nwas determined to preserve my disguise as long as possible, and\nhence my preference for a dirty face. Knowing that my wife would\nbe terribly anxious, I slipped off my ring and confided it to the\nLascar at a moment when no constable was watching me, together\nwith a hurried scrawl, telling her that she had no cause to\nfear.\"\n\n\"That note only reached her yesterday,\" said Holmes.\n\n\"Good God! What a week she must have spent!\"\n\n\"The police have watched this Lascar,\" said Inspector Bradstreet,\n\"and I can quite understand that he might find it difficult to\npost a letter unobserved. Probably he handed it to some sailor\ncustomer of his, who forgot all about it for some days.\"\n\n\"That was it,\" said Holmes, nodding approvingly; \"I have no doubt\nof it. But have you never been prosecuted for begging?\"\n\n\"Many times; but what was a fine to me?\"\n\n\"It must stop here, however,\" said Bradstreet. \"If the police are\nto hush this thing up, there must be no more of Hugh Boone.\"\n\n\"I have sworn it by the most solemn oaths which a man can take.\"\n\n\"In that case I think that it is probable that no further steps\nmay be taken. But if you are found again, then all must come out.\nI am sure, Mr. Holmes, that we are very much indebted to you for\nhaving cleared the matter up. I wish I knew how you reach your\nresults.\"\n\n\"I reached this one,\" said my friend, \"by sitting upon five\npillows and consuming an ounce of shag. I think, Watson, that if\nwe drive to Baker Street we shall just be in time for breakfast.\"\n\n\n\nVII. THE ADVENTURE OF THE BLUE CARBUNCLE\n\nI had called upon my friend Sherlock Holmes upon the second\nmorning after Christmas, with the intention of wishing him the\ncompliments of the season. He was lounging upon the sofa in a\npurple dressing-gown, a pipe-rack within his reach upon the\nright, and a pile of crumpled morning papers, evidently newly\nstudied, near at hand. Beside the couch was a wooden chair, and\non the angle of the back hung a very seedy and disreputable\nhard-felt hat, much the worse for wear, and cracked in several\nplaces. A lens and a forceps lying upon the seat of the chair\nsuggested that the hat had been suspended in this manner for the\npurpose of examination.\n\n\"You are engaged,\" said I; \"perhaps I interrupt you.\"\n\n\"Not at all. I am glad to have a friend with whom I can discuss\nmy results. The matter is a perfectly trivial one\"--he jerked his\nthumb in the direction of the old hat--\"but there are points in\nconnection with it which are not entirely devoid of interest and\neven of instruction.\"\n\nI seated myself in his armchair and warmed my hands before his\ncrackling fire, for a sharp frost had set in, and the windows\nwere thick with the ice crystals. \"I suppose,\" I remarked, \"that,\nhomely as it looks, this thing has some deadly story linked on to\nit--that it is the clue which will guide you in the solution of\nsome mystery and the punishment of some crime.\"\n\n\"No, no. No crime,\" said Sherlock Holmes, laughing. \"Only one of\nthose whimsical little incidents which will happen when you have\nfour million human beings all jostling each other within the\nspace of a few square miles. Amid the action and reaction of so\ndense a swarm of humanity, every possible combination of events\nmay be expected to take place, and many a little problem will be\npresented which may be striking and bizarre without being\ncriminal. We have already had experience of such.\"\n\n\"So much so,\" I remarked, \"that of the last six cases which I\nhave added to my notes, three have been entirely free of any\nlegal crime.\"\n\n\"Precisely. You allude to my attempt to recover the Irene Adler\npapers, to the singular case of Miss Mary Sutherland, and to the\nadventure of the man with the twisted lip. Well, I have no doubt\nthat this small matter will fall into the same innocent category.\nYou know Peterson, the commissionaire?\"\n\n\"Yes.\"\n\n\"It is to him that this trophy belongs.\"\n\n\"It is his hat.\"\n\n\"No, no, he found it. Its owner is unknown. I beg that you will\nlook upon it not as a battered billycock but as an intellectual\nproblem. And, first, as to how it came here. It arrived upon\nChristmas morning, in company with a good fat goose, which is, I\nhave no doubt, roasting at this moment in front of Peterson's\nfire. The facts are these: about four o'clock on Christmas\nmorning, Peterson, who, as you know, is a very honest fellow, was\nreturning from some small jollification and was making his way\nhomeward down Tottenham Court Road. In front of him he saw, in\nthe gaslight, a tallish man, walking with a slight stagger, and\ncarrying a white goose slung over his shoulder. As he reached the\ncorner of Goodge Street, a row broke out between this stranger\nand a little knot of roughs. One of the latter knocked off the\nman's hat, on which he raised his stick to defend himself and,\nswinging it over his head, smashed the shop window behind him.\nPeterson had rushed forward to protect the stranger from his\nassailants; but the man, shocked at having broken the window, and\nseeing an official-looking person in uniform rushing towards him,\ndropped his goose, took to his heels, and vanished amid the\nlabyrinth of small streets which lie at the back of Tottenham\nCourt Road. The roughs had also fled at the appearance of\nPeterson, so that he was left in possession of the field of\nbattle, and also of the spoils of victory in the shape of this\nbattered hat and a most unimpeachable Christmas goose.\"\n\n\"Which surely he restored to their owner?\"\n\n\"My dear fellow, there lies the problem. It is true that 'For\nMrs. Henry Baker' was printed upon a small card which was tied to\nthe bird's left leg, and it is also true that the initials 'H.\nB.' are legible upon the lining of this hat, but as there are\nsome thousands of Bakers, and some hundreds of Henry Bakers in\nthis city of ours, it is not easy to restore lost property to any\none of them.\"\n\n\"What, then, did Peterson do?\"\n\n\"He brought round both hat and goose to me on Christmas morning,\nknowing that even the smallest problems are of interest to me.\nThe goose we retained until this morning, when there were signs\nthat, in spite of the slight frost, it would be well that it\nshould be eaten without unnecessary delay. Its finder has carried\nit off, therefore, to fulfil the ultimate destiny of a goose,\nwhile I continue to retain the hat of the unknown gentleman who\nlost his Christmas dinner.\"\n\n\"Did he not advertise?\"\n\n\"No.\"\n\n\"Then, what clue could you have as to his identity?\"\n\n\"Only as much as we can deduce.\"\n\n\"From his hat?\"\n\n\"Precisely.\"\n\n\"But you are joking. What can you gather from this old battered\nfelt?\"\n\n\"Here is my lens. You know my methods. What can you gather\nyourself as to the individuality of the man who has worn this\narticle?\"\n\nI took the tattered object in my hands and turned it over rather\nruefully. It was a very ordinary black hat of the usual round\nshape, hard and much the worse for wear. The lining had been of\nred silk, but was a good deal discoloured. There was no maker's\nname; but, as Holmes had remarked, the initials \"H. B.\" were\nscrawled upon one side. It was pierced in the brim for a\nhat-securer, but the elastic was missing. For the rest, it was\ncracked, exceedingly dusty, and spotted in several places,\nalthough there seemed to have been some attempt to hide the\ndiscoloured patches by smearing them with ink.\n\n\"I can see nothing,\" said I, handing it back to my friend.\n\n\"On the contrary, Watson, you can see everything. You fail,\nhowever, to reason from what you see. You are too timid in\ndrawing your inferences.\"\n\n\"Then, pray tell me what it is that you can infer from this hat?\"\n\nHe picked it up and gazed at it in the peculiar introspective\nfashion which was characteristic of him. \"It is perhaps less\nsuggestive than it might have been,\" he remarked, \"and yet there\nare a few inferences which are very distinct, and a few others\nwhich represent at least a strong balance of probability. That\nthe man was highly intellectual is of course obvious upon the\nface of it, and also that he was fairly well-to-do within the\nlast three years, although he has now fallen upon evil days. He\nhad foresight, but has less now than formerly, pointing to a\nmoral retrogression, which, when taken with the decline of his\nfortunes, seems to indicate some evil influence, probably drink,\nat work upon him. This may account also for the obvious fact that\nhis wife has ceased to love him.\"\n\n\"My dear Holmes!\"\n\n\"He has, however, retained some degree of self-respect,\" he\ncontinued, disregarding my remonstrance. \"He is a man who leads a\nsedentary life, goes out little, is out of training entirely, is\nmiddle-aged, has grizzled hair which he has had cut within the\nlast few days, and which he anoints with lime-cream. These are\nthe more patent facts which are to be deduced from his hat. Also,\nby the way, that it is extremely improbable that he has gas laid\non in his house.\"\n\n\"You are certainly joking, Holmes.\"\n\n\"Not in the least. Is it possible that even now, when I give you\nthese results, you are unable to see how they are attained?\"\n\n\"I have no doubt that I am very stupid, but I must confess that I\nam unable to follow you. For example, how did you deduce that\nthis man was intellectual?\"\n\nFor answer Holmes clapped the hat upon his head. It came right\nover the forehead and settled upon the bridge of his nose. \"It is\na question of cubic capacity,\" said he; \"a man with so large a\nbrain must have something in it.\"\n\n\"The decline of his fortunes, then?\"\n\n\"This hat is three years old. These flat brims curled at the edge\ncame in then. It is a hat of the very best quality. Look at the\nband of ribbed silk and the excellent lining. If this man could\nafford to buy so expensive a hat three years ago, and has had no\nhat since, then he has assuredly gone down in the world.\"\n\n\"Well, that is clear enough, certainly. But how about the\nforesight and the moral retrogression?\"\n\nSherlock Holmes laughed. \"Here is the foresight,\" said he putting\nhis finger upon the little disc and loop of the hat-securer.\n\"They are never sold upon hats. If this man ordered one, it is a\nsign of a certain amount of foresight, since he went out of his\nway to take this precaution against the wind. But since we see\nthat he has broken the elastic and has not troubled to replace\nit, it is obvious that he has less foresight now than formerly,\nwhich is a distinct proof of a weakening nature. On the other\nhand, he has endeavoured to conceal some of these stains upon the\nfelt by daubing them with ink, which is a sign that he has not\nentirely lost his self-respect.\"\n\n\"Your reasoning is certainly plausible.\"\n\n\"The further points, that he is middle-aged, that his hair is\ngrizzled, that it has been recently cut, and that he uses\nlime-cream, are all to be gathered from a close examination of the\nlower part of the lining. The lens discloses a large number of\nhair-ends, clean cut by the scissors of the barber. They all\nappear to be adhesive, and there is a distinct odour of\nlime-cream. This dust, you will observe, is not the gritty, grey\ndust of the street but the fluffy brown dust of the house,\nshowing that it has been hung up indoors most of the time, while\nthe marks of moisture upon the inside are proof positive that the\nwearer perspired very freely, and could therefore, hardly be in\nthe best of training.\"\n\n\"But his wife--you said that she had ceased to love him.\"\n\n\"This hat has not been brushed for weeks. When I see you, my dear\nWatson, with a week's accumulation of dust upon your hat, and\nwhen your wife allows you to go out in such a state, I shall fear\nthat you also have been unfortunate enough to lose your wife's\naffection.\"\n\n\"But he might be a bachelor.\"\n\n\"Nay, he was bringing home the goose as a peace-offering to his\nwife. Remember the card upon the bird's leg.\"\n\n\"You have an answer to everything. But how on earth do you deduce\nthat the gas is not laid on in his house?\"\n\n\"One tallow stain, or even two, might come by chance; but when I\nsee no less than five, I think that there can be little doubt\nthat the individual must be brought into frequent contact with\nburning tallow--walks upstairs at night probably with his hat in\none hand and a guttering candle in the other. Anyhow, he never\ngot tallow-stains from a gas-jet. Are you satisfied?\"\n\n\"Well, it is very ingenious,\" said I, laughing; \"but since, as\nyou said just now, there has been no crime committed, and no harm\ndone save the loss of a goose, all this seems to be rather a\nwaste of energy.\"\n\nSherlock Holmes had opened his mouth to reply, when the door flew\nopen, and Peterson, the commissionaire, rushed into the apartment\nwith flushed cheeks and the face of a man who is dazed with\nastonishment.\n\n\"The goose, Mr. Holmes! The goose, sir!\" he gasped.\n\n\"Eh? What of it, then? Has it returned to life and flapped off\nthrough the kitchen window?\" Holmes twisted himself round upon\nthe sofa to get a fairer view of the man's excited face.\n\n\"See here, sir! See what my wife found in its crop!\" He held out\nhis hand and displayed upon the centre of the palm a brilliantly\nscintillating blue stone, rather smaller than a bean in size, but\nof such purity and radiance that it twinkled like an electric\npoint in the dark hollow of his hand.\n\nSherlock Holmes sat up with a whistle. \"By Jove, Peterson!\" said\nhe, \"this is treasure trove indeed. I suppose you know what you\nhave got?\"\n\n\"A diamond, sir? A precious stone. It cuts into glass as though\nit were putty.\"\n\n\"It's more than a precious stone. It is the precious stone.\"\n\n\"Not the Countess of Morcar's blue carbuncle!\" I ejaculated.\n\n\"Precisely so. I ought to know its size and shape, seeing that I\nhave read the advertisement about it in The Times every day\nlately. It is absolutely unique, and its value can only be\nconjectured, but the reward offered of 1000 pounds is certainly\nnot within a twentieth part of the market price.\"\n\n\"A thousand pounds! Great Lord of mercy!\" The commissionaire\nplumped down into a chair and stared from one to the other of us.\n\n\"That is the reward, and I have reason to know that there are\nsentimental considerations in the background which would induce\nthe Countess to part with half her fortune if she could but\nrecover the gem.\"\n\n\"It was lost, if I remember aright, at the Hotel Cosmopolitan,\" I\nremarked.\n\n\"Precisely so, on December 22nd, just five days ago. John Horner,\na plumber, was accused of having abstracted it from the lady's\njewel-case. The evidence against him was so strong that the case\nhas been referred to the Assizes. I have some account of the\nmatter here, I believe.\" He rummaged amid his newspapers,\nglancing over the dates, until at last he smoothed one out,\ndoubled it over, and read the following paragraph:\n\n\"Hotel Cosmopolitan Jewel Robbery. John Horner, 26, plumber, was\nbrought up upon the charge of having upon the 22nd inst.,\nabstracted from the jewel-case of the Countess of Morcar the\nvaluable gem known as the blue carbuncle. James Ryder,\nupper-attendant at the hotel, gave his evidence to the effect\nthat he had shown Horner up to the dressing-room of the Countess\nof Morcar upon the day of the robbery in order that he might\nsolder the second bar of the grate, which was loose. He had\nremained with Horner some little time, but had finally been\ncalled away. On returning, he found that Horner had disappeared,\nthat the bureau had been forced open, and that the small morocco\ncasket in which, as it afterwards transpired, the Countess was\naccustomed to keep her jewel, was lying empty upon the\ndressing-table. Ryder instantly gave the alarm, and Horner was\narrested the same evening; but the stone could not be found\neither upon his person or in his rooms. Catherine Cusack, maid to\nthe Countess, deposed to having heard Ryder's cry of dismay on\ndiscovering the robbery, and to having rushed into the room,\nwhere she found matters as described by the last witness.\nInspector Bradstreet, B division, gave evidence as to the arrest\nof Horner, who struggled frantically, and protested his innocence\nin the strongest terms. Evidence of a previous conviction for\nrobbery having been given against the prisoner, the magistrate\nrefused to deal summarily with the offence, but referred it to\nthe Assizes. Horner, who had shown signs of intense emotion\nduring the proceedings, fainted away at the conclusion and was\ncarried out of court.\"\n\n\"Hum! So much for the police-court,\" said Holmes thoughtfully,\ntossing aside the paper. \"The question for us now to solve is the\nsequence of events leading from a rifled jewel-case at one end to\nthe crop of a goose in Tottenham Court Road at the other. You\nsee, Watson, our little deductions have suddenly assumed a much\nmore important and less innocent aspect. Here is the stone; the\nstone came from the goose, and the goose came from Mr. Henry\nBaker, the gentleman with the bad hat and all the other\ncharacteristics with which I have bored you. So now we must set\nourselves very seriously to finding this gentleman and\nascertaining what part he has played in this little mystery. To\ndo this, we must try the simplest means first, and these lie\nundoubtedly in an advertisement in all the evening papers. If\nthis fail, I shall have recourse to other methods.\"\n\n\"What will you say?\"\n\n\"Give me a pencil and that slip of paper. Now, then: 'Found at\nthe corner of Goodge Street, a goose and a black felt hat. Mr.\nHenry Baker can have the same by applying at 6:30 this evening at\n221B, Baker Street.' That is clear and concise.\"\n\n\"Very. But will he see it?\"\n\n\"Well, he is sure to keep an eye on the papers, since, to a poor\nman, the loss was a heavy one. He was clearly so scared by his\nmischance in breaking the window and by the approach of Peterson\nthat he thought of nothing but flight, but since then he must\nhave bitterly regretted the impulse which caused him to drop his\nbird. Then, again, the introduction of his name will cause him to\nsee it, for everyone who knows him will direct his attention to\nit. Here you are, Peterson, run down to the advertising agency\nand have this put in the evening papers.\"\n\n\"In which, sir?\"\n\n\"Oh, in the Globe, Star, Pall Mall, St. James's, Evening News,\nStandard, Echo, and any others that occur to you.\"\n\n\"Very well, sir. And this stone?\"\n\n\"Ah, yes, I shall keep the stone. Thank you. And, I say,\nPeterson, just buy a goose on your way back and leave it here\nwith me, for we must have one to give to this gentleman in place\nof the one which your family is now devouring.\"\n\nWhen the commissionaire had gone, Holmes took up the stone and\nheld it against the light. \"It's a bonny thing,\" said he. \"Just\nsee how it glints and sparkles. Of course it is a nucleus and\nfocus of crime. Every good stone is. They are the devil's pet\nbaits. In the larger and older jewels every facet may stand for a\nbloody deed. This stone is not yet twenty years old. It was found\nin the banks of the Amoy River in southern China and is remarkable\nin having every characteristic of the carbuncle, save that it is\nblue in shade instead of ruby red. In spite of its youth, it has\nalready a sinister history. There have been two murders, a\nvitriol-throwing, a suicide, and several robberies brought about\nfor the sake of this forty-grain weight of crystallised charcoal.\nWho would think that so pretty a toy would be a purveyor to the\ngallows and the prison? I'll lock it up in my strong box now and\ndrop a line to the Countess to say that we have it.\"\n\n\"Do you think that this man Horner is innocent?\"\n\n\"I cannot tell.\"\n\n\"Well, then, do you imagine that this other one, Henry Baker, had\nanything to do with the matter?\"\n\n\"It is, I think, much more likely that Henry Baker is an\nabsolutely innocent man, who had no idea that the bird which he\nwas carrying was of considerably more value than if it were made\nof solid gold. That, however, I shall determine by a very simple\ntest if we have an answer to our advertisement.\"\n\n\"And you can do nothing until then?\"\n\n\"Nothing.\"\n\n\"In that case I shall continue my professional round. But I shall\ncome back in the evening at the hour you have mentioned, for I\nshould like to see the solution of so tangled a business.\"\n\n\"Very glad to see you. I dine at seven. There is a woodcock, I\nbelieve. By the way, in view of recent occurrences, perhaps I\nought to ask Mrs. Hudson to examine its crop.\"\n\nI had been delayed at a case, and it was a little after half-past\nsix when I found myself in Baker Street once more. As I\napproached the house I saw a tall man in a Scotch bonnet with a\ncoat which was buttoned up to his chin waiting outside in the\nbright semicircle which was thrown from the fanlight. Just as I\narrived the door was opened, and we were shown up together to\nHolmes' room.\n\n\"Mr. Henry Baker, I believe,\" said he, rising from his armchair\nand greeting his visitor with the easy air of geniality which he\ncould so readily assume. \"Pray take this chair by the fire, Mr.\nBaker. It is a cold night, and I observe that your circulation is\nmore adapted for summer than for winter. Ah, Watson, you have\njust come at the right time. Is that your hat, Mr. Baker?\"\n\n\"Yes, sir, that is undoubtedly my hat.\"\n\nHe was a large man with rounded shoulders, a massive head, and a\nbroad, intelligent face, sloping down to a pointed beard of\ngrizzled brown. A touch of red in nose and cheeks, with a slight\ntremor of his extended hand, recalled Holmes' surmise as to his\nhabits. His rusty black frock-coat was buttoned right up in\nfront, with the collar turned up, and his lank wrists protruded\nfrom his sleeves without a sign of cuff or shirt. He spoke in a\nslow staccato fashion, choosing his words with care, and gave the\nimpression generally of a man of learning and letters who had had\nill-usage at the hands of fortune.\n\n\"We have retained these things for some days,\" said Holmes,\n\"because we expected to see an advertisement from you giving your\naddress. I am at a loss to know now why you did not advertise.\"\n\nOur visitor gave a rather shamefaced laugh. \"Shillings have not\nbeen so plentiful with me as they once were,\" he remarked. \"I had\nno doubt that the gang of roughs who assaulted me had carried off\nboth my hat and the bird. I did not care to spend more money in a\nhopeless attempt at recovering them.\"\n\n\"Very naturally. By the way, about the bird, we were compelled to\neat it.\"\n\n\"To eat it!\" Our visitor half rose from his chair in his\nexcitement.\n\n\"Yes, it would have been of no use to anyone had we not done so.\nBut I presume that this other goose upon the sideboard, which is\nabout the same weight and perfectly fresh, will answer your\npurpose equally well?\"\n\n\"Oh, certainly, certainly,\" answered Mr. Baker with a sigh of\nrelief.\n\n\"Of course, we still have the feathers, legs, crop, and so on of\nyour own bird, so if you wish--\"\n\nThe man burst into a hearty laugh. \"They might be useful to me as\nrelics of my adventure,\" said he, \"but beyond that I can hardly\nsee what use the disjecta membra of my late acquaintance are\ngoing to be to me. No, sir, I think that, with your permission, I\nwill confine my attentions to the excellent bird which I perceive\nupon the sideboard.\"\n\nSherlock Holmes glanced sharply across at me with a slight shrug\nof his shoulders.\n\n\"There is your hat, then, and there your bird,\" said he. \"By the\nway, would it bore you to tell me where you got the other one\nfrom? I am somewhat of a fowl fancier, and I have seldom seen a\nbetter grown goose.\"\n\n\"Certainly, sir,\" said Baker, who had risen and tucked his newly\ngained property under his arm. \"There are a few of us who\nfrequent the Alpha Inn, near the Museum--we are to be found in\nthe Museum itself during the day, you understand. This year our\ngood host, Windigate by name, instituted a goose club, by which,\non consideration of some few pence every week, we were each to\nreceive a bird at Christmas. My pence were duly paid, and the\nrest is familiar to you. I am much indebted to you, sir, for a\nScotch bonnet is fitted neither to my years nor my gravity.\" With\na comical pomposity of manner he bowed solemnly to both of us and\nstrode off upon his way.\n\n\"So much for Mr. Henry Baker,\" said Holmes when he had closed the\ndoor behind him. \"It is quite certain that he knows nothing\nwhatever about the matter. Are you hungry, Watson?\"\n\n\"Not particularly.\"\n\n\"Then I suggest that we turn our dinner into a supper and follow\nup this clue while it is still hot.\"\n\n\"By all means.\"\n\nIt was a bitter night, so we drew on our ulsters and wrapped\ncravats about our throats. Outside, the stars were shining coldly\nin a cloudless sky, and the breath of the passers-by blew out\ninto smoke like so many pistol shots. Our footfalls rang out\ncrisply and loudly as we swung through the doctors' quarter,\nWimpole Street, Harley Street, and so through Wigmore Street into\nOxford Street. In a quarter of an hour we were in Bloomsbury at\nthe Alpha Inn, which is a small public-house at the corner of one\nof the streets which runs down into Holborn. Holmes pushed open\nthe door of the private bar and ordered two glasses of beer from\nthe ruddy-faced, white-aproned landlord.\n\n\"Your beer should be excellent if it is as good as your geese,\"\nsaid he.\n\n\"My geese!\" The man seemed surprised.\n\n\"Yes. I was speaking only half an hour ago to Mr. Henry Baker,\nwho was a member of your goose club.\"\n\n\"Ah! yes, I see. But you see, sir, them's not our geese.\"\n\n\"Indeed! Whose, then?\"\n\n\"Well, I got the two dozen from a salesman in Covent Garden.\"\n\n\"Indeed? I know some of them. Which was it?\"\n\n\"Breckinridge is his name.\"\n\n\"Ah! I don't know him. Well, here's your good health landlord,\nand prosperity to your house. Good-night.\"\n\n\"Now for Mr. Breckinridge,\" he continued, buttoning up his coat\nas we came out into the frosty air. \"Remember, Watson that though\nwe have so homely a thing as a goose at one end of this chain, we\nhave at the other a man who will certainly get seven years' penal\nservitude unless we can establish his innocence. It is possible\nthat our inquiry may but confirm his guilt; but, in any case, we\nhave a line of investigation which has been missed by the police,\nand which a singular chance has placed in our hands. Let us\nfollow it out to the bitter end. Faces to the south, then, and\nquick march!\"\n\nWe passed across Holborn, down Endell Street, and so through a\nzigzag of slums to Covent Garden Market. One of the largest\nstalls bore the name of Breckinridge upon it, and the proprietor\na horsey-looking man, with a sharp face and trim side-whiskers was\nhelping a boy to put up the shutters.\n\n\"Good-evening. It's a cold night,\" said Holmes.\n\nThe salesman nodded and shot a questioning glance at my\ncompanion.\n\n\"Sold out of geese, I see,\" continued Holmes, pointing at the\nbare slabs of marble.\n\n\"Let you have five hundred to-morrow morning.\"\n\n\"That's no good.\"\n\n\"Well, there are some on the stall with the gas-flare.\"\n\n\"Ah, but I was recommended to you.\"\n\n\"Who by?\"\n\n\"The landlord of the Alpha.\"\n\n\"Oh, yes; I sent him a couple of dozen.\"\n\n\"Fine birds they were, too. Now where did you get them from?\"\n\nTo my surprise the question provoked a burst of anger from the\nsalesman.\n\n\"Now, then, mister,\" said he, with his head cocked and his arms\nakimbo, \"what are you driving at? Let's have it straight, now.\"\n\n\"It is straight enough. I should like to know who sold you the\ngeese which you supplied to the Alpha.\"\n\n\"Well then, I shan't tell you. So now!\"\n\n\"Oh, it is a matter of no importance; but I don't know why you\nshould be so warm over such a trifle.\"\n\n\"Warm! You'd be as warm, maybe, if you were as pestered as I am.\nWhen I pay good money for a good article there should be an end\nof the business; but it's 'Where are the geese?' and 'Who did you\nsell the geese to?' and 'What will you take for the geese?' One\nwould think they were the only geese in the world, to hear the\nfuss that is made over them.\"\n\n\"Well, I have no connection with any other people who have been\nmaking inquiries,\" said Holmes carelessly. \"If you won't tell us\nthe bet is off, that is all. But I'm always ready to back my\nopinion on a matter of fowls, and I have a fiver on it that the\nbird I ate is country bred.\"\n\n\"Well, then, you've lost your fiver, for it's town bred,\" snapped\nthe salesman.\n\n\"It's nothing of the kind.\"\n\n\"I say it is.\"\n\n\"I don't believe it.\"\n\n\"D'you think you know more about fowls than I, who have handled\nthem ever since I was a nipper? I tell you, all those birds that\nwent to the Alpha were town bred.\"\n\n\"You'll never persuade me to believe that.\"\n\n\"Will you bet, then?\"\n\n\"It's merely taking your money, for I know that I am right. But\nI'll have a sovereign on with you, just to teach you not to be\nobstinate.\"\n\nThe salesman chuckled grimly. \"Bring me the books, Bill,\" said\nhe.\n\nThe small boy brought round a small thin volume and a great\ngreasy-backed one, laying them out together beneath the hanging\nlamp.\n\n\"Now then, Mr. Cocksure,\" said the salesman, \"I thought that I\nwas out of geese, but before I finish you'll find that there is\nstill one left in my shop. You see this little book?\"\n\n\"Well?\"\n\n\"That's the list of the folk from whom I buy. D'you see? Well,\nthen, here on this page are the country folk, and the numbers\nafter their names are where their accounts are in the big ledger.\nNow, then! You see this other page in red ink? Well, that is a\nlist of my town suppliers. Now, look at that third name. Just\nread it out to me.\"\n\n\"Mrs. Oakshott, 117, Brixton Road--249,\" read Holmes.\n\n\"Quite so. Now turn that up in the ledger.\"\n\nHolmes turned to the page indicated. \"Here you are, 'Mrs.\nOakshott, 117, Brixton Road, egg and poultry supplier.'\"\n\n\"Now, then, what's the last entry?\"\n\n\"'December 22nd. Twenty-four geese at 7s. 6d.'\"\n\n\"Quite so. There you are. And underneath?\"\n\n\"'Sold to Mr. Windigate of the Alpha, at 12s.'\"\n\n\"What have you to say now?\"\n\nSherlock Holmes looked deeply chagrined. He drew a sovereign from\nhis pocket and threw it down upon the slab, turning away with the\nair of a man whose disgust is too deep for words. A few yards off\nhe stopped under a lamp-post and laughed in the hearty, noiseless\nfashion which was peculiar to him.\n\n\"When you see a man with whiskers of that cut and the 'Pink 'un'\nprotruding out of his pocket, you can always draw him by a bet,\"\nsaid he. \"I daresay that if I had put 100 pounds down in front of\nhim, that man would not have given me such complete information\nas was drawn from him by the idea that he was doing me on a\nwager. Well, Watson, we are, I fancy, nearing the end of our\nquest, and the only point which remains to be determined is\nwhether we should go on to this Mrs. Oakshott to-night, or\nwhether we should reserve it for to-morrow. It is clear from what\nthat surly fellow said that there are others besides ourselves\nwho are anxious about the matter, and I should--\"\n\nHis remarks were suddenly cut short by a loud hubbub which broke\nout from the stall which we had just left. Turning round we saw a\nlittle rat-faced fellow standing in the centre of the circle of\nyellow light which was thrown by the swinging lamp, while\nBreckinridge, the salesman, framed in the door of his stall, was\nshaking his fists fiercely at the cringing figure.\n\n\"I've had enough of you and your geese,\" he shouted. \"I wish you\nwere all at the devil together. If you come pestering me any more\nwith your silly talk I'll set the dog at you. You bring Mrs.\nOakshott here and I'll answer her, but what have you to do with\nit? Did I buy the geese off you?\"\n\n\"No; but one of them was mine all the same,\" whined the little\nman.\n\n\"Well, then, ask Mrs. Oakshott for it.\"\n\n\"She told me to ask you.\"\n\n\"Well, you can ask the King of Proosia, for all I care. I've had\nenough of it. Get out of this!\" He rushed fiercely forward, and\nthe inquirer flitted away into the darkness.\n\n\"Ha! this may save us a visit to Brixton Road,\" whispered Holmes.\n\"Come with me, and we will see what is to be made of this\nfellow.\" Striding through the scattered knots of people who\nlounged round the flaring stalls, my companion speedily overtook\nthe little man and touched him upon the shoulder. He sprang\nround, and I could see in the gas-light that every vestige of\ncolour had been driven from his face.\n\n\"Who are you, then? What do you want?\" he asked in a quavering\nvoice.\n\n\"You will excuse me,\" said Holmes blandly, \"but I could not help\noverhearing the questions which you put to the salesman just now.\nI think that I could be of assistance to you.\"\n\n\"You? Who are you? How could you know anything of the matter?\"\n\n\"My name is Sherlock Holmes. It is my business to know what other\npeople don't know.\"\n\n\"But you can know nothing of this?\"\n\n\"Excuse me, I know everything of it. You are endeavouring to\ntrace some geese which were sold by Mrs. Oakshott, of Brixton\nRoad, to a salesman named Breckinridge, by him in turn to Mr.\nWindigate, of the Alpha, and by him to his club, of which Mr.\nHenry Baker is a member.\"\n\n\"Oh, sir, you are the very man whom I have longed to meet,\" cried\nthe little fellow with outstretched hands and quivering fingers.\n\"I can hardly explain to you how interested I am in this matter.\"\n\nSherlock Holmes hailed a four-wheeler which was passing. \"In that\ncase we had better discuss it in a cosy room rather than in this\nwind-swept market-place,\" said he. \"But pray tell me, before we\ngo farther, who it is that I have the pleasure of assisting.\"\n\nThe man hesitated for an instant. \"My name is John Robinson,\" he\nanswered with a sidelong glance.\n\n\"No, no; the real name,\" said Holmes sweetly. \"It is always\nawkward doing business with an alias.\"\n\nA flush sprang to the white cheeks of the stranger. \"Well then,\"\nsaid he, \"my real name is James Ryder.\"\n\n\"Precisely so. Head attendant at the Hotel Cosmopolitan. Pray\nstep into the cab, and I shall soon be able to tell you\neverything which you would wish to know.\"\n\nThe little man stood glancing from one to the other of us with\nhalf-frightened, half-hopeful eyes, as one who is not sure\nwhether he is on the verge of a windfall or of a catastrophe.\nThen he stepped into the cab, and in half an hour we were back in\nthe sitting-room at Baker Street. Nothing had been said during\nour drive, but the high, thin breathing of our new companion, and\nthe claspings and unclaspings of his hands, spoke of the nervous\ntension within him.\n\n\"Here we are!\" said Holmes cheerily as we filed into the room.\n\"The fire looks very seasonable in this weather. You look cold,\nMr. Ryder. Pray take the basket-chair. I will just put on my\nslippers before we settle this little matter of yours. Now, then!\nYou want to know what became of those geese?\"\n\n\"Yes, sir.\"\n\n\"Or rather, I fancy, of that goose. It was one bird, I imagine in\nwhich you were interested--white, with a black bar across the\ntail.\"\n\nRyder quivered with emotion. \"Oh, sir,\" he cried, \"can you tell\nme where it went to?\"\n\n\"It came here.\"\n\n\"Here?\"\n\n\"Yes, and a most remarkable bird it proved. I don't wonder that\nyou should take an interest in it. It laid an egg after it was\ndead--the bonniest, brightest little blue egg that ever was seen.\nI have it here in my museum.\"\n\nOur visitor staggered to his feet and clutched the mantelpiece\nwith his right hand. Holmes unlocked his strong-box and held up\nthe blue carbuncle, which shone out like a star, with a cold,\nbrilliant, many-pointed radiance. Ryder stood glaring with a\ndrawn face, uncertain whether to claim or to disown it.\n\n\"The game's up, Ryder,\" said Holmes quietly. \"Hold up, man, or\nyou'll be into the fire! Give him an arm back into his chair,\nWatson. He's not got blood enough to go in for felony with\nimpunity. Give him a dash of brandy. So! Now he looks a little\nmore human. What a shrimp it is, to be sure!\"\n\nFor a moment he had staggered and nearly fallen, but the brandy\nbrought a tinge of colour into his cheeks, and he sat staring\nwith frightened eyes at his accuser.\n\n\"I have almost every link in my hands, and all the proofs which I\ncould possibly need, so there is little which you need tell me.\nStill, that little may as well be cleared up to make the case\ncomplete. You had heard, Ryder, of this blue stone of the\nCountess of Morcar's?\"\n\n\"It was Catherine Cusack who told me of it,\" said he in a\ncrackling voice.\n\n\"I see--her ladyship's waiting-maid. Well, the temptation of\nsudden wealth so easily acquired was too much for you, as it has\nbeen for better men before you; but you were not very scrupulous\nin the means you used. It seems to me, Ryder, that there is the\nmaking of a very pretty villain in you. You knew that this man\nHorner, the plumber, had been concerned in some such matter\nbefore, and that suspicion would rest the more readily upon him.\nWhat did you do, then? You made some small job in my lady's\nroom--you and your confederate Cusack--and you managed that he\nshould be the man sent for. Then, when he had left, you rifled\nthe jewel-case, raised the alarm, and had this unfortunate man\narrested. You then--\"\n\nRyder threw himself down suddenly upon the rug and clutched at my\ncompanion's knees. \"For God's sake, have mercy!\" he shrieked.\n\"Think of my father! Of my mother! It would break their hearts. I\nnever went wrong before! I never will again. I swear it. I'll\nswear it on a Bible. Oh, don't bring it into court! For Christ's\nsake, don't!\"\n\n\"Get back into your chair!\" said Holmes sternly. \"It is very well\nto cringe and crawl now, but you thought little enough of this\npoor Horner in the dock for a crime of which he knew nothing.\"\n\n\"I will fly, Mr. Holmes. I will leave the country, sir. Then the\ncharge against him will break down.\"\n\n\"Hum! We will talk about that. And now let us hear a true account\nof the next act. How came the stone into the goose, and how came\nthe goose into the open market? Tell us the truth, for there lies\nyour only hope of safety.\"\n\nRyder passed his tongue over his parched lips. \"I will tell you\nit just as it happened, sir,\" said he. \"When Horner had been\narrested, it seemed to me that it would be best for me to get\naway with the stone at once, for I did not know at what moment\nthe police might not take it into their heads to search me and my\nroom. There was no place about the hotel where it would be safe.\nI went out, as if on some commission, and I made for my sister's\nhouse. She had married a man named Oakshott, and lived in Brixton\nRoad, where she fattened fowls for the market. All the way there\nevery man I met seemed to me to be a policeman or a detective;\nand, for all that it was a cold night, the sweat was pouring down\nmy face before I came to the Brixton Road. My sister asked me\nwhat was the matter, and why I was so pale; but I told her that I\nhad been upset by the jewel robbery at the hotel. Then I went\ninto the back yard and smoked a pipe and wondered what it would\nbe best to do.\n\n\"I had a friend once called Maudsley, who went to the bad, and\nhas just been serving his time in Pentonville. One day he had met\nme, and fell into talk about the ways of thieves, and how they\ncould get rid of what they stole. I knew that he would be true to\nme, for I knew one or two things about him; so I made up my mind\nto go right on to Kilburn, where he lived, and take him into my\nconfidence. He would show me how to turn the stone into money.\nBut how to get to him in safety? I thought of the agonies I had\ngone through in coming from the hotel. I might at any moment be\nseized and searched, and there would be the stone in my waistcoat\npocket. I was leaning against the wall at the time and looking at\nthe geese which were waddling about round my feet, and suddenly\nan idea came into my head which showed me how I could beat the\nbest detective that ever lived.\n\n\"My sister had told me some weeks before that I might have the\npick of her geese for a Christmas present, and I knew that she\nwas always as good as her word. I would take my goose now, and in\nit I would carry my stone to Kilburn. There was a little shed in\nthe yard, and behind this I drove one of the birds--a fine big\none, white, with a barred tail. I caught it, and prying its bill\nopen, I thrust the stone down its throat as far as my finger\ncould reach. The bird gave a gulp, and I felt the stone pass\nalong its gullet and down into its crop. But the creature flapped\nand struggled, and out came my sister to know what was the\nmatter. As I turned to speak to her the brute broke loose and\nfluttered off among the others.\n\n\"'Whatever were you doing with that bird, Jem?' says she.\n\n\"'Well,' said I, 'you said you'd give me one for Christmas, and I\nwas feeling which was the fattest.'\n\n\"'Oh,' says she, 'we've set yours aside for you--Jem's bird, we\ncall it. It's the big white one over yonder. There's twenty-six\nof them, which makes one for you, and one for us, and two dozen\nfor the market.'\n\n\"'Thank you, Maggie,' says I; 'but if it is all the same to you,\nI'd rather have that one I was handling just now.'\n\n\"'The other is a good three pound heavier,' said she, 'and we\nfattened it expressly for you.'\n\n\"'Never mind. I'll have the other, and I'll take it now,' said I.\n\n\"'Oh, just as you like,' said she, a little huffed. 'Which is it\nyou want, then?'\n\n\"'That white one with the barred tail, right in the middle of the\nflock.'\n\n\"'Oh, very well. Kill it and take it with you.'\n\n\"Well, I did what she said, Mr. Holmes, and I carried the bird\nall the way to Kilburn. I told my pal what I had done, for he was\na man that it was easy to tell a thing like that to. He laughed\nuntil he choked, and we got a knife and opened the goose. My\nheart turned to water, for there was no sign of the stone, and I\nknew that some terrible mistake had occurred. I left the bird,\nrushed back to my sister's, and hurried into the back yard. There\nwas not a bird to be seen there.\n\n\"'Where are they all, Maggie?' I cried.\n\n\"'Gone to the dealer's, Jem.'\n\n\"'Which dealer's?'\n\n\"'Breckinridge, of Covent Garden.'\n\n\"'But was there another with a barred tail?' I asked, 'the same\nas the one I chose?'\n\n\"'Yes, Jem; there were two barred-tailed ones, and I could never\ntell them apart.'\n\n\"Well, then, of course I saw it all, and I ran off as hard as my\nfeet would carry me to this man Breckinridge; but he had sold the\nlot at once, and not one word would he tell me as to where they\nhad gone. You heard him yourselves to-night. Well, he has always\nanswered me like that. My sister thinks that I am going mad.\nSometimes I think that I am myself. And now--and now I am myself\na branded thief, without ever having touched the wealth for which\nI sold my character. God help me! God help me!\" He burst into\nconvulsive sobbing, with his face buried in his hands.\n\nThere was a long silence, broken only by his heavy breathing and\nby the measured tapping of Sherlock Holmes' finger-tips upon the\nedge of the table. Then my friend rose and threw open the door.\n\n\"Get out!\" said he.\n\n\"What, sir! Oh, Heaven bless you!\"\n\n\"No more words. Get out!\"\n\nAnd no more words were needed. There was a rush, a clatter upon\nthe stairs, the bang of a door, and the crisp rattle of running\nfootfalls from the street.\n\n\"After all, Watson,\" said Holmes, reaching up his hand for his\nclay pipe, \"I am not retained by the police to supply their\ndeficiencies. If Horner were in danger it would be another thing;\nbut this fellow will not appear against him, and the case must\ncollapse. I suppose that I am commuting a felony, but it is just\npossible that I am saving a soul. This fellow will not go wrong\nagain; he is too terribly frightened. Send him to gaol now, and\nyou make him a gaol-bird for life. Besides, it is the season of\nforgiveness. Chance has put in our way a most singular and\nwhimsical problem, and its solution is its own reward. If you\nwill have the goodness to touch the bell, Doctor, we will begin\nanother investigation, in which, also a bird will be the chief\nfeature.\"\n\n\n\nVIII. THE ADVENTURE OF THE SPECKLED BAND\n\nOn glancing over my notes of the seventy odd cases in which I\nhave during the last eight years studied the methods of my friend\nSherlock Holmes, I find many tragic, some comic, a large number\nmerely strange, but none commonplace; for, working as he did\nrather for the love of his art than for the acquirement of\nwealth, he refused to associate himself with any investigation\nwhich did not tend towards the unusual, and even the fantastic.\nOf all these varied cases, however, I cannot recall any which\npresented more singular features than that which was associated\nwith the well-known Surrey family of the Roylotts of Stoke Moran.\nThe events in question occurred in the early days of my\nassociation with Holmes, when we were sharing rooms as bachelors\nin Baker Street. It is possible that I might have placed them\nupon record before, but a promise of secrecy was made at the\ntime, from which I have only been freed during the last month by\nthe untimely death of the lady to whom the pledge was given. It\nis perhaps as well that the facts should now come to light, for I\nhave reasons to know that there are widespread rumours as to the\ndeath of Dr. Grimesby Roylott which tend to make the matter even\nmore terrible than the truth.\n\nIt was early in April in the year '83 that I woke one morning to\nfind Sherlock Holmes standing, fully dressed, by the side of my\nbed. He was a late riser, as a rule, and as the clock on the\nmantelpiece showed me that it was only a quarter-past seven, I\nblinked up at him in some surprise, and perhaps just a little\nresentment, for I was myself regular in my habits.\n\n\"Very sorry to knock you up, Watson,\" said he, \"but it's the\ncommon lot this morning. Mrs. Hudson has been knocked up, she\nretorted upon me, and I on you.\"\n\n\"What is it, then--a fire?\"\n\n\"No; a client. It seems that a young lady has arrived in a\nconsiderable state of excitement, who insists upon seeing me. She\nis waiting now in the sitting-room. Now, when young ladies wander\nabout the metropolis at this hour of the morning, and knock\nsleepy people up out of their beds, I presume that it is\nsomething very pressing which they have to communicate. Should it\nprove to be an interesting case, you would, I am sure, wish to\nfollow it from the outset. I thought, at any rate, that I should\ncall you and give you the chance.\"\n\n\"My dear fellow, I would not miss it for anything.\"\n\nI had no keener pleasure than in following Holmes in his\nprofessional investigations, and in admiring the rapid\ndeductions, as swift as intuitions, and yet always founded on a\nlogical basis with which he unravelled the problems which were\nsubmitted to him. I rapidly threw on my clothes and was ready in\na few minutes to accompany my friend down to the sitting-room. A\nlady dressed in black and heavily veiled, who had been sitting in\nthe window, rose as we entered.\n\n\"Good-morning, madam,\" said Holmes cheerily. \"My name is Sherlock\nHolmes. This is my intimate friend and associate, Dr. Watson,\nbefore whom you can speak as freely as before myself. Ha! I am\nglad to see that Mrs. Hudson has had the good sense to light the\nfire. Pray draw up to it, and I shall order you a cup of hot\ncoffee, for I observe that you are shivering.\"\n\n\"It is not cold which makes me shiver,\" said the woman in a low\nvoice, changing her seat as requested.\n\n\"What, then?\"\n\n\"It is fear, Mr. Holmes. It is terror.\" She raised her veil as\nshe spoke, and we could see that she was indeed in a pitiable\nstate of agitation, her face all drawn and grey, with restless\nfrightened eyes, like those of some hunted animal. Her features\nand figure were those of a woman of thirty, but her hair was shot\nwith premature grey, and her expression was weary and haggard.\nSherlock Holmes ran her over with one of his quick,\nall-comprehensive glances.\n\n\"You must not fear,\" said he soothingly, bending forward and\npatting her forearm. \"We shall soon set matters right, I have no\ndoubt. You have come in by train this morning, I see.\"\n\n\"You know me, then?\"\n\n\"No, but I observe the second half of a return ticket in the palm\nof your left glove. You must have started early, and yet you had\na good drive in a dog-cart, along heavy roads, before you reached\nthe station.\"\n\nThe lady gave a violent start and stared in bewilderment at my\ncompanion.\n\n\"There is no mystery, my dear madam,\" said he, smiling. \"The left\narm of your jacket is spattered with mud in no less than seven\nplaces. The marks are perfectly fresh. There is no vehicle save a\ndog-cart which throws up mud in that way, and then only when you\nsit on the left-hand side of the driver.\"\n\n\"Whatever your reasons may be, you are perfectly correct,\" said\nshe. \"I started from home before six, reached Leatherhead at\ntwenty past, and came in by the first train to Waterloo. Sir, I\ncan stand this strain no longer; I shall go mad if it continues.\nI have no one to turn to--none, save only one, who cares for me,\nand he, poor fellow, can be of little aid. I have heard of you,\nMr. Holmes; I have heard of you from Mrs. Farintosh, whom you\nhelped in the hour of her sore need. It was from her that I had\nyour address. Oh, sir, do you not think that you could help me,\ntoo, and at least throw a little light through the dense darkness\nwhich surrounds me? At present it is out of my power to reward\nyou for your services, but in a month or six weeks I shall be\nmarried, with the control of my own income, and then at least you\nshall not find me ungrateful.\"\n\nHolmes turned to his desk and, unlocking it, drew out a small\ncase-book, which he consulted.\n\n\"Farintosh,\" said he. \"Ah yes, I recall the case; it was\nconcerned with an opal tiara. I think it was before your time,\nWatson. I can only say, madam, that I shall be happy to devote\nthe same care to your case as I did to that of your friend. As to\nreward, my profession is its own reward; but you are at liberty\nto defray whatever expenses I may be put to, at the time which\nsuits you best. And now I beg that you will lay before us\neverything that may help us in forming an opinion upon the\nmatter.\"\n\n\"Alas!\" replied our visitor, \"the very horror of my situation\nlies in the fact that my fears are so vague, and my suspicions\ndepend so entirely upon small points, which might seem trivial to\nanother, that even he to whom of all others I have a right to\nlook for help and advice looks upon all that I tell him about it\nas the fancies of a nervous woman. He does not say so, but I can\nread it from his soothing answers and averted eyes. But I have\nheard, Mr. Holmes, that you can see deeply into the manifold\nwickedness of the human heart. You may advise me how to walk amid\nthe dangers which encompass me.\"\n\n\"I am all attention, madam.\"\n\n\"My name is Helen Stoner, and I am living with my stepfather, who\nis the last survivor of one of the oldest Saxon families in\nEngland, the Roylotts of Stoke Moran, on the western border of\nSurrey.\"\n\nHolmes nodded his head. \"The name is familiar to me,\" said he.\n\n\"The family was at one time among the richest in England, and the\nestates extended over the borders into Berkshire in the north,\nand Hampshire in the west. In the last century, however, four\nsuccessive heirs were of a dissolute and wasteful disposition,\nand the family ruin was eventually completed by a gambler in the\ndays of the Regency. Nothing was left save a few acres of ground,\nand the two-hundred-year-old house, which is itself crushed under\na heavy mortgage. The last squire dragged out his existence\nthere, living the horrible life of an aristocratic pauper; but\nhis only son, my stepfather, seeing that he must adapt himself to\nthe new conditions, obtained an advance from a relative, which\nenabled him to take a medical degree and went out to Calcutta,\nwhere, by his professional skill and his force of character, he\nestablished a large practice. In a fit of anger, however, caused\nby some robberies which had been perpetrated in the house, he\nbeat his native butler to death and narrowly escaped a capital\nsentence. As it was, he suffered a long term of imprisonment and\nafterwards returned to England a morose and disappointed man.\n\n\"When Dr. Roylott was in India he married my mother, Mrs. Stoner,\nthe young widow of Major-General Stoner, of the Bengal Artillery.\nMy sister Julia and I were twins, and we were only two years old\nat the time of my mother's re-marriage. She had a considerable\nsum of money--not less than 1000 pounds a year--and this she\nbequeathed to Dr. Roylott entirely while we resided with him,\nwith a provision that a certain annual sum should be allowed to\neach of us in the event of our marriage. Shortly after our return\nto England my mother died--she was killed eight years ago in a\nrailway accident near Crewe. Dr. Roylott then abandoned his\nattempts to establish himself in practice in London and took us\nto live with him in the old ancestral house at Stoke Moran. The\nmoney which my mother had left was enough for all our wants, and\nthere seemed to be no obstacle to our happiness.\n\n\"But a terrible change came over our stepfather about this time.\nInstead of making friends and exchanging visits with our\nneighbours, who had at first been overjoyed to see a Roylott of\nStoke Moran back in the old family seat, he shut himself up in\nhis house and seldom came out save to indulge in ferocious\nquarrels with whoever might cross his path. Violence of temper\napproaching to mania has been hereditary in the men of the\nfamily, and in my stepfather's case it had, I believe, been\nintensified by his long residence in the tropics. A series of\ndisgraceful brawls took place, two of which ended in the\npolice-court, until at last he became the terror of the village,\nand the folks would fly at his approach, for he is a man of\nimmense strength, and absolutely uncontrollable in his anger.\n\n\"Last week he hurled the local blacksmith over a parapet into a\nstream, and it was only by paying over all the money which I\ncould gather together that I was able to avert another public\nexposure. He had no friends at all save the wandering gipsies,\nand he would give these vagabonds leave to encamp upon the few\nacres of bramble-covered land which represent the family estate,\nand would accept in return the hospitality of their tents,\nwandering away with them sometimes for weeks on end. He has a\npassion also for Indian animals, which are sent over to him by a\ncorrespondent, and he has at this moment a cheetah and a baboon,\nwhich wander freely over his grounds and are feared by the\nvillagers almost as much as their master.\n\n\"You can imagine from what I say that my poor sister Julia and I\nhad no great pleasure in our lives. No servant would stay with\nus, and for a long time we did all the work of the house. She was\nbut thirty at the time of her death, and yet her hair had already\nbegun to whiten, even as mine has.\"\n\n\"Your sister is dead, then?\"\n\n\"She died just two years ago, and it is of her death that I wish\nto speak to you. You can understand that, living the life which I\nhave described, we were little likely to see anyone of our own\nage and position. We had, however, an aunt, my mother's maiden\nsister, Miss Honoria Westphail, who lives near Harrow, and we\nwere occasionally allowed to pay short visits at this lady's\nhouse. Julia went there at Christmas two years ago, and met there\na half-pay major of marines, to whom she became engaged. My\nstepfather learned of the engagement when my sister returned and\noffered no objection to the marriage; but within a fortnight of\nthe day which had been fixed for the wedding, the terrible event\noccurred which has deprived me of my only companion.\"\n\nSherlock Holmes had been leaning back in his chair with his eyes\nclosed and his head sunk in a cushion, but he half opened his\nlids now and glanced across at his visitor.\n\n\"Pray be precise as to details,\" said he.\n\n\"It is easy for me to be so, for every event of that dreadful\ntime is seared into my memory. The manor-house is, as I have\nalready said, very old, and only one wing is now inhabited. The\nbedrooms in this wing are on the ground floor, the sitting-rooms\nbeing in the central block of the buildings. Of these bedrooms\nthe first is Dr. Roylott's, the second my sister's, and the third\nmy own. There is no communication between them, but they all open\nout into the same corridor. Do I make myself plain?\"\n\n\"Perfectly so.\"\n\n\"The windows of the three rooms open out upon the lawn. That\nfatal night Dr. Roylott had gone to his room early, though we\nknew that he had not retired to rest, for my sister was troubled\nby the smell of the strong Indian cigars which it was his custom\nto smoke. She left her room, therefore, and came into mine, where\nshe sat for some time, chatting about her approaching wedding. At\neleven o'clock she rose to leave me, but she paused at the door\nand looked back.\n\n\"'Tell me, Helen,' said she, 'have you ever heard anyone whistle\nin the dead of the night?'\n\n\"'Never,' said I.\n\n\"'I suppose that you could not possibly whistle, yourself, in\nyour sleep?'\n\n\"'Certainly not. But why?'\n\n\"'Because during the last few nights I have always, about three\nin the morning, heard a low, clear whistle. I am a light sleeper,\nand it has awakened me. I cannot tell where it came from--perhaps\nfrom the next room, perhaps from the lawn. I thought that I would\njust ask you whether you had heard it.'\n\n\"'No, I have not. It must be those wretched gipsies in the\nplantation.'\n\n\"'Very likely. And yet if it were on the lawn, I wonder that you\ndid not hear it also.'\n\n\"'Ah, but I sleep more heavily than you.'\n\n\"'Well, it is of no great consequence, at any rate.' She smiled\nback at me, closed my door, and a few moments later I heard her\nkey turn in the lock.\"\n\n\"Indeed,\" said Holmes. \"Was it your custom always to lock\nyourselves in at night?\"\n\n\"Always.\"\n\n\"And why?\"\n\n\"I think that I mentioned to you that the doctor kept a cheetah\nand a baboon. We had no feeling of security unless our doors were\nlocked.\"\n\n\"Quite so. Pray proceed with your statement.\"\n\n\"I could not sleep that night. A vague feeling of impending\nmisfortune impressed me. My sister and I, you will recollect,\nwere twins, and you know how subtle are the links which bind two\nsouls which are so closely allied. It was a wild night. The wind\nwas howling outside, and the rain was beating and splashing\nagainst the windows. Suddenly, amid all the hubbub of the gale,\nthere burst forth the wild scream of a terrified woman. I knew\nthat it was my sister's voice. I sprang from my bed, wrapped a\nshawl round me, and rushed into the corridor. As I opened my door\nI seemed to hear a low whistle, such as my sister described, and\na few moments later a clanging sound, as if a mass of metal had\nfallen. As I ran down the passage, my sister's door was unlocked,\nand revolved slowly upon its hinges. I stared at it\nhorror-stricken, not knowing what was about to issue from it. By\nthe light of the corridor-lamp I saw my sister appear at the\nopening, her face blanched with terror, her hands groping for\nhelp, her whole figure swaying to and fro like that of a\ndrunkard. I ran to her and threw my arms round her, but at that\nmoment her knees seemed to give way and she fell to the ground.\nShe writhed as one who is in terrible pain, and her limbs were\ndreadfully convulsed. At first I thought that she had not\nrecognised me, but as I bent over her she suddenly shrieked out\nin a voice which I shall never forget, 'Oh, my God! Helen! It was\nthe band! The speckled band!' There was something else which she\nwould fain have said, and she stabbed with her finger into the\nair in the direction of the doctor's room, but a fresh convulsion\nseized her and choked her words. I rushed out, calling loudly for\nmy stepfather, and I met him hastening from his room in his\ndressing-gown. When he reached my sister's side she was\nunconscious, and though he poured brandy down her throat and sent\nfor medical aid from the village, all efforts were in vain, for\nshe slowly sank and died without having recovered her\nconsciousness. Such was the dreadful end of my beloved sister.\"\n\n\"One moment,\" said Holmes, \"are you sure about this whistle and\nmetallic sound? Could you swear to it?\"\n\n\"That was what the county coroner asked me at the inquiry. It is\nmy strong impression that I heard it, and yet, among the crash of\nthe gale and the creaking of an old house, I may possibly have\nbeen deceived.\"\n\n\"Was your sister dressed?\"\n\n\"No, she was in her night-dress. In her right hand was found the\ncharred stump of a match, and in her left a match-box.\"\n\n\"Showing that she had struck a light and looked about her when\nthe alarm took place. That is important. And what conclusions did\nthe coroner come to?\"\n\n\"He investigated the case with great care, for Dr. Roylott's\nconduct had long been notorious in the county, but he was unable\nto find any satisfactory cause of death. My evidence showed that\nthe door had been fastened upon the inner side, and the windows\nwere blocked by old-fashioned shutters with broad iron bars,\nwhich were secured every night. The walls were carefully sounded,\nand were shown to be quite solid all round, and the flooring was\nalso thoroughly examined, with the same result. The chimney is\nwide, but is barred up by four large staples. It is certain,\ntherefore, that my sister was quite alone when she met her end.\nBesides, there were no marks of any violence upon her.\"\n\n\"How about poison?\"\n\n\"The doctors examined her for it, but without success.\"\n\n\"What do you think that this unfortunate lady died of, then?\"\n\n\"It is my belief that she died of pure fear and nervous shock,\nthough what it was that frightened her I cannot imagine.\"\n\n\"Were there gipsies in the plantation at the time?\"\n\n\"Yes, there are nearly always some there.\"\n\n\"Ah, and what did you gather from this allusion to a band--a\nspeckled band?\"\n\n\"Sometimes I have thought that it was merely the wild talk of\ndelirium, sometimes that it may have referred to some band of\npeople, perhaps to these very gipsies in the plantation. I do not\nknow whether the spotted handkerchiefs which so many of them wear\nover their heads might have suggested the strange adjective which\nshe used.\"\n\nHolmes shook his head like a man who is far from being satisfied.\n\n\"These are very deep waters,\" said he; \"pray go on with your\nnarrative.\"\n\n\"Two years have passed since then, and my life has been until\nlately lonelier than ever. A month ago, however, a dear friend,\nwhom I have known for many years, has done me the honour to ask\nmy hand in marriage. His name is Armitage--Percy Armitage--the\nsecond son of Mr. Armitage, of Crane Water, near Reading. My\nstepfather has offered no opposition to the match, and we are to\nbe married in the course of the spring. Two days ago some repairs\nwere started in the west wing of the building, and my bedroom\nwall has been pierced, so that I have had to move into the\nchamber in which my sister died, and to sleep in the very bed in\nwhich she slept. Imagine, then, my thrill of terror when last\nnight, as I lay awake, thinking over her terrible fate, I\nsuddenly heard in the silence of the night the low whistle which\nhad been the herald of her own death. I sprang up and lit the\nlamp, but nothing was to be seen in the room. I was too shaken to\ngo to bed again, however, so I dressed, and as soon as it was\ndaylight I slipped down, got a dog-cart at the Crown Inn, which\nis opposite, and drove to Leatherhead, from whence I have come on\nthis morning with the one object of seeing you and asking your\nadvice.\"\n\n\"You have done wisely,\" said my friend. \"But have you told me\nall?\"\n\n\"Yes, all.\"\n\n\"Miss Roylott, you have not. You are screening your stepfather.\"\n\n\"Why, what do you mean?\"\n\nFor answer Holmes pushed back the frill of black lace which\nfringed the hand that lay upon our visitor's knee. Five little\nlivid spots, the marks of four fingers and a thumb, were printed\nupon the white wrist.\n\n\"You have been cruelly used,\" said Holmes.\n\nThe lady coloured deeply and covered over her injured wrist. \"He\nis a hard man,\" she said, \"and perhaps he hardly knows his own\nstrength.\"\n\nThere was a long silence, during which Holmes leaned his chin\nupon his hands and stared into the crackling fire.\n\n\"This is a very deep business,\" he said at last. \"There are a\nthousand details which I should desire to know before I decide\nupon our course of action. Yet we have not a moment to lose. If\nwe were to come to Stoke Moran to-day, would it be possible for\nus to see over these rooms without the knowledge of your\nstepfather?\"\n\n\"As it happens, he spoke of coming into town to-day upon some\nmost important business. It is probable that he will be away all\nday, and that there would be nothing to disturb you. We have a\nhousekeeper now, but she is old and foolish, and I could easily\nget her out of the way.\"\n\n\"Excellent. You are not averse to this trip, Watson?\"\n\n\"By no means.\"\n\n\"Then we shall both come. What are you going to do yourself?\"\n\n\"I have one or two things which I would wish to do now that I am\nin town. But I shall return by the twelve o'clock train, so as to\nbe there in time for your coming.\"\n\n\"And you may expect us early in the afternoon. I have myself some\nsmall business matters to attend to. Will you not wait and\nbreakfast?\"\n\n\"No, I must go. My heart is lightened already since I have\nconfided my trouble to you. I shall look forward to seeing you\nagain this afternoon.\" She dropped her thick black veil over her\nface and glided from the room.\n\n\"And what do you think of it all, Watson?\" asked Sherlock Holmes,\nleaning back in his chair.\n\n\"It seems to me to be a most dark and sinister business.\"\n\n\"Dark enough and sinister enough.\"\n\n\"Yet if the lady is correct in saying that the flooring and walls\nare sound, and that the door, window, and chimney are impassable,\nthen her sister must have been undoubtedly alone when she met her\nmysterious end.\"\n\n\"What becomes, then, of these nocturnal whistles, and what of the\nvery peculiar words of the dying woman?\"\n\n\"I cannot think.\"\n\n\"When you combine the ideas of whistles at night, the presence of\na band of gipsies who are on intimate terms with this old doctor,\nthe fact that we have every reason to believe that the doctor has\nan interest in preventing his stepdaughter's marriage, the dying\nallusion to a band, and, finally, the fact that Miss Helen Stoner\nheard a metallic clang, which might have been caused by one of\nthose metal bars that secured the shutters falling back into its\nplace, I think that there is good ground to think that the\nmystery may be cleared along those lines.\"\n\n\"But what, then, did the gipsies do?\"\n\n\"I cannot imagine.\"\n\n\"I see many objections to any such theory.\"\n\n\"And so do I. It is precisely for that reason that we are going\nto Stoke Moran this day. I want to see whether the objections are\nfatal, or if they may be explained away. But what in the name of\nthe devil!\"\n\nThe ejaculation had been drawn from my companion by the fact that\nour door had been suddenly dashed open, and that a huge man had\nframed himself in the aperture. His costume was a peculiar\nmixture of the professional and of the agricultural, having a\nblack top-hat, a long frock-coat, and a pair of high gaiters,\nwith a hunting-crop swinging in his hand. So tall was he that his\nhat actually brushed the cross bar of the doorway, and his\nbreadth seemed to span it across from side to side. A large face,\nseared with a thousand wrinkles, burned yellow with the sun, and\nmarked with every evil passion, was turned from one to the other\nof us, while his deep-set, bile-shot eyes, and his high, thin,\nfleshless nose, gave him somewhat the resemblance to a fierce old\nbird of prey.\n\n\"Which of you is Holmes?\" asked this apparition.\n\n\"My name, sir; but you have the advantage of me,\" said my\ncompanion quietly.\n\n\"I am Dr. Grimesby Roylott, of Stoke Moran.\"\n\n\"Indeed, Doctor,\" said Holmes blandly. \"Pray take a seat.\"\n\n\"I will do nothing of the kind. My stepdaughter has been here. I\nhave traced her. What has she been saying to you?\"\n\n\"It is a little cold for the time of the year,\" said Holmes.\n\n\"What has she been saying to you?\" screamed the old man\nfuriously.\n\n\"But I have heard that the crocuses promise well,\" continued my\ncompanion imperturbably.\n\n\"Ha! You put me off, do you?\" said our new visitor, taking a step\nforward and shaking his hunting-crop. \"I know you, you scoundrel!\nI have heard of you before. You are Holmes, the meddler.\"\n\nMy friend smiled.\n\n\"Holmes, the busybody!\"\n\nHis smile broadened.\n\n\"Holmes, the Scotland Yard Jack-in-office!\"\n\nHolmes chuckled heartily. \"Your conversation is most\nentertaining,\" said he. \"When you go out close the door, for\nthere is a decided draught.\"\n\n\"I will go when I have said my say. Don't you dare to meddle with\nmy affairs. I know that Miss Stoner has been here. I traced her!\nI am a dangerous man to fall foul of! See here.\" He stepped\nswiftly forward, seized the poker, and bent it into a curve with\nhis huge brown hands.\n\n\"See that you keep yourself out of my grip,\" he snarled, and\nhurling the twisted poker into the fireplace he strode out of the\nroom.\n\n\"He seems a very amiable person,\" said Holmes, laughing. \"I am\nnot quite so bulky, but if he had remained I might have shown him\nthat my grip was not much more feeble than his own.\" As he spoke\nhe picked up the steel poker and, with a sudden effort,\nstraightened it out again.\n\n\"Fancy his having the insolence to confound me with the official\ndetective force! This incident gives zest to our investigation,\nhowever, and I only trust that our little friend will not suffer\nfrom her imprudence in allowing this brute to trace her. And now,\nWatson, we shall order breakfast, and afterwards I shall walk\ndown to Doctors' Commons, where I hope to get some data which may\nhelp us in this matter.\"\n\n\nIt was nearly one o'clock when Sherlock Holmes returned from his\nexcursion. He held in his hand a sheet of blue paper, scrawled\nover with notes and figures.\n\n\"I have seen the will of the deceased wife,\" said he. \"To\ndetermine its exact meaning I have been obliged to work out the\npresent prices of the investments with which it is concerned. The\ntotal income, which at the time of the wife's death was little\nshort of 1100 pounds, is now, through the fall in agricultural\nprices, not more than 750 pounds. Each daughter can claim an\nincome of 250 pounds, in case of marriage. It is evident,\ntherefore, that if both girls had married, this beauty would have\nhad a mere pittance, while even one of them would cripple him to\na very serious extent. My morning's work has not been wasted,\nsince it has proved that he has the very strongest motives for\nstanding in the way of anything of the sort. And now, Watson,\nthis is too serious for dawdling, especially as the old man is\naware that we are interesting ourselves in his affairs; so if you\nare ready, we shall call a cab and drive to Waterloo. I should be\nvery much obliged if you would slip your revolver into your\npocket. An Eley's No. 2 is an excellent argument with gentlemen\nwho can twist steel pokers into knots. That and a tooth-brush\nare, I think, all that we need.\"\n\nAt Waterloo we were fortunate in catching a train for\nLeatherhead, where we hired a trap at the station inn and drove\nfor four or five miles through the lovely Surrey lanes. It was a\nperfect day, with a bright sun and a few fleecy clouds in the\nheavens. The trees and wayside hedges were just throwing out\ntheir first green shoots, and the air was full of the pleasant\nsmell of the moist earth. To me at least there was a strange\ncontrast between the sweet promise of the spring and this\nsinister quest upon which we were engaged. My companion sat in\nthe front of the trap, his arms folded, his hat pulled down over\nhis eyes, and his chin sunk upon his breast, buried in the\ndeepest thought. Suddenly, however, he started, tapped me on the\nshoulder, and pointed over the meadows.\n\n\"Look there!\" said he.\n\nA heavily timbered park stretched up in a gentle slope,\nthickening into a grove at the highest point. From amid the\nbranches there jutted out the grey gables and high roof-tree of a\nvery old mansion.\n\n\"Stoke Moran?\" said he.\n\n\"Yes, sir, that be the house of Dr. Grimesby Roylott,\" remarked\nthe driver.\n\n\"There is some building going on there,\" said Holmes; \"that is\nwhere we are going.\"\n\n\"There's the village,\" said the driver, pointing to a cluster of\nroofs some distance to the left; \"but if you want to get to the\nhouse, you'll find it shorter to get over this stile, and so by\nthe foot-path over the fields. There it is, where the lady is\nwalking.\"\n\n\"And the lady, I fancy, is Miss Stoner,\" observed Holmes, shading\nhis eyes. \"Yes, I think we had better do as you suggest.\"\n\nWe got off, paid our fare, and the trap rattled back on its way\nto Leatherhead.\n\n\"I thought it as well,\" said Holmes as we climbed the stile,\n\"that this fellow should think we had come here as architects, or\non some definite business. It may stop his gossip.\nGood-afternoon, Miss Stoner. You see that we have been as good as\nour word.\"\n\nOur client of the morning had hurried forward to meet us with a\nface which spoke her joy. \"I have been waiting so eagerly for\nyou,\" she cried, shaking hands with us warmly. \"All has turned\nout splendidly. Dr. Roylott has gone to town, and it is unlikely\nthat he will be back before evening.\"\n\n\"We have had the pleasure of making the doctor's acquaintance,\"\nsaid Holmes, and in a few words he sketched out what had\noccurred. Miss Stoner turned white to the lips as she listened.\n\n\"Good heavens!\" she cried, \"he has followed me, then.\"\n\n\"So it appears.\"\n\n\"He is so cunning that I never know when I am safe from him. What\nwill he say when he returns?\"\n\n\"He must guard himself, for he may find that there is someone\nmore cunning than himself upon his track. You must lock yourself\nup from him to-night. If he is violent, we shall take you away to\nyour aunt's at Harrow. Now, we must make the best use of our\ntime, so kindly take us at once to the rooms which we are to\nexamine.\"\n\nThe building was of grey, lichen-blotched stone, with a high\ncentral portion and two curving wings, like the claws of a crab,\nthrown out on each side. In one of these wings the windows were\nbroken and blocked with wooden boards, while the roof was partly\ncaved in, a picture of ruin. The central portion was in little\nbetter repair, but the right-hand block was comparatively modern,\nand the blinds in the windows, with the blue smoke curling up\nfrom the chimneys, showed that this was where the family resided.\nSome scaffolding had been erected against the end wall, and the\nstone-work had been broken into, but there were no signs of any\nworkmen at the moment of our visit. Holmes walked slowly up and\ndown the ill-trimmed lawn and examined with deep attention the\noutsides of the windows.\n\n\"This, I take it, belongs to the room in which you used to sleep,\nthe centre one to your sister's, and the one next to the main\nbuilding to Dr. Roylott's chamber?\"\n\n\"Exactly so. But I am now sleeping in the middle one.\"\n\n\"Pending the alterations, as I understand. By the way, there does\nnot seem to be any very pressing need for repairs at that end\nwall.\"\n\n\"There were none. I believe that it was an excuse to move me from\nmy room.\"\n\n\"Ah! that is suggestive. Now, on the other side of this narrow\nwing runs the corridor from which these three rooms open. There\nare windows in it, of course?\"\n\n\"Yes, but very small ones. Too narrow for anyone to pass\nthrough.\"\n\n\"As you both locked your doors at night, your rooms were\nunapproachable from that side. Now, would you have the kindness\nto go into your room and bar your shutters?\"\n\nMiss Stoner did so, and Holmes, after a careful examination\nthrough the open window, endeavoured in every way to force the\nshutter open, but without success. There was no slit through\nwhich a knife could be passed to raise the bar. Then with his\nlens he tested the hinges, but they were of solid iron, built\nfirmly into the massive masonry. \"Hum!\" said he, scratching his\nchin in some perplexity, \"my theory certainly presents some\ndifficulties. No one could pass these shutters if they were\nbolted. Well, we shall see if the inside throws any light upon\nthe matter.\"\n\nA small side door led into the whitewashed corridor from which\nthe three bedrooms opened. Holmes refused to examine the third\nchamber, so we passed at once to the second, that in which Miss\nStoner was now sleeping, and in which her sister had met with her\nfate. It was a homely little room, with a low ceiling and a\ngaping fireplace, after the fashion of old country-houses. A\nbrown chest of drawers stood in one corner, a narrow\nwhite-counterpaned bed in another, and a dressing-table on the\nleft-hand side of the window. These articles, with two small\nwicker-work chairs, made up all the furniture in the room save\nfor a square of Wilton carpet in the centre. The boards round and\nthe panelling of the walls were of brown, worm-eaten oak, so old\nand discoloured that it may have dated from the original building\nof the house. Holmes drew one of the chairs into a corner and sat\nsilent, while his eyes travelled round and round and up and down,\ntaking in every detail of the apartment.\n\n\"Where does that bell communicate with?\" he asked at last\npointing to a thick bell-rope which hung down beside the bed, the\ntassel actually lying upon the pillow.\n\n\"It goes to the housekeeper's room.\"\n\n\"It looks newer than the other things?\"\n\n\"Yes, it was only put there a couple of years ago.\"\n\n\"Your sister asked for it, I suppose?\"\n\n\"No, I never heard of her using it. We used always to get what we\nwanted for ourselves.\"\n\n\"Indeed, it seemed unnecessary to put so nice a bell-pull there.\nYou will excuse me for a few minutes while I satisfy myself as to\nthis floor.\" He threw himself down upon his face with his lens in\nhis hand and crawled swiftly backward and forward, examining\nminutely the cracks between the boards. Then he did the same with\nthe wood-work with which the chamber was panelled. Finally he\nwalked over to the bed and spent some time in staring at it and\nin running his eye up and down the wall. Finally he took the\nbell-rope in his hand and gave it a brisk tug.\n\n\"Why, it's a dummy,\" said he.\n\n\"Won't it ring?\"\n\n\"No, it is not even attached to a wire. This is very interesting.\nYou can see now that it is fastened to a hook just above where\nthe little opening for the ventilator is.\"\n\n\"How very absurd! I never noticed that before.\"\n\n\"Very strange!\" muttered Holmes, pulling at the rope. \"There are\none or two very singular points about this room. For example,\nwhat a fool a builder must be to open a ventilator into another\nroom, when, with the same trouble, he might have communicated\nwith the outside air!\"\n\n\"That is also quite modern,\" said the lady.\n\n\"Done about the same time as the bell-rope?\" remarked Holmes.\n\n\"Yes, there were several little changes carried out about that\ntime.\"\n\n\"They seem to have been of a most interesting character--dummy\nbell-ropes, and ventilators which do not ventilate. With your\npermission, Miss Stoner, we shall now carry our researches into\nthe inner apartment.\"\n\nDr. Grimesby Roylott's chamber was larger than that of his\nstep-daughter, but was as plainly furnished. A camp-bed, a small\nwooden shelf full of books, mostly of a technical character, an\narmchair beside the bed, a plain wooden chair against the wall, a\nround table, and a large iron safe were the principal things\nwhich met the eye. Holmes walked slowly round and examined each\nand all of them with the keenest interest.\n\n\"What's in here?\" he asked, tapping the safe.\n\n\"My stepfather's business papers.\"\n\n\"Oh! you have seen inside, then?\"\n\n\"Only once, some years ago. I remember that it was full of\npapers.\"\n\n\"There isn't a cat in it, for example?\"\n\n\"No. What a strange idea!\"\n\n\"Well, look at this!\" He took up a small saucer of milk which\nstood on the top of it.\n\n\"No; we don't keep a cat. But there is a cheetah and a baboon.\"\n\n\"Ah, yes, of course! Well, a cheetah is just a big cat, and yet a\nsaucer of milk does not go very far in satisfying its wants, I\ndaresay. There is one point which I should wish to determine.\" He\nsquatted down in front of the wooden chair and examined the seat\nof it with the greatest attention.\n\n\"Thank you. That is quite settled,\" said he, rising and putting\nhis lens in his pocket. \"Hullo! Here is something interesting!\"\n\nThe object which had caught his eye was a small dog lash hung on\none corner of the bed. The lash, however, was curled upon itself\nand tied so as to make a loop of whipcord.\n\n\"What do you make of that, Watson?\"\n\n\"It's a common enough lash. But I don't know why it should be\ntied.\"\n\n\"That is not quite so common, is it? Ah, me! it's a wicked world,\nand when a clever man turns his brains to crime it is the worst\nof all. I think that I have seen enough now, Miss Stoner, and\nwith your permission we shall walk out upon the lawn.\"\n\nI had never seen my friend's face so grim or his brow so dark as\nit was when we turned from the scene of this investigation. We\nhad walked several times up and down the lawn, neither Miss\nStoner nor myself liking to break in upon his thoughts before he\nroused himself from his reverie.\n\n\"It is very essential, Miss Stoner,\" said he, \"that you should\nabsolutely follow my advice in every respect.\"\n\n\"I shall most certainly do so.\"\n\n\"The matter is too serious for any hesitation. Your life may\ndepend upon your compliance.\"\n\n\"I assure you that I am in your hands.\"\n\n\"In the first place, both my friend and I must spend the night in\nyour room.\"\n\nBoth Miss Stoner and I gazed at him in astonishment.\n\n\"Yes, it must be so. Let me explain. I believe that that is the\nvillage inn over there?\"\n\n\"Yes, that is the Crown.\"\n\n\"Very good. Your windows would be visible from there?\"\n\n\"Certainly.\"\n\n\"You must confine yourself to your room, on pretence of a\nheadache, when your stepfather comes back. Then when you hear him\nretire for the night, you must open the shutters of your window,\nundo the hasp, put your lamp there as a signal to us, and then\nwithdraw quietly with everything which you are likely to want\ninto the room which you used to occupy. I have no doubt that, in\nspite of the repairs, you could manage there for one night.\"\n\n\"Oh, yes, easily.\"\n\n\"The rest you will leave in our hands.\"\n\n\"But what will you do?\"\n\n\"We shall spend the night in your room, and we shall investigate\nthe cause of this noise which has disturbed you.\"\n\n\"I believe, Mr. Holmes, that you have already made up your mind,\"\nsaid Miss Stoner, laying her hand upon my companion's sleeve.\n\n\"Perhaps I have.\"\n\n\"Then, for pity's sake, tell me what was the cause of my sister's\ndeath.\"\n\n\"I should prefer to have clearer proofs before I speak.\"\n\n\"You can at least tell me whether my own thought is correct, and\nif she died from some sudden fright.\"\n\n\"No, I do not think so. I think that there was probably some more\ntangible cause. And now, Miss Stoner, we must leave you for if\nDr. Roylott returned and saw us our journey would be in vain.\nGood-bye, and be brave, for if you will do what I have told you,\nyou may rest assured that we shall soon drive away the dangers\nthat threaten you.\"\n\nSherlock Holmes and I had no difficulty in engaging a bedroom and\nsitting-room at the Crown Inn. They were on the upper floor, and\nfrom our window we could command a view of the avenue gate, and\nof the inhabited wing of Stoke Moran Manor House. At dusk we saw\nDr. Grimesby Roylott drive past, his huge form looming up beside\nthe little figure of the lad who drove him. The boy had some\nslight difficulty in undoing the heavy iron gates, and we heard\nthe hoarse roar of the doctor's voice and saw the fury with which\nhe shook his clinched fists at him. The trap drove on, and a few\nminutes later we saw a sudden light spring up among the trees as\nthe lamp was lit in one of the sitting-rooms.\n\n\"Do you know, Watson,\" said Holmes as we sat together in the\ngathering darkness, \"I have really some scruples as to taking you\nto-night. There is a distinct element of danger.\"\n\n\"Can I be of assistance?\"\n\n\"Your presence might be invaluable.\"\n\n\"Then I shall certainly come.\"\n\n\"It is very kind of you.\"\n\n\"You speak of danger. You have evidently seen more in these rooms\nthan was visible to me.\"\n\n\"No, but I fancy that I may have deduced a little more. I imagine\nthat you saw all that I did.\"\n\n\"I saw nothing remarkable save the bell-rope, and what purpose\nthat could answer I confess is more than I can imagine.\"\n\n\"You saw the ventilator, too?\"\n\n\"Yes, but I do not think that it is such a very unusual thing to\nhave a small opening between two rooms. It was so small that a\nrat could hardly pass through.\"\n\n\"I knew that we should find a ventilator before ever we came to\nStoke Moran.\"\n\n\"My dear Holmes!\"\n\n\"Oh, yes, I did. You remember in her statement she said that her\nsister could smell Dr. Roylott's cigar. Now, of course that\nsuggested at once that there must be a communication between the\ntwo rooms. It could only be a small one, or it would have been\nremarked upon at the coroner's inquiry. I deduced a ventilator.\"\n\n\"But what harm can there be in that?\"\n\n\"Well, there is at least a curious coincidence of dates. A\nventilator is made, a cord is hung, and a lady who sleeps in the\nbed dies. Does not that strike you?\"\n\n\"I cannot as yet see any connection.\"\n\n\"Did you observe anything very peculiar about that bed?\"\n\n\"No.\"\n\n\"It was clamped to the floor. Did you ever see a bed fastened\nlike that before?\"\n\n\"I cannot say that I have.\"\n\n\"The lady could not move her bed. It must always be in the same\nrelative position to the ventilator and to the rope--or so we may\ncall it, since it was clearly never meant for a bell-pull.\"\n\n\"Holmes,\" I cried, \"I seem to see dimly what you are hinting at.\nWe are only just in time to prevent some subtle and horrible\ncrime.\"\n\n\"Subtle enough and horrible enough. When a doctor does go wrong\nhe is the first of criminals. He has nerve and he has knowledge.\nPalmer and Pritchard were among the heads of their profession.\nThis man strikes even deeper, but I think, Watson, that we shall\nbe able to strike deeper still. But we shall have horrors enough\nbefore the night is over; for goodness' sake let us have a quiet\npipe and turn our minds for a few hours to something more\ncheerful.\"\n\n\nAbout nine o'clock the light among the trees was extinguished,\nand all was dark in the direction of the Manor House. Two hours\npassed slowly away, and then, suddenly, just at the stroke of\neleven, a single bright light shone out right in front of us.\n\n\"That is our signal,\" said Holmes, springing to his feet; \"it\ncomes from the middle window.\"\n\nAs we passed out he exchanged a few words with the landlord,\nexplaining that we were going on a late visit to an acquaintance,\nand that it was possible that we might spend the night there. A\nmoment later we were out on the dark road, a chill wind blowing\nin our faces, and one yellow light twinkling in front of us\nthrough the gloom to guide us on our sombre errand.\n\nThere was little difficulty in entering the grounds, for\nunrepaired breaches gaped in the old park wall. Making our way\namong the trees, we reached the lawn, crossed it, and were about\nto enter through the window when out from a clump of laurel\nbushes there darted what seemed to be a hideous and distorted\nchild, who threw itself upon the grass with writhing limbs and\nthen ran swiftly across the lawn into the darkness.\n\n\"My God!\" I whispered; \"did you see it?\"\n\nHolmes was for the moment as startled as I. His hand closed like\na vice upon my wrist in his agitation. Then he broke into a low\nlaugh and put his lips to my ear.\n\n\"It is a nice household,\" he murmured. \"That is the baboon.\"\n\nI had forgotten the strange pets which the doctor affected. There\nwas a cheetah, too; perhaps we might find it upon our shoulders\nat any moment. I confess that I felt easier in my mind when,\nafter following Holmes' example and slipping off my shoes, I\nfound myself inside the bedroom. My companion noiselessly closed\nthe shutters, moved the lamp onto the table, and cast his eyes\nround the room. All was as we had seen it in the daytime. Then\ncreeping up to me and making a trumpet of his hand, he whispered\ninto my ear again so gently that it was all that I could do to\ndistinguish the words:\n\n\"The least sound would be fatal to our plans.\"\n\nI nodded to show that I had heard.\n\n\"We must sit without light. He would see it through the\nventilator.\"\n\nI nodded again.\n\n\"Do not go asleep; your very life may depend upon it. Have your\npistol ready in case we should need it. I will sit on the side of\nthe bed, and you in that chair.\"\n\nI took out my revolver and laid it on the corner of the table.\n\nHolmes had brought up a long thin cane, and this he placed upon\nthe bed beside him. By it he laid the box of matches and the\nstump of a candle. Then he turned down the lamp, and we were left\nin darkness.\n\nHow shall I ever forget that dreadful vigil? I could not hear a\nsound, not even the drawing of a breath, and yet I knew that my\ncompanion sat open-eyed, within a few feet of me, in the same\nstate of nervous tension in which I was myself. The shutters cut\noff the least ray of light, and we waited in absolute darkness.\n\nFrom outside came the occasional cry of a night-bird, and once at\nour very window a long drawn catlike whine, which told us that\nthe cheetah was indeed at liberty. Far away we could hear the\ndeep tones of the parish clock, which boomed out every quarter of\nan hour. How long they seemed, those quarters! Twelve struck, and\none and two and three, and still we sat waiting silently for\nwhatever might befall.\n\nSuddenly there was the momentary gleam of a light up in the\ndirection of the ventilator, which vanished immediately, but was\nsucceeded by a strong smell of burning oil and heated metal.\nSomeone in the next room had lit a dark-lantern. I heard a gentle\nsound of movement, and then all was silent once more, though the\nsmell grew stronger. For half an hour I sat with straining ears.\nThen suddenly another sound became audible--a very gentle,\nsoothing sound, like that of a small jet of steam escaping\ncontinually from a kettle. The instant that we heard it, Holmes\nsprang from the bed, struck a match, and lashed furiously with\nhis cane at the bell-pull.\n\n\"You see it, Watson?\" he yelled. \"You see it?\"\n\nBut I saw nothing. At the moment when Holmes struck the light I\nheard a low, clear whistle, but the sudden glare flashing into my\nweary eyes made it impossible for me to tell what it was at which\nmy friend lashed so savagely. I could, however, see that his face\nwas deadly pale and filled with horror and loathing. He had\nceased to strike and was gazing up at the ventilator when\nsuddenly there broke from the silence of the night the most\nhorrible cry to which I have ever listened. It swelled up louder\nand louder, a hoarse yell of pain and fear and anger all mingled\nin the one dreadful shriek. They say that away down in the\nvillage, and even in the distant parsonage, that cry raised the\nsleepers from their beds. It struck cold to our hearts, and I\nstood gazing at Holmes, and he at me, until the last echoes of it\nhad died away into the silence from which it rose.\n\n\"What can it mean?\" I gasped.\n\n\"It means that it is all over,\" Holmes answered. \"And perhaps,\nafter all, it is for the best. Take your pistol, and we will\nenter Dr. Roylott's room.\"\n\nWith a grave face he lit the lamp and led the way down the\ncorridor. Twice he struck at the chamber door without any reply\nfrom within. Then he turned the handle and entered, I at his\nheels, with the cocked pistol in my hand.\n\nIt was a singular sight which met our eyes. On the table stood a\ndark-lantern with the shutter half open, throwing a brilliant\nbeam of light upon the iron safe, the door of which was ajar.\nBeside this table, on the wooden chair, sat Dr. Grimesby Roylott\nclad in a long grey dressing-gown, his bare ankles protruding\nbeneath, and his feet thrust into red heelless Turkish slippers.\nAcross his lap lay the short stock with the long lash which we\nhad noticed during the day. His chin was cocked upward and his\neyes were fixed in a dreadful, rigid stare at the corner of the\nceiling. Round his brow he had a peculiar yellow band, with\nbrownish speckles, which seemed to be bound tightly round his\nhead. As we entered he made neither sound nor motion.\n\n\"The band! the speckled band!\" whispered Holmes.\n\nI took a step forward. In an instant his strange headgear began\nto move, and there reared itself from among his hair the squat\ndiamond-shaped head and puffed neck of a loathsome serpent.\n\n\"It is a swamp adder!\" cried Holmes; \"the deadliest snake in\nIndia. He has died within ten seconds of being bitten. Violence\ndoes, in truth, recoil upon the violent, and the schemer falls\ninto the pit which he digs for another. Let us thrust this\ncreature back into its den, and we can then remove Miss Stoner to\nsome place of shelter and let the county police know what has\nhappened.\"\n\nAs he spoke he drew the dog-whip swiftly from the dead man's lap,\nand throwing the noose round the reptile's neck he drew it from\nits horrid perch and, carrying it at arm's length, threw it into\nthe iron safe, which he closed upon it.\n\nSuch are the true facts of the death of Dr. Grimesby Roylott, of\nStoke Moran. It is not necessary that I should prolong a\nnarrative which has already run to too great a length by telling\nhow we broke the sad news to the terrified girl, how we conveyed\nher by the morning train to the care of her good aunt at Harrow,\nof how the slow process of official inquiry came to the\nconclusion that the doctor met his fate while indiscreetly\nplaying with a dangerous pet. The little which I had yet to learn\nof the case was told me by Sherlock Holmes as we travelled back\nnext day.\n\n\"I had,\" said he, \"come to an entirely erroneous conclusion which\nshows, my dear Watson, how dangerous it always is to reason from\ninsufficient data. The presence of the gipsies, and the use of\nthe word 'band,' which was used by the poor girl, no doubt, to\nexplain the appearance which she had caught a hurried glimpse of\nby the light of her match, were sufficient to put me upon an\nentirely wrong scent. I can only claim the merit that I instantly\nreconsidered my position when, however, it became clear to me\nthat whatever danger threatened an occupant of the room could not\ncome either from the window or the door. My attention was\nspeedily drawn, as I have already remarked to you, to this\nventilator, and to the bell-rope which hung down to the bed. The\ndiscovery that this was a dummy, and that the bed was clamped to\nthe floor, instantly gave rise to the suspicion that the rope was\nthere as a bridge for something passing through the hole and\ncoming to the bed. The idea of a snake instantly occurred to me,\nand when I coupled it with my knowledge that the doctor was\nfurnished with a supply of creatures from India, I felt that I\nwas probably on the right track. The idea of using a form of\npoison which could not possibly be discovered by any chemical\ntest was just such a one as would occur to a clever and ruthless\nman who had had an Eastern training. The rapidity with which such\na poison would take effect would also, from his point of view, be\nan advantage. It would be a sharp-eyed coroner, indeed, who could\ndistinguish the two little dark punctures which would show where\nthe poison fangs had done their work. Then I thought of the\nwhistle. Of course he must recall the snake before the morning\nlight revealed it to the victim. He had trained it, probably by\nthe use of the milk which we saw, to return to him when summoned.\nHe would put it through this ventilator at the hour that he\nthought best, with the certainty that it would crawl down the\nrope and land on the bed. It might or might not bite the\noccupant, perhaps she might escape every night for a week, but\nsooner or later she must fall a victim.\n\n\"I had come to these conclusions before ever I had entered his\nroom. An inspection of his chair showed me that he had been in\nthe habit of standing on it, which of course would be necessary\nin order that he should reach the ventilator. The sight of the\nsafe, the saucer of milk, and the loop of whipcord were enough to\nfinally dispel any doubts which may have remained. The metallic\nclang heard by Miss Stoner was obviously caused by her stepfather\nhastily closing the door of his safe upon its terrible occupant.\nHaving once made up my mind, you know the steps which I took in\norder to put the matter to the proof. I heard the creature hiss\nas I have no doubt that you did also, and I instantly lit the\nlight and attacked it.\"\n\n\"With the result of driving it through the ventilator.\"\n\n\"And also with the result of causing it to turn upon its master\nat the other side. Some of the blows of my cane came home and\nroused its snakish temper, so that it flew upon the first person\nit saw. In this way I am no doubt indirectly responsible for Dr.\nGrimesby Roylott's death, and I cannot say that it is likely to\nweigh very heavily upon my conscience.\"\n\n\n\nIX. THE ADVENTURE OF THE ENGINEER'S THUMB\n\nOf all the problems which have been submitted to my friend, Mr.\nSherlock Holmes, for solution during the years of our intimacy,\nthere were only two which I was the means of introducing to his\nnotice--that of Mr. Hatherley's thumb, and that of Colonel\nWarburton's madness. Of these the latter may have afforded a\nfiner field for an acute and original observer, but the other was\nso strange in its inception and so dramatic in its details that\nit may be the more worthy of being placed upon record, even if it\ngave my friend fewer openings for those deductive methods of\nreasoning by which he achieved such remarkable results. The story\nhas, I believe, been told more than once in the newspapers, but,\nlike all such narratives, its effect is much less striking when\nset forth en bloc in a single half-column of print than when the\nfacts slowly evolve before your own eyes, and the mystery clears\ngradually away as each new discovery furnishes a step which leads\non to the complete truth. At the time the circumstances made a\ndeep impression upon me, and the lapse of two years has hardly\nserved to weaken the effect.\n\nIt was in the summer of '89, not long after my marriage, that the\nevents occurred which I am now about to summarise. I had returned\nto civil practice and had finally abandoned Holmes in his Baker\nStreet rooms, although I continually visited him and occasionally\neven persuaded him to forgo his Bohemian habits so far as to come\nand visit us. My practice had steadily increased, and as I\nhappened to live at no very great distance from Paddington\nStation, I got a few patients from among the officials. One of\nthese, whom I had cured of a painful and lingering disease, was\nnever weary of advertising my virtues and of endeavouring to send\nme on every sufferer over whom he might have any influence.\n\nOne morning, at a little before seven o'clock, I was awakened by\nthe maid tapping at the door to announce that two men had come\nfrom Paddington and were waiting in the consulting-room. I\ndressed hurriedly, for I knew by experience that railway cases\nwere seldom trivial, and hastened downstairs. As I descended, my\nold ally, the guard, came out of the room and closed the door\ntightly behind him.\n\n\"I've got him here,\" he whispered, jerking his thumb over his\nshoulder; \"he's all right.\"\n\n\"What is it, then?\" I asked, for his manner suggested that it was\nsome strange creature which he had caged up in my room.\n\n\"It's a new patient,\" he whispered. \"I thought I'd bring him\nround myself; then he couldn't slip away. There he is, all safe\nand sound. I must go now, Doctor; I have my dooties, just the\nsame as you.\" And off he went, this trusty tout, without even\ngiving me time to thank him.\n\nI entered my consulting-room and found a gentleman seated by the\ntable. He was quietly dressed in a suit of heather tweed with a\nsoft cloth cap which he had laid down upon my books. Round one of\nhis hands he had a handkerchief wrapped, which was mottled all\nover with bloodstains. He was young, not more than\nfive-and-twenty, I should say, with a strong, masculine face; but\nhe was exceedingly pale and gave me the impression of a man who\nwas suffering from some strong agitation, which it took all his\nstrength of mind to control.\n\n\"I am sorry to knock you up so early, Doctor,\" said he, \"but I\nhave had a very serious accident during the night. I came in by\ntrain this morning, and on inquiring at Paddington as to where I\nmight find a doctor, a worthy fellow very kindly escorted me\nhere. I gave the maid a card, but I see that she has left it upon\nthe side-table.\"\n\nI took it up and glanced at it. \"Mr. Victor Hatherley, hydraulic\nengineer, 16A, Victoria Street (3rd floor).\" That was the name,\nstyle, and abode of my morning visitor. \"I regret that I have\nkept you waiting,\" said I, sitting down in my library-chair. \"You\nare fresh from a night journey, I understand, which is in itself\na monotonous occupation.\"\n\n\"Oh, my night could not be called monotonous,\" said he, and\nlaughed. He laughed very heartily, with a high, ringing note,\nleaning back in his chair and shaking his sides. All my medical\ninstincts rose up against that laugh.\n\n\"Stop it!\" I cried; \"pull yourself together!\" and I poured out\nsome water from a caraffe.\n\nIt was useless, however. He was off in one of those hysterical\noutbursts which come upon a strong nature when some great crisis\nis over and gone. Presently he came to himself once more, very\nweary and pale-looking.\n\n\"I have been making a fool of myself,\" he gasped.\n\n\"Not at all. Drink this.\" I dashed some brandy into the water,\nand the colour began to come back to his bloodless cheeks.\n\n\"That's better!\" said he. \"And now, Doctor, perhaps you would\nkindly attend to my thumb, or rather to the place where my thumb\nused to be.\"\n\nHe unwound the handkerchief and held out his hand. It gave even\nmy hardened nerves a shudder to look at it. There were four\nprotruding fingers and a horrid red, spongy surface where the\nthumb should have been. It had been hacked or torn right out from\nthe roots.\n\n\"Good heavens!\" I cried, \"this is a terrible injury. It must have\nbled considerably.\"\n\n\"Yes, it did. I fainted when it was done, and I think that I must\nhave been senseless for a long time. When I came to I found that\nit was still bleeding, so I tied one end of my handkerchief very\ntightly round the wrist and braced it up with a twig.\"\n\n\"Excellent! You should have been a surgeon.\"\n\n\"It is a question of hydraulics, you see, and came within my own\nprovince.\"\n\n\"This has been done,\" said I, examining the wound, \"by a very\nheavy and sharp instrument.\"\n\n\"A thing like a cleaver,\" said he.\n\n\"An accident, I presume?\"\n\n\"By no means.\"\n\n\"What! a murderous attack?\"\n\n\"Very murderous indeed.\"\n\n\"You horrify me.\"\n\nI sponged the wound, cleaned it, dressed it, and finally covered\nit over with cotton wadding and carbolised bandages. He lay back\nwithout wincing, though he bit his lip from time to time.\n\n\"How is that?\" I asked when I had finished.\n\n\"Capital! Between your brandy and your bandage, I feel a new man.\nI was very weak, but I have had a good deal to go through.\"\n\n\"Perhaps you had better not speak of the matter. It is evidently\ntrying to your nerves.\"\n\n\"Oh, no, not now. I shall have to tell my tale to the police;\nbut, between ourselves, if it were not for the convincing\nevidence of this wound of mine, I should be surprised if they\nbelieved my statement, for it is a very extraordinary one, and I\nhave not much in the way of proof with which to back it up; and,\neven if they believe me, the clues which I can give them are so\nvague that it is a question whether justice will be done.\"\n\n\"Ha!\" cried I, \"if it is anything in the nature of a problem\nwhich you desire to see solved, I should strongly recommend you\nto come to my friend, Mr. Sherlock Holmes, before you go to the\nofficial police.\"\n\n\"Oh, I have heard of that fellow,\" answered my visitor, \"and I\nshould be very glad if he would take the matter up, though of\ncourse I must use the official police as well. Would you give me\nan introduction to him?\"\n\n\"I'll do better. I'll take you round to him myself.\"\n\n\"I should be immensely obliged to you.\"\n\n\"We'll call a cab and go together. We shall just be in time to\nhave a little breakfast with him. Do you feel equal to it?\"\n\n\"Yes; I shall not feel easy until I have told my story.\"\n\n\"Then my servant will call a cab, and I shall be with you in an\ninstant.\" I rushed upstairs, explained the matter shortly to my\nwife, and in five minutes was inside a hansom, driving with my\nnew acquaintance to Baker Street.\n\nSherlock Holmes was, as I expected, lounging about his\nsitting-room in his dressing-gown, reading the agony column of The\nTimes and smoking his before-breakfast pipe, which was composed\nof all the plugs and dottles left from his smokes of the day\nbefore, all carefully dried and collected on the corner of the\nmantelpiece. He received us in his quietly genial fashion,\nordered fresh rashers and eggs, and joined us in a hearty meal.\nWhen it was concluded he settled our new acquaintance upon the\nsofa, placed a pillow beneath his head, and laid a glass of\nbrandy and water within his reach.\n\n\"It is easy to see that your experience has been no common one,\nMr. Hatherley,\" said he. \"Pray, lie down there and make yourself\nabsolutely at home. Tell us what you can, but stop when you are\ntired and keep up your strength with a little stimulant.\"\n\n\"Thank you,\" said my patient, \"but I have felt another man since\nthe doctor bandaged me, and I think that your breakfast has\ncompleted the cure. I shall take up as little of your valuable\ntime as possible, so I shall start at once upon my peculiar\nexperiences.\"\n\nHolmes sat in his big armchair with the weary, heavy-lidded\nexpression which veiled his keen and eager nature, while I sat\nopposite to him, and we listened in silence to the strange story\nwhich our visitor detailed to us.\n\n\"You must know,\" said he, \"that I am an orphan and a bachelor,\nresiding alone in lodgings in London. By profession I am a\nhydraulic engineer, and I have had considerable experience of my\nwork during the seven years that I was apprenticed to Venner &\nMatheson, the well-known firm, of Greenwich. Two years ago,\nhaving served my time, and having also come into a fair sum of\nmoney through my poor father's death, I determined to start in\nbusiness for myself and took professional chambers in Victoria\nStreet.\n\n\"I suppose that everyone finds his first independent start in\nbusiness a dreary experience. To me it has been exceptionally so.\nDuring two years I have had three consultations and one small\njob, and that is absolutely all that my profession has brought\nme. My gross takings amount to 27 pounds 10s. Every day, from\nnine in the morning until four in the afternoon, I waited in my\nlittle den, until at last my heart began to sink, and I came to\nbelieve that I should never have any practice at all.\n\n\"Yesterday, however, just as I was thinking of leaving the\noffice, my clerk entered to say there was a gentleman waiting who\nwished to see me upon business. He brought up a card, too, with\nthe name of 'Colonel Lysander Stark' engraved upon it. Close at\nhis heels came the colonel himself, a man rather over the middle\nsize, but of an exceeding thinness. I do not think that I have\never seen so thin a man. His whole face sharpened away into nose\nand chin, and the skin of his cheeks was drawn quite tense over\nhis outstanding bones. Yet this emaciation seemed to be his\nnatural habit, and due to no disease, for his eye was bright, his\nstep brisk, and his bearing assured. He was plainly but neatly\ndressed, and his age, I should judge, would be nearer forty than\nthirty.\n\n\"'Mr. Hatherley?' said he, with something of a German accent.\n'You have been recommended to me, Mr. Hatherley, as being a man\nwho is not only proficient in his profession but is also discreet\nand capable of preserving a secret.'\n\n\"I bowed, feeling as flattered as any young man would at such an\naddress. 'May I ask who it was who gave me so good a character?'\n\n\"'Well, perhaps it is better that I should not tell you that just\nat this moment. I have it from the same source that you are both\nan orphan and a bachelor and are residing alone in London.'\n\n\"'That is quite correct,' I answered; 'but you will excuse me if\nI say that I cannot see how all this bears upon my professional\nqualifications. I understand that it was on a professional matter\nthat you wished to speak to me?'\n\n\"'Undoubtedly so. But you will find that all I say is really to\nthe point. I have a professional commission for you, but absolute\nsecrecy is quite essential--absolute secrecy, you understand, and\nof course we may expect that more from a man who is alone than\nfrom one who lives in the bosom of his family.'\n\n\"'If I promise to keep a secret,' said I, 'you may absolutely\ndepend upon my doing so.'\n\n\"He looked very hard at me as I spoke, and it seemed to me that I\nhad never seen so suspicious and questioning an eye.\n\n\"'Do you promise, then?' said he at last.\n\n\"'Yes, I promise.'\n\n\"'Absolute and complete silence before, during, and after? No\nreference to the matter at all, either in word or writing?'\n\n\"'I have already given you my word.'\n\n\"'Very good.' He suddenly sprang up, and darting like lightning\nacross the room he flung open the door. The passage outside was\nempty.\n\n\"'That's all right,' said he, coming back. 'I know that clerks are\nsometimes curious as to their master's affairs. Now we can talk\nin safety.' He drew up his chair very close to mine and began to\nstare at me again with the same questioning and thoughtful look.\n\n\"A feeling of repulsion, and of something akin to fear had begun\nto rise within me at the strange antics of this fleshless man.\nEven my dread of losing a client could not restrain me from\nshowing my impatience.\n\n\"'I beg that you will state your business, sir,' said I; 'my time\nis of value.' Heaven forgive me for that last sentence, but the\nwords came to my lips.\n\n\"'How would fifty guineas for a night's work suit you?' he asked.\n\n\"'Most admirably.'\n\n\"'I say a night's work, but an hour's would be nearer the mark. I\nsimply want your opinion about a hydraulic stamping machine which\nhas got out of gear. If you show us what is wrong we shall soon\nset it right ourselves. What do you think of such a commission as\nthat?'\n\n\"'The work appears to be light and the pay munificent.'\n\n\"'Precisely so. We shall want you to come to-night by the last\ntrain.'\n\n\"'Where to?'\n\n\"'To Eyford, in Berkshire. It is a little place near the borders\nof Oxfordshire, and within seven miles of Reading. There is a\ntrain from Paddington which would bring you there at about\n11:15.'\n\n\"'Very good.'\n\n\"'I shall come down in a carriage to meet you.'\n\n\"'There is a drive, then?'\n\n\"'Yes, our little place is quite out in the country. It is a good\nseven miles from Eyford Station.'\n\n\"'Then we can hardly get there before midnight. I suppose there\nwould be no chance of a train back. I should be compelled to stop\nthe night.'\n\n\"'Yes, we could easily give you a shake-down.'\n\n\"'That is very awkward. Could I not come at some more convenient\nhour?'\n\n\"'We have judged it best that you should come late. It is to\nrecompense you for any inconvenience that we are paying to you, a\nyoung and unknown man, a fee which would buy an opinion from the\nvery heads of your profession. Still, of course, if you would\nlike to draw out of the business, there is plenty of time to do\nso.'\n\n\"I thought of the fifty guineas, and of how very useful they\nwould be to me. 'Not at all,' said I, 'I shall be very happy to\naccommodate myself to your wishes. I should like, however, to\nunderstand a little more clearly what it is that you wish me to\ndo.'\n\n\"'Quite so. It is very natural that the pledge of secrecy which\nwe have exacted from you should have aroused your curiosity. I\nhave no wish to commit you to anything without your having it all\nlaid before you. I suppose that we are absolutely safe from\neavesdroppers?'\n\n\"'Entirely.'\n\n\"'Then the matter stands thus. You are probably aware that\nfuller's-earth is a valuable product, and that it is only found\nin one or two places in England?'\n\n\"'I have heard so.'\n\n\"'Some little time ago I bought a small place--a very small\nplace--within ten miles of Reading. I was fortunate enough to\ndiscover that there was a deposit of fuller's-earth in one of my\nfields. On examining it, however, I found that this deposit was a\ncomparatively small one, and that it formed a link between two\nvery much larger ones upon the right and left--both of them,\nhowever, in the grounds of my neighbours. These good people were\nabsolutely ignorant that their land contained that which was\nquite as valuable as a gold-mine. Naturally, it was to my\ninterest to buy their land before they discovered its true value,\nbut unfortunately I had no capital by which I could do this. I\ntook a few of my friends into the secret, however, and they\nsuggested that we should quietly and secretly work our own little\ndeposit and that in this way we should earn the money which would\nenable us to buy the neighbouring fields. This we have now been\ndoing for some time, and in order to help us in our operations we\nerected a hydraulic press. This press, as I have already\nexplained, has got out of order, and we wish your advice upon the\nsubject. We guard our secret very jealously, however, and if it\nonce became known that we had hydraulic engineers coming to our\nlittle house, it would soon rouse inquiry, and then, if the facts\ncame out, it would be good-bye to any chance of getting these\nfields and carrying out our plans. That is why I have made you\npromise me that you will not tell a human being that you are\ngoing to Eyford to-night. I hope that I make it all plain?'\n\n\"'I quite follow you,' said I. 'The only point which I could not\nquite understand was what use you could make of a hydraulic press\nin excavating fuller's-earth, which, as I understand, is dug out\nlike gravel from a pit.'\n\n\"'Ah!' said he carelessly, 'we have our own process. We compress\nthe earth into bricks, so as to remove them without revealing\nwhat they are. But that is a mere detail. I have taken you fully\ninto my confidence now, Mr. Hatherley, and I have shown you how I\ntrust you.' He rose as he spoke. 'I shall expect you, then, at\nEyford at 11:15.'\n\n\"'I shall certainly be there.'\n\n\"'And not a word to a soul.' He looked at me with a last long,\nquestioning gaze, and then, pressing my hand in a cold, dank\ngrasp, he hurried from the room.\n\n\"Well, when I came to think it all over in cool blood I was very\nmuch astonished, as you may both think, at this sudden commission\nwhich had been intrusted to me. On the one hand, of course, I was\nglad, for the fee was at least tenfold what I should have asked\nhad I set a price upon my own services, and it was possible that\nthis order might lead to other ones. On the other hand, the face\nand manner of my patron had made an unpleasant impression upon\nme, and I could not think that his explanation of the\nfuller's-earth was sufficient to explain the necessity for my\ncoming at midnight, and his extreme anxiety lest I should tell\nanyone of my errand. However, I threw all fears to the winds, ate\na hearty supper, drove to Paddington, and started off, having\nobeyed to the letter the injunction as to holding my tongue.\n\n\"At Reading I had to change not only my carriage but my station.\nHowever, I was in time for the last train to Eyford, and I\nreached the little dim-lit station after eleven o'clock. I was the\nonly passenger who got out there, and there was no one upon the\nplatform save a single sleepy porter with a lantern. As I passed\nout through the wicket gate, however, I found my acquaintance of\nthe morning waiting in the shadow upon the other side. Without a\nword he grasped my arm and hurried me into a carriage, the door\nof which was standing open. He drew up the windows on either\nside, tapped on the wood-work, and away we went as fast as the\nhorse could go.\"\n\n\"One horse?\" interjected Holmes.\n\n\"Yes, only one.\"\n\n\"Did you observe the colour?\"\n\n\"Yes, I saw it by the side-lights when I was stepping into the\ncarriage. It was a chestnut.\"\n\n\"Tired-looking or fresh?\"\n\n\"Oh, fresh and glossy.\"\n\n\"Thank you. I am sorry to have interrupted you. Pray continue\nyour most interesting statement.\"\n\n\"Away we went then, and we drove for at least an hour. Colonel\nLysander Stark had said that it was only seven miles, but I\nshould think, from the rate that we seemed to go, and from the\ntime that we took, that it must have been nearer twelve. He sat\nat my side in silence all the time, and I was aware, more than\nonce when I glanced in his direction, that he was looking at me\nwith great intensity. The country roads seem to be not very good\nin that part of the world, for we lurched and jolted terribly. I\ntried to look out of the windows to see something of where we\nwere, but they were made of frosted glass, and I could make out\nnothing save the occasional bright blur of a passing light. Now\nand then I hazarded some remark to break the monotony of the\njourney, but the colonel answered only in monosyllables, and the\nconversation soon flagged. At last, however, the bumping of the\nroad was exchanged for the crisp smoothness of a gravel-drive,\nand the carriage came to a stand. Colonel Lysander Stark sprang\nout, and, as I followed after him, pulled me swiftly into a porch\nwhich gaped in front of us. We stepped, as it were, right out of\nthe carriage and into the hall, so that I failed to catch the\nmost fleeting glance of the front of the house. The instant that\nI had crossed the threshold the door slammed heavily behind us,\nand I heard faintly the rattle of the wheels as the carriage\ndrove away.\n\n\"It was pitch dark inside the house, and the colonel fumbled\nabout looking for matches and muttering under his breath.\nSuddenly a door opened at the other end of the passage, and a\nlong, golden bar of light shot out in our direction. It grew\nbroader, and a woman appeared with a lamp in her hand, which she\nheld above her head, pushing her face forward and peering at us.\nI could see that she was pretty, and from the gloss with which\nthe light shone upon her dark dress I knew that it was a rich\nmaterial. She spoke a few words in a foreign tongue in a tone as\nthough asking a question, and when my companion answered in a\ngruff monosyllable she gave such a start that the lamp nearly\nfell from her hand. Colonel Stark went up to her, whispered\nsomething in her ear, and then, pushing her back into the room\nfrom whence she had come, he walked towards me again with the\nlamp in his hand.\n\n\"'Perhaps you will have the kindness to wait in this room for a\nfew minutes,' said he, throwing open another door. It was a\nquiet, little, plainly furnished room, with a round table in the\ncentre, on which several German books were scattered. Colonel\nStark laid down the lamp on the top of a harmonium beside the\ndoor. 'I shall not keep you waiting an instant,' said he, and\nvanished into the darkness.\n\n\"I glanced at the books upon the table, and in spite of my\nignorance of German I could see that two of them were treatises\non science, the others being volumes of poetry. Then I walked\nacross to the window, hoping that I might catch some glimpse of\nthe country-side, but an oak shutter, heavily barred, was folded\nacross it. It was a wonderfully silent house. There was an old\nclock ticking loudly somewhere in the passage, but otherwise\neverything was deadly still. A vague feeling of uneasiness began\nto steal over me. Who were these German people, and what were\nthey doing living in this strange, out-of-the-way place? And\nwhere was the place? I was ten miles or so from Eyford, that was\nall I knew, but whether north, south, east, or west I had no\nidea. For that matter, Reading, and possibly other large towns,\nwere within that radius, so the place might not be so secluded,\nafter all. Yet it was quite certain, from the absolute stillness,\nthat we were in the country. I paced up and down the room,\nhumming a tune under my breath to keep up my spirits and feeling\nthat I was thoroughly earning my fifty-guinea fee.\n\n\"Suddenly, without any preliminary sound in the midst of the\nutter stillness, the door of my room swung slowly open. The woman\nwas standing in the aperture, the darkness of the hall behind\nher, the yellow light from my lamp beating upon her eager and\nbeautiful face. I could see at a glance that she was sick with\nfear, and the sight sent a chill to my own heart. She held up one\nshaking finger to warn me to be silent, and she shot a few\nwhispered words of broken English at me, her eyes glancing back,\nlike those of a frightened horse, into the gloom behind her.\n\n\"'I would go,' said she, trying hard, as it seemed to me, to\nspeak calmly; 'I would go. I should not stay here. There is no\ngood for you to do.'\n\n\"'But, madam,' said I, 'I have not yet done what I came for. I\ncannot possibly leave until I have seen the machine.'\n\n\"'It is not worth your while to wait,' she went on. 'You can pass\nthrough the door; no one hinders.' And then, seeing that I smiled\nand shook my head, she suddenly threw aside her constraint and\nmade a step forward, with her hands wrung together. 'For the love\nof Heaven!' she whispered, 'get away from here before it is too\nlate!'\n\n\"But I am somewhat headstrong by nature, and the more ready to\nengage in an affair when there is some obstacle in the way. I\nthought of my fifty-guinea fee, of my wearisome journey, and of\nthe unpleasant night which seemed to be before me. Was it all to\ngo for nothing? Why should I slink away without having carried\nout my commission, and without the payment which was my due? This\nwoman might, for all I knew, be a monomaniac. With a stout\nbearing, therefore, though her manner had shaken me more than I\ncared to confess, I still shook my head and declared my intention\nof remaining where I was. She was about to renew her entreaties\nwhen a door slammed overhead, and the sound of several footsteps\nwas heard upon the stairs. She listened for an instant, threw up\nher hands with a despairing gesture, and vanished as suddenly and\nas noiselessly as she had come.\n\n\"The newcomers were Colonel Lysander Stark and a short thick man\nwith a chinchilla beard growing out of the creases of his double\nchin, who was introduced to me as Mr. Ferguson.\n\n\"'This is my secretary and manager,' said the colonel. 'By the\nway, I was under the impression that I left this door shut just\nnow. I fear that you have felt the draught.'\n\n\"'On the contrary,' said I, 'I opened the door myself because I\nfelt the room to be a little close.'\n\n\"He shot one of his suspicious looks at me. 'Perhaps we had\nbetter proceed to business, then,' said he. 'Mr. Ferguson and I\nwill take you up to see the machine.'\n\n\"'I had better put my hat on, I suppose.'\n\n\"'Oh, no, it is in the house.'\n\n\"'What, you dig fuller's-earth in the house?'\n\n\"'No, no. This is only where we compress it. But never mind that.\nAll we wish you to do is to examine the machine and to let us\nknow what is wrong with it.'\n\n\"We went upstairs together, the colonel first with the lamp, the\nfat manager and I behind him. It was a labyrinth of an old house,\nwith corridors, passages, narrow winding staircases, and little\nlow doors, the thresholds of which were hollowed out by the\ngenerations who had crossed them. There were no carpets and no\nsigns of any furniture above the ground floor, while the plaster\nwas peeling off the walls, and the damp was breaking through in\ngreen, unhealthy blotches. I tried to put on as unconcerned an\nair as possible, but I had not forgotten the warnings of the\nlady, even though I disregarded them, and I kept a keen eye upon\nmy two companions. Ferguson appeared to be a morose and silent\nman, but I could see from the little that he said that he was at\nleast a fellow-countryman.\n\n\"Colonel Lysander Stark stopped at last before a low door, which\nhe unlocked. Within was a small, square room, in which the three\nof us could hardly get at one time. Ferguson remained outside,\nand the colonel ushered me in.\n\n\"'We are now,' said he, 'actually within the hydraulic press, and\nit would be a particularly unpleasant thing for us if anyone were\nto turn it on. The ceiling of this small chamber is really the\nend of the descending piston, and it comes down with the force of\nmany tons upon this metal floor. There are small lateral columns\nof water outside which receive the force, and which transmit and\nmultiply it in the manner which is familiar to you. The machine\ngoes readily enough, but there is some stiffness in the working\nof it, and it has lost a little of its force. Perhaps you will\nhave the goodness to look it over and to show us how we can set\nit right.'\n\n\"I took the lamp from him, and I examined the machine very\nthoroughly. It was indeed a gigantic one, and capable of\nexercising enormous pressure. When I passed outside, however, and\npressed down the levers which controlled it, I knew at once by\nthe whishing sound that there was a slight leakage, which allowed\na regurgitation of water through one of the side cylinders. An\nexamination showed that one of the india-rubber bands which was\nround the head of a driving-rod had shrunk so as not quite to\nfill the socket along which it worked. This was clearly the cause\nof the loss of power, and I pointed it out to my companions, who\nfollowed my remarks very carefully and asked several practical\nquestions as to how they should proceed to set it right. When I\nhad made it clear to them, I returned to the main chamber of the\nmachine and took a good look at it to satisfy my own curiosity.\nIt was obvious at a glance that the story of the fuller's-earth\nwas the merest fabrication, for it would be absurd to suppose\nthat so powerful an engine could be designed for so inadequate a\npurpose. The walls were of wood, but the floor consisted of a\nlarge iron trough, and when I came to examine it I could see a\ncrust of metallic deposit all over it. I had stooped and was\nscraping at this to see exactly what it was when I heard a\nmuttered exclamation in German and saw the cadaverous face of the\ncolonel looking down at me.\n\n\"'What are you doing there?' he asked.\n\n\"I felt angry at having been tricked by so elaborate a story as\nthat which he had told me. 'I was admiring your fuller's-earth,'\nsaid I; 'I think that I should be better able to advise you as to\nyour machine if I knew what the exact purpose was for which it\nwas used.'\n\n\"The instant that I uttered the words I regretted the rashness of\nmy speech. His face set hard, and a baleful light sprang up in\nhis grey eyes.\n\n\"'Very well,' said he, 'you shall know all about the machine.' He\ntook a step backward, slammed the little door, and turned the key\nin the lock. I rushed towards it and pulled at the handle, but it\nwas quite secure, and did not give in the least to my kicks and\nshoves. 'Hullo!' I yelled. 'Hullo! Colonel! Let me out!'\n\n\"And then suddenly in the silence I heard a sound which sent my\nheart into my mouth. It was the clank of the levers and the swish\nof the leaking cylinder. He had set the engine at work. The lamp\nstill stood upon the floor where I had placed it when examining\nthe trough. By its light I saw that the black ceiling was coming\ndown upon me, slowly, jerkily, but, as none knew better than\nmyself, with a force which must within a minute grind me to a\nshapeless pulp. I threw myself, screaming, against the door, and\ndragged with my nails at the lock. I implored the colonel to let\nme out, but the remorseless clanking of the levers drowned my\ncries. The ceiling was only a foot or two above my head, and with\nmy hand upraised I could feel its hard, rough surface. Then it\nflashed through my mind that the pain of my death would depend\nvery much upon the position in which I met it. If I lay on my\nface the weight would come upon my spine, and I shuddered to\nthink of that dreadful snap. Easier the other way, perhaps; and\nyet, had I the nerve to lie and look up at that deadly black\nshadow wavering down upon me? Already I was unable to stand\nerect, when my eye caught something which brought a gush of hope\nback to my heart.\n\n\"I have said that though the floor and ceiling were of iron, the\nwalls were of wood. As I gave a last hurried glance around, I saw\na thin line of yellow light between two of the boards, which\nbroadened and broadened as a small panel was pushed backward. For\nan instant I could hardly believe that here was indeed a door\nwhich led away from death. The next instant I threw myself\nthrough, and lay half-fainting upon the other side. The panel had\nclosed again behind me, but the crash of the lamp, and a few\nmoments afterwards the clang of the two slabs of metal, told me\nhow narrow had been my escape.\n\n\"I was recalled to myself by a frantic plucking at my wrist, and\nI found myself lying upon the stone floor of a narrow corridor,\nwhile a woman bent over me and tugged at me with her left hand,\nwhile she held a candle in her right. It was the same good friend\nwhose warning I had so foolishly rejected.\n\n\"'Come! come!' she cried breathlessly. 'They will be here in a\nmoment. They will see that you are not there. Oh, do not waste\nthe so-precious time, but come!'\n\n\"This time, at least, I did not scorn her advice. I staggered to\nmy feet and ran with her along the corridor and down a winding\nstair. The latter led to another broad passage, and just as we\nreached it we heard the sound of running feet and the shouting of\ntwo voices, one answering the other from the floor on which  we\nwere and from the one beneath. My guide stopped and looked about\nher like one  who is at her wit's end. Then she threw open a door\nwhich led into a bedroom, through the window of which the moon\nwas shining brightly.\n\n\"'It is your only chance,' said she. 'It is high, but it may be\nthat you can jump it.'\n\n\"As she spoke a light sprang into view at the further end of the\npassage, and I saw the lean figure of Colonel Lysander Stark\nrushing forward with a lantern in one hand and a weapon like a\nbutcher's cleaver in the other. I rushed across the bedroom,\nflung open the window, and looked out. How quiet and sweet and\nwholesome the garden looked in the moonlight, and it could not be\nmore than thirty feet down. I clambered out upon the sill, but I\nhesitated to jump until I should have heard what passed between\nmy saviour and the ruffian who pursued me. If she were ill-used,\nthen at any risks I was determined to go back to her assistance.\nThe thought had hardly flashed through my mind before he was at\nthe door, pushing his way past her; but she threw her arms round\nhim and tried to hold him back.\n\n\"'Fritz! Fritz!' she cried in English, 'remember your promise\nafter the last time. You said it should not be again. He will be\nsilent! Oh, he will be silent!'\n\n\"'You are mad, Elise!' he shouted, struggling to break away from\nher. 'You will be the ruin of us. He has seen too much. Let me\npass, I say!' He dashed her to one side, and, rushing to the\nwindow, cut at me with his heavy weapon. I had let myself go, and\nwas hanging by the hands to the sill, when his blow fell. I was\nconscious of a dull pain, my grip loosened, and I fell into the\ngarden below.\n\n\"I was shaken but not hurt by the fall; so I picked myself up and\nrushed off among the bushes as hard as I could run, for I\nunderstood that I was far from being out of danger yet. Suddenly,\nhowever, as I ran, a deadly dizziness and sickness came over me.\nI glanced down at my hand, which was throbbing painfully, and\nthen, for the first time, saw that my thumb had been cut off and\nthat the blood was pouring from my wound. I endeavoured to tie my\nhandkerchief round it, but there came a sudden buzzing in my\nears, and next moment I fell in a dead faint among the\nrose-bushes.\n\n\"How long I remained unconscious I cannot tell. It must have been\na very long time, for the moon had sunk, and a bright morning was\nbreaking when I came to myself. My clothes were all sodden with\ndew, and my coat-sleeve was drenched with blood from my wounded\nthumb. The smarting of it recalled in an instant all the\nparticulars of my night's adventure, and I sprang to my feet with\nthe feeling that I might hardly yet be safe from my pursuers. But\nto my astonishment, when I came to look round me, neither house\nnor garden were to be seen. I had been lying in an angle of the\nhedge close by the highroad, and just a little lower down was a\nlong building, which proved, upon my approaching it, to be the\nvery station at which I had arrived upon the previous night. Were\nit not for the ugly wound upon my hand, all that had passed\nduring those dreadful hours might have been an evil dream.\n\n\"Half dazed, I went into the station and asked about the morning\ntrain. There would be one to Reading in less than an hour. The\nsame porter was on duty, I found, as had been there when I\narrived. I inquired of him whether he had ever heard of Colonel\nLysander Stark. The name was strange to him. Had he observed a\ncarriage the night before waiting for me? No, he had not. Was\nthere a police-station anywhere near? There was one about three\nmiles off.\n\n\"It was too far for me to go, weak and ill as I was. I determined\nto wait until I got back to town before telling my story to the\npolice. It was a little past six when I arrived, so I went first\nto have my wound dressed, and then the doctor was kind enough to\nbring me along here. I put the case into your hands and shall do\nexactly what you advise.\"\n\nWe both sat in silence for some little time after listening to\nthis extraordinary narrative. Then Sherlock Holmes pulled down\nfrom the shelf one of the ponderous commonplace books in which he\nplaced his cuttings.\n\n\"Here is an advertisement which will interest you,\" said he. \"It\nappeared in all the papers about a year ago. Listen to this:\n'Lost, on the 9th inst., Mr. Jeremiah Hayling, aged\ntwenty-six, a hydraulic engineer. Left his lodgings at ten\no'clock at night, and has not been heard of since. Was\ndressed in,' etc., etc. Ha! That represents the last time that\nthe colonel needed to have his machine overhauled, I fancy.\"\n\n\"Good heavens!\" cried my patient. \"Then that explains what the\ngirl said.\"\n\n\"Undoubtedly. It is quite clear that the colonel was a cool and\ndesperate man, who was absolutely determined that nothing should\nstand in the way of his little game, like those out-and-out\npirates who will leave no survivor from a captured ship. Well,\nevery moment now is precious, so if you feel equal to it we shall\ngo down to Scotland Yard at once as a preliminary to starting for\nEyford.\"\n\nSome three hours or so afterwards we were all in the train\ntogether, bound from Reading to the little Berkshire village.\nThere were Sherlock Holmes, the hydraulic engineer, Inspector\nBradstreet, of Scotland Yard, a plain-clothes man, and myself.\nBradstreet had spread an ordnance map of the county out upon the\nseat and was busy with his compasses drawing a circle with Eyford\nfor its centre.\n\n\"There you are,\" said he. \"That circle is drawn at a radius of\nten miles from the village. The place we want must be somewhere\nnear that line. You said ten miles, I think, sir.\"\n\n\"It was an hour's good drive.\"\n\n\"And you think that they brought you back all that way when you\nwere unconscious?\"\n\n\"They must have done so. I have a confused memory, too, of having\nbeen lifted and conveyed somewhere.\"\n\n\"What I cannot understand,\" said I, \"is why they should have\nspared you when they found you lying fainting in the garden.\nPerhaps the villain was softened by the woman's entreaties.\"\n\n\"I hardly think that likely. I never saw a more inexorable face\nin my life.\"\n\n\"Oh, we shall soon clear up all that,\" said Bradstreet. \"Well, I\nhave drawn my circle, and I only wish I knew at what point upon\nit the folk that we are in search of are to be found.\"\n\n\"I think I could lay my finger on it,\" said Holmes quietly.\n\n\"Really, now!\" cried the inspector, \"you have formed your\nopinion! Come, now, we shall see who agrees with you. I say it is\nsouth, for the country is more deserted there.\"\n\n\"And I say east,\" said my patient.\n\n\"I am for west,\" remarked the plain-clothes man. \"There are\nseveral quiet little villages up there.\"\n\n\"And I am for north,\" said I, \"because there are no hills there,\nand our friend says that he did not notice the carriage go up\nany.\"\n\n\"Come,\" cried the inspector, laughing; \"it's a very pretty\ndiversity of opinion. We have boxed the compass among us. Who do\nyou give your casting vote to?\"\n\n\"You are all wrong.\"\n\n\"But we can't all be.\"\n\n\"Oh, yes, you can. This is my point.\" He placed his finger in the\ncentre of the circle. \"This is where we shall find them.\"\n\n\"But the twelve-mile drive?\" gasped Hatherley.\n\n\"Six out and six back. Nothing simpler. You say yourself that the\nhorse was fresh and glossy when you got in. How could it be that\nif it had gone twelve miles over heavy roads?\"\n\n\"Indeed, it is a likely ruse enough,\" observed Bradstreet\nthoughtfully. \"Of course there can be no doubt as to the nature\nof this gang.\"\n\n\"None at all,\" said Holmes. \"They are coiners on a large scale,\nand have used the machine to form the amalgam which has taken the\nplace of silver.\"\n\n\"We have known for some time that a clever gang was at work,\"\nsaid the inspector. \"They have been turning out half-crowns by\nthe thousand. We even traced them as far as Reading, but could\nget no farther, for they had covered their traces in a way that\nshowed that they were very old hands. But now, thanks to this\nlucky chance, I think that we have got them right enough.\"\n\nBut the inspector was mistaken, for those criminals were not\ndestined to fall into the hands of justice. As we rolled into\nEyford Station we saw a gigantic column of smoke which streamed\nup from behind a small clump of trees in the neighbourhood and\nhung like an immense ostrich feather over the landscape.\n\n\"A house on fire?\" asked Bradstreet as the train steamed off\nagain on its way.\n\n\"Yes, sir!\" said the station-master.\n\n\"When did it break out?\"\n\n\"I hear that it was during the night, sir, but it has got worse,\nand the whole place is in a blaze.\"\n\n\"Whose house is it?\"\n\n\"Dr. Becher's.\"\n\n\"Tell me,\" broke in the engineer, \"is Dr. Becher a German, very\nthin, with a long, sharp nose?\"\n\nThe station-master laughed heartily. \"No, sir, Dr. Becher is an\nEnglishman, and there isn't a man in the parish who has a\nbetter-lined waistcoat. But he has a gentleman staying with him,\na patient, as I understand, who is a foreigner, and he looks as\nif a little good Berkshire beef would do him no harm.\"\n\nThe station-master had not finished his speech before we were all\nhastening in the direction of the fire. The road topped a low\nhill, and there was a great widespread whitewashed building in\nfront of us, spouting fire at every chink and window, while in\nthe garden in front three fire-engines were vainly striving to\nkeep the flames under.\n\n\"That's it!\" cried Hatherley, in intense excitement. \"There is\nthe gravel-drive, and there are the rose-bushes where I lay. That\nsecond window is the one that I jumped from.\"\n\n\"Well, at least,\" said Holmes, \"you have had your revenge upon\nthem. There can be no question that it was your oil-lamp which,\nwhen it was crushed in the press, set fire to the wooden walls,\nthough no doubt they were too excited in the chase after you to\nobserve it at the time. Now keep your eyes open in this crowd for\nyour friends of last night, though I very much fear that they are\na good hundred miles off by now.\"\n\nAnd Holmes' fears came to be realised, for from that day to this\nno word has ever been heard either of the beautiful woman, the\nsinister German, or the morose Englishman. Early that morning a\npeasant had met a cart containing several people and some very\nbulky boxes driving rapidly in the direction of Reading, but\nthere all traces of the fugitives disappeared, and even Holmes'\ningenuity failed ever to discover the least clue as to their\nwhereabouts.\n\nThe firemen had been much perturbed at the strange arrangements\nwhich they had found within, and still more so by discovering a\nnewly severed human thumb upon a window-sill of the second floor.\nAbout sunset, however, their efforts were at last successful, and\nthey subdued the flames, but not before the roof had fallen in,\nand the whole place been reduced to such absolute ruin that, save\nsome twisted cylinders and iron piping, not a trace remained of\nthe machinery which had cost our unfortunate acquaintance so\ndearly. Large masses of nickel and of tin were discovered stored\nin an out-house, but no coins were to be found, which may have\nexplained the presence of those bulky boxes which have been\nalready referred to.\n\nHow our hydraulic engineer had been conveyed from the garden to\nthe spot where he recovered his senses might have remained\nforever a mystery were it not for the soft mould, which told us a\nvery plain tale. He had evidently been carried down by two\npersons, one of whom had remarkably small feet and the other\nunusually large ones. On the whole, it was most probable that the\nsilent Englishman, being less bold or less murderous than his\ncompanion, had assisted the woman to bear the unconscious man out\nof the way of danger.\n\n\"Well,\" said our engineer ruefully as we took our seats to return\nonce more to London, \"it has been a pretty business for me! I\nhave lost my thumb and I have lost a fifty-guinea fee, and what\nhave I gained?\"\n\n\"Experience,\" said Holmes, laughing. \"Indirectly it may be of\nvalue, you know; you have only to put it into words to gain the\nreputation of being excellent company for the remainder of your\nexistence.\"\n\n\n\nX. THE ADVENTURE OF THE NOBLE BACHELOR\n\nThe Lord St. Simon marriage, and its curious termination, have\nlong ceased to be a subject of interest in those exalted circles\nin which the unfortunate bridegroom moves. Fresh scandals have\neclipsed it, and their more piquant details have drawn the\ngossips away from this four-year-old drama. As I have reason to\nbelieve, however, that the full facts have never been revealed to\nthe general public, and as my friend Sherlock Holmes had a\nconsiderable share in clearing the matter up, I feel that no\nmemoir of him would be complete without some little sketch of\nthis remarkable episode.\n\nIt was a few weeks before my own marriage, during the days when I\nwas still sharing rooms with Holmes in Baker Street, that he came\nhome from an afternoon stroll to find a letter on the table\nwaiting for him. I had remained indoors all day, for the weather\nhad taken a sudden turn to rain, with high autumnal winds, and\nthe Jezail bullet which I had brought back in one of my limbs as\na relic of my Afghan campaign throbbed with dull persistence.\nWith my body in one easy-chair and my legs upon another, I had\nsurrounded myself with a cloud of newspapers until at last,\nsaturated with the news of the day, I tossed them all aside and\nlay listless, watching the huge crest and monogram upon the\nenvelope upon the table and wondering lazily who my friend's\nnoble correspondent could be.\n\n\"Here is a very fashionable epistle,\" I remarked as he entered.\n\"Your morning letters, if I remember right, were from a\nfish-monger and a tide-waiter.\"\n\n\"Yes, my correspondence has certainly the charm of variety,\" he\nanswered, smiling, \"and the humbler are usually the more\ninteresting. This looks like one of those unwelcome social\nsummonses which call upon a man either to be bored or to lie.\"\n\nHe broke the seal and glanced over the contents.\n\n\"Oh, come, it may prove to be something of interest, after all.\"\n\n\"Not social, then?\"\n\n\"No, distinctly professional.\"\n\n\"And from a noble client?\"\n\n\"One of the highest in England.\"\n\n\"My dear fellow, I congratulate you.\"\n\n\"I assure you, Watson, without affectation, that the status of my\nclient is a matter of less moment to me than the interest of his\ncase. It is just possible, however, that that also may not be\nwanting in this new investigation. You have been reading the\npapers diligently of late, have you not?\"\n\n\"It looks like it,\" said I ruefully, pointing to a huge bundle in\nthe corner. \"I have had nothing else to do.\"\n\n\"It is fortunate, for you will perhaps be able to post me up. I\nread nothing except the criminal news and the agony column. The\nlatter is always instructive. But if you have followed recent\nevents so closely you must have read about Lord St. Simon and his\nwedding?\"\n\n\"Oh, yes, with the deepest interest.\"\n\n\"That is well. The letter which I hold in my hand is from Lord\nSt. Simon. I will read it to you, and in return you must turn\nover these papers and let me have whatever bears upon the matter.\nThis is what he says:\n\n\"'MY DEAR MR. SHERLOCK HOLMES:--Lord Backwater tells me that I\nmay place implicit reliance upon your judgment and discretion. I\nhave determined, therefore, to call upon you and to consult you\nin reference to the very painful event which has occurred in\nconnection with my wedding. Mr. Lestrade, of Scotland Yard, is\nacting already in the matter, but he assures me that he sees no\nobjection to your co-operation, and that he even thinks that\nit might be of some assistance. I will call at four o'clock in\nthe afternoon, and, should you have any other engagement at that\ntime, I hope that you will postpone it, as this matter is of\nparamount importance. Yours faithfully, ST. SIMON.'\n\n\"It is dated from Grosvenor Mansions, written with a quill pen,\nand the noble lord has had the misfortune to get a smear of ink\nupon the outer side of his right little finger,\" remarked Holmes\nas he folded up the epistle.\n\n\"He says four o'clock. It is three now. He will be here in an\nhour.\"\n\n\"Then I have just time, with your assistance, to get clear upon\nthe subject. Turn over those papers and arrange the extracts in\ntheir order of time, while I take a glance as to who our client\nis.\" He picked a red-covered volume from a line of books of\nreference beside the mantelpiece. \"Here he is,\" said he, sitting\ndown and flattening it out upon his knee. \"'Lord Robert Walsingham\nde Vere St. Simon, second son of the Duke of Balmoral.' Hum! 'Arms:\nAzure, three caltrops in chief over a fess sable. Born in 1846.'\nHe's forty-one years of age, which is mature for marriage. Was\nUnder-Secretary for the colonies in a late administration. The\nDuke, his father, was at one time Secretary for Foreign Affairs.\nThey inherit Plantagenet blood by direct descent, and Tudor on\nthe distaff side. Ha! Well, there is nothing very instructive in\nall this. I think that I must turn to you Watson, for something\nmore solid.\"\n\n\"I have very little difficulty in finding what I want,\" said I,\n\"for the facts are quite recent, and the matter struck me as\nremarkable. I feared to refer them to you, however, as I knew\nthat you had an inquiry on hand and that you disliked the\nintrusion of other matters.\"\n\n\"Oh, you mean the little problem of the Grosvenor Square\nfurniture van. That is quite cleared up now--though, indeed, it\nwas obvious from the first. Pray give me the results of your\nnewspaper selections.\"\n\n\"Here is the first notice which I can find. It is in the personal\ncolumn of the Morning Post, and dates, as you see, some weeks\nback: 'A marriage has been arranged,' it says, 'and will, if\nrumour is correct, very shortly take place, between Lord Robert\nSt. Simon, second son of the Duke of Balmoral, and Miss Hatty\nDoran, the only daughter of Aloysius Doran. Esq., of San\nFrancisco, Cal., U.S.A.' That is all.\"\n\n\"Terse and to the point,\" remarked Holmes, stretching his long,\nthin legs towards the fire.\n\n\"There was a paragraph amplifying this in one of the society\npapers of the same week. Ah, here it is: 'There will soon be a\ncall for protection in the marriage market, for the present\nfree-trade principle appears to tell heavily against our home\nproduct. One by one the management of the noble houses of Great\nBritain is passing into the hands of our fair cousins from across\nthe Atlantic. An important addition has been made during the last\nweek to the list of the prizes which have been borne away by\nthese charming invaders. Lord St. Simon, who has shown himself\nfor over twenty years proof against the little god's arrows, has\nnow definitely announced his approaching marriage with Miss Hatty\nDoran, the fascinating daughter of a California millionaire. Miss\nDoran, whose graceful figure and striking face attracted much\nattention at the Westbury House festivities, is an only child,\nand it is currently reported that her dowry will run to\nconsiderably over the six figures, with expectancies for the\nfuture. As it is an open secret that the Duke of Balmoral has\nbeen compelled to sell his pictures within the last few years,\nand as Lord St. Simon has no property of his own save the small\nestate of Birchmoor, it is obvious that the Californian heiress\nis not the only gainer by an alliance which will enable her to\nmake the easy and common transition from a Republican lady to a\nBritish peeress.'\"\n\n\"Anything else?\" asked Holmes, yawning.\n\n\"Oh, yes; plenty. Then there is another note in the Morning Post\nto say that the marriage would be an absolutely quiet one, that it\nwould be at St. George's, Hanover Square, that only half a dozen\nintimate friends would be invited, and that the party would\nreturn to the furnished house at Lancaster Gate which has been\ntaken by Mr. Aloysius Doran. Two days later--that is, on\nWednesday last--there is a curt announcement that the wedding had\ntaken place, and that the honeymoon would be passed at Lord\nBackwater's place, near Petersfield. Those are all the notices\nwhich appeared before the disappearance of the bride.\"\n\n\"Before the what?\" asked Holmes with a start.\n\n\"The vanishing of the lady.\"\n\n\"When did she vanish, then?\"\n\n\"At the wedding breakfast.\"\n\n\"Indeed. This is more interesting than it promised to be; quite\ndramatic, in fact.\"\n\n\"Yes; it struck me as being a little out of the common.\"\n\n\"They often vanish before the ceremony, and occasionally during\nthe honeymoon; but I cannot call to mind anything quite so prompt\nas this. Pray let me have the details.\"\n\n\"I warn you that they are very incomplete.\"\n\n\"Perhaps we may make them less so.\"\n\n\"Such as they are, they are set forth in a single article of a\nmorning paper of yesterday, which I will read to you. It is\nheaded, 'Singular Occurrence at a Fashionable Wedding':\n\n\"'The family of Lord Robert St. Simon has been thrown into the\ngreatest consternation by the strange and painful episodes which\nhave taken place in connection with his wedding. The ceremony, as\nshortly announced in the papers of yesterday, occurred on the\nprevious morning; but it is only now that it has been possible to\nconfirm the strange rumours which have been so persistently\nfloating about. In spite of the attempts of the friends to hush\nthe matter up, so much public attention has now been drawn to it\nthat no good purpose can be served by affecting to disregard what\nis a common subject for conversation.\n\n\"'The ceremony, which was performed at St. George's, Hanover\nSquare, was a very quiet one, no one being present save the\nfather of the bride, Mr. Aloysius Doran, the Duchess of Balmoral,\nLord Backwater, Lord Eustace and Lady Clara St. Simon (the\nyounger brother and sister of the bridegroom), and Lady Alicia\nWhittington. The whole party proceeded afterwards to the house of\nMr. Aloysius Doran, at Lancaster Gate, where breakfast had been\nprepared. It appears that some little trouble was caused by a\nwoman, whose name has not been ascertained, who endeavoured to\nforce her way into the house after the bridal party, alleging\nthat she had some claim upon Lord St. Simon. It was only after a\npainful and prolonged scene that she was ejected by the butler\nand the footman. The bride, who had fortunately entered the house\nbefore this unpleasant interruption, had sat down to breakfast\nwith the rest, when she complained of a sudden indisposition and\nretired to her room. Her prolonged absence having caused some\ncomment, her father followed her, but learned from her maid that\nshe had only come up to her chamber for an instant, caught up an\nulster and bonnet, and hurried down to the passage. One of the\nfootmen declared that he had seen a lady leave the house thus\napparelled, but had refused to credit that it was his mistress,\nbelieving her to be with the company. On ascertaining that his\ndaughter had disappeared, Mr. Aloysius Doran, in conjunction with\nthe bridegroom, instantly put themselves in communication with\nthe police, and very energetic inquiries are being made, which\nwill probably result in a speedy clearing up of this very\nsingular business. Up to a late hour last night, however, nothing\nhad transpired as to the whereabouts of the missing lady. There\nare rumours of foul play in the matter, and it is said that the\npolice have caused the arrest of the woman who had caused the\noriginal disturbance, in the belief that, from jealousy or some\nother motive, she may have been concerned in the strange\ndisappearance of the bride.'\"\n\n\"And is that all?\"\n\n\"Only one little item in another of the morning papers, but it is\na suggestive one.\"\n\n\"And it is--\"\n\n\"That Miss Flora Millar, the lady who had caused the disturbance,\nhas actually been arrested. It appears that she was formerly a\ndanseuse at the Allegro, and that she has known the bridegroom\nfor some years. There are no further particulars, and the whole\ncase is in your hands now--so far as it has been set forth in the\npublic press.\"\n\n\"And an exceedingly interesting case it appears to be. I would\nnot have missed it for worlds. But there is a ring at the bell,\nWatson, and as the clock makes it a few minutes after four, I\nhave no doubt that this will prove to be our noble client. Do not\ndream of going, Watson, for I very much prefer having a witness,\nif only as a check to my own memory.\"\n\n\"Lord Robert St. Simon,\" announced our page-boy, throwing open\nthe door. A gentleman entered, with a pleasant, cultured face,\nhigh-nosed and pale, with something perhaps of petulance about\nthe mouth, and with the steady, well-opened eye of a man whose\npleasant lot it had ever been to command and to be obeyed. His\nmanner was brisk, and yet his general appearance gave an undue\nimpression of age, for he had a slight forward stoop and a little\nbend of the knees as he walked. His hair, too, as he swept off\nhis very curly-brimmed hat, was grizzled round the edges and thin\nupon the top. As to his dress, it was careful to the verge of\nfoppishness, with high collar, black frock-coat, white waistcoat,\nyellow gloves, patent-leather shoes, and light-coloured gaiters.\nHe advanced slowly into the room, turning his head from left to\nright, and swinging in his right hand the cord which held his\ngolden eyeglasses.\n\n\"Good-day, Lord St. Simon,\" said Holmes, rising and bowing. \"Pray\ntake the basket-chair. This is my friend and colleague, Dr.\nWatson. Draw up a little to the fire, and we will talk this\nmatter over.\"\n\n\"A most painful matter to me, as you can most readily imagine,\nMr. Holmes. I have been cut to the quick. I understand that you\nhave already managed several delicate cases of this sort, sir,\nthough I presume that they were hardly from the same class of\nsociety.\"\n\n\"No, I am descending.\"\n\n\"I beg pardon.\"\n\n\"My last client of the sort was a king.\"\n\n\"Oh, really! I had no idea. And which king?\"\n\n\"The King of Scandinavia.\"\n\n\"What! Had he lost his wife?\"\n\n\"You can understand,\" said Holmes suavely, \"that I extend to the\naffairs of my other clients the same secrecy which I promise to\nyou in yours.\"\n\n\"Of course! Very right! very right! I'm sure I beg pardon. As to\nmy own case, I am ready to give you any information which may\nassist you in forming an opinion.\"\n\n\"Thank you. I have already learned all that is in the public\nprints, nothing more. I presume that I may take it as correct--this\narticle, for example, as to the disappearance of the bride.\"\n\nLord St. Simon glanced over it. \"Yes, it is correct, as far as it\ngoes.\"\n\n\"But it needs a great deal of supplementing before anyone could\noffer an opinion. I think that I may arrive at my facts most\ndirectly by questioning you.\"\n\n\"Pray do so.\"\n\n\"When did you first meet Miss Hatty Doran?\"\n\n\"In San Francisco, a year ago.\"\n\n\"You were travelling in the States?\"\n\n\"Yes.\"\n\n\"Did you become engaged then?\"\n\n\"No.\"\n\n\"But you were on a friendly footing?\"\n\n\"I was amused by her society, and she could see that I was\namused.\"\n\n\"Her father is very rich?\"\n\n\"He is said to be the richest man on the Pacific slope.\"\n\n\"And how did he make his money?\"\n\n\"In mining. He had nothing a few years ago. Then he struck gold,\ninvested it, and came up by leaps and bounds.\"\n\n\"Now, what is your own impression as to the young lady's--your\nwife's character?\"\n\nThe nobleman swung his glasses a little faster and stared down\ninto the fire. \"You see, Mr. Holmes,\" said he, \"my wife was\ntwenty before her father became a rich man. During that time she\nran free in a mining camp and wandered through woods or\nmountains, so that her education has come from Nature rather than\nfrom the schoolmaster. She is what we call in England a tomboy,\nwith a strong nature, wild and free, unfettered by any sort of\ntraditions. She is impetuous--volcanic, I was about to say. She\nis swift in making up her mind and fearless in carrying out her\nresolutions. On the other hand, I would not have given her the\nname which I have the honour to bear\"--he gave a little stately\ncough--\"had not I thought her to be at bottom a noble woman. I\nbelieve that she is capable of heroic self-sacrifice and that\nanything dishonourable would be repugnant to her.\"\n\n\"Have you her photograph?\"\n\n\"I brought this with me.\" He opened a locket and showed us the\nfull face of a very lovely woman. It was not a photograph but an\nivory miniature, and the artist had brought out the full effect\nof the lustrous black hair, the large dark eyes, and the\nexquisite mouth. Holmes gazed long and earnestly at it. Then he\nclosed the locket and handed it back to Lord St. Simon.\n\n\"The young lady came to London, then, and you renewed your\nacquaintance?\"\n\n\"Yes, her father brought her over for this last London season. I\nmet her several times, became engaged to her, and have now\nmarried her.\"\n\n\"She brought, I understand, a considerable dowry?\"\n\n\"A fair dowry. Not more than is usual in my family.\"\n\n\"And this, of course, remains to you, since the marriage is a\nfait accompli?\"\n\n\"I really have made no inquiries on the subject.\"\n\n\"Very naturally not. Did you see Miss Doran on the day before the\nwedding?\"\n\n\"Yes.\"\n\n\"Was she in good spirits?\"\n\n\"Never better. She kept talking of what we should do in our\nfuture lives.\"\n\n\"Indeed! That is very interesting. And on the morning of the\nwedding?\"\n\n\"She was as bright as possible--at least until after the\nceremony.\"\n\n\"And did you observe any change in her then?\"\n\n\"Well, to tell the truth, I saw then the first signs that I had\never seen that her temper was just a little sharp. The incident\nhowever, was too trivial to relate and can have no possible\nbearing upon the case.\"\n\n\"Pray let us have it, for all that.\"\n\n\"Oh, it is childish. She dropped her bouquet as we went towards\nthe vestry. She was passing the front pew at the time, and it\nfell over into the pew. There was a moment's delay, but the\ngentleman in the pew handed it up to her again, and it did not\nappear to be the worse for the fall. Yet when I spoke to her of\nthe matter, she answered me abruptly; and in the carriage, on our\nway home, she seemed absurdly agitated over this trifling cause.\"\n\n\"Indeed! You say that there was a gentleman in the pew. Some of\nthe general public were present, then?\"\n\n\"Oh, yes. It is impossible to exclude them when the church is\nopen.\"\n\n\"This gentleman was not one of your wife's friends?\"\n\n\"No, no; I call him a gentleman by courtesy, but he was quite a\ncommon-looking person. I hardly noticed his appearance. But\nreally I think that we are wandering rather far from the point.\"\n\n\"Lady St. Simon, then, returned from the wedding in a less\ncheerful frame of mind than she had gone to it. What did she do\non re-entering her father's house?\"\n\n\"I saw her in conversation with her maid.\"\n\n\"And who is her maid?\"\n\n\"Alice is her name. She is an American and came from California\nwith her.\"\n\n\"A confidential servant?\"\n\n\"A little too much so. It seemed to me that her mistress allowed\nher to take great liberties. Still, of course, in America they\nlook upon these things in a different way.\"\n\n\"How long did she speak to this Alice?\"\n\n\"Oh, a few minutes. I had something else to think of.\"\n\n\"You did not overhear what they said?\"\n\n\"Lady St. Simon said something about 'jumping a claim.' She was\naccustomed to use slang of the kind. I have no idea what she\nmeant.\"\n\n\"American slang is very expressive sometimes. And what did your\nwife do when she finished speaking to her maid?\"\n\n\"She walked into the breakfast-room.\"\n\n\"On your arm?\"\n\n\"No, alone. She was very independent in little matters like that.\nThen, after we had sat down for ten minutes or so, she rose\nhurriedly, muttered some words of apology, and left the room. She\nnever came back.\"\n\n\"But this maid, Alice, as I understand, deposes that she went to\nher room, covered her bride's dress with a long ulster, put on a\nbonnet, and went out.\"\n\n\"Quite so. And she was afterwards seen walking into Hyde Park in\ncompany with Flora Millar, a woman who is now in custody, and who\nhad already made a disturbance at Mr. Doran's house that\nmorning.\"\n\n\"Ah, yes. I should like a few particulars as to this young lady,\nand your relations to her.\"\n\nLord St. Simon shrugged his shoulders and raised his eyebrows.\n\"We have been on a friendly footing for some years--I may say on\na very friendly footing. She used to be at the Allegro. I have\nnot treated her ungenerously, and she had no just cause of\ncomplaint against me, but you know what women are, Mr. Holmes.\nFlora was a dear little thing, but exceedingly hot-headed and\ndevotedly attached to me. She wrote me dreadful letters when she\nheard that I was about to be married, and, to tell the truth, the\nreason why I had the marriage celebrated so quietly was that I\nfeared lest there might be a scandal in the church. She came to\nMr. Doran's door just after we returned, and she endeavoured to\npush her way in, uttering very abusive expressions towards my\nwife, and even threatening her, but I had foreseen the\npossibility of something of the sort, and I had two police\nfellows there in private clothes, who soon pushed her out again.\nShe was quiet when she saw that there was no good in making a\nrow.\"\n\n\"Did your wife hear all this?\"\n\n\"No, thank goodness, she did not.\"\n\n\"And she was seen walking with this very woman afterwards?\"\n\n\"Yes. That is what Mr. Lestrade, of Scotland Yard, looks upon as\nso serious. It is thought that Flora decoyed my wife out and laid\nsome terrible trap for her.\"\n\n\"Well, it is a possible supposition.\"\n\n\"You think so, too?\"\n\n\"I did not say a probable one. But you do not yourself look upon\nthis as likely?\"\n\n\"I do not think Flora would hurt a fly.\"\n\n\"Still, jealousy is a strange transformer of characters. Pray\nwhat is your own theory as to what took place?\"\n\n\"Well, really, I came to seek a theory, not to propound one. I\nhave given you all the facts. Since you ask me, however, I may\nsay that it has occurred to me as possible that the excitement of\nthis affair, the consciousness that she had made so immense a\nsocial stride, had the effect of causing some little nervous\ndisturbance in my wife.\"\n\n\"In short, that she had become suddenly deranged?\"\n\n\"Well, really, when I consider that she has turned her back--I\nwill not say upon me, but upon so much that many have aspired to\nwithout success--I can hardly explain it in any other fashion.\"\n\n\"Well, certainly that is also a conceivable hypothesis,\" said\nHolmes, smiling. \"And now, Lord St. Simon, I think that I have\nnearly all my data. May I ask whether you were seated at the\nbreakfast-table so that you could see out of the window?\"\n\n\"We could see the other side of the road and the Park.\"\n\n\"Quite so. Then I do not think that I need to detain you longer.\nI shall communicate with you.\"\n\n\"Should you be fortunate enough to solve this problem,\" said our\nclient, rising.\n\n\"I have solved it.\"\n\n\"Eh? What was that?\"\n\n\"I say that I have solved it.\"\n\n\"Where, then, is my wife?\"\n\n\"That is a detail which I shall speedily supply.\"\n\nLord St. Simon shook his head. \"I am afraid that it will take\nwiser heads than yours or mine,\" he remarked, and bowing in a\nstately, old-fashioned manner he departed.\n\n\"It is very good of Lord St. Simon to honour my head by putting\nit on a level with his own,\" said Sherlock Holmes, laughing. \"I\nthink that I shall have a whisky and soda and a cigar after all\nthis cross-questioning. I had formed my conclusions as to the\ncase before our client came into the room.\"\n\n\"My dear Holmes!\"\n\n\"I have notes of several similar cases, though none, as I\nremarked before, which were quite as prompt. My whole examination\nserved to turn my conjecture into a certainty. Circumstantial\nevidence is occasionally very convincing, as when you find a\ntrout in the milk, to quote Thoreau's example.\"\n\n\"But I have heard all that you have heard.\"\n\n\"Without, however, the knowledge of pre-existing cases which\nserves me so well. There was a parallel instance in Aberdeen some\nyears back, and something on very much the same lines at Munich\nthe year after the Franco-Prussian War. It is one of these\ncases--but, hullo, here is Lestrade! Good-afternoon, Lestrade!\nYou will find an extra tumbler upon the sideboard, and there are\ncigars in the box.\"\n\nThe official detective was attired in a pea-jacket and cravat,\nwhich gave him a decidedly nautical appearance, and he carried a\nblack canvas bag in his hand. With a short greeting he seated\nhimself and lit the cigar which had been offered to him.\n\n\"What's up, then?\" asked Holmes with a twinkle in his eye. \"You\nlook dissatisfied.\"\n\n\"And I feel dissatisfied. It is this infernal St. Simon marriage\ncase. I can make neither head nor tail of the business.\"\n\n\"Really! You surprise me.\"\n\n\"Who ever heard of such a mixed affair? Every clue seems to slip\nthrough my fingers. I have been at work upon it all day.\"\n\n\"And very wet it seems to have made you,\" said Holmes laying his\nhand upon the arm of the pea-jacket.\n\n\"Yes, I have been dragging the Serpentine.\"\n\n\"In heaven's name, what for?\"\n\n\"In search of the body of Lady St. Simon.\"\n\nSherlock Holmes leaned back in his chair and laughed heartily.\n\n\"Have you dragged the basin of Trafalgar Square fountain?\" he\nasked.\n\n\"Why? What do you mean?\"\n\n\"Because you have just as good a chance of finding this lady in\nthe one as in the other.\"\n\nLestrade shot an angry glance at my companion. \"I suppose you\nknow all about it,\" he snarled.\n\n\"Well, I have only just heard the facts, but my mind is made up.\"\n\n\"Oh, indeed! Then you think that the Serpentine plays no part in\nthe matter?\"\n\n\"I think it very unlikely.\"\n\n\"Then perhaps you will kindly explain how it is that we found\nthis in it?\" He opened his bag as he spoke, and tumbled onto the\nfloor a wedding-dress of watered silk, a pair of white satin\nshoes and a bride's wreath and veil, all discoloured and soaked\nin water. \"There,\" said he, putting a new wedding-ring upon the\ntop of the pile. \"There is a little nut for you to crack, Master\nHolmes.\"\n\n\"Oh, indeed!\" said my friend, blowing blue rings into the air.\n\"You dragged them from the Serpentine?\"\n\n\"No. They were found floating near the margin by a park-keeper.\nThey have been identified as her clothes, and it seemed to me\nthat if the clothes were there the body would not be far off.\"\n\n\"By the same brilliant reasoning, every man's body is to be found\nin the neighbourhood of his wardrobe. And pray what did you hope\nto arrive at through this?\"\n\n\"At some evidence implicating Flora Millar in the disappearance.\"\n\n\"I am afraid that you will find it difficult.\"\n\n\"Are you, indeed, now?\" cried Lestrade with some bitterness. \"I\nam afraid, Holmes, that you are not very practical with your\ndeductions and your inferences. You have made two blunders in as\nmany minutes. This dress does implicate Miss Flora Millar.\"\n\n\"And how?\"\n\n\"In the dress is a pocket. In the pocket is a card-case. In the\ncard-case is a note. And here is the very note.\" He slapped it\ndown upon the table in front of him. \"Listen to this: 'You will\nsee me when all is ready. Come at once. F.H.M.' Now my theory all\nalong has been that Lady St. Simon was decoyed away by Flora\nMillar, and that she, with confederates, no doubt, was\nresponsible for her disappearance. Here, signed with her\ninitials, is the very note which was no doubt quietly slipped\ninto her hand at the door and which lured her within their\nreach.\"\n\n\"Very good, Lestrade,\" said Holmes, laughing. \"You really are\nvery fine indeed. Let me see it.\" He took up the paper in a\nlistless way, but his attention instantly became riveted, and he\ngave a little cry of satisfaction. \"This is indeed important,\"\nsaid he.\n\n\"Ha! you find it so?\"\n\n\"Extremely so. I congratulate you warmly.\"\n\nLestrade rose in his triumph and bent his head to look. \"Why,\" he\nshrieked, \"you're looking at the wrong side!\"\n\n\"On the contrary, this is the right side.\"\n\n\"The right side? You're mad! Here is the note written in pencil\nover here.\"\n\n\"And over here is what appears to be the fragment of a hotel\nbill, which interests me deeply.\"\n\n\"There's nothing in it. I looked at it before,\" said Lestrade.\n\"'Oct. 4th, rooms 8s., breakfast 2s. 6d., cocktail 1s., lunch 2s.\n6d., glass sherry, 8d.' I see nothing in that.\"\n\n\"Very likely not. It is most important, all the same. As to the\nnote, it is important also, or at least the initials are, so I\ncongratulate you again.\"\n\n\"I've wasted time enough,\" said Lestrade, rising. \"I believe in\nhard work and not in sitting by the fire spinning fine theories.\nGood-day, Mr. Holmes, and we shall see which gets to the bottom\nof the matter first.\" He gathered up the garments, thrust them\ninto the bag, and made for the door.\n\n\"Just one hint to you, Lestrade,\" drawled Holmes before his rival\nvanished; \"I will tell you the true solution of the matter. Lady\nSt. Simon is a myth. There is not, and there never has been, any\nsuch person.\"\n\nLestrade looked sadly at my companion. Then he turned to me,\ntapped his forehead three times, shook his head solemnly, and\nhurried away.\n\nHe had hardly shut the door behind him when Holmes rose to put on\nhis overcoat. \"There is something in what the fellow says about\noutdoor work,\" he remarked, \"so I think, Watson, that I must\nleave you to your papers for a little.\"\n\nIt was after five o'clock when Sherlock Holmes left me, but I had\nno time to be lonely, for within an hour there arrived a\nconfectioner's man with a very large flat box. This he unpacked\nwith the help of a youth whom he had brought with him, and\npresently, to my very great astonishment, a quite epicurean\nlittle cold supper began to be laid out upon our humble\nlodging-house mahogany. There were a couple of brace of cold\nwoodcock, a pheasant, a pâté de foie gras pie with a group of\nancient and cobwebby bottles. Having laid out all these luxuries,\nmy two visitors vanished away, like the genii of the Arabian\nNights, with no explanation save that the things had been paid\nfor and were ordered to this address.\n\nJust before nine o'clock Sherlock Holmes stepped briskly into the\nroom. His features were gravely set, but there was a light in his\neye which made me think that he had not been disappointed in his\nconclusions.\n\n\"They have laid the supper, then,\" he said, rubbing his hands.\n\n\"You seem to expect company. They have laid for five.\"\n\n\"Yes, I fancy we may have some company dropping in,\" said he. \"I\nam surprised that Lord St. Simon has not already arrived. Ha! I\nfancy that I hear his step now upon the stairs.\"\n\nIt was indeed our visitor of the afternoon who came bustling in,\ndangling his glasses more vigorously than ever, and with a very\nperturbed expression upon his aristocratic features.\n\n\"My messenger reached you, then?\" asked Holmes.\n\n\"Yes, and I confess that the contents startled me beyond measure.\nHave you good authority for what you say?\"\n\n\"The best possible.\"\n\nLord St. Simon sank into a chair and passed his hand over his\nforehead.\n\n\"What will the Duke say,\" he murmured, \"when he hears that one of\nthe family has been subjected to such humiliation?\"\n\n\"It is the purest accident. I cannot allow that there is any\nhumiliation.\"\n\n\"Ah, you look on these things from another standpoint.\"\n\n\"I fail to see that anyone is to blame. I can hardly see how the\nlady could have acted otherwise, though her abrupt method of\ndoing it was undoubtedly to be regretted. Having no mother, she\nhad no one to advise her at such a crisis.\"\n\n\"It was a slight, sir, a public slight,\" said Lord St. Simon,\ntapping his fingers upon the table.\n\n\"You must make allowance for this poor girl, placed in so\nunprecedented a position.\"\n\n\"I will make no allowance. I am very angry indeed, and I have\nbeen shamefully used.\"\n\n\"I think that I heard a ring,\" said Holmes. \"Yes, there are steps\non the landing. If I cannot persuade you to take a lenient view\nof the matter, Lord St. Simon, I have brought an advocate here\nwho may be more successful.\" He opened the door and ushered in a\nlady and gentleman. \"Lord St. Simon,\" said he \"allow me to\nintroduce you to Mr. and Mrs. Francis Hay Moulton. The lady, I\nthink, you have already met.\"\n\nAt the sight of these newcomers our client had sprung from his\nseat and stood very erect, with his eyes cast down and his hand\nthrust into the breast of his frock-coat, a picture of offended\ndignity. The lady had taken a quick step forward and had held out\nher hand to him, but he still refused to raise his eyes. It was\nas well for his resolution, perhaps, for her pleading face was\none which it was hard to resist.\n\n\"You're angry, Robert,\" said she. \"Well, I guess you have every\ncause to be.\"\n\n\"Pray make no apology to me,\" said Lord St. Simon bitterly.\n\n\"Oh, yes, I know that I have treated you real bad and that I\nshould have spoken to you before I went; but I was kind of\nrattled, and from the time when I saw Frank here again I just\ndidn't know what I was doing or saying. I only wonder I didn't\nfall down and do a faint right there before the altar.\"\n\n\"Perhaps, Mrs. Moulton, you would like my friend and me to leave\nthe room while you explain this matter?\"\n\n\"If I may give an opinion,\" remarked the strange gentleman,\n\"we've had just a little too much secrecy over this business\nalready. For my part, I should like all Europe and America to\nhear the rights of it.\" He was a small, wiry, sunburnt man,\nclean-shaven, with a sharp face and alert manner.\n\n\"Then I'll tell our story right away,\" said the lady. \"Frank here\nand I met in '84, in McQuire's camp, near the Rockies, where pa\nwas working a claim. We were engaged to each other, Frank and I;\nbut then one day father struck a rich pocket and made a pile,\nwhile poor Frank here had a claim that petered out and came to\nnothing. The richer pa grew the poorer was Frank; so at last pa\nwouldn't hear of our engagement lasting any longer, and he took\nme away to 'Frisco. Frank wouldn't throw up his hand, though; so\nhe followed me there, and he saw me without pa knowing anything\nabout it. It would only have made him mad to know, so we just\nfixed it all up for ourselves. Frank said that he would go and\nmake his pile, too, and never come back to claim me until he had\nas much as pa. So then I promised to wait for him to the end of\ntime and pledged myself not to marry anyone else while he lived.\n'Why shouldn't we be married right away, then,' said he, 'and\nthen I will feel sure of you; and I won't claim to be your\nhusband until I come back?' Well, we talked it over, and he had\nfixed it all up so nicely, with a clergyman all ready in waiting,\nthat we just did it right there; and then Frank went off to seek\nhis fortune, and I went back to pa.\n\n\"The next I heard of Frank was that he was in Montana, and then\nhe went prospecting in Arizona, and then I heard of him from New\nMexico. After that came a long newspaper story about how a\nminers' camp had been attacked by Apache Indians, and there was\nmy Frank's name among the killed. I fainted dead away, and I was\nvery sick for months after. Pa thought I had a decline and took\nme to half the doctors in 'Frisco. Not a word of news came for a\nyear and more, so that I never doubted that Frank was really\ndead. Then Lord St. Simon came to 'Frisco, and we came to London,\nand a marriage was arranged, and pa was very pleased, but I felt\nall the time that no man on this earth would ever take the place\nin my heart that had been given to my poor Frank.\n\n\"Still, if I had married Lord St. Simon, of course I'd have done\nmy duty by him. We can't command our love, but we can our\nactions. I went to the altar with him with the intention to make\nhim just as good a wife as it was in me to be. But you may\nimagine what I felt when, just as I came to the altar rails, I\nglanced back and saw Frank standing and looking at me out of the\nfirst pew. I thought it was his ghost at first; but when I looked\nagain there he was still, with a kind of question in his eyes, as\nif to ask me whether I were glad or sorry to see him. I wonder I\ndidn't drop. I know that everything was turning round, and the\nwords of the clergyman were just like the buzz of a bee in my\near. I didn't know what to do. Should I stop the service and make\na scene in the church? I glanced at him again, and he seemed to\nknow what I was thinking, for he raised his finger to his lips to\ntell me to be still. Then I saw him scribble on a piece of paper,\nand I knew that he was writing me a note. As I passed his pew on\nthe way out I dropped my bouquet over to him, and he slipped the\nnote into my hand when he returned me the flowers. It was only a\nline asking me to join him when he made the sign to me to do so.\nOf course I never doubted for a moment that my first duty was now\nto him, and I determined to do just whatever he might direct.\n\n\"When I got back I told my maid, who had known him in California,\nand had always been his friend. I ordered her to say nothing, but\nto get a few things packed and my ulster ready. I know I ought to\nhave spoken to Lord St. Simon, but it was dreadful hard before\nhis mother and all those great people. I just made up my mind to\nrun away and explain afterwards. I hadn't been at the table ten\nminutes before I saw Frank out of the window at the other side of\nthe road. He beckoned to me and then began walking into the Park.\nI slipped out, put on my things, and followed him. Some woman\ncame talking something or other about Lord St. Simon to\nme--seemed to me from the little I heard as if he had a little\nsecret of his own before marriage also--but I managed to get away\nfrom her and soon overtook Frank. We got into a cab together, and\naway we drove to some lodgings he had taken in Gordon Square, and\nthat was my true wedding after all those years of waiting. Frank\nhad been a prisoner among the Apaches, had escaped, came on to\n'Frisco, found that I had given him up for dead and had gone to\nEngland, followed me there, and had come upon me at last on the\nvery morning of my second wedding.\"\n\n\"I saw it in a paper,\" explained the American. \"It gave the name\nand the church but not where the lady lived.\"\n\n\"Then we had a talk as to what we should do, and Frank was all\nfor openness, but I was so ashamed of it all that I felt as if I\nshould like to vanish away and never see any of them again--just\nsending a line to pa, perhaps, to show him that I was alive. It\nwas awful to me to think of all those lords and ladies sitting\nround that breakfast-table and waiting for me to come back. So\nFrank took my wedding-clothes and things and made a bundle of\nthem, so that I should not be traced, and dropped them away\nsomewhere where no one could find them. It is likely that we\nshould have gone on to Paris to-morrow, only that this good\ngentleman, Mr. Holmes, came round to us this evening, though how\nhe found us is more than I can think, and he showed us very\nclearly and kindly that I was wrong and that Frank was right, and\nthat we should be putting ourselves in the wrong if we were so\nsecret. Then he offered to give us a chance of talking to Lord\nSt. Simon alone, and so we came right away round to his rooms at\nonce. Now, Robert, you have heard it all, and I am very sorry if\nI have given you pain, and I hope that you do not think very\nmeanly of me.\"\n\nLord St. Simon had by no means relaxed his rigid attitude, but\nhad listened with a frowning brow and a compressed lip to this\nlong narrative.\n\n\"Excuse me,\" he said, \"but it is not my custom to discuss my most\nintimate personal affairs in this public manner.\"\n\n\"Then you won't forgive me? You won't shake hands before I go?\"\n\n\"Oh, certainly, if it would give you any pleasure.\" He put out\nhis hand and coldly grasped that which she extended to him.\n\n\"I had hoped,\" suggested Holmes, \"that you would have joined us\nin a friendly supper.\"\n\n\"I think that there you ask a little too much,\" responded his\nLordship. \"I may be forced to acquiesce in these recent\ndevelopments, but I can hardly be expected to make merry over\nthem. I think that with your permission I will now wish you all a\nvery good-night.\" He included us all in a sweeping bow and\nstalked out of the room.\n\n\"Then I trust that you at least will honour me with your\ncompany,\" said Sherlock Holmes. \"It is always a joy to meet an\nAmerican, Mr. Moulton, for I am one of those who believe that the\nfolly of a monarch and the blundering of a minister in far-gone\nyears will not prevent our children from being some day citizens\nof the same world-wide country under a flag which shall be a\nquartering of the Union Jack with the Stars and Stripes.\"\n\n\"The case has been an interesting one,\" remarked Holmes when our\nvisitors had left us, \"because it serves to show very clearly how\nsimple the explanation may be of an affair which at first sight\nseems to be almost inexplicable. Nothing could be more natural\nthan the sequence of events as narrated by this lady, and nothing\nstranger than the result when viewed, for instance, by Mr.\nLestrade of Scotland Yard.\"\n\n\"You were not yourself at fault at all, then?\"\n\n\"From the first, two facts were very obvious to me, the one that\nthe lady had been quite willing to undergo the wedding ceremony,\nthe other that she had repented of it within a few minutes of\nreturning home. Obviously something had occurred during the\nmorning, then, to cause her to change her mind. What could that\nsomething be? She could not have spoken to anyone when she was\nout, for she had been in the company of the bridegroom. Had she\nseen someone, then? If she had, it must be someone from America\nbecause she had spent so short a time in this country that she\ncould hardly have allowed anyone to acquire so deep an influence\nover her that the mere sight of him would induce her to change\nher plans so completely. You see we have already arrived, by a\nprocess of exclusion, at the idea that she might have seen an\nAmerican. Then who could this American be, and why should he\npossess so much influence over her? It might be a lover; it might\nbe a husband. Her young womanhood had, I knew, been spent in\nrough scenes and under strange conditions. So far I had got\nbefore I ever heard Lord St. Simon's narrative. When he told us\nof a man in a pew, of the change in the bride's manner, of so\ntransparent a device for obtaining a note as the dropping of a\nbouquet, of her resort to her confidential maid, and of her very\nsignificant allusion to claim-jumping--which in miners' parlance\nmeans taking possession of that which another person has a prior\nclaim to--the whole situation became absolutely clear. She had\ngone off with a man, and the man was either a lover or was a\nprevious husband--the chances being in favour of the latter.\"\n\n\"And how in the world did you find them?\"\n\n\"It might have been difficult, but friend Lestrade held\ninformation in his hands the value of which he did not himself\nknow. The initials were, of course, of the highest importance,\nbut more valuable still was it to know that within a week he had\nsettled his bill at one of the most select London hotels.\"\n\n\"How did you deduce the select?\"\n\n\"By the select prices. Eight shillings for a bed and eightpence\nfor a glass of sherry pointed to one of the most expensive\nhotels. There are not many in London which charge at that rate.\nIn the second one which I visited in Northumberland Avenue, I\nlearned by an inspection of the book that Francis H. Moulton, an\nAmerican gentleman, had left only the day before, and on looking\nover the entries against him, I came upon the very items which I\nhad seen in the duplicate bill. His letters were to be forwarded\nto 226 Gordon Square; so thither I travelled, and being fortunate\nenough to find the loving couple at home, I ventured to give them\nsome paternal advice and to point out to them that it would be\nbetter in every way that they should make their position a little\nclearer both to the general public and to Lord St. Simon in\nparticular. I invited them to meet him here, and, as you see, I\nmade him keep the appointment.\"\n\n\"But with no very good result,\" I remarked. \"His conduct was\ncertainly not very gracious.\"\n\n\"Ah, Watson,\" said Holmes, smiling, \"perhaps you would not be\nvery gracious either, if, after all the trouble of wooing and\nwedding, you found yourself deprived in an instant of wife and of\nfortune. I think that we may judge Lord St. Simon very mercifully\nand thank our stars that we are never likely to find ourselves in\nthe same position. Draw your chair up and hand me my violin, for\nthe only problem we have still to solve is how to while away\nthese bleak autumnal evenings.\"\n\n\n\nXI. THE ADVENTURE OF THE BERYL CORONET\n\n\"Holmes,\" said I as I stood one morning in our bow-window looking\ndown the street, \"here is a madman coming along. It seems rather\nsad that his relatives should allow him to come out alone.\"\n\nMy friend rose lazily from his armchair and stood with his hands\nin the pockets of his dressing-gown, looking over my shoulder. It\nwas a bright, crisp February morning, and the snow of the day\nbefore still lay deep upon the ground, shimmering brightly in the\nwintry sun. Down the centre of Baker Street it had been ploughed\ninto a brown crumbly band by the traffic, but at either side and\non the heaped-up edges of the foot-paths it still lay as white as\nwhen it fell. The grey pavement had been cleaned and scraped, but\nwas still dangerously slippery, so that there were fewer\npassengers than usual. Indeed, from the direction of the\nMetropolitan Station no one was coming save the single gentleman\nwhose eccentric conduct had drawn my attention.\n\nHe was a man of about fifty, tall, portly, and imposing, with a\nmassive, strongly marked face and a commanding figure. He was\ndressed in a sombre yet rich style, in black frock-coat, shining\nhat, neat brown gaiters, and well-cut pearl-grey trousers. Yet\nhis actions were in absurd contrast to the dignity of his dress\nand features, for he was running hard, with occasional little\nsprings, such as a weary man gives who is little accustomed to\nset any tax upon his legs. As he ran he jerked his hands up and\ndown, waggled his head, and writhed his face into the most\nextraordinary contortions.\n\n\"What on earth can be the matter with him?\" I asked. \"He is\nlooking up at the numbers of the houses.\"\n\n\"I believe that he is coming here,\" said Holmes, rubbing his\nhands.\n\n\"Here?\"\n\n\"Yes; I rather think he is coming to consult me professionally. I\nthink that I recognise the symptoms. Ha! did I not tell you?\" As\nhe spoke, the man, puffing and blowing, rushed at our door and\npulled at our bell until the whole house resounded with the\nclanging.\n\nA few moments later he was in our room, still puffing, still\ngesticulating, but with so fixed a look of grief and despair in\nhis eyes that our smiles were turned in an instant to horror and\npity. For a while he could not get his words out, but swayed his\nbody and plucked at his hair like one who has been driven to the\nextreme limits of his reason. Then, suddenly springing to his\nfeet, he beat his head against the wall with such force that we\nboth rushed upon him and tore him away to the centre of the room.\nSherlock Holmes pushed him down into the easy-chair and, sitting\nbeside him, patted his hand and chatted with him in the easy,\nsoothing tones which he knew so well how to employ.\n\n\"You have come to me to tell your story, have you not?\" said he.\n\"You are fatigued with your haste. Pray wait until you have\nrecovered yourself, and then I shall be most happy to look into\nany little problem which you may submit to me.\"\n\nThe man sat for a minute or more with a heaving chest, fighting\nagainst his emotion. Then he passed his handkerchief over his\nbrow, set his lips tight, and turned his face towards us.\n\n\"No doubt you think me mad?\" said he.\n\n\"I see that you have had some great trouble,\" responded Holmes.\n\n\"God knows I have!--a trouble which is enough to unseat my\nreason, so sudden and so terrible is it. Public disgrace I might\nhave faced, although I am a man whose character has never yet\nborne a stain. Private affliction also is the lot of every man;\nbut the two coming together, and in so frightful a form, have\nbeen enough to shake my very soul. Besides, it is not I alone.\nThe very noblest in the land may suffer unless some way be found\nout of this horrible affair.\"\n\n\"Pray compose yourself, sir,\" said Holmes, \"and let me have a\nclear account of who you are and what it is that has befallen\nyou.\"\n\n\"My name,\" answered our visitor, \"is probably familiar to your\nears. I am Alexander Holder, of the banking firm of Holder &\nStevenson, of Threadneedle Street.\"\n\nThe name was indeed well known to us as belonging to the senior\npartner in the second largest private banking concern in the City\nof London. What could have happened, then, to bring one of the\nforemost citizens of London to this most pitiable pass? We\nwaited, all curiosity, until with another effort he braced\nhimself to tell his story.\n\n\"I feel that time is of value,\" said he; \"that is why I hastened\nhere when the police inspector suggested that I should secure\nyour co-operation. I came to Baker Street by the Underground and\nhurried from there on foot, for the cabs go slowly through this\nsnow. That is why I was so out of breath, for I am a man who\ntakes very little exercise. I feel better now, and I will put the\nfacts before you as shortly and yet as clearly as I can.\n\n\"It is, of course, well known to you that in a successful banking\nbusiness as much depends upon our being able to find remunerative\ninvestments for our funds as upon our increasing our connection\nand the number of our depositors. One of our most lucrative means\nof laying out money is in the shape of loans, where the security\nis unimpeachable. We have done a good deal in this direction\nduring the last few years, and there are many noble families to\nwhom we have advanced large sums upon the security of their\npictures, libraries, or plate.\n\n\"Yesterday morning I was seated in my office at the bank when a\ncard was brought in to me by one of the clerks. I started when I\nsaw the name, for it was that of none other than--well, perhaps\neven to you I had better say no more than that it was a name\nwhich is a household word all over the earth--one of the highest,\nnoblest, most exalted names in England. I was overwhelmed by the\nhonour and attempted, when he entered, to say so, but he plunged\nat once into business with the air of a man who wishes to hurry\nquickly through a disagreeable task.\n\n\"'Mr. Holder,' said he, 'I have been informed that you are in the\nhabit of advancing money.'\n\n\"'The firm does so when the security is good.' I answered.\n\n\"'It is absolutely essential to me,' said he, 'that I should have\n50,000 pounds at once. I could, of course, borrow so trifling a\nsum ten times over from my friends, but I much prefer to make it\na matter of business and to carry out that business myself. In my\nposition you can readily understand that it is unwise to place\none's self under obligations.'\n\n\"'For how long, may I ask, do you want this sum?' I asked.\n\n\"'Next Monday I have a large sum due to me, and I shall then most\ncertainly repay what you advance, with whatever interest you\nthink it right to charge. But it is very essential to me that the\nmoney should be paid at once.'\n\n\"'I should be happy to advance it without further parley from my\nown private purse,' said I, 'were it not that the strain would be\nrather more than it could bear. If, on the other hand, I am to do\nit in the name of the firm, then in justice to my partner I must\ninsist that, even in your case, every businesslike precaution\nshould be taken.'\n\n\"'I should much prefer to have it so,' said he, raising up a\nsquare, black morocco case which he had laid beside his chair.\n'You have doubtless heard of the Beryl Coronet?'\n\n\"'One of the most precious public possessions of the empire,'\nsaid I.\n\n\"'Precisely.' He opened the case, and there, imbedded in soft,\nflesh-coloured velvet, lay the magnificent piece of jewellery\nwhich he had named. 'There are thirty-nine enormous beryls,' said\nhe, 'and the price of the gold chasing is incalculable. The\nlowest estimate would put the worth of the coronet at double the\nsum which I have asked. I am prepared to leave it with you as my\nsecurity.'\n\n\"I took the precious case into my hands and looked in some\nperplexity from it to my illustrious client.\n\n\"'You doubt its value?' he asked.\n\n\"'Not at all. I only doubt--'\n\n\"'The propriety of my leaving it. You may set your mind at rest\nabout that. I should not dream of doing so were it not absolutely\ncertain that I should be able in four days to reclaim it. It is a\npure matter of form. Is the security sufficient?'\n\n\"'Ample.'\n\n\"'You understand, Mr. Holder, that I am giving you a strong proof\nof the confidence which I have in you, founded upon all that I\nhave heard of you. I rely upon you not only to be discreet and to\nrefrain from all gossip upon the matter but, above all, to\npreserve this coronet with every possible precaution because I\nneed not say that a great public scandal would be caused if any\nharm were to befall it. Any injury to it would be almost as\nserious as its complete loss, for there are no beryls in the\nworld to match these, and it would be impossible to replace them.\nI leave it with you, however, with every confidence, and I shall\ncall for it in person on Monday morning.'\n\n\"Seeing that my client was anxious to leave, I said no more but,\ncalling for my cashier, I ordered him to pay over fifty 1000\npound notes. When I was alone once more, however, with the\nprecious case lying upon the table in front of me, I could not\nbut think with some misgivings of the immense responsibility\nwhich it entailed upon me. There could be no doubt that, as it\nwas a national possession, a horrible scandal would ensue if any\nmisfortune should occur to it. I already regretted having ever\nconsented to take charge of it. However, it was too late to alter\nthe matter now, so I locked it up in my private safe and turned\nonce more to my work.\n\n\"When evening came I felt that it would be an imprudence to leave\nso precious a thing in the office behind me. Bankers' safes had\nbeen forced before now, and why should not mine be? If so, how\nterrible would be the position in which I should find myself! I\ndetermined, therefore, that for the next few days I would always\ncarry the case backward and forward with me, so that it might\nnever be really out of my reach. With this intention, I called a\ncab and drove out to my house at Streatham, carrying the jewel\nwith me. I did not breathe freely until I had taken it upstairs\nand locked it in the bureau of my dressing-room.\n\n\"And now a word as to my household, Mr. Holmes, for I wish you to\nthoroughly understand the situation. My groom and my page sleep\nout of the house, and may be set aside altogether. I have three\nmaid-servants who have been with me a number of years and whose\nabsolute reliability is quite above suspicion. Another, Lucy\nParr, the second waiting-maid, has only been in my service a few\nmonths. She came with an excellent character, however, and has\nalways given me satisfaction. She is a very pretty girl and has\nattracted admirers who have occasionally hung about the place.\nThat is the only drawback which we have found to her, but we\nbelieve her to be a thoroughly good girl in every way.\n\n\"So much for the servants. My family itself is so small that it\nwill not take me long to describe it. I am a widower and have an\nonly son, Arthur. He has been a disappointment to me, Mr.\nHolmes--a grievous disappointment. I have no doubt that I am\nmyself to blame. People tell me that I have spoiled him. Very\nlikely I have. When my dear wife died I felt that he was all I\nhad to love. I could not bear to see the smile fade even for a\nmoment from his face. I have never denied him a wish. Perhaps it\nwould have been better for both of us had I been sterner, but I\nmeant it for the best.\n\n\"It was naturally my intention that he should succeed me in my\nbusiness, but he was not of a business turn. He was wild,\nwayward, and, to speak the truth, I could not trust him in the\nhandling of large sums of money. When he was young he became a\nmember of an aristocratic club, and there, having charming\nmanners, he was soon the intimate of a number of men with long\npurses and expensive habits. He learned to play heavily at cards\nand to squander money on the turf, until he had again and again\nto come to me and implore me to give him an advance upon his\nallowance, that he might settle his debts of honour. He tried\nmore than once to break away from the dangerous company which he\nwas keeping, but each time the influence of his friend, Sir\nGeorge Burnwell, was enough to draw him back again.\n\n\"And, indeed, I could not wonder that such a man as Sir George\nBurnwell should gain an influence over him, for he has frequently\nbrought him to my house, and I have found myself that I could\nhardly resist the fascination of his manner. He is older than\nArthur, a man of the world to his finger-tips, one who had been\neverywhere, seen everything, a brilliant talker, and a man of\ngreat personal beauty. Yet when I think of him in cold blood, far\naway from the glamour of his presence, I am convinced from his\ncynical speech and the look which I have caught in his eyes that\nhe is one who should be deeply distrusted. So I think, and so,\ntoo, thinks my little Mary, who has a woman's quick insight into\ncharacter.\n\n\"And now there is only she to be described. She is my niece; but\nwhen my brother died five years ago and left her alone in the\nworld I adopted her, and have looked upon her ever since as my\ndaughter. She is a sunbeam in my house--sweet, loving, beautiful,\na wonderful manager and housekeeper, yet as tender and quiet and\ngentle as a woman could be. She is my right hand. I do not know\nwhat I could do without her. In only one matter has she ever gone\nagainst my wishes. Twice my boy has asked her to marry him, for\nhe loves her devotedly, but each time she has refused him. I\nthink that if anyone could have drawn him into the right path it\nwould have been she, and that his marriage might have changed his\nwhole life; but now, alas! it is too late--forever too late!\n\n\"Now, Mr. Holmes, you know the people who live under my roof, and\nI shall continue with my miserable story.\n\n\"When we were taking coffee in the drawing-room that night after\ndinner, I told Arthur and Mary my experience, and of the precious\ntreasure which we had under our roof, suppressing only the name\nof my client. Lucy Parr, who had brought in the coffee, had, I am\nsure, left the room; but I cannot swear that the door was closed.\nMary and Arthur were much interested and wished to see the famous\ncoronet, but I thought it better not to disturb it.\n\n\"'Where have you put it?' asked Arthur.\n\n\"'In my own bureau.'\n\n\"'Well, I hope to goodness the house won't be burgled during the\nnight.' said he.\n\n\"'It is locked up,' I answered.\n\n\"'Oh, any old key will fit that bureau. When I was a youngster I\nhave opened it myself with the key of the box-room cupboard.'\n\n\"He often had a wild way of talking, so that I thought little of\nwhat he said. He followed me to my room, however, that night with\na very grave face.\n\n\"'Look here, dad,' said he with his eyes cast down, 'can you let\nme have 200 pounds?'\n\n\"'No, I cannot!' I answered sharply. 'I have been far too\ngenerous with you in money matters.'\n\n\"'You have been very kind,' said he, 'but I must have this money,\nor else I can never show my face inside the club again.'\n\n\"'And a very good thing, too!' I cried.\n\n\"'Yes, but you would not have me leave it a dishonoured man,'\nsaid he. 'I could not bear the disgrace. I must raise the money\nin some way, and if you will not let me have it, then I must try\nother means.'\n\n\"I was very angry, for this was the third demand during the\nmonth. 'You shall not have a farthing from me,' I cried, on which\nhe bowed and left the room without another word.\n\n\"When he was gone I unlocked my bureau, made sure that my\ntreasure was safe, and locked it again. Then I started to go\nround the house to see that all was secure--a duty which I\nusually leave to Mary but which I thought it well to perform\nmyself that night. As I came down the stairs I saw Mary herself\nat the side window of the hall, which she closed and fastened as\nI approached.\n\n\"'Tell me, dad,' said she, looking, I thought, a little\ndisturbed, 'did you give Lucy, the maid, leave to go out\nto-night?'\n\n\"'Certainly not.'\n\n\"'She came in just now by the back door. I have no doubt that she\nhas only been to the side gate to see someone, but I think that\nit is hardly safe and should be stopped.'\n\n\"'You must speak to her in the morning, or I will if you prefer\nit. Are you sure that everything is fastened?'\n\n\"'Quite sure, dad.'\n\n\"'Then, good-night.' I kissed her and went up to my bedroom\nagain, where I was soon asleep.\n\n\"I am endeavouring to tell you everything, Mr. Holmes, which may\nhave any bearing upon the case, but I beg that you will question\nme upon any point which I do not make clear.\"\n\n\"On the contrary, your statement is singularly lucid.\"\n\n\"I come to a part of my story now in which I should wish to be\nparticularly so. I am not a very heavy sleeper, and the anxiety\nin my mind tended, no doubt, to make me even less so than usual.\nAbout two in the morning, then, I was awakened by some sound in\nthe house. It had ceased ere I was wide awake, but it had left an\nimpression behind it as though a window had gently closed\nsomewhere. I lay listening with all my ears. Suddenly, to my\nhorror, there was a distinct sound of footsteps moving softly in\nthe next room. I slipped out of bed, all palpitating with fear,\nand peeped round the corner of my dressing-room door.\n\n\"'Arthur!' I screamed, 'you villain! you thief! How dare you\ntouch that coronet?'\n\n\"The gas was half up, as I had left it, and my unhappy boy,\ndressed only in his shirt and trousers, was standing beside the\nlight, holding the coronet in his hands. He appeared to be\nwrenching at it, or bending it with all his strength. At my cry\nhe dropped it from his grasp and turned as pale as death. I\nsnatched it up and examined it. One of the gold corners, with\nthree of the beryls in it, was missing.\n\n\"'You blackguard!' I shouted, beside myself with rage. 'You have\ndestroyed it! You have dishonoured me forever! Where are the\njewels which you have stolen?'\n\n\"'Stolen!' he cried.\n\n\"'Yes, thief!' I roared, shaking him by the shoulder.\n\n\"'There are none missing. There cannot be any missing,' said he.\n\n\"'There are three missing. And you know where they are. Must I\ncall you a liar as well as a thief? Did I not see you trying to\ntear off another piece?'\n\n\"'You have called me names enough,' said he, 'I will not stand it\nany longer. I shall not say another word about this business,\nsince you have chosen to insult me. I will leave your house in\nthe morning and make my own way in the world.'\n\n\"'You shall leave it in the hands of the police!' I cried\nhalf-mad with grief and rage. 'I shall have this matter probed to\nthe bottom.'\n\n\"'You shall learn nothing from me,' said he with a passion such\nas I should not have thought was in his nature. 'If you choose to\ncall the police, let the police find what they can.'\n\n\"By this time the whole house was astir, for I had raised my\nvoice in my anger. Mary was the first to rush into my room, and,\nat the sight of the coronet and of Arthur's face, she read the\nwhole story and, with a scream, fell down senseless on the\nground. I sent the house-maid for the police and put the\ninvestigation into their hands at once. When the inspector and a\nconstable entered the house, Arthur, who had stood sullenly with\nhis arms folded, asked me whether it was my intention to charge\nhim with theft. I answered that it had ceased to be a private\nmatter, but had become a public one, since the ruined coronet was\nnational property. I was determined that the law should have its\nway in everything.\n\n\"'At least,' said he, 'you will not have me arrested at once. It\nwould be to your advantage as well as mine if I might leave the\nhouse for five minutes.'\n\n\"'That you may get away, or perhaps that you may conceal what you\nhave stolen,' said I. And then, realising the dreadful position\nin which I was placed, I implored him to remember that not only\nmy honour but that of one who was far greater than I was at\nstake; and that he threatened to raise a scandal which would\nconvulse the nation. He might avert it all if he would but tell\nme what he had done with the three missing stones.\n\n\"'You may as well face the matter,' said I; 'you have been caught\nin the act, and no confession could make your guilt more heinous.\nIf you but make such reparation as is in your power, by telling\nus where the beryls are, all shall be forgiven and forgotten.'\n\n\"'Keep your forgiveness for those who ask for it,' he answered,\nturning away from me with a sneer. I saw that he was too hardened\nfor any words of mine to influence him. There was but one way for\nit. I called in the inspector and gave him into custody. A search\nwas made at once not only of his person but of his room and of\nevery portion of the house where he could possibly have concealed\nthe gems; but no trace of them could be found, nor would the\nwretched boy open his mouth for all our persuasions and our\nthreats. This morning he was removed to a cell, and I, after\ngoing through all the police formalities, have hurried round to\nyou to implore you to use your skill in unravelling the matter.\nThe police have openly confessed that they can at present make\nnothing of it. You may go to any expense which you think\nnecessary. I have already offered a reward of 1000 pounds. My\nGod, what shall I do! I have lost my honour, my gems, and my son\nin one night. Oh, what shall I do!\"\n\nHe put a hand on either side of his head and rocked himself to\nand fro, droning to himself like a child whose grief has got\nbeyond words.\n\nSherlock Holmes sat silent for some few minutes, with his brows\nknitted and his eyes fixed upon the fire.\n\n\"Do you receive much company?\" he asked.\n\n\"None save my partner with his family and an occasional friend of\nArthur's. Sir George Burnwell has been several times lately. No\none else, I think.\"\n\n\"Do you go out much in society?\"\n\n\"Arthur does. Mary and I stay at home. We neither of us care for\nit.\"\n\n\"That is unusual in a young girl.\"\n\n\"She is of a quiet nature. Besides, she is not so very young. She\nis four-and-twenty.\"\n\n\"This matter, from what you say, seems to have been a shock to\nher also.\"\n\n\"Terrible! She is even more affected than I.\"\n\n\"You have neither of you any doubt as to your son's guilt?\"\n\n\"How can we have when I saw him with my own eyes with the coronet\nin his hands.\"\n\n\"I hardly consider that a conclusive proof. Was the remainder of\nthe coronet at all injured?\"\n\n\"Yes, it was twisted.\"\n\n\"Do you not think, then, that he might have been trying to\nstraighten it?\"\n\n\"God bless you! You are doing what you can for him and for me.\nBut it is too heavy a task. What was he doing there at all? If\nhis purpose were innocent, why did he not say so?\"\n\n\"Precisely. And if it were guilty, why did he not invent a lie?\nHis silence appears to me to cut both ways. There are several\nsingular points about the case. What did the police think of the\nnoise which awoke you from your sleep?\"\n\n\"They considered that it might be caused by Arthur's closing his\nbedroom door.\"\n\n\"A likely story! As if a man bent on felony would slam his door\nso as to wake a household. What did they say, then, of the\ndisappearance of these gems?\"\n\n\"They are still sounding the planking and probing the furniture\nin the hope of finding them.\"\n\n\"Have they thought of looking outside the house?\"\n\n\"Yes, they have shown extraordinary energy. The whole garden has\nalready been minutely examined.\"\n\n\"Now, my dear sir,\" said Holmes, \"is it not obvious to you now\nthat this matter really strikes very much deeper than either you\nor the police were at first inclined to think? It appeared to you\nto be a simple case; to me it seems exceedingly complex. Consider\nwhat is involved by your theory. You suppose that your son came\ndown from his bed, went, at great risk, to your dressing-room,\nopened your bureau, took out your coronet, broke off by main\nforce a small portion of it, went off to some other place,\nconcealed three gems out of the thirty-nine, with such skill that\nnobody can find them, and then returned with the other thirty-six\ninto the room in which he exposed himself to the greatest danger\nof being discovered. I ask you now, is such a theory tenable?\"\n\n\"But what other is there?\" cried the banker with a gesture of\ndespair. \"If his motives were innocent, why does he not explain\nthem?\"\n\n\"It is our task to find that out,\" replied Holmes; \"so now, if\nyou please, Mr. Holder, we will set off for Streatham together,\nand devote an hour to glancing a little more closely into\ndetails.\"\n\nMy friend insisted upon my accompanying them in their expedition,\nwhich I was eager enough to do, for my curiosity and sympathy\nwere deeply stirred by the story to which we had listened. I\nconfess that the guilt of the banker's son appeared to me to be\nas obvious as it did to his unhappy father, but still I had such\nfaith in Holmes' judgment that I felt that there must be some\ngrounds for hope as long as he was dissatisfied with the accepted\nexplanation. He hardly spoke a word the whole way out to the\nsouthern suburb, but sat with his chin upon his breast and his\nhat drawn over his eyes, sunk in the deepest thought. Our client\nappeared to have taken fresh heart at the little glimpse of hope\nwhich had been presented to him, and he even broke into a\ndesultory chat with me over his business affairs. A short railway\njourney and a shorter walk brought us to Fairbank, the modest\nresidence of the great financier.\n\nFairbank was a good-sized square house of white stone, standing\nback a little from the road. A double carriage-sweep, with a\nsnow-clad lawn, stretched down in front to two large iron gates\nwhich closed the entrance. On the right side was a small wooden\nthicket, which led into a narrow path between two neat hedges\nstretching from the road to the kitchen door, and forming the\ntradesmen's entrance. On the left ran a lane which led to the\nstables, and was not itself within the grounds at all, being a\npublic, though little used, thoroughfare. Holmes left us standing\nat the door and walked slowly all round the house, across the\nfront, down the tradesmen's path, and so round by the garden\nbehind into the stable lane. So long was he that Mr. Holder and I\nwent into the dining-room and waited by the fire until he should\nreturn. We were sitting there in silence when the door opened and\na young lady came in. She was rather above the middle height,\nslim, with dark hair and eyes, which seemed the darker against\nthe absolute pallor of her skin. I do not think that I have ever\nseen such deadly paleness in a woman's face. Her lips, too, were\nbloodless, but her eyes were flushed with crying. As she swept\nsilently into the room she impressed me with a greater sense of\ngrief than the banker had done in the morning, and it was the\nmore striking in her as she was evidently a woman of strong\ncharacter, with immense capacity for self-restraint. Disregarding\nmy presence, she went straight to her uncle and passed her hand\nover his head with a sweet womanly caress.\n\n\"You have given orders that Arthur should be liberated, have you\nnot, dad?\" she asked.\n\n\"No, no, my girl, the matter must be probed to the bottom.\"\n\n\"But I am so sure that he is innocent. You know what woman's\ninstincts are. I know that he has done no harm and that you will\nbe sorry for having acted so harshly.\"\n\n\"Why is he silent, then, if he is innocent?\"\n\n\"Who knows? Perhaps because he was so angry that you should\nsuspect him.\"\n\n\"How could I help suspecting him, when I actually saw him with\nthe coronet in his hand?\"\n\n\"Oh, but he had only picked it up to look at it. Oh, do, do take\nmy word for it that he is innocent. Let the matter drop and say\nno more. It is so dreadful to think of our dear Arthur in\nprison!\"\n\n\"I shall never let it drop until the gems are found--never, Mary!\nYour affection for Arthur blinds you as to the awful consequences\nto me. Far from hushing the thing up, I have brought a gentleman\ndown from London to inquire more deeply into it.\"\n\n\"This gentleman?\" she asked, facing round to me.\n\n\"No, his friend. He wished us to leave him alone. He is round in\nthe stable lane now.\"\n\n\"The stable lane?\" She raised her dark eyebrows. \"What can he\nhope to find there? Ah! this, I suppose, is he. I trust, sir,\nthat you will succeed in proving, what I feel sure is the truth,\nthat my cousin Arthur is innocent of this crime.\"\n\n\"I fully share your opinion, and I trust, with you, that we may\nprove it,\" returned Holmes, going back to the mat to knock the\nsnow from his shoes. \"I believe I have the honour of addressing\nMiss Mary Holder. Might I ask you a question or two?\"\n\n\"Pray do, sir, if it may help to clear this horrible affair up.\"\n\n\"You heard nothing yourself last night?\"\n\n\"Nothing, until my uncle here began to speak loudly. I heard\nthat, and I came down.\"\n\n\"You shut up the windows and doors the night before. Did you\nfasten all the windows?\"\n\n\"Yes.\"\n\n\"Were they all fastened this morning?\"\n\n\"Yes.\"\n\n\"You have a maid who has a sweetheart? I think that you remarked\nto your uncle last night that she had been out to see him?\"\n\n\"Yes, and she was the girl who waited in the drawing-room, and\nwho may have heard uncle's remarks about the coronet.\"\n\n\"I see. You infer that she may have gone out to tell her\nsweetheart, and that the two may have planned the robbery.\"\n\n\"But what is the good of all these vague theories,\" cried the\nbanker impatiently, \"when I have told you that I saw Arthur with\nthe coronet in his hands?\"\n\n\"Wait a little, Mr. Holder. We must come back to that. About this\ngirl, Miss Holder. You saw her return by the kitchen door, I\npresume?\"\n\n\"Yes; when I went to see if the door was fastened for the night I\nmet her slipping in. I saw the man, too, in the gloom.\"\n\n\"Do you know him?\"\n\n\"Oh, yes! he is the green-grocer who brings our vegetables round.\nHis name is Francis Prosper.\"\n\n\"He stood,\" said Holmes, \"to the left of the door--that is to\nsay, farther up the path than is necessary to reach the door?\"\n\n\"Yes, he did.\"\n\n\"And he is a man with a wooden leg?\"\n\nSomething like fear sprang up in the young lady's expressive\nblack eyes. \"Why, you are like a magician,\" said she. \"How do you\nknow that?\" She smiled, but there was no answering smile in\nHolmes' thin, eager face.\n\n\"I should be very glad now to go upstairs,\" said he. \"I shall\nprobably wish to go over the outside of the house again. Perhaps\nI had better take a look at the lower windows before I go up.\"\n\nHe walked swiftly round from one to the other, pausing only at\nthe large one which looked from the hall onto the stable lane.\nThis he opened and made a very careful examination of the sill\nwith his powerful magnifying lens. \"Now we shall go upstairs,\"\nsaid he at last.\n\nThe banker's dressing-room was a plainly furnished little\nchamber, with a grey carpet, a large bureau, and a long mirror.\nHolmes went to the bureau first and looked hard at the lock.\n\n\"Which key was used to open it?\" he asked.\n\n\"That which my son himself indicated--that of the cupboard of the\nlumber-room.\"\n\n\"Have you it here?\"\n\n\"That is it on the dressing-table.\"\n\nSherlock Holmes took it up and opened the bureau.\n\n\"It is a noiseless lock,\" said he. \"It is no wonder that it did\nnot wake you. This case, I presume, contains the coronet. We must\nhave a look at it.\" He opened the case, and taking out the diadem\nhe laid it upon the table. It was a magnificent specimen of the\njeweller's art, and the thirty-six stones were the finest that I\nhave ever seen. At one side of the coronet was a cracked edge,\nwhere a corner holding three gems had been torn away.\n\n\"Now, Mr. Holder,\" said Holmes, \"here is the corner which\ncorresponds to that which has been so unfortunately lost. Might I\nbeg that you will break it off.\"\n\nThe banker recoiled in horror. \"I should not dream of trying,\"\nsaid he.\n\n\"Then I will.\" Holmes suddenly bent his strength upon it, but\nwithout result. \"I feel it give a little,\" said he; \"but, though\nI am exceptionally strong in the fingers, it would take me all my\ntime to break it. An ordinary man could not do it. Now, what do\nyou think would happen if I did break it, Mr. Holder? There would\nbe a noise like a pistol shot. Do you tell me that all this\nhappened within a few yards of your bed and that you heard\nnothing of it?\"\n\n\"I do not know what to think. It is all dark to me.\"\n\n\"But perhaps it may grow lighter as we go. What do you think,\nMiss Holder?\"\n\n\"I confess that I still share my uncle's perplexity.\"\n\n\"Your son had no shoes or slippers on when you saw him?\"\n\n\"He had nothing on save only his trousers and shirt.\"\n\n\"Thank you. We have certainly been favoured with extraordinary\nluck during this inquiry, and it will be entirely our own fault\nif we do not succeed in clearing the matter up. With your\npermission, Mr. Holder, I shall now continue my investigations\noutside.\"\n\nHe went alone, at his own request, for he explained that any\nunnecessary footmarks might make his task more difficult. For an\nhour or more he was at work, returning at last with his feet\nheavy with snow and his features as inscrutable as ever.\n\n\"I think that I have seen now all that there is to see, Mr.\nHolder,\" said he; \"I can serve you best by returning to my\nrooms.\"\n\n\"But the gems, Mr. Holmes. Where are they?\"\n\n\"I cannot tell.\"\n\nThe banker wrung his hands. \"I shall never see them again!\" he\ncried. \"And my son? You give me hopes?\"\n\n\"My opinion is in no way altered.\"\n\n\"Then, for God's sake, what was this dark business which was\nacted in my house last night?\"\n\n\"If you can call upon me at my Baker Street rooms to-morrow\nmorning between nine and ten I shall be happy to do what I can to\nmake it clearer. I understand that you give me carte blanche to\nact for you, provided only that I get back the gems, and that you\nplace no limit on the sum I may draw.\"\n\n\"I would give my fortune to have them back.\"\n\n\"Very good. I shall look into the matter between this and then.\nGood-bye; it is just possible that I may have to come over here\nagain before evening.\"\n\nIt was obvious to me that my companion's mind was now made up\nabout the case, although what his conclusions were was more than\nI could even dimly imagine. Several times during our homeward\njourney I endeavoured to sound him upon the point, but he always\nglided away to some other topic, until at last I gave it over in\ndespair. It was not yet three when we found ourselves in our\nrooms once more. He hurried to his chamber and was down again in\na few minutes dressed as a common loafer. With his collar turned\nup, his shiny, seedy coat, his red cravat, and his worn boots, he\nwas a perfect sample of the class.\n\n\"I think that this should do,\" said he, glancing into the glass\nabove the fireplace. \"I only wish that you could come with me,\nWatson, but I fear that it won't do. I may be on the trail in\nthis matter, or I may be following a will-o'-the-wisp, but I\nshall soon know which it is. I hope that I may be back in a few\nhours.\" He cut a slice of beef from the joint upon the sideboard,\nsandwiched it between two rounds of bread, and thrusting this\nrude meal into his pocket he started off upon his expedition.\n\nI had just finished my tea when he returned, evidently in\nexcellent spirits, swinging an old elastic-sided boot in his\nhand. He chucked it down into a corner and helped himself to a\ncup of tea.\n\n\"I only looked in as I passed,\" said he. \"I am going right on.\"\n\n\"Where to?\"\n\n\"Oh, to the other side of the West End. It may be some time\nbefore I get back. Don't wait up for me in case I should be\nlate.\"\n\n\"How are you getting on?\"\n\n\"Oh, so so. Nothing to complain of. I have been out to Streatham\nsince I saw you last, but I did not call at the house. It is a\nvery sweet little problem, and I would not have missed it for a\ngood deal. However, I must not sit gossiping here, but must get\nthese disreputable clothes off and return to my highly\nrespectable self.\"\n\nI could see by his manner that he had stronger reasons for\nsatisfaction than his words alone would imply. His eyes twinkled,\nand there was even a touch of colour upon his sallow cheeks. He\nhastened upstairs, and a few minutes later I heard the slam of\nthe hall door, which told me that he was off once more upon his\ncongenial hunt.\n\nI waited until midnight, but there was no sign of his return, so\nI retired to my room. It was no uncommon thing for him to be away\nfor days and nights on end when he was hot upon a scent, so that\nhis lateness caused me no surprise. I do not know at what hour he\ncame in, but when I came down to breakfast in the morning there\nhe was with a cup of coffee in one hand and the paper in the\nother, as fresh and trim as possible.\n\n\"You will excuse my beginning without you, Watson,\" said he, \"but\nyou remember that our client has rather an early appointment this\nmorning.\"\n\n\"Why, it is after nine now,\" I answered. \"I should not be\nsurprised if that were he. I thought I heard a ring.\"\n\nIt was, indeed, our friend the financier. I was shocked by the\nchange which had come over him, for his face which was naturally\nof a broad and massive mould, was now pinched and fallen in,\nwhile his hair seemed to me at least a shade whiter. He entered\nwith a weariness and lethargy which was even more painful than\nhis violence of the morning before, and he dropped heavily into\nthe armchair which I pushed forward for him.\n\n\"I do not know what I have done to be so severely tried,\" said\nhe. \"Only two days ago I was a happy and prosperous man, without\na care in the world. Now I am left to a lonely and dishonoured\nage. One sorrow comes close upon the heels of another. My niece,\nMary, has deserted me.\"\n\n\"Deserted you?\"\n\n\"Yes. Her bed this morning had not been slept in, her room was\nempty, and a note for me lay upon the hall table. I had said to\nher last night, in sorrow and not in anger, that if she had\nmarried my boy all might have been well with him. Perhaps it was\nthoughtless of me to say so. It is to that remark that she refers\nin this note:\n\n\"'MY DEAREST UNCLE:--I feel that I have brought trouble upon you,\nand that if I had acted differently this terrible misfortune\nmight never have occurred. I cannot, with this thought in my\nmind, ever again be happy under your roof, and I feel that I must\nleave you forever. Do not worry about my future, for that is\nprovided for; and, above all, do not search for me, for it will\nbe fruitless labour and an ill-service to me. In life or in\ndeath, I am ever your loving,--MARY.'\n\n\"What could she mean by that note, Mr. Holmes? Do you think it\npoints to suicide?\"\n\n\"No, no, nothing of the kind. It is perhaps the best possible\nsolution. I trust, Mr. Holder, that you are nearing the end of\nyour troubles.\"\n\n\"Ha! You say so! You have heard something, Mr. Holmes; you have\nlearned something! Where are the gems?\"\n\n\"You would not think 1000 pounds apiece an excessive sum for\nthem?\"\n\n\"I would pay ten.\"\n\n\"That would be unnecessary. Three thousand will cover the matter.\nAnd there is a little reward, I fancy. Have you your check-book?\nHere is a pen. Better make it out for 4000 pounds.\"\n\nWith a dazed face the banker made out the required check. Holmes\nwalked over to his desk, took out a little triangular piece of\ngold with three gems in it, and threw it down upon the table.\n\nWith a shriek of joy our client clutched it up.\n\n\"You have it!\" he gasped. \"I am saved! I am saved!\"\n\nThe reaction of joy was as passionate as his grief had been, and\nhe hugged his recovered gems to his bosom.\n\n\"There is one other thing you owe, Mr. Holder,\" said Sherlock\nHolmes rather sternly.\n\n\"Owe!\" He caught up a pen. \"Name the sum, and I will pay it.\"\n\n\"No, the debt is not to me. You owe a very humble apology to that\nnoble lad, your son, who has carried himself in this matter as I\nshould be proud to see my own son do, should I ever chance to\nhave one.\"\n\n\"Then it was not Arthur who took them?\"\n\n\"I told you yesterday, and I repeat to-day, that it was not.\"\n\n\"You are sure of it! Then let us hurry to him at once to let him\nknow that the truth is known.\"\n\n\"He knows it already. When I had cleared it all up I had an\ninterview with him, and finding that he would not tell me the\nstory, I told it to him, on which he had to confess that I was\nright and to add the very few details which were not yet quite\nclear to me. Your news of this morning, however, may open his\nlips.\"\n\n\"For heaven's sake, tell me, then, what is this extraordinary\nmystery!\"\n\n\"I will do so, and I will show you the steps by which I reached\nit. And let me say to you, first, that which it is hardest for me\nto say and for you to hear: there has been an understanding\nbetween Sir George Burnwell and your niece Mary. They have now\nfled together.\"\n\n\"My Mary? Impossible!\"\n\n\"It is unfortunately more than possible; it is certain. Neither\nyou nor your son knew the true character of this man when you\nadmitted him into your family circle. He is one of the most\ndangerous men in England--a ruined gambler, an absolutely\ndesperate villain, a man without heart or conscience. Your niece\nknew nothing of such men. When he breathed his vows to her, as he\nhad done to a hundred before her, she flattered herself that she\nalone had touched his heart. The devil knows best what he said,\nbut at least she became his tool and was in the habit of seeing\nhim nearly every evening.\"\n\n\"I cannot, and I will not, believe it!\" cried the banker with an\nashen face.\n\n\"I will tell you, then, what occurred in your house last night.\nYour niece, when you had, as she thought, gone to your room,\nslipped down and talked to her lover through the window which\nleads into the stable lane. His footmarks had pressed right\nthrough the snow, so long had he stood there. She told him of the\ncoronet. His wicked lust for gold kindled at the news, and he\nbent her to his will. I have no doubt that she loved you, but\nthere are women in whom the love of a lover extinguishes all\nother loves, and I think that she must have been one. She had\nhardly listened to his instructions when she saw you coming\ndownstairs, on which she closed the window rapidly and told you\nabout one of the servants' escapade with her wooden-legged lover,\nwhich was all perfectly true.\n\n\"Your boy, Arthur, went to bed after his interview with you but\nhe slept badly on account of his uneasiness about his club debts.\nIn the middle of the night he heard a soft tread pass his door,\nso he rose and, looking out, was surprised to see his cousin\nwalking very stealthily along the passage until she disappeared\ninto your dressing-room. Petrified with astonishment, the lad\nslipped on some clothes and waited there in the dark to see what\nwould come of this strange affair. Presently she emerged from the\nroom again, and in the light of the passage-lamp your son saw\nthat she carried the precious coronet in her hands. She passed\ndown the stairs, and he, thrilling with horror, ran along and\nslipped behind the curtain near your door, whence he could see\nwhat passed in the hall beneath. He saw her stealthily open the\nwindow, hand out the coronet to someone in the gloom, and then\nclosing it once more hurry back to her room, passing quite close\nto where he stood hid behind the curtain.\n\n\"As long as she was on the scene he could not take any action\nwithout a horrible exposure of the woman whom he loved. But the\ninstant that she was gone he realised how crushing a misfortune\nthis would be for you, and how all-important it was to set it\nright. He rushed down, just as he was, in his bare feet, opened\nthe window, sprang out into the snow, and ran down the lane,\nwhere he could see a dark figure in the moonlight. Sir George\nBurnwell tried to get away, but Arthur caught him, and there was\na struggle between them, your lad tugging at one side of the\ncoronet, and his opponent at the other. In the scuffle, your son\nstruck Sir George and cut him over the eye. Then something\nsuddenly snapped, and your son, finding that he had the coronet\nin his hands, rushed back, closed the window, ascended to your\nroom, and had just observed that the coronet had been twisted in\nthe struggle and was endeavouring to straighten it when you\nappeared upon the scene.\"\n\n\"Is it possible?\" gasped the banker.\n\n\"You then roused his anger by calling him names at a moment when\nhe felt that he had deserved your warmest thanks. He could not\nexplain the true state of affairs without betraying one who\ncertainly deserved little enough consideration at his hands. He\ntook the more chivalrous view, however, and preserved her\nsecret.\"\n\n\"And that was why she shrieked and fainted when she saw the\ncoronet,\" cried Mr. Holder. \"Oh, my God! what a blind fool I have\nbeen! And his asking to be allowed to go out for five minutes!\nThe dear fellow wanted to see if the missing piece were at the\nscene of the struggle. How cruelly I have misjudged him!\"\n\n\"When I arrived at the house,\" continued Holmes, \"I at once went\nvery carefully round it to observe if there were any traces in\nthe snow which might help me. I knew that none had fallen since\nthe evening before, and also that there had been a strong frost\nto preserve impressions. I passed along the tradesmen's path, but\nfound it all trampled down and indistinguishable. Just beyond it,\nhowever, at the far side of the kitchen door, a woman had stood\nand talked with a man, whose round impressions on one side showed\nthat he had a wooden leg. I could even tell that they had been\ndisturbed, for the woman had run back swiftly to the door, as was\nshown by the deep toe and light heel marks, while Wooden-leg had\nwaited a little, and then had gone away. I thought at the time\nthat this might be the maid and her sweetheart, of whom you had\nalready spoken to me, and inquiry showed it was so. I passed\nround the garden without seeing anything more than random tracks,\nwhich I took to be the police; but when I got into the stable\nlane a very long and complex story was written in the snow in\nfront of me.\n\n\"There was a double line of tracks of a booted man, and a second\ndouble line which I saw with delight belonged to a man with naked\nfeet. I was at once convinced from what you had told me that the\nlatter was your son. The first had walked both ways, but the\nother had run swiftly, and as his tread was marked in places over\nthe depression of the boot, it was obvious that he had passed\nafter the other. I followed them up and found they led to the\nhall window, where Boots had worn all the snow away while\nwaiting. Then I walked to the other end, which was a hundred\nyards or more down the lane. I saw where Boots had faced round,\nwhere the snow was cut up as though there had been a struggle,\nand, finally, where a few drops of blood had fallen, to show me\nthat I was not mistaken. Boots had then run down the lane, and\nanother little smudge of blood showed that it was he who had been\nhurt. When he came to the highroad at the other end, I found that\nthe pavement had been cleared, so there was an end to that clue.\n\n\"On entering the house, however, I examined, as you remember, the\nsill and framework of the hall window with my lens, and I could\nat once see that someone had passed out. I could distinguish the\noutline of an instep where the wet foot had been placed in coming\nin. I was then beginning to be able to form an opinion as to what\nhad occurred. A man had waited outside the window; someone had\nbrought the gems; the deed had been overseen by your son; he had\npursued the thief; had struggled with him; they had each tugged\nat the coronet, their united strength causing injuries which\nneither alone could have effected. He had returned with the\nprize, but had left a fragment in the grasp of his opponent. So\nfar I was clear. The question now was, who was the man and who\nwas it brought him the coronet?\n\n\"It is an old maxim of mine that when you have excluded the\nimpossible, whatever remains, however improbable, must be the\ntruth. Now, I knew that it was not you who had brought it down,\nso there only remained your niece and the maids. But if it were\nthe maids, why should your son allow himself to be accused in\ntheir place? There could be no possible reason. As he loved his\ncousin, however, there was an excellent explanation why he should\nretain her secret--the more so as the secret was a disgraceful\none. When I remembered that you had seen her at that window, and\nhow she had fainted on seeing the coronet again, my conjecture\nbecame a certainty.\n\n\"And who could it be who was her confederate? A lover evidently,\nfor who else could outweigh the love and gratitude which she must\nfeel to you? I knew that you went out little, and that your\ncircle of friends was a very limited one. But among them was Sir\nGeorge Burnwell. I had heard of him before as being a man of evil\nreputation among women. It must have been he who wore those boots\nand retained the missing gems. Even though he knew that Arthur\nhad discovered him, he might still flatter himself that he was\nsafe, for the lad could not say a word without compromising his\nown family.\n\n\"Well, your own good sense will suggest what measures I took\nnext. I went in the shape of a loafer to Sir George's house,\nmanaged to pick up an acquaintance with his valet, learned that\nhis master had cut his head the night before, and, finally, at\nthe expense of six shillings, made all sure by buying a pair of\nhis cast-off shoes. With these I journeyed down to Streatham and\nsaw that they exactly fitted the tracks.\"\n\n\"I saw an ill-dressed vagabond in the lane yesterday evening,\"\nsaid Mr. Holder.\n\n\"Precisely. It was I. I found that I had my man, so I came home\nand changed my clothes. It was a delicate part which I had to\nplay then, for I saw that a prosecution must be avoided to avert\nscandal, and I knew that so astute a villain would see that our\nhands were tied in the matter. I went and saw him. At first, of\ncourse, he denied everything. But when I gave him every\nparticular that had occurred, he tried to bluster and took down a\nlife-preserver from the wall. I knew my man, however, and I\nclapped a pistol to his head before he could strike. Then he\nbecame a little more reasonable. I told him that we would give\nhim a price for the stones he held--1000 pounds apiece. That\nbrought out the first signs of grief that he had shown. 'Why,\ndash it all!' said he, 'I've let them go at six hundred for the\nthree!' I soon managed to get the address of the receiver who had\nthem, on promising him that there would be no prosecution. Off I\nset to him, and after much chaffering I got our stones at 1000\npounds apiece. Then I looked in upon your son, told him that all\nwas right, and eventually got to my bed about two o'clock, after\nwhat I may call a really hard day's work.\"\n\n\"A day which has saved England from a great public scandal,\" said\nthe banker, rising. \"Sir, I cannot find words to thank you, but\nyou shall not find me ungrateful for what you have done. Your\nskill has indeed exceeded all that I have heard of it. And now I\nmust fly to my dear boy to apologise to him for the wrong which I\nhave done him. As to what you tell me of poor Mary, it goes to my\nvery heart. Not even your skill can inform me where she is now.\"\n\n\"I think that we may safely say,\" returned Holmes, \"that she is\nwherever Sir George Burnwell is. It is equally certain, too, that\nwhatever her sins are, they will soon receive a more than\nsufficient punishment.\"\n\n\n\nXII. THE ADVENTURE OF THE COPPER BEECHES\n\n\"To the man who loves art for its own sake,\" remarked Sherlock\nHolmes, tossing aside the advertisement sheet of the Daily\nTelegraph, \"it is frequently in its least important and lowliest\nmanifestations that the keenest pleasure is to be derived. It is\npleasant to me to observe, Watson, that you have so far grasped\nthis truth that in these little records of our cases which you\nhave been good enough to draw up, and, I am bound to say,\noccasionally to embellish, you have given prominence not so much\nto the many causes célèbres and sensational trials in which I\nhave figured but rather to those incidents which may have been\ntrivial in themselves, but which have given room for those\nfaculties of deduction and of logical synthesis which I have made\nmy special province.\"\n\n\"And yet,\" said I, smiling, \"I cannot quite hold myself absolved\nfrom the charge of sensationalism which has been urged against my\nrecords.\"\n\n\"You have erred, perhaps,\" he observed, taking up a glowing\ncinder with the tongs and lighting with it the long cherry-wood\npipe which was wont to replace his clay when he was in a\ndisputatious rather than a meditative mood--\"you have erred\nperhaps in attempting to put colour and life into each of your\nstatements instead of confining yourself to the task of placing\nupon record that severe reasoning from cause to effect which is\nreally the only notable feature about the thing.\"\n\n\"It seems to me that I have done you full justice in the matter,\"\nI remarked with some coldness, for I was repelled by the egotism\nwhich I had more than once observed to be a strong factor in my\nfriend's singular character.\n\n\"No, it is not selfishness or conceit,\" said he, answering, as\nwas his wont, my thoughts rather than my words. \"If I claim full\njustice for my art, it is because it is an impersonal thing--a\nthing beyond myself. Crime is common. Logic is rare. Therefore it\nis upon the logic rather than upon the crime that you should\ndwell. You have degraded what should have been a course of\nlectures into a series of tales.\"\n\nIt was a cold morning of the early spring, and we sat after\nbreakfast on either side of a cheery fire in the old room at\nBaker Street. A thick fog rolled down between the lines of\ndun-coloured houses, and the opposing windows loomed like dark,\nshapeless blurs through the heavy yellow wreaths. Our gas was lit\nand shone on the white cloth and glimmer of china and metal, for\nthe table had not been cleared yet. Sherlock Holmes had been\nsilent all the morning, dipping continuously into the\nadvertisement columns of a succession of papers until at last,\nhaving apparently given up his search, he had emerged in no very\nsweet temper to lecture me upon my literary shortcomings.\n\n\"At the same time,\" he remarked after a pause, during which he\nhad sat puffing at his long pipe and gazing down into the fire,\n\"you can hardly be open to a charge of sensationalism, for out of\nthese cases which you have been so kind as to interest yourself\nin, a fair proportion do not treat of crime, in its legal sense,\nat all. The small matter in which I endeavoured to help the King\nof Bohemia, the singular experience of Miss Mary Sutherland, the\nproblem connected with the man with the twisted lip, and the\nincident of the noble bachelor, were all matters which are\noutside the pale of the law. But in avoiding the sensational, I\nfear that you may have bordered on the trivial.\"\n\n\"The end may have been so,\" I answered, \"but the methods I hold\nto have been novel and of interest.\"\n\n\"Pshaw, my dear fellow, what do the public, the great unobservant\npublic, who could hardly tell a weaver by his tooth or a\ncompositor by his left thumb, care about the finer shades of\nanalysis and deduction! But, indeed, if you are trivial, I cannot\nblame you, for the days of the great cases are past. Man, or at\nleast criminal man, has lost all enterprise and originality. As\nto my own little practice, it seems to be degenerating into an\nagency for recovering lost lead pencils and giving advice to\nyoung ladies from boarding-schools. I think that I have touched\nbottom at last, however. This note I had this morning marks my\nzero-point, I fancy. Read it!\" He tossed a crumpled letter across\nto me.\n\nIt was dated from Montague Place upon the preceding evening, and\nran thus:\n\n\"DEAR MR. HOLMES:--I am very anxious to consult you as to whether\nI should or should not accept a situation which has been offered\nto me as governess. I shall call at half-past ten to-morrow if I\ndo not inconvenience you. Yours faithfully,\n                                               \"VIOLET HUNTER.\"\n\n\"Do you know the young lady?\" I asked.\n\n\"Not I.\"\n\n\"It is half-past ten now.\"\n\n\"Yes, and I have no doubt that is her ring.\"\n\n\"It may turn out to be of more interest than you think. You\nremember that the affair of the blue carbuncle, which appeared to\nbe a mere whim at first, developed into a serious investigation.\nIt may be so in this case, also.\"\n\n\"Well, let us hope so. But our doubts will very soon be solved,\nfor here, unless I am much mistaken, is the person in question.\"\n\nAs he spoke the door opened and a young lady entered the room.\nShe was plainly but neatly dressed, with a bright, quick face,\nfreckled like a plover's egg, and with the brisk manner of a\nwoman who has had her own way to make in the world.\n\n\"You will excuse my troubling you, I am sure,\" said she, as my\ncompanion rose to greet her, \"but I have had a very strange\nexperience, and as I have no parents or relations of any sort\nfrom whom I could ask advice, I thought that perhaps you would be\nkind enough to tell me what I should do.\"\n\n\"Pray take a seat, Miss Hunter. I shall be happy to do anything\nthat I can to serve you.\"\n\nI could see that Holmes was favourably impressed by the manner\nand speech of his new client. He looked her over in his searching\nfashion, and then composed himself, with his lids drooping and\nhis finger-tips together, to listen to her story.\n\n\"I have been a governess for five years,\" said she, \"in the\nfamily of Colonel Spence Munro, but two months ago the colonel\nreceived an appointment at Halifax, in Nova Scotia, and took his\nchildren over to America with him, so that I found myself without\na situation. I advertised, and I answered advertisements, but\nwithout success. At last the little money which I had saved began\nto run short, and I was at my wit's end as to what I should do.\n\n\"There is a well-known agency for governesses in the West End\ncalled Westaway's, and there I used to call about once a week in\norder to see whether anything had turned up which might suit me.\nWestaway was the name of the founder of the business, but it is\nreally managed by Miss Stoper. She sits in her own little office,\nand the ladies who are seeking employment wait in an anteroom,\nand are then shown in one by one, when she consults her ledgers\nand sees whether she has anything which would suit them.\n\n\"Well, when I called last week I was shown into the little office\nas usual, but I found that Miss Stoper was not alone. A\nprodigiously stout man with a very smiling face and a great heavy\nchin which rolled down in fold upon fold over his throat sat at\nher elbow with a pair of glasses on his nose, looking very\nearnestly at the ladies who entered. As I came in he gave quite a\njump in his chair and turned quickly to Miss Stoper.\n\n\"'That will do,' said he; 'I could not ask for anything better.\nCapital! capital!' He seemed quite enthusiastic and rubbed his\nhands together in the most genial fashion. He was such a\ncomfortable-looking man that it was quite a pleasure to look at\nhim.\n\n\"'You are looking for a situation, miss?' he asked.\n\n\"'Yes, sir.'\n\n\"'As governess?'\n\n\"'Yes, sir.'\n\n\"'And what salary do you ask?'\n\n\"'I had 4 pounds a month in my last place with Colonel Spence\nMunro.'\n\n\"'Oh, tut, tut! sweating--rank sweating!' he cried, throwing his\nfat hands out into the air like a man who is in a boiling\npassion. 'How could anyone offer so pitiful a sum to a lady with\nsuch attractions and accomplishments?'\n\n\"'My accomplishments, sir, may be less than you imagine,' said I.\n'A little French, a little German, music, and drawing--'\n\n\"'Tut, tut!' he cried. 'This is all quite beside the question.\nThe point is, have you or have you not the bearing and deportment\nof a lady? There it is in a nutshell. If you have not, you are\nnot fitted for the rearing of a child who may some day play a\nconsiderable part in the history of the country. But if you have\nwhy, then, how could any gentleman ask you to condescend to\naccept anything under the three figures? Your salary with me,\nmadam, would commence at 100 pounds a year.'\n\n\"You may imagine, Mr. Holmes, that to me, destitute as I was,\nsuch an offer seemed almost too good to be true. The gentleman,\nhowever, seeing perhaps the look of incredulity upon my face,\nopened a pocket-book and took out a note.\n\n\"'It is also my custom,' said he, smiling in the most pleasant\nfashion until his eyes were just two little shining slits amid\nthe white creases of his face, 'to advance to my young ladies\nhalf their salary beforehand, so that they may meet any little\nexpenses of their journey and their wardrobe.'\n\n\"It seemed to me that I had never met so fascinating and so\nthoughtful a man. As I was already in debt to my tradesmen, the\nadvance was a great convenience, and yet there was something\nunnatural about the whole transaction which made me wish to know\na little more before I quite committed myself.\n\n\"'May I ask where you live, sir?' said I.\n\n\"'Hampshire. Charming rural place. The Copper Beeches, five miles\non the far side of Winchester. It is the most lovely country, my\ndear young lady, and the dearest old country-house.'\n\n\"'And my duties, sir? I should be glad to know what they would\nbe.'\n\n\"'One child--one dear little romper just six years old. Oh, if\nyou could see him killing cockroaches with a slipper! Smack!\nsmack! smack! Three gone before you could wink!' He leaned back\nin his chair and laughed his eyes into his head again.\n\n\"I was a little startled at the nature of the child's amusement,\nbut the father's laughter made me think that perhaps he was\njoking.\n\n\"'My sole duties, then,' I asked, 'are to take charge of a single\nchild?'\n\n\"'No, no, not the sole, not the sole, my dear young lady,' he\ncried. 'Your duty would be, as I am sure your good sense would\nsuggest, to obey any little commands my wife might give, provided\nalways that they were such commands as a lady might with\npropriety obey. You see no difficulty, heh?'\n\n\"'I should be happy to make myself useful.'\n\n\"'Quite so. In dress now, for example. We are faddy people, you\nknow--faddy but kind-hearted. If you were asked to wear any dress\nwhich we might give you, you would not object to our little whim.\nHeh?'\n\n\"'No,' said I, considerably astonished at his words.\n\n\"'Or to sit here, or sit there, that would not be offensive to\nyou?'\n\n\"'Oh, no.'\n\n\"'Or to cut your hair quite short before you come to us?'\n\n\"I could hardly believe my ears. As you may observe, Mr. Holmes,\nmy hair is somewhat luxuriant, and of a rather peculiar tint of\nchestnut. It has been considered artistic. I could not dream of\nsacrificing it in this offhand fashion.\n\n\"'I am afraid that that is quite impossible,' said I. He had been\nwatching me eagerly out of his small eyes, and I could see a\nshadow pass over his face as I spoke.\n\n\"'I am afraid that it is quite essential,' said he. 'It is a\nlittle fancy of my wife's, and ladies' fancies, you know, madam,\nladies' fancies must be consulted. And so you won't cut your\nhair?'\n\n\"'No, sir, I really could not,' I answered firmly.\n\n\"'Ah, very well; then that quite settles the matter. It is a\npity, because in other respects you would really have done very\nnicely. In that case, Miss Stoper, I had best inspect a few more\nof your young ladies.'\n\n\"The manageress had sat all this while busy with her papers\nwithout a word to either of us, but she glanced at me now with so\nmuch annoyance upon her face that I could not help suspecting\nthat she had lost a handsome commission through my refusal.\n\n\"'Do you desire your name to be kept upon the books?' she asked.\n\n\"'If you please, Miss Stoper.'\n\n\"'Well, really, it seems rather useless, since you refuse the\nmost excellent offers in this fashion,' said she sharply. 'You\ncan hardly expect us to exert ourselves to find another such\nopening for you. Good-day to you, Miss Hunter.' She struck a gong\nupon the table, and I was shown out by the page.\n\n\"Well, Mr. Holmes, when I got back to my lodgings and found\nlittle enough in the cupboard, and two or three bills upon the\ntable, I began to ask myself whether I had not done a very\nfoolish thing. After all, if these people had strange fads and\nexpected obedience on the most extraordinary matters, they were\nat least ready to pay for their eccentricity. Very few\ngovernesses in England are getting 100 pounds a year. Besides,\nwhat use was my hair to me? Many people are improved by wearing\nit short and perhaps I should be among the number. Next day I was\ninclined to think that I had made a mistake, and by the day after\nI was sure of it. I had almost overcome my pride so far as to go\nback to the agency and inquire whether the place was still open\nwhen I received this letter from the gentleman himself. I have it\nhere and I will read it to you:\n\n                       \"'The Copper Beeches, near Winchester.\n\"'DEAR MISS HUNTER:--Miss Stoper has very kindly given me your\naddress, and I write from here to ask you whether you have\nreconsidered your decision. My wife is very anxious that you\nshould come, for she has been much attracted by my description of\nyou. We are willing to give 30 pounds a quarter, or 120 pounds a\nyear, so as to recompense you for any little inconvenience which\nour fads may cause you. They are not very exacting, after all. My\nwife is fond of a particular shade of electric blue and would\nlike you to wear such a dress indoors in the morning. You need\nnot, however, go to the expense of purchasing one, as we have one\nbelonging to my dear daughter Alice (now in Philadelphia), which\nwould, I should think, fit you very well. Then, as to sitting\nhere or there, or amusing yourself in any manner indicated, that\nneed cause you no inconvenience. As regards your hair, it is no\ndoubt a pity, especially as I could not help remarking its beauty\nduring our short interview, but I am afraid that I must remain\nfirm upon this point, and I only hope that the increased salary\nmay recompense you for the loss. Your duties, as far as the child\nis concerned, are very light. Now do try to come, and I shall\nmeet you with the dog-cart at Winchester. Let me know your train.\nYours faithfully, JEPHRO RUCASTLE.'\n\n\"That is the letter which I have just received, Mr. Holmes, and\nmy mind is made up that I will accept it. I thought, however,\nthat before taking the final step I should like to submit the\nwhole matter to your consideration.\"\n\n\"Well, Miss Hunter, if your mind is made up, that settles the\nquestion,\" said Holmes, smiling.\n\n\"But you would not advise me to refuse?\"\n\n\"I confess that it is not the situation which I should like to\nsee a sister of mine apply for.\"\n\n\"What is the meaning of it all, Mr. Holmes?\"\n\n\"Ah, I have no data. I cannot tell. Perhaps you have yourself\nformed some opinion?\"\n\n\"Well, there seems to me to be only one possible solution. Mr.\nRucastle seemed to be a very kind, good-natured man. Is it not\npossible that his wife is a lunatic, that he desires to keep the\nmatter quiet for fear she should be taken to an asylum, and that\nhe humours her fancies in every way in order to prevent an\noutbreak?\"\n\n\"That is a possible solution--in fact, as matters stand, it is\nthe most probable one. But in any case it does not seem to be a\nnice household for a young lady.\"\n\n\"But the money, Mr. Holmes, the money!\"\n\n\"Well, yes, of course the pay is good--too good. That is what\nmakes me uneasy. Why should they give you 120 pounds a year, when\nthey could have their pick for 40 pounds? There must be some\nstrong reason behind.\"\n\n\"I thought that if I told you the circumstances you would\nunderstand afterwards if I wanted your help. I should feel so\nmuch stronger if I felt that you were at the back of me.\"\n\n\"Oh, you may carry that feeling away with you. I assure you that\nyour little problem promises to be the most interesting which has\ncome my way for some months. There is something distinctly novel\nabout some of the features. If you should find yourself in doubt\nor in danger--\"\n\n\"Danger! What danger do you foresee?\"\n\nHolmes shook his head gravely. \"It would cease to be a danger if\nwe could define it,\" said he. \"But at any time, day or night, a\ntelegram would bring me down to your help.\"\n\n\"That is enough.\" She rose briskly from her chair with the\nanxiety all swept from her face. \"I shall go down to Hampshire\nquite easy in my mind now. I shall write to Mr. Rucastle at once,\nsacrifice my poor hair to-night, and start for Winchester\nto-morrow.\" With a few grateful words to Holmes she bade us both\ngood-night and bustled off upon her way.\n\n\"At least,\" said I as we heard her quick, firm steps descending\nthe stairs, \"she seems to be a young lady who is very well able\nto take care of herself.\"\n\n\"And she would need to be,\" said Holmes gravely. \"I am much\nmistaken if we do not hear from her before many days are past.\"\n\nIt was not very long before my friend's prediction was fulfilled.\nA fortnight went by, during which I frequently found my thoughts\nturning in her direction and wondering what strange side-alley of\nhuman experience this lonely woman had strayed into. The unusual\nsalary, the curious conditions, the light duties, all pointed to\nsomething abnormal, though whether a fad or a plot, or whether\nthe man were a philanthropist or a villain, it was quite beyond\nmy powers to determine. As to Holmes, I observed that he sat\nfrequently for half an hour on end, with knitted brows and an\nabstracted air, but he swept the matter away with a wave of his\nhand when I mentioned it. \"Data! data! data!\" he cried\nimpatiently. \"I can't make bricks without clay.\" And yet he would\nalways wind up by muttering that no sister of his should ever\nhave accepted such a situation.\n\nThe telegram which we eventually received came late one night\njust as I was thinking of turning in and Holmes was settling down\nto one of those all-night chemical researches which he frequently\nindulged in, when I would leave him stooping over a retort and a\ntest-tube at night and find him in the same position when I came\ndown to breakfast in the morning. He opened the yellow envelope,\nand then, glancing at the message, threw it across to me.\n\n\"Just look up the trains in Bradshaw,\" said he, and turned back\nto his chemical studies.\n\nThe summons was a brief and urgent one.\n\n\"Please be at the Black Swan Hotel at Winchester at midday\nto-morrow,\" it said. \"Do come! I am at my wit's end.  HUNTER.\"\n\n\"Will you come with me?\" asked Holmes, glancing up.\n\n\"I should wish to.\"\n\n\"Just look it up, then.\"\n\n\"There is a train at half-past nine,\" said I, glancing over my\nBradshaw. \"It is due at Winchester at 11:30.\"\n\n\"That will do very nicely. Then perhaps I had better postpone my\nanalysis of the acetones, as we may need to be at our best in the\nmorning.\"\n\nBy eleven o'clock the next day we were well upon our way to the\nold English capital. Holmes had been buried in the morning papers\nall the way down, but after we had passed the Hampshire border he\nthrew them down and began to admire the scenery. It was an ideal\nspring day, a light blue sky, flecked with little fleecy white\nclouds drifting across from west to east. The sun was shining\nvery brightly, and yet there was an exhilarating nip in the air,\nwhich set an edge to a man's energy. All over the countryside,\naway to the rolling hills around Aldershot, the little red and\ngrey roofs of the farm-steadings peeped out from amid the light\ngreen of the new foliage.\n\n\"Are they not fresh and beautiful?\" I cried with all the\nenthusiasm of a man fresh from the fogs of Baker Street.\n\nBut Holmes shook his head gravely.\n\n\"Do you know, Watson,\" said he, \"that it is one of the curses of\na mind with a turn like mine that I must look at everything with\nreference to my own special subject. You look at these scattered\nhouses, and you are impressed by their beauty. I look at them,\nand the only thought which comes to me is a feeling of their\nisolation and of the impunity with which crime may be committed\nthere.\"\n\n\"Good heavens!\" I cried. \"Who would associate crime with these\ndear old homesteads?\"\n\n\"They always fill me with a certain horror. It is my belief,\nWatson, founded upon my experience, that the lowest and vilest\nalleys in London do not present a more dreadful record of sin\nthan does the smiling and beautiful countryside.\"\n\n\"You horrify me!\"\n\n\"But the reason is very obvious. The pressure of public opinion\ncan do in the town what the law cannot accomplish. There is no\nlane so vile that the scream of a tortured child, or the thud of\na drunkard's blow, does not beget sympathy and indignation among\nthe neighbours, and then the whole machinery of justice is ever\nso close that a word of complaint can set it going, and there is\nbut a step between the crime and the dock. But look at these\nlonely houses, each in its own fields, filled for the most part\nwith poor ignorant folk who know little of the law. Think of the\ndeeds of hellish cruelty, the hidden wickedness which may go on,\nyear in, year out, in such places, and none the wiser. Had this\nlady who appeals to us for help gone to live in Winchester, I\nshould never have had a fear for her. It is the five miles of\ncountry which makes the danger. Still, it is clear that she is\nnot personally threatened.\"\n\n\"No. If she can come to Winchester to meet us she can get away.\"\n\n\"Quite so. She has her freedom.\"\n\n\"What CAN be the matter, then? Can you suggest no explanation?\"\n\n\"I have devised seven separate explanations, each of which would\ncover the facts as far as we know them. But which of these is\ncorrect can only be determined by the fresh information which we\nshall no doubt find waiting for us. Well, there is the tower of\nthe cathedral, and we shall soon learn all that Miss Hunter has\nto tell.\"\n\nThe Black Swan is an inn of repute in the High Street, at no\ndistance from the station, and there we found the young lady\nwaiting for us. She had engaged a sitting-room, and our lunch\nawaited us upon the table.\n\n\"I am so delighted that you have come,\" she said earnestly. \"It\nis so very kind of you both; but indeed I do not know what I\nshould do. Your advice will be altogether invaluable to me.\"\n\n\"Pray tell us what has happened to you.\"\n\n\"I will do so, and I must be quick, for I have promised Mr.\nRucastle to be back before three. I got his leave to come into\ntown this morning, though he little knew for what purpose.\"\n\n\"Let us have everything in its due order.\" Holmes thrust his long\nthin legs out towards the fire and composed himself to listen.\n\n\"In the first place, I may say that I have met, on the whole,\nwith no actual ill-treatment from Mr. and Mrs. Rucastle. It is\nonly fair to them to say that. But I cannot understand them, and\nI am not easy in my mind about them.\"\n\n\"What can you not understand?\"\n\n\"Their reasons for their conduct. But you shall have it all just\nas it occurred. When I came down, Mr. Rucastle met me here and\ndrove me in his dog-cart to the Copper Beeches. It is, as he\nsaid, beautifully situated, but it is not beautiful in itself,\nfor it is a large square block of a house, whitewashed, but all\nstained and streaked with damp and bad weather. There are grounds\nround it, woods on three sides, and on the fourth a field which\nslopes down to the Southampton highroad, which curves past about\na hundred yards from the front door. This ground in front belongs\nto the house, but the woods all round are part of Lord\nSoutherton's preserves. A clump of copper beeches immediately in\nfront of the hall door has given its name to the place.\n\n\"I was driven over by my employer, who was as amiable as ever,\nand was introduced by him that evening to his wife and the child.\nThere was no truth, Mr. Holmes, in the conjecture which seemed to\nus to be probable in your rooms at Baker Street. Mrs. Rucastle is\nnot mad. I found her to be a silent, pale-faced woman, much\nyounger than her husband, not more than thirty, I should think,\nwhile he can hardly be less than forty-five. From their\nconversation I have gathered that they have been married about\nseven years, that he was a widower, and that his only child by\nthe first wife was the daughter who has gone to Philadelphia. Mr.\nRucastle told me in private that the reason why she had left them\nwas that she had an unreasoning aversion to her stepmother. As\nthe daughter could not have been less than twenty, I can quite\nimagine that her position must have been uncomfortable with her\nfather's young wife.\n\n\"Mrs. Rucastle seemed to me to be colourless in mind as well as\nin feature. She impressed me neither favourably nor the reverse.\nShe was a nonentity. It was easy to see that she was passionately\ndevoted both to her husband and to her little son. Her light grey\neyes wandered continually from one to the other, noting every\nlittle want and forestalling it if possible. He was kind to her\nalso in his bluff, boisterous fashion, and on the whole they\nseemed to be a happy couple. And yet she had some secret sorrow,\nthis woman. She would often be lost in deep thought, with the\nsaddest look upon her face. More than once I have surprised her\nin tears. I have thought sometimes that it was the disposition of\nher child which weighed upon her mind, for I have never met so\nutterly spoiled and so ill-natured a little creature. He is small\nfor his age, with a head which is quite disproportionately large.\nHis whole life appears to be spent in an alternation between\nsavage fits of passion and gloomy intervals of sulking. Giving\npain to any creature weaker than himself seems to be his one idea\nof amusement, and he shows quite remarkable talent in planning\nthe capture of mice, little birds, and insects. But I would\nrather not talk about the creature, Mr. Holmes, and, indeed, he\nhas little to do with my story.\"\n\n\"I am glad of all details,\" remarked my friend, \"whether they\nseem to you to be relevant or not.\"\n\n\"I shall try not to miss anything of importance. The one\nunpleasant thing about the house, which struck me at once, was\nthe appearance and conduct of the servants. There are only two, a\nman and his wife. Toller, for that is his name, is a rough,\nuncouth man, with grizzled hair and whiskers, and a perpetual\nsmell of drink. Twice since I have been with them he has been\nquite drunk, and yet Mr. Rucastle seemed to take no notice of it.\nHis wife is a very tall and strong woman with a sour face, as\nsilent as Mrs. Rucastle and much less amiable. They are a most\nunpleasant couple, but fortunately I spend most of my time in the\nnursery and my own room, which are next to each other in one\ncorner of the building.\n\n\"For two days after my arrival at the Copper Beeches my life was\nvery quiet; on the third, Mrs. Rucastle came down just after\nbreakfast and whispered something to her husband.\n\n\"'Oh, yes,' said he, turning to me, 'we are very much obliged to\nyou, Miss Hunter, for falling in with our whims so far as to cut\nyour hair. I assure you that it has not detracted in the tiniest\niota from your appearance. We shall now see how the electric-blue\ndress will become you. You will find it laid out upon the bed in\nyour room, and if you would be so good as to put it on we should\nboth be extremely obliged.'\n\n\"The dress which I found waiting for me was of a peculiar shade\nof blue. It was of excellent material, a sort of beige, but it\nbore unmistakable signs of having been worn before. It could not\nhave been a better fit if I had been measured for it. Both Mr.\nand Mrs. Rucastle expressed a delight at the look of it, which\nseemed quite exaggerated in its vehemence. They were waiting for\nme in the drawing-room, which is a very large room, stretching\nalong the entire front of the house, with three long windows\nreaching down to the floor. A chair had been placed close to the\ncentral window, with its back turned towards it. In this I was\nasked to sit, and then Mr. Rucastle, walking up and down on the\nother side of the room, began to tell me a series of the funniest\nstories that I have ever listened to. You cannot imagine how\ncomical he was, and I laughed until I was quite weary. Mrs.\nRucastle, however, who has evidently no sense of humour, never so\nmuch as smiled, but sat with her hands in her lap, and a sad,\nanxious look upon her face. After an hour or so, Mr. Rucastle\nsuddenly remarked that it was time to commence the duties of the\nday, and that I might change my dress and go to little Edward in\nthe nursery.\n\n\"Two days later this same performance was gone through under\nexactly similar circumstances. Again I changed my dress, again I\nsat in the window, and again I laughed very heartily at the funny\nstories of which my employer had an immense répertoire, and which\nhe told inimitably. Then he handed me a yellow-backed novel, and\nmoving my chair a little sideways, that my own shadow might not\nfall upon the page, he begged me to read aloud to him. I read for\nabout ten minutes, beginning in the heart of a chapter, and then\nsuddenly, in the middle of a sentence, he ordered me to cease and\nto change my dress.\n\n\"You can easily imagine, Mr. Holmes, how curious I became as to\nwhat the meaning of this extraordinary performance could possibly\nbe. They were always very careful, I observed, to turn my face\naway from the window, so that I became consumed with the desire\nto see what was going on behind my back. At first it seemed to be\nimpossible, but I soon devised a means. My hand-mirror had been\nbroken, so a happy thought seized me, and I concealed a piece of\nthe glass in my handkerchief. On the next occasion, in the midst\nof my laughter, I put my handkerchief up to my eyes, and was able\nwith a little management to see all that there was behind me. I\nconfess that I was disappointed. There was nothing. At least that\nwas my first impression. At the second glance, however, I\nperceived that there was a man standing in the Southampton Road,\na small bearded man in a grey suit, who seemed to be looking in\nmy direction. The road is an important highway, and there are\nusually people there. This man, however, was leaning against the\nrailings which bordered our field and was looking earnestly up. I\nlowered my handkerchief and glanced at Mrs. Rucastle to find her\neyes fixed upon me with a most searching gaze. She said nothing,\nbut I am convinced that she had divined that I had a mirror in my\nhand and had seen what was behind me. She rose at once.\n\n\"'Jephro,' said she, 'there is an impertinent fellow upon the\nroad there who stares up at Miss Hunter.'\n\n\"'No friend of yours, Miss Hunter?' he asked.\n\n\"'No, I know no one in these parts.'\n\n\"'Dear me! How very impertinent! Kindly turn round and motion to\nhim to go away.'\n\n\"'Surely it would be better to take no notice.'\n\n\"'No, no, we should have him loitering here always. Kindly turn\nround and wave him away like that.'\n\n\"I did as I was told, and at the same instant Mrs. Rucastle drew\ndown the blind. That was a week ago, and from that time I have\nnot sat again in the window, nor have I worn the blue dress, nor\nseen the man in the road.\"\n\n\"Pray continue,\" said Holmes. \"Your narrative promises to be a\nmost interesting one.\"\n\n\"You will find it rather disconnected, I fear, and there may\nprove to be little relation between the different incidents of\nwhich I speak. On the very first day that I was at the Copper\nBeeches, Mr. Rucastle took me to a small outhouse which stands\nnear the kitchen door. As we approached it I heard the sharp\nrattling of a chain, and the sound as of a large animal moving\nabout.\n\n\"'Look in here!' said Mr. Rucastle, showing me a slit between two\nplanks. 'Is he not a beauty?'\n\n\"I looked through and was conscious of two glowing eyes, and of a\nvague figure huddled up in the darkness.\n\n\"'Don't be frightened,' said my employer, laughing at the start\nwhich I had given. 'It's only Carlo, my mastiff. I call him mine,\nbut really old Toller, my groom, is the only man who can do\nanything with him. We feed him once a day, and not too much then,\nso that he is always as keen as mustard. Toller lets him loose\nevery night, and God help the trespasser whom he lays his fangs\nupon. For goodness' sake don't you ever on any pretext set your\nfoot over the threshold at night, for it's as much as your life\nis worth.'\n\n\"The warning was no idle one, for two nights later I happened to\nlook out of my bedroom window about two o'clock in the morning.\nIt was a beautiful moonlight night, and the lawn in front of the\nhouse was silvered over and almost as bright as day. I was\nstanding, rapt in the peaceful beauty of the scene, when I was\naware that something was moving under the shadow of the copper\nbeeches. As it emerged into the moonshine I saw what it was. It\nwas a giant dog, as large as a calf, tawny tinted, with hanging\njowl, black muzzle, and huge projecting bones. It walked slowly\nacross the lawn and vanished into the shadow upon the other side.\nThat dreadful sentinel sent a chill to my heart which I do not\nthink that any burglar could have done.\n\n\"And now I have a very strange experience to tell you. I had, as\nyou know, cut off my hair in London, and I had placed it in a\ngreat coil at the bottom of my trunk. One evening, after the\nchild was in bed, I began to amuse myself by examining the\nfurniture of my room and by rearranging my own little things.\nThere was an old chest of drawers in the room, the two upper ones\nempty and open, the lower one locked. I had filled the first two\nwith my linen, and as I had still much to pack away I was\nnaturally annoyed at not having the use of the third drawer. It\nstruck me that it might have been fastened by a mere oversight,\nso I took out my bunch of keys and tried to open it. The very\nfirst key fitted to perfection, and I drew the drawer open. There\nwas only one thing in it, but I am sure that you would never\nguess what it was. It was my coil of hair.\n\n\"I took it up and examined it. It was of the same peculiar tint,\nand the same thickness. But then the impossibility of the thing\nobtruded itself upon me. How could my hair have been locked in\nthe drawer? With trembling hands I undid my trunk, turned out the\ncontents, and drew from the bottom my own hair. I laid the two\ntresses together, and I assure you that they were identical. Was\nit not extraordinary? Puzzle as I would, I could make nothing at\nall of what it meant. I returned the strange hair to the drawer,\nand I said nothing of the matter to the Rucastles as I felt that\nI had put myself in the wrong by opening a drawer which they had\nlocked.\n\n\"I am naturally observant, as you may have remarked, Mr. Holmes,\nand I soon had a pretty good plan of the whole house in my head.\nThere was one wing, however, which appeared not to be inhabited\nat all. A door which faced that which led into the quarters of\nthe Tollers opened into this suite, but it was invariably locked.\nOne day, however, as I ascended the stair, I met Mr. Rucastle\ncoming out through this door, his keys in his hand, and a look on\nhis face which made him a very different person to the round,\njovial man to whom I was accustomed. His cheeks were red, his\nbrow was all crinkled with anger, and the veins stood out at his\ntemples with passion. He locked the door and hurried past me\nwithout a word or a look.\n\n\"This aroused my curiosity, so when I went out for a walk in the\ngrounds with my charge, I strolled round to the side from which I\ncould see the windows of this part of the house. There were four\nof them in a row, three of which were simply dirty, while the\nfourth was shuttered up. They were evidently all deserted. As I\nstrolled up and down, glancing at them occasionally, Mr. Rucastle\ncame out to me, looking as merry and jovial as ever.\n\n\"'Ah!' said he, 'you must not think me rude if I passed you\nwithout a word, my dear young lady. I was preoccupied with\nbusiness matters.'\n\n\"I assured him that I was not offended. 'By the way,' said I,\n'you seem to have quite a suite of spare rooms up there, and one\nof them has the shutters up.'\n\n\"He looked surprised and, as it seemed to me, a little startled\nat my remark.\n\n\"'Photography is one of my hobbies,' said he. 'I have made my\ndark room up there. But, dear me! what an observant young lady we\nhave come upon. Who would have believed it? Who would have ever\nbelieved it?' He spoke in a jesting tone, but there was no jest\nin his eyes as he looked at me. I read suspicion there and\nannoyance, but no jest.\n\n\"Well, Mr. Holmes, from the moment that I understood that there\nwas something about that suite of rooms which I was not to know,\nI was all on fire to go over them. It was not mere curiosity,\nthough I have my share of that. It was more a feeling of duty--a\nfeeling that some good might come from my penetrating to this\nplace. They talk of woman's instinct; perhaps it was woman's\ninstinct which gave me that feeling. At any rate, it was there,\nand I was keenly on the lookout for any chance to pass the\nforbidden door.\n\n\"It was only yesterday that the chance came. I may tell you that,\nbesides Mr. Rucastle, both Toller and his wife find something to\ndo in these deserted rooms, and I once saw him carrying a large\nblack linen bag with him through the door. Recently he has been\ndrinking hard, and yesterday evening he was very drunk; and when\nI came upstairs there was the key in the door. I have no doubt at\nall that he had left it there. Mr. and Mrs. Rucastle were both\ndownstairs, and the child was with them, so that I had an\nadmirable opportunity. I turned the key gently in the lock,\nopened the door, and slipped through.\n\n\"There was a little passage in front of me, unpapered and\nuncarpeted, which turned at a right angle at the farther end.\nRound this corner were three doors in a line, the first and third\nof which were open. They each led into an empty room, dusty and\ncheerless, with two windows in the one and one in the other, so\nthick with dirt that the evening light glimmered dimly through\nthem. The centre door was closed, and across the outside of it\nhad been fastened one of the broad bars of an iron bed, padlocked\nat one end to a ring in the wall, and fastened at the other with\nstout cord. The door itself was locked as well, and the key was\nnot there. This barricaded door corresponded clearly with the\nshuttered window outside, and yet I could see by the glimmer from\nbeneath it that the room was not in darkness. Evidently there was\na skylight which let in light from above. As I stood in the\npassage gazing at the sinister door and wondering what secret it\nmight veil, I suddenly heard the sound of steps within the room\nand saw a shadow pass backward and forward against the little\nslit of dim light which shone out from under the door. A mad,\nunreasoning terror rose up in me at the sight, Mr. Holmes. My\noverstrung nerves failed me suddenly, and I turned and ran--ran\nas though some dreadful hand were behind me clutching at the\nskirt of my dress. I rushed down the passage, through the door,\nand straight into the arms of Mr. Rucastle, who was waiting\noutside.\n\n\"'So,' said he, smiling, 'it was you, then. I thought that it\nmust be when I saw the door open.'\n\n\"'Oh, I am so frightened!' I panted.\n\n\"'My dear young lady! my dear young lady!'--you cannot think how\ncaressing and soothing his manner was--'and what has frightened\nyou, my dear young lady?'\n\n\"But his voice was just a little too coaxing. He overdid it. I\nwas keenly on my guard against him.\n\n\"'I was foolish enough to go into the empty wing,' I answered.\n'But it is so lonely and eerie in this dim light that I was\nfrightened and ran out again. Oh, it is so dreadfully still in\nthere!'\n\n\"'Only that?' said he, looking at me keenly.\n\n\"'Why, what did you think?' I asked.\n\n\"'Why do you think that I lock this door?'\n\n\"'I am sure that I do not know.'\n\n\"'It is to keep people out who have no business there. Do you\nsee?' He was still smiling in the most amiable manner.\n\n\"'I am sure if I had known--'\n\n\"'Well, then, you know now. And if you ever put your foot over\nthat threshold again'--here in an instant the smile hardened into\na grin of rage, and he glared down at me with the face of a\ndemon--'I'll throw you to the mastiff.'\n\n\"I was so terrified that I do not know what I did. I suppose that\nI must have rushed past him into my room. I remember nothing\nuntil I found myself lying on my bed trembling all over. Then I\nthought of you, Mr. Holmes. I could not live there longer without\nsome advice. I was frightened of the house, of the man, of the\nwoman, of the servants, even of the child. They were all horrible\nto me. If I could only bring you down all would be well. Of\ncourse I might have fled from the house, but my curiosity was\nalmost as strong as my fears. My mind was soon made up. I would\nsend you a wire. I put on my hat and cloak, went down to the\noffice, which is about half a mile from the house, and then\nreturned, feeling very much easier. A horrible doubt came into my\nmind as I approached the door lest the dog might be loose, but I\nremembered that Toller had drunk himself into a state of\ninsensibility that evening, and I knew that he was the only one\nin the household who had any influence with the savage creature,\nor who would venture to set him free. I slipped in in safety and\nlay awake half the night in my joy at the thought of seeing you.\nI had no difficulty in getting leave to come into Winchester this\nmorning, but I must be back before three o'clock, for Mr. and\nMrs. Rucastle are going on a visit, and will be away all the\nevening, so that I must look after the child. Now I have told you\nall my adventures, Mr. Holmes, and I should be very glad if you\ncould tell me what it all means, and, above all, what I should\ndo.\"\n\nHolmes and I had listened spellbound to this extraordinary story.\nMy friend rose now and paced up and down the room, his hands in\nhis pockets, and an expression of the most profound gravity upon\nhis face.\n\n\"Is Toller still drunk?\" he asked.\n\n\"Yes. I heard his wife tell Mrs. Rucastle that she could do\nnothing with him.\"\n\n\"That is well. And the Rucastles go out to-night?\"\n\n\"Yes.\"\n\n\"Is there a cellar with a good strong lock?\"\n\n\"Yes, the wine-cellar.\"\n\n\"You seem to me to have acted all through this matter like a very\nbrave and sensible girl, Miss Hunter. Do you think that you could\nperform one more feat? I should not ask it of you if I did not\nthink you a quite exceptional woman.\"\n\n\"I will try. What is it?\"\n\n\"We shall be at the Copper Beeches by seven o'clock, my friend\nand I. The Rucastles will be gone by that time, and Toller will,\nwe hope, be incapable. There only remains Mrs. Toller, who might\ngive the alarm. If you could send her into the cellar on some\nerrand, and then turn the key upon her, you would facilitate\nmatters immensely.\"\n\n\"I will do it.\"\n\n\"Excellent! We shall then look thoroughly into the affair. Of\ncourse there is only one feasible explanation. You have been\nbrought there to personate someone, and the real person is\nimprisoned in this chamber. That is obvious. As to who this\nprisoner is, I have no doubt that it is the daughter, Miss Alice\nRucastle, if I remember right, who was said to have gone to\nAmerica. You were chosen, doubtless, as resembling her in height,\nfigure, and the colour of your hair. Hers had been cut off, very\npossibly in some illness through which she has passed, and so, of\ncourse, yours had to be sacrificed also. By a curious chance you\ncame upon her tresses. The man in the road was undoubtedly some\nfriend of hers--possibly her fiancé--and no doubt, as you wore\nthe girl's dress and were so like her, he was convinced from your\nlaughter, whenever he saw you, and afterwards from your gesture,\nthat Miss Rucastle was perfectly happy, and that she no longer\ndesired his attentions. The dog is let loose at night to prevent\nhim from endeavouring to communicate with her. So much is fairly\nclear. The most serious point in the case is the disposition of\nthe child.\"\n\n\"What on earth has that to do with it?\" I ejaculated.\n\n\"My dear Watson, you as a medical man are continually gaining\nlight as to the tendencies of a child by the study of the\nparents. Don't you see that the converse is equally valid. I have\nfrequently gained my first real insight into the character of\nparents by studying their children. This child's disposition is\nabnormally cruel, merely for cruelty's sake, and whether he\nderives this from his smiling father, as I should suspect, or\nfrom his mother, it bodes evil for the poor girl who is in their\npower.\"\n\n\"I am sure that you are right, Mr. Holmes,\" cried our client. \"A\nthousand things come back to me which make me certain that you\nhave hit it. Oh, let us lose not an instant in bringing help to\nthis poor creature.\"\n\n\"We must be circumspect, for we are dealing with a very cunning\nman. We can do nothing until seven o'clock. At that hour we shall\nbe with you, and it will not be long before we solve the\nmystery.\"\n\nWe were as good as our word, for it was just seven when we\nreached the Copper Beeches, having put up our trap at a wayside\npublic-house. The group of trees, with their dark leaves shining\nlike burnished metal in the light of the setting sun, were\nsufficient to mark the house even had Miss Hunter not been\nstanding smiling on the door-step.\n\n\"Have you managed it?\" asked Holmes.\n\nA loud thudding noise came from somewhere downstairs. \"That is\nMrs. Toller in the cellar,\" said she. \"Her husband lies snoring\non the kitchen rug. Here are his keys, which are the duplicates\nof Mr. Rucastle's.\"\n\n\"You have done well indeed!\" cried Holmes with enthusiasm. \"Now\nlead the way, and we shall soon see the end of this black\nbusiness.\"\n\nWe passed up the stair, unlocked the door, followed on down a\npassage, and found ourselves in front of the barricade which Miss\nHunter had described. Holmes cut the cord and removed the\ntransverse bar. Then he tried the various keys in the lock, but\nwithout success. No sound came from within, and at the silence\nHolmes' face clouded over.\n\n\"I trust that we are not too late,\" said he. \"I think, Miss\nHunter, that we had better go in without you. Now, Watson, put\nyour shoulder to it, and we shall see whether we cannot make our\nway in.\"\n\nIt was an old rickety door and gave at once before our united\nstrength. Together we rushed into the room. It was empty. There\nwas no furniture save a little pallet bed, a small table, and a\nbasketful of linen. The skylight above was open, and the prisoner\ngone.\n\n\"There has been some villainy here,\" said Holmes; \"this beauty\nhas guessed Miss Hunter's intentions and has carried his victim\noff.\"\n\n\"But how?\"\n\n\"Through the skylight. We shall soon see how he managed it.\" He\nswung himself up onto the roof. \"Ah, yes,\" he cried, \"here's the\nend of a long light ladder against the eaves. That is how he did\nit.\"\n\n\"But it is impossible,\" said Miss Hunter; \"the ladder was not\nthere when the Rucastles went away.\"\n\n\"He has come back and done it. I tell you that he is a clever and\ndangerous man. I should not be very much surprised if this were\nhe whose step I hear now upon the stair. I think, Watson, that it\nwould be as well for you to have your pistol ready.\"\n\nThe words were hardly out of his mouth before a man appeared at\nthe door of the room, a very fat and burly man, with a heavy\nstick in his hand. Miss Hunter screamed and shrunk against the\nwall at the sight of him, but Sherlock Holmes sprang forward and\nconfronted him.\n\n\"You villain!\" said he, \"where's your daughter?\"\n\nThe fat man cast his eyes round, and then up at the open\nskylight.\n\n\"It is for me to ask you that,\" he shrieked, \"you thieves! Spies\nand thieves! I have caught you, have I? You are in my power. I'll\nserve you!\" He turned and clattered down the stairs as hard as he\ncould go.\n\n\"He's gone for the dog!\" cried Miss Hunter.\n\n\"I have my revolver,\" said I.\n\n\"Better close the front door,\" cried Holmes, and we all rushed\ndown the stairs together. We had hardly reached the hall when we\nheard the baying of a hound, and then a scream of agony, with a\nhorrible worrying sound which it was dreadful to listen to. An\nelderly man with a red face and shaking limbs came staggering out\nat a side door.\n\n\"My God!\" he cried. \"Someone has loosed the dog. It's not been\nfed for two days. Quick, quick, or it'll be too late!\"\n\nHolmes and I rushed out and round the angle of the house, with\nToller hurrying behind us. There was the huge famished brute, its\nblack muzzle buried in Rucastle's throat, while he writhed and\nscreamed upon the ground. Running up, I blew its brains out, and\nit fell over with its keen white teeth still meeting in the great\ncreases of his neck. With much labour we separated them and\ncarried him, living but horribly mangled, into the house. We laid\nhim upon the drawing-room sofa, and having dispatched the sobered\nToller to bear the news to his wife, I did what I could to\nrelieve his pain. We were all assembled round him when the door\nopened, and a tall, gaunt woman entered the room.\n\n\"Mrs. Toller!\" cried Miss Hunter.\n\n\"Yes, miss. Mr. Rucastle let me out when he came back before he\nwent up to you. Ah, miss, it is a pity you didn't let me know\nwhat you were planning, for I would have told you that your pains\nwere wasted.\"\n\n\"Ha!\" said Holmes, looking keenly at her. \"It is clear that Mrs.\nToller knows more about this matter than anyone else.\"\n\n\"Yes, sir, I do, and I am ready enough to tell what I know.\"\n\n\"Then, pray, sit down, and let us hear it for there are several\npoints on which I must confess that I am still in the dark.\"\n\n\"I will soon make it clear to you,\" said she; \"and I'd have done\nso before now if I could ha' got out from the cellar. If there's\npolice-court business over this, you'll remember that I was the\none that stood your friend, and that I was Miss Alice's friend\ntoo.\n\n\"She was never happy at home, Miss Alice wasn't, from the time\nthat her father married again. She was slighted like and had no\nsay in anything, but it never really became bad for her until\nafter she met Mr. Fowler at a friend's house. As well as I could\nlearn, Miss Alice had rights of her own by will, but she was so\nquiet and patient, she was, that she never said a word about them\nbut just left everything in Mr. Rucastle's hands. He knew he was\nsafe with her; but when there was a chance of a husband coming\nforward, who would ask for all that the law would give him, then\nher father thought it time to put a stop on it. He wanted her to\nsign a paper, so that whether she married or not, he could use\nher money. When she wouldn't do it, he kept on worrying her until\nshe got brain-fever, and for six weeks was at death's door. Then\nshe got better at last, all worn to a shadow, and with her\nbeautiful hair cut off; but that didn't make no change in her\nyoung man, and he stuck to her as true as man could be.\"\n\n\"Ah,\" said Holmes, \"I think that what you have been good enough\nto tell us makes the matter fairly clear, and that I can deduce\nall that remains. Mr. Rucastle then, I presume, took to this\nsystem of imprisonment?\"\n\n\"Yes, sir.\"\n\n\"And brought Miss Hunter down from London in order to get rid of\nthe disagreeable persistence of Mr. Fowler.\"\n\n\"That was it, sir.\"\n\n\"But Mr. Fowler being a persevering man, as a good seaman should\nbe, blockaded the house, and having met you succeeded by certain\narguments, metallic or otherwise, in convincing you that your\ninterests were the same as his.\"\n\n\"Mr. Fowler was a very kind-spoken, free-handed gentleman,\" said\nMrs. Toller serenely.\n\n\"And in this way he managed that your good man should have no\nwant of drink, and that a ladder should be ready at the moment\nwhen your master had gone out.\"\n\n\"You have it, sir, just as it happened.\"\n\n\"I am sure we owe you an apology, Mrs. Toller,\" said Holmes, \"for\nyou have certainly cleared up everything which puzzled us. And\nhere comes the country surgeon and Mrs. Rucastle, so I think,\nWatson, that we had best escort Miss Hunter back to Winchester,\nas it seems to me that our locus standi now is rather a\nquestionable one.\"\n\nAnd thus was solved the mystery of the sinister house with the\ncopper beeches in front of the door. Mr. Rucastle survived, but\nwas always a broken man, kept alive solely through the care of\nhis devoted wife. They still live with their old servants, who\nprobably know so much of Rucastle's past life that he finds it\ndifficult to part from them. Mr. Fowler and Miss Rucastle were\nmarried, by special license, in Southampton the day after their\nflight, and he is now the holder of a government appointment in\nthe island of Mauritius. As to Miss Violet Hunter, my friend\nHolmes, rather to my disappointment, manifested no further\ninterest in her when once she had ceased to be the centre of one\nof his problems, and she is now the head of a private school at\nWalsall, where I believe that she has met with considerable success.\n\n\n\n\n\n\n\n\n\nEnd of the Project Gutenberg EBook of The Adventures of Sherlock Holmes, by\nArthur Conan Doyle\n\n*** END OF THIS PROJECT GUTENBERG EBOOK THE ADVENTURES OF SHERLOCK HOLMES ***\n\n***** This file should be named 1661-8.txt or 1661-8.zip *****\nThis and all associated files of various formats will be found in:\n        http://www.gutenberg.org/1/6/6/1661/\n\nProduced by an anonymous Project Gutenberg volunteer and Jose Menendez\n\nUpdated editions will replace the previous one--the old editions\nwill be renamed.\n\nCreating the works from public domain print editions means that no\none owns a United States copyright in these works, so the Foundation\n(and you!) can copy and distribute it in the United States without\npermission and without paying copyright royalties.  Special rules,\nset forth in the General Terms of Use part of this license, apply to\ncopying and distributing Project Gutenberg-tm electronic works to\nprotect the PROJECT GUTENBERG-tm concept and trademark.  Project\nGutenberg is a registered trademark, and may not be used if you\ncharge for the eBooks, unless you receive specific permission.  If you\ndo not charge anything for copies of this eBook, complying with the\nrules is very easy.  You may use this eBook for nearly any purpose\nsuch as creation of derivative works, reports, performances and\nresearch.  They may be modified and printed and given away--you may do\npractically ANYTHING with public domain eBooks.  Redistribution is\nsubject to the trademark license, especially commercial\nredistribution.\n\n\n\n*** START: FULL LICENSE ***\n\nTHE FULL PROJECT GUTENBERG LICENSE\nPLEASE READ THIS BEFORE YOU DISTRIBUTE OR USE THIS WORK\n\nTo protect the Project Gutenberg-tm mission of promoting the free\ndistribution of electronic works, by using or distributing this work\n(or any other work associated in any way with the phrase \"Project\nGutenberg\"), you agree to comply with all the terms of the Full Project\nGutenberg-tm License (available with this file or online at\nhttp://gutenberg.net/license).\n\n\nSection 1.  General Terms of Use and Redistributing Project Gutenberg-tm\nelectronic works\n\n1.A.  By reading or using any part of this Project Gutenberg-tm\nelectronic work, you indicate that you have read, understand, agree to\nand accept all the terms of this license and intellectual property\n(trademark/copyright) agreement.  If you do not agree to abide by all\nthe terms of this agreement, you must cease using and return or destroy\nall copies of Project Gutenberg-tm electronic works in your possession.\nIf you paid a fee for obtaining a copy of or access to a Project\nGutenberg-tm electronic work and you do not agree to be bound by the\nterms of this agreement, you may obtain a refund from the person or\nentity to whom you paid the fee as set forth in paragraph 1.E.8.\n\n1.B.  \"Project Gutenberg\" is a registered trademark.  It may only be\nused on or associated in any way with an electronic work by people who\nagree to be bound by the terms of this agreement.  There are a few\nthings that you can do with most Project Gutenberg-tm electronic works\neven without complying with the full terms of this agreement.  See\nparagraph 1.C below.  There are a lot of things you can do with Project\nGutenberg-tm electronic works if you follow the terms of this agreement\nand help preserve free future access to Project Gutenberg-tm electronic\nworks.  See paragraph 1.E below.\n\n1.C.  The Project Gutenberg Literary Archive Foundation (\"the Foundation\"\nor PGLAF), owns a compilation copyright in the collection of Project\nGutenberg-tm electronic works.  Nearly all the individual works in the\ncollection are in the public domain in the United States.  If an\nindividual work is in the public domain in the United States and you are\nlocated in the United States, we do not claim a right to prevent you from\ncopying, distributing, performing, displaying or creating derivative\nworks based on the work as long as all references to Project Gutenberg\nare removed.  Of course, we hope that you will support the Project\nGutenberg-tm mission of promoting free access to electronic works by\nfreely sharing Project Gutenberg-tm works in compliance with the terms of\nthis agreement for keeping the Project Gutenberg-tm name associated with\nthe work.  You can easily comply with the terms of this agreement by\nkeeping this work in the same format with its attached full Project\nGutenberg-tm License when you share it without charge with others.\n\n1.D.  The copyright laws of the place where you are located also govern\nwhat you can do with this work.  Copyright laws in most countries are in\na constant state of change.  If you are outside the United States, check\nthe laws of your country in addition to the terms of this agreement\nbefore downloading, copying, displaying, performing, distributing or\ncreating derivative works based on this work or any other Project\nGutenberg-tm work.  The Foundation makes no representations concerning\nthe copyright status of any work in any country outside the United\nStates.\n\n1.E.  Unless you have removed all references to Project Gutenberg:\n\n1.E.1.  The following sentence, with active links to, or other immediate\naccess to, the full Project Gutenberg-tm License must appear prominently\nwhenever any copy of a Project Gutenberg-tm work (any work on which the\nphrase \"Project Gutenberg\" appears, or with which the phrase \"Project\nGutenberg\" is associated) is accessed, displayed, performed, viewed,\ncopied or distributed:\n\nThis eBook is for the use of anyone anywhere at no cost and with\nalmost no restrictions whatsoever.  You may copy it, give it away or\nre-use it under the terms of the Project Gutenberg License included\nwith this eBook or online at www.gutenberg.net\n\n1.E.2.  If an individual Project Gutenberg-tm electronic work is derived\nfrom the public domain (does not contain a notice indicating that it is\nposted with permission of the copyright holder), the work can be copied\nand distributed to anyone in the United States without paying any fees\nor charges.  If you are redistributing or providing access to a work\nwith the phrase \"Project Gutenberg\" associated with or appearing on the\nwork, you must comply either with the requirements of paragraphs 1.E.1\nthrough 1.E.7 or obtain permission for the use of the work and the\nProject Gutenberg-tm trademark as set forth in paragraphs 1.E.8 or\n1.E.9.\n\n1.E.3.  If an individual Project Gutenberg-tm electronic work is posted\nwith the permission of the copyright holder, your use and distribution\nmust comply with both paragraphs 1.E.1 through 1.E.7 and any additional\nterms imposed by the copyright holder.  Additional terms will be linked\nto the Project Gutenberg-tm License for all works posted with the\npermission of the copyright holder found at the beginning of this work.\n\n1.E.4.  Do not unlink or detach or remove the full Project Gutenberg-tm\nLicense terms from this work, or any files containing a part of this\nwork or any other work associated with Project Gutenberg-tm.\n\n1.E.5.  Do not copy, display, perform, distribute or redistribute this\nelectronic work, or any part of this electronic work, without\nprominently displaying the sentence set forth in paragraph 1.E.1 with\nactive links or immediate access to the full terms of the Project\nGutenberg-tm License.\n\n1.E.6.  You may convert to and distribute this work in any binary,\ncompressed, marked up, nonproprietary or proprietary form, including any\nword processing or hypertext form.  However, if you provide access to or\ndistribute copies of a Project Gutenberg-tm work in a format other than\n\"Plain Vanilla ASCII\" or other format used in the official version\nposted on the official Project Gutenberg-tm web site (www.gutenberg.net),\nyou must, at no additional cost, fee or expense to the user, provide a\ncopy, a means of exporting a copy, or a means of obtaining a copy upon\nrequest, of the work in its original \"Plain Vanilla ASCII\" or other\nform.  Any alternate format must include the full Project Gutenberg-tm\nLicense as specified in paragraph 1.E.1.\n\n1.E.7.  Do not charge a fee for access to, viewing, displaying,\nperforming, copying or distributing any Project Gutenberg-tm works\nunless you comply with paragraph 1.E.8 or 1.E.9.\n\n1.E.8.  You may charge a reasonable fee for copies of or providing\naccess to or distributing Project Gutenberg-tm electronic works provided\nthat\n\n- You pay a royalty fee of 20% of the gross profits you derive from\n     the use of Project Gutenberg-tm works calculated using the method\n     you already use to calculate your applicable taxes.  The fee is\n     owed to the owner of the Project Gutenberg-tm trademark, but he\n     has agreed to donate royalties under this paragraph to the\n     Project Gutenberg Literary Archive Foundation.  Royalty payments\n     must be paid within 60 days following each date on which you\n     prepare (or are legally required to prepare) your periodic tax\n     returns.  Royalty payments should be clearly marked as such and\n     sent to the Project Gutenberg Literary Archive Foundation at the\n     address specified in Section 4, \"Information about donations to\n     the Project Gutenberg Literary Archive Foundation.\"\n\n- You provide a full refund of any money paid by a user who notifies\n     you in writing (or by e-mail) within 30 days of receipt that s/he\n     does not agree to the terms of the full Project Gutenberg-tm\n     License.  You must require such a user to return or\n     destroy all copies of the works possessed in a physical medium\n     and discontinue all use of and all access to other copies of\n     Project Gutenberg-tm works.\n\n- You provide, in accordance with paragraph 1.F.3, a full refund of any\n     money paid for a work or a replacement copy, if a defect in the\n     electronic work is discovered and reported to you within 90 days\n     of receipt of the work.\n\n- You comply with all other terms of this agreement for free\n     distribution of Project Gutenberg-tm works.\n\n1.E.9.  If you wish to charge a fee or distribute a Project Gutenberg-tm\nelectronic work or group of works on different terms than are set\nforth in this agreement, you must obtain permission in writing from\nboth the Project Gutenberg Literary Archive Foundation and Michael\nHart, the owner of the Project Gutenberg-tm trademark.  Contact the\nFoundation as set forth in Section 3 below.\n\n1.F.\n\n1.F.1.  Project Gutenberg volunteers and employees expend considerable\neffort to identify, do copyright research on, transcribe and proofread\npublic domain works in creating the Project Gutenberg-tm\ncollection.  Despite these efforts, Project Gutenberg-tm electronic\nworks, and the medium on which they may be stored, may contain\n\"Defects,\" such as, but not limited to, incomplete, inaccurate or\ncorrupt data, transcription errors, a copyright or other intellectual\nproperty infringement, a defective or damaged disk or other medium, a\ncomputer virus, or computer codes that damage or cannot be read by\nyour equipment.\n\n1.F.2.  LIMITED WARRANTY, DISCLAIMER OF DAMAGES - Except for the \"Right\nof Replacement or Refund\" described in paragraph 1.F.3, the Project\nGutenberg Literary Archive Foundation, the owner of the Project\nGutenberg-tm trademark, and any other party distributing a Project\nGutenberg-tm electronic work under this agreement, disclaim all\nliability to you for damages, costs and expenses, including legal\nfees.  YOU AGREE THAT YOU HAVE NO REMEDIES FOR NEGLIGENCE, STRICT\nLIABILITY, BREACH OF WARRANTY OR BREACH OF CONTRACT EXCEPT THOSE\nPROVIDED IN PARAGRAPH 1.F.3.  YOU AGREE THAT THE FOUNDATION, THE\nTRADEMARK OWNER, AND ANY DISTRIBUTOR UNDER THIS AGREEMENT WILL NOT BE\nLIABLE TO YOU FOR ACTUAL, DIRECT, INDIRECT, CONSEQUENTIAL, PUNITIVE OR\nINCIDENTAL DAMAGES EVEN IF YOU GIVE NOTICE OF THE POSSIBILITY OF SUCH\nDAMAGE.\n\n1.F.3.  LIMITED RIGHT OF REPLACEMENT OR REFUND - If you discover a\ndefect in this electronic work within 90 days of receiving it, you can\nreceive a refund of the money (if any) you paid for it by sending a\nwritten explanation to the person you received the work from.  If you\nreceived the work on a physical medium, you must return the medium with\nyour written explanation.  The person or entity that provided you with\nthe defective work may elect to provide a replacement copy in lieu of a\nrefund.  If you received the work electronically, the person or entity\nproviding it to you may choose to give you a second opportunity to\nreceive the work electronically in lieu of a refund.  If the second copy\nis also defective, you may demand a refund in writing without further\nopportunities to fix the problem.\n\n1.F.4.  Except for the limited right of replacement or refund set forth\nin paragraph 1.F.3, this work is provided to you 'AS-IS' WITH NO OTHER\nWARRANTIES OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\nWARRANTIES OF MERCHANTIBILITY OR FITNESS FOR ANY PURPOSE.\n\n1.F.5.  Some states do not allow disclaimers of certain implied\nwarranties or the exclusion or limitation of certain types of damages.\nIf any disclaimer or limitation set forth in this agreement violates the\nlaw of the state applicable to this agreement, the agreement shall be\ninterpreted to make the maximum disclaimer or limitation permitted by\nthe applicable state law.  The invalidity or unenforceability of any\nprovision of this agreement shall not void the remaining provisions.\n\n1.F.6.  INDEMNITY - You agree to indemnify and hold the Foundation, the\ntrademark owner, any agent or employee of the Foundation, anyone\nproviding copies of Project Gutenberg-tm electronic works in accordance\nwith this agreement, and any volunteers associated with the production,\npromotion and distribution of Project Gutenberg-tm electronic works,\nharmless from all liability, costs and expenses, including legal fees,\nthat arise directly or indirectly from any of the following which you do\nor cause to occur: (a) distribution of this or any Project Gutenberg-tm\nwork, (b) alteration, modification, or additions or deletions to any\nProject Gutenberg-tm work, and (c) any Defect you cause.\n\n\nSection  2.  Information about the Mission of Project Gutenberg-tm\n\nProject Gutenberg-tm is synonymous with the free distribution of\nelectronic works in formats readable by the widest variety of computers\nincluding obsolete, old, middle-aged and new computers.  It exists\nbecause of the efforts of hundreds of volunteers and donations from\npeople in all walks of life.\n\nVolunteers and financial support to provide volunteers with the\nassistance they need are critical to reaching Project Gutenberg-tm's\ngoals and ensuring that the Project Gutenberg-tm collection will\nremain freely available for generations to come.  In 2001, the Project\nGutenberg Literary Archive Foundation was created to provide a secure\nand permanent future for Project Gutenberg-tm and future generations.\nTo learn more about the Project Gutenberg Literary Archive Foundation\nand how your efforts and donations can help, see Sections 3 and 4\nand the Foundation web page at http://www.pglaf.org.\n\n\nSection 3.  Information about the Project Gutenberg Literary Archive\nFoundation\n\nThe Project Gutenberg Literary Archive Foundation is a non profit\n501(c)(3) educational corporation organized under the laws of the\nstate of Mississippi and granted tax exempt status by the Internal\nRevenue Service.  The Foundation's EIN or federal tax identification\nnumber is 64-6221541.  Its 501(c)(3) letter is posted at\nhttp://pglaf.org/fundraising.  Contributions to the Project Gutenberg\nLiterary Archive Foundation are tax deductible to the full extent\npermitted by U.S. federal laws and your state's laws.\n\nThe Foundation's principal office is located at 4557 Melan Dr. S.\nFairbanks, AK, 99712., but its volunteers and employees are scattered\nthroughout numerous locations.  Its business office is located at\n809 North 1500 West, Salt Lake City, UT 84116, (801) 596-1887, email\nbusiness@pglaf.org.  Email contact links and up to date contact\ninformation can be found at the Foundation's web site and official\npage at http://pglaf.org\n\nFor additional contact information:\n     Dr. Gregory B. Newby\n     Chief Executive and Director\n     gbnewby@pglaf.org\n\n\nSection 4.  Information about Donations to the Project Gutenberg\nLiterary Archive Foundation\n\nProject Gutenberg-tm depends upon and cannot survive without wide\nspread public support and donations to carry out its mission of\nincreasing the number of public domain and licensed works that can be\nfreely distributed in machine readable form accessible by the widest\narray of equipment including outdated equipment.  Many small donations\n($1 to $5,000) are particularly important to maintaining tax exempt\nstatus with the IRS.\n\nThe Foundation is committed to complying with the laws regulating\ncharities and charitable donations in all 50 states of the United\nStates.  Compliance requirements are not uniform and it takes a\nconsiderable effort, much paperwork and many fees to meet and keep up\nwith these requirements.  We do not solicit donations in locations\nwhere we have not received written confirmation of compliance.  To\nSEND DONATIONS or determine the status of compliance for any\nparticular state visit http://pglaf.org\n\nWhile we cannot and do not solicit contributions from states where we\nhave not met the solicitation requirements, we know of no prohibition\nagainst accepting unsolicited donations from donors in such states who\napproach us with offers to donate.\n\nInternational donations are gratefully accepted, but we cannot make\nany statements concerning tax treatment of donations received from\noutside the United States.  U.S. laws alone swamp our small staff.\n\nPlease check the Project Gutenberg Web pages for current donation\nmethods and addresses.  Donations are accepted in a number of other\nways including including checks, online payments and credit card\ndonations.  To donate, please visit: http://pglaf.org/donate\n\n\nSection 5.  General Information About Project Gutenberg-tm electronic\nworks.\n\nProfessor Michael S. Hart is the originator of the Project Gutenberg-tm\nconcept of a library of electronic works that could be freely shared\nwith anyone.  For thirty years, he produced and distributed Project\nGutenberg-tm eBooks with only a loose network of volunteer support.\n\n\nProject Gutenberg-tm eBooks are often created from several printed\neditions, all of which are confirmed as Public Domain in the U.S.\nunless a copyright notice is included.  Thus, we do not necessarily\nkeep eBooks in compliance with any particular paper edition.\n\n\nMost people start at our Web site which has the main PG search facility:\n\n     http://www.gutenberg.net\n\nThis Web site includes information about Project Gutenberg-tm,\nincluding how to make donations to the Project Gutenberg Literary\nArchive Foundation, how to help produce our new eBooks, and how to\nsubscribe to our email newsletter to hear about new eBooks.\n"
  },
  {
    "path": "packages/ipfs-http-response/test/fixtures/test-site/index.html",
    "content": "<html>\n  <body>\n    Website\n  </body>\n</html>\n"
  },
  {
    "path": "packages/ipfs-http-response/test/fixtures/test-site/pp.txt",
    "content": "PRIDE AND PREJUDICE\n\nBy Jane Austen\n\n\n\nChapter 1\n\n\nIt is a truth universally acknowledged, that a single man in possession\nof a good fortune, must be in want of a wife.\n\nHowever little known the feelings or views of such a man may be on his\nfirst entering a neighbourhood, this truth is so well fixed in the minds\nof the surrounding families, that he is considered the rightful property\nof some one or other of their daughters.\n\n\"My dear Mr. Bennet,\" said his lady to him one day, \"have you heard that\nNetherfield Park is let at last?\"\n\nMr. Bennet replied that he had not.\n\n\"But it is,\" returned she; \"for Mrs. Long has just been here, and she\ntold me all about it.\"\n\nMr. Bennet made no answer.\n\n\"Do you not want to know who has taken it?\" cried his wife impatiently.\n\n\"_You_ want to tell me, and I have no objection to hearing it.\"\n\nThis was invitation enough.\n\n\"Why, my dear, you must know, Mrs. Long says that Netherfield is taken\nby a young man of large fortune from the north of England; that he came\ndown on Monday in a chaise and four to see the place, and was so much\ndelighted with it, that he agreed with Mr. Morris immediately; that he\nis to take possession before Michaelmas, and some of his servants are to\nbe in the house by the end of next week.\"\n\n\"What is his name?\"\n\n\"Bingley.\"\n\n\"Is he married or single?\"\n\n\"Oh! Single, my dear, to be sure! A single man of large fortune; four or\nfive thousand a year. What a fine thing for our girls!\"\n\n\"How so? How can it affect them?\"\n\n\"My dear Mr. Bennet,\" replied his wife, \"how can you be so tiresome! You\nmust know that I am thinking of his marrying one of them.\"\n\n\"Is that his design in settling here?\"\n\n\"Design! Nonsense, how can you talk so! But it is very likely that he\n_may_ fall in love with one of them, and therefore you must visit him as\nsoon as he comes.\"\n\n\"I see no occasion for that. You and the girls may go, or you may send\nthem by themselves, which perhaps will be still better, for as you are\nas handsome as any of them, Mr. Bingley may like you the best of the\nparty.\"\n\n\"My dear, you flatter me. I certainly _have_ had my share of beauty, but\nI do not pretend to be anything extraordinary now. When a woman has five\ngrown-up daughters, she ought to give over thinking of her own beauty.\"\n\n\"In such cases, a woman has not often much beauty to think of.\"\n\n\"But, my dear, you must indeed go and see Mr. Bingley when he comes into\nthe neighbourhood.\"\n\n\"It is more than I engage for, I assure you.\"\n\n\"But consider your daughters. Only think what an establishment it would\nbe for one of them. Sir William and Lady Lucas are determined to\ngo, merely on that account, for in general, you know, they visit no\nnewcomers. Indeed you must go, for it will be impossible for _us_ to\nvisit him if you do not.\"\n\n\"You are over-scrupulous, surely. I dare say Mr. Bingley will be very\nglad to see you; and I will send a few lines by you to assure him of my\nhearty consent to his marrying whichever he chooses of the girls; though\nI must throw in a good word for my little Lizzy.\"\n\n\"I desire you will do no such thing. Lizzy is not a bit better than the\nothers; and I am sure she is not half so handsome as Jane, nor half so\ngood-humoured as Lydia. But you are always giving _her_ the preference.\"\n\n\"They have none of them much to recommend them,\" replied he; \"they are\nall silly and ignorant like other girls; but Lizzy has something more of\nquickness than her sisters.\"\n\n\"Mr. Bennet, how _can_ you abuse your own children in such a way? You\ntake delight in vexing me. You have no compassion for my poor nerves.\"\n\n\"You mistake me, my dear. I have a high respect for your nerves. They\nare my old friends. I have heard you mention them with consideration\nthese last twenty years at least.\"\n\n\"Ah, you do not know what I suffer.\"\n\n\"But I hope you will get over it, and live to see many young men of four\nthousand a year come into the neighbourhood.\"\n\n\"It will be no use to us, if twenty such should come, since you will not\nvisit them.\"\n\n\"Depend upon it, my dear, that when there are twenty, I will visit them\nall.\"\n\nMr. Bennet was so odd a mixture of quick parts, sarcastic humour,\nreserve, and caprice, that the experience of three-and-twenty years had\nbeen insufficient to make his wife understand his character. _Her_ mind\nwas less difficult to develop. She was a woman of mean understanding,\nlittle information, and uncertain temper. When she was discontented,\nshe fancied herself nervous. The business of her life was to get her\ndaughters married; its solace was visiting and news.\n"
  },
  {
    "path": "packages/ipfs-http-response/test/fixtures/testfile.txt",
    "content": "Plz add me!\n"
  },
  {
    "path": "packages/ipfs-http-response/test/index.spec.js",
    "content": "/* eslint-env mocha */\nimport { expect } from 'aegir/chai'\nimport loadFixture from 'aegir/fixtures'\nimport { createFactory } from 'ipfsd-ctl'\nimport getStream from 'get-stream'\nimport all from 'it-all'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\nimport { getResponse } from '../src/index.js'\nimport makeWebResponseEnv from './utils/web-response-env.js'\nimport * as ipfsModule from 'ipfs-core'\n\nconst factory = createFactory({\n  test: true,\n  type: 'proc',\n  ipfsModule\n})\n\ndescribe('resolve file (CIDv0)', function () {\n  /** @type {*} */\n  let ipfs = null\n\n  const file = {\n    cid: 'Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP',\n    data: loadFixture('test/fixtures/testfile.txt')\n  }\n\n  before(async function () {\n    this.timeout(20 * 1000)\n    Object.assign(global, makeWebResponseEnv())\n\n    const ipfsd = await factory.spawn()\n    ipfs = ipfsd.api\n\n    const retrievedFile = await ipfs.add(file.data, { cidVersion: 0 })\n    expect(retrievedFile.cid.toString()).to.equal(file.cid)\n    expect(retrievedFile.size, 'ipfs.add result size should not be smaller than input buffer').greaterThan(file.data.length)\n  })\n\n  after(() => factory.clean())\n\n  it('should resolve a CIDv0', async () => {\n    const res = await getResponse(ipfs, `/ipfs/${file.cid}`)\n\n    expect(res).to.exist()\n    expect(res.status).to.equal(200)\n\n    // @ts-expect-error types are wrong\n    const contents = await getStream(res.body)\n    const expectedContents = uint8ArrayToString(loadFixture('test/fixtures/testfile.txt'))\n\n    expect(contents).to.equal(expectedContents)\n  })\n})\n\ndescribe('resolve file (CIDv1)', function () {\n  /** @type {any} */\n  let ipfs = null\n  let ipfsd = null\n\n  const file = {\n    cid: 'bafkreidffqfydlguosmmyebv5rp72m45tbpbq6segnkosa45kjfnduix6u',\n    data: loadFixture('test/fixtures/testfile.txt')\n  }\n\n  before(async function () {\n    this.timeout(20 * 1000)\n    Object.assign(global, makeWebResponseEnv())\n\n    ipfsd = await factory.spawn()\n    ipfs = ipfsd.api\n\n    const retrievedFile = await ipfs.add(file.data, { cidVersion: 1 })\n    expect(retrievedFile.cid.toString()).to.equal(file.cid)\n    expect(retrievedFile.size, 'ipfs.add result size should equal input buffer').to.equal(file.data.length)\n  })\n\n  after(() => factory.clean())\n\n  it('should resolve a CIDv1', async () => {\n    const res = await getResponse(ipfs, `/ipfs/${file.cid}`)\n\n    expect(res).to.exist()\n    expect(res.status).to.equal(200)\n\n    // @ts-expect-error types are wrong\n    const contents = await getStream(res.body)\n    const expectedContents = uint8ArrayToString(loadFixture('test/fixtures/testfile.txt'))\n\n    expect(contents).to.equal(expectedContents)\n  })\n})\n\ndescribe('resolve directory (CIDv0)', function () {\n  /** @type {any} */\n  let ipfs = null\n  let ipfsd = null\n\n  const directory = {\n    cid: 'QmU1aW5x8tXfbRpJ71zoEVwxrRDHybC2iTVacCMabCUniZ',\n    /** @type {Record<string, Uint8Array>} */\n    files: {\n      'pp.txt': loadFixture('test/fixtures/test-folder/pp.txt'),\n      'holmes.txt': loadFixture('test/fixtures/test-folder/holmes.txt')\n    }\n  }\n\n  before(async function () {\n    this.timeout(20 * 1000)\n    Object.assign(global, makeWebResponseEnv())\n\n    ipfsd = await factory.spawn()\n    ipfs = ipfsd.api\n\n    /**\n     * @param {string} name\n     */\n    const content = (name) => ({\n      path: `test-folder/${name}`,\n      content: directory.files[name]\n    })\n\n    const dirs = [\n      content('pp.txt'),\n      content('holmes.txt')\n    ]\n\n    const res = await all(ipfs.addAll(dirs, { cidVersion: 0 }))\n    const root = res[res.length - 1]\n\n    expect(root.path).to.equal('test-folder')\n    expect(root.cid.toString()).to.equal(directory.cid)\n\n    expect(res[0].size, 'ipfs.add 1st result size should not be smaller than 1st input buffer').greaterThan(dirs[0].content.length)\n    expect(res[1].size, 'ipfs.add 2nd result size should not be smaller than 2nd input buffer').greaterThan(dirs[1].content.length)\n  })\n\n  after(() => factory.clean())\n\n  it('should return the list of files of a directory', async () => {\n    const res = await getResponse(ipfs, `/ipfs/${directory.cid}`)\n\n    expect(res.status).to.equal(200)\n    expect(res.body).to.match(/<html>/)\n  })\n\n  it('should return the pp.txt file', async () => {\n    const res = await getResponse(ipfs, `/ipfs/${directory.cid}/pp.txt`)\n\n    // @ts-expect-error types are wrong\n    const contents = await getStream(res.body)\n    const expectedContents = uint8ArrayToString(loadFixture('test/fixtures/test-folder/pp.txt'))\n\n    expect(contents).to.equal(expectedContents)\n  })\n\n  it('should return the holmes.txt file', async () => {\n    const res = await getResponse(ipfs, `/ipfs/${directory.cid}/holmes.txt`)\n\n    // @ts-expect-error types are wrong\n    const contents = await getStream(res.body)\n    const expectedContents = uint8ArrayToString(loadFixture('test/fixtures/test-folder/holmes.txt'))\n\n    expect(contents).to.equal(expectedContents)\n  })\n})\n\ndescribe('resolve directory (CIDv1)', function () {\n  /** @type {any} */\n  let ipfs = null\n  let ipfsd = null\n\n  const directory = {\n    cid: 'bafybeifhimn7nu6dgmdvj6o63zegwro3yznnpfqib6kkjnagc54h46ox5q',\n    /** @type {Record<string, Uint8Array>} */\n    files: {\n      'pp.txt': loadFixture('test/fixtures/test-folder/pp.txt'),\n      'holmes.txt': loadFixture('test/fixtures/test-folder/holmes.txt')\n    }\n  }\n\n  before(async function () {\n    this.timeout(20 * 1000)\n    Object.assign(global, makeWebResponseEnv())\n\n    ipfsd = await factory.spawn()\n    ipfs = ipfsd.api\n\n    /**\n     * @param {string} name\n     */\n    const content = (name) => ({\n      path: `test-folder/${name}`,\n      content: directory.files[name]\n    })\n\n    const dirs = [\n      content('pp.txt'),\n      content('holmes.txt')\n    ]\n\n    const res = await all(ipfs.addAll(dirs, { cidVersion: 1 }))\n    const root = res[res.length - 1]\n    expect(root.path).to.equal('test-folder')\n    // expect(res[0].size, 'ipfs.files.add 1st result size should not be smaller than 1st input buffer').greaterThan(dirs[0].content.length)\n    // expect(res[1].size, 'ipfs.files.add 2nd result size should not be smaller than 2nd input buffer').greaterThan(dirs[1].content.length)\n    expect(root.cid.toString()).to.equal(directory.cid)\n  })\n\n  after(() => factory.clean())\n\n  it('should return the list of files of a directory', async () => {\n    const res = await getResponse(ipfs, `/ipfs/${directory.cid}`)\n\n    expect(res.status).to.equal(200)\n    expect(res.body).to.match(/<html>/)\n  })\n\n  it('should return the pp.txt file', async () => {\n    const res = await getResponse(ipfs, `/ipfs/${directory.cid}/pp.txt`)\n\n    // @ts-expect-error types are wrong\n    const contents = await getStream(res.body)\n    const expectedContents = uint8ArrayToString(loadFixture('test/fixtures/test-folder/pp.txt'))\n\n    expect(contents).to.equal(expectedContents)\n  })\n\n  it('should return the holmes.txt file', async () => {\n    const res = await getResponse(ipfs, `/ipfs/${directory.cid}/holmes.txt`)\n\n    // @ts-expect-error types are wrong\n    const contents = await getStream(res.body)\n    const expectedContents = uint8ArrayToString(loadFixture('test/fixtures/test-folder/holmes.txt'))\n\n    expect(contents).to.equal(expectedContents)\n  })\n})\n\ndescribe('resolve web page (CIDv0)', function () {\n  /** @type {any} */\n  let ipfs = null\n  let ipfsd = null\n\n  const webpage = {\n    cid: 'QmR3fdaM5B3LZog6TqpuHvoHYWQpRoaYwFTZ5YmdzGX5U5',\n    /** @type {Record<string, Uint8Array>} */\n    files: {\n      'pp.txt': loadFixture('test/fixtures/test-site/pp.txt'),\n      'holmes.txt': loadFixture('test/fixtures/test-site/holmes.txt'),\n      'index.html': loadFixture('test/fixtures/test-site/index.html')\n    }\n  }\n\n  before(async function () {\n    this.timeout(20 * 1000)\n    Object.assign(global, makeWebResponseEnv())\n\n    ipfsd = await factory.spawn()\n    ipfs = ipfsd.api\n\n    /**\n     * @param {string} name\n     */\n    const content = (name) => ({\n      path: `test-site/${name}`,\n      content: webpage.files[name]\n    })\n\n    const dirs = [\n      content('pp.txt'),\n      content('holmes.txt'),\n      content('index.html')\n    ]\n\n    const res = await all(ipfs.addAll(dirs, { cidVersion: 0 }))\n    const root = res[res.length - 1]\n\n    expect(root.path).to.equal('test-site')\n    expect(root.cid.toString()).to.equal(webpage.cid)\n  })\n\n  after(() => factory.clean())\n\n  it('should return the entry point of a web page when a trying to fetch a directory containing a web page', async () => {\n    const res = await getResponse(ipfs, `/ipfs/${webpage.cid}`)\n\n    expect(res.status).to.equal(302)\n    expect(res.headers.get('Location')).to.equal(`/ipfs/${webpage.cid}/index.html`)\n  })\n})\n\ndescribe('resolve web page (CIDv1)', function () {\n  /** @type {any} */\n  let ipfs = null\n  let ipfsd = null\n\n  const webpage = {\n    cid: 'bafybeibpkvlqjkwl73yam6ffsbrlgbwiffnehajc6qvnrhai5bve6jnawi',\n    /** @type {Record<string, Uint8Array>} */\n    files: {\n      'pp.txt': loadFixture('test/fixtures/test-site/pp.txt'),\n      'holmes.txt': loadFixture('test/fixtures/test-site/holmes.txt'),\n      'index.html': loadFixture('test/fixtures/test-site/index.html')\n    }\n  }\n\n  before(async function () {\n    this.timeout(20 * 1000)\n    Object.assign(global, makeWebResponseEnv())\n\n    ipfsd = await factory.spawn()\n    ipfs = ipfsd.api\n\n    /**\n     * @param {string} name\n     */\n    const content = (name) => ({\n      path: `test-site/${name}`,\n      content: webpage.files[name]\n    })\n\n    const dirs = [\n      content('pp.txt'),\n      content('holmes.txt'),\n      content('index.html')\n    ]\n\n    const res = await all(ipfs.addAll(dirs, { cidVersion: 1 }))\n    const root = res[res.length - 1]\n\n    expect(root.path).to.equal('test-site')\n    expect(root.cid.toString()).to.equal(webpage.cid)\n  })\n\n  after(() => factory.clean())\n\n  it('should return the entry point of a web page when a trying to fetch a directory containing a web page', async () => {\n    const res = await getResponse(ipfs, `/ipfs/${webpage.cid}`)\n\n    expect(res.status).to.equal(302)\n    expect(res.headers.get('Location')).to.equal(`/ipfs/${webpage.cid}/index.html`)\n  })\n})\n\n// TODO: move mime-types to separate test file\ndescribe('mime-types', () => {\n  /** @type {any} */\n  let ipfs = null\n  let ipfsd = null\n\n  const webpage = {\n    cid: 'QmWU1PAWCyd3MBAnALacXXbGU44RpgwdnGPrShSZoQj1H6',\n    /** @type {Record<string, Uint8Array>} */\n    files: {\n      'cat.jpg': loadFixture('test/fixtures/test-mime-types/cat.jpg'),\n      'hexagons-xml.svg': loadFixture('test/fixtures/test-mime-types/hexagons-xml.svg'),\n      'hexagons.svg': loadFixture('test/fixtures/test-mime-types/hexagons.svg'),\n      'pp.txt': loadFixture('test/fixtures/test-mime-types/pp.txt'),\n      'index.html': loadFixture('test/fixtures/test-mime-types/index.html')\n    }\n  }\n\n  before(async function () {\n    this.timeout(20 * 1000)\n    Object.assign(global, makeWebResponseEnv())\n\n    ipfsd = await factory.spawn()\n    ipfs = ipfsd.api\n\n    /**\n     * @param {string} name\n     * @returns\n     */\n    const content = (name) => ({\n      path: `test-mime-types/${name}`,\n      content: webpage.files[name]\n    })\n\n    const dirs = [\n      content('cat.jpg'),\n      content('hexagons-xml.svg'),\n      content('hexagons.svg'),\n      content('pp.txt'),\n      content('index.html')\n    ]\n\n    const res = await all(ipfs.addAll(dirs, { cidVersion: 0 }))\n    const root = res[res.length - 1]\n\n    expect(root.path).to.equal('test-mime-types')\n    expect(root.cid.toString()).to.equal(webpage.cid)\n  })\n\n  after(() => factory.clean())\n\n  it('should return the correct mime-type for pp.txt', async () => {\n    const res = await getResponse(ipfs, `/ipfs/${webpage.cid}/pp.txt`)\n\n    expect(res.headers.get('Content-Type')).to.equal('text/plain; charset=utf-8')\n  })\n\n  it('should return the correct mime-type for cat.jpg', async () => {\n    const res = await getResponse(ipfs, `/ipfs/${webpage.cid}/cat.jpg`)\n\n    expect(res.headers.get('Content-Type')).to.equal('image/jpeg')\n  })\n\n  it('should return the correct mime-type for index.html', async () => {\n    const res = await getResponse(ipfs, `/ipfs/${webpage.cid}/index.html`)\n\n    expect(res.headers.get('Content-Type')).to.equal('text/html; charset=utf-8')\n  })\n\n  it('should return the correct mime-type for hexagons.svg', async () => {\n    const res = await getResponse(ipfs, `/ipfs/${webpage.cid}/hexagons.svg`)\n\n    expect(res.headers.get('Content-Type')).to.equal('image/svg+xml')\n  })\n\n  it('should return the correct mime-type for hexagons.svg', async () => {\n    const res = await getResponse(ipfs, `/ipfs/${webpage.cid}/hexagons.svg`)\n\n    expect(res.headers.get('Content-Type')).to.equal('image/svg+xml')\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-response/test/resolver.spec.js",
    "content": "/* eslint-env mocha */\nimport { expect } from 'aegir/chai'\nimport loadFixture from 'aegir/fixtures'\nimport { createFactory } from 'ipfsd-ctl'\nimport all from 'it-all'\nimport * as ipfsResolver from '../src/resolver.js'\nimport * as ipfsModule from 'ipfs-core'\n\nconst factory = createFactory({\n  test: true,\n  type: 'proc',\n  ipfsModule\n})\n\ndescribe('resolve file (CIDv0)', function () {\n  /** @type {any} */\n  let ipfs = null\n  let ipfsd = null\n\n  const file = {\n    cid: 'Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP',\n    data: loadFixture('test/fixtures/testfile.txt')\n  }\n\n  before(async function () {\n    this.timeout(20 * 1000)\n\n    ipfsd = await factory.spawn()\n    ipfs = ipfsd.api\n\n    const retrievedFile = await ipfs.add(file.data, { cidVersion: 0 })\n    expect(retrievedFile.cid.toString()).to.equal(file.cid)\n  })\n\n  after(() => factory.clean())\n\n  it('should resolve a cid', async () => {\n    const res = await ipfsResolver.cid(ipfs, `/ipfs/${file.cid}`)\n\n    expect(res).to.exist()\n    expect(res).to.have.property('cid')\n    expect(res.cid.toString()).to.equal(file.cid)\n  })\n})\n\ndescribe('resolve file (CIDv1)', function () {\n  /** @type {any} */\n  let ipfs = null\n  let ipfsd = null\n\n  const file = {\n    cid: 'bafkreidffqfydlguosmmyebv5rp72m45tbpbq6segnkosa45kjfnduix6u',\n    data: loadFixture('test/fixtures/testfile.txt')\n  }\n\n  before(async function () {\n    this.timeout(20 * 1000)\n\n    ipfsd = await factory.spawn()\n    ipfs = ipfsd.api\n\n    const retrievedFile = await ipfs.add(file.data, { cidVersion: 1 })\n    expect(retrievedFile.cid.toString()).to.equal(file.cid)\n    expect(retrievedFile.size, 'ipfs.files.add result size should not be smaller than input buffer').equal(file.data.length)\n  })\n\n  after(() => factory.clean())\n\n  it('should resolve a cid', async () => {\n    const res = await ipfsResolver.cid(ipfs, `/ipfs/${file.cid}`)\n\n    expect(res).to.exist()\n    expect(res).to.have.property('cid')\n    expect(res.cid.toString()).to.equal(file.cid)\n  })\n})\n\ndescribe('resolve directory (CIDv0)', function () {\n  /** @type {any} */\n  let ipfs = null\n  let ipfsd = null\n\n  const directory = {\n    cid: 'QmU1aW5x8tXfbRpJ71zoEVwxrRDHybC2iTVacCMabCUniZ',\n    /** @type {Record<string, Uint8Array>} */\n    files: {\n      'pp.txt': loadFixture('test/fixtures/test-folder/pp.txt'),\n      'holmes.txt': loadFixture('test/fixtures/test-folder/holmes.txt')\n    }\n  }\n\n  before(async function () {\n    this.timeout(20 * 1000)\n\n    ipfsd = await factory.spawn()\n    ipfs = ipfsd.api\n\n    /**\n     * @param {string} name\n     */\n    const content = (name) => ({\n      path: `test-folder/${name}`,\n      content: directory.files[name]\n    })\n\n    const dirs = [\n      content('pp.txt'),\n      content('holmes.txt')\n    ]\n\n    const res = await all(ipfs.addAll(dirs, { cidVersion: 0 }))\n    const root = res[res.length - 1]\n\n    expect(root.path).to.equal('test-folder')\n    expect(root.cid.toString()).to.equal(directory.cid)\n  })\n\n  after(() => factory.clean())\n\n  it('should throw an error when trying to fetch a directory', async () => {\n    try {\n      const res = await ipfsResolver.cid(ipfs, `/ipfs/${directory.cid}`)\n\n      expect(res).to.not.exist()\n    } catch (/** @type {any} */ err) {\n      expect(err.toString()).to.equal('Error: This dag node is a directory')\n    }\n  })\n\n  // TODO: unskip when https://github.com/ipfs/js-ipfs/pull/3556 lands\n  it.skip('should return HTML listing of files of a directory', async () => {\n    const res = await ipfsResolver.directory(ipfs, `/ipfs/${directory.cid}`, directory.cid)\n\n    expect(res).to.exist()\n    expect(res).to.include('</html>')\n  })\n})\n\ndescribe('resolve directory (CIDv1)', function () {\n  /** @type {any} */\n  let ipfs = null\n  let ipfsd = null\n\n  const directory = {\n    cid: 'bafybeifhimn7nu6dgmdvj6o63zegwro3yznnpfqib6kkjnagc54h46ox5q',\n    /** @type {Record<string, Uint8Array>} */\n    files: {\n      'pp.txt': loadFixture('test/fixtures/test-folder/pp.txt'),\n      'holmes.txt': loadFixture('test/fixtures/test-folder/holmes.txt')\n    }\n  }\n\n  before(async function () {\n    this.timeout(20 * 1000)\n\n    ipfsd = await factory.spawn()\n    ipfs = ipfsd.api\n\n    /**\n     * @param {string} name\n     */\n    const content = (name) => ({\n      path: `test-folder/${name}`,\n      content: directory.files[name]\n    })\n\n    const dirs = [\n      content('pp.txt'),\n      content('holmes.txt')\n    ]\n\n    const res = await all(ipfs.addAll(dirs, { cidVersion: 1 }))\n    const root = res[res.length - 1]\n    // console.log('ipfs.files.add result', res)\n    expect(root.path).to.equal('test-folder')\n    // expect(res[0].size, 'ipfs.files.add 1st result size should not be smaller than 1st input buffer').greaterThan(dirs[0].content.length)\n    // expect(res[1].size, 'ipfs.files.add 2nd result size should not be smaller than 2nd input buffer').greaterThan(dirs[1].content.length)\n    expect(root.cid.toString()).to.equal(directory.cid)\n  })\n\n  after(() => factory.clean())\n\n  it('should throw an error when trying to fetch a directory', async () => {\n    try {\n      const res = await ipfsResolver.cid(ipfs, `/ipfs/${directory.cid}`)\n\n      expect(res).to.not.exist()\n    } catch (/** @type {any} */ err) {\n      expect(err.toString()).to.equal('Error: This dag node is a directory')\n    }\n  })\n\n  // TODO: unskip when https://github.com/ipfs/js-ipfs/pull/3556 lands\n  it.skip('should return HTML listing of files of a directory', async () => {\n    const res = await ipfsResolver.directory(ipfs, `/ipfs/${directory.cid}`, directory.cid)\n    expect(res).to.exist()\n    expect(res).to.include('pp.txt')\n    expect(res).to.include('holmes.txt')\n    expect(res).to.include('</html>')\n  })\n})\n\ndescribe('resolve web page (CIDv0)', function () {\n  /** @type {any} */\n  let ipfs = null\n  let ipfsd = null\n\n  const webpage = {\n    cid: 'QmR3fdaM5B3LZog6TqpuHvoHYWQpRoaYwFTZ5YmdzGX5U5',\n    /** @type {Record<string, Uint8Array>} */\n    files: {\n      'pp.txt': loadFixture('test/fixtures/test-site/pp.txt'),\n      'holmes.txt': loadFixture('test/fixtures/test-site/holmes.txt'),\n      'index.html': loadFixture('test/fixtures/test-site/index.html')\n    }\n  }\n\n  before(async function () {\n    this.timeout(20 * 1000)\n\n    ipfsd = await factory.spawn()\n    ipfs = ipfsd.api\n\n    /**\n     * @param {string} name\n     */\n    const content = (name) => ({\n      path: `test-site/${name}`,\n      content: webpage.files[name]\n    })\n\n    const dirs = [\n      content('pp.txt'),\n      content('holmes.txt'),\n      content('index.html')\n    ]\n\n    const res = await all(ipfs.addAll(dirs, { cidVersion: 0 }))\n    const root = res[res.length - 1]\n\n    expect(root.path).to.equal('test-site')\n    expect(root.cid.toString()).to.deep.equal(webpage.cid)\n  })\n\n  after(() => factory.clean())\n\n  it('should throw an error when trying to fetch a directory containing a web page', async () => {\n    try {\n      const res = await ipfsResolver.cid(ipfs, `/ipfs/${webpage.cid}`)\n\n      expect(res).to.not.exist()\n    } catch (/** @type {any} */ err) {\n      expect(err.toString()).to.equal('Error: This dag node is a directory')\n    }\n  })\n\n  it('should return the entry point of a web page when a trying to fetch a directory containing a web page', async () => {\n    const res = await ipfsResolver.directory(ipfs, `/ipfs/${webpage.cid}`, webpage.cid)\n\n    expect(res).to.exist()\n    expect(res[0]).to.deep.include({\n      Name: 'index.html'\n    })\n  })\n})\n\ndescribe('resolve web page (CIDv1)', function () {\n  /** @type {any} */\n  let ipfs = null\n  let ipfsd = null\n\n  const webpage = {\n    cid: 'bafybeibpkvlqjkwl73yam6ffsbrlgbwiffnehajc6qvnrhai5bve6jnawi',\n    /** @type {Record<string, Uint8Array>} */\n    files: {\n      'pp.txt': loadFixture('test/fixtures/test-site/pp.txt'),\n      'holmes.txt': loadFixture('test/fixtures/test-site/holmes.txt'),\n      'index.html': loadFixture('test/fixtures/test-site/index.html')\n    }\n  }\n\n  before(async function () {\n    this.timeout(20 * 1000)\n\n    ipfsd = await factory.spawn()\n    ipfs = ipfsd.api\n\n    /**\n     * @param {string} name\n     */\n    const content = (name) => ({\n      path: `test-site/${name}`,\n      content: webpage.files[name]\n    })\n\n    const dirs = [\n      content('pp.txt'),\n      content('holmes.txt'),\n      content('index.html')\n    ]\n\n    const res = await all(ipfs.addAll(dirs, { cidVersion: 1 }))\n    // console.log(res)\n    const root = res[res.length - 1]\n    expect(root.path).to.equal('test-site')\n    expect(root.cid.toString()).to.equal(webpage.cid)\n  })\n\n  after(() => factory.clean())\n\n  it('should throw an error when trying to fetch a directory containing a web page', async () => {\n    try {\n      const res = await ipfsResolver.cid(ipfs, `/ipfs/${webpage.cid}`)\n\n      expect(res).to.not.exist()\n    } catch (/** @type {any} */ err) {\n      expect(err.toString()).to.equal('Error: This dag node is a directory')\n    }\n  })\n\n  it('should return the entry point of a web page when a trying to fetch a directory containing a web page', async () => {\n    const res = await ipfsResolver.directory(ipfs, `/ipfs/${webpage.cid}`, webpage.cid)\n\n    expect(res).to.exist()\n    expect(res[0]).to.deep.include({\n      Name: 'index.html'\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-response/test/utils/web-response-env.js",
    "content": "class Response {\n  /**\n   * @param {*} body\n   * @param {*} init\n   */\n  constructor (body, init) {\n    this.body = body || ''\n    this.status = (init && typeof init.status === 'number') ? init.status : 200\n    this.ok = this.status >= 200 && this.status < 300\n    this.statusText = (init && init.statusText) || 'OK'\n    this.headers = new Map(init && init.headers ? Object.entries(init.headers) : [])\n\n    this.type = this.status === 0 ? 'opaque' : 'basic'\n    this.url = (init && init.url) || 'http://example.com/asset'\n  }\n}\n\nclass WebResponseGlobalScope {\n  constructor () {\n    this.Response = Object.assign(Response, {\n      redirect: (/** @type {string} */ url) => new Response(null, {\n        status: 302,\n        headers: { Location: url }\n      })\n    })\n  }\n}\n\nexport default () => new WebResponseGlobalScope()\n"
  },
  {
    "path": "packages/ipfs-http-response/tsconfig.json",
    "content": "{\n  \"extends\": \"aegir/src/config/tsconfig.aegir.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"emitDeclarationOnly\": true\n  },\n  \"include\": [\n    \"src\",\n    \"test\"\n  ],\n  \"exclude\": [\n    \"dist\",\n    \"node_modules\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../ipfs-core\"\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/CHANGELOG.md",
    "content": "# Change Log\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n### [0.15.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-server-v0.15.0...ipfs-http-server-v0.15.1) (2023-05-25)\n\n\n### Bug Fixes\n\n* add deprecation notice to readmes ([#4362](https://www.github.com/ipfs/js-ipfs/issues/4362)) ([7b79c1b](https://www.github.com/ipfs/js-ipfs/commit/7b79c1b8df5c818dc124b346ea28330455732d5c))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.14.0 to ^0.14.1\n    * ipfs-core-utils bumped from ^0.18.0 to ^0.18.1\n    * ipfs-http-gateway bumped from ^0.13.0 to ^0.13.1\n  * devDependencies\n    * ipfs-http-client bumped from ^60.0.0 to ^60.0.1\n\n## [0.15.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-server-v0.14.0...ipfs-http-server-v0.15.0) (2023-01-11)\n\n\n### ⚠ BREAKING CHANGES\n\n* update multiformats to v11.x.x and related depenendcies (#4277)\n\n### Bug Fixes\n\n* update multiformats to v11.x.x and related depenendcies ([#4277](https://www.github.com/ipfs/js-ipfs/issues/4277)) ([521c84a](https://www.github.com/ipfs/js-ipfs/commit/521c84a958b04d61702577a5adce28519c1b2a3b))\n* use aegir to publish RCs ([#4284](https://www.github.com/ipfs/js-ipfs/issues/4284)) ([6d90cbf](https://www.github.com/ipfs/js-ipfs/commit/6d90cbf321a7dbf4b1084ba20f0c514dc08d8d0a))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.13.0 to ^0.14.0\n    * ipfs-core-utils bumped from ^0.17.0 to ^0.18.0\n    * ipfs-http-gateway bumped from ^0.12.0 to ^0.13.0\n  * devDependencies\n    * ipfs-http-client bumped from ^59.0.0 to ^60.0.0\n\n## [0.14.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-server-v0.13.2...ipfs-http-server-v0.14.0) (2022-10-24)\n\n\n### ⚠ BREAKING CHANGES\n\n* ipfs is now bundled with libp2p@0.40.x which has different config\n\n### Features\n\n* upgrade libp2p to 0.40.x ([#4237](https://www.github.com/ipfs/js-ipfs/issues/4237)) ([0cee4a4](https://www.github.com/ipfs/js-ipfs/commit/0cee4a4c55767022584dcbade0b0b9b43326f9c9))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.12.1 to ^0.13.0\n    * ipfs-core-utils bumped from ^0.16.1 to ^0.17.0\n    * ipfs-http-gateway bumped from ^0.11.1 to ^0.12.0\n  * devDependencies\n    * ipfs-http-client bumped from ^58.0.1 to ^59.0.0\n\n### [0.13.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-server-v0.13.1...ipfs-http-server-v0.13.2) (2022-09-21)\n\n\n### Bug Fixes\n\n* update @multiformats/multiadd to 11.0.0 ([2a830bf](https://www.github.com/ipfs/js-ipfs/commit/2a830bf58a5929fcce51dede871c99f62192fbda))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.12.0 to ^0.12.1\n    * ipfs-core-utils bumped from ^0.16.0 to ^0.16.1\n    * ipfs-http-gateway bumped from ^0.11.0 to ^0.11.1\n  * devDependencies\n    * ipfs-http-client bumped from ^58.0.0 to ^58.0.1\n\n### [0.13.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-server-v0.13.0...ipfs-http-server-v0.13.1) (2022-09-16)\n\n\n### Bug Fixes\n\n* add node memory stats to prometheus output ([#4209](https://www.github.com/ipfs/js-ipfs/issues/4209)) ([b545688](https://www.github.com/ipfs/js-ipfs/commit/b545688264a21a756e470bab022d53762d58562d))\n\n## [0.13.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-server-v0.12.5...ipfs-http-server-v0.13.0) (2022-09-06)\n\n\n### ⚠ BREAKING CHANGES\n\n* update to libp2p@0.38.x (#4151)\n\n### deps\n\n* update to libp2p@0.38.x ([#4151](https://www.github.com/ipfs/js-ipfs/issues/4151)) ([39dbf70](https://www.github.com/ipfs/js-ipfs/commit/39dbf708ec31b263115e44f420651fa4e056a89e))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.11.0 to ^0.12.0\n    * ipfs-core-utils bumped from ^0.15.0 to ^0.16.0\n    * ipfs-http-gateway bumped from ^0.10.0 to ^0.11.0\n  * devDependencies\n    * ipfs-http-client bumped from ^57.0.0 to ^58.0.0\n\n### [0.12.5](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-server-v0.12.4...ipfs-http-server-v0.12.5) (2022-06-24)\n\n\n### Bug Fixes\n\n* make pubsub message types consistent ([#4145](https://www.github.com/ipfs/js-ipfs/issues/4145)) ([00bd3dd](https://www.github.com/ipfs/js-ipfs/commit/00bd3dd0bca7fc705e5e87272972f586d1f161e8))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-http-gateway bumped from ^0.10.3 to ^0.10.4\n  * devDependencies\n    * ipfs-http-client bumped from ^57.0.2 to ^57.0.3\n\n### [0.12.4](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-server-v0.12.3...ipfs-http-server-v0.12.4) (2022-06-22)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.11.0 to ^0.11.1\n    * ipfs-core-utils bumped from ^0.15.0 to ^0.15.1\n    * ipfs-http-gateway bumped from ^0.10.2 to ^0.10.3\n  * devDependencies\n    * ipfs-http-client bumped from ^57.0.1 to ^57.0.2\n\n### [0.12.3](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-server-v0.12.2...ipfs-http-server-v0.12.3) (2022-06-13)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-http-gateway bumped from ^0.10.1 to ^0.10.2\n\n### [0.12.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-server-v0.12.1...ipfs-http-server-v0.12.2) (2022-06-01)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-http-gateway bumped from ^0.10.0 to ^0.10.1\n  * devDependencies\n    * ipfs-http-client bumped from ^57.0.0 to ^57.0.1\n\n### [0.12.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-server-v0.12.0...ipfs-http-server-v0.12.1) (2022-05-30)\n\n\n### Bug Fixes\n\n* base64url encode seq no ([ddfb899](https://www.github.com/ipfs/js-ipfs/commit/ddfb899f2cc46f822da6fe06ce0dc6672b4c89b2))\n* pad seqno if needed ([8eeba69](https://www.github.com/ipfs/js-ipfs/commit/8eeba696686a5c781034803acdb2b6c6906db0e5))\n\n## [0.12.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-server-v0.11.3...ipfs-http-server-v0.12.0) (2022-05-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* This module is now ESM only and there return types of some methods have changed\n\n### Features\n\n* update to libp2p 0.37.x ([#4092](https://www.github.com/ipfs/js-ipfs/issues/4092)) ([74aee8b](https://www.github.com/ipfs/js-ipfs/commit/74aee8b3d78f233c3199a3e9a6c0ac628a31a433))\n\n\n### Bug Fixes\n\n* update to latest libp2p interfaces ([#4111](https://www.github.com/ipfs/js-ipfs/issues/4111)) ([4e93dd5](https://www.github.com/ipfs/js-ipfs/commit/4e93dd5d4f4be397c2b1cd8ae5d17e593493e6a9))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.3 to ^0.11.0\n    * ipfs-core-utils bumped from ^0.14.3 to ^0.15.0\n    * ipfs-http-gateway bumped from ^0.9.2 to ^0.10.0\n  * devDependencies\n    * ipfs-http-client bumped from ^56.0.3 to ^57.0.0\n\n### [0.11.3](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-server-v0.11.2...ipfs-http-server-v0.11.3) (2022-04-20)\n\n\n### Bug Fixes\n\n* exclude fs from bundle ([#4076](https://www.github.com/ipfs/js-ipfs/issues/4076)) ([6c3cb73](https://www.github.com/ipfs/js-ipfs/commit/6c3cb73db7b46211c88431273f61f04463a4f80d))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.2 to ^0.10.3\n    * ipfs-core-utils bumped from ^0.14.2 to ^0.14.3\n    * ipfs-http-gateway bumped from ^0.9.2 to ^0.9.3\n  * devDependencies\n    * ipfs-http-client bumped from ^56.0.2 to ^56.0.3\n\n### [0.11.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-server-v0.11.1...ipfs-http-server-v0.11.2) (2022-03-01)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.1 to ^0.10.2\n    * ipfs-core-utils bumped from ^0.14.1 to ^0.14.2\n    * ipfs-http-gateway bumped from ^0.9.1 to ^0.9.2\n  * devDependencies\n    * ipfs-http-client bumped from ^56.0.1 to ^56.0.2\n\n### [0.11.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-server-v0.11.0...ipfs-http-server-v0.11.1) (2022-02-06)\n\n\n### Bug Fixes\n\n* **dag:** replace custom dag walk with multiformats/traversal ([#3950](https://www.github.com/ipfs/js-ipfs/issues/3950)) ([596b1f4](https://www.github.com/ipfs/js-ipfs/commit/596b1f48a014083b1736e4ad7e746c652d2583b1))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.0 to ^0.10.1\n    * ipfs-core-utils bumped from ^0.14.0 to ^0.14.1\n    * ipfs-http-gateway bumped from ^0.9.0 to ^0.9.1\n  * devDependencies\n    * ipfs-http-client bumped from ^56.0.0 to ^56.0.1\n\n## [0.11.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-http-server-v0.10.0...ipfs-http-server-v0.11.0) (2022-01-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* peerstore methods are now all async, the repo is migrated to v12\n* node 15+ is required\n\n### Features\n\n* libp2p async peerstore ([#4018](https://www.github.com/ipfs/js-ipfs/issues/4018)) ([a6b201a](https://www.github.com/ipfs/js-ipfs/commit/a6b201af2c3697430ab0ebe002dd573d185f1ac0))\n\n\n### Bug Fixes\n\n* remove abort-controller deps ([#4015](https://www.github.com/ipfs/js-ipfs/issues/4015)) ([902e887](https://www.github.com/ipfs/js-ipfs/commit/902e887e1acac87f607324fa7cb5ad4b14aefcf3))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.9.0 to ^0.10.0\n    * ipfs-core-utils bumped from ^0.13.0 to ^0.14.0\n    * ipfs-http-gateway bumped from ^0.8.0 to ^0.9.0\n  * devDependencies\n    * ipfs-http-client bumped from ^55.0.0 to ^56.0.0\n\n## [0.10.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.9.2...ipfs-http-server@0.10.0) (2021-12-15)\n\n\n### Bug Fixes\n\n* **pubsub:** multibase in pubsub http rpc ([#3922](https://github.com/ipfs/js-ipfs/issues/3922)) ([6eeaca4](https://github.com/ipfs/js-ipfs/commit/6eeaca452c36fa13be42d704575c577e4ca938f1))\n\n\n### Features\n\n* dht client ([#3947](https://github.com/ipfs/js-ipfs/issues/3947)) ([62d8ecb](https://github.com/ipfs/js-ipfs/commit/62d8ecbc723e693a2544e69172d99c576d187c23))\n* improve collected metrics ([#3978](https://github.com/ipfs/js-ipfs/issues/3978)) ([33f1034](https://github.com/ipfs/js-ipfs/commit/33f1034a6fc257f1a87de7bb38d876925f61cb5f))\n* update DAG API to match go-ipfs@0.10 changes ([#3917](https://github.com/ipfs/js-ipfs/issues/3917)) ([38c01be](https://github.com/ipfs/js-ipfs/commit/38c01be03b4fd5f401cd9b698cfdb4237d835b01))\n\n\n### BREAKING CHANGES\n\n* **pubsub:** We had to make breaking changes to `pubsub` commands sent over HTTP RPC  to fix data corruption caused by topic names and payload bytes that included `\\n`. More details in https://github.com/ipfs/go-ipfs/issues/7939 and https://github.com/ipfs/go-ipfs/pull/8183\n* `ipfs.dag.put` no longer accepts a `format` arg, it is now `storeCodec` and `inputCodec`.  `'json'` has become `'dag-json'`, `'cbor'` has become `'dag-cbor'` and so on\n* The DHT API has been refactored to return async iterators of query events\n\n\n\n### [0.9.2](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.9.1...ipfs-http-server@0.9.2) (2021-11-24)\n\n**Note:** Version bump only for package ipfs-http-server\n\n\n\n\n\n### [0.9.1](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.9.0...ipfs-http-server@0.9.1) (2021-11-19)\n\n**Note:** Version bump only for package ipfs-http-server\n\n\n\n\n\n## [0.9.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.8.1...ipfs-http-server@0.9.0) (2021-11-12)\n\n\n### Bug Fixes\n\n* do not accept single items for ipfs.add ([#3900](https://github.com/ipfs/js-ipfs/issues/3900)) ([04e3cf3](https://github.com/ipfs/js-ipfs/commit/04e3cf3f46b585c4644cba70516f375e95361f52))\n\n\n### BREAKING CHANGES\n\n* errors will now be thrown if multiple items are passed to `ipfs.add` or single items to `ipfs.addAll` (n.b. you can still pass a list of a single item to `ipfs.addAll`)\n\n\n\n\n\n### [0.8.1](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.8.0...ipfs-http-server@0.8.1) (2021-09-28)\n\n**Note:** Version bump only for package ipfs-http-server\n\n\n\n\n\n## [0.8.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.7.6...ipfs-http-server@0.8.0) (2021-09-24)\n\n\n### Features\n\n* switch to esm ([#3879](https://github.com/ipfs/js-ipfs/issues/3879)) ([9a40109](https://github.com/ipfs/js-ipfs/commit/9a40109632e5b4837eb77a2f57dbc77fbf1fe099))\n\n\n### BREAKING CHANGES\n\n* There are no default exports and everything is now dual published as ESM/CJS\n\n\n\n\n\n### [0.7.6](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.7.5...ipfs-http-server@0.7.6) (2021-09-17)\n\n**Note:** Version bump only for package ipfs-http-server\n\n\n\n\n\n### [0.7.5](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.7.4...ipfs-http-server@0.7.5) (2021-09-17)\n\n**Note:** Version bump only for package ipfs-http-server\n\n\n\n\n\n### [0.7.4](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.7.3...ipfs-http-server@0.7.4) (2021-09-08)\n\n**Note:** Version bump only for package ipfs-http-server\n\n\n\n\n\n### [0.7.3](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.7.2...ipfs-http-server@0.7.3) (2021-09-02)\n\n\n### Bug Fixes\n\n* declare types in .ts files ([#3840](https://github.com/ipfs/js-ipfs/issues/3840)) ([eba5fe6](https://github.com/ipfs/js-ipfs/commit/eba5fe6832858107b3e1ae02c99de674622f12b4))\n* remove use of instanceof for CID class ([#3847](https://github.com/ipfs/js-ipfs/issues/3847)) ([ebbb12d](https://github.com/ipfs/js-ipfs/commit/ebbb12db523c53ce8e4ddae5266cd9acb3504431))\n\n\n\n\n\n### [0.7.2](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.7.1...ipfs-http-server@0.7.2) (2021-08-25)\n\n**Note:** Version bump only for package ipfs-http-server\n\n\n\n\n\n### [0.7.1](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.7.0...ipfs-http-server@0.7.1) (2021-08-17)\n\n**Note:** Version bump only for package ipfs-http-server\n\n\n\n\n\n## [0.7.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.6.1...ipfs-http-server@0.7.0) (2021-08-11)\n\n\n### Bug Fixes\n\n* return rate in/out as number ([#3798](https://github.com/ipfs/js-ipfs/issues/3798)) ([2f3df7a](https://github.com/ipfs/js-ipfs/commit/2f3df7a70fe94d6bdf20947854dc9d0b88cb759a)), closes [#3782](https://github.com/ipfs/js-ipfs/issues/3782)\n\n\n### Features\n\n* make ipfs.get output tarballs ([#3785](https://github.com/ipfs/js-ipfs/issues/3785)) ([1ad6001](https://github.com/ipfs/js-ipfs/commit/1ad60018d39d5b46c484756631e30e1989fd8eba))\n\n\n### BREAKING CHANGES\n\n* rateIn/rateOut are returned as numbers\n* the output type of `ipfs.get` has changed and the `recursive` option has been removed from `ipfs.ls` since it was not supported everywhere\n\n\n\n\n\n### [0.6.1](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.6.0...ipfs-http-server@0.6.1) (2021-07-30)\n\n\n### Bug Fixes\n\n* typo in 'multiformats' type defs ([#3778](https://github.com/ipfs/js-ipfs/issues/3778)) ([1bf35f8](https://github.com/ipfs/js-ipfs/commit/1bf35f8a1622dea1e88bfbd701205df4f96998b1))\n\n\n\n\n\n## [0.6.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.5.2...ipfs-http-server@0.6.0) (2021-07-27)\n\n\n### Features\n\n* implement dag import/export ([#3728](https://github.com/ipfs/js-ipfs/issues/3728)) ([700765b](https://github.com/ipfs/js-ipfs/commit/700765be2634fa5d2d71d8b87cf68c9cd328d2c4)), closes [#2953](https://github.com/ipfs/js-ipfs/issues/2953) [#2745](https://github.com/ipfs/js-ipfs/issues/2745)\n* upgrade to the new multiformats ([#3556](https://github.com/ipfs/js-ipfs/issues/3556)) ([d13d15f](https://github.com/ipfs/js-ipfs/commit/d13d15f022a87d04a35f0f7822142f9cb898479c))\n\n\n### BREAKING CHANGES\n\n* ipld-formats no longer supported, use multiformat BlockCodecs instead\n\nCo-authored-by: Rod Vagg <rod@vagg.org>\nCo-authored-by: achingbrain <alex@achingbrain.net>\n\n\n\n\n\n### [0.5.2](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.5.1...ipfs-http-server@0.5.2) (2021-06-18)\n\n**Note:** Version bump only for package ipfs-http-server\n\n\n\n\n\n### [0.5.1](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.5.0...ipfs-http-server@0.5.1) (2021-06-05)\n\n**Note:** Version bump only for package ipfs-http-server\n\n\n\n\n\n## [0.5.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.4.0...ipfs-http-server@0.5.0) (2021-05-26)\n\n\n### Features\n\n* allow passing the id of a network peer to ipfs.id ([#3386](https://github.com/ipfs/js-ipfs/issues/3386)) ([00fd709](https://github.com/ipfs/js-ipfs/commit/00fd709a7b71e7cf354ea452ebce460dd7375d34))\n\n\n\n\n\n## [0.4.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.3.4...ipfs-http-server@0.4.0) (2021-05-10)\n\n\n### Bug Fixes\n\n* only use public api in http api server ([#3660](https://github.com/ipfs/js-ipfs/issues/3660)) ([61d0981](https://github.com/ipfs/js-ipfs/commit/61d0981c05371c4846dcea3330ac9fb2e810b8fa)), closes [#3639](https://github.com/ipfs/js-ipfs/issues/3639)\n* reject requests when cors origin list is empty ([#3674](https://github.com/ipfs/js-ipfs/issues/3674)) ([0b2d98c](https://github.com/ipfs/js-ipfs/commit/0b2d98c53ba18491d7b99ae9cc0955281146610d))\n\n\n### chore\n\n* upgrade deps with new typedefs ([#3550](https://github.com/ipfs/js-ipfs/issues/3550)) ([a418a52](https://github.com/ipfs/js-ipfs/commit/a418a521574c878d7aabd0ad2fd8d516908a3756))\n\n\n### Features\n\n* support identity hash in block.get + dag.get ([#3616](https://github.com/ipfs/js-ipfs/issues/3616)) ([28ad9ad](https://github.com/ipfs/js-ipfs/commit/28ad9ad6e50abb89a366ecd6b5301e848f0e9962))\n\n\n### BREAKING CHANGES\n\n* all core api methods now have types, some method signatures have changed, named exports are now used by the http, grpc and ipfs client modules\n\n\n\n\n\n### [0.3.4](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.3.3...ipfs-http-server@0.3.4) (2021-03-10)\n\n**Note:** Version bump only for package ipfs-http-server\n\n\n\n\n\n### [0.3.3](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.3.2...ipfs-http-server@0.3.3) (2021-03-09)\n\n\n### Bug Fixes\n\n* update to new aegir ([#3528](https://github.com/ipfs/js-ipfs/issues/3528)) ([49f7880](https://github.com/ipfs/js-ipfs/commit/49f78807d7e26483bd926b45cc7e0f797d77e41b))\n\n\n\n\n\n### [0.3.2](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.3.1...ipfs-http-server@0.3.2) (2021-02-08)\n\n**Note:** Version bump only for package ipfs-http-server\n\n\n\n\n\n### [0.3.1](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.3.0...ipfs-http-server@0.3.1) (2021-02-02)\n\n**Note:** Version bump only for package ipfs-http-server\n\n\n\n\n\n## [0.3.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.2.2...ipfs-http-server@0.3.0) (2021-02-01)\n\n\n### chore\n\n* update deps ([#3514](https://github.com/ipfs/js-ipfs/issues/3514)) ([061d77c](https://github.com/ipfs/js-ipfs/commit/061d77cc03f40af5a3bc3590481e1e5836e7f0d8))\n\n\n### BREAKING CHANGES\n\n* ipfs-repo upgrade requires repo migration to v10\n\n\n\n\n\n### [0.2.2](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.2.1...ipfs-http-server@0.2.2) (2021-01-22)\n\n**Note:** Version bump only for package ipfs-http-server\n\n\n\n\n\n### [0.2.1](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.2.0...ipfs-http-server@0.2.1) (2021-01-20)\n\n**Note:** Version bump only for package ipfs-http-server\n\n\n\n\n\n## [0.2.0](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.1.4...ipfs-http-server@0.2.0) (2021-01-15)\n\n\n### Features\n\n* allow passing a http.Agent to the grpc client ([#3477](https://github.com/ipfs/js-ipfs/issues/3477)) ([c5f0bc5](https://github.com/ipfs/js-ipfs/commit/c5f0bc5eeee15369b7d02901035b04184a8608d2)), closes [#3474](https://github.com/ipfs/js-ipfs/issues/3474)\n\n\n\n\n\n### [0.1.4](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.1.3...ipfs-http-server@0.1.4) (2020-12-16)\n\n\n### Bug Fixes\n\n* fix ipfs.ls() for a single file object ([#3440](https://github.com/ipfs/js-ipfs/issues/3440)) ([f243dd1](https://github.com/ipfs/js-ipfs/commit/f243dd1c37fcb9786d77d129cd9b238457d18a15))\n* regressions introduced by new releases of CID & multicodec ([#3442](https://github.com/ipfs/js-ipfs/issues/3442)) ([b5152d8](https://github.com/ipfs/js-ipfs/commit/b5152d8cc93ecc8d39fc353ea66d7eaf1661e3c0)), closes [/github.com/multiformats/js-cid/commit/0e11f035c9230e7f6d79c159ace9b80de88cb5eb#diff-25a6634263c1b1f6fc4697a04e2b9904ea4b042a89af59dc93ec1f5d44848a26](https://github.com//github.com/multiformats/js-cid/commit/0e11f035c9230e7f6d79c159ace9b80de88cb5eb/issues/diff-25a6634263c1b1f6fc4697a04e2b9904ea4b042a89af59dc93ec1f5d44848a26)\n\n\n\n\n\n### [0.1.3](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.1.2...ipfs-http-server@0.1.3) (2020-11-25)\n\n**Note:** Version bump only for package ipfs-http-server\n\n\n\n\n\n### [0.1.2](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.1.1...ipfs-http-server@0.1.2) (2020-11-16)\n\n\n### Bug Fixes\n\n* align behaviour between go and js for content without paths ([#3385](https://github.com/ipfs/js-ipfs/issues/3385)) ([334873d](https://github.com/ipfs/js-ipfs/commit/334873d3784e2baa2b19f8f69b5aade36715ba03))\n\n\n\n\n\n### [0.1.1](https://github.com/ipfs/js-ipfs/compare/ipfs-http-server@0.1.0...ipfs-http-server@0.1.1) (2020-11-09)\n\n**Note:** Version bump only for package ipfs-http-server\n\n\n\n\n\n# 0.1.0 (2020-10-28)\n\n\n### Bug Fixes\n\n* files ls should return string ([#3352](https://github.com/ipfs/js-ipfs/issues/3352)) ([16ecc74](https://github.com/ipfs/js-ipfs/commit/16ecc7485dfbb1f0c827c5f804974bb804f3dafd)), closes [#3345](https://github.com/ipfs/js-ipfs/issues/3345) [#2939](https://github.com/ipfs/js-ipfs/issues/2939) [#3330](https://github.com/ipfs/js-ipfs/issues/3330) [#2948](https://github.com/ipfs/js-ipfs/issues/2948)\n* use fetch in electron renderer and electron-fetch in main ([#3251](https://github.com/ipfs/js-ipfs/issues/3251)) ([639d71f](https://github.com/ipfs/js-ipfs/commit/639d71f7ac8f66d9633e753a2a6be927e14a5af0))\n\n\n### Features\n\n* enable custom formats for dag put and get ([#3347](https://github.com/ipfs/js-ipfs/issues/3347)) ([3250ff4](https://github.com/ipfs/js-ipfs/commit/3250ff453a1d3275cc4ab746f59f9f70abd5cc5f))\n* type check & generate defs from jsdoc ([#3281](https://github.com/ipfs/js-ipfs/issues/3281)) ([bbcaf34](https://github.com/ipfs/js-ipfs/commit/bbcaf34111251b142273a5675f4754ff68bd9fa0))\n* webui v2.11.4 ([#3317](https://github.com/ipfs/js-ipfs/issues/3317)) ([7f32f7f](https://github.com/ipfs/js-ipfs/commit/7f32f7fd1eb3cffc3cd529827e4af7a8a08e36d9))\n\n\n### BREAKING CHANGES\n\n* types returned by `ipfs.files.ls` are now strings, in line with the docs but different to previous behaviour\n\nCo-authored-by: Geoffrey Cohler <g.cohler@computer.org>"
  },
  {
    "path": "packages/ipfs-http-server/CODE_OF_CONDUCT.md",
    "content": "# Contributor Code of Conduct\n\nThe `js-ipfs` project follows the [`IPFS Community Code of Conduct`](https://github.com/ipfs/community/blob/master/code-of-conduct.md)\n"
  },
  {
    "path": "packages/ipfs-http-server/CONTRIBUTING.md",
    "content": "# Contributing guidelines\n\nIPFS as a project, including js-ipfs and all of its modules, follows the [standard IPFS Community contributing guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md).\n\nWe also adhere to the [IPFS JavaScript Community contributing guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING_JS.md) which provide additional information of how to collaborate and contribute in the JavaScript implementation of IPFS.\n\nWe appreciate your time and attention for going over these. Please open an issue on [ipfs/community](https://github.com/ipfs/community) if you have any question.\n\nThank you.\n"
  },
  {
    "path": "packages/ipfs-http-server/COPYRIGHT",
    "content": "This project is transitioning from an MIT-only license to a dual MIT/Apache-2.0 license.\nUnless otherwise noted, all code contributed prior to 2019-11-21 and not contributed by\na user listed in [this signoff issue](https://github.com/ipfs/js-ipfs/issues/2624) is\nlicensed under MIT-only. All new contributions (and past contributions since 2019-11-21)\nare licensed under a dual MIT/Apache-2.0 license.\n"
  },
  {
    "path": "packages/ipfs-http-server/LICENSE",
    "content": "This project is dual licensed under MIT and Apache-2.0.\n\nMIT: https://www.opensource.org/licenses/mit\nApache-2.0: https://www.apache.org/licenses/license-2.0\n"
  },
  {
    "path": "packages/ipfs-http-server/LICENSE-APACHE",
    "content": "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\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless 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.\n"
  },
  {
    "path": "packages/ipfs-http-server/LICENSE-MIT",
    "content": "The MIT License (MIT)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "packages/ipfs-http-server/README.md",
    "content": "> # ⛔️ DEPRECATED: [js-IPFS](https://github.com/ipfs/js-ipfs) has been superseded by [Helia](https://github.com/ipfs/helia)\n>\n> 📚 [Learn more about this deprecation](https://github.com/ipfs/js-ipfs/issues/4336) or [how to migrate](https://github.com/ipfs/helia/wiki/Migrating-from-js-IPFS)\n>\n> ⚠️ If you continue using this repo, please note that security fixes will not be provided\n\n# ipfs-http-server <!-- omit in toc -->\n\n[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech)\n[![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech)\n[![codecov](https://img.shields.io/codecov/c/github/ipfs/js-ipfs.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipfs)\n[![CI](https://img.shields.io/github/actions/workflow/status/ipfs/js-ipfs/test.yml?branch=master\\&style=flat-square)](https://github.com/ipfs/js-ipfs/actions/workflows/test.yml?query=branch%3Amaster)\n\n> JavaScript implementation of the IPFS specification\n\n## Table of contents <!-- omit in toc -->\n\n- [Install](#install)\n- [License](#license)\n- [Contribute](#contribute)\n\n## Install\n\n```console\n$ npm i ipfs-http-server\n```\n\n```console\n$ npm install -g ipfs\n// npm install output\n$ jsipfs daemon\n$ curl -X POST http://localhost:5002/api/v0/id | npx json\n{\n\"ID\": \"QmdY8wcMj...\",\n\"PublicKey\": \"CAASpgI...\",\n...\n```\n\n## License\n\nLicensed under either of\n\n- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / <http://www.apache.org/licenses/LICENSE-2.0>)\n- MIT ([LICENSE-MIT](LICENSE-MIT) / <http://opensource.org/licenses/MIT>)\n\n## Contribute\n\nContributions welcome! Please check out [the issues](https://github.com/ipfs/js-ipfs/issues).\n\nAlso 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.\n\nPlease 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).\n\nUnless 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.\n\n[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md)\n"
  },
  {
    "path": "packages/ipfs-http-server/package.json",
    "content": "{\n  \"name\": \"ipfs-http-server\",\n  \"version\": \"0.15.1\",\n  \"description\": \"JavaScript implementation of the IPFS specification\",\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"homepage\": \"https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-http-server#readme\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ipfs/js-ipfs.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ipfs/js-ipfs/issues\"\n  },\n  \"keywords\": [\n    \"IPFS\"\n  ],\n  \"engines\": {\n    \"node\": \">=16.0.0\",\n    \"npm\": \">=7.0.0\"\n  },\n  \"type\": \"module\",\n  \"types\": \"./dist/src/index.d.ts\",\n  \"typesVersions\": {\n    \"*\": {\n      \"*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ],\n      \"src/*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ]\n    }\n  },\n  \"files\": [\n    \"src\",\n    \"dist\",\n    \"!dist/test\",\n    \"!**/*.tsbuildinfo\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/src/index.d.ts\",\n      \"import\": \"./src/index.js\"\n    }\n  },\n  \"eslintConfig\": {\n    \"extends\": \"ipfs\",\n    \"parserOptions\": {\n      \"sourceType\": \"module\"\n    }\n  },\n  \"scripts\": {\n    \"lint\": \"aegir lint\",\n    \"test\": \"aegir test -t node\",\n    \"test:node\": \"aegir test -t node --cov\",\n    \"clean\": \"aegir clean\",\n    \"dep-check\": \"aegir dep-check -i ipfs-http-client -i ipfs-core-types -i @libp2p/interfaces -i ipfs-unixfs -i @libp2p/interface-dht\",\n    \"build\": \"aegir build --no-bundle\",\n    \"prepublishOnly\": \"node scripts/update-version.js\"\n  },\n  \"dependencies\": {\n    \"@hapi/boom\": \"^9.1.0\",\n    \"@hapi/content\": \"^5.0.2\",\n    \"@hapi/hapi\": \"^20.0.0\",\n    \"@ipld/dag-pb\": \"^4.0.0\",\n    \"@libp2p/interface-dht\": \"^2.0.0\",\n    \"@libp2p/interfaces\": \"^3.2.0\",\n    \"@libp2p/logger\": \"^2.0.5\",\n    \"@libp2p/peer-id\": \"^2.0.0\",\n    \"@multiformats/multiaddr\": \"^11.1.5\",\n    \"@multiformats/uri-to-multiaddr\": \"^7.0.0\",\n    \"any-signal\": \"^3.0.0\",\n    \"dlv\": \"^1.1.3\",\n    \"hapi-pino\": \"^8.5.0\",\n    \"ipfs-core-types\": \"^0.14.1\",\n    \"ipfs-core-utils\": \"^0.18.1\",\n    \"ipfs-http-gateway\": \"^0.13.1\",\n    \"ipfs-unixfs\": \"^9.0.0\",\n    \"it-all\": \"^2.0.0\",\n    \"it-drain\": \"^2.0.0\",\n    \"it-filter\": \"^2.0.0\",\n    \"it-last\": \"^2.0.0\",\n    \"it-map\": \"^2.0.0\",\n    \"it-merge\": \"^2.0.0\",\n    \"it-multipart\": \"^3.0.0\",\n    \"it-pipe\": \"^2.0.3\",\n    \"it-pushable\": \"^3.0.0\",\n    \"it-reduce\": \"^2.0.0\",\n    \"joi\": \"^17.2.1\",\n    \"just-safe-set\": \"^4.0.2\",\n    \"multiformats\": \"^11.0.0\",\n    \"parse-duration\": \"^1.0.0\",\n    \"stream-to-it\": \"^0.2.2\",\n    \"timeout-abort-controller\": \"^3.0.0\",\n    \"uint8arrays\": \"^4.0.2\"\n  },\n  \"devDependencies\": {\n    \"@types/hapi-pino\": \"^8.0.1\",\n    \"@types/hapi__hapi\": \"^20.0.5\",\n    \"@types/node\": \"^18.0.0\",\n    \"aegir\": \"^37.11.0\",\n    \"err-code\": \"^3.0.1\",\n    \"form-data\": \"^4.0.0\",\n    \"ipfs-http-client\": \"^60.0.1\",\n    \"iso-random-stream\": \"^2.0.2\",\n    \"it-first\": \"^2.0.0\",\n    \"it-to-buffer\": \"^3.0.0\",\n    \"qs\": \"^6.9.4\",\n    \"sinon\": \"^15.0.1\",\n    \"stream-to-promise\": \"^3.0.0\"\n  },\n  \"optionalDependencies\": {\n    \"prom-client\": \"^14.0.1\"\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/scripts/update-version.js",
    "content": "import { readFile, writeFile } from 'fs/promises'\n\nconst pkg = JSON.parse(\n  await readFile(\n    new URL('../package.json', import.meta.url)\n  )\n)\n\nawait writeFile(\n  new URL('../src/version.js', import.meta.url),\n  `\nexport const ipfsHttpClient = '${pkg.devDependencies['ipfs-http-client']}'\n`\n)\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/bitswap.js",
    "content": "import Joi from '../../utils/joi.js'\n\nexport const wantlistResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        peer: Joi.peerId(),\n        cidBase: Joi.string().default('base58btc'),\n        timeout: Joi.timeout()\n      })\n        .rename('cid-base', 'cidBase', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        peer,\n        cidBase,\n        timeout\n      }\n    } = request\n\n    let list\n\n    if (peer) {\n      list = await ipfs.bitswap.wantlistForPeer(peer, {\n        signal,\n        timeout\n      })\n    } else {\n      list = await ipfs.bitswap.wantlist({\n        signal,\n        timeout\n      })\n    }\n\n    const base = await ipfs.bases.getBase(cidBase)\n\n    return h.response({\n      Keys: list.map(cid => ({\n        '/': cid.toString(base.encoder)\n      }))\n    })\n  }\n}\n\nexport const statResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        cidBase: Joi.string().default('base58btc'),\n        timeout: Joi.timeout()\n      })\n        .rename('cid-base', 'cidBase', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        cidBase,\n        timeout\n      }\n    } = request\n\n    const stats = await ipfs.bitswap.stat({\n      signal,\n      timeout\n    })\n\n    const base = await ipfs.bases.getBase(cidBase)\n\n    return h.response({\n      ProvideBufLen: stats.provideBufLen,\n      BlocksReceived: stats.blocksReceived.toString(),\n      Wantlist: stats.wantlist.map(cid => ({\n        '/': cid.toString(base.encoder)\n      })),\n      Peers: stats.peers,\n      DupBlksReceived: stats.dupBlksReceived.toString(),\n      DupDataReceived: stats.dupDataReceived.toString(),\n      DataReceived: stats.dataReceived.toString(),\n      BlocksSent: stats.blocksSent.toString(),\n      DataSent: stats.dataSent.toString()\n    })\n  }\n}\n\nexport const unwantResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        cid: Joi.cid().required(),\n        cidBase: Joi.string().default('base58btc'),\n        timeout: Joi.timeout()\n      })\n        .rename('arg', 'cid', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('cid-base', 'cidBase', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        cid,\n        cidBase,\n        timeout\n      }\n    } = request\n\n    await ipfs.bitswap.unwant(cid, {\n      signal,\n      timeout\n    })\n\n    const base = await ipfs.bases.getBase(cidBase)\n\n    return h.response({ key: cid.toString(base.encoder) })\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/block.js",
    "content": "import { multipartRequestParser } from '../../utils/multipart-request-parser.js'\nimport Joi from '../../utils/joi.js'\nimport Boom from '@hapi/boom'\nimport all from 'it-all'\nimport { pipe } from 'it-pipe'\nimport map from 'it-map'\nimport { streamResponse } from '../../utils/stream-response.js'\n\nexport const getResource = {\n  options: {\n    payload: {\n      parse: false,\n      output: 'stream'\n    },\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        cid: Joi.cid().required(),\n        timeout: Joi.timeout()\n      })\n        .rename('arg', 'cid', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        cid,\n        timeout\n      }\n    } = request\n\n    let block\n    try {\n      block = await ipfs.block.get(cid, {\n        timeout,\n        signal\n      })\n    } catch (/** @type {any} */ err) {\n      throw Boom.boomify(err, { message: 'Failed to get block' })\n    }\n\n    if (!block) {\n      throw Boom.notFound('Block was unwanted before it could be remotely retrieved')\n    }\n\n    return h.response(Buffer.from(block.buffer, block.byteOffset, block.byteLength)).header('X-Stream-Output', '1')\n  }\n}\nexport const putResource = {\n  options: {\n    payload: {\n      parse: false,\n      output: 'stream'\n    },\n    pre: [{\n      assign: 'args',\n      /**\n       * @param {import('../../types').Request} request\n       * @param {import('@hapi/hapi').ResponseToolkit} _h\n       */\n      method: async (request, _h) => {\n        if (!request.payload) {\n          throw Boom.badRequest(\"File argument 'data' is required\")\n        }\n\n        let data\n\n        for await (const part of multipartRequestParser(request.raw.req)) {\n          if (part.type !== 'file') {\n            continue\n          }\n\n          data = Buffer.concat(await all(part.content))\n        }\n\n        if (!data) {\n          throw Boom.badRequest(\"File argument 'data' is required\")\n        }\n\n        return { data }\n      }\n    }],\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        cidBase: Joi.string().default('base32'),\n        format: Joi.string().default('dag-pb'),\n        mhtype: Joi.string().default('sha2-256'),\n        mhlen: Joi.number(),\n        pin: Joi.bool().default(false),\n        version: Joi.number().default(0),\n        timeout: Joi.timeout()\n      })\n        .rename('cid-base', 'cidBase', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      pre: {\n        args: {\n          data\n        }\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        mhtype,\n        format,\n        version,\n        pin,\n        timeout,\n        cidBase\n      }\n    } = request\n\n    const codec = format === 'v0' ? 'dag-pb' : format\n    const cidVersion = codec === 'dag-pb' && mhtype === 'sha2-256' ? version : 1\n    let cid\n\n    try {\n      cid = await ipfs.block.put(data, {\n        mhtype,\n        format: codec,\n        version: cidVersion,\n        pin,\n        signal,\n        timeout\n      })\n    } catch (/** @type {any} */ err) {\n      throw Boom.boomify(err, { message: 'Failed to put block' })\n    }\n\n    const base = await ipfs.bases.getBase(cidVersion === 0 ? 'base58btc' : cidBase)\n\n    return h.response({\n      Key: cid.toString(base.encoder),\n      Size: data.length\n    })\n  }\n}\n\nexport const rmResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        cids: Joi.array().single().items(Joi.cid()).min(1).required(),\n        force: Joi.boolean().default(false),\n        quiet: Joi.boolean().default(false),\n        cidBase: Joi.string().default('base58btc'),\n        timeout: Joi.timeout()\n      })\n        .rename('cid-base', 'cidBase', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('arg', 'cids', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        cids,\n        force,\n        quiet,\n        timeout,\n        cidBase\n      }\n    } = request\n\n    return streamResponse(request, h, () => pipe(\n      ipfs.block.rm(cids, {\n        force,\n        quiet,\n        timeout,\n        signal\n      }),\n      async function * (source) {\n        const base = await ipfs.bases.getBase(cidBase)\n\n        yield * map(source, ({ cid, error }) => ({ Hash: cid.toString(base.encoder), Error: error ? error.message : undefined }))\n      }\n    ))\n  }\n}\n\nexport const statResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        cid: Joi.cid().required(),\n        cidBase: Joi.string().default('base58btc'),\n        timeout: Joi.timeout()\n      })\n        .rename('arg', 'cid', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('cid-base', 'cidBase', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        cid,\n        cidBase,\n        timeout\n      }\n    } = request\n\n    let stats\n    try {\n      stats = await ipfs.block.stat(cid, {\n        timeout,\n        signal\n      })\n    } catch (/** @type {any} */ err) {\n      throw Boom.boomify(err, { message: 'Failed to get block stats' })\n    }\n\n    const base = await ipfs.bases.getBase(cidBase)\n\n    return h.response({\n      Key: stats.cid.toString(base.encoder),\n      Size: stats.size\n    })\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/bootstrap.js",
    "content": "import Boom from '@hapi/boom'\nimport Joi from '../../utils/joi.js'\n\nexport const listResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        timeout: Joi.timeout()\n      })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  handler: async (request, h) => {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        timeout\n      }\n    } = request\n\n    const list = await ipfs.bootstrap.list({\n      timeout,\n      signal\n    })\n    return h.response(list)\n  }\n}\n\nexport const addResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        addr: Joi.multiaddr(),\n        default: Joi.boolean().default(false),\n        timeout: Joi.timeout()\n      })\n        .rename('arg', 'addr', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        addr,\n        default: def,\n        timeout\n      }\n    } = request\n\n    let list\n\n    if (def) {\n      list = await ipfs.bootstrap.reset({\n        signal,\n        timeout\n      })\n    } else if (addr) {\n      list = await ipfs.bootstrap.add(addr, {\n        signal,\n        timeout\n      })\n    } else {\n      throw Boom.badRequest('arg is required')\n    }\n\n    return h.response(list)\n  }\n}\n\nexport const addDefaultResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        timeout: Joi.timeout()\n      })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  handler: async (request, h) => {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        timeout\n      }\n    } = request\n\n    const list = await ipfs.bootstrap.reset({\n      signal,\n      timeout\n    })\n    return h.response(list)\n  }\n}\n\nexport const rmResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        addr: Joi.multiaddr(),\n        all: Joi.boolean().default(false),\n        timeout: Joi.timeout()\n      })\n        .rename('arg', 'addr', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        addr,\n        all,\n        timeout\n      }\n    } = request\n\n    let list\n\n    if (all) {\n      list = await ipfs.bootstrap.clear({\n        signal,\n        timeout\n      })\n    } else if (addr) {\n      list = await ipfs.bootstrap.rm(addr, {\n        signal,\n        timeout\n      })\n    } else {\n      throw Boom.badRequest('arg is required')\n    }\n\n    return h.response(list)\n  }\n}\n\nexport const rmAllResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        timeout: Joi.timeout()\n      })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  handler: async (request, h) => {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        timeout\n      }\n    } = request\n\n    const list = await ipfs.bootstrap.clear({\n      signal,\n      timeout\n    })\n\n    return h.response(list)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/config.js",
    "content": "import { logger } from '@libp2p/logger'\nimport get from 'dlv'\nimport set from 'just-safe-set'\nimport { multipartRequestParser } from '../../utils/multipart-request-parser.js'\nimport Boom from '@hapi/boom'\nimport Joi from '../../utils/joi.js'\nimport all from 'it-all'\n\nconst log = logger('ipfs:http-api:config')\n\nexport const getOrSetResource = {\n  options: {\n    payload: {\n      parse: false,\n      output: 'stream'\n    },\n    pre: [{\n      assign: 'args',\n      /**\n       * @param {import('../../types').Request} request\n       * @param {import('@hapi/hapi').ResponseToolkit} _h\n       */\n      method: (request, _h) => {\n        /**\n         * @param {{ key: string, value: any }} args\n         */\n        const parseValue = (args) => {\n          if (request.query.bool) {\n            args.value = args.value === 'true'\n          } else if (request.query.json) {\n            try {\n              args.value = JSON.parse(args.value)\n            } catch (/** @type {any} */ err) {\n              log.error(err)\n              throw Boom.badRequest('failed to unmarshal json. ' + err)\n            }\n          }\n\n          return args\n        }\n\n        if (request.query.arg instanceof Array) {\n          return parseValue({\n            key: request.query.arg[0],\n            value: request.query.arg[1]\n          })\n        }\n\n        if (request.params.key) {\n          return parseValue({\n            key: request.params.key,\n            value: request.query.arg\n          })\n        }\n\n        if (!request.query.arg) {\n          throw Boom.badRequest(\"Argument 'key' is required\")\n        }\n\n        return { key: request.query.arg }\n      }\n    }],\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        arg: Joi.array().single(),\n        key: Joi.string(),\n        bool: Joi.boolean().truthy(''),\n        json: Joi.boolean().truthy(''),\n        timeout: Joi.timeout()\n      })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      pre: {\n        args: {\n          key,\n          value\n        }\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        timeout\n      }\n    } = request\n\n    if (value && value.type === 'Buffer' && Array.isArray(value.data)) {\n      // serialized node buffer?\n      throw Boom.badRequest('Invalid value')\n    }\n\n    let originalConfig\n    try {\n      originalConfig = await ipfs.config.getAll({\n        signal,\n        timeout\n      })\n    } catch (/** @type {any} */ err) {\n      throw Boom.boomify(err, { message: 'Failed to get config value' })\n    }\n\n    if (value === undefined) {\n      // Get the value of a given key\n      const existingValue = get(originalConfig, key)\n\n      if (existingValue === undefined) {\n        throw Boom.notFound('Failed to get config value: key has no attributes')\n      }\n\n      return h.response({\n        Key: key,\n        Value: existingValue\n      })\n    }\n\n    // Set the new value of a given key\n    const result = set(originalConfig, key, value)\n    if (!result) {\n      throw Boom.badRequest('Failed to set config value')\n    }\n\n    try {\n      await ipfs.config.replace(originalConfig, {\n        signal,\n        timeout\n      })\n    } catch (/** @type {any} */ err) {\n      throw Boom.boomify(err, { message: 'Failed to replace config value' })\n    }\n\n    return h.response({\n      Key: key,\n      Value: value\n    })\n  }\n}\n\nexport const getResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        timeout: Joi.timeout()\n      })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  handler: async (request, h) => {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        timeout\n      }\n    } = request\n\n    let config\n    try {\n      config = await ipfs.config.getAll({\n        signal,\n        timeout\n      })\n    } catch (/** @type {any} */ err) {\n      throw Boom.boomify(err, { message: 'Failed to get config value' })\n    }\n\n    return h.response({\n      Value: config\n    })\n  }\n}\n\nexport const showResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        timeout: Joi.timeout()\n      })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  handler: async (request, h) => {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        timeout\n      }\n    } = request\n\n    let config\n    try {\n      config = await ipfs.config.getAll({\n        signal,\n        timeout\n      })\n    } catch (/** @type {any} */ err) {\n      throw Boom.boomify(err, { message: 'Failed to get config value' })\n    }\n\n    return h.response(config)\n  }\n}\n\nexport const replaceResource = {\n  options: {\n    payload: {\n      parse: false,\n      output: 'stream'\n    },\n    pre: [{\n      assign: 'args',\n      /**\n       * @param {import('../../types').Request} request\n       * @param {import('@hapi/hapi').ResponseToolkit} _h\n       */\n      method: async (request, _h) => {\n        if (!request.payload) {\n          throw Boom.badRequest(\"Argument 'file' is required\")\n        }\n\n        let file\n\n        for await (const part of multipartRequestParser(request.raw.req)) {\n          if (part.type !== 'file') {\n            continue\n          }\n\n          file = Buffer.concat(await all(part.content))\n        }\n\n        if (!file) {\n          throw Boom.badRequest(\"Argument 'file' is required\")\n        }\n\n        try {\n          return { config: JSON.parse(file.toString('utf8')) }\n        } catch (/** @type {any} */ err) {\n          throw Boom.boomify(err, { message: 'Failed to decode file as config' })\n        }\n      }\n    }],\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        timeout: Joi.timeout()\n      })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      pre: {\n        args: {\n          config\n        }\n      },\n      query: {\n        timeout\n      }\n    } = request\n\n    try {\n      await ipfs.config.replace(config, {\n        signal,\n        timeout\n      })\n    } catch (/** @type {any} */ err) {\n      throw Boom.boomify(err, { message: 'Failed to save config' })\n    }\n\n    return h.response()\n  }\n}\n\nexport const profilesApplyResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        profile: Joi.string().required(),\n        dryRun: Joi.boolean().default(false),\n        timeout: Joi.timeout()\n      })\n        .rename('dry-run', 'dryRun', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('arg', 'profile', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  handler: async function (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        profile,\n        dryRun,\n        timeout\n      }\n    } = request\n\n    try {\n      const diff = await ipfs.config.profiles.apply(profile, {\n        dryRun,\n        signal,\n        timeout\n      })\n\n      return h.response({ OldCfg: diff.original, NewCfg: diff.updated })\n    } catch (/** @type {any} */ err) {\n      throw Boom.boomify(err, { message: 'Failed to apply profile' })\n    }\n  }\n}\n\nexport const profilesListResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        timeout: Joi.timeout()\n      })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  handler: async function (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        timeout\n      }\n    } = request\n\n    const list = await ipfs.config.profiles.list({\n      signal,\n      timeout\n    })\n\n    return h.response(\n      list.map(profile => ({\n        Name: profile.name,\n        Description: profile.description\n      }))\n    )\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/dag.js",
    "content": "import { multipartRequestParser } from '../../utils/multipart-request-parser.js'\nimport { streamResponse } from '../../utils/stream-response.js'\nimport Joi from '../../utils/joi.js'\nimport Boom from '@hapi/boom'\nimport all from 'it-all'\nimport { pipe } from 'it-pipe'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\n\n/**\n * @param {undefined | Uint8Array | Record<string, any>} obj\n * @param {'base64pad' | 'base16' | 'utf8'} encoding\n */\nconst encodeBufferKeys = (obj, encoding) => {\n  if (!obj) {\n    return obj\n  }\n\n  if (obj instanceof Uint8Array) {\n    return uint8ArrayToString(obj, encoding)\n  }\n\n  Object.keys(obj).forEach(key => {\n    if (obj[key] instanceof Uint8Array) {\n      obj[key] = uint8ArrayToString(obj[key], encoding)\n\n      return\n    }\n\n    if (typeof obj[key] === 'object') {\n      obj[key] = encodeBufferKeys(obj[key], encoding)\n    }\n  })\n\n  return obj\n}\n\nexport const getResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        arg: Joi.cidAndPath().required(),\n        dataEncoding: Joi.string()\n          .valid('ascii', 'base64pad', 'base16', 'utf8')\n          .replace(/text/, 'ascii')\n          .replace(/base64/, 'base64pad')\n          .replace(/hex/, 'base16')\n          .default('utf8'),\n        timeout: Joi.timeout()\n      })\n        .rename('data-encoding', 'dataEncoding', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        arg: {\n          cid,\n          path\n        },\n        dataEncoding,\n        timeout\n      }\n    } = request\n\n    let result\n\n    try {\n      result = await ipfs.dag.get(cid, {\n        path,\n        signal,\n        timeout\n      })\n    } catch (/** @type {any} */ err) {\n      throw Boom.badRequest(err)\n    }\n\n    let value = result.value\n\n    if (!(result.value instanceof Uint8Array) && result.value.toJSON) {\n      value = result.value.toJSON()\n    }\n\n    try {\n      result.value = encodeBufferKeys(value, dataEncoding)\n    } catch (/** @type {any} */ err) {\n      throw Boom.boomify(err)\n    }\n\n    return h.response(result.value)\n  }\n}\n\nexport const putResource = {\n  options: {\n    payload: {\n      parse: false,\n      output: 'stream'\n    },\n    pre: [{\n      assign: 'args',\n      /**\n       * @param {import('../../types').Request} request\n       * @param {import('@hapi/hapi').ResponseToolkit} _h\n       */\n      method: async (request, _h) => {\n        if (!request.payload) {\n          throw Boom.badRequest(\"File argument 'object data' is required\")\n        }\n\n        if (!request.headers['content-type']) {\n          throw Boom.badRequest(\"File argument 'object data' is required\")\n        }\n\n        let data\n\n        for await (const part of multipartRequestParser(request.raw.req)) {\n          if (part.type !== 'file') {\n            continue\n          }\n\n          data = Buffer.concat(await all(part.content))\n        }\n\n        if (!data) {\n          throw Boom.badRequest(\"File argument 'object data' is required\")\n        }\n\n        return {\n          data,\n          hashAlg: request.query.hash\n        }\n      }\n    }],\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        storeCodec: Joi.string().default('dag-cbor'),\n        inputCodec: Joi.string().default('dag-json'),\n        pin: Joi.boolean().default(false),\n        hash: Joi.string().default('sha2-256'),\n        cidBase: Joi.string().default('base32'),\n        version: Joi.number().integer().valid(0, 1).default(1),\n        timeout: Joi.timeout()\n      })\n        .rename('store-codec', 'storeCodec', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('input-codec', 'inputCodec', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('cid-base', 'cidBase', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      pre: {\n        args: {\n          data,\n          hashAlg\n        }\n      },\n      query: {\n        inputCodec,\n        storeCodec,\n        pin,\n        cidBase,\n        version,\n        timeout\n      }\n    } = request\n\n    const cidVersion = storeCodec === 'dag-pb' && hashAlg === 'sha2-256' ? version : 1\n\n    let cid\n\n    try {\n      cid = await ipfs.dag.put(data, {\n        inputCodec,\n        storeCodec,\n        hashAlg,\n        version: cidVersion,\n        pin,\n        signal,\n        timeout\n      })\n    } catch (/** @type {any} */ err) {\n      throw Boom.boomify(err, { message: 'Failed to put node' })\n    }\n\n    const base = await ipfs.bases.getBase(cidVersion === 0 ? 'base58btc' : cidBase)\n\n    return h.response({\n      Cid: {\n        '/': cid.toString(base.encoder)\n      }\n    })\n  }\n}\n\nexport const resolveResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        arg: Joi.cidAndPath().required(),\n        cidBase: Joi.string().default('base58btc'),\n        timeout: Joi.timeout(),\n        path: Joi.string()\n      })\n        .rename('cid-base', 'cidBase', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        arg: {\n          cid,\n          path\n        },\n        cidBase,\n        timeout,\n        path: queryPath\n      }\n    } = request\n\n    // to be consistent with go we need to return the CID to the last node we've traversed\n    // along with the path inside that node as the remainder path\n    try {\n      const result = await ipfs.dag.resolve(cid, {\n        path: path || queryPath,\n        signal,\n        timeout\n      })\n\n      const base = await ipfs.bases.getBase(cidBase)\n\n      return h.response({\n        Cid: {\n          '/': result.cid.toString(base.encoder)\n        },\n        RemPath: result.remainderPath\n      })\n    } catch (/** @type {any} */ err) {\n      throw Boom.boomify(err)\n    }\n  }\n}\n\nexport const exportResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        root: Joi.cid().required(),\n        timeout: Joi.timeout()\n      })\n        .rename('arg', 'root', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        root,\n        timeout\n      }\n    } = request\n\n    return streamResponse(request, h, () => ipfs.dag.export(root, {\n      timeout,\n      signal\n    }), {\n      onError (err) {\n        err.message = 'Failed to export DAG: ' + err.message\n      }\n    })\n  }\n}\n\nexport const importResource = {\n  options: {\n    payload: {\n      parse: false,\n      output: 'stream',\n      maxBytes: Number.MAX_SAFE_INTEGER\n    },\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        pinRoots: Joi.boolean().default(true),\n        timeout: Joi.timeout()\n      })\n        .rename('pin-roots', 'pinRoots', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        pinRoots,\n        timeout\n      }\n    } = request\n\n    let filesParsed = false\n\n    return streamResponse(request, h, () => pipe(\n      multipartRequestParser(request.raw.req),\n      async function * (source) {\n        for await (const entry of source) {\n          if (entry.type !== 'file') {\n            throw Boom.badRequest('Unexpected upload type')\n          }\n\n          filesParsed = true\n          yield entry.content\n        }\n      },\n      async function * (source) {\n        yield * ipfs.dag.import(source, {\n          pinRoots,\n          timeout,\n          signal\n        })\n      },\n      async function * (source) {\n        for await (const res of source) {\n          yield {\n            Root: {\n              Cid: {\n                '/': res.root.cid.toString()\n              },\n              PinErrorMsg: res.root.pinErrorMsg\n            }\n          }\n        }\n      }\n    ), {\n      onError (err) {\n        err.message = 'Failed to import DAG: ' + err.message\n      },\n      onEnd () {\n        if (!filesParsed) {\n          throw Boom.badRequest(\"File argument 'data' is required.\")\n        }\n      }\n    })\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/dht.js",
    "content": "import Joi from '../../utils/joi.js'\nimport { streamResponse } from '../../utils/stream-response.js'\nimport { TimeoutController } from 'timeout-abort-controller'\nimport { anySignal } from 'any-signal'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\nimport { multipartRequestParser } from '../../utils/multipart-request-parser.js'\nimport all from 'it-all'\nimport Boom from '@hapi/boom'\n\n/**\n * @typedef {import('ipfs-core-types/src/dht').QueryEvent} QueryEvent\n * @typedef {import('@libp2p/interface-peer-id').PeerId} PeerId\n */\n\n/**\n * @param {QueryEvent} event\n */\nfunction mapQueryEvent (event) {\n  let id\n  let extra = ''\n  const type = event.type\n  let responses = null\n\n  if (event.name === 'PEER_RESPONSE') {\n    id = event.from.toString()\n    responses = event.closer.map(peerData => ({\n      ID: peerData.id,\n      Addrs: peerData.multiaddrs\n    }))\n  } else if (event.name === 'QUERY_ERROR') {\n    extra = event.error.message\n  } else if (event.name === 'PROVIDER') {\n    responses = event.providers.map(peerData => ({\n      ID: peerData.id,\n      Addrs: peerData.multiaddrs\n    }))\n  } else if (event.name === 'VALUE') {\n    extra = uint8ArrayToString(event.value, 'base64pad')\n  } else if (event.name === 'ADDING_PEER') {\n    responses = [{\n      ID: event.peer,\n      Addrs: []\n    }]\n  } else if (event.name === 'DIALING_PEER') {\n    id = event.peer.toString()\n  } else if (event.name === 'FINAL_PEER') {\n    id = event.peer.id.toString()\n    responses = [{\n      ID: event.peer.id,\n      Addrs: event.peer.multiaddrs\n    }]\n  }\n\n  return {\n    Extra: extra,\n    ID: id,\n    Type: type,\n    Responses: responses\n  }\n}\n\nexport const findPeerResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        peerId: Joi.peerId().required(),\n        timeout: Joi.timeout()\n      })\n        .rename('arg', 'peerId', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        peerId,\n        timeout\n      }\n    } = request\n\n    const signals = [signal]\n    /** @type {TimeoutController | undefined} */\n    let timeoutController\n\n    if (timeout != null) {\n      timeoutController = new TimeoutController(timeout)\n      signals.push(timeoutController.signal)\n    }\n\n    return streamResponse(request, h, () => {\n      return (async function * () {\n        for await (const event of ipfs.dht.findPeer(peerId, {\n          signal: anySignal(signals)\n        })) {\n          yield mapQueryEvent(event)\n        }\n\n        if (timeoutController) {\n          timeoutController.clear()\n        }\n      }())\n    })\n  }\n}\n\nexport const findProvsResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        cid: Joi.cid().required(),\n        numProviders: Joi.number().integer().default(20),\n        timeout: Joi.timeout()\n      })\n        .rename('arg', 'cid', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('num-providers', 'numProviders', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        cid,\n        numProviders,\n        timeout\n      }\n    } = request\n\n    const signals = [signal]\n    /** @type {TimeoutController | undefined} */\n    let timeoutController\n\n    if (timeout != null) {\n      timeoutController = new TimeoutController(timeout)\n      signals.push(timeoutController.signal)\n    }\n\n    const providers = new Set()\n\n    return streamResponse(request, h, () => {\n      return (async function * () {\n        for await (const event of ipfs.dht.findProvs(cid, {\n          signal: anySignal(signals)\n        })) {\n          if (event.name === 'PROVIDER') {\n            event.providers.forEach(peerData => {\n              providers.add(peerData.id)\n            })\n          }\n\n          yield mapQueryEvent(event)\n\n          if (providers.size >= numProviders) {\n            break\n          }\n        }\n\n        if (timeoutController) {\n          timeoutController.clear()\n        }\n      }())\n    })\n  }\n}\n\nexport const getResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        key: Joi.string().required(),\n        timeout: Joi.timeout()\n      })\n        .rename('arg', 'key', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        key,\n        timeout\n      }\n    } = request\n\n    const signals = [signal]\n    /** @type {TimeoutController | undefined} */\n    let timeoutController\n\n    if (timeout != null) {\n      timeoutController = new TimeoutController(timeout)\n      signals.push(timeoutController.signal)\n    }\n\n    return streamResponse(request, h, () => {\n      return (async function * () {\n        for await (const event of ipfs.dht.get(key, {\n          signal: anySignal(signals)\n        })) {\n          yield mapQueryEvent(event)\n        }\n\n        if (timeoutController) {\n          timeoutController.clear()\n        }\n      }())\n    })\n  }\n}\n\nexport const provideResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        cid: Joi.cid().required(),\n        timeout: Joi.timeout()\n      })\n        .rename('arg', 'cid', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        cid,\n        timeout\n      }\n    } = request\n\n    const signals = [signal]\n    /** @type {TimeoutController | undefined} */\n    let timeoutController\n\n    if (timeout != null) {\n      timeoutController = new TimeoutController(timeout)\n      signals.push(timeoutController.signal)\n    }\n\n    return streamResponse(request, h, () => {\n      return (async function * () {\n        for await (const event of ipfs.dht.provide(cid, {\n          signal: anySignal(signals)\n        })) {\n          yield mapQueryEvent(event)\n        }\n\n        if (timeoutController) {\n          timeoutController.clear()\n        }\n      }())\n    })\n  }\n}\n\nexport const putResource = {\n  options: {\n    payload: {\n      parse: false,\n      output: 'stream'\n    },\n    pre: [{\n      assign: 'args',\n      /**\n       * @param {import('../../types').Request} request\n       * @param {import('@hapi/hapi').ResponseToolkit} _h\n       */\n      method: async (request, _h) => {\n        if (!request.payload) {\n          throw Boom.badRequest(\"Argument 'file' is required\")\n        }\n\n        let value\n\n        for await (const part of multipartRequestParser(request.raw.req)) {\n          if (part.type !== 'file') {\n            continue\n          }\n\n          value = Buffer.concat(await all(part.content))\n        }\n\n        if (!value) {\n          throw Boom.badRequest(\"Argument 'file' is required\")\n        }\n\n        try {\n          return { value }\n        } catch (/** @type {any} */ err) {\n          throw Boom.boomify(err, { message: 'Failed to decode file as config' })\n        }\n      }\n    }],\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        key: Joi.string().required(),\n        timeout: Joi.timeout()\n      })\n        .rename('arg', 'key', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      pre: {\n        args: {\n          value\n        }\n      },\n      query: {\n        key,\n        timeout\n      }\n    } = request\n\n    const signals = [signal]\n    /** @type {TimeoutController | undefined} */\n    let timeoutController\n\n    if (timeout != null) {\n      timeoutController = new TimeoutController(timeout)\n      signals.push(timeoutController.signal)\n    }\n\n    return streamResponse(request, h, () => {\n      return (async function * () {\n        for await (const event of ipfs.dht.put(key, value, {\n          signal: anySignal(signals)\n        })) {\n          yield mapQueryEvent(event)\n        }\n\n        if (timeoutController) {\n          timeoutController.clear()\n        }\n      }())\n    })\n  }\n}\n\nexport const queryResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        key: Joi.string().required(),\n        timeout: Joi.timeout()\n      })\n        .rename('arg', 'key', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        key,\n        timeout\n      }\n    } = request\n\n    const signals = [signal]\n    /** @type {TimeoutController | undefined} */\n    let timeoutController\n\n    if (timeout != null) {\n      timeoutController = new TimeoutController(timeout)\n      signals.push(timeoutController.signal)\n    }\n\n    return streamResponse(request, h, () => {\n      return (async function * () {\n        for await (const event of ipfs.dht.query(key, {\n          signal: anySignal(signals)\n        })) {\n          yield mapQueryEvent(event)\n        }\n\n        if (timeoutController) {\n          timeoutController.clear()\n        }\n      }())\n    })\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/dns.js",
    "content": "import Joi from '../../utils/joi.js'\n\nexport const dnsResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        domain: Joi.string().required(),\n        format: Joi.string(),\n        recursive: Joi.boolean().default(false),\n        timeout: Joi.timeout()\n      })\n        .rename('r', 'recursive', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('arg', 'domain', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        domain,\n        recursive,\n        timeout\n      }\n    } = request\n\n    const path = await ipfs.dns(domain, {\n      recursive,\n      signal,\n      timeout\n    })\n\n    return h.response({\n      Path: path\n    })\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/files/chmod.js",
    "content": "import Joi from '../../../utils/joi.js'\n\nexport const chmodResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        path: Joi.string(),\n        mode: Joi.string(),\n        recursive: Joi.boolean().default(false),\n        flush: Joi.boolean().default(true),\n        hashAlg: Joi.string().default('sha2-256'),\n        shardSplitThreshold: Joi.number().integer().min(0).default(1000),\n        timeout: Joi.timeout()\n      })\n        .rename('arg', 'path', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('shard-split-threshold', 'shardSplitThreshold', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('hash-alg', 'hashAlg', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('hash', 'hashAlg', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        path,\n        mode,\n        recursive,\n        hashAlg,\n        flush,\n        shardSplitThreshold,\n        timeout\n      }\n    } = request\n\n    await ipfs.files.chmod(path, mode, {\n      recursive,\n      hashAlg,\n      flush,\n      shardSplitThreshold,\n      signal,\n      timeout\n    })\n\n    return h.response()\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/files/cp.js",
    "content": "import Joi from '../../../utils/joi.js'\n\nexport const cpResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        paths: Joi.array().required().items(Joi.string()).min(2),\n        parents: Joi.boolean().default(false),\n        flush: Joi.boolean().default(true),\n        hashAlg: Joi.string().default('sha2-256'),\n        cidVersion: Joi.number().integer().valid(0, 1).default(0),\n        shardSplitThreshold: Joi.number().integer().min(0).default(1000),\n        timeout: Joi.timeout()\n      })\n        .rename('shard-split-threshold', 'shardSplitThreshold', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('hash-alg', 'hashAlg', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('hash', 'hashAlg', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('cid-version', 'cidVersion', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('arg', 'paths', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        paths,\n        parents,\n        flush,\n        hashAlg,\n        cidVersion,\n        shardSplitThreshold,\n        timeout\n      }\n    } = request\n\n    const dest = paths.pop()\n\n    await ipfs.files.cp(paths, dest, {\n      parents,\n      flush,\n      hashAlg,\n      cidVersion,\n      shardSplitThreshold,\n      signal,\n      timeout\n    })\n\n    return h.response()\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/files/flush.js",
    "content": "import Joi from '../../../utils/joi.js'\n\nexport const flushResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        path: Joi.string().default('/'),\n        cidBase: Joi.string().default('base58btc'),\n        timeout: Joi.timeout()\n      })\n        .rename('arg', 'path', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('cid-base', 'cidBase', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        path,\n        cidBase,\n        timeout\n      }\n    } = request\n\n    const cid = await ipfs.files.flush(path, {\n      signal,\n      timeout\n    })\n\n    const base = await ipfs.bases.getBase(cidBase)\n\n    return h.response({\n      Cid: cid.toString(base.encoder)\n    })\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/files/ls.js",
    "content": "import Joi from '../../../utils/joi.js'\nimport all from 'it-all'\nimport map from 'it-map'\nimport { pipe } from 'it-pipe'\nimport { streamResponse } from '../../../utils/stream-response.js'\n\n/**\n * @param {*} entry\n * @param {import('multiformats/bases/interface').MultibaseCodec<any>} base\n * @param {boolean} [long]\n */\nconst mapEntry = (entry, base, long) => {\n  const type = entry.type === 'file' ? 0 : 1\n\n  return {\n    Name: entry.name,\n    Type: long ? type : 0,\n    Size: long ? entry.size || 0 : 0,\n    Hash: entry.cid.toString(base.encoder),\n    Mtime: entry.mtime ? entry.mtime.secs : undefined,\n    MtimeNsecs: entry.mtime ? entry.mtime.nsecs : undefined,\n    Mode: entry.mode != null ? entry.mode.toString(8).padStart(4, '0') : undefined\n  }\n}\n\nexport const lsResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        path: Joi.string().default('/'),\n        long: Joi.boolean().default(false),\n        cidBase: Joi.string().default('base58btc'),\n        stream: Joi.boolean().default(false),\n        timeout: Joi.timeout()\n      })\n        .rename('arg', 'path', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        path,\n        long,\n        cidBase,\n        stream,\n        timeout\n      }\n    } = request\n\n    const base = await ipfs.bases.getBase(cidBase)\n\n    if (stream) {\n      return streamResponse(request, h, () => pipe(\n        ipfs.files.ls(path, {\n          signal,\n          timeout\n        }),\n        source => map(source, (entry) => mapEntry(entry, base, long))\n      ))\n    }\n\n    const files = await all(ipfs.files.ls(path, {\n      signal,\n      timeout\n    }))\n\n    return h.response({\n      Entries: files.map(entry => mapEntry(entry, base, long))\n    })\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/files/mkdir.js",
    "content": "import Joi from '../../../utils/joi.js'\nimport { parseMtime } from './utils/parse-mtime.js'\n\nexport const mkdirResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        arg: Joi.string().trim().min(1).required().error(new Error('no path given')),\n        mode: Joi.string(),\n        mtime: Joi.number().integer(),\n        mtimeNsecs: Joi.number().integer().min(0),\n        parents: Joi.boolean().default(false),\n        hashAlg: Joi.string().default('sha2-256'),\n        cidVersion: Joi.number().integer().valid(0, 1).default(0),\n        flush: Joi.boolean().default(true),\n        shardSplitThreshold: Joi.number().integer().min(0).default(1000),\n        timeout: Joi.timeout()\n      })\n        .rename('p', 'parents', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('shard-split-threshold', 'shardSplitThreshold', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('hash-alg', 'hashAlg', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('hash', 'hashAlg', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('cid-version', 'cidVersion', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('mtime-nsecs', 'mtimeNsecs', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      ipfs\n    } = request.server.app\n    const {\n      arg,\n      mode,\n      mtime,\n      mtimeNsecs,\n      parents,\n      hashAlg,\n      cidVersion,\n      flush,\n      shardSplitThreshold,\n      timeout\n    } = request.query\n\n    await ipfs.files.mkdir(arg, {\n      mode,\n      mtime: parseMtime(mtime, mtimeNsecs),\n      parents,\n      hashAlg,\n      cidVersion,\n      flush,\n      shardSplitThreshold,\n      signal: request.app.signal,\n      timeout\n    })\n\n    return h.response()\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/files/mv.js",
    "content": "import Joi from '../../../utils/joi.js'\n\nexport const mvResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        arg: Joi.array().required().items(Joi.string()).min(2),\n        recursive: Joi.boolean().default(false),\n        parents: Joi.boolean().default(false),\n        hashAlg: Joi.string().default('sha2-256'),\n        cidVersion: Joi.number().integer().valid(0, 1).default(0),\n        flush: Joi.boolean().default(true),\n        shardSplitThreshold: Joi.number().integer().min(0).default(1000),\n        timeout: Joi.timeout()\n      })\n        .rename('shard-split-threshold', 'shardSplitThreshold', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('hash-alg', 'hashAlg', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('hash', 'hashAlg', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('cid-version', 'cidVersion', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      ipfs\n    } = request.server.app\n    const {\n      arg,\n      recursive,\n      parents,\n      hashAlg,\n      cidVersion,\n      flush,\n      shardSplitThreshold,\n      timeout\n    } = request.query\n\n    const args = arg.concat({\n      recursive,\n      parents,\n      cidVersion,\n      flush,\n      hashAlg,\n      shardSplitThreshold,\n      signal: request.app.signal,\n      timeout\n    })\n\n    await ipfs.files.mv.apply(null, args)\n\n    return h.response()\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/files/read.js",
    "content": "import Joi from '../../../utils/joi.js'\nimport { streamResponse } from '../../../utils/stream-response.js'\n\nexport const readResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        arg: Joi.string().required(),\n        offset: Joi.number().integer().min(0),\n        length: Joi.number().integer().min(0),\n        timeout: Joi.timeout()\n      })\n        .rename('o', 'offset', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('n', 'length', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('count', 'length', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  handler (request, h) {\n    const {\n      ipfs\n    } = request.server.app\n    const {\n      arg,\n      offset,\n      length,\n      timeout\n    } = request.query\n\n    return streamResponse(request, h, () => ipfs.files.read(arg, {\n      offset,\n      length,\n      signal: request.app.signal,\n      timeout\n    }))\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/files/rm.js",
    "content": "import Joi from '../../../utils/joi.js'\n\nexport const rmResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        arg: Joi.array().required().items(Joi.string()).min(1).single(),\n        recursive: Joi.boolean().default(false),\n        shardSplitThreshold: Joi.number().integer().min(0).default(1000),\n        timeout: Joi.timeout()\n      })\n        .rename('r', 'recursive', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('shard-split-threshold', 'shardSplitThreshold', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        arg,\n        recursive,\n        shardSplitThreshold,\n        timeout\n      },\n      app: {\n        signal\n      }\n    } = request\n\n    await ipfs.files.rm(arg, {\n      recursive,\n      shardSplitThreshold,\n      signal,\n      timeout\n    })\n\n    return h.response()\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/files/stat.js",
    "content": "import Joi from '../../../utils/joi.js'\n\nexport const statResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        arg: Joi.string().default('/'),\n        hash: Joi.boolean().default(false),\n        size: Joi.boolean().default(false),\n        withLocal: Joi.boolean().default(false),\n        cidBase: Joi.string().default('base58btc'),\n        timeout: Joi.timeout()\n      })\n    }\n  },\n\n  /**\n   * @param {import('../../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      ipfs\n    } = request.server.app\n    const {\n      arg,\n      hash,\n      size,\n      withLocal,\n      cidBase,\n      timeout\n    } = request.query\n\n    const stats = await ipfs.files.stat(arg, {\n      hash,\n      size,\n      withLocal,\n      signal: request.app.signal,\n      timeout\n    })\n\n    const base = await ipfs.bases.getBase(cidBase)\n\n    const output = {\n      Type: stats.type,\n      Blocks: stats.blocks,\n      Size: stats.size,\n      Hash: stats.cid.toString(base.encoder),\n      CumulativeSize: stats.cumulativeSize,\n      WithLocality: stats.withLocality,\n      Local: stats.local,\n      SizeLocal: stats.sizeLocal,\n      Mtime: stats.mtime ? stats.mtime.secs : undefined,\n      MtimeNsecs: stats.mtime ? stats.mtime.nsecs : undefined,\n      Mode: stats.mode != null ? stats.mode.toString(8).padStart(4, '0') : undefined\n    }\n\n    return h.response(output)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/files/touch.js",
    "content": "import Joi from '../../../utils/joi.js'\nimport { parseMtime } from './utils/parse-mtime.js'\n\nexport const touchResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        arg: Joi.string().required(),\n        mtime: Joi.number().integer(),\n        mtimeNsecs: Joi.number().integer().min(0),\n        hashAlg: Joi.string().default('sha2-256'),\n        cidVersion: Joi.number().integer().valid(0, 1).default(0),\n        flush: Joi.boolean().default(true),\n        shardSplitThreshold: Joi.number().integer().min(0).default(1000),\n        timeout: Joi.timeout()\n      })\n        .rename('shard-split-threshold', 'shardSplitThreshold', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('mtime-nsecs', 'mtimeNsecs', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('hash-alg', 'hashAlg', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('hash', 'hashAlg', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('cid-version', 'cidVersion', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      ipfs\n    } = request.server.app\n    const {\n      arg,\n      flush,\n      shardSplitThreshold,\n      cidVersion,\n      hashAlg,\n      mtime,\n      mtimeNsecs,\n      timeout\n    } = request.query\n\n    await ipfs.files.touch(arg, {\n      mtime: parseMtime(mtime, mtimeNsecs),\n      flush,\n      shardSplitThreshold,\n      cidVersion,\n      hashAlg,\n      signal: request.app.signal,\n      timeout\n    })\n\n    return h.response()\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/files/utils/parse-mtime.js",
    "content": "\n/**\n * @param {number | undefined} secs\n * @param {number | undefined} nsecs\n */\nexport function parseMtime (secs, nsecs) {\n  if (secs == null && nsecs == null) {\n    return\n  }\n\n  const mtime = {}\n\n  if (nsecs || nsecs === 0) {\n    mtime.secs = 0\n    mtime.nsecs = nsecs\n  }\n\n  if (secs || secs === 0) {\n    mtime.secs = secs\n  }\n\n  return mtime\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/files/write.js",
    "content": "import Joi from '../../../utils/joi.js'\nimport { multipartRequestParser } from '../../../utils/multipart-request-parser.js'\nimport Boom from '@hapi/boom'\nimport drain from 'it-drain'\n\nexport const writeResource = {\n  options: {\n    payload: {\n      parse: false,\n      output: 'stream',\n      maxBytes: Number.MAX_SAFE_INTEGER\n    },\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        arg: Joi.string().regex(/^\\/.+/).required(),\n        offset: Joi.number().integer().min(0),\n        length: Joi.number().integer().min(0),\n        create: Joi.boolean().default(false),\n        truncate: Joi.boolean().default(false),\n        rawLeaves: Joi.boolean().default(false),\n        cidVersion: Joi.number().integer().valid(0, 1).default(0),\n        hashAlg: Joi.string().default('sha2-256'),\n        parents: Joi.boolean().default(false),\n        strategy: Joi.string().valid('flat', 'balanced', 'trickle').default('trickle'),\n        flush: Joi.boolean().default(true),\n        reduceSingleLeafToSelf: Joi.boolean().default(false),\n        shardSplitThreshold: Joi.number().integer().min(0).default(1000),\n        timeout: Joi.timeout()\n      })\n        .rename('o', 'offset', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('e', 'create', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('t', 'truncate', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('shard-split-threshold', 'shardSplitThreshold', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('hash-alg', 'hashAlg', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('hash', 'hashAlg', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('cid-version', 'cidVersion', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('raw-leaves', 'rawLeaves', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('reduce-single-leaf-to-self', 'reduceSingleLeafToSelf', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      ipfs\n    } = request.server.app\n    const {\n      arg,\n      offset,\n      length,\n      create,\n      truncate,\n      rawLeaves,\n      reduceSingleLeafToSelf,\n      cidVersion,\n      hashAlg,\n      parents,\n      strategy,\n      flush,\n      shardSplitThreshold,\n      timeout\n    } = request.query\n\n    let files = 0\n\n    for await (const entry of multipartRequestParser(request.raw.req)) {\n      if (entry.type === 'file') {\n        files++\n\n        if (files > 1) {\n          throw Boom.badRequest('Please only send one file')\n        }\n\n        await ipfs.files.write(arg, entry.content, {\n          offset,\n          length,\n          create,\n          truncate,\n          rawLeaves,\n          reduceSingleLeafToSelf,\n          cidVersion,\n          hashAlg,\n          parents,\n          strategy,\n          flush,\n          shardSplitThreshold,\n          mode: entry.mode,\n          mtime: entry.mtime,\n          signal: request.app.signal,\n          timeout\n        })\n\n        // if we didn't read the whole body, read it and discard the remainder\n        // otherwise the request will never end\n        await drain(entry.content)\n      }\n    }\n\n    return h.response()\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/files-regular.js",
    "content": "import { multipartRequestParser } from '../../utils/multipart-request-parser.js'\nimport Joi from '../../utils/joi.js'\nimport Boom from '@hapi/boom'\nimport { pipe } from 'it-pipe'\nimport all from 'it-all'\nimport { streamResponse } from '../../utils/stream-response.js'\nimport merge from 'it-merge'\nimport map from 'it-map'\nimport { PassThrough } from 'stream'\n\nexport const catResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object()\n        .keys({\n          path: Joi.ipfsPath().required(),\n          offset: Joi.number().integer().min(0),\n          length: Joi.number().integer().min(1),\n          timeout: Joi.timeout()\n        })\n        .rename('arg', 'path', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        path,\n        offset,\n        length,\n        timeout\n      }\n    } = request\n\n    return streamResponse(request, h, () => ipfs.cat(path, {\n      offset,\n      length,\n      timeout,\n      signal\n    }), {\n      onError (err) {\n        err.message = err.message === 'file does not exist'\n          ? err.message\n          : 'Failed to cat file: ' + err.message\n      }\n    })\n  }\n}\n\nexport const getResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object()\n        .keys({\n          path: Joi.ipfsPath().required(),\n          archive: Joi.boolean(),\n          compress: Joi.boolean(),\n          compressionLevel: Joi.number().integer().min(1).max(9),\n          timeout: Joi.timeout()\n        })\n        .rename('arg', 'path', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('compression-level', 'compressionLevel', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        path,\n        archive,\n        compress,\n        compressionLevel,\n        timeout\n      }\n    } = request\n\n    return streamResponse(request, h, () => ipfs.get(path, {\n      timeout,\n      archive,\n      compress,\n      compressionLevel,\n      signal\n    }))\n  }\n}\n\nexport const addResource = {\n  options: {\n    payload: {\n      parse: false,\n      output: 'stream',\n      maxBytes: Number.MAX_SAFE_INTEGER\n    },\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object()\n        .keys({\n          cidVersion: Joi.number().integer().min(0).max(1),\n          hashAlg: Joi.string(),\n          cidBase: Joi.string().default('base58btc'),\n          rawLeaves: Joi.boolean(),\n          onlyHash: Joi.boolean(),\n          pin: Joi.boolean(),\n          wrapWithDirectory: Joi.boolean(),\n          fileImportConcurrency: Joi.number().integer().min(0),\n          blockWriteConcurrency: Joi.number().integer().min(0),\n          shardSplitThreshold: Joi.number().integer().min(0),\n          chunker: Joi.string(),\n          trickle: Joi.boolean(),\n          preload: Joi.boolean(),\n          progress: Joi.boolean()\n        })\n        .rename('cid-version', 'cidVersion', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('cid-base', 'cidBase', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('hash', 'hashAlg', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('raw-leaves', 'rawLeaves', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('only-hash', 'onlyHash', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('wrap-with-directory', 'wrapWithDirectory', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('file-import-concurrency', 'fileImportConcurrency', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('block-write-concurrency', 'blockWriteConcurrency', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('shard-split-threshold', 'shardSplitThreshold', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  handler (request, h) {\n    if (!request.payload) {\n      throw Boom.badRequest('Array, Buffer, or String is required.')\n    }\n\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        cidVersion,\n        cidBase,\n        rawLeaves,\n        progress,\n        onlyHash,\n        hashAlg,\n        wrapWithDirectory,\n        pin,\n        chunker,\n        trickle,\n        preload,\n        shardSplitThreshold,\n        blockWriteConcurrency,\n        timeout\n      }\n    } = request\n\n    let filesParsed = false\n\n    return streamResponse(request, h, () => pipe(\n      multipartRequestParser(request.raw.req),\n      async function * (source) {\n        for await (const entry of source) {\n          if (entry.type === 'file') {\n            filesParsed = true\n\n            yield {\n              path: entry.name,\n              content: entry.content,\n              mode: entry.mode,\n              mtime: entry.mtime\n            }\n          }\n\n          if (entry.type === 'directory') {\n            filesParsed = true\n\n            yield {\n              path: entry.name,\n              mode: entry.mode,\n              mtime: entry.mtime\n            }\n          }\n        }\n      },\n      async function * (source) {\n        const progressStream = new PassThrough({\n          objectMode: true\n        })\n\n        yield * merge(\n          progressStream,\n          pipe(\n            ipfs.addAll(source, {\n              cidVersion,\n              rawLeaves,\n              progress: progress\n                ? (bytes, path) => {\n                    progressStream.write({\n                      Name: path,\n                      Bytes: bytes\n                    })\n                  }\n                : () => {},\n              onlyHash,\n              hashAlg,\n              wrapWithDirectory,\n              pin,\n              chunker,\n              trickle,\n              preload,\n              shardSplitThreshold,\n\n              // this has to be hardcoded to 1 because we can only read one file\n              // at a time from a http request and we have to consume it completely\n              // before we can read the next file\n              fileImportConcurrency: 1,\n              blockWriteConcurrency,\n              signal,\n              timeout\n            }),\n            async function * (source) {\n              const base = await ipfs.bases.getBase(cidBase)\n\n              yield * map(source, file => {\n                return {\n                  Name: file.path,\n                  Hash: file.cid.toString(base.encoder),\n                  Size: file.size,\n                  Mode: file.mode === undefined ? undefined : file.mode.toString(8).padStart(4, '0'),\n                  Mtime: file.mtime ? file.mtime.secs : undefined,\n                  MtimeNsecs: file.mtime ? file.mtime.nsecs : undefined\n                }\n              })\n\n              // no more files, end the progress stream\n              progressStream.end()\n            }\n          )\n        )\n      }\n    ), {\n      onEnd () {\n        if (!filesParsed) {\n          throw Boom.badRequest(\"File argument 'data' is required.\")\n        }\n      }\n    })\n  }\n}\n\nexport const lsResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object()\n        .keys({\n          path: Joi.ipfsPath().required(),\n          cidBase: Joi.string().default('base58btc'),\n          stream: Joi.boolean().default(false),\n          timeout: Joi.timeout()\n        })\n        .rename('arg', 'path', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('cid-base', 'cidBase', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        path,\n        cidBase,\n        stream,\n        timeout\n      }\n    } = request\n\n    const base = await ipfs.bases.getBase(cidBase)\n\n    /**\n     * TODO: can be ipfs.files.stat result or ipfs.ls result\n     *\n     * @param {any} link\n     */\n    const mapLink = link => {\n      return {\n        Hash: link.cid.toString(base.encoder),\n        Size: link.size,\n        Type: toTypeCode(link.type),\n        Depth: link.depth,\n        Name: link.name ? link.name : undefined,\n        Mode: link.mode != null ? link.mode.toString(8).padStart(4, '0') : undefined,\n        Mtime: link.mtime ? link.mtime.secs : undefined,\n        MtimeNsecs: link.mtime ? link.mtime.nsecs : undefined\n      }\n    }\n\n    const stat = await ipfs.files.stat(path.startsWith('/ipfs/') ? path : `/ipfs/${path}`, {\n      signal,\n      timeout\n    })\n\n    if (stat.type === 'file') {\n      // return single object with metadata\n      return h.response({\n        Objects: [{\n          ...mapLink(stat),\n          Hash: path,\n          Depth: 1,\n          Links: []\n        }]\n      })\n    }\n\n    if (!stream) {\n      try {\n        const links = await all(ipfs.ls(path, {\n          signal,\n          timeout\n        }))\n\n        return h.response({ Objects: [{ Hash: path, Links: links.map(mapLink) }] })\n      } catch (/** @type {any} */ err) {\n        throw Boom.boomify(err, { message: 'Failed to list dir' })\n      }\n    }\n    return streamResponse(request, h, () => pipe(\n      ipfs.ls(path, {\n        signal,\n        timeout\n      }),\n      async function * (source) {\n        yield * map(source, link => ({ Objects: [{ Hash: path, Links: [mapLink(link)] }] }))\n      }\n    ))\n  }\n}\n\n/**\n * @param {string} type\n */\nfunction toTypeCode (type) {\n  switch (type) {\n    case 'dir':\n      return 1\n    case 'file':\n      return 2\n    default:\n      return 0\n  }\n}\n\nexport const refsResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        paths: Joi.array().single().items(Joi.ipfsPath()),\n        recursive: Joi.boolean().default(false),\n        edges: Joi.boolean().default(false),\n        unique: Joi.boolean().default(false),\n        maxDepth: Joi.number().integer().min(-1),\n        format: Joi.string(),\n        timeout: Joi.timeout()\n      })\n        .rename('max-depth', 'maxDepth', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('arg', 'paths', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        paths,\n        recursive,\n        edges,\n        unique,\n        maxDepth,\n        format,\n        timeout\n      }\n    } = request\n\n    return streamResponse(request, h, () => pipe(\n      ipfs.refs(paths, {\n        recursive,\n        edges,\n        unique,\n        maxDepth,\n        format,\n        signal,\n        timeout\n      }),\n      async function * (source) {\n        yield * map(source, ({ ref, err }) => ({ Ref: ref, Err: err }))\n      }\n    ))\n  }\n}\n\nexport const refsLocalResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        timeout: Joi.timeout()\n      })\n    }\n  },\n\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        timeout\n      }\n    } = request\n\n    return streamResponse(request, h, () => pipe(\n      ipfs.refs.local({\n        signal,\n        timeout\n      }),\n      async function * (source) {\n        yield * map(source, ({ ref, err }) => ({ Ref: ref, Err: err }))\n      }\n    ))\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/id.js",
    "content": "import Joi from '../../utils/joi.js'\n\nexport const idResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        timeout: Joi.timeout(),\n        peerId: Joi.peerId()\n      })\n        .rename('arg', 'peerId', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  handler: async (request, h) => {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        timeout,\n        peerId\n      }\n    } = request\n\n    const id = await ipfs.id({\n      signal,\n      timeout,\n      peerId\n    })\n    return h.response({\n      ID: id.id.toString(),\n      PublicKey: id.publicKey,\n      Addresses: id.addresses,\n      AgentVersion: id.agentVersion,\n      ProtocolVersion: id.protocolVersion,\n      Protocols: id.protocols\n    })\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/key.js",
    "content": "import Joi from '../../utils/joi.js'\n\n/**\n * @param {any} key\n */\nfunction toKeyInfo (key) {\n  return {\n    Name: key.name,\n    Id: key.id\n  }\n}\n\nexport const listResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        timeout: Joi.timeout()\n      })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  handler: async (request, h) => {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        timeout\n      }\n    } = request\n\n    const keys = await ipfs.key.list({\n      signal,\n      timeout\n    })\n\n    return h.response({ Keys: keys.map(toKeyInfo) })\n  }\n}\n\nexport const rmResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        name: Joi.string().required(),\n        timeout: Joi.timeout()\n      })\n        .rename('arg', 'name', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  handler: async (request, h) => {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        name,\n        timeout\n      }\n    } = request\n\n    const key = await ipfs.key.rm(name, {\n      timeout,\n      signal\n    })\n\n    return h.response({ Keys: [toKeyInfo(key)] })\n  }\n}\n\nexport const renameResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        arg: Joi.array().single().length(2).required(),\n        timeout: Joi.timeout()\n      })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  handler: async (request, h) => {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        arg: [\n          oldName,\n          newName\n        ],\n        timeout\n      }\n    } = request\n\n    const key = await ipfs.key.rename(oldName, newName, {\n      signal,\n      timeout\n    })\n\n    return h.response({\n      Was: key.was,\n      Now: key.now,\n      Id: key.id,\n      Overwrite: key.overwrite\n    })\n  }\n}\n\nexport const genResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        name: Joi.string().required(),\n        type: Joi.string().default('rsa'),\n        size: Joi.number().integer().default(2048),\n        timeout: Joi.timeout()\n      })\n        .rename('arg', 'name', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  handler: async (request, h) => {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        name,\n        type,\n        size,\n        timeout\n      }\n    } = request\n\n    const key = await ipfs.key.gen(name, {\n      type,\n      size,\n      signal,\n      timeout\n    })\n\n    return h.response(toKeyInfo(key))\n  }\n}\n\nexport const importResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        name: Joi.string().required(),\n        password: Joi.string().required(),\n        pem: Joi.string().required(),\n        timeout: Joi.timeout()\n      })\n        .rename('arg', 'name', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  handler: async (request, h) => {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        name,\n        password,\n        pem,\n        timeout\n      }\n    } = request\n\n    const key = await ipfs.key.import(name, pem, password, {\n      signal,\n      timeout\n    })\n\n    return h.response(toKeyInfo(key))\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/name.js",
    "content": "import Joi from '../../utils/joi.js'\nimport { pipe } from 'it-pipe'\nimport map from 'it-map'\nimport last from 'it-last'\nimport { streamResponse } from '../../utils/stream-response.js'\n\nexport const resolveResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        name: Joi.string(),\n        nocache: Joi.boolean().default(false),\n        recursive: Joi.boolean().default(true),\n        stream: Joi.boolean().default(false),\n        timeout: Joi.timeout()\n      })\n        .rename('arg', 'name', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        name,\n        nocache,\n        recursive,\n        stream,\n        timeout\n      }\n    } = request\n\n    if (!stream) {\n      const value = await last(ipfs.name.resolve(name, {\n        nocache,\n        recursive,\n        signal,\n        timeout\n      }))\n      return h.response({ Path: value })\n    }\n\n    return streamResponse(request, h, () => pipe(\n      ipfs.name.resolve(name, {\n        nocache,\n        recursive,\n        signal,\n        timeout\n      }),\n      async function * (source) {\n        yield * map(source, value => ({ Path: value }))\n      }\n    ))\n  }\n}\n\nexport const publishResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        name: Joi.string().required(),\n        resolve: Joi.boolean().default(true),\n        lifetime: Joi.string().default('24h'),\n        ttl: Joi.string().allow(''),\n        key: Joi.string().default('self'),\n        allowOffline: Joi.boolean(),\n        timeout: Joi.timeout()\n      })\n        .rename('allow-offline', 'allowOffline', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('arg', 'name', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        name,\n        resolve,\n        lifetime,\n        ttl,\n        key,\n        allowOffline,\n        timeout\n      }\n    } = request\n\n    const res = await ipfs.name.publish(name, {\n      resolve,\n      lifetime,\n      ttl,\n      key,\n      allowOffline,\n      signal,\n      timeout\n    })\n\n    return h.response({\n      Name: res.name,\n      Value: res.value\n    })\n  }\n}\n\nexport const stateResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        timeout: Joi.timeout()\n      })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        timeout\n      }\n    } = request\n\n    const res = await ipfs.name.pubsub.state({\n      signal,\n      timeout\n    })\n\n    return h.response({\n      Enabled: res.enabled\n    })\n  }\n}\n\nexport const pubsubSubsResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        timeout: Joi.timeout()\n      })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        timeout\n      }\n    } = request\n\n    const res = await ipfs.name.pubsub.subs({\n      signal,\n      timeout\n    })\n\n    return h.response({\n      Strings: res\n    })\n  }\n}\n\nexport const pubsubCancelResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        topic: Joi.string().required(),\n        timeout: Joi.timeout()\n      })\n        .rename('arg', 'topic', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        topic,\n        timeout\n      }\n    } = request\n\n    const res = await ipfs.name.pubsub.cancel(topic, {\n      signal,\n      timeout\n    })\n\n    return h.response({\n      Canceled: res.canceled\n    })\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/object.js",
    "content": "import { multipartRequestParser } from '../../utils/multipart-request-parser.js'\nimport all from 'it-all'\nimport * as dagPB from '@ipld/dag-pb'\nimport Joi from '../../utils/joi.js'\nimport Boom from '@hapi/boom'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\nimport { logger } from '@libp2p/logger'\nimport { CID } from 'multiformats/cid'\nimport { base64pad } from 'multiformats/bases/base64'\nimport { base16 } from 'multiformats/bases/base16'\n\nconst log = logger('ipfs:http-api:object')\n\n/**\n * @type {Record<string, (str: string) => Uint8Array>}\n */\nconst DECODINGS = {\n  ascii: (str) => uint8ArrayFromString(str),\n  utf8: (str) => uint8ArrayFromString(str),\n  base64pad: (str) => base64pad.decode(`M${str}`),\n  base16: (str) => base16.decode(`f${str}`)\n}\n\n/**\n * @param {import('../../types').Request} request\n * @param {import('@hapi/hapi').ResponseToolkit} _h\n */\nconst readFilePart = async (request, _h) => {\n  if (!request.payload) {\n    throw Boom.badRequest(\"File argument 'data' is required\")\n  }\n\n  let data\n\n  for await (const part of multipartRequestParser(request.raw.req)) {\n    if (part.type !== 'file') {\n      continue\n    }\n\n    data = Buffer.concat(await all(part.content))\n  }\n\n  if (!data) {\n    throw Boom.badRequest(\"File argument 'data' is required\")\n  }\n\n  if (request.query.enc === 'json') {\n    try {\n      data = JSON.parse(data.toString('utf8'))\n    } catch (/** @type {any} */ err) {\n      log(err)\n      throw Boom.badRequest(\"File argument 'data' is required\")\n    }\n  }\n\n  return {\n    data\n  }\n}\n\nexport const newResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        template: Joi.string().valid('unixfs-dir'),\n        cidBase: Joi.string().default('base32'),\n        timeout: Joi.timeout()\n      })\n        .rename('cid-base', 'cidBase', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('arg', 'template', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        template,\n        cidBase,\n        timeout\n      }\n    } = request\n\n    let cid, block, node\n    try {\n      cid = await ipfs.object.new({\n        template,\n        signal,\n        timeout\n      })\n      node = await ipfs.object.get(cid, {\n        signal,\n        timeout\n      })\n      block = dagPB.encode(node)\n    } catch (/** @type {any} */ err) {\n      throw Boom.boomify(err, { message: 'Failed to create object' })\n    }\n\n    const base = await ipfs.bases.getBase(cidBase)\n    const base58 = await ipfs.bases.getBase('base58btc')\n\n    const answer = {\n      Data: node.Data ? uint8ArrayToString(node.Data, 'base64pad') : '',\n      Hash: cid.toString(cid.version === 1 ? base.encoder : base58.encoder),\n      Size: block.length,\n      Links: node.Links.map((l) => {\n        return {\n          Name: l.Name,\n          Size: l.Tsize,\n          Hash: l.Hash.toString(l.Hash.version === 1 ? base.encoder : base58.encoder)\n        }\n      })\n    }\n\n    return h.response(answer)\n  }\n}\n\nexport const getResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        cid: Joi.cid().required(),\n        cidBase: Joi.string().default('base32'),\n        dataEncoding: Joi.string()\n          .valid('ascii', 'base64pad', 'base16', 'utf8')\n          .replace(/text/, 'ascii')\n          .replace(/base64/, 'base64pad')\n          .replace(/hex/, 'base16')\n          .default('base64pad'),\n        timeout: Joi.timeout()\n      })\n        .rename('cid-base', 'cidBase', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('data-encoding', 'dataEncoding', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('arg', 'cid', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        cid,\n        cidBase,\n        dataEncoding,\n        timeout\n      }\n    } = request\n\n    let node, block\n    try {\n      node = await ipfs.object.get(cid, {\n        signal,\n        timeout\n      })\n      block = dagPB.encode(node)\n    } catch (/** @type {any} */ err) {\n      throw Boom.boomify(err, { message: 'Failed to get object' })\n    }\n\n    const base = await ipfs.bases.getBase(cidBase)\n    const base58 = await ipfs.bases.getBase('base58btc')\n\n    return h.response({\n      Data: node.Data ? uint8ArrayToString(node.Data, dataEncoding) : '',\n      Hash: cid.toString(cid.version === 1 ? base.encoder : base58.encoder),\n      Size: block.length,\n      Links: node.Links.map((l) => {\n        return {\n          Name: l.Name,\n          Size: l.Tsize,\n          Hash: l.Hash.toString(l.Hash.version === 1 ? base.encoder : base58.encoder)\n        }\n      })\n    })\n  }\n}\n\nexport const putResource = {\n  options: {\n    payload: {\n      parse: false,\n      output: 'stream'\n    },\n    pre: [{\n      assign: 'args',\n      method: readFilePart\n    }],\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        cidBase: Joi.string().default('base32'),\n        dataEncoding: Joi.string()\n          .valid('ascii', 'base64pad', 'base16', 'utf8')\n          .replace(/text/, 'ascii')\n          .replace(/base64/, 'base64pad')\n          .replace(/hex/, 'base16')\n          .default('base64pad'),\n        enc: Joi.string().valid('json', 'protobuf').default('json'),\n        pin: Joi.boolean().default(false),\n        timeout: Joi.timeout()\n      })\n        .rename('cid-base', 'cidBase', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('datafieldenc', 'dataEncoding', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('inputenc', 'enc', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      pre: {\n        args: {\n          data\n        }\n      },\n      query: {\n        enc,\n        cidBase,\n        dataEncoding,\n        timeout,\n        pin\n      }\n    } = request\n\n    /** @type {import('@ipld/dag-pb').PBNode} */\n    let input\n\n    if (enc === 'json') {\n      input = {\n        Data: data.Data ? DECODINGS[dataEncoding](data.Data) : undefined,\n        Links: (data.Links || []).map((/** @type {any} */ l) => {\n          return {\n            Name: l.Name || '',\n            Tsize: l.Size || l.Tsize || 0,\n            Hash: CID.parse(l.Hash)\n          }\n        })\n      }\n    } else {\n      input = dagPB.decode(data)\n    }\n\n    let cid, node, block\n    try {\n      cid = await ipfs.object.put(input, {\n        signal,\n        timeout,\n        pin\n      })\n      node = await ipfs.object.get(cid, {\n        signal,\n        timeout\n      })\n      block = dagPB.encode(node)\n    } catch (/** @type {any} */ err) {\n      throw Boom.badRequest(err, { message: 'Failed to put node' })\n    }\n\n    const base = await ipfs.bases.getBase(cidBase)\n    const base58 = await ipfs.bases.getBase('base58btc')\n\n    const answer = {\n      Data: node.Data ? uint8ArrayToString(node.Data, dataEncoding) : '',\n      Hash: cid.toString(cid.version === 1 ? base.encoder : base58.encoder),\n      Size: block.length,\n      Links: node.Links.map((l) => {\n        return {\n          Name: l.Name,\n          Size: l.Tsize,\n          Hash: l.Hash.toString(l.Hash.version === 1 ? base.encoder : base58.encoder)\n        }\n      })\n    }\n\n    return h.response(answer)\n  }\n}\n\nexport const statResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        cid: Joi.cid().required(),\n        cidBase: Joi.string().default('base32'),\n        timeout: Joi.timeout()\n      })\n        .rename('cid-base', 'cidBase', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('arg', 'cid', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        cid,\n        cidBase,\n        timeout\n      }\n    } = request\n\n    let stats\n    try {\n      stats = await ipfs.object.stat(cid, {\n        signal,\n        timeout\n      })\n    } catch (/** @type {any} */ err) {\n      throw Boom.boomify(err, { message: 'Failed to stat object' })\n    }\n\n    const base = await ipfs.bases.getBase(cidBase)\n    const base58 = await ipfs.bases.getBase('base58btc')\n\n    return h.response({\n      ...stats,\n      Hash: stats.Hash.toString(stats.Hash.version === 1 ? base.encoder : base58.encoder)\n    })\n  }\n}\n\nexport const dataResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        cid: Joi.cid().required(),\n        cidBase: Joi.string().default('base32'),\n        timeout: Joi.timeout()\n      })\n        .rename('cid-base', 'cidBase', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('arg', 'cid', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        cid,\n        timeout\n      }\n    } = request\n\n    let data\n    try {\n      data = await ipfs.object.data(cid, {\n        signal,\n        timeout\n      })\n    } catch (/** @type {any} */ err) {\n      throw Boom.boomify(err, { message: 'Failed to get object data' })\n    }\n\n    return h.response(data)\n  }\n}\n\nexport const linksResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        cid: Joi.cid().required(),\n        cidBase: Joi.string().default('base32'),\n        timeout: Joi.timeout()\n      })\n        .rename('cid-base', 'cidBase', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('arg', 'cid', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        cid,\n        cidBase,\n        timeout\n      }\n    } = request\n\n    const links = await ipfs.object.links(cid, {\n      signal,\n      timeout\n    })\n\n    const base = await ipfs.bases.getBase(cidBase)\n    const base58 = await ipfs.bases.getBase('base58btc')\n\n    const response = {\n      Hash: cid.toString(cid.version === 1 ? base.encoder : base58.encoder),\n      Links: (links || []).map((l) => {\n        return {\n          Name: l.Name,\n          Size: l.Tsize,\n          Hash: l.Hash.toString(l.Hash.version === 1 ? base.encoder : base58.encoder)\n        }\n      })\n    }\n\n    return h.response(response)\n  }\n}\n\nexport const patchAppendDataResource = {\n  options: {\n    payload: {\n      parse: false,\n      output: 'stream'\n    },\n    pre: [{\n      assign: 'args',\n      method: readFilePart\n    }],\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        cid: Joi.cid().required(),\n        cidBase: Joi.string().default('base32'),\n        dataEncoding: Joi.string()\n          .valid('ascii', 'base64pad', 'base16', 'utf8')\n          .replace(/text/, 'ascii')\n          .replace(/base64/, 'base64pad')\n          .replace(/hex/, 'base16')\n          .default('base64pad'),\n        timeout: Joi.timeout()\n      })\n        .rename('cid-base', 'cidBase', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('data-encoding', 'dataEncoding', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('arg', 'cid', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      pre: {\n        args: {\n          data\n        }\n      },\n      query: {\n        cid,\n        cidBase,\n        dataEncoding,\n        timeout\n      }\n    } = request\n\n    let newCid, node, block\n    try {\n      newCid = await ipfs.object.patch.appendData(cid, data, {\n        signal,\n        timeout\n      })\n      node = await ipfs.object.get(newCid, {\n        signal,\n        timeout\n      })\n      block = dagPB.encode(node)\n    } catch (/** @type {any} */ err) {\n      throw Boom.boomify(err, { message: 'Failed to append data to object' })\n    }\n\n    const base = await ipfs.bases.getBase(cidBase)\n    const base58 = await ipfs.bases.getBase('base58btc')\n\n    const answer = {\n      Data: node.Data ? uint8ArrayToString(node.Data, dataEncoding) : '',\n      Hash: newCid.toString(newCid.version === 1 ? base.encoder : base58.encoder),\n      Size: block.length,\n      Links: node.Links.map((l) => {\n        return {\n          Name: l.Name,\n          Size: l.Tsize,\n          Hash: l.Hash.toString(l.Hash.version === 1 ? base.encoder : base58.encoder)\n        }\n      })\n    }\n\n    return h.response(answer)\n  }\n}\n\nexport const patchSetDataResource = {\n  options: {\n    payload: {\n      parse: false,\n      output: 'stream'\n    },\n    pre: [{\n      assign: 'args',\n      method: readFilePart\n    }],\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        cid: Joi.cid().required(),\n        cidBase: Joi.string().default('base32'),\n        timeout: Joi.timeout()\n      })\n        .rename('cid-base', 'cidBase', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('arg', 'cid', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      pre: {\n        args: {\n          data\n        }\n      },\n      query: {\n        cid,\n        cidBase,\n        timeout\n      }\n    } = request\n\n    let newCid, node\n    try {\n      newCid = await ipfs.object.patch.setData(cid, data, {\n        signal,\n        timeout\n      })\n      node = await ipfs.object.get(newCid, {\n        signal: request.app.signal\n      })\n    } catch (/** @type {any} */ err) {\n      throw Boom.boomify(err, { message: 'Failed to set data on object' })\n    }\n\n    const base = await ipfs.bases.getBase(cidBase)\n    const base58 = await ipfs.bases.getBase('base58btc')\n\n    return h.response({\n      Hash: newCid.toString(newCid.version === 1 ? base.encoder : base58.encoder),\n      Links: node.Links.map((l) => {\n        return {\n          Name: l.Name,\n          Size: l.Tsize,\n          Hash: l.Hash.toString(l.Hash.version === 1 ? base.encoder : base58.encoder)\n        }\n      })\n    })\n  }\n}\n\nexport const patchAddLinkResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        args: Joi.array().length(3).ordered(\n          Joi.cid().required(),\n          Joi.string().required(),\n          Joi.cid().required()\n        ).required(),\n        cidBase: Joi.string().default('base32'),\n        dataEncoding: Joi.string()\n          .valid('ascii', 'base64pad', 'base16', 'utf8')\n          .replace(/text/, 'ascii')\n          .replace(/base64/, 'base64pad')\n          .replace(/hex/, 'base16')\n          .default('base64pad'),\n        timeout: Joi.timeout()\n      })\n        .rename('cid-base', 'cidBase', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('arg', 'args', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        args: [\n          root,\n          name,\n          ref\n        ],\n        cidBase,\n        dataEncoding,\n        timeout\n      }\n    } = request\n\n    let node, cid, block\n    try {\n      node = await ipfs.object.get(ref, {\n        signal,\n        timeout\n      })\n      block = dagPB.encode(node)\n      cid = await ipfs.object.patch.addLink(root, { Name: name, Tsize: block.length, Hash: ref }, {\n        signal,\n        timeout\n      })\n      node = await ipfs.object.get(cid, {\n        signal,\n        timeout\n      })\n      block = dagPB.encode(node)\n    } catch (/** @type {any} */ err) {\n      throw Boom.boomify(err, { message: 'Failed to add link to object' })\n    }\n\n    const base = await ipfs.bases.getBase(cidBase)\n    const base58 = await ipfs.bases.getBase('base58btc')\n\n    const answer = {\n      Data: node.Data ? uint8ArrayToString(node.Data, dataEncoding) : '',\n      Hash: cid.toString(cid.version === 1 ? base.encoder : base58.encoder),\n      Size: block.length,\n      Links: node.Links.map((l) => {\n        return {\n          Name: l.Name,\n          Size: l.Tsize,\n          Hash: l.Hash.toString(l.Hash.version === 1 ? base.encoder : base58.encoder)\n        }\n      })\n    }\n\n    return h.response(answer)\n  }\n}\n\nexport const patchRmLinkResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        args: Joi.array().length(2).ordered(\n          Joi.cid().required(),\n          Joi.string().required()\n        ).required(),\n        cidBase: Joi.string().default('base32'),\n        dataEncoding: Joi.string()\n          .valid('ascii', 'base64pad', 'base16', 'utf8')\n          .replace(/text/, 'ascii')\n          .replace(/base64/, 'base64pad')\n          .replace(/hex/, 'base16')\n          .default('base64pad'),\n        timeout: Joi.timeout()\n      })\n        .rename('cid-base', 'cidBase', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('arg', 'args', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        args: [\n          root,\n          link\n        ],\n        cidBase,\n        dataEncoding,\n        timeout\n      }\n    } = request\n\n    let cid, node, block\n    try {\n      cid = await ipfs.object.patch.rmLink(root, link, {\n        signal,\n        timeout\n      })\n      node = await ipfs.object.get(cid, {\n        signal,\n        timeout\n      })\n      block = dagPB.encode(node)\n    } catch (/** @type {any} */ err) {\n      throw Boom.boomify(err, { message: 'Failed to remove link from object' })\n    }\n\n    const base = await ipfs.bases.getBase(cidBase)\n    const base58 = await ipfs.bases.getBase('base58btc')\n\n    const answer = {\n      Data: node.Data ? uint8ArrayToString(node.Data, dataEncoding) : '',\n      Hash: cid.toString(cid.version === 1 ? base.encoder : base58.encoder),\n      Size: block.length,\n      Links: node.Links.map((l) => {\n        return {\n          Name: l.Name,\n          Size: l.Tsize,\n          Hash: l.Hash.toString(l.Hash.version === 1 ? base.encoder : base58.encoder)\n        }\n      })\n    }\n\n    return h.response(answer)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/pin.js",
    "content": "import Joi from '../../utils/joi.js'\nimport Boom from '@hapi/boom'\nimport map from 'it-map'\nimport { pipe } from 'it-pipe'\nimport { streamResponse } from '../../utils/stream-response.js'\nimport all from 'it-all'\nimport reduce from 'it-reduce'\n\n/**\n * @typedef {import('multiformats/cid').CID} CID\n */\n\n/**\n * @param {string} type\n * @param {string} [cid]\n * @param {Record<string, any>} [metadata]\n */\nfunction toPin (type, cid, metadata) {\n  /** @type {{ Type: string, Cid?: string, Metadata?: Record<string, any> }} */\n  const output = {\n    Type: type\n  }\n\n  if (cid) {\n    output.Cid = cid\n  }\n\n  if (metadata) {\n    output.Metadata = metadata\n  }\n\n  return output\n}\n\nexport const lsResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        paths: Joi.array().single().items(Joi.ipfsPath()),\n        recursive: Joi.boolean().default(true),\n        cidBase: Joi.string().default('base58btc'),\n        type: Joi.string().valid('all', 'direct', 'indirect', 'recursive').default('all'),\n        stream: Joi.boolean().default(false),\n        timeout: Joi.timeout()\n      })\n        .rename('cid-base', 'cidBase', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('arg', 'paths', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        paths,\n        type,\n        cidBase,\n        stream,\n        timeout\n      }\n    } = request\n\n    const source = ipfs.pin.ls({\n      paths,\n      type,\n      signal,\n      timeout\n    })\n\n    const base = await ipfs.bases.getBase(cidBase)\n\n    if (!stream) {\n      const res = await pipe(\n        source,\n        function collectKeys (source) {\n          /** @type {{ Keys: Record<string, any> }} */\n          const init = { Keys: {} }\n\n          return reduce(source, (res, { type, cid, metadata }) => {\n            res.Keys[cid.toString(base.encoder)] = toPin(type, undefined, metadata)\n\n            return res\n          }, init)\n        }\n      )\n\n      return h.response(res)\n    }\n\n    return streamResponse(request, h, () => pipe(\n      source,\n      async function * transform (source) {\n        yield * map(source, ({ type, cid, metadata }) => toPin(type, cid.toString(base.encoder), metadata))\n      }\n    ))\n  }\n}\n\nexport const addResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        cids: Joi.array().single().items(Joi.cid()).min(1).required(),\n        recursive: Joi.boolean().default(true),\n        cidBase: Joi.string().default('base58btc'),\n        timeout: Joi.timeout(),\n        metadata: Joi.json()\n      })\n        .rename('cid-base', 'cidBase', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('arg', 'cids', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        cids,\n        recursive,\n        cidBase,\n        timeout,\n        metadata\n      }\n    } = request\n\n    let result\n    try {\n      result = await all(ipfs.pin.addAll(cids.map((/** @type {CID} */ cid) => ({ cid, recursive, metadata })), {\n        signal,\n        timeout\n      }))\n    } catch (/** @type {any} */ err) {\n      if (err.code === 'ERR_BAD_PATH') {\n        throw Boom.boomify(err, { statusCode: 400 })\n      }\n\n      if (err.message.includes('already pinned recursively')) {\n        throw Boom.boomify(err, { statusCode: 400 })\n      }\n\n      throw Boom.boomify(err, { message: 'Failed to add pin' })\n    }\n\n    const base = await ipfs.bases.getBase(cidBase)\n\n    return h.response({\n      Pins: result.map(cid => cid.toString(base.encoder))\n    })\n  }\n}\n\nexport const rmResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        cids: Joi.array().single().items(Joi.cid()).min(1).required(),\n        recursive: Joi.boolean().default(true),\n        cidBase: Joi.string().default('base58btc'),\n        timeout: Joi.timeout()\n      })\n        .rename('cid-base', 'cidBase', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('arg', 'cids', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        cids,\n        recursive,\n        cidBase,\n        timeout\n      }\n    } = request\n\n    let result\n    try {\n      result = await all(ipfs.pin.rmAll(cids.map((/** @type {CID} */ cid) => ({ cid, recursive })), {\n        signal,\n        timeout\n      }))\n    } catch (/** @type {any} */ err) {\n      if (err.code === 'ERR_BAD_PATH') {\n        throw Boom.boomify(err, { statusCode: 400 })\n      }\n\n      throw Boom.boomify(err, { message: 'Failed to remove pin' })\n    }\n\n    const base = await ipfs.bases.getBase(cidBase)\n\n    return h.response({\n      Pins: result.map(cid => cid.toString(base.encoder))\n    })\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/ping.js",
    "content": "import Joi from '../../utils/joi.js'\nimport { pipe } from 'it-pipe'\nimport map from 'it-map'\nimport { streamResponse } from '../../utils/stream-response.js'\n\nexport const pingResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        count: Joi.number().integer().greater(0).default(10),\n        peerId: Joi.peerId().required(),\n        timeout: Joi.timeout()\n      })\n        .rename('arg', 'peerId', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('n', 'count', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        peerId,\n        count,\n        timeout\n      }\n    } = request\n\n    return streamResponse(request, h, () => pipe(\n      ipfs.ping(peerId, {\n        count,\n        signal,\n        timeout\n      }),\n      async function * (source) {\n        yield * map(source, pong => ({ Success: pong.success, Time: pong.time, Text: pong.text }))\n      }\n    ))\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/pubsub.js",
    "content": "import Joi from '../../utils/joi.js'\nimport all from 'it-all'\nimport { multipartRequestParser } from '../../utils/multipart-request-parser.js'\nimport Boom from '@hapi/boom'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\nimport { streamResponse } from '../../utils/stream-response.js'\nimport { pushable } from 'it-pushable'\nimport { base64url } from 'multiformats/bases/base64'\n\n/**\n * @typedef {import('@libp2p/interface-pubsub').Message} Message\n */\n\nconst preDecodeTopicFromHttpRpc = {\n  assign: 'topic',\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} _h\n   */\n  method: async (request, _h) => {\n    try {\n      return uint8ArrayToString(base64url.decode(request.query.topic))\n    } catch (/** @type {any} */ err) {\n      throw Boom.boomify(err, { message: `Failed to decode topic  from HTTP RPC form ${request.query.topic}` })\n    }\n  }\n}\n\nexport const subscribeResource = {\n  options: {\n    timeout: {\n      socket: false\n    },\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        topic: Joi.string().required()\n      })\n        .rename('arg', 'topic', {\n          override: true,\n          ignoreUndefined: true\n        })\n    },\n    pre: [preDecodeTopicFromHttpRpc]\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      pre: {\n        topic // decoded version created by preDecodeTopicFromHttpRpc\n      }\n    } = request\n\n    // request.raw.res.setHeader('x-chunked-output', '1')\n    request.raw.res.setHeader('content-type', 'identity') // stop gzip from buffering, see https://github.com/hapijs/hapi/issues/2975\n    // request.raw.res.setHeader('Trailer', 'X-Stream-Error')\n\n    return streamResponse(request, h, () => {\n      const output = pushable({ objectMode: true })\n\n      /**\n       * @type {import('@libp2p/interfaces/events').EventHandler<Message>}\n       */\n      const handler = (msg) => {\n        if (msg.type === 'signed') {\n          let numberString = msg.sequenceNumber.toString(16)\n\n          if (numberString.length % 2 !== 0) {\n            numberString = `0${numberString}`\n          }\n\n          const sequenceNumber = base64url.encode(uint8ArrayFromString(numberString, 'base16'))\n\n          output.push({\n            from: msg.from, // TODO: switch to peerIdFromString(msg.from).toString() when go-ipfs defaults to CIDv1\n            data: base64url.encode(msg.data),\n            seqno: sequenceNumber,\n            topicIDs: [base64url.encode(uint8ArrayFromString(msg.topic))],\n            key: base64url.encode(msg.key),\n            signature: base64url.encode(msg.signature)\n          })\n        } else {\n          output.push({\n            data: base64url.encode(msg.data),\n            topicIDs: [base64url.encode(uint8ArrayFromString(msg.topic))]\n          })\n        }\n      }\n\n      // js-ipfs-http-client needs a reply, and go-ipfs does the same thing\n      output.push({})\n\n      const unsubscribe = () => {\n        ipfs.pubsub.unsubscribe(topic, handler)\n        output.end()\n      }\n\n      request.raw.res.once('close', unsubscribe)\n\n      ipfs.pubsub.subscribe(topic, handler, {\n        signal\n      })\n        .catch(err => output.end(err))\n\n      return output\n    })\n  }\n}\n\nexport const publishResource = {\n  options: {\n    payload: {\n      parse: false,\n      output: 'stream'\n    },\n    pre: [preDecodeTopicFromHttpRpc, {\n      assign: 'data',\n      /**\n       * @param {import('../../types').Request} request\n       * @param {import('@hapi/hapi').ResponseToolkit} _h\n       */\n      method: async (request, _h) => {\n        if (!request.payload) {\n          throw Boom.badRequest('argument \"data\" is required')\n        }\n\n        let data\n\n        for await (const part of multipartRequestParser(request.raw.req)) {\n          if (part.type === 'file') {\n            data = Buffer.concat(await all(part.content))\n          }\n        }\n\n        if (!data || data.byteLength === 0) {\n          throw Boom.badRequest('argument \"data\" is required')\n        }\n\n        return data\n      }\n    }],\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        topic: Joi.string().required(),\n        discover: Joi.boolean(),\n        timeout: Joi.timeout()\n      })\n        .rename('arg', 'topic', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      pre: {\n        topic,\n        data\n      },\n      query: {\n        timeout\n      }\n    } = request\n\n    try {\n      await ipfs.pubsub.publish(topic, data, {\n        signal,\n        timeout\n      })\n    } catch (/** @type {any} */ err) {\n      throw Boom.boomify(err, { message: `Failed to publish to topic ${topic}` })\n    }\n\n    return h.response()\n  }\n}\n\nexport const lsResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        timeout: Joi.timeout()\n      })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        timeout\n      }\n    } = request\n\n    let subscriptions\n    try {\n      subscriptions = await ipfs.pubsub.ls({\n        signal,\n        timeout\n      })\n    } catch (/** @type {any} */ err) {\n      throw Boom.boomify(err, { message: 'Failed to list subscriptions' })\n    }\n\n    return h.response({ Strings: subscriptions.map(s => base64url.encode(uint8ArrayFromString(s))) })\n  }\n}\n\nexport const peersResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        topic: Joi.string().required(),\n        timeout: Joi.timeout()\n      })\n        .rename('arg', 'topic', {\n          override: true,\n          ignoreUndefined: true\n        })\n    },\n    pre: [preDecodeTopicFromHttpRpc]\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      pre: {\n        topic\n      },\n      query: {\n        timeout\n      }\n    } = request\n\n    let peers\n    try {\n      peers = await ipfs.pubsub.peers(topic, {\n        signal,\n        timeout\n      })\n    } catch (/** @type {any} */ err) {\n      const message = topic\n        ? `Failed to find peers subscribed to ${topic}: ${err}`\n        : `Failed to find peers: ${err}`\n\n      throw Boom.boomify(err, { message })\n    }\n\n    return h.response({ Strings: peers })\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/repo.js",
    "content": "import Joi from '../../utils/joi.js'\nimport map from 'it-map'\nimport filter from 'it-filter'\nimport { pipe } from 'it-pipe'\nimport { streamResponse } from '../../utils/stream-response.js'\n\nexport const gcResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        streamErrors: Joi.boolean().default(false),\n        timeout: Joi.timeout()\n      })\n        .rename('stream-errors', 'streamErrors', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        streamErrors,\n        timeout\n      }\n    } = request\n\n    return streamResponse(request, h, () => pipe(\n      ipfs.repo.gc({\n        signal,\n        timeout\n      }),\n      async function * filterErrors (source) {\n        yield * filter(source, r => !r.err || streamErrors)\n      },\n      async function * transformGcOutput (source) {\n        yield * map(source, r => ({\n          Error: (r.err && r.err.message) || undefined,\n          Key: (!r.err && { '/': r.cid.toString() }) || undefined\n        }))\n      }\n    ))\n  }\n}\n\nexport const versionResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        timeout: Joi.timeout()\n      })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  handler: async (request, h) => {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        timeout\n      }\n    } = request\n\n    const version = await ipfs.repo.version({\n      signal,\n      timeout\n    })\n    return h.response({\n      Version: version\n    })\n  }\n}\n\nexport const statResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        timeout: Joi.timeout()\n      })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  handler: async (request, h) => {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        timeout\n      }\n    } = request\n\n    const stat = await ipfs.repo.stat({\n      signal,\n      timeout\n    })\n\n    return h.response({\n      NumObjects: stat.numObjects.toString(),\n      RepoSize: stat.repoSize.toString(),\n      RepoPath: stat.repoPath,\n      Version: stat.version,\n      StorageMax: stat.storageMax.toString()\n    })\n  }\n\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/resolve.js",
    "content": "import Joi from '../../utils/joi.js'\n\nexport const resolveResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        path: Joi.string().required(),\n        recursive: Joi.boolean().default(true),\n        cidBase: Joi.string().default('base58btc'),\n        timeout: Joi.timeout()\n      })\n        .rename('arg', 'path', {\n          override: true,\n          ignoreUndefined: true\n        })\n        .rename('cid-base', 'cidBase', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        path,\n        recursive,\n        cidBase,\n        timeout\n      }\n    } = request\n\n    const res = await ipfs.resolve(path, {\n      recursive,\n      cidBase,\n      signal,\n      timeout\n    })\n\n    return h.response({ Path: res })\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/shutdown.js",
    "content": "\n/*\n * Stop the daemon.\n *\n * Returns an empty response to the caller then\n * on the next 'tick' emits SIGTERM.\n */\nexport const shutdownResource = {\n  /**\n   * @param {import('../../types').Request} _request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  handler: (_request, h) => {\n    setImmediate(() => process.emit('SIGTERM', 'SIGTERM'))\n    return h.response()\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/stats.js",
    "content": "import map from 'it-map'\nimport { pipe } from 'it-pipe'\nimport { streamResponse } from '../../utils/stream-response.js'\nimport Joi from '../../utils/joi.js'\n\nexport { statResource as bitswapResource } from './bitswap.js'\n\nexport { statResource as repoResource } from './repo.js'\n\nexport const bwResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        peer: Joi.peerId(),\n        proto: Joi.string(),\n        poll: Joi.boolean().default(false),\n        interval: Joi.string().default('1s'),\n        timeout: Joi.timeout()\n      })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        peer,\n        proto,\n        poll,\n        interval,\n        timeout\n      }\n    } = request\n\n    return streamResponse(request, h, () => pipe(\n      ipfs.stats.bw({\n        peer,\n        proto,\n        poll,\n        interval,\n        signal,\n        timeout\n      }),\n      async function * (source) {\n        yield * map(source, stat => ({\n          TotalIn: stat.totalIn.toString(),\n          TotalOut: stat.totalOut.toString(),\n          RateIn: stat.rateIn,\n          RateOut: stat.rateOut\n        }))\n      }\n    ))\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/swarm.js",
    "content": "import Joi from '../../utils/joi.js'\nimport { peerIdFromString } from '@libp2p/peer-id'\nimport { multiaddr } from '@multiformats/multiaddr'\n\nexport const peersResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        verbose: Joi.boolean().default(false),\n        direction: Joi.boolean().default(false),\n        timeout: Joi.timeout()\n      })\n        .rename('v', 'verbose', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        verbose,\n        direction,\n        timeout\n      }\n    } = request\n\n    const peers = await ipfs.swarm.peers({\n      verbose,\n      signal,\n      timeout\n    })\n\n    return h.response({\n      Peers: peers.map((p) => {\n        return {\n          Peer: p.peer.toString(),\n          Addr: p.addr.toString(),\n          Direction: verbose || direction ? p.direction : undefined,\n          Muxer: verbose ? p.muxer : undefined,\n          Latency: verbose ? p.latency : undefined\n        }\n      })\n    })\n  }\n}\n\nexport const addrsResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        timeout: Joi.timeout()\n      })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        timeout\n      }\n    } = request\n\n    const peers = await ipfs.swarm.addrs({\n      signal,\n      timeout\n    })\n\n    return h.response({\n      Addrs: peers.reduce((/** @type {Record<string, any>} */ addrs, peer) => {\n        addrs[peer.id.toString()] = peer.addrs.map(a => a.toString())\n        return addrs\n      }, {})\n    })\n  }\n}\n\nexport const localAddrsResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        timeout: Joi.timeout()\n      })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        timeout\n      }\n    } = request\n\n    const addrs = await ipfs.swarm.localAddrs({\n      signal,\n      timeout\n    })\n\n    return h.response({\n      Strings: addrs.map((addr) => addr.toString())\n    })\n  }\n}\n\nexport const connectResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        addr: Joi.multiaddr().required(),\n        timeout: Joi.timeout()\n      })\n        .rename('arg', 'addr', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        addr,\n        timeout\n      }\n    } = request\n\n    // dialing another peer returns after the connection is opened\n    // but before identify completes. 'abort' is emitted by the signal\n    // when the client disconnects, but we want Identify to complete\n    // so don't forward on the abort event if we've successfully connected.\n    const controller = new AbortController()\n    let connected = false\n\n    signal.addEventListener('abort', () => {\n      if (!connected) {\n        controller.abort()\n      }\n    })\n\n    let peerIdOrMultiaddr\n\n    if (addr[0] === '/') {\n      peerIdOrMultiaddr = multiaddr(addr)\n    } else {\n      peerIdOrMultiaddr = peerIdFromString(addr)\n    }\n\n    await ipfs.swarm.connect(peerIdOrMultiaddr, {\n      signal: controller.signal,\n      timeout\n    })\n\n    connected = true\n\n    return h.response({\n      Strings: [`connect ${addr} success`]\n    })\n  }\n}\n\nexport const disconnectResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        addr: Joi.multiaddr().required(),\n        timeout: Joi.timeout()\n      })\n        .rename('arg', 'addr', {\n          override: true,\n          ignoreUndefined: true\n        })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        addr,\n        timeout\n      }\n    } = request\n\n    let peerIdOrMultiaddr\n\n    if (addr[0] === '/') {\n      peerIdOrMultiaddr = multiaddr(addr)\n    } else {\n      peerIdOrMultiaddr = peerIdFromString(addr)\n    }\n\n    await ipfs.swarm.disconnect(peerIdOrMultiaddr, {\n      signal,\n      timeout\n    })\n\n    return h.response({\n      Strings: [`disconnect ${addr} success`]\n    })\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/resources/version.js",
    "content": "import Joi from '../../utils/joi.js'\nimport { ipfsHttpClient } from '../../version.js'\n\nexport const versionResource = {\n  options: {\n    validate: {\n      options: {\n        allowUnknown: true,\n        stripUnknown: true\n      },\n      query: Joi.object().keys({\n        timeout: Joi.timeout()\n      })\n    }\n  },\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    const {\n      app: {\n        signal\n      },\n      server: {\n        app: {\n          ipfs\n        }\n      },\n      query: {\n        timeout\n      }\n    } = request\n\n    const version = await ipfs.version({\n      signal,\n      timeout\n    })\n\n    return h.response({\n      Version: version.version,\n      Commit: version.commit,\n      Repo: version.repo,\n      'ipfs-core': version['ipfs-core'],\n      'interface-ipfs-core': version['interface-ipfs-core'],\n      'ipfs-http-client': ipfsHttpClient\n    })\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/routes/bitswap.js",
    "content": "import {\n  wantlistResource,\n  statResource,\n  unwantResource\n} from '../resources/bitswap.js'\n\nexport default [\n  {\n    method: 'POST',\n    path: '/api/v0/bitswap/wantlist',\n    ...wantlistResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/bitswap/stat',\n    ...statResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/bitswap/unwant',\n    ...unwantResource\n  }\n]\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/routes/block.js",
    "content": "import {\n  getResource,\n  putResource,\n  rmResource,\n  statResource\n} from '../resources/block.js'\n\nexport default [\n  {\n    method: 'POST',\n    path: '/api/v0/block/get',\n    ...getResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/block/put',\n    ...putResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/block/rm',\n    ...rmResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/block/stat',\n    ...statResource\n  }\n]\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/routes/bootstrap.js",
    "content": "import {\n  listResource,\n  addResource,\n  addDefaultResource,\n  rmResource,\n  rmAllResource\n} from '../resources/bootstrap.js'\n\nexport default [\n  {\n    method: 'POST',\n    path: '/api/v0/bootstrap',\n    ...listResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/bootstrap/add',\n    ...addResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/bootstrap/add/default',\n    ...addDefaultResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/bootstrap/list',\n    ...listResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/bootstrap/rm',\n    ...rmResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/bootstrap/rm/all',\n    ...rmAllResource\n  }\n]\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/routes/config.js",
    "content": "import {\n  getOrSetResource,\n  showResource,\n  replaceResource,\n  profilesApplyResource,\n  profilesListResource\n} from '../resources/config.js'\n\nexport default [\n  {\n    method: 'POST',\n    path: '/api/v0/config/{key?}',\n    ...getOrSetResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/config/show',\n    ...showResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/config/replace',\n    ...replaceResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/config/profile/apply',\n    ...profilesApplyResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/config/profile/list',\n    ...profilesListResource\n  }\n]\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/routes/dag.js",
    "content": "import {\n  exportResource,\n  getResource,\n  importResource,\n  putResource,\n  resolveResource\n} from '../resources/dag.js'\n\nexport default [\n  {\n    method: 'POST',\n    path: '/api/v0/dag/export',\n    ...exportResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/dag/get',\n    ...getResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/dag/import',\n    ...importResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/dag/put',\n    ...putResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/dag/resolve',\n    ...resolveResource\n  }\n]\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/routes/debug.js",
    "content": "/* eslint-disable max-depth */\n\nimport client from 'prom-client'\nimport Boom from '@hapi/boom'\nimport { disable, enable } from '@libp2p/logger'\n\n// Endpoint for handling debug metrics\nexport default [{\n  method: 'GET',\n  path: '/debug/metrics/prometheus',\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    if (!process.env.IPFS_MONITORING) {\n      throw Boom.notImplemented('Monitoring is disabled. Enable it by setting environment variable IPFS_MONITORING')\n    }\n\n    return h.response(await client.register.metrics())\n      .type(client.register.contentType)\n  }\n}, {\n  method: 'POST',\n  path: '/debug/logs',\n  /**\n   * @param {import('../../types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  async handler (request, h) {\n    if (!process.env.IPFS_MONITORING) {\n      throw Boom.notImplemented('Monitoring is disabled. Enable it by setting environment variable IPFS_MONITORING')\n    }\n\n    if (!request.query.debug) {\n      disable()\n    } else {\n      enable(request.query.debug)\n    }\n\n    return h.response()\n  }\n}]\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/routes/dht.js",
    "content": "import {\n  findPeerResource,\n  findProvsResource,\n  getResource,\n  provideResource,\n  putResource,\n  queryResource\n} from '../resources/dht.js'\n\nexport default [\n  {\n    method: 'POST',\n    path: '/api/v0/dht/findpeer',\n    ...findPeerResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/dht/findprovs',\n    ...findProvsResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/dht/get',\n    ...getResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/dht/provide',\n    ...provideResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/dht/put',\n    ...putResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/dht/query',\n    ...queryResource\n  }\n]\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/routes/dns.js",
    "content": "import {\n  dnsResource\n} from '../resources/dns.js'\n\nexport default [{\n  method: 'POST',\n  path: '/api/v0/dns',\n  ...dnsResource\n}]\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/routes/files-regular.js",
    "content": "import {\n  catResource,\n  getResource,\n  addResource,\n  lsResource,\n  refsResource,\n  refsLocalResource\n} from '../resources/files-regular.js'\n\nexport default [\n  {\n    method: 'POST',\n    path: '/api/v0/cat',\n    ...catResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/get',\n    ...getResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/add',\n    ...addResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/ls',\n    ...lsResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/refs',\n    ...refsResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/refs/local',\n    ...refsLocalResource\n  }\n]\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/routes/files.js",
    "content": "import { chmodResource } from '../resources/files/chmod.js'\nimport { cpResource } from '../resources/files/cp.js'\nimport { flushResource } from '../resources/files/flush.js'\nimport { lsResource } from '../resources/files/ls.js'\nimport { mkdirResource } from '../resources/files/mkdir.js'\nimport { mvResource } from '../resources/files/mv.js'\nimport { readResource } from '../resources/files/read.js'\nimport { rmResource } from '../resources/files/rm.js'\nimport { statResource } from '../resources/files/stat.js'\nimport { touchResource } from '../resources/files/touch.js'\nimport { writeResource } from '../resources/files/write.js'\n\nexport default [\n  {\n    method: 'POST',\n    path: '/api/v0/files/chmod',\n    ...chmodResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/files/cp',\n    ...cpResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/files/flush',\n    ...flushResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/files/ls',\n    ...lsResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/files/mkdir',\n    ...mkdirResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/files/mv',\n    ...mvResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/files/read',\n    ...readResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/files/rm',\n    ...rmResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/files/stat',\n    ...statResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/files/touch',\n    ...touchResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/files/write',\n    ...writeResource\n  }\n]\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/routes/id.js",
    "content": "import {\n  idResource\n} from '../resources/id.js'\n\nexport default [{\n  method: 'POST',\n  path: '/api/v0/id',\n  ...idResource\n}]\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/routes/index.js",
    "content": "import bitswapRoutes from './bitswap.js'\nimport blockRoutes from './block.js'\nimport bootstrapRoutes from './bootstrap.js'\nimport configRoutes from './config.js'\nimport dagRoutes from './dag.js'\nimport debugRoutes from './debug.js'\nimport dhtRoutes from './dht.js'\nimport dnsRoutes from './dns.js'\nimport filesRegularRoutes from './files-regular.js'\nimport filesRoutes from './files.js'\nimport idRoutes from './id.js'\nimport keyRoutes from './key.js'\nimport nameRoutes from './name.js'\nimport objectRoutes from './object.js'\nimport pinRoutes from './pin.js'\nimport pingRoutes from './ping.js'\nimport pubsubRoutes from './pubsub.js'\nimport repoRoutes from './repo.js'\nimport resolveRoutes from './resolve.js'\nimport shutdownRoutes from './shutdown.js'\nimport statsRoutes from './stats.js'\nimport swarmRoutes from './swarm.js'\nimport versionRoutes from './version.js'\nimport webuiRoutes from './webui.js'\n\n/** @type {import('@hapi/hapi').ServerRoute[]} */\nexport const routes = []\n\nroutes.push(\n  ...bitswapRoutes,\n  ...blockRoutes,\n  ...bootstrapRoutes,\n  ...configRoutes,\n  ...dagRoutes,\n  ...debugRoutes,\n  ...dhtRoutes,\n  ...dnsRoutes,\n  ...filesRegularRoutes,\n  ...filesRoutes,\n  ...idRoutes,\n  ...keyRoutes,\n  ...nameRoutes,\n  ...objectRoutes,\n  ...pinRoutes,\n  ...pingRoutes,\n  ...pubsubRoutes,\n  ...repoRoutes,\n  ...resolveRoutes,\n  ...shutdownRoutes,\n  ...statsRoutes,\n  ...swarmRoutes,\n  ...versionRoutes,\n  ...webuiRoutes\n)\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/routes/key.js",
    "content": "import {\n  listResource,\n  genResource,\n  rmResource,\n  renameResource,\n  importResource\n} from '../resources/key.js'\n\nexport default [\n  {\n    method: 'POST',\n    path: '/api/v0/key/list',\n    ...listResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/key/gen',\n    ...genResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/key/rm',\n    ...rmResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/key/rename',\n    ...renameResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/key/import',\n    ...importResource\n  }\n]\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/routes/name.js",
    "content": "import {\n  resolveResource,\n  publishResource,\n  stateResource,\n  pubsubSubsResource,\n  pubsubCancelResource\n} from '../resources/name.js'\n\nexport default [\n  {\n    method: 'POST',\n    path: '/api/v0/name/resolve',\n    ...resolveResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/name/publish',\n    ...publishResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/name/pubsub/state',\n    ...stateResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/name/pubsub/subs',\n    ...pubsubSubsResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/name/pubsub/cancel',\n    ...pubsubCancelResource\n  }\n]\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/routes/object.js",
    "content": "import {\n  newResource,\n  getResource,\n  putResource,\n  statResource,\n  dataResource,\n  linksResource,\n  patchAppendDataResource,\n  patchSetDataResource,\n  patchAddLinkResource,\n  patchRmLinkResource\n} from '../resources/object.js'\n\nexport default [\n  {\n    method: 'POST',\n    path: '/api/v0/object/new',\n    ...newResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/object/get',\n    ...getResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/object/put',\n    ...putResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/object/stat',\n    ...statResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/object/data',\n    ...dataResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/object/links',\n    ...linksResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/object/patch/append-data',\n    ...patchAppendDataResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/object/patch/set-data',\n    ...patchSetDataResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/object/patch/add-link',\n    ...patchAddLinkResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/object/patch/rm-link',\n    ...patchRmLinkResource\n  }\n]\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/routes/pin.js",
    "content": "import {\n  addResource,\n  rmResource,\n  lsResource\n} from '../resources/pin.js'\n\nexport default [\n  {\n    method: 'POST',\n    path: '/api/v0/pin/add',\n    ...addResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/pin/rm',\n    ...rmResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/pin/ls',\n    ...lsResource\n  }\n]\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/routes/ping.js",
    "content": "import {\n  pingResource\n} from '../resources/ping.js'\n\nexport default [{\n  method: 'POST',\n  path: '/api/v0/ping',\n  ...pingResource\n}]\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/routes/pubsub.js",
    "content": "import {\n  subscribeResource,\n  publishResource,\n  lsResource,\n  peersResource\n} from '../resources/pubsub.js'\n\nexport default [\n  {\n    method: 'POST',\n    path: '/api/v0/pubsub/sub',\n    ...subscribeResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/pubsub/pub',\n    ...publishResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/pubsub/ls',\n    ...lsResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/pubsub/peers',\n    ...peersResource\n  }\n]\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/routes/repo.js",
    "content": "import {\n  versionResource,\n  statResource,\n  gcResource\n} from '../resources/repo.js'\n\nexport default [\n  {\n    method: 'POST',\n    path: '/api/v0/repo/version',\n    ...versionResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/repo/stat',\n    ...statResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/repo/gc',\n    ...gcResource\n  }\n]\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/routes/resolve.js",
    "content": "import {\n  resolveResource\n} from '../resources/resolve.js'\n\nexport default [{\n  method: 'POST',\n  path: '/api/v0/resolve',\n  ...resolveResource\n}]\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/routes/shutdown.js",
    "content": "import {\n  shutdownResource\n} from '../resources/shutdown.js'\n\nexport default [{\n  method: 'POST',\n  path: '/api/v0/shutdown',\n  ...shutdownResource\n}]\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/routes/stats.js",
    "content": "import {\n  bitswapResource,\n  repoResource,\n  bwResource\n} from '../resources/stats.js'\n\nexport default [\n  {\n    method: 'POST',\n    path: '/api/v0/stats/bitswap',\n    ...bitswapResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/stats/repo',\n    ...repoResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/stats/bw',\n    ...bwResource\n  }\n]\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/routes/swarm.js",
    "content": "import {\n  peersResource,\n  addrsResource,\n  localAddrsResource,\n  connectResource,\n  disconnectResource\n} from '../resources/swarm.js'\n\nexport default [\n  {\n    method: 'POST',\n    path: '/api/v0/swarm/peers',\n    ...peersResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/swarm/addrs',\n    ...addrsResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/swarm/addrs/local',\n    ...localAddrsResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/swarm/connect',\n    ...connectResource\n  },\n  {\n    method: 'POST',\n    path: '/api/v0/swarm/disconnect',\n    ...disconnectResource\n  }\n]\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/routes/version.js",
    "content": "import { versionResource } from '../resources/version.js'\n\nexport default [{\n  method: 'POST',\n  path: '/api/v0/version',\n  ...versionResource\n}]\n"
  },
  {
    "path": "packages/ipfs-http-server/src/api/routes/webui.js",
    "content": "import resources from 'ipfs-http-gateway/resources/index'\n\nconst webuiCid = 'bafybeif4zkmu7qdhkpf3pnhwxipylqleof7rl6ojbe7mq3fzogz6m4xk3i' // v2.11.4\n\nexport default [\n  {\n    method: 'GET',\n    path: `/ipfs/${webuiCid}/{path*}`, // only the whitelisted webui is allowed on API port\n    options: {\n      handler: resources.gateway.handler,\n      response: {\n        ranges: false // disable built-in support, handler does it manually\n      },\n      ext: {\n        onPostHandler: { method: resources.gateway.afterHandler }\n      }\n    }\n  },\n  {\n    method: 'GET',\n    path: '/webui/{slug?}', // optional slug makes it work with and without slash\n    /**\n     * @param {import('../../types').Request} _request\n     * @param {import('@hapi/hapi').ResponseToolkit} h\n     */\n    handler (_request, h) {\n      return h.redirect(`/ipfs/${webuiCid}/`)\n    }\n  }\n]\n"
  },
  {
    "path": "packages/ipfs-http-server/src/error-handler.js",
    "content": "import Boom from '@hapi/boom'\n\n/**\n * @param {import('./types').Server} server\n */\nexport function errorHandler (server) {\n  /**\n   * @param {import('./types').Request} request\n   * @param {import('@hapi/hapi').ResponseToolkit} h\n   */\n  server.ext('onPreResponse', (request, h) => {\n    const res = request.response\n\n    if (!Boom.isBoom(res)) {\n      return h.continue\n    }\n\n    const message = res.message || res.output.payload.message\n    const { statusCode } = res.output.payload\n    let code\n\n    if (res.data && res.data.code != null) {\n      code = res.data.code\n    } else {\n      // Map status code to error code as defined by go-ipfs\n      // https://github.com/ipfs/go-ipfs-cmdkit/blob/0262a120012063c359727423ec703b9649eec447/error.go#L12-L20\n      if (statusCode >= 400 && statusCode < 500) {\n        code = statusCode === 404 ? 3 : 1\n      } else {\n        code = 0\n      }\n    }\n\n    if (process.env.DEBUG || statusCode >= 500) {\n      const { req } = request.raw\n      const debug = {\n        method: req.method?.toString(),\n        url: request.url?.toString(),\n        headers: req.headers,\n        payload: request.payload,\n        response: res.output.payload\n      }\n\n      server.logger.error(debug)\n    }\n\n    const response = h.response({\n      Message: message,\n      Code: code,\n      Type: 'error'\n    }).code(statusCode)\n\n    const headers = res.output.headers || {}\n\n    Object.keys(headers).forEach(header => {\n      response.header(header, `${headers[header]}`)\n    })\n\n    return response\n  })\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/index.js",
    "content": "import Hapi from '@hapi/hapi'\nimport Pino from 'hapi-pino'\nimport { logger, enabled } from '@libp2p/logger'\nimport { multiaddr } from '@multiformats/multiaddr'\nimport toMultiaddr from '@multiformats/uri-to-multiaddr'\nimport Boom from '@hapi/boom'\nimport { routes } from './api/routes/index.js'\nimport { errorHandler } from './error-handler.js'\nimport { setMaxListeners } from 'events'\n\nconst LOG = 'ipfs:http-api'\nconst LOG_ERROR = 'ipfs:http-api:error'\n\n/**\n * @typedef {import('ipfs-core-types').IPFS} IPFS\n * @typedef {import('./types').Server} Server\n * @typedef {import('libp2p').Libp2p} libp2p\n */\n\n/**\n * @param {import('@hapi/hapi').ServerInfo} info\n */\nfunction hapiInfoToMultiaddr (info) {\n  let hostname = info.host\n  let uri = info.uri\n  // ipv6 fix\n  if (hostname.includes(':') && !hostname.startsWith('[')) {\n    // hapi 16 produces invalid URI for ipv6\n    // we fix it here by restoring missing square brackets\n    hostname = `[${hostname}]`\n    uri = uri.replace(`://${info.host}`, `://${hostname}`)\n  }\n  return toMultiaddr(uri)\n}\n\n/**\n * @param {string | string[]} serverAddrs\n * @param {(host: string, port: string, ipfs: IPFS, cors: Record<string, any>) => Promise<Server>} createServer\n * @param {IPFS} ipfs\n * @param {Record<string, any>} cors\n */\nasync function serverCreator (serverAddrs, createServer, ipfs, cors) {\n  serverAddrs = serverAddrs || []\n  // just in case the address is just string\n  serverAddrs = Array.isArray(serverAddrs) ? serverAddrs : [serverAddrs]\n\n  /** @type {Server[]} */\n  const servers = []\n  for (const address of serverAddrs) {\n    const addrParts = address.split('/')\n    const server = await createServer(addrParts[2], addrParts[4], ipfs, cors)\n    await server.start()\n    server.info.ma = hapiInfoToMultiaddr(server.info)\n    servers.push(server)\n  }\n  return servers\n}\n\n/**\n * @param {string} [str]\n * @param {string[]} [allowedOrigins]\n */\nfunction isAllowedOrigin (str, allowedOrigins = []) {\n  if (!str) {\n    return false\n  }\n\n  let origin\n\n  try {\n    origin = (new URL(str)).origin\n  } catch {\n    return false\n  }\n\n  for (const allowedOrigin of allowedOrigins) {\n    if (allowedOrigin === '*') {\n      return true\n    }\n\n    if (allowedOrigin === origin) {\n      return true\n    }\n  }\n\n  return false\n}\n\nexport class HttpApi {\n  /**\n   * @param {IPFS} ipfs\n   */\n  constructor (ipfs) {\n    this._ipfs = ipfs\n    this._log = logger(LOG)\n    /** @type {Server[]} */\n    this._apiServers = []\n  }\n\n  /**\n   * Starts the IPFS HTTP server\n   */\n  async start () {\n    this._log('starting')\n\n    const ipfs = this._ipfs\n\n    const config = await ipfs.config.getAll()\n    const headers = (config.API && config.API.HTTPHeaders) || {}\n    const apiAddrs = (config.Addresses && config.Addresses.API) || []\n\n    this._apiServers = await serverCreator(apiAddrs, this._createApiServer, ipfs, {\n      origin: headers['Access-Control-Allow-Origin'] || [],\n      credentials: Boolean(headers['Access-Control-Allow-Credentials'])\n    })\n\n    // for the CLI to know the whereabouts of the API\n    // @ts-expect-error - ipfs.repo.setApiAddr is not part of the core api\n    await ipfs.repo.setApiAddr(this._apiServers[0].info.ma)\n\n    this._log('started')\n  }\n\n  /**\n   * @param {string} host\n   * @param {string} port\n   * @param {IPFS} ipfs\n   * @param {Record<string, any>} cors\n   */\n  async _createApiServer (host, port, ipfs, cors) {\n    cors = {\n      ...cors,\n      additionalHeaders: ['X-Stream-Output', 'X-Chunked-Output', 'X-Content-Length'],\n      additionalExposedHeaders: ['X-Stream-Output', 'X-Chunked-Output', 'X-Content-Length']\n    }\n\n    const enableCors = Boolean(cors.origin?.length)\n\n    const server = Hapi.server({\n      host,\n      port,\n      routes: {\n        cors: enableCors ? cors : false,\n        response: {\n          emptyStatusCode: 200\n        }\n      },\n      // Disable Compression\n      // Why? Streaming compression in Hapi is not stable enough,\n      // it requires bug-prone hacks such as https://github.com/hapijs/hapi/issues/3599\n      compression: false\n    })\n    server.app.ipfs = ipfs\n\n    await server.register({\n      plugin: Pino,\n      options: {\n        prettyPrint: Boolean(enabled(LOG)),\n        logEvents: ['onPostStart', 'onPostStop', 'response', 'request-error'],\n        level: enabled(LOG) ? 'debug' : (enabled(LOG_ERROR) ? 'error' : 'fatal')\n      }\n    })\n\n    // block all non-post or non-options requests\n    server.ext({\n      type: 'onRequest',\n      method: function (request, h) {\n        if (request.method === 'post' || request.method === 'options') {\n          return h.continue\n        }\n\n        if (request.method === 'get') {\n          if (request.path.startsWith('/ipfs') || request.path.startsWith('/webui')) {\n            // allow requests to the gateway and webui\n            return h.continue\n          }\n\n          if (process.env.IPFS_MONITORING && request.path.startsWith('/debug')) {\n            // allow requests to prometheus stats when monitoring is enabled\n            return h.continue\n          }\n        }\n\n        throw Boom.methodNotAllowed()\n      }\n    })\n\n    // https://tools.ietf.org/html/rfc7231#section-6.5.5\n    server.ext('onPreResponse', (request, h) => {\n      const { response } = request\n\n      if (Boom.isBoom(response) && response.output && response.output.statusCode === 405) {\n        response.output.headers.Allow = 'OPTIONS, POST'\n      }\n\n      return h.continue\n    })\n\n    // https://github.com/ipfs/go-ipfs-cmds/pull/193/files\n    server.ext({\n      type: 'onRequest',\n      method: function (request, h) {\n        // This check affects POST as we should never get POST requests from a\n        // browser without Origin or Referer, but we might:\n        // https://bugzilla.mozilla.org/show_bug.cgi?id=429594\n        if (request.method !== 'post') {\n          return h.continue\n        }\n\n        const headers = request.headers || {}\n        const origin = headers.origin || ''\n        const referer = headers.referer || ''\n        const userAgent = headers['user-agent'] || ''\n\n        // If these are set, check them against the configured list\n        if (origin || referer) {\n          if (!isAllowedOrigin(origin || referer, cors.origin)) {\n            // Hapi will not allow an empty CORS origin list so we have to manually\n            // reject the request if CORS origins have not been configured\n            throw Boom.forbidden()\n          }\n\n          return h.continue\n        }\n\n        // Allow if the user agent includes Electron\n        if (userAgent.includes('Electron')) {\n          return h.continue\n        }\n\n        // Allow if the user agent does not start with Mozilla... (i.e. curl)\n        if (!userAgent.startsWith('Mozilla')) {\n          return h.continue\n        }\n\n        // Disallow otherwise.\n        //\n        // This means the request probably came from a browser and thus, it\n        // should have included Origin or referer headers.\n        throw Boom.forbidden()\n      }\n    })\n\n    server.ext({\n      type: 'onRequest',\n      method: function (request, h) {\n        const controller = new AbortController()\n        // make sure we don't cause warnings to be logged for 'abort' event listeners\n        setMaxListeners && setMaxListeners(Infinity, controller.signal)\n        request.app.signal = controller.signal\n\n        // abort the request if the client disconnects\n        request.raw.res.once('close', () => {\n          controller.abort()\n        })\n\n        // abort the request if the client disconnects\n        request.events.once('disconnect', () => {\n          controller.abort()\n        })\n\n        return h.continue\n      }\n    })\n\n    server.route(routes)\n\n    errorHandler(server)\n\n    return server\n  }\n\n  get apiAddr () {\n    if (!this._apiServers || !this._apiServers.length) {\n      throw new Error('API address unavailable - server is not started')\n    }\n    return multiaddr('/ip4/127.0.0.1/tcp/' + this._apiServers[0].info.port)\n  }\n\n  async stop () {\n    this._log('stopping')\n    /**\n     * @param {Server[]} servers\n     */\n    const stopServers = servers => Promise.all((servers || []).map(s => s.stop()))\n    await Promise.all([\n      stopServers(this._apiServers)\n    ])\n    this._log('stopped')\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/types.ts",
    "content": "import type { IPFS } from 'ipfs-core-types'\nimport type { Request, Server } from '@hapi/hapi'\nimport type { Multiaddr } from '@multiformats/multiaddr'\nimport type { Mtime } from 'ipfs-unixfs'\n\ndeclare module '@hapi/hapi' {\n  interface ServerApplicationState {\n    ipfs: IPFS\n  }\n  interface RequestApplicationState {\n    signal: AbortSignal\n  }\n  interface ServerInfo {\n    ma: Multiaddr\n  }\n}\n\nexport type { Request, Server }\n\ninterface MultipartUpload {\n  name: string\n  mtime?: Mtime\n  mode?: number\n}\n\nexport interface MultipartFile extends MultipartUpload {\n  type: 'file'\n  content: AsyncIterable<Uint8Array>\n}\n\nexport interface MultipartDirectory extends MultipartUpload {\n  type: 'directory'\n}\n\nexport interface MultipartSymlink extends MultipartUpload {\n  type: 'symlink'\n  target: string\n}\n\nexport type MultipartEntry = MultipartFile | MultipartDirectory | MultipartSymlink\n"
  },
  {
    "path": "packages/ipfs-http-server/src/utils/joi.js",
    "content": "import { CID } from 'multiformats/cid'\nimport parseDuration from 'parse-duration'\nimport { multiaddr } from '@multiformats/multiaddr'\nimport { toCidAndPath } from 'ipfs-core-utils/to-cid-and-path'\nimport Joi from 'joi'\nimport { peerIdFromString } from '@libp2p/peer-id'\n\n/**\n * @param {*} value\n */\nconst toIpfsPath = (value) => {\n  if (!value) {\n    throw new Error('Must have value')\n  }\n\n  value = value.toString()\n  let startedWithIpfs = false\n\n  if (value.startsWith('/ipfs/')) {\n    startedWithIpfs = true\n    value = value.replace(/^\\/ipfs\\//, '')\n  }\n\n  // section after /ipfs/ should be a valid CID\n  const parts = value.split('/')\n\n  // will throw if not valid\n  parts[0] = CID.parse(parts[0])\n\n  // go-ipfs returns /ipfs/ prefix for ipfs paths when passed to the http api\n  // and not when it isn't.  E.g.\n  // GET /api/v0/ls?arg=/ipfs/Qmfoo  -> /ipfs/Qmfoo will be in the result\n  // GET /api/v0/ls?arg=Qmfoo  -> Qmfoo will be in the result\n  return `${startedWithIpfs ? '/ipfs/' : ''}${parts.join('/')}`\n}\n\n/**\n * @param {*} value\n */\nconst toCID = (value) => {\n  return CID.parse(value.toString().replace('/ipfs/', ''))\n}\n\n/**\n * @param {*} value\n * @param {import('joi').CustomHelpers} helpers\n */\nconst requireIfRequired = (value, helpers) => {\n  if (helpers.schema.$_getFlag('presence') === 'required' && !value) {\n    return { value, errors: helpers.error('required') }\n  }\n}\n\nexport default Joi\n  .extend(\n    // @ts-expect-error - according to typedefs coerce should always return\n    // { errors?: ErrorReport[], value?: any }\n    (joi) => {\n      return {\n        type: 'cid',\n        base: joi.any(),\n        validate: requireIfRequired,\n        coerce (value, _helpers) {\n          if (!value) {\n            return\n          }\n\n          return { value: toCID(value) }\n        }\n      }\n    },\n    (joi) => {\n      return {\n        type: 'peerId',\n        base: joi.any(),\n        validate: requireIfRequired,\n        coerce (value, _helpers) {\n          if (!value) {\n            return\n          }\n\n          return { value: peerIdFromString(value) }\n        }\n      }\n    },\n    (joi) => {\n      return {\n        type: 'ipfsPath',\n        base: joi.string(),\n        validate: requireIfRequired,\n        coerce (value, _helpers) {\n          if (!value) {\n            return\n          }\n\n          return { value: toIpfsPath(value) }\n        }\n      }\n    },\n    (joi) => {\n      return {\n        type: 'multiaddr',\n        base: joi.string(),\n        validate: requireIfRequired,\n        coerce (value, _helpers) {\n          if (!value) {\n            return\n          }\n\n          return { value: multiaddr(value).toString() }\n        }\n      }\n    },\n    (joi) => {\n      return {\n        type: 'timeout',\n        base: joi.number(),\n        validate: requireIfRequired,\n        coerce (value, _helpers) {\n          if (!value) {\n            return\n          }\n\n          return { value: parseDuration(value) }\n        }\n      }\n    },\n    (joi) => {\n      return {\n        type: 'cidAndPath',\n        base: joi.any(),\n        validate: requireIfRequired,\n        coerce (value, _helpers) {\n          if (!value) {\n            return\n          }\n\n          return { value: toCidAndPath(value) }\n        }\n      }\n    },\n    (joi) => {\n      return {\n        type: 'json',\n        base: joi.any(),\n        validate: requireIfRequired,\n        coerce (value, _helpers) {\n          if (!value) {\n            return\n          }\n\n          return { value: JSON.parse(value) }\n        }\n      }\n    })\n"
  },
  {
    "path": "packages/ipfs-http-server/src/utils/multipart-request-parser.js",
    "content": "import { concat as uint8ArrayConcat } from 'uint8arrays/concat'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\nimport drain from 'it-drain'\n// @ts-expect-error no types\nimport Content from '@hapi/content'\nimport multipart from 'it-multipart'\nimport qs from 'querystring'\n\nconst multipartFormdataType = 'multipart/form-data'\nconst applicationDirectory = 'application/x-directory'\nconst applicationSymlink = 'application/symlink'\n\n/**\n * @typedef {import('http').IncomingMessage} IncomingMessage\n * @typedef {import('it-multipart').Part} Part\n * @typedef {import('../types').MultipartEntry} MultipartEntry\n */\n\n/**\n * @param {string} mediatype\n */\nconst isDirectory = (mediatype) => mediatype === multipartFormdataType || mediatype === applicationDirectory\n\n/**\n * @param {string} disposition\n */\nconst parseDisposition = (disposition) => {\n  const details = {}\n  details.type = disposition.split(';')[0]\n\n  if (details.type === 'file' || details.type === 'form-data') {\n    const filenamePattern = / filename=\"(.[^\"]+)\"/\n    const filenameMatches = disposition.match(filenamePattern)\n    details.filename = filenameMatches ? filenameMatches[1] : ''\n\n    const namePattern = / name=\"(.[^\"]+)\"/\n    const nameMatches = disposition.match(namePattern)\n    details.name = nameMatches ? nameMatches[1] : ''\n  }\n\n  return details\n}\n\n/**\n * @param {AsyncIterable<Uint8Array>} stream\n */\nconst collect = async (stream) => {\n  const buffers = []\n  let size = 0\n\n  for await (const buf of stream) {\n    size += buf.length\n    buffers.push(buf)\n  }\n\n  return uint8ArrayConcat(buffers, size)\n}\n\n/**\n * @typedef {object} MultipartUpload\n * @property {'file' | 'directory' | 'symlink'} type\n * @property {string} name\n * @property {AsyncIterable<Uint8Array>} body\n * @property {number} [mode]\n * @property {import('ipfs-unixfs').Mtime} [mtime]\n *\n * @param {AsyncIterable<Part>} stream\n * @returns {AsyncGenerator<MultipartUpload, void, undefined>}\n */\nasync function * parseEntry (stream) {\n  for await (const part of stream) {\n    if (!part.headers['content-type']) {\n      throw new Error('No content-type in multipart part')\n    }\n\n    const type = Content.type(part.headers['content-type'])\n\n    if (!part.headers['content-disposition']) {\n      throw new Error('No content disposition in multipart part')\n    }\n\n    /** @type {MultipartUpload} */\n    const entry = {}\n\n    if (isDirectory(type.mime)) {\n      entry.type = 'directory'\n    } else if (type.mime === applicationSymlink) {\n      entry.type = 'symlink'\n    } else {\n      entry.type = 'file'\n    }\n\n    const disposition = parseDisposition(part.headers['content-disposition'])\n    const query = qs.parse(`${disposition.name}`.split('?').pop() || '')\n\n    if (query.mode) {\n      entry.mode = parseInt(readQueryParam(query.mode), 8)\n    }\n\n    if (query.mtime) {\n      entry.mtime = {\n        secs: parseInt(readQueryParam(query.mtime), 10)\n      }\n\n      if (query['mtime-nsecs']) {\n        entry.mtime.nsecs = parseInt(readQueryParam(query['mtime-nsecs']), 10)\n      }\n    }\n\n    entry.name = decodeURIComponent(disposition.filename)\n    entry.body = part.body\n\n    yield entry\n  }\n}\n\n/**\n * @param {string|string[]} value\n * @returns {string}\n */\nconst readQueryParam = value => Array.isArray(value) ? value[0] : value\n\n/**\n * @param {IncomingMessage} stream\n * @returns {AsyncGenerator<MultipartEntry, void, undefined>}\n */\nexport async function * multipartRequestParser (stream) {\n  for await (const entry of parseEntry(multipart(stream))) {\n    if (entry.type === 'directory') {\n      /** @type {import('../types').MultipartDirectory} */\n      yield {\n        type: 'directory',\n        name: entry.name,\n        mtime: entry.mtime,\n        mode: entry.mode\n      }\n\n      await drain(entry.body)\n    }\n\n    if (entry.type === 'symlink') {\n      /** @type {import('../types').MultipartSymlink} */\n      yield {\n        type: 'symlink',\n        name: entry.name,\n        target: uint8ArrayToString(await collect(entry.body)),\n        mtime: entry.mtime,\n        mode: entry.mode\n      }\n    }\n\n    if (entry.type === 'file') {\n      /** @type {import('../types').MultipartFile} */\n      yield {\n        type: 'file',\n        name: entry.name,\n        content: entry.body,\n        mtime: entry.mtime,\n        mode: entry.mode\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/utils/stream-response.js",
    "content": "import { PassThrough } from 'stream'\nimport { pipe } from 'it-pipe'\nimport { logger } from '@libp2p/logger'\n// @ts-expect-error no types\nimport toIterable from 'stream-to-it'\n\nconst log = logger('ipfs:http-api:utils:stream-response')\nconst ERROR_TRAILER = 'X-Stream-Error'\n\n/**\n *\n * @param {import('../types').Request} request\n * @param {import('@hapi/hapi').ResponseToolkit} h\n * @param {() => AsyncIterable<any>} getSource\n * @param {{ onError?: (error: Error) => void, onEnd?: () => void }} [options]\n */\nexport async function streamResponse (request, h, getSource, options = {}) {\n  // eslint-disable-next-line no-async-promise-executor\n  const stream = await new Promise(async (resolve, reject) => {\n    let started = false\n    const stream = new PassThrough()\n\n    try {\n      await pipe(\n        (async function * () {\n          try {\n            for await (const chunk of getSource()) {\n              if (!started) {\n                started = true\n                resolve(stream)\n              }\n\n              if (chunk instanceof Uint8Array || typeof chunk === 'string') {\n                yield chunk\n              } else {\n                yield JSON.stringify(chunk) + '\\n'\n              }\n            }\n\n            if (options.onEnd) {\n              options.onEnd()\n            }\n          } catch (/** @type {any} */ err) {\n            log(err)\n\n            if (options.onError) {\n              options.onError(err)\n            }\n\n            if (request.raw.res.headersSent) {\n              request.raw.res.addTrailers({\n                [ERROR_TRAILER]: JSON.stringify({\n                  Message: err.message,\n                  Code: 0\n                })\n              })\n            }\n\n            reject(err)\n          } finally {\n            if (!started) { // Maybe it was an empty source?\n              started = true\n              resolve(stream)\n            }\n\n            // close the stream as we may have aborted execution during a yield\n            stream.end()\n          }\n        })(),\n        toIterable.sink(stream)\n      )\n    } catch (/** @type {any} */ err) {\n      reject(err)\n    }\n  })\n\n  return h.response(stream)\n    .header('X-Chunked-Output', '1')\n    .header('Content-Type', 'application/json')\n    .header('Trailer', ERROR_TRAILER)\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/src/version.js",
    "content": "\nexport const ipfsHttpClient = ''\n"
  },
  {
    "path": "packages/ipfs-http-server/test/cors.js",
    "content": "/* eslint max-nested-callbacks: [\"error\", 8] */\n/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { http } from './utils/http.js'\nimport sinon from 'sinon'\n\ndescribe('cors', () => {\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      id: sinon.stub().returns({\n        id: 'id',\n        publicKey: 'publicKey',\n        addresses: 'addresses',\n        agentVersion: 'agentVersion',\n        protocolVersion: 'protocolVersion'\n      })\n    }\n  })\n\n  describe('should allow configuring CORS', () => {\n    it('returns allowed origins when origin is supplied in request', async () => {\n      const origin = 'http://localhost:8080'\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/id',\n        headers: {\n          origin\n        }\n      }, { ipfs, cors: { origin: [origin] } })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('headers.access-control-allow-origin', origin)\n    })\n\n    it('allows request when referer is supplied in request', async () => {\n      const origin = 'http://localhost:8080'\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/id',\n        headers: {\n          referer: origin + '/index.html'\n        }\n      }, { ipfs, cors: { origin: [origin] } })\n\n      expect(res).to.have.property('statusCode', 200)\n    })\n\n    it('does not allow credentials when omitted in config', async () => {\n      const origin = 'http://localhost:8080'\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/id',\n        headers: {\n          origin\n        }\n      }, { ipfs, cors: { origin: [origin] } })\n\n      expect(res).to.not.have.nested.property('headers.access-control-allow-credentials')\n    })\n\n    it('returns allowed credentials when origin is supplied in request', async () => {\n      const origin = 'http://localhost:8080'\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/id',\n        headers: {\n          origin\n        }\n      }, { ipfs, cors: { origin: [origin], credentials: true } })\n\n      expect(res).to.have.nested.property('headers.access-control-allow-credentials', 'true')\n    })\n\n    it('does not return allowed origins when origin is not supplied in request', async () => {\n      const origin = 'http://localhost:8080'\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/id'\n      }, { ipfs, cors: { origin: [origin] } })\n\n      expect(res).to.not.have.nested.property('headers.access-control-allow-origin')\n    })\n\n    it('does not return allowed credentials when origin is not supplied in request', async () => {\n      const origin = 'http://localhost:8080'\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/id'\n      }, { ipfs, cors: { origin: [origin], credentials: true } })\n\n      expect(res).to.not.have.nested.property('headers.access-control-allow-credentials')\n    })\n\n    it('does not return allowed origins when different origin is supplied in request', async () => {\n      const origin = 'http://localhost:8080'\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/id',\n        headers: {\n          origin: origin + '/'\n        }\n      }, { ipfs, cors: { origin: [origin] } })\n\n      expect(res).to.not.have.nested.property('headers.access-control-allow-origin')\n    })\n\n    it('allows wildcard origins', async () => {\n      const origin = 'http://localhost:8080'\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/id',\n        headers: {\n          origin: origin + '/'\n        }\n      }, { ipfs, cors: { origin: ['*'] } })\n\n      expect(res).to.have.nested.property('headers.access-control-allow-origin', origin + '/')\n    })\n\n    it('makes preflight request for post', async () => {\n      const origin = 'http://localhost:8080'\n      const res = await http({\n        method: 'OPTIONS',\n        url: '/api/v0/id',\n        headers: {\n          origin,\n          'Access-Control-Request-Method': 'POST',\n          // browsers specifying custom headers triggers CORS pre-flight requests\n          // so simulate that here\n          'Access-Control-Request-Headers': 'X-Stream-Output'\n        }\n      }, {\n        ipfs,\n        cors: { origin: [origin] }\n      })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('headers.access-control-allow-origin', origin)\n      expect(res).to.have.nested.property('headers.access-control-allow-methods').that.includes('POST')\n      expect(res).to.have.nested.property('headers.access-control-allow-headers').that.includes('X-Stream-Output')\n    })\n\n    it('responds with 404 for preflight request for get', async () => {\n      const origin = 'http://localhost:8080'\n      const res = await http({\n        method: 'OPTIONS',\n        url: '/api/v0/id',\n        headers: {\n          origin,\n          'Access-Control-Request-Method': 'GET',\n          // browsers specifying custom headers triggers CORS pre-flight requests\n          // so simulate that here\n          'Access-Control-Request-Headers': 'X-Stream-Output'\n        }\n      }, {\n        ipfs,\n        cors: { origin: [origin] }\n      })\n\n      expect(res).to.have.property('statusCode', 404)\n    })\n\n    it('rejects requests when cors origin list is empty and origin is sent', async () => {\n      const origin = 'http://localhost:8080'\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/id',\n        headers: {\n          origin\n        }\n      }, {\n        ipfs,\n        cors: { origin: [] }\n      })\n\n      expect(res).to.have.property('statusCode', 403)\n    })\n\n    it('rejects requests when cors origin list is empty and referer is sent', async () => {\n      const referer = 'http://localhost:8080/index.html'\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/id',\n        headers: {\n          referer\n        }\n      }, {\n        ipfs,\n        cors: { origin: [] }\n      })\n\n      expect(res).to.have.property('statusCode', 403)\n    })\n\n    it('rejects requests when cors origin list is empty and referer and origin are sent', async () => {\n      const referer = 'http://localhost:8080/index.html'\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/id',\n        headers: {\n          referer,\n          origin: 'http://localhost:8080'\n        }\n      }, {\n        ipfs,\n        cors: { origin: [] }\n      })\n\n      expect(res).to.have.property('statusCode', 403)\n    })\n\n    it('rejects requests when cors origin list is empty and origin is sent as \"null\" (e.g. data urls and sandboxed iframes)', async () => {\n      const origin = 'null'\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/id',\n        headers: {\n          origin\n        }\n      }, {\n        ipfs,\n        cors: { origin: [] }\n      })\n\n      expect(res).to.have.property('statusCode', 403)\n    })\n\n    it('rejects requests when cors origin list does not contain the correct origin and origin is sent', async () => {\n      const origin = 'http://localhost:8080'\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/id',\n        headers: {\n          origin\n        }\n      }, {\n        ipfs,\n        cors: { origin: ['http://example.com:8080'] }\n      })\n\n      expect(res).to.have.property('statusCode', 403)\n    })\n\n    it('rejects requests when cors origin list does not contain the correct origin and referer is sent', async () => {\n      const referer = 'http://localhost:8080/index.html'\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/id',\n        headers: {\n          referer\n        }\n      }, {\n        ipfs,\n        cors: { origin: ['http://example.com:8080'] }\n      })\n\n      expect(res).to.have.property('statusCode', 403)\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/fixtures/test-data/badconfig",
    "content": "{\n  bad config\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/test/fixtures/test-data/badnode.json",
    "content": "{\n  bad config\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/test/fixtures/test-data/node.json",
    "content": "{ \"Data\": \"another\", \"Links\": [ { \"Name\": \"some link\", \"Hash\": \"QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V\", \"Size\": 8 } ] }\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/bitswap.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { CID } from 'multiformats/cid'\nimport sinon from 'sinon'\nimport { testHttpMethod } from '../utils/test-http-method.js'\nimport { http } from '../utils/http.js'\nimport { base58btc } from 'multiformats/bases/base58'\nimport { base64 } from 'multiformats/bases/base64'\nimport { peerIdFromString } from '@libp2p/peer-id'\n\ndescribe('/bitswap', () => {\n  const cid = CID.parse('QmUBdnXXPyoDFXj3Hj39dNJ5VkN3QFRskXxcGaYFBB8CNR')\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      bitswap: {\n        wantlist: sinon.stub(),\n        wantlistForPeer: sinon.stub(),\n        stat: sinon.stub(),\n        unwant: sinon.stub()\n      },\n      bases: {\n        getBase: sinon.stub()\n      }\n    }\n  })\n\n  describe('/wantlist', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/bitswap/wantlist')\n    })\n\n    it('/wantlist', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.bitswap.wantlist.withArgs(defaultOptions).returns([\n        cid\n      ])\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/bitswap/wantlist'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Keys').that.deep.includes({ '/': cid.toString() })\n    })\n\n    it('/wantlist?timeout=1s', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.bitswap.wantlist.withArgs({\n        ...defaultOptions,\n        timeout: 1000\n      }).returns([\n        cid\n      ])\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/bitswap/wantlist?timeout=1s'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Keys').that.deep.includes({ '/': cid.toString() })\n    })\n\n    // TODO: unskip after switch to v1 CIDs by default\n    it.skip('/wantlist?cid-base=base64', async () => {\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n      ipfs.bitswap.wantlist.withArgs(defaultOptions).returns([\n        cid.toV1()\n      ])\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/bitswap/wantlist?cid-base=base64'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Keys').that.deep.includes({ '/': cid.toV1().toString(base64) })\n    })\n\n    it('/wantlist?peer=QmSnuWmxptJZdLJpKRarxBMS2Ju2oANVrgbr2xWbie9b2D', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      const peerId = peerIdFromString('QmSnuWmxptJZdLJpKRarxBMS2Ju2oANVrgbr2xWbie9b2D')\n\n      ipfs.bitswap.wantlistForPeer.withArgs(peerId, defaultOptions).returns([\n        cid\n      ])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/bitswap/wantlist?peer=${peerId.toString()}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Keys').that.deep.includes({ '/': cid.toString() })\n    })\n\n    it('/wantlist?peer=QmSnuWmxptJZdLJpKRarxBMS2Ju2oANVrgbr2xWbie9b2D&timeout=1s', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      const peerId = peerIdFromString('QmSnuWmxptJZdLJpKRarxBMS2Ju2oANVrgbr2xWbie9b2D')\n\n      ipfs.bitswap.wantlistForPeer.withArgs(peerId, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns([\n        cid\n      ])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/bitswap/wantlist?peer=${peerId.toString()}&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Keys').that.deep.includes({ '/': cid.toString() })\n    })\n  })\n\n  describe('/stat', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/bitswap/stat')\n    })\n\n    it('/stat', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.bitswap.stat.withArgs(defaultOptions).returns({\n        provideBufLen: 'provideBufLen',\n        blocksReceived: 'blocksReceived',\n        wantlist: [\n          cid\n        ],\n        peers: 'peers',\n        dupBlksReceived: 'dupBlksReceived',\n        dupDataReceived: 'dupDataReceived',\n        dataReceived: 'dataReceived',\n        blocksSent: 'blocksSent',\n        dataSent: 'dataSent'\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/bitswap/stat'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.ProvideBufLen', 'provideBufLen')\n      expect(res).to.have.nested.property('result.BlocksReceived', 'blocksReceived')\n      expect(res).to.have.nested.property('result.Wantlist').that.deep.includes({ '/': cid.toString() })\n      expect(res).to.have.nested.property('result.Peers', 'peers')\n      expect(res).to.have.nested.property('result.DupBlksReceived', 'dupBlksReceived')\n      expect(res).to.have.nested.property('result.DupDataReceived', 'dupDataReceived')\n      expect(res).to.have.nested.property('result.DataReceived', 'dataReceived')\n      expect(res).to.have.nested.property('result.BlocksSent', 'blocksSent')\n      expect(res).to.have.nested.property('result.DataSent', 'dataSent')\n    })\n\n    it('/stat?timeout=1s', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.bitswap.stat.withArgs(defaultOptions).withArgs({\n        signal: sinon.match.any,\n        timeout: 1000\n      }).returns({\n        provideBufLen: 'provideBufLen',\n        blocksReceived: 'blocksReceived',\n        wantlist: [\n          cid\n        ],\n        peers: 'peers',\n        dupBlksReceived: 'dupBlksReceived',\n        dupDataReceived: 'dupDataReceived',\n        dataReceived: 'dataReceived',\n        blocksSent: 'blocksSent',\n        dataSent: 'dataSent'\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/bitswap/stat?timeout=1s'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n    })\n\n    it('/stat?cid-base=base64', async () => {\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n      ipfs.bitswap.stat.withArgs(defaultOptions).returns({\n        provideBufLen: 'provideBufLen',\n        blocksReceived: 'blocksReceived',\n        wantlist: [\n          cid.toV1()\n        ],\n        peers: 'peers',\n        dupBlksReceived: 'dupBlksReceived',\n        dupDataReceived: 'dupDataReceived',\n        dataReceived: 'dataReceived',\n        blocksSent: 'blocksSent',\n        dataSent: 'dataSent'\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/bitswap/stat?cid-base=base64'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Wantlist').that.deep.includes({ '/': cid.toV1().toString(base64) })\n    })\n\n    it.skip('/stat?cid-base=invalid', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/bitswap/stat?cid-base=invalid'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Message').that.includes('Invalid request query input')\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.bitswap.stat.withArgs(defaultOptions).withArgs(sinon.match({\n        timeout: 1000\n      })).returns({\n        provideBufLen: 'provideBufLen',\n        blocksReceived: 'blocksReceived',\n        wantlist: [\n          cid.toV1()\n        ],\n        peers: 'peers',\n        dupBlksReceived: 'dupBlksReceived',\n        dupDataReceived: 'dupDataReceived',\n        dataReceived: 'dataReceived',\n        blocksSent: 'blocksSent',\n        dataSent: 'dataSent'\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/bitswap/stat?timeout=1s'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n    })\n  })\n\n  describe('/unwant', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/bitswap/unwant')\n    })\n\n    it('/unwant', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/bitswap/unwant?arg=${cid}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(ipfs.bitswap.unwant.calledWith(cid, defaultOptions)).to.be.true()\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/bitswap/unwant?arg=${cid}&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(ipfs.bitswap.unwant.calledWith(cid, {\n        ...defaultOptions,\n        timeout: 1000\n      })).to.be.true()\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/block.js",
    "content": "/* eslint max-nested-callbacks: [\"error\", 8] */\n/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport FormData from 'form-data'\nimport streamToPromise from 'stream-to-promise'\nimport { testHttpMethod } from '../utils/test-http-method.js'\nimport { http } from '../utils/http.js'\nimport sinon from 'sinon'\nimport { CID } from 'multiformats/cid'\nimport { base58btc } from 'multiformats/bases/base58'\nimport { base64 } from 'multiformats/bases/base64'\nimport { base32 } from 'multiformats/bases/base32'\n\nconst sendData = async (data) => {\n  const form = new FormData()\n  form.append('data', data)\n  const headers = form.getHeaders()\n  const payload = await streamToPromise(form)\n\n  return {\n    headers,\n    payload\n  }\n}\n\ndescribe('/block', () => {\n  const cid = CID.parse('QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kp')\n  const data = Buffer.from('hello world\\n')\n  const expectedResult = {\n    Key: cid.toString(),\n    Size: 12\n  }\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      block: {\n        put: sinon.stub(),\n        get: sinon.stub(),\n        stat: sinon.stub(),\n        rm: sinon.stub()\n      },\n      bases: {\n        getBase: sinon.stub()\n      }\n    }\n  })\n\n  describe('/put', () => {\n    const defaultOptions = {\n      mhtype: 'sha2-256',\n      format: 'dag-pb',\n      version: 0,\n      pin: false,\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/block/put')\n    })\n\n    it('returns 400 if no node is provided', async () => {\n      const form = new FormData()\n      const headers = form.getHeaders()\n      const payload = await streamToPromise(form)\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/block/put',\n        headers,\n        payload\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n    })\n\n    it('updates value', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.block.put.withArgs(data, defaultOptions).returns(cid)\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/block/put',\n        ...await sendData(data)\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.property('result', expectedResult)\n    })\n\n    it('converts a v0 format to dag-pb', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.block.put.withArgs(data, defaultOptions).returns(cid)\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/block/put?format=v0',\n        ...await sendData(data)\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.property('result', expectedResult)\n    })\n\n    it('updates value and pins block', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.block.put.withArgs(data, {\n        ...defaultOptions,\n        pin: true\n      }).returns(cid)\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/block/put?pin=true',\n        ...await sendData(data)\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.property('result', expectedResult)\n    })\n\n    it('defaults to base32 encoding with a v1 CID', async () => {\n      ipfs.bases.getBase.withArgs('base32').returns(base32)\n      ipfs.block.put.withArgs(data, {\n        ...defaultOptions,\n        version: 1\n      }).returns(cid.toV1())\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/block/put?version=1',\n        ...await sendData(data)\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res.result.Key).to.equal(cid.toV1().toString())\n    })\n\n    it('should put a value and return a base64 encoded CID', async () => {\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n      ipfs.block.put.withArgs(data, {\n        ...defaultOptions,\n        version: 1\n      }).returns(cid.toV1())\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/block/put?version=1&cid-base=base64',\n        ...await sendData(data)\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res.result.Key).to.equal(cid.toV1().toString(base64))\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.block.put.withArgs(data, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns(cid)\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/block/put?timeout=1s',\n        ...await sendData(data)\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n    })\n  })\n\n  describe('/get', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/block/get')\n    })\n\n    it('returns 400 for request without argument', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/block/get'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Message').that.is.a('string')\n    })\n\n    it('returns 400 for request with invalid argument', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/block/get?arg=invalid'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Code', 1)\n      expect(res).to.have.nested.property('result.Message').that.is.a('string')\n    })\n\n    it('returns value', async () => {\n      ipfs.block.get.withArgs(cid, defaultOptions).returns(data)\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/block/get?arg=${cid}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result', 'hello world\\n')\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.block.get.withArgs(cid, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns(data)\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/block/get?arg=${cid}&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n    })\n  })\n\n  describe('/stat', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/block/stat')\n    })\n\n    it('returns 400 for request without argument', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/block/stat'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Message').that.is.a('string')\n    })\n\n    it('returns 400 for request with invalid argument', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/block/stat?arg=invalid'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Code', 1)\n      expect(res).to.have.nested.property('result.Message').that.is.a('string')\n    })\n\n    it('returns value', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.block.stat.withArgs(cid, defaultOptions).returns({\n        cid,\n        size: data.byteLength\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/block/stat?arg=${cid}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res.result.Key)\n        .to.equal(cid.toString())\n      expect(res.result.Size).to.equal(12)\n    })\n\n    it('should stat a block and return a base64 encoded CID', async () => {\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n      ipfs.block.stat.withArgs(cid, defaultOptions).returns({\n        cid: cid.toV1(),\n        size: data.byteLength\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/block/stat?arg=${cid}&cid-base=base64`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res.result.Key).to.equal(cid.toV1().toString(base64))\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.block.stat.withArgs(cid, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns({\n        cid,\n        size: data.byteLength\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/block/stat?arg=${cid}&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n    })\n  })\n\n  describe('/rm', () => {\n    const defaultOptions = {\n      force: false,\n      quiet: false,\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/block/rm')\n    })\n\n    it('returns 400 for request without argument', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/block/rm'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Message').that.is.a('string')\n    })\n\n    it('returns 400 for request with invalid argument', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/block/rm?arg=invalid'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Code', 1)\n      expect(res).to.have.nested.property('result.Message').that.is.a('string')\n    })\n\n    it('returns 200', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.block.rm.withArgs([cid], defaultOptions).returns([{ cid }])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/block/rm?arg=${cid}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n    })\n\n    it('returns 200 when forcing removal', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.block.rm.withArgs([cid], {\n        ...defaultOptions,\n        force: true\n      }).returns([{ cid }])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/block/rm?arg=${cid}&force=true`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n    })\n\n    it('returns 200 when removing quietly', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.block.rm.withArgs([cid], {\n        ...defaultOptions,\n        quiet: true\n      }).returns([{ cid }])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/block/rm?arg=${cid}&quiet=true`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n    })\n\n    it('returns 200 for multiple CIDs', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      const cid2 = CID.parse('QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Ka')\n\n      ipfs.block.rm.withArgs([cid, cid2], defaultOptions).returns([{ cid, cid2 }])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/block/rm?arg=${cid}&arg=${cid2}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.block.rm.withArgs([cid], {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns([{ cid }])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/block/rm?arg=${cid}&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/bootstrap.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport qs from 'qs'\nimport { testHttpMethod } from '../utils/test-http-method.js'\nimport { http } from '../utils/http.js'\nimport sinon from 'sinon'\n\nconst defaultList = [\n  'server0',\n  'server1',\n  'server2',\n  'server3'\n]\n\ndescribe('/bootstrap', () => {\n  const validIp4 = '/ip4/101.236.176.52/tcp/4001/p2p/QmSoLnSGccFuZQJzRadHn95W2CrSFmZuTdDWP8HXaHca9z'\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      bootstrap: {\n        list: sinon.stub(),\n        add: sinon.stub(),\n        rm: sinon.stub(),\n        clear: sinon.stub(),\n        reset: sinon.stub()\n      }\n    }\n  })\n\n  describe('/list', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/bootstrap/list')\n    })\n\n    it('returns a list', async () => {\n      ipfs.bootstrap.list.withArgs(defaultOptions).returns({\n        Peers: defaultList\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/bootstrap/list'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Peers', defaultList)\n    })\n\n    it('alias', async () => {\n      ipfs.bootstrap.list.withArgs(defaultOptions).returns({\n        Peers: defaultList\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/bootstrap'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Peers', defaultList)\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.bootstrap.list.withArgs({\n        ...defaultOptions,\n        timeout: 1000\n      }).returns({\n        Peers: defaultList\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/bootstrap/list?timeout=1s'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Peers', defaultList)\n    })\n  })\n\n  describe('/add', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      const query = {\n        arg: validIp4\n      }\n\n      return testHttpMethod(`/api/v0/bootstrap/add?${qs.stringify(query)}`)\n    })\n\n    it('adds a bootstrapper', async () => {\n      ipfs.bootstrap.add.withArgs(validIp4, defaultOptions).returns({\n        Peers: [\n          validIp4\n        ]\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/bootstrap/add?arg=${validIp4}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Peers', [validIp4])\n    })\n\n    it('adds a bootstrapper with a timeout', async () => {\n      ipfs.bootstrap.add.withArgs(validIp4, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns({\n        Peers: [\n          validIp4\n        ]\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/bootstrap/add?arg=${validIp4}&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Peers', [validIp4])\n    })\n\n    it('restores default', async () => {\n      ipfs.bootstrap.reset.withArgs(defaultOptions).returns({\n        Peers: defaultList\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/bootstrap/add?default=true'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Peers', defaultList)\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.bootstrap.reset.withArgs({\n        ...defaultOptions,\n        timeout: 1000\n      }).returns({\n        Peers: defaultList\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/bootstrap/add?default=true&timeout=1s'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Peers', defaultList)\n    })\n\n    describe('/default', () => {\n      const defaultOptions = {\n        signal: sinon.match.instanceOf(AbortSignal),\n        timeout: undefined\n      }\n\n      it('only accepts POST', () => {\n        return testHttpMethod('/api/v0/bootstrap/add/default')\n      })\n\n      it('restores default', async () => {\n        ipfs.bootstrap.reset.withArgs(defaultOptions).returns({\n          Peers: defaultList\n        })\n\n        const res = await http({\n          method: 'POST',\n          url: '/api/v0/bootstrap/add/default'\n        }, { ipfs })\n\n        expect(res).to.have.property('statusCode', 200)\n        expect(res).to.have.deep.nested.property('result.Peers', defaultList)\n      })\n\n      it('accepts a timeout', async () => {\n        ipfs.bootstrap.reset.withArgs({\n          ...defaultOptions,\n          timeout: 1000\n        }).returns({\n          Peers: defaultList\n        })\n\n        const res = await http({\n          method: 'POST',\n          url: '/api/v0/bootstrap/add/default?timeout=1s'\n        }, { ipfs })\n\n        expect(res).to.have.property('statusCode', 200)\n        expect(res).to.have.deep.nested.property('result.Peers', defaultList)\n      })\n    })\n  })\n\n  describe('/rm', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      const query = {\n        arg: validIp4\n      }\n\n      return testHttpMethod(`/api/v0/bootstrap/rm?${qs.stringify(query)}`)\n    })\n\n    it('removes a bootstrapper', async () => {\n      ipfs.bootstrap.rm.withArgs(validIp4, defaultOptions).returns({\n        Peers: [\n          validIp4\n        ]\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/bootstrap/rm?arg=${validIp4}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Peers', [validIp4])\n    })\n\n    it('removes a bootstrapper with a timeout', async () => {\n      ipfs.bootstrap.rm.withArgs(validIp4, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns({\n        Peers: [\n          validIp4\n        ]\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/bootstrap/rm?arg=${validIp4}&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Peers', [validIp4])\n    })\n\n    it('removes all bootstrappers', async () => {\n      ipfs.bootstrap.clear.withArgs(defaultOptions).returns({\n        Peers: defaultList\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/bootstrap/rm?all=true'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Peers', defaultList)\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.bootstrap.clear.withArgs({\n        ...defaultOptions,\n        timeout: 1000\n      }).returns({\n        Peers: defaultList\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/bootstrap/rm?all=true&timeout=1s'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Peers', defaultList)\n    })\n\n    describe('/all', () => {\n      const defaultOptions = {\n        signal: sinon.match.instanceOf(AbortSignal),\n        timeout: undefined\n      }\n\n      it('only accepts POST', () => {\n        return testHttpMethod('/api/v0/bootstrap/rm/all')\n      })\n\n      it('removes all bootstrappers', async () => {\n        ipfs.bootstrap.clear.withArgs(defaultOptions).returns({\n          Peers: defaultList\n        })\n\n        const res = await http({\n          method: 'POST',\n          url: '/api/v0/bootstrap/rm/all'\n        }, { ipfs })\n\n        expect(res).to.have.property('statusCode', 200)\n        expect(res).to.have.deep.nested.property('result.Peers', defaultList)\n      })\n\n      it('accepts a timeout', async () => {\n        ipfs.bootstrap.clear.withArgs({\n          ...defaultOptions,\n          timeout: 1000\n        }).returns({\n          Peers: defaultList\n        })\n\n        const res = await http({\n          method: 'POST',\n          url: '/api/v0/bootstrap/rm/all?timeout=1s'\n        }, { ipfs })\n\n        expect(res).to.have.property('statusCode', 200)\n        expect(res).to.have.deep.nested.property('result.Peers', defaultList)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/browser-headers.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport sinon from 'sinon'\nimport { http } from '../utils/http.js'\n\nexport default () => {\n  describe('browser headers', () => {\n    let ipfs\n\n    before(() => {\n      ipfs = {\n        id: sinon.stub().returns({})\n      }\n    })\n\n    it('should block Mozilla* browsers that do not provide origins', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/id',\n        headers: {\n          'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20100101 Firefox/10.0'\n        }\n      }, { ipfs })\n\n      expect(res.statusCode).to.equal(403)\n    })\n\n    it('should not block on GETs', async () => {\n      const res = await http({\n        method: 'GET',\n        url: '/api/v0/id',\n        headers: {\n          'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20100101 Firefox/10.0'\n        }\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 405)\n    })\n\n    it('should not block a Mozilla* browser that provides an allowed Origin', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/id',\n        headers: {\n          'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20100101 Firefox/10.0',\n          Origin: 'null'\n        }\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/config.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport fs from 'fs'\nimport FormData from 'form-data'\nimport streamToPromise from 'stream-to-promise'\nimport { testHttpMethod } from '../utils/test-http-method.js'\nimport { http } from '../utils/http.js'\nimport sinon from 'sinon'\n\nconst profiles = {\n  profile1: {\n    description: 'this is profile 1'\n  },\n  profile2: {\n    description: 'this is profile 2'\n  }\n}\n\nconst defaultOptions = {\n  signal: sinon.match.instanceOf(AbortSignal),\n  timeout: undefined\n}\n\ndescribe('/config', () => {\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      config: {\n        getAll: sinon.stub(),\n        get: sinon.stub(),\n        replace: sinon.stub(),\n        profiles: {\n          apply: sinon.stub(),\n          list: sinon.stub()\n        }\n      }\n    }\n  })\n\n  it('only accepts POST', () => {\n    return testHttpMethod('/api/v0/config')\n  })\n\n  it('returns 400 for request without arguments', async () => {\n    const res = await http({\n      method: 'POST',\n      url: '/api/v0/config'\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 400)\n  })\n\n  it('404 for request with missing args', async () => {\n    const res = await http({\n      method: 'POST',\n      url: '/api/v0/config?arg=kitten'\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 404)\n    expect(res).to.have.nested.property('result.Code', 3)\n    expect(res).to.have.nested.property('result.Message').that.is.a('string')\n  })\n\n  it('returns value for request with valid arg', async () => {\n    ipfs.config.getAll.withArgs(defaultOptions).returns({\n      API: {\n        HTTPHeaders: 'value'\n      }\n    })\n\n    const res = await http({\n      method: 'POST',\n      url: '/api/v0/config?arg=API.HTTPHeaders'\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n    expect(res).to.have.nested.property('result.Key', 'API.HTTPHeaders')\n    expect(res).to.have.nested.property('result.Value', 'value')\n  })\n\n  it('returns value for request as subcommand', async () => {\n    ipfs.config.getAll.withArgs(defaultOptions).returns({\n      API: {\n        HTTPHeaders: 'value'\n      }\n    })\n\n    const res = await http({\n      method: 'POST',\n      url: '/api/v0/config/API.HTTPHeaders'\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n    expect(res).to.have.nested.property('result.Key', 'API.HTTPHeaders')\n    expect(res).to.have.nested.property('result.Value', 'value')\n  })\n\n  it('updates value for request with both args', async () => {\n    ipfs.config.getAll.withArgs(defaultOptions).returns({\n      Datastore: {\n        Path: 'not-kitten'\n      }\n    })\n\n    const res = await http({\n      method: 'POST',\n      url: '/api/v0/config?arg=Datastore.Path&arg=kitten'\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n    expect(res).to.have.nested.property('result.Key', 'Datastore.Path')\n    expect(res).to.have.nested.property('result.Value', 'kitten')\n    expect(ipfs.config.replace.calledWith({\n      Datastore: {\n        Path: 'kitten'\n      }\n    })).to.be.true()\n  })\n\n  it('returns 400 value for request with both args and JSON flag with invalid JSON argument', async () => {\n    ipfs.config.getAll.withArgs(defaultOptions).returns({\n      Datastore: {\n        Path: 'not-kitten'\n      }\n    })\n\n    const res = await http({\n      method: 'POST',\n      url: '/api/v0/config?arg=Datastore.Path&arg=kitten&json'\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 400)\n    expect(res).to.have.nested.property('result.Code', 1)\n    expect(res).to.have.nested.property('result.Message').that.is.a('string')\n    expect(ipfs.config.replace.called).to.be.false()\n  })\n\n  it('updates value for request with both args and JSON flag with valid JSON argument', async () => {\n    ipfs.config.getAll.withArgs(defaultOptions).returns({\n      Datastore: {\n        Path: 'not-kitten'\n      }\n    })\n\n    const res = await http({\n      method: 'POST',\n      url: '/api/v0/config?arg=Datastore.Path&arg={\"kitten\": true}&json'\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n    expect(res).to.have.nested.property('result.Key', 'Datastore.Path')\n    expect(res).to.have.deep.nested.property('result.Value', { kitten: true })\n    expect(ipfs.config.replace.calledWith({\n      Datastore: {\n        Path: {\n          kitten: true\n        }\n      }\n    })).to.be.true()\n  })\n\n  it('updates null json value', async () => {\n    ipfs.config.getAll.withArgs(defaultOptions).returns({\n      Datastore: {\n        Path: 'not-kitten'\n      }\n    })\n\n    const res = await http({\n      method: 'POST',\n      url: '/api/v0/config?arg=Datastore.Path&arg=null&json'\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n    expect(res).to.have.nested.property('result.Key', 'Datastore.Path')\n    expect(res).to.have.deep.nested.property('result.Value', null)\n    expect(ipfs.config.replace.calledWith({\n      Datastore: {\n        Path: null\n      }\n    })).to.be.true()\n  })\n\n  it('updates value for request with both args and bool flag and true argument', async () => {\n    ipfs.config.getAll.withArgs(defaultOptions).returns({\n      Datastore: {\n        Path: 'not-kitten'\n      }\n    })\n\n    const res = await http({\n      method: 'POST',\n      url: '/api/v0/config?arg=Datastore.Path&arg=true&bool'\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n    expect(res).to.have.nested.property('result.Key', 'Datastore.Path')\n    expect(res).to.have.nested.property('result.Value', true)\n    expect(ipfs.config.replace.calledWith({\n      Datastore: {\n        Path: true\n      }\n    })).to.be.true()\n  })\n\n  it('updates value for request with both args and bool flag and false argument', async () => {\n    ipfs.config.getAll.withArgs(defaultOptions).returns({\n      Datastore: {\n        Path: 'not-kitten'\n      }\n    })\n\n    const res = await http({\n      method: 'POST',\n      url: '/api/v0/config?arg=Datastore.Path&arg=false&bool'\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n    expect(res).to.have.nested.property('result.Key', 'Datastore.Path')\n    expect(res).to.have.nested.property('result.Value', false)\n    expect(ipfs.config.replace.calledWith({\n      Datastore: {\n        Path: false\n      }\n    })).to.be.true()\n  })\n\n  it('accepts a timeout', async () => {\n    const key = 'Datastore.Path'\n    const value = 'value'\n    ipfs.config.getAll.withArgs({\n      ...defaultOptions,\n      timeout: 1000\n    }).returns({\n      Datastore: {\n        Path: 'not-kitten'\n      }\n    })\n\n    const res = await http({\n      method: 'POST',\n      url: `/api/v0/config?arg=${key}&arg=${value}&timeout=1s`\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n    expect(res).to.have.nested.property('result.Key', key)\n    expect(res).to.have.nested.property('result.Value', value)\n    expect(ipfs.config.replace.calledWith({\n      Datastore: {\n        Path: value\n      }\n    }, sinon.match({\n      timeout: 1000\n    }))).to.be.true()\n  })\n\n  describe('/show', () => {\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/config/show')\n    })\n\n    it('shows config', async () => {\n      const config = {\n        Datastore: {\n          Path: 'not-kitten'\n        }\n      }\n\n      ipfs.config.getAll.withArgs(defaultOptions).returns(config)\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/config/show'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.property('result', config)\n    })\n\n    it('accepts a timeout', async () => {\n      const config = {\n        Datastore: {\n          Path: 'not-kitten'\n        }\n      }\n\n      ipfs.config.getAll.withArgs({\n        ...defaultOptions,\n        timeout: 1000\n      }).returns(config)\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/config/show?timeout=1s'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.property('result', config)\n    })\n  })\n\n  describe('/replace', () => {\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/config/replace')\n    })\n\n    it('returns 400 if no config is provided', async () => {\n      const form = new FormData()\n      const headers = form.getHeaders()\n\n      const payload = await streamToPromise(form)\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/config/replace',\n        headers,\n        payload\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n    })\n\n    it('returns 500 if the config is invalid', async () => {\n      const form = new FormData()\n      const filePath = 'test/fixtures/test-data/badconfig'\n      form.append('file', fs.createReadStream(filePath))\n      const headers = form.getHeaders()\n\n      const payload = await streamToPromise(form)\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/config/replace',\n        headers,\n        payload\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 500)\n    })\n\n    it('updates value', async () => {\n      const expectedConfig = {\n        Key: 'value',\n        OtherKey: 'otherValue',\n        Deep: {\n          Key: {\n            Is: 'value'\n          }\n        },\n        Array: [\n          'va1',\n          'val2',\n          'val3'\n        ]\n      }\n      const form = new FormData()\n      form.append('file', Buffer.from(JSON.stringify(expectedConfig)))\n      const headers = form.getHeaders()\n\n      const payload = await streamToPromise(form)\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/config/replace',\n        headers,\n        payload\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(ipfs.config.replace.calledWith(expectedConfig, defaultOptions)).to.be.true()\n    })\n\n    it('accepts a timeout', async () => {\n      const expectedConfig = {\n        Key: 'value',\n        OtherKey: 'otherValue',\n        Deep: {\n          Key: {\n            Is: 'value'\n          }\n        },\n        Array: [\n          'va1',\n          'val2',\n          'val3'\n        ]\n      }\n      const form = new FormData()\n      form.append('file', Buffer.from(JSON.stringify(expectedConfig)))\n      const headers = form.getHeaders()\n\n      const payload = await streamToPromise(form)\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/config/replace?timeout=1s',\n        headers,\n        payload\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(ipfs.config.replace.calledWith(expectedConfig, {\n        ...defaultOptions,\n        timeout: 1000\n      })).to.be.true()\n    })\n  })\n\n  describe('/profile/apply', () => {\n    const defaultOptions = {\n      dryRun: false,\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/config/profile/apply')\n    })\n\n    it('returns 400 if no config profile is provided', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/config/profile/apply'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n    })\n\n    it('does not apply config profile with dry-run argument', async () => {\n      ipfs.config.profiles.apply.withArgs('lowpower', {\n        ...defaultOptions,\n        dryRun: true\n      }).returns({\n        original: {},\n        updated: {}\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/config/profile/apply?arg=lowpower&dry-run=true'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.config.profiles.apply.withArgs('lowpower', {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns({\n        original: {},\n        updated: {}\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/config/profile/apply?arg=lowpower&timeout=1s'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n    })\n\n    Object.keys(profiles).forEach(profile => {\n      it(`applies config profile ${profile}`, async () => {\n        ipfs.config.profiles.apply.withArgs(profile, defaultOptions).returns({\n          original: {},\n          updated: {}\n        })\n\n        const res = await http({\n          method: 'POST',\n          url: `/api/v0/config/profile/apply?arg=${profile}`\n        }, { ipfs })\n\n        expect(res).to.have.property('statusCode', 200)\n      })\n    })\n  })\n\n  describe('/profile/list', () => {\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/config/profile/list')\n    })\n\n    it('lists available profiles', async () => {\n      ipfs.config.profiles.list.withArgs(defaultOptions).returns(Object.keys(profiles).map(name => ({\n        name,\n        description: profiles[name].description\n      })))\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/config/profile/list'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.property('payload').that.satisfies(payload => {\n        const listed = JSON.parse(payload)\n\n        return Object.keys(profiles).reduce((acc, name) => { // eslint-disable-line max-nested-callbacks\n          const profile = listed.find(profile => profile.Name === name) // eslint-disable-line max-nested-callbacks\n\n          return acc && profile && profile.Description === profiles[name].description\n        }, true)\n      })\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.config.profiles.list.withArgs({\n        ...defaultOptions,\n        timeout: 1000\n      }).returns(Object.keys(profiles).map(name => ({\n        name,\n        description: profiles[name].description\n      })))\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/config/profile/list?timeout=1s'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/dag.js",
    "content": "/* eslint max-nested-callbacks: [\"error\", 8] */\n/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { Readable } from 'stream'\nimport FormData from 'form-data'\nimport streamToPromise from 'stream-to-promise'\nimport { CID } from 'multiformats/cid'\nimport { testHttpMethod } from '../utils/test-http-method.js'\nimport { http } from '../utils/http.js'\nimport { matchIterable } from '../utils/match-iterable.js'\nimport sinon from 'sinon'\nimport { base58btc } from 'multiformats/bases/base58'\nimport { base32 } from 'multiformats/bases/base32'\nimport drain from 'it-drain'\n\nconst toHeadersAndPayload = async (thing) => {\n  const stream = new Readable()\n  stream.push(thing)\n  stream.push(null)\n\n  const form = new FormData()\n  form.append('file', stream)\n\n  return {\n    headers: form.getHeaders(),\n    payload: await streamToPromise(form)\n  }\n}\n\ndescribe('/dag', () => {\n  const cid = CID.parse('QmUBdnXXPyoDFXj3Hj39dNJ5VkN3QFRskXxcGaYFBB8CNR')\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      dag: {\n        get: sinon.stub(),\n        put: sinon.stub(),\n        resolve: sinon.stub(),\n        import: sinon.stub(),\n        export: sinon.stub()\n      },\n      block: {\n        put: sinon.stub()\n      },\n      bases: {\n        getBase: sinon.stub()\n      }\n    }\n  })\n\n  describe('/get', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined,\n      path: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/dag/get')\n    })\n\n    it('returns error for request without argument', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/dag/get'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n    })\n\n    it('returns error for request with invalid argument', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/dag/get?arg=5'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n    })\n\n    it('returns value', async () => {\n      const node = {\n        Data: Uint8Array.from([]),\n        Links: []\n      }\n      ipfs.dag.get.withArgs(cid, defaultOptions).returns({ value: node })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/dag/get?arg=${cid.toString()}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Links').that.is.empty()\n      expect(res).to.have.nested.property('result.Data').that.is.empty()\n    })\n\n    it('uses text encoding for data by default', async () => {\n      const node = {\n        Data: Uint8Array.from([0, 1, 2, 3]),\n        Links: []\n      }\n      ipfs.dag.get.withArgs(cid, defaultOptions).returns({ value: node })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/dag/get?arg=${cid}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res.result).to.be.ok()\n      expect(res).to.have.nested.property('result.Links').that.is.empty()\n      expect(res).to.have.nested.property('result.Data', '\\u0000\\u0001\\u0002\\u0003')\n    })\n\n    it('overrides data encoding', async () => {\n      const node = {\n        Data: Uint8Array.from([0, 1, 2, 3]),\n        Links: []\n      }\n      ipfs.dag.get.withArgs(cid, defaultOptions).returns({ value: node })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/dag/get?arg=${cid.toString()}&data-encoding=base64`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Links').that.is.empty()\n      expect(res).to.have.nested.property('result.Data').that.equals('AAECAw==')\n    })\n\n    it('returns value with a path as part of the cid', async () => {\n      ipfs.dag.get.withArgs(cid, {\n        ...defaultOptions,\n        path: '/foo'\n      }).returns({ value: 'bar' })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/dag/get?arg=${cid.toString()}/foo`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result', 'bar')\n    })\n\n    it('returns value with a path as part of the cid for dag-pb nodes', async () => {\n      const node = {\n        Data: Uint8Array.from([0, 1, 2, 3]),\n        Links: []\n      }\n      ipfs.dag.get.withArgs(cid, {\n        ...defaultOptions,\n        path: '/Data'\n      }).returns({ value: node.Data })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/dag/get?arg=${cid.toString()}/Data&data-encoding=base64`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.property('result', 'AAECAw==')\n    })\n\n    it('encodes buffers in arbitrary positions', async () => {\n      const node = {\n        foo: 'bar',\n        baz: {\n          qux: Uint8Array.from([0, 1, 2, 3])\n        }\n      }\n      ipfs.dag.get.withArgs(cid, defaultOptions).returns({ value: node })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/dag/get?arg=${cid.toString()}&data-encoding=base64`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.baz.qux', 'AAECAw==')\n    })\n\n    it('supports specifying buffer encoding', async () => {\n      const node = {\n        foo: 'bar',\n        baz: Uint8Array.from([0, 1, 2, 3])\n      }\n      ipfs.dag.get.withArgs(cid, defaultOptions).returns({ value: node })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/dag/get?arg=${cid.toString()}&data-encoding=hex`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.baz', '00010203')\n    })\n\n    it('accepts a timeout', async () => {\n      const node = {\n        foo: 'bar'\n      }\n      ipfs.dag.get.withArgs(cid, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns({ value: node })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/dag/get?arg=${cid.toString()}&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.foo', 'bar')\n    })\n  })\n\n  describe('/put', () => {\n    const defaultOptions = {\n      inputCodec: 'dag-json',\n      storeCodec: 'dag-cbor',\n      hashAlg: 'sha2-256',\n      version: 1,\n      pin: false,\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/dag/put')\n    })\n\n    it('returns error for request without file argument', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/dag/put'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Message').that.includes(\"File argument 'object data' is required\")\n    })\n\n    it('adds a dag-cbor node by default', async () => {\n      ipfs.bases.getBase.withArgs('base32').returns(base32)\n      const node = {\n        foo: 'bar'\n      }\n      const encoded = Buffer.from(JSON.stringify(node))\n      ipfs.dag.put.withArgs(encoded, defaultOptions).returns(cid.toV1())\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/dag/put',\n        ...await toHeadersAndPayload(JSON.stringify(node))\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Cid', { '/': cid.toV1().toString() })\n    })\n\n    it('adds a dag-pb node', async () => {\n      ipfs.bases.getBase.withArgs('base32').returns(base32)\n      const node = {\n        data: [],\n        links: []\n      }\n      const encoded = Buffer.from(JSON.stringify(node))\n      ipfs.dag.put.withArgs(encoded, {\n        ...defaultOptions,\n        storeCodec: 'dag-pb'\n      }).returns(cid.toV1())\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/dag/put?storeCodec=dag-pb',\n        ...await toHeadersAndPayload(JSON.stringify(node))\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Cid', { '/': cid.toV1().toString() })\n    })\n\n    it('defaults to base58btc when adding a v0 dag-pb node', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      const node = {\n        data: [],\n        links: []\n      }\n      const encoded = Buffer.from(JSON.stringify(node))\n      ipfs.dag.put.withArgs(encoded, {\n        ...defaultOptions,\n        version: 0,\n        inputCodec: 'dag-json',\n        storeCodec: 'dag-pb'\n      }).returns(cid)\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/dag/put?inputCodec=dag-json&storeCodec=dag-pb&version=0',\n        ...await toHeadersAndPayload(JSON.stringify(node))\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Cid', { '/': cid.toString() })\n    })\n\n    it('adds a raw node', async () => {\n      ipfs.bases.getBase.withArgs('base32').returns(base32)\n      const node = Buffer.from([0, 1, 2, 3])\n      ipfs.dag.put.withArgs(node, {\n        ...defaultOptions,\n        storeCodec: 'raw'\n      }).returns(cid.toV1())\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/dag/put?storeCodec=raw',\n        ...await toHeadersAndPayload(node)\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Cid', { '/': cid.toV1().toString() })\n    })\n\n    it('pins a node after adding', async () => {\n      ipfs.bases.getBase.withArgs('base32').returns(base32)\n      const node = {\n        foo: 'bar'\n      }\n      const encoded = Buffer.from(JSON.stringify(node))\n      ipfs.dag.put.withArgs(encoded, {\n        ...defaultOptions,\n        pin: true\n      }).returns(cid.toV1())\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/dag/put?pin=true',\n        ...await toHeadersAndPayload(JSON.stringify(node))\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Cid', { '/': cid.toV1().toString() })\n    })\n\n    it('adds a node with an esoteric format', async () => {\n      ipfs.bases.getBase.withArgs('base32').returns(base32)\n      const cid = CID.parse('baf4beiata6mq425fzikf5m26temcvg7mizjrxrkn35swuybmpah2ajan5y')\n      const data = Buffer.from('some data')\n      const codec = 'git-raw'\n\n      ipfs.dag.put.withArgs(data, {\n        ...defaultOptions,\n        inputCodec: codec,\n        storeCodec: codec\n      }).returns(cid.toV1())\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/dag/put?storeCodec=${codec}&inputCodec=${codec}`,\n        ...await toHeadersAndPayload(data)\n      }, { ipfs })\n\n      expect(ipfs.dag.put.called).to.be.true()\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Cid', { '/': cid.toV1().toString() })\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.bases.getBase.withArgs('base32').returns(base32)\n      const node = {\n        foo: 'bar'\n      }\n      const encoded = Buffer.from(JSON.stringify(node))\n      ipfs.dag.put.withArgs(encoded, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns(cid.toV1())\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/dag/put?timeout=1s',\n        ...await toHeadersAndPayload(JSON.stringify(node))\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Cid', { '/': cid.toV1().toString() })\n    })\n  })\n\n  describe('/resolve', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined,\n      path: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/dag/resolve')\n    })\n\n    it('returns error for request without argument', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/dag/resolve'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n    })\n\n    it('resolves a node', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.dag.resolve.withArgs(cid, defaultOptions).returns({\n        cid,\n        remainderPath: ''\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/dag/resolve?arg=${cid.toString()}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Cid', { '/': cid.toString() })\n      expect(res).to.have.nested.property('result.RemPath', '')\n    })\n\n    it('resolves a node with a path arg', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.dag.resolve.withArgs(cid, {\n        ...defaultOptions,\n        path: '/foo'\n      }).returns({\n        cid,\n        remainderPath: ''\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/dag/resolve?arg=${cid.toString()}&path=/foo`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Cid', { '/': cid.toString() })\n      expect(res).to.have.nested.property('result.RemPath', '')\n    })\n\n    it('returns the remainder path from within the resolved node', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.dag.resolve.withArgs(cid, {\n        ...defaultOptions,\n        path: '/foo'\n      }).returns({\n        cid,\n        remainderPath: 'foo'\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/dag/resolve?arg=${cid.toString()}/foo`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Cid', { '/': cid.toString() })\n      expect(res).to.have.nested.property('result.RemPath', 'foo')\n    })\n\n    it('returns an error when the path is not available', async () => {\n      ipfs.dag.resolve.withArgs(cid, {\n        ...defaultOptions,\n        path: '/bar'\n      }).throws(new Error('Not found'))\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/dag/resolve?arg=${cid.toString()}/bar`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 500)\n      expect(res.result).to.be.ok()\n    })\n\n    it('resolves across multiple nodes, returning the CID of the last node traversed', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      const cid2 = CID.parse('QmUBdnXXPyoDFXj3Hj39dNJ5VkN3QFRskXxcGaYFBB8CNA')\n\n      ipfs.dag.resolve.withArgs(cid, {\n        ...defaultOptions,\n        path: '/foo/bar'\n      }).returns({\n        cid: cid2,\n        remainderPath: 'bar'\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/dag/resolve?arg=${cid.toString()}/foo/bar`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Cid', { '/': cid2.toString() })\n      expect(res).to.have.nested.property('result.RemPath', 'bar')\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.dag.resolve.withArgs(cid, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns({\n        cid,\n        remainderPath: ''\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/dag/resolve?arg=${cid.toString()}&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Cid', { '/': cid.toString() })\n      expect(res).to.have.nested.property('result.RemPath', '')\n    })\n  })\n\n  describe('/import', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined,\n      pinRoots: true\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/dag/import')\n    })\n\n    it('imports car', async () => {\n      const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n      ipfs.dag.import.withArgs(matchIterable(), {\n        ...defaultOptions\n      })\n        .callsFake(async function * (source) {\n          await drain(source)\n          yield { root: { cid, pinErrorMsg: '' } }\n        })\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/dag/import',\n        ...await toHeadersAndPayload('car content')\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n\n      const response = JSON.parse(res.result)\n      expect(response).to.have.nested.property('Root.Cid./', cid.toString())\n      expect(response).to.have.nested.property('Root.PinErrorMsg').that.is.empty()\n    })\n\n    it('imports car with pin error', async () => {\n      const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n      ipfs.dag.import.withArgs(matchIterable(), {\n        ...defaultOptions\n      })\n        .callsFake(async function * (source) {\n          await drain(source)\n          yield { root: { cid, pinErrorMsg: 'derp' } }\n        })\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/dag/import',\n        ...await toHeadersAndPayload('car content')\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n\n      const response = JSON.parse(res.result)\n      expect(response).to.have.nested.property('Root.Cid./', cid.toString())\n      expect(response).to.have.nested.property('Root.PinErrorMsg').that.equals('derp')\n    })\n\n    it('imports car without pinning', async () => {\n      ipfs.dag.import.withArgs(matchIterable(), {\n        ...defaultOptions,\n        pinRoots: false\n      })\n        .callsFake(async function * (source) { // eslint-disable-line require-yield\n          await drain(source)\n        })\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/dag/import?pin-roots=false',\n        ...await toHeadersAndPayload('car content')\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res.result).to.be.empty()\n    })\n\n    it('imports car with timeout', async () => {\n      const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n      ipfs.dag.import.withArgs(matchIterable(), {\n        ...defaultOptions,\n        timeout: 1000\n      })\n        .callsFake(async function * (source) {\n          await drain(source)\n          yield { root: { cid, pinErrorMsg: '' } }\n        })\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/dag/import?timeout=1s',\n        ...await toHeadersAndPayload('car content')\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n\n      const response = JSON.parse(res.result)\n      expect(response).to.have.nested.property('Root.Cid./', cid.toString())\n      expect(response).to.have.nested.property('Root.PinErrorMsg').that.equals('')\n    })\n  })\n\n  describe('/export', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/dag/export')\n    })\n\n    it('returns error for request without root', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/dag/export'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n    })\n\n    it('exports car', async () => {\n      const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n      ipfs.dag.export.withArgs(cid, {\n        ...defaultOptions\n      })\n        .returns(['some data'])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/dag/export?arg=${cid}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result', 'some data')\n    })\n\n    it('exports car with a timeout', async () => {\n      const cid = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')\n\n      ipfs.dag.export.withArgs(cid, {\n        ...defaultOptions,\n        timeout: 1000\n      })\n        .returns(['some data'])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/dag/export?arg=${cid}&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result', 'some data')\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/dht.js",
    "content": "/* eslint max-nested-callbacks: [\"error\", 8] */\n/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { testHttpMethod } from '../utils/test-http-method.js'\nimport { http } from '../utils/http.js'\nimport sinon from 'sinon'\nimport errCode from 'err-code'\nimport { CID } from 'multiformats/cid'\nimport { allNdjson } from '../utils/all-ndjson.js'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\nimport FormData from 'form-data'\nimport streamToPromise from 'stream-to-promise'\nimport { EventTypes } from '@libp2p/interface-dht'\n\ndescribe('/dht', () => {\n  const peerId = 'QmQ2zigjQikYnyYUSXZydNXrDRhBut2mubwJBaLXobMt3A'\n  const cid = CID.parse('Qmc77hSNykXJ6Jxp1C6RpD8VENV7RK6JD7eAcWpc7nEZx2')\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      id: sinon.stub().resolves({ id: 'Qmfoo' }),\n      dht: {\n        findPeer: sinon.stub(),\n        findProvs: sinon.stub(),\n        get: sinon.stub(),\n        provide: sinon.stub(),\n        put: sinon.stub(),\n        query: sinon.stub()\n      }\n    }\n  })\n\n  describe('/findpeer', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal)\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/dht/findpeer')\n    })\n\n    it('returns 400 if no peerId is provided', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/dht/findpeer'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Code', 1)\n    })\n\n    it('returns 500 if peerId is provided and there are no peers in the routing table', async () => {\n      ipfs.dht.findPeer.withArgs(peerId, defaultOptions).throws(errCode(new Error('Nope'), 'ERR_LOOKUP_FAILED'))\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/dht/findpeer?arg=${peerId.toString()}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 500)\n      expect(ipfs.dht.findPeer.called).to.be.true()\n      expect(ipfs.dht.findPeer.getCall(0).args[0].toString()).to.equal(peerId.toString())\n    })\n  })\n\n  describe('/findprovs', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal)\n    }\n\n    it('only accepts POST', async () => {\n      const res = await http({\n        method: 'GET',\n        url: '/api/v0/dht/findprovs'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 405)\n    })\n\n    it('returns 400 if no key is provided', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/dht/findprovs'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Code', 1)\n    })\n\n    it('returns 200 if key is provided', async () => {\n      ipfs.dht.findProvs.withArgs(cid, defaultOptions).returns([{\n        name: 'PROVIDER',\n        type: EventTypes.PROVIDER,\n        providers: [{\n          id: peerId,\n          multiaddrs: [\n            'addr'\n          ]\n        }]\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/dht/findprovs?arg=${cid}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(allNdjson(res)).to.deep.equal([{\n        Extra: '',\n        Type: EventTypes.PROVIDER,\n        Responses: [{\n          ID: peerId,\n          Addrs: ['addr']\n        }]\n      }])\n    })\n\n    it('overrides num-providers', async () => {\n      const providers = new Array(20).fill(0).map((val, i) => ({\n        id: peerId + i,\n        multiaddrs: [\n          'addr'\n        ]\n      }))\n\n      ipfs.dht.findProvs.withArgs(cid, {\n        ...defaultOptions\n      }).returns([{\n        name: 'PROVIDER',\n        type: EventTypes.PROVIDER,\n        providers: providers.slice(0, 4)\n      }, {\n        name: 'PROVIDER',\n        type: EventTypes.PROVIDER,\n        providers: providers.slice(4, 8)\n      }, {\n        name: 'PROVIDER',\n        type: EventTypes.PROVIDER,\n        providers: providers.slice(8, 12)\n      }, {\n        name: 'PROVIDER',\n        type: EventTypes.PROVIDER,\n        providers: providers.slice(12)\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/dht/findprovs?arg=${cid}&num-providers=10`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n\n      const provs = allNdjson(res).map(event => event.Responses).reduce((acc, curr) => {\n        return acc.concat(...curr)\n      }, [])\n\n      // should ignore subsequent providers after reaching limit\n      expect(provs).to.have.lengthOf(12)\n    })\n  })\n\n  describe('/get', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal)\n    }\n\n    it('only accepts POST', async () => {\n      const res = await http({\n        method: 'GET',\n        url: '/api/v0/dht/get'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 405)\n    })\n\n    it('returns 400 if no key is provided', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/dht/get'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Code', 1)\n    })\n\n    it('returns 200 if key is provided', async () => {\n      const key = 'key'\n      const value = Buffer.from('hello world')\n      ipfs.dht.get.withArgs(key, defaultOptions).returns([{\n        type: 5,\n        name: 'VALUE',\n        value: value\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/dht/get?arg=${key}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(allNdjson(res)).to.deep.equal([{\n        Extra: uint8ArrayToString(value, 'base64pad'),\n        Type: 5,\n        Responses: null\n      }])\n    })\n  })\n\n  describe('/provide', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal)\n    }\n\n    it('only accepts POST', async () => {\n      const res = await http({\n        method: 'GET',\n        url: '/api/v0/dht/provide'\n      }, { ipfs })\n\n      expect(res.statusCode).to.equal(405)\n    })\n\n    it('returns 400 if no key is provided', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/dht/provide'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Code', 1)\n    })\n\n    it('returns 400 if key is invalid', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/dht/provide?arg=derp'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(ipfs.dht.provide.called).to.be.false()\n    })\n\n    it('returns 500 if key is provided as the file was not added', async () => {\n      ipfs.dht.provide.withArgs(cid, defaultOptions).throws(new Error('wut'))\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/dht/provide?arg=${cid}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 500) // needs file add\n    })\n\n    it('returns 200 if key is provided', async () => {\n      ipfs.dht.provide.withArgs(cid, defaultOptions).returns([{\n        name: 'DIALING_PEER',\n        type: EventTypes.DIALING_PEER,\n        peer: peerId\n      }, {\n        name: 'SENDING_QUERY',\n        type: EventTypes.SENDING_QUERY,\n        to: peerId\n      }, {\n        name: 'PEER_RESPONSE',\n        type: EventTypes.PEER_RESPONSE,\n        from: peerId,\n        closer: [],\n        providers: []\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/dht/provide?arg=${cid}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200) // needs file add\n      expect(allNdjson(res)).to.deep.equal([{\n        Extra: '',\n        ID: peerId,\n        Type: EventTypes.DIALING_PEER,\n        Responses: null\n      }, {\n        Extra: '',\n        Type: EventTypes.SENDING_QUERY,\n        Responses: null\n      }, {\n        Extra: '',\n        ID: peerId,\n        Type: EventTypes.PEER_RESPONSE,\n        Responses: []\n      }])\n    })\n  })\n\n  describe('/put', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal)\n    }\n\n    it('only accepts POST', async () => {\n      const res = await http({\n        method: 'GET',\n        url: '/api/v0/dht/put'\n      }, { ipfs })\n\n      expect(res.statusCode).to.equal(405)\n    })\n\n    it('returns 400 if no key or value is provided', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/dht/put'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Code', 1)\n    })\n\n    it('returns 200 if key and value is provided', async function () {\n      const key = 'key'\n      const value = Buffer.from('value')\n\n      ipfs.dht.put.withArgs(key, value, defaultOptions).returns([{\n        name: 'DIALING_PEER',\n        type: EventTypes.DIALING_PEER,\n        peer: peerId\n      }, {\n        name: 'SENDING_QUERY',\n        type: EventTypes.SENDING_QUERY,\n        to: peerId\n      }, {\n        name: 'PEER_RESPONSE',\n        type: EventTypes.PEER_RESPONSE,\n        from: peerId,\n        closer: [],\n        providers: []\n      }])\n\n      const form = new FormData()\n      form.append('data', value)\n      const headers = form.getHeaders()\n\n      const payload = await streamToPromise(form)\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/dht/put?arg=${key}`,\n        headers,\n        payload\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(allNdjson(res)).to.deep.equal([{\n        Extra: '',\n        ID: peerId,\n        Type: EventTypes.DIALING_PEER,\n        Responses: null\n      }, {\n        Extra: '',\n        Type: EventTypes.SENDING_QUERY,\n        Responses: null\n      }, {\n        Extra: '',\n        ID: peerId,\n        Type: EventTypes.PEER_RESPONSE,\n        Responses: []\n      }])\n    })\n  })\n\n  describe('/query', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal)\n    }\n\n    it('only accepts POST', async () => {\n      const res = await http({\n        method: 'GET',\n        url: '/api/v0/dht/query'\n      }, { ipfs })\n\n      expect(res.statusCode).to.equal(405)\n    })\n\n    it('returns 400 if no key or value is provided', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/dht/query'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Code', 1)\n    })\n\n    it('returns 200 if key is provided', async function () {\n      ipfs.dht.query.withArgs(peerId, defaultOptions).returns([{\n        name: 'DIALING_PEER',\n        type: EventTypes.DIALING_PEER,\n        peer: peerId\n      }, {\n        name: 'SENDING_QUERY',\n        type: EventTypes.SENDING_QUERY,\n        to: peerId\n      }, {\n        name: 'PEER_RESPONSE',\n        type: EventTypes.PEER_RESPONSE,\n        from: peerId,\n        closer: [],\n        providers: []\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/dht/query?arg=${peerId}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(allNdjson(res)).to.deep.equal([{\n        Extra: '',\n        ID: peerId,\n        Type: EventTypes.DIALING_PEER,\n        Responses: null\n      }, {\n        Extra: '',\n        Type: EventTypes.SENDING_QUERY,\n        Responses: null\n      }, {\n        Extra: '',\n        ID: peerId,\n        Type: EventTypes.PEER_RESPONSE,\n        Responses: []\n      }])\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/dns.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { testHttpMethod } from '../utils/test-http-method.js'\nimport { http } from '../utils/http.js'\nimport sinon from 'sinon'\n\nconst defaultOptions = {\n  recursive: false,\n  signal: sinon.match.instanceOf(AbortSignal),\n  timeout: undefined\n}\n\ndescribe('/dns', () => {\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      dns: sinon.stub()\n    }\n  })\n\n  it('only accepts POST', () => {\n    return testHttpMethod('/api/v0/dns?arg=ipfs.io')\n  })\n\n  it('resolves a domain', async () => {\n    const domain = 'ipfs.io'\n    ipfs.dns.withArgs(domain, defaultOptions).returns('path')\n\n    const res = await http({\n      method: 'POST',\n      url: `/api/v0/dns?arg=${domain}`\n    }, { ipfs })\n\n    expect(res).to.have.nested.property('result.Path', 'path')\n  })\n\n  it('resolves a domain recursively', async () => {\n    const domain = 'ipfs.io'\n    ipfs.dns.withArgs(domain, {\n      ...defaultOptions,\n      recursive: true\n    }).returns('path')\n\n    const res = await http({\n      method: 'POST',\n      url: `/api/v0/dns?arg=${domain}&recursive=true`\n    }, { ipfs })\n\n    expect(res).to.have.nested.property('result.Path', 'path')\n  })\n\n  it('resolves a domain recursively (short option)', async () => {\n    const domain = 'ipfs.io'\n    ipfs.dns.withArgs(domain, {\n      ...defaultOptions,\n      recursive: true\n    }).returns('path')\n\n    const res = await http({\n      method: 'POST',\n      url: `/api/v0/dns?arg=${domain}&r=true`\n    }, { ipfs })\n\n    expect(res).to.have.nested.property('result.Path', 'path')\n  })\n\n  it('accepts a timeout', async () => {\n    const domain = 'ipfs.io'\n    ipfs.dns.withArgs(domain, {\n      ...defaultOptions,\n      timeout: 1000\n    }).returns('path')\n\n    const res = await http({\n      method: 'POST',\n      url: `/api/v0/dns?arg=${domain}&timeout=1s`\n    }, { ipfs })\n\n    expect(res).to.have.nested.property('result.Path', 'path')\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/files.js",
    "content": "/* eslint max-nested-callbacks: [\"error\", 8] */\n/* eslint-env mocha */\n\nimport { randomBytes } from 'iso-random-stream'\nimport { expect } from 'aegir/chai'\nimport FormData from 'form-data'\nimport streamToPromise from 'stream-to-promise'\nimport { testHttpMethod } from '../utils/test-http-method.js'\nimport { http } from '../utils/http.js'\nimport sinon from 'sinon'\nimport { CID } from 'multiformats/cid'\nimport first from 'it-first'\nimport toBuffer from 'it-to-buffer'\nimport { base58btc } from 'multiformats/bases/base58'\nimport { base64 } from 'multiformats/bases/base64'\nimport { matchIterable } from '../utils/match-iterable.js'\nimport drain from 'it-drain'\n\ndescribe('/files', () => {\n  const cid = CID.parse('QmUBdnXXPyoDFXj3Hj39dNJ5VkN3QFRskXxcGaYFBB8CNR')\n  const cid2 = CID.parse('QmUBdnXXPyoDFXj3Hj39dNJ5VkN3QFRskXxcGaYFBB8CNA')\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      addAll: sinon.stub(),\n      cat: sinon.stub(),\n      get: sinon.stub(),\n      ls: sinon.stub(),\n      refs: sinon.stub(),\n      files: {\n        stat: sinon.stub()\n      },\n      bases: {\n        getBase: sinon.stub()\n      }\n    }\n\n    ipfs.refs.local = sinon.stub()\n  })\n\n  async function assertAddArgs (url, fn) {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n    const content = Buffer.from('TEST\\n')\n\n    ipfs.addAll.callsFake(async function * (source, opts) {\n      expect(fn(opts)).to.be.true()\n\n      const input = await first(source)\n      expect(await toBuffer(input.content)).to.deep.equal(content)\n\n      yield {\n        path: cid.toString(),\n        cid,\n        size: content.byteLength,\n        mode: 0o420,\n        mtime: {\n          secs: 100,\n          nsecs: 0\n        }\n      }\n    })\n\n    const form = new FormData()\n    form.append('data', content)\n    const headers = form.getHeaders()\n\n    const payload = await streamToPromise(form)\n    const res = await http({\n      method: 'POST',\n      url,\n      headers,\n      payload\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n    expect(JSON.parse(res.result).Hash).to.equal(cid.toString())\n  }\n\n  describe('/add', () => {\n    const defaultOptions = {\n      cidVersion: undefined,\n      rawLeaves: undefined,\n      progress: sinon.match.func,\n      onlyHash: undefined,\n      hashAlg: undefined,\n      wrapWithDirectory: undefined,\n      pin: undefined,\n      chunker: undefined,\n      trickle: undefined,\n      preload: undefined,\n      shardSplitThreshold: undefined,\n      fileImportConcurrency: 1,\n      blockWriteConcurrency: undefined,\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/add')\n    })\n\n    it('should add buffer bigger than Hapi default max bytes (1024 * 1024)', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      const payload = Buffer.from([\n        '',\n        '------------287032381131322',\n        'Content-Disposition: form-data; name=\"test\"; filename=\"test.txt\"',\n        'Content-Type: text/plain',\n        '',\n        randomBytes(1024 * 1024 * 2).toString('hex'),\n        '------------287032381131322--'\n      ].join('\\r\\n'))\n\n      ipfs.addAll.withArgs(matchIterable(), defaultOptions)\n        .callsFake(async function * (source) {\n          await drain(source)\n          yield {\n            path: cid.toString(),\n            cid,\n            size: 1024 * 1024 * 2,\n            mode: 0o420,\n            mtime: {\n              secs: 100,\n              nsecs: 0\n            }\n          }\n        })\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/add',\n        headers: {\n          'Content-Type': 'multipart/form-data; boundary=----------287032381131322'\n        },\n        payload\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n    })\n\n    it('should add data and return a base64 encoded CID', async () => {\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n      const content = Buffer.from('TEST' + Date.now())\n\n      ipfs.addAll.withArgs(matchIterable(), defaultOptions)\n        .callsFake(async function * (source) {\n          await drain(source)\n          yield {\n            path: cid.toString(),\n            cid: cid.toV1(),\n            size: content.byteLength,\n            mode: 0o420,\n            mtime: {\n              secs: 100,\n              nsecs: 0\n            }\n          }\n        })\n\n      const form = new FormData()\n      form.append('data', content)\n      const headers = form.getHeaders()\n\n      const payload = await streamToPromise(form)\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/add?cid-base=base64',\n        headers,\n        payload\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(JSON.parse(res.result).Hash).to.equal(cid.toV1().toString(base64))\n    })\n\n    it('should add data without pinning and return a base64 encoded CID', async () => {\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n      const content = Buffer.from('TEST' + Date.now())\n\n      ipfs.addAll.callsFake(async function * (source, opts) {\n        expect(opts).to.have.property('pin', false)\n\n        const input = await first(source)\n        expect(await toBuffer(input.content)).to.deep.equal(content)\n\n        yield {\n          path: cid.toString(),\n          cid: cid.toV1(),\n          size: content.byteLength,\n          mode: 0o420,\n          mtime: {\n            secs: 100,\n            nsecs: 0\n          }\n        }\n      })\n\n      const form = new FormData()\n      form.append('data', content)\n      const headers = form.getHeaders()\n\n      const payload = await streamToPromise(form)\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/add?cid-base=base64&pin=false',\n        headers,\n        payload\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(JSON.parse(res.result).Hash).to.equal(cid.toV1().toString(base64))\n    })\n\n    it('should specify the cid version', () => assertAddArgs('/api/v0/add?cid-version=1', (opts) => opts.cidVersion === 1))\n\n    it('should specify raw leaves', () => assertAddArgs('/api/v0/add?raw-leaves=true', (opts) => opts.rawLeaves === true))\n\n    it('should specify only hash', () => assertAddArgs('/api/v0/add?only-hash=true', (opts) => opts.onlyHash === true))\n\n    it('should specify pin', () => assertAddArgs('/api/v0/add?pin=true', (opts) => opts.pin === true))\n\n    it('should specify wrap with directory', () => assertAddArgs('/api/v0/add?wrap-with-directory=true', (opts) => opts.wrapWithDirectory === true))\n\n    it('should ignore file import concurrency', () => assertAddArgs('/api/v0/add?file-import-concurrency=5', (opts) => opts.fileImportConcurrency === 1))\n\n    it('should specify block write concurrency', () => assertAddArgs('/api/v0/add?block-write-concurrency=5', (opts) => opts.blockWriteConcurrency === 5))\n\n    it('should specify shard split threshold', () => assertAddArgs('/api/v0/add?shard-split-threshold=5', (opts) => opts.shardSplitThreshold === 5))\n\n    it('should specify chunker', () => assertAddArgs('/api/v0/add?chunker=derp', (opts) => opts.chunker === 'derp'))\n\n    it('should add data using the trickle importer', () => assertAddArgs('/api/v0/add?trickle=true', (opts) => opts.trickle === true))\n\n    it('should specify preload', () => assertAddArgs('/api/v0/add?preload=false', (opts) => opts.preload === false))\n  })\n\n  describe('/cat', () => {\n    const defaultOptions = {\n      offset: undefined,\n      length: undefined,\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/cat')\n    })\n\n    it('returns 400 for request without argument', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/cat'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Message').that.is.a('string')\n    })\n\n    it('returns 400 for request with invalid argument', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/cat?arg=invalid'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Message').that.is.a('string')\n    })\n\n    it('should cat a valid hash', async function () {\n      const data = Buffer.from('TEST' + Date.now())\n\n      ipfs.cat.withArgs(`${cid}`, defaultOptions).returns([\n        data\n      ])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/cat?arg=${cid}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.property('rawPayload', data)\n      expect(res).to.have.property('payload', data.toString())\n    })\n\n    it('should cat a valid hash with an offset', async function () {\n      const data = Buffer.from('TEST' + Date.now())\n\n      ipfs.cat.withArgs(`${cid}`, {\n        ...defaultOptions,\n        offset: 10\n      }).returns([\n        data\n      ])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/cat?arg=${cid}&offset=10`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.property('rawPayload', data)\n      expect(res).to.have.property('payload', data.toString())\n    })\n\n    it('should cat a valid hash with a length', async function () {\n      const data = Buffer.from('TEST' + Date.now())\n\n      ipfs.cat.withArgs(`${cid}`, {\n        ...defaultOptions,\n        length: 10\n      }).returns([\n        data\n      ])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/cat?arg=${cid}&length=10`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.property('rawPayload', data)\n      expect(res).to.have.property('payload', data.toString())\n    })\n  })\n\n  describe('/get', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined,\n      archive: undefined,\n      compress: undefined,\n      compressionLevel: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/get')\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.get.withArgs(`${cid}`, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns(async function * () { yield { path: 'path' } }())\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/get?arg=${cid}&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n    })\n  })\n\n  describe('/ls', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/ls')\n    })\n\n    it('should list directory contents', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.files.stat.withArgs(`/ipfs/${cid}`).returns({\n        type: 'directory'\n      })\n      ipfs.ls.withArgs(`${cid}`, defaultOptions).returns([{\n        name: 'link',\n        cid,\n        size: 10,\n        type: 'file',\n        depth: 1,\n        mode: 0o420\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/ls?arg=${cid}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Objects[0]', {\n        Hash: `${cid}`,\n        Links: [{\n          Depth: 1,\n          Hash: cid.toString(),\n          Mode: '0420',\n          Mtime: undefined,\n          MtimeNsecs: undefined,\n          Name: 'link',\n          Size: 10,\n          Type: 2\n        }]\n      })\n    })\n\n    it('should list a file', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.files.stat.withArgs(`/ipfs/${cid}/derp`).returns({\n        cid,\n        size: 10,\n        type: 'file',\n        depth: 1,\n        mode: 0o420\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/ls?arg=${cid}/derp`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Objects[0]', {\n        Hash: `${cid}/derp`,\n        Depth: 1,\n        Mode: '0420',\n        Mtime: undefined,\n        MtimeNsecs: undefined,\n        Name: undefined,\n        Size: 10,\n        Type: 2,\n        Links: []\n      })\n      expect(ipfs.ls.called).to.be.false()\n    })\n\n    it('should list directory contents without unixfs v1.5 fields', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.files.stat.withArgs(`/ipfs/${cid}`).returns({\n        type: 'directory'\n      })\n      ipfs.ls.withArgs(`${cid}`, defaultOptions).returns([{\n        name: 'link',\n        cid,\n        size: 10,\n        type: 'file',\n        depth: 1\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/ls?arg=${cid}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Objects[0]', {\n        Hash: `${cid}`,\n        Links: [{\n          Depth: 1,\n          Hash: cid.toString(),\n          Mode: undefined,\n          Mtime: undefined,\n          MtimeNsecs: undefined,\n          Name: 'link',\n          Size: 10,\n          Type: 2\n        }]\n      })\n    })\n\n    // TODO: unskip after switch to v1 CIDs by default\n    it.skip('should return base64 encoded CIDs', async () => {\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n      ipfs.ls.withArgs(`${cid}`, defaultOptions).returns([])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/ls?cid-base=base64&arg=${cid}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Objects[0]', {\n        Hash: cid.toV1().toString(base64),\n        Links: []\n      })\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.files.stat.withArgs(`/ipfs/${cid}`).returns({\n        type: 'directory'\n      })\n      ipfs.ls.withArgs(`${cid}`, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns([{\n        name: 'link',\n        cid,\n        size: 10,\n        type: 'file',\n        depth: 1,\n        mode: 0o420\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/ls?arg=${cid}&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n    })\n\n    it('accepts a timeout when streaming', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.files.stat.withArgs(`/ipfs/${cid}`).returns({\n        type: 'directory'\n      })\n      ipfs.ls.withArgs(`${cid}`, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns([{\n        name: 'link',\n        cid,\n        size: 10,\n        type: 'file',\n        depth: 1,\n        mode: 0o420\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/ls?arg=${cid}&stream=true&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n    })\n  })\n\n  describe('/refs', () => {\n    const defaultOptions = {\n      recursive: false,\n      edges: false,\n      unique: false,\n      maxDepth: undefined,\n      format: undefined,\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/refs')\n    })\n\n    it('should list refs', async () => {\n      ipfs.refs.withArgs([`${cid}`], defaultOptions).returns([{\n        ref: cid.toString()\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/refs?arg=${cid}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(JSON.parse(res.result)).to.have.property('Ref', cid.toString())\n    })\n\n    it('should format refs', async () => {\n      ipfs.refs.withArgs([`${cid}`], {\n        ...defaultOptions,\n        format: 'format'\n      }).returns([{\n        ref: cid.toString()\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/refs?arg=${cid}&format=format`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(JSON.parse(res.result)).to.have.property('Ref', cid.toString())\n    })\n\n    it('should list refs for multiple IPFS paths', async () => {\n      ipfs.refs.withArgs([`${cid}`, `/ipfs/${cid2}`], defaultOptions).returns([{\n        ref: cid.toString()\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/refs?arg=${cid}&arg=/ipfs/${cid2}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(JSON.parse(res.result)).to.have.property('Ref', cid.toString())\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.refs.withArgs([`${cid}`], {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns([{\n        ref: cid.toString()\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/refs?arg=${cid}&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(JSON.parse(res.result)).to.have.property('Ref', cid.toString())\n    })\n  })\n\n  describe('/refs/local', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/refs/local')\n    })\n\n    it('should list local refs', async () => {\n      ipfs.refs.local.withArgs(defaultOptions).returns([{\n        ref: cid.toString()\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/refs/local'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(JSON.parse(res.result)).to.have.property('Ref', cid.toString())\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.refs.local.withArgs({\n        ...defaultOptions,\n        timeout: 1000\n      }).returns([{\n        ref: cid.toString()\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/refs/local?timeout=1s'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(JSON.parse(res.result)).to.have.property('Ref', cid.toString())\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/id.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { testHttpMethod } from '../utils/test-http-method.js'\nimport { http } from '../utils/http.js'\nimport sinon from 'sinon'\nimport { peerIdFromString } from '@libp2p/peer-id'\n\nconst defaultOptions = {\n  signal: sinon.match.instanceOf(AbortSignal),\n  timeout: undefined,\n  peerId: undefined\n}\n\ndescribe('/id', () => {\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      id: sinon.stub()\n    }\n  })\n\n  it('only accepts POST', () => {\n    return testHttpMethod('/api/v0/id')\n  })\n\n  it('get the id', async () => {\n    ipfs.id.withArgs(defaultOptions).returns({\n      id: 'id',\n      publicKey: 'publicKey',\n      addresses: 'addresses',\n      agentVersion: 'agentVersion',\n      protocolVersion: 'protocolVersion'\n    })\n\n    const res = await http({\n      method: 'POST',\n      url: '/api/v0/id'\n    }, { ipfs })\n\n    expect(res).to.have.nested.property('result.ID', 'id')\n    expect(res).to.have.nested.property('result.PublicKey', 'publicKey')\n    expect(res).to.have.nested.property('result.Addresses', 'addresses')\n    expect(res).to.have.nested.property('result.AgentVersion', 'agentVersion')\n    expect(res).to.have.nested.property('result.ProtocolVersion', 'protocolVersion')\n  })\n\n  it('accepts a timeout', async () => {\n    ipfs.id.withArgs({\n      ...defaultOptions,\n      timeout: 1000\n    }).returns({\n      id: 'id',\n      publicKey: 'publicKey',\n      addresses: 'addresses',\n      agentVersion: 'agentVersion',\n      protocolVersion: 'protocolVersion'\n    })\n\n    const res = await http({\n      method: 'POST',\n      url: '/api/v0/id?timeout=1s'\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n  })\n\n  it('get the id of another peer', async () => {\n    const peerId = peerIdFromString('QmSnuWmxptJZdLJpKRarxBMS2Ju2oANVrgbr2xWbie9b2D')\n\n    ipfs.id.withArgs({\n      ...defaultOptions,\n      peerId\n    }).returns({\n      id: 'id',\n      publicKey: 'publicKey',\n      addresses: 'addresses',\n      agentVersion: 'agentVersion',\n      protocolVersion: 'protocolVersion'\n    })\n\n    const res = await http({\n      method: 'POST',\n      url: `/api/v0/id?peerId=${peerId.toString()}`\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/key.js",
    "content": "/* eslint max-nested-callbacks: [\"error\", 8] */\n/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { testHttpMethod } from '../utils/test-http-method.js'\nimport { http } from '../utils/http.js'\nimport sinon from 'sinon'\n\ndescribe('/key', function () {\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      key: {\n        list: sinon.stub(),\n        rm: sinon.stub(),\n        rename: sinon.stub(),\n        gen: sinon.stub(),\n        import: sinon.stub()\n      }\n    }\n  })\n\n  describe('/list', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/key/list')\n    })\n\n    it('should list keys', async () => {\n      ipfs.key.list.withArgs(defaultOptions).returns([{\n        name: 'name',\n        id: 'id'\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/key/list'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Keys[0].Name', 'name')\n      expect(res).to.have.nested.property('result.Keys[0].Id', 'id')\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.key.list.withArgs({\n        ...defaultOptions,\n        timeout: 1000\n      }).returns([{\n        name: 'name',\n        id: 'id'\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/key/list?timeout=1s'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n    })\n  })\n\n  describe('/gen', () => {\n    const defaultOptions = {\n      type: 'rsa',\n      size: 2048,\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/key/gen')\n    })\n\n    it('should generate a key', async () => {\n      const name = 'name'\n      const type = 'type'\n      const size = 10\n\n      ipfs.key.gen.withArgs(name, {\n        ...defaultOptions,\n        type,\n        size\n      }).returns({\n        name: 'name',\n        id: 'id'\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/key/gen?arg=${name}&type=${type}&size=${size}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Name', name)\n      expect(res).to.have.nested.property('result.Id', 'id')\n    })\n\n    it('accepts a timeout', async () => {\n      const name = 'name'\n\n      ipfs.key.gen.withArgs(name, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns({\n        name: 'name',\n        id: 'id'\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/key/gen?arg=${name}&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n    })\n  })\n\n  describe('/rm', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/key/rm')\n    })\n\n    it('should remove a key', async () => {\n      const name = 'name'\n\n      ipfs.key.rm.withArgs(name, defaultOptions).returns({\n        name: 'name',\n        id: 'id'\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/key/rm?arg=${name}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Keys[0].Name', 'name')\n      expect(res).to.have.nested.property('result.Keys[0].Id', 'id')\n    })\n\n    it('accepts a timeout', async () => {\n      const name = 'name'\n\n      ipfs.key.rm.withArgs(name, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns({\n        name: 'name',\n        id: 'id'\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/key/rm?arg=${name}&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n    })\n  })\n\n  describe('/rename', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/key/rename')\n    })\n\n    it('should rename a key', async () => {\n      const oldName = 'oldName'\n      const newName = 'newName'\n\n      ipfs.key.rename.withArgs(oldName, newName, defaultOptions).returns({\n        was: oldName,\n        now: newName,\n        id: 'id',\n        overwrite: 'overwrite'\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/key/rename?arg=${oldName}&arg=${newName}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Was', oldName)\n      expect(res).to.have.nested.property('result.Now', newName)\n      expect(res).to.have.nested.property('result.Id', 'id')\n      expect(res).to.have.nested.property('result.Overwrite', 'overwrite')\n    })\n\n    it('accepts a timeout', async () => {\n      const oldName = 'oldName'\n      const newName = 'newName'\n\n      ipfs.key.rename.withArgs(oldName, newName, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns({\n        was: oldName,\n        now: newName,\n        id: 'id',\n        overwrite: 'overwrite'\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/key/rename?arg=${oldName}&arg=${newName}&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n    })\n  })\n\n  describe('/import', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/key/import')\n    })\n\n    it('should import a key', async () => {\n      const name = 'name'\n      const pem = 'pem'\n      const password = 'password'\n\n      ipfs.key.import.withArgs(name, pem, password, defaultOptions).returns({\n        name,\n        id: 'id'\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/key/import?arg=${name}&pem=${pem}&password=${password}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Name', name)\n      expect(res).to.have.nested.property('result.Id', 'id')\n    })\n\n    it('accepts a timeout', async () => {\n      const name = 'name'\n      const pem = 'pem'\n      const password = 'password'\n\n      ipfs.key.import.withArgs(name, pem, password, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns({\n        name,\n        id: 'id'\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/key/import?arg=${name}&pem=${pem}&password=${password}&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Name', name)\n      expect(res).to.have.nested.property('result.Id', 'id')\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/mfs/chmod.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { http } from '../../utils/http.js'\nimport sinon from 'sinon'\nimport { testHttpMethod } from '../../utils/test-http-method.js'\n\nconst defaultOptions = {\n  recursive: false,\n  hashAlg: 'sha2-256',\n  flush: true,\n  shardSplitThreshold: 1000,\n  timeout: undefined,\n  signal: sinon.match.instanceOf(AbortSignal)\n}\n\ndescribe('/files/chmod', () => {\n  const path = '/foo'\n  const mode = '0654'\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      files: {\n        chmod: sinon.stub()\n      }\n    }\n  })\n\n  it('only accepts POST', () => {\n    return testHttpMethod(`/api/v0/files/chmod?arg=${path}&mode=${mode}`, ipfs)\n  })\n\n  it('should update the mode for a file', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/chmod?arg=${path}&mode=${mode}`\n    }, { ipfs })\n\n    expect(ipfs.files.chmod.callCount).to.equal(1)\n    expect(ipfs.files.chmod.calledWith(path, mode, defaultOptions)).to.be.true()\n  })\n\n  it('should update the mode for a file as a string', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/chmod?arg=${path}&mode=-x`\n    }, { ipfs })\n\n    expect(ipfs.files.chmod.callCount).to.equal(1)\n    expect(ipfs.files.chmod.calledWith(path, '-x', defaultOptions)).to.be.true()\n  })\n\n  it('should update the mode recursively', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/chmod?arg=${path}&mode=${mode}&recursive=true`\n    }, { ipfs })\n\n    expect(ipfs.files.chmod.callCount).to.equal(1)\n    expect(ipfs.files.chmod.calledWith(path, mode, {\n      ...defaultOptions,\n      recursive: true\n    })).to.be.true()\n  })\n\n  it('should update the mode without flushing', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/chmod?arg=${path}&mode=${mode}&flush=false`\n    }, { ipfs })\n\n    expect(ipfs.files.chmod.callCount).to.equal(1)\n    expect(ipfs.files.chmod.calledWith(path, mode, {\n      ...defaultOptions,\n      flush: false\n    })).to.be.true()\n  })\n\n  it('should update the mode a different hash algorithm', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/chmod?arg=${path}&mode=${mode}&hashAlg=sha3-256`\n    }, { ipfs })\n\n    expect(ipfs.files.chmod.callCount).to.equal(1)\n    expect(ipfs.files.chmod.calledWith(path, mode, {\n      ...defaultOptions,\n      hashAlg: 'sha3-256'\n    })).to.be.true()\n  })\n\n  it('should update the mode with a shard split threshold', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/chmod?arg=${path}&mode=${mode}&shardSplitThreshold=10`\n    }, { ipfs })\n\n    expect(ipfs.files.chmod.callCount).to.equal(1)\n    expect(ipfs.files.chmod.calledWith(path, mode, {\n      ...defaultOptions,\n      shardSplitThreshold: 10\n    })).to.be.true()\n  })\n\n  it('accepts a timeout', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/chmod?arg=${path}&mode=${mode}&timeout=1s`\n    }, { ipfs })\n\n    expect(ipfs.files.chmod.callCount).to.equal(1)\n    expect(ipfs.files.chmod.calledWith(path, mode, {\n      ...defaultOptions,\n      timeout: 1000\n    })).to.be.true()\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/mfs/cp.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { http } from '../../utils/http.js'\nimport sinon from 'sinon'\nimport { testHttpMethod } from '../../utils/test-http-method.js'\n\nconst defaultOptions = {\n  cidVersion: 0,\n  parents: false,\n  hashAlg: 'sha2-256',\n  flush: true,\n  shardSplitThreshold: 1000,\n  timeout: undefined,\n  signal: sinon.match.instanceOf(AbortSignal)\n}\n\ndescribe('/files/cp', () => {\n  const source = 'source'\n  const dest = 'dest'\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      files: {\n        cp: sinon.stub()\n      }\n    }\n  })\n\n  it('only accepts POST', () => {\n    return testHttpMethod(`/api/v0/files/cp?arg=${source}&arg=${dest}`, ipfs)\n  })\n\n  it('should copy files', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/cp?arg=${source}&arg=${dest}`\n    }, { ipfs })\n\n    expect(ipfs.files.cp.callCount).to.equal(1)\n    expect(ipfs.files.cp.calledWith([source], dest, defaultOptions)).to.be.true()\n  })\n\n  it('should copy files and create intermediate directories', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/cp?arg=${source}&arg=${dest}&parents=true`\n    }, { ipfs })\n\n    expect(ipfs.files.cp.callCount).to.equal(1)\n    expect(ipfs.files.cp.calledWith([source], dest, {\n      ...defaultOptions,\n      parents: true\n    })).to.be.true()\n  })\n\n  it('should copy files with a different cid version', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/cp?arg=${source}&arg=${dest}&cidVersion=1`\n    }, { ipfs })\n\n    expect(ipfs.files.cp.callCount).to.equal(1)\n    expect(ipfs.files.cp.calledWith([source], dest, {\n      ...defaultOptions,\n      cidVersion: 1\n    })).to.be.true()\n  })\n\n  it('should copy files with a different hash algorithm', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/cp?arg=${source}&arg=${dest}&hashAlg=sha3-256`\n    }, { ipfs })\n\n    expect(ipfs.files.cp.callCount).to.equal(1)\n    expect(ipfs.files.cp.calledWith([source], dest, {\n      ...defaultOptions,\n      hashAlg: 'sha3-256'\n    })).to.be.true()\n  })\n\n  it('should copy files with a different shard split threshold', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/cp?arg=${source}&arg=${dest}&shardSplitThreshold=10`\n    }, { ipfs })\n\n    expect(ipfs.files.cp.callCount).to.equal(1)\n    expect(ipfs.files.cp.calledWith([source], dest, {\n      ...defaultOptions,\n      shardSplitThreshold: 10\n    })).to.be.true()\n  })\n\n  it('accepts a timeout', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/cp?arg=${source}&arg=${dest}&timeout=1s`\n    }, { ipfs })\n\n    expect(ipfs.files.cp.callCount).to.equal(1)\n    expect(ipfs.files.cp.calledWith([source], dest, {\n      ...defaultOptions,\n      timeout: 1000\n    })).to.be.true()\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/mfs/flush.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { http } from '../../utils/http.js'\nimport sinon from 'sinon'\nimport { CID } from 'multiformats/cid'\nimport { testHttpMethod } from '../../utils/test-http-method.js'\nimport { base58btc } from 'multiformats/bases/base58'\nimport { base64 } from 'multiformats/bases/base64'\n\nconst cid = CID.parse('QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn')\n\nconst defaultOptions = {\n  timeout: undefined,\n  signal: sinon.match.instanceOf(AbortSignal)\n}\n\ndescribe('/files/flush', () => {\n  const path = '/foo'\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      files: {\n        flush: sinon.stub().resolves(cid)\n      },\n      bases: {\n        getBase: sinon.stub()\n      }\n    }\n  })\n\n  it('only accepts POST', () => {\n    return testHttpMethod(`/api/v0/files/flush?arg=${path}`, ipfs)\n  })\n\n  it('should flush a path', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n    const response = await http({\n      method: 'POST',\n      url: `/api/v0/files/flush?arg=${path}`\n    }, { ipfs })\n\n    expect(ipfs.files.flush.callCount).to.equal(1)\n    expect(ipfs.files.flush.calledWith(path, defaultOptions)).to.be.true()\n    expect(response).to.have.nested.property('result.Cid', cid.toString())\n  })\n\n  it('should flush without a path', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n    const response = await http({\n      method: 'POST',\n      url: '/api/v0/files/flush'\n    }, { ipfs })\n\n    expect(ipfs.files.flush.callCount).to.equal(1)\n    expect(ipfs.files.flush.calledWith('/', defaultOptions)).to.be.true()\n    expect(response).to.have.nested.property('result.Cid', cid.toString())\n  })\n\n  it('should flush with a different CID base', async () => {\n    ipfs.bases.getBase.withArgs('base64').returns(base64)\n    ipfs.files.flush.resolves(cid.toV1())\n\n    const response = await http({\n      method: 'POST',\n      url: '/api/v0/files/flush?cid-base=base64'\n    }, { ipfs })\n\n    expect(ipfs.files.flush.callCount).to.equal(1)\n    expect(ipfs.files.flush.calledWith('/', defaultOptions)).to.be.true()\n    expect(response).to.have.nested.property('result.Cid', cid.toV1().toString(base64))\n  })\n\n  it('accepts a timeout', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n    const response = await http({\n      method: 'POST',\n      url: '/api/v0/files/flush?timeout=1s'\n    }, { ipfs })\n\n    expect(ipfs.files.flush.callCount).to.equal(1)\n    expect(ipfs.files.flush.calledWith('/', {\n      ...defaultOptions,\n      timeout: 1000\n    })).to.be.true()\n    expect(response).to.have.nested.property('result.Cid', cid.toString())\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/mfs/ls.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { http } from '../../utils/http.js'\nimport sinon from 'sinon'\nimport { CID } from 'multiformats/cid'\nimport { testHttpMethod } from '../../utils/test-http-method.js'\nimport { base58btc } from 'multiformats/bases/base58'\n\nconst fileCid = CID.parse('bafybeigyov3nzxrqjismjpq7ghkkjorcmozy5rgaikvyieakoqpxfc3rvu')\n\nconst defaultOptions = {\n  timeout: undefined,\n  signal: sinon.match.instanceOf(AbortSignal)\n}\n\ndescribe('/files/ls', () => {\n  const path = '/foo'\n  const file = {\n    name: 'file-name',\n    type: 'file-type',\n    size: 'file-size',\n    cid: fileCid,\n    mode: 'file-mode',\n    mtime: {\n      secs: 'file-mtime-secs',\n      nsecs: 'file-mtime-nsecs'\n    }\n  }\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      files: {\n        ls: sinon.stub()\n      },\n      bases: {\n        getBase: sinon.stub()\n      }\n    }\n  })\n\n  it('only accepts POST', () => {\n    return testHttpMethod(`/api/v0/files/ls?arg=${path}`, ipfs)\n  })\n\n  it('should list a path', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n    ipfs.files.ls.withArgs(path, defaultOptions).returns([file])\n\n    const response = await http({\n      method: 'POST',\n      url: `/api/v0/files/ls?arg=${path}`\n    }, { ipfs })\n\n    expect(ipfs.files.ls.callCount).to.equal(1)\n    expect(ipfs.files.ls.calledWith(path, defaultOptions)).to.be.true()\n    expect(response).to.have.nested.property('result.Entries.length', 1)\n    expect(response).to.have.nested.property('result.Entries[0].Name', file.name)\n    expect(response).to.have.nested.property('result.Entries[0].Type', 0)\n    expect(response).to.have.nested.property('result.Entries[0].Size', 0)\n    expect(response).to.have.nested.property('result.Entries[0].Hash', file.cid.toString(base58btc))\n  })\n\n  it('should list without a path', async () => {\n    ipfs.files.ls.withArgs('/', defaultOptions).returns([file])\n\n    await http({\n      method: 'POST',\n      url: '/api/v0/files/ls'\n    }, { ipfs })\n\n    expect(ipfs.files.ls.callCount).to.equal(1)\n    expect(ipfs.files.ls.calledWith('/', defaultOptions)).to.be.true()\n  })\n\n  it('should list a path with details', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n    ipfs.files.ls.withArgs(path, defaultOptions).returns([file])\n\n    const response = await http({\n      method: 'POST',\n      url: `/api/v0/files/ls?arg=${path}&long=true`\n    }, { ipfs })\n\n    expect(ipfs.files.ls.callCount).to.equal(1)\n    expect(ipfs.files.ls.calledWith(path, defaultOptions)).to.be.true()\n\n    expect(response).to.have.nested.property('result.Entries.length', 1)\n    expect(response).to.have.nested.property('result.Entries[0].Name', file.name)\n    expect(response).to.have.nested.property('result.Entries[0].Type', 1)\n    expect(response).to.have.nested.property('result.Entries[0].Size', file.size)\n    expect(response).to.have.nested.property('result.Entries[0].Hash', file.cid.toString(base58btc))\n    expect(response).to.have.nested.property('result.Entries[0].Mode', file.mode)\n    expect(response).to.have.nested.property('result.Entries[0].Mtime', file.mtime.secs)\n    expect(response).to.have.nested.property('result.Entries[0].MtimeNsecs', file.mtime.nsecs)\n  })\n\n  it('should stream a path', async () => {\n    ipfs.files.ls.withArgs(path, {\n      ...defaultOptions\n    })\n      .callsFake(async function * () {\n        yield file\n      })\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/ls?arg=${path}&stream=true`\n    }, { ipfs })\n\n    expect(ipfs.files.ls.callCount).to.equal(1)\n    expect(ipfs.files.ls.calledWith(path, defaultOptions)).to.be.true()\n  })\n\n  it('accepts a timeout', async () => {\n    ipfs.files.ls.withArgs({\n      ...defaultOptions,\n      timeout: 1000\n    }).returns([file])\n\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/ls?arg=${path}&timeout=1s`\n    }, { ipfs })\n\n    expect(ipfs.files.ls.callCount).to.equal(1)\n    expect(ipfs.files.ls.calledWith(path, {\n      ...defaultOptions,\n      timeout: 1000\n    })).to.be.true()\n  })\n\n  it('accepts a timeout when streaming', async () => {\n    ipfs.files.ls.withArgs(path, {\n      ...defaultOptions,\n      timeout: 1000\n    }).callsFake(async function * () {\n      yield file\n    })\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/ls?arg=${path}&stream=true&timeout=1s`\n    }, { ipfs })\n\n    expect(ipfs.files.ls.callCount).to.equal(1)\n    expect(ipfs.files.ls.calledWith(path, {\n      ...defaultOptions,\n      timeout: 1000\n    })).to.be.true()\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/mfs/mkdir.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { http } from '../../utils/http.js'\nimport sinon from 'sinon'\nimport { testHttpMethod } from '../../utils/test-http-method.js'\n\nconst defaultOptions = {\n  parents: false,\n  cidVersion: 0,\n  hashAlg: 'sha2-256',\n  flush: true,\n  shardSplitThreshold: 1000,\n  mode: undefined,\n  mtime: undefined,\n  timeout: undefined,\n  signal: sinon.match.instanceOf(AbortSignal)\n}\n\ndescribe('/files/mkdir', () => {\n  const path = '/foo'\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      files: {\n        mkdir: sinon.stub()\n      }\n    }\n  })\n\n  it('only accepts POST', () => {\n    return testHttpMethod(`/api/v0/files/mkdir?arg=${path}`, ipfs)\n  })\n\n  it('should make a directory', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/mkdir?arg=${path}`\n    }, { ipfs })\n\n    expect(ipfs.files.mkdir.callCount).to.equal(1)\n    expect(ipfs.files.mkdir.calledWith(path, defaultOptions)).to.be.true()\n  })\n\n  it('should make a directory with parents', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/mkdir?arg=${path}&parents=true`\n    }, { ipfs })\n\n    expect(ipfs.files.mkdir.callCount).to.equal(1)\n    expect(ipfs.files.mkdir.calledWith(path, {\n      ...defaultOptions,\n      parents: true\n    })).to.be.true()\n  })\n\n  it('should make a directory with a different cid version', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/mkdir?arg=${path}&cidVersion=1`\n    }, { ipfs })\n\n    expect(ipfs.files.mkdir.callCount).to.equal(1)\n    expect(ipfs.files.mkdir.calledWith(path, {\n      ...defaultOptions,\n      cidVersion: 1\n    })).to.be.true()\n  })\n\n  it('should make a directory with a different hash algorithm', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/mkdir?arg=${path}&hashAlg=sha3-256`\n    }, { ipfs })\n\n    expect(ipfs.files.mkdir.callCount).to.equal(1)\n    expect(ipfs.files.mkdir.calledWith(path, {\n      ...defaultOptions,\n      hashAlg: 'sha3-256'\n    })).to.be.true()\n  })\n\n  it('should make a directory without flushing', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/mkdir?arg=${path}&flush=false`\n    }, { ipfs })\n\n    expect(ipfs.files.mkdir.callCount).to.equal(1)\n    expect(ipfs.files.mkdir.calledWith(path, {\n      ...defaultOptions,\n      flush: false\n    })).to.be.true()\n  })\n\n  it('should make a directory a different shard split threshold', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/mkdir?arg=${path}&shardSplitThreshold=10`\n    }, { ipfs })\n\n    expect(ipfs.files.mkdir.callCount).to.equal(1)\n    expect(ipfs.files.mkdir.calledWith(path, {\n      ...defaultOptions,\n      shardSplitThreshold: 10\n    })).to.be.true()\n  })\n\n  it('should make a directory a different mode', async () => {\n    const mode = '0513'\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/mkdir?arg=${path}&mode=${mode}`\n    }, { ipfs })\n\n    expect(ipfs.files.mkdir.callCount).to.equal(1)\n    expect(ipfs.files.mkdir.calledWith(path, {\n      ...defaultOptions,\n      mode: mode\n    })).to.be.true()\n  })\n\n  it('should make a directory a different mtime', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/mkdir?arg=${path}&mtime=5`\n    }, { ipfs })\n\n    expect(ipfs.files.mkdir.callCount).to.equal(1)\n    expect(ipfs.files.mkdir.calledWith(path, {\n      ...defaultOptions,\n      mtime: {\n        secs: 5\n      }\n    })).to.be.true()\n  })\n\n  it('accepts a timeout', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/mkdir?arg=${path}&timeout=1s`\n    }, { ipfs })\n\n    expect(ipfs.files.mkdir.callCount).to.equal(1)\n    expect(ipfs.files.mkdir.calledWith(path, {\n      ...defaultOptions,\n      timeout: 1000\n    })).to.be.true()\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/mfs/mv.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { http } from '../../utils/http.js'\nimport sinon from 'sinon'\nimport { testHttpMethod } from '../../utils/test-http-method.js'\n\nconst defaultOptions = {\n  parents: false,\n  recursive: false,\n  cidVersion: 0,\n  hashAlg: 'sha2-256',\n  flush: true,\n  shardSplitThreshold: 1000,\n  timeout: undefined,\n  signal: sinon.match.instanceOf(AbortSignal)\n}\n\ndescribe('/files/mv', () => {\n  const source = '/src'\n  const dest = '/dest'\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      files: {\n        mv: sinon.stub()\n      }\n    }\n  })\n\n  it('only accepts POST', () => {\n    return testHttpMethod(`/api/v0/files/mv?arg=${source}&arg=${dest}`, ipfs)\n  })\n\n  it('should move an entry', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/mv?arg=${source}&arg=${dest}`\n    }, { ipfs })\n\n    expect(ipfs.files.mv.callCount).to.equal(1)\n    expect(ipfs.files.mv.calledWith(source, dest, defaultOptions)).to.be.true()\n  })\n\n  it('should move an entry and create parents', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/mv?arg=${source}&arg=${dest}&parents=true`\n    }, { ipfs })\n\n    expect(ipfs.files.mv.callCount).to.equal(1)\n    expect(ipfs.files.mv.calledWith(source, dest, {\n      ...defaultOptions,\n      parents: true\n    })).to.be.true()\n  })\n\n  it('should move an entry recursively', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/mv?arg=${source}&arg=${dest}&recursive=true`\n    }, { ipfs })\n\n    expect(ipfs.files.mv.callCount).to.equal(1)\n    expect(ipfs.files.mv.calledWith(source, dest, {\n      ...defaultOptions,\n      recursive: true\n    })).to.be.true()\n  })\n\n  it('should make a directory with a different cid version', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/mv?arg=${source}&arg=${dest}&cidVersion=1`\n    }, { ipfs })\n\n    expect(ipfs.files.mv.callCount).to.equal(1)\n    expect(ipfs.files.mv.calledWith(source, dest, {\n      ...defaultOptions,\n      cidVersion: 1\n    })).to.be.true()\n  })\n\n  it('should make a directory with a different hash algorithm', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/mv?arg=${source}&arg=${dest}&hashAlg=sha3-256`\n    }, { ipfs })\n\n    expect(ipfs.files.mv.callCount).to.equal(1)\n    expect(ipfs.files.mv.calledWith(source, dest, {\n      ...defaultOptions,\n      hashAlg: 'sha3-256'\n    })).to.be.true()\n  })\n\n  it('should make a directory without flushing', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/mv?arg=${source}&arg=${dest}&flush=false`\n    }, { ipfs })\n\n    expect(ipfs.files.mv.callCount).to.equal(1)\n    expect(ipfs.files.mv.calledWith(source, dest, {\n      ...defaultOptions,\n      flush: false\n    })).to.be.true()\n  })\n\n  it('should make a directory a different shard split threshold', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/mv?arg=${source}&arg=${dest}&shardSplitThreshold=10`\n    }, { ipfs })\n\n    expect(ipfs.files.mv.callCount).to.equal(1)\n    expect(ipfs.files.mv.calledWith(source, dest, {\n      ...defaultOptions,\n      shardSplitThreshold: 10\n    })).to.be.true()\n  })\n\n  it('accepts a timeout', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/mv?arg=${source}&arg=${dest}&timeout=1s`\n    }, { ipfs })\n\n    expect(ipfs.files.mv.callCount).to.equal(1)\n    expect(ipfs.files.mv.calledWith(source, dest, {\n      ...defaultOptions,\n      timeout: 1000\n    })).to.be.true()\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/mfs/read.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { http } from '../../utils/http.js'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport sinon from 'sinon'\nimport { testHttpMethod } from '../../utils/test-http-method.js'\n\nconst defaultOptions = {\n  offset: undefined,\n  length: undefined,\n  timeout: undefined,\n  signal: sinon.match.instanceOf(AbortSignal)\n}\n\ndescribe('/files/read', () => {\n  const path = '/foo'\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      files: {\n        read: sinon.stub().returns([uint8ArrayFromString('hello world')])\n      }\n    }\n  })\n\n  it('only accepts POST', () => {\n    return testHttpMethod(`/api/v0/files/read?arg=${path}`, ipfs)\n  })\n\n  it('should read a path', async () => {\n    const response = await http({\n      method: 'POST',\n      url: `/api/v0/files/read?arg=${path}`\n    }, { ipfs })\n\n    expect(ipfs.files.read.callCount).to.equal(1)\n    expect(ipfs.files.read.calledWith(path, defaultOptions)).to.be.true()\n    expect(response).to.have.property('result', 'hello world')\n  })\n\n  it('should read a path with an offset', async () => {\n    const offset = 5\n    const response = await http({\n      method: 'POST',\n      url: `/api/v0/files/read?arg=${path}&offset=${offset}`\n    }, { ipfs })\n\n    expect(ipfs.files.read.callCount).to.equal(1)\n    expect(ipfs.files.read.calledWith(path, {\n      ...defaultOptions,\n      offset\n    })).to.be.true()\n    expect(response).to.have.property('result', 'hello world')\n  })\n\n  it('should read a path with a length', async () => {\n    const length = 5\n    const response = await http({\n      method: 'POST',\n      url: `/api/v0/files/read?arg=${path}&length=${length}`\n    }, { ipfs })\n\n    expect(ipfs.files.read.callCount).to.equal(1)\n    expect(ipfs.files.read.calledWith(path, {\n      ...defaultOptions,\n      length\n    })).to.be.true()\n    expect(response).to.have.property('result', 'hello world')\n  })\n\n  it('should read a path with count treated as length', async () => {\n    const length = 5\n    const response = await http({\n      method: 'POST',\n      url: `/api/v0/files/read?arg=${path}&count=${length}`\n    }, { ipfs })\n\n    expect(ipfs.files.read.callCount).to.equal(1)\n    expect(ipfs.files.read.callCount).to.equal(1)\n    expect(ipfs.files.read.calledWith(path, {\n      ...defaultOptions,\n      length\n    })).to.be.true()\n    expect(response).to.have.property('result', 'hello world')\n  })\n\n  it('accepts a timeout', async () => {\n    const response = await http({\n      method: 'POST',\n      url: `/api/v0/files/read?arg=${path}&timeout=1s`\n    }, { ipfs })\n\n    expect(ipfs.files.read.callCount).to.equal(1)\n    expect(ipfs.files.read.callCount).to.equal(1)\n    expect(ipfs.files.read.calledWith(path, {\n      ...defaultOptions,\n      timeout: 1000\n    })).to.be.true()\n    expect(response).to.have.property('result', 'hello world')\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/mfs/rm.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { http } from '../../utils/http.js'\nimport sinon from 'sinon'\nimport { testHttpMethod } from '../../utils/test-http-method.js'\n\nconst defaultOptions = {\n  recursive: false,\n  shardSplitThreshold: 1000,\n  timeout: undefined,\n  signal: sinon.match.instanceOf(AbortSignal)\n}\n\ndescribe('/files/rm', () => {\n  const path = '/foo'\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      files: {\n        rm: sinon.stub().resolves()\n      }\n    }\n  })\n\n  it('only accepts POST', () => {\n    return testHttpMethod(`/api/v0/files/rm?arg=${path}`, ipfs)\n  })\n\n  it('should remove a path', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/rm?arg=${path}`\n    }, { ipfs })\n\n    expect(ipfs.files.rm.callCount).to.equal(1)\n    expect(ipfs.files.rm.calledWith([path], defaultOptions)).to.be.true()\n  })\n\n  it('should remove a path recursively', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/rm?arg=${path}&recursive=true`\n    }, { ipfs })\n\n    expect(ipfs.files.rm.callCount).to.equal(1)\n    expect(ipfs.files.rm.calledWith([path], {\n      ...defaultOptions,\n      recursive: true\n    })).to.be.true()\n  })\n\n  it('should remove a path with a different shard split threshold', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/rm?arg=${path}&shardSplitThreshold=10`\n    }, { ipfs })\n\n    expect(ipfs.files.rm.callCount).to.equal(1)\n    expect(ipfs.files.rm.calledWith([path], {\n      ...defaultOptions,\n      shardSplitThreshold: 10\n    })).to.be.true()\n  })\n\n  it('accepts a timeout', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/rm?arg=${path}&timeout=1s`\n    }, { ipfs })\n\n    expect(ipfs.files.rm.callCount).to.equal(1)\n    expect(ipfs.files.rm.calledWith([path], {\n      ...defaultOptions,\n      timeout: 1000\n    })).to.be.true()\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/mfs/stat.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { http } from '../../utils/http.js'\nimport sinon from 'sinon'\nimport { CID } from 'multiformats/cid'\nimport { testHttpMethod } from '../../utils/test-http-method.js'\nimport { base58btc } from 'multiformats/bases/base58'\nimport { base64 } from 'multiformats/bases/base64'\n\nconst fileCid = CID.parse('bafybeigyov3nzxrqjismjpq7ghkkjorcmozy5rgaikvyieakoqpxfc3rvu')\n\nconst defaultOptions = {\n  withLocal: false,\n  hash: false,\n  size: false,\n  timeout: undefined,\n  signal: sinon.match.instanceOf(AbortSignal)\n}\n\ndescribe('/files/stat', () => {\n  const path = '/foo'\n  const stats = {\n    cid: fileCid,\n    size: 'stats-size',\n    cumulativeSize: 'stats-cumulativeSize',\n    blocks: 'stats-blocks',\n    type: 'stats-type',\n    mode: 'stats-mode',\n    mtime: 'stats-mtime'\n  }\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      files: {\n        stat: sinon.stub().resolves(stats)\n      },\n      bases: {\n        getBase: sinon.stub()\n      }\n    }\n  })\n\n  it('only accepts POST', () => {\n    return testHttpMethod(`/api/v0/files/stat?arg=${path}`, ipfs)\n  })\n\n  it('should stat a path', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n    const response = await http({\n      method: 'POST',\n      url: `/api/v0/files/stat?arg=${path}`\n    }, { ipfs })\n\n    expect(ipfs.files.stat.callCount).to.equal(1)\n    expect(ipfs.files.stat.calledWith(path, defaultOptions)).to.be.true()\n    expect(response).to.have.nested.property('result.CumulativeSize', stats.cumulativeSize)\n  })\n\n  it('should stat a path with local', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/stat?arg=${path}&withLocal=true`\n    }, { ipfs })\n\n    expect(ipfs.files.stat.callCount).to.equal(1)\n    expect(ipfs.files.stat.calledWith(path, {\n      ...defaultOptions,\n      withLocal: true\n    })).to.be.true()\n  })\n\n  it('should stat a path and only show hashes', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n    const response = await http({\n      method: 'POST',\n      url: `/api/v0/files/stat?arg=${path}&hash=true`\n    }, { ipfs })\n\n    expect(ipfs.files.stat.callCount).to.equal(1)\n    expect(ipfs.files.stat.calledWith(path, {\n      ...defaultOptions,\n      hash: true\n    })).to.be.true()\n    expect(response).to.have.nested.property('result.Hash', stats.cid.toString(base58btc))\n  })\n\n  it('should stat a path and only show sizes', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n    const response = await http({\n      method: 'POST',\n      url: `/api/v0/files/stat?arg=${path}&size=true`\n    }, { ipfs })\n\n    expect(ipfs.files.stat.callCount).to.equal(1)\n    expect(ipfs.files.stat.calledWith(path, {\n      ...defaultOptions,\n      size: true\n    })).to.be.true()\n    expect(response).to.have.nested.property('result.Size', stats.size)\n  })\n\n  it('should stat a path and show hashes with a different base', async () => {\n    ipfs.bases.getBase.withArgs('base64').returns(base64)\n    const response = await http({\n      method: 'POST',\n      url: `/api/v0/files/stat?arg=${path}&cidBase=base64`\n    }, { ipfs })\n\n    expect(ipfs.files.stat.callCount).to.equal(1)\n    expect(ipfs.files.stat.calledWith(path, defaultOptions)).to.be.true()\n    expect(response).to.have.nested.property('result.Hash', stats.cid.toString(base64))\n  })\n\n  it('accepts a timeout', async () => {\n    ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n    const response = await http({\n      method: 'POST',\n      url: `/api/v0/files/stat?arg=${path}&timeout=1s`\n    }, { ipfs })\n\n    expect(ipfs.files.stat.callCount).to.equal(1)\n    expect(ipfs.files.stat.calledWith(path, {\n      ...defaultOptions,\n      timeout: 1000\n    })).to.be.true()\n    expect(response).to.have.nested.property('result.Size', stats.size)\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/mfs/touch.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { http } from '../../utils/http.js'\nimport sinon from 'sinon'\nimport { testHttpMethod } from '../../utils/test-http-method.js'\n\nconst defaultOptions = {\n  mtime: undefined,\n  cidVersion: 0,\n  hashAlg: 'sha2-256',\n  flush: true,\n  shardSplitThreshold: 1000,\n  timeout: undefined,\n  signal: sinon.match.instanceOf(AbortSignal)\n}\n\ndescribe('/files/touch', () => {\n  const path = '/foo'\n  const mtime = new Date(1000000)\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      files: {\n        touch: sinon.stub()\n      }\n    }\n  })\n\n  it('only accepts POST', () => {\n    return testHttpMethod(`/api/v0/files/touch?arg=${path}&mtime=${mtime.getTime() / 1000}`, ipfs)\n  })\n\n  it('should update the mtime for a file', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/touch?arg=${path}`\n    }, { ipfs })\n\n    expect(ipfs.files.touch.callCount).to.equal(1)\n    expect(ipfs.files.touch.calledWith(path, defaultOptions)).to.be.true()\n  })\n\n  it('should specify the mtime for a file', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/touch?arg=${path}&mtime=${mtime.getTime() / 1000}`\n    }, { ipfs })\n\n    expect(ipfs.files.touch.callCount).to.equal(1)\n    expect(ipfs.files.touch.calledWith(path, {\n      ...defaultOptions,\n      mtime: {\n        secs: 1000\n      }\n    })).to.be.true()\n  })\n\n  it('should update the mtime without flushing', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/touch?arg=${path}&flush=false`\n    }, { ipfs })\n\n    expect(ipfs.files.touch.callCount).to.equal(1)\n    expect(ipfs.files.touch.calledWith(path, {\n      ...defaultOptions,\n      flush: false\n    })).to.be.true()\n  })\n\n  it('should update the mtime with a different hash algorithm', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/touch?arg=${path}&hashAlg=sha3-256`\n    }, { ipfs })\n\n    expect(ipfs.files.touch.callCount).to.equal(1)\n    expect(ipfs.files.touch.calledWith(path, {\n      ...defaultOptions,\n      hashAlg: 'sha3-256'\n    })).to.be.true()\n  })\n\n  it('should update the mtime with a shard split threshold', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/touch?arg=${path}&shardSplitThreshold=10`\n    }, { ipfs })\n\n    expect(ipfs.files.touch.callCount).to.equal(1)\n    expect(ipfs.files.touch.calledWith(path, {\n      ...defaultOptions,\n      shardSplitThreshold: 10\n    })).to.be.true()\n  })\n\n  it('should update the mtime with nanoseconds', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/touch?arg=${path}&mtime=${mtime.getTime() / 1000}&mtimeNsecs=100`\n    }, { ipfs })\n\n    expect(ipfs.files.touch.callCount).to.equal(1)\n    expect(ipfs.files.touch.calledWith(path, {\n      ...defaultOptions,\n      mtime: {\n        secs: 1000,\n        nsecs: 100\n      }\n    })).to.be.true()\n  })\n\n  it('accepts a timeout', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/touch?arg=${path}&timeout=1s`\n    }, { ipfs })\n\n    expect(ipfs.files.touch.callCount).to.equal(1)\n    expect(ipfs.files.touch.calledWith(path, {\n      ...defaultOptions,\n      timeout: 1000\n    })).to.be.true()\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/mfs/write.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { http } from '../../utils/http.js'\nimport { matchIterable } from '../../utils/match-iterable.js'\nimport sinon from 'sinon'\nimport FormData from 'form-data'\nimport streamToPromise from 'stream-to-promise'\n\nconst defaultOptions = {\n  offset: undefined,\n  length: undefined,\n  create: false,\n  truncate: false,\n  rawLeaves: false,\n  reduceSingleLeafToSelf: false,\n  cidVersion: 0,\n  hashAlg: 'sha2-256',\n  parents: false,\n  strategy: 'trickle',\n  flush: true,\n  shardSplitThreshold: 1000,\n  mode: undefined,\n  mtime: undefined,\n  timeout: undefined,\n  signal: sinon.match.instanceOf(AbortSignal)\n}\n\nasync function send (text, options = {}) {\n  let fieldName = 'file-0'\n  const query = []\n\n  if (options.mode) {\n    query.push(`mode=${options.mode}`)\n  }\n\n  if (options.mtime) {\n    query.push(`mtime=${options.mtime}`)\n  }\n\n  if (options.mtimeNsecs) {\n    query.push(`mtime-nsecs=${options.mtimeNsecs}`)\n  }\n\n  if (query.length) {\n    fieldName = `${fieldName}?${query.join('&')}`\n  }\n\n  const form = new FormData()\n  form.append(fieldName, Buffer.from(text))\n\n  return {\n    headers: form.getHeaders(),\n    payload: await streamToPromise(form)\n  }\n}\n\ndescribe('/files/write', () => {\n  const path = '/foo'\n  let ipfs\n  let content\n\n  beforeEach(() => {\n    content = Buffer.alloc(0)\n\n    ipfs = {\n      files: {\n        write: sinon.stub().callsFake(async (path, input) => {\n          for await (const buf of input) {\n            content = Buffer.concat([content, buf])\n          }\n\n          content = content.toString('utf8')\n        })\n      }\n    }\n  })\n\n  it('should write to a file', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/write?arg=${path}`,\n      ...await send('hello world')\n    }, { ipfs })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.calledWith(path, matchIterable(), defaultOptions)).to.be.true()\n    expect(content).to.equal('hello world')\n  })\n\n  it('should write to a file and create parents', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/write?arg=${path}&parents=true`,\n      ...await send('hello world')\n    }, { ipfs })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.calledWith(path, matchIterable(), {\n      ...defaultOptions,\n      parents: true\n    })).to.be.true()\n    expect(content).to.equal('hello world')\n  })\n\n  it('should write to a file and create it', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/write?arg=${path}&create=true`,\n      ...await send('hello world')\n    }, { ipfs })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.calledWith(path, matchIterable(), {\n      ...defaultOptions,\n      create: true\n    })).to.be.true()\n    expect(content).to.equal('hello world')\n  })\n\n  it('should write to a file with an offset', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/write?arg=${path}&offset=10`,\n      ...await send('hello world')\n    }, { ipfs })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.calledWith(path, matchIterable(), {\n      ...defaultOptions,\n      offset: 10\n    })).to.be.true()\n    expect(content).to.equal('hello world')\n  })\n\n  it('should write to a file with a length', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/write?arg=${path}&length=10`,\n      ...await send('hello world')\n    }, { ipfs })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.calledWith(path, matchIterable(), {\n      ...defaultOptions,\n      length: 10\n    })).to.be.true()\n    expect(content).to.equal('hello world')\n  })\n\n  it('should write to a file and truncate it', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/write?arg=${path}&truncate=true`,\n      ...await send('hello world')\n    }, { ipfs })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.calledWith(path, matchIterable(), {\n      ...defaultOptions,\n      truncate: true\n    })).to.be.true()\n    expect(content).to.equal('hello world')\n  })\n\n  it('should write to a file with raw leaves', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/write?arg=${path}&rawLeaves=true`,\n      ...await send('hello world')\n    }, { ipfs })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.calledWith(path, matchIterable(), {\n      ...defaultOptions,\n      rawLeaves: true\n    })).to.be.true()\n    expect(content).to.equal('hello world')\n  })\n\n  it('should write to a file and reduce a single leaf to one node', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/write?arg=${path}&reduceSingleLeafToSelf=true`,\n      ...await send('hello world')\n    }, { ipfs })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.calledWith(path, matchIterable(), {\n      ...defaultOptions,\n      reduceSingleLeafToSelf: true\n    })).to.be.true()\n    expect(content).to.equal('hello world')\n  })\n\n  it('should write to a file without flushing', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/write?arg=${path}&flush=false`,\n      ...await send('hello world')\n    }, { ipfs })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.calledWith(path, matchIterable(), {\n      ...defaultOptions,\n      flush: false\n    })).to.be.true()\n    expect(content).to.equal('hello world')\n  })\n\n  it('should write to a file with a specified strategy', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/write?arg=${path}&strategy=flat`,\n      ...await send('hello world')\n    }, { ipfs })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.calledWith(path, matchIterable(), {\n      ...defaultOptions,\n      strategy: 'flat'\n    })).to.be.true()\n    expect(content).to.equal('hello world')\n  })\n\n  it('should write to a file with a specified cid version', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/write?arg=${path}&cidVersion=1`,\n      ...await send('hello world')\n    }, { ipfs })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.calledWith(path, matchIterable(), {\n      ...defaultOptions,\n      cidVersion: 1\n    })).to.be.true()\n    expect(content).to.equal('hello world')\n  })\n\n  it('should write to a file with a specified hash algorithm', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/write?arg=${path}&hashAlg=sha3-256`,\n      ...await send('hello world')\n    }, { ipfs })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.calledWith(path, matchIterable(), {\n      ...defaultOptions,\n      hashAlg: 'sha3-256'\n    })).to.be.true()\n    expect(content).to.equal('hello world')\n  })\n\n  it('should write to a file with a specified shard split threshold', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/write?arg=${path}&shardSplitThreshold=10`,\n      ...await send('hello world')\n    }, { ipfs })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.calledWith(path, matchIterable(), {\n      ...defaultOptions,\n      shardSplitThreshold: 10\n    })).to.be.true()\n    expect(content).to.equal('hello world')\n  })\n\n  it('should write to a file with a specified mode', async () => {\n    const mode = '0577'\n\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/write?arg=${path}`,\n      ...await send('hello world', {\n        mode\n      })\n    }, { ipfs })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.calledWith(path, matchIterable(), {\n      ...defaultOptions,\n      mode: parseInt(mode, 8)\n    })).to.be.true()\n    expect(content).to.equal('hello world')\n  })\n\n  it('should write to a file with a specified mtime', async () => {\n    const mtime = 11\n\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/write?arg=${path}`,\n      ...await send('hello world', {\n        mtime\n      })\n    }, { ipfs })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.calledWith(path, matchIterable(), {\n      ...defaultOptions,\n      mtime: {\n        secs: 11\n      }\n    })).to.be.true()\n    expect(content).to.equal('hello world')\n  })\n\n  it('accepts a timeout', async () => {\n    await http({\n      method: 'POST',\n      url: `/api/v0/files/write?arg=${path}&timeout=1s`,\n      ...await send('hello world')\n    }, { ipfs })\n\n    expect(ipfs.files.write.callCount).to.equal(1)\n    expect(ipfs.files.write.calledWith(path, matchIterable(), {\n      ...defaultOptions,\n      timeout: 1000\n    })).to.be.true()\n    expect(content).to.equal('hello world')\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/mfs.js",
    "content": "/* eslint-env mocha */\n\nimport './mfs/chmod.js'\nimport './mfs/cp.js'\nimport './mfs/flush.js'\nimport './mfs/ls.js'\nimport './mfs/mkdir.js'\nimport './mfs/mv.js'\nimport './mfs/read.js'\nimport './mfs/rm.js'\nimport './mfs/stat.js'\nimport './mfs/touch.js'\nimport './mfs/write.js'\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/name.js",
    "content": "/* eslint max-nested-callbacks: [\"error\", 8] */\n/* eslint-env mocha */\n\nimport { CID } from 'multiformats/cid'\nimport { expect } from 'aegir/chai'\nimport { testHttpMethod } from '../utils/test-http-method.js'\nimport { http } from '../utils/http.js'\nimport sinon from 'sinon'\n\ndescribe('/name', function () {\n  const cid = CID.parse('QmbndGRXYRyfU41TUvc52gMrwq87JJg18QsDPcCeaMcM61')\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      name: {\n        resolve: sinon.stub(),\n        publish: sinon.stub(),\n        pubsub: {\n          state: sinon.stub(),\n          subs: sinon.stub(),\n          cancel: sinon.stub()\n        }\n      }\n    }\n  })\n\n  describe('/publish', () => {\n    const defaultOptions = {\n      resolve: true,\n      lifetime: '24h',\n      ttl: undefined,\n      key: 'self',\n      allowOffline: undefined,\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod(`/api/v0/name/publish?arg=${cid}&resolve=false`)\n    })\n\n    it('should publish a record', async () => {\n      const resolve = true\n      const lifetime = '24h'\n      const ttl = 'ttl'\n      const key = 'key'\n      const allowOffline = true\n      const name = 'name'\n\n      ipfs.name.publish.withArgs(cid.toString(), {\n        ...defaultOptions,\n        resolve,\n        lifetime,\n        ttl,\n        key,\n        allowOffline\n      }).returns({\n        name,\n        value: cid.toString()\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/name/publish?arg=${cid}&resolve=${resolve}&lifetime=${lifetime}&ttl=${ttl}&key=${key}&allow-offline=${allowOffline}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Name', name)\n      expect(res).to.have.nested.property('result.Value', cid.toString())\n    })\n\n    it('accepts a timeout', async () => {\n      const name = 'name'\n\n      ipfs.name.publish.withArgs(cid.toString(), {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns({\n        name,\n        value: cid.toString()\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/name/publish?arg=${cid}&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Name', name)\n      expect(res).to.have.nested.property('result.Value', cid.toString())\n    })\n  })\n\n  describe('/resolve', () => {\n    const defaultOptions = {\n      nocache: false,\n      recursive: true,\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/name/resolve')\n    })\n\n    it('should resolve a record', async () => {\n      const nocache = true\n      const recursive = true\n\n      ipfs.name.resolve.withArgs(cid.toString(), {\n        ...defaultOptions,\n        nocache,\n        recursive\n      }).returns([\n        cid.toString()\n      ])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/name/resolve?arg=${cid}&nocache=${nocache}&recursive=${recursive}`\n      }, { ipfs })\n\n      expect(res).to.exist()\n      expect(res).to.have.nested.property('result.Path', cid.toString())\n    })\n\n    it('should resolve a record in streaming mode', async () => {\n      const nocache = true\n      const recursive = true\n\n      ipfs.name.resolve.withArgs(cid.toString(), {\n        ...defaultOptions,\n        nocache,\n        recursive\n      }).returns([\n        cid.toString()\n      ])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/name/resolve?arg=${cid}&nocache=${nocache}&recursive=${recursive}&stream=true`\n      }, { ipfs })\n\n      expect(res).to.exist()\n      expect(res).to.have.property('result')\n      expect(JSON.parse(res.result)).to.have.property('Path', cid.toString())\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.name.resolve.withArgs(cid.toString(), {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns([\n        cid.toString()\n      ])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/name/resolve?arg=${cid}&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.exist()\n      expect(res).to.have.nested.property('result.Path', cid.toString())\n    })\n  })\n\n  describe('/pubsub', () => {\n    describe('/state', () => {\n      const defaultOptions = {\n        signal: sinon.match.instanceOf(AbortSignal),\n        timeout: undefined\n      }\n\n      it('only accepts POST', () => {\n        return testHttpMethod('/api/v0/name/pubsub/state')\n      })\n\n      it('should return enabled state', async () => {\n        ipfs.name.pubsub.state.withArgs(defaultOptions).returns({\n          enabled: true\n        })\n\n        const res = await http({\n          method: 'POST',\n          url: '/api/v0/name/pubsub/state'\n        }, { ipfs })\n\n        expect(res).to.exist()\n        expect(res).to.have.nested.property('result.Enabled', true)\n      })\n\n      it('accepts a timeout', async () => {\n        ipfs.name.pubsub.state.withArgs({\n          ...defaultOptions,\n          timeout: 1000\n        }).returns({\n          enabled: true\n        })\n\n        const res = await http({\n          method: 'POST',\n          url: '/api/v0/name/pubsub/state?timeout=1s'\n        }, { ipfs })\n\n        expect(res).to.exist()\n        expect(res).to.have.nested.property('result.Enabled', true)\n      })\n    })\n\n    describe('/subs', () => {\n      const defaultOptions = {\n        signal: sinon.match.instanceOf(AbortSignal),\n        timeout: undefined\n      }\n\n      it('only accepts POST', () => {\n        return testHttpMethod('/api/v0/name/pubsub/subs')\n      })\n\n      it('should return subscriptions', async () => {\n        ipfs.name.pubsub.subs.withArgs(defaultOptions).returns('value')\n\n        const res = await http({\n          method: 'POST',\n          url: '/api/v0/name/pubsub/subs'\n        }, { ipfs })\n\n        expect(res).to.exist()\n        expect(res).to.have.nested.property('result.Strings', 'value')\n      })\n\n      it('accepts a timeout', async () => {\n        ipfs.name.pubsub.subs.withArgs({\n          ...defaultOptions,\n          timeout: 1000\n        }).returns('value')\n\n        const res = await http({\n          method: 'POST',\n          url: '/api/v0/name/pubsub/subs?timeout=1s'\n        }, { ipfs })\n\n        expect(res).to.exist()\n        expect(res).to.have.nested.property('result.Strings', 'value')\n      })\n    })\n\n    describe('/cancel', () => {\n      const defaultOptions = {\n        signal: sinon.match.instanceOf(AbortSignal),\n        timeout: undefined\n      }\n\n      it('only accepts POST', () => {\n        return testHttpMethod('/api/v0/name/pubsub/cancel')\n      })\n\n      it('should cancel subscription', async () => {\n        const name = 'name'\n\n        ipfs.name.pubsub.cancel.withArgs(name, defaultOptions).returns({\n          canceled: true\n        })\n\n        const res = await http({\n          method: 'POST',\n          url: `/api/v0/name/pubsub/cancel?arg=${name}`\n        }, { ipfs })\n\n        expect(res).to.exist()\n        expect(res).to.have.nested.property('result.Canceled', true)\n      })\n\n      it('accepts a timeout', async () => {\n        const name = 'name'\n\n        ipfs.name.pubsub.cancel.withArgs(name, {\n          ...defaultOptions,\n          timeout: 1000\n        }).returns({\n          canceled: true\n        })\n\n        const res = await http({\n          method: 'POST',\n          url: `/api/v0/name/pubsub/cancel?arg=${name}&timeout=1s`\n        }, { ipfs })\n\n        expect(res).to.exist()\n        expect(res).to.have.nested.property('result.Canceled', true)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/object.js",
    "content": "/* eslint max-nested-callbacks: [\"error\", 8] */\n/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport fs from 'fs'\nimport FormData from 'form-data'\nimport streamToPromise from 'stream-to-promise'\nimport { testHttpMethod } from '../utils/test-http-method.js'\nimport { http } from '../utils/http.js'\nimport sinon from 'sinon'\nimport { CID } from 'multiformats/cid'\nimport { UnixFS } from 'ipfs-unixfs'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { toString as uint8ArrayToString } from 'uint8arrays/to-string'\nimport { base58btc } from 'multiformats/bases/base58'\nimport { base64, base64pad } from 'multiformats/bases/base64'\n\ndescribe('/object', () => {\n  const cid = CID.parse('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n')\n  const cid2 = CID.parse('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1a')\n  const unixfs = new UnixFS({\n    type: 'file'\n  })\n  const fileNode = {\n    Data: unixfs.marshal(),\n    Links: [{\n      Name: '',\n      Tsize: 5,\n      Hash: cid\n    }]\n  }\n  const emptyDirectoryNode = {\n    Data: new UnixFS({\n      type: 'directory'\n    }).marshal(),\n    Links: []\n  }\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      object: {\n        new: sinon.stub(),\n        get: sinon.stub(),\n        put: sinon.stub(),\n        stat: sinon.stub(),\n        data: sinon.stub(),\n        links: sinon.stub(),\n        patch: {\n          appendData: sinon.stub(),\n          setData: sinon.stub(),\n          addLink: sinon.stub(),\n          rmLink: sinon.stub()\n        }\n      },\n      bases: {\n        getBase: sinon.stub()\n      }\n    }\n  })\n\n  describe('/new', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/object/new')\n    })\n\n    it('returns value', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.object.new.withArgs({\n        ...defaultOptions,\n        template: undefined\n      }).returns(cid)\n      ipfs.object.get.withArgs(cid, defaultOptions).returns(emptyDirectoryNode)\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/object/new'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Hash', cid.toString())\n      expect(res).to.have.nested.property('result.Links').that.is.empty()\n    })\n\n    it('should create an object with the passed template', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      const template = 'unixfs-dir'\n\n      ipfs.object.new.withArgs({\n        ...defaultOptions,\n        template\n      }).returns(cid)\n      ipfs.object.get.withArgs(cid, defaultOptions).returns(emptyDirectoryNode)\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/object/new?arg=${template}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Hash', cid.toString())\n      expect(res).to.have.nested.property('result.Links').that.is.empty()\n    })\n\n    it('should not reate an object with an invalid template', async () => {\n      const template = 'derp'\n\n      ipfs.object.new.withArgs({\n        ...defaultOptions,\n        template\n      }).returns(cid)\n      ipfs.object.get.withArgs(cid, defaultOptions).returns(emptyDirectoryNode)\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/object/new?arg=${template}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n    })\n\n    it('should create a new object and return a base64 encoded CID', async () => {\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n      ipfs.object.new.withArgs({\n        ...defaultOptions,\n        template: undefined\n      }).returns(cid.toV1())\n      ipfs.object.get.withArgs(cid.toV1(), defaultOptions).returns(emptyDirectoryNode)\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/object/new?cid-base=base64'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res.result.Hash).to.equal(cid.toV1().toString(base64))\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.object.new.withArgs({\n        ...defaultOptions,\n        template: undefined,\n        timeout: 1000\n      }).returns(cid)\n      ipfs.object.get.withArgs(cid, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns(emptyDirectoryNode)\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/object/new?timeout=1s'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Hash', cid.toString())\n      expect(res).to.have.nested.property('result.Links').that.is.empty()\n    })\n  })\n\n  describe('/get', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/object/get')\n    })\n\n    it('returns 400 for request without argument', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/object/get'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Message').that.is.a('string')\n    })\n\n    it('returns 400 for request with invalid argument', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/object/get?arg=invalid'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Code', 1)\n      expect(res).to.have.nested.property('result.Message').that.is.a('string')\n    })\n\n    it('returns value', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.object.get.withArgs(cid, defaultOptions).returns(emptyDirectoryNode)\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/object/get?arg=${cid}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Links').that.is.empty()\n      expect(res).to.have.nested.property('result.Data', uint8ArrayToString(emptyDirectoryNode.Data, 'base64pad'))\n    })\n\n    it('should get object and return a base64 encoded CID', async () => {\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n      ipfs.object.get.withArgs(cid.toV1(), defaultOptions).returns(emptyDirectoryNode)\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/object/get?cid-base=base64&arg=${cid.toV1()}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res.result.Hash).to.equal(cid.toV1().toString(base64))\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.object.get.withArgs(cid, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns(emptyDirectoryNode)\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/object/get?arg=${cid}&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Links').that.is.empty()\n      expect(res).to.have.nested.property('result.Data', uint8ArrayToString(emptyDirectoryNode.Data, 'base64pad'))\n    })\n  })\n\n  describe('/put', () => {\n    const defaultOptions = {\n      pin: false,\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/object/put')\n    })\n\n    it('returns 400 if no node is provided', async () => {\n      const form = new FormData()\n      const headers = form.getHeaders()\n\n      const payload = await streamToPromise(form)\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/object/put',\n        headers,\n        payload\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n    })\n\n    it('returns 400 if the node is invalid', async () => {\n      ipfs.object.put.withArgs(sinon.match.instanceOf(Buffer), {\n        ...defaultOptions,\n        enc: 'json'\n      }).throws(new Error('Bad node'))\n\n      const form = new FormData()\n      const filePath = 'test/fixtures/test-data/badnode.json'\n      form.append('file', fs.createReadStream(filePath))\n      const headers = form.getHeaders()\n\n      const payload = await streamToPromise(form)\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/object/put?enc=json',\n        headers,\n        payload\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n    })\n\n    it('puts value', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const pbNode = {\n        Data: Uint8Array.from(uint8ArrayFromString('another')),\n        Links: [{\n          Name: 'some link',\n          Hash: CID.parse('QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V'),\n          Tsize: 8\n        }\n        ]\n      }\n\n      ipfs.object.put.withArgs(pbNode, defaultOptions).returns(cid)\n      ipfs.object.get.withArgs(cid).resolves(pbNode)\n\n      const form = new FormData()\n      form.append('data', Buffer.from(JSON.stringify({\n        Data: Buffer.from('another').toString('base64'),\n        Links: [{\n          Name: 'some link',\n          Hash: 'QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V',\n          Size: 8\n        }\n        ]\n      })))\n      const headers = form.getHeaders()\n\n      const payload = await streamToPromise(form)\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/object/put',\n        headers,\n        payload\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.property('result', {\n        Data: Buffer.from('another').toString('base64'),\n        Hash: cid.toString(),\n        Links: [{\n          Name: 'some link',\n          Hash: 'QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V',\n          Size: 8\n        }],\n        Size: 60\n      })\n    })\n\n    it('should put data and return a base64 encoded CID', async () => {\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n\n      const pbNode = {\n        Data: Uint8Array.from(uint8ArrayFromString('another')),\n        Links: [{\n          Name: 'some link',\n          Hash: CID.parse('QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V').toV1(),\n          Tsize: 8\n        }\n        ]\n      }\n\n      ipfs.object.put.withArgs(pbNode, defaultOptions).returns(cid.toV1())\n      ipfs.object.get.withArgs(cid.toV1()).resolves(pbNode)\n\n      const form = new FormData()\n      form.append('data', Buffer.from(JSON.stringify({\n        Data: Buffer.from('another').toString('base64'),\n        Links: [{\n          Name: 'some link',\n          Hash: CID.parse('QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V').toV1().toString(),\n          Size: 8\n        }\n        ]\n      })))\n      const headers = form.getHeaders()\n\n      const payload = await streamToPromise(form)\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/object/put?cid-base=base64',\n        headers,\n        payload\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.property('result', {\n        Data: Buffer.from('another').toString('base64'),\n        Hash: cid.toV1().toString(base64),\n        Links: [{\n          Name: 'some link',\n          Hash: CID.parse('QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V').toV1().toString(base64),\n          Size: 8\n        }],\n        Size: 62\n      })\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n\n      const pbNode = {\n        Data: Uint8Array.from(uint8ArrayFromString('another')),\n        Links: [{\n          Name: 'some link',\n          Hash: CID.parse('QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V'),\n          Tsize: 8\n        }\n        ]\n      }\n\n      ipfs.object.put.withArgs(pbNode, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns(cid)\n      ipfs.object.get.withArgs(cid, {\n        signal: sinon.match.instanceOf(AbortSignal),\n        timeout: 1000\n      }).resolves(pbNode)\n\n      const form = new FormData()\n      form.append('data', Buffer.from(JSON.stringify({\n        Data: Buffer.from('another').toString('base64'),\n        Links: [{\n          Name: 'some link',\n          Hash: 'QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V',\n          Size: 8\n        }\n        ]\n      })))\n      const headers = form.getHeaders()\n\n      const payload = await streamToPromise(form)\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/object/put?timeout=1s',\n        headers,\n        payload\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.property('result', {\n        Data: Buffer.from('another').toString('base64'),\n        Hash: cid.toString(),\n        Links: [{\n          Name: 'some link',\n          Hash: 'QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V',\n          Size: 8\n        }],\n        Size: 60\n      })\n    })\n  })\n\n  describe('/stat', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/object/stat')\n    })\n\n    it('returns 400 for request without argument', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/object/stat'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Message').that.is.a('string')\n    })\n\n    it('returns 400 for request with invalid argument', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/object/stat?arg=invalid'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Code', 1)\n      expect(res).to.have.nested.property('result.Message').that.is.a('string')\n    })\n\n    it('returns value', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.object.stat.withArgs(cid, defaultOptions).returns({\n        Hash: cid,\n        NumLinks: 'NumLinks',\n        BlockSize: 'BlockSize',\n        LinksSize: 'LinksSize',\n        DataSize: 'DataSize',\n        CumulativeSize: 'CumulativeSize'\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/object/stat?arg=${cid}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Hash', cid.toString())\n      expect(res).to.have.nested.property('result.NumLinks', 'NumLinks')\n      expect(res).to.have.nested.property('result.BlockSize', 'BlockSize')\n      expect(res).to.have.nested.property('result.LinksSize', 'LinksSize')\n      expect(res).to.have.nested.property('result.DataSize', 'DataSize')\n      expect(res).to.have.nested.property('result.CumulativeSize', 'CumulativeSize')\n    })\n\n    it('should stat object and return a base64 encoded CID', async () => {\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n      ipfs.object.stat.withArgs(cid, defaultOptions).returns({\n        Hash: cid.toV1(),\n        NumLinks: 'NumLinks',\n        BlockSize: 'BlockSize',\n        LinksSize: 'LinksSize',\n        DataSize: 'DataSize',\n        CumulativeSize: 'CumulativeSize'\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/object/stat?cid-base=base64&arg=${cid}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Hash', cid.toV1().toString(base64))\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.object.stat.withArgs(cid, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns({\n        Hash: cid,\n        NumLinks: 'NumLinks',\n        BlockSize: 'BlockSize',\n        LinksSize: 'LinksSize',\n        DataSize: 'DataSize',\n        CumulativeSize: 'CumulativeSize'\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/object/stat?arg=${cid}&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Hash', cid.toString())\n      expect(res).to.have.nested.property('result.NumLinks', 'NumLinks')\n      expect(res).to.have.nested.property('result.BlockSize', 'BlockSize')\n      expect(res).to.have.nested.property('result.LinksSize', 'LinksSize')\n      expect(res).to.have.nested.property('result.DataSize', 'DataSize')\n      expect(res).to.have.nested.property('result.CumulativeSize', 'CumulativeSize')\n    })\n  })\n\n  describe('/data', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/object/data')\n    })\n\n    it('returns 400 for request without argument', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/object/data'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Message').that.is.a('string')\n    })\n\n    it('returns 400 for request with invalid argument', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/object/data?arg=invalid'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Code', 1)\n      expect(res).to.have.nested.property('result.Message').that.is.a('string')\n    })\n\n    it('returns value', async () => {\n      ipfs.object.data.withArgs(cid, defaultOptions).returns(emptyDirectoryNode.Data)\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/object/data?arg=${cid}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.property('rawPayload', emptyDirectoryNode.Data)\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.object.data.withArgs(cid, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns(emptyDirectoryNode.Data)\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/object/data?arg=${cid}&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.property('rawPayload', emptyDirectoryNode.Data)\n    })\n  })\n\n  describe('/links', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/object/links')\n    })\n\n    it('returns 400 for request without argument', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/object/links'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Message').that.is.a('string')\n    })\n\n    it('returns 400 for request with invalid argument', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/object/links?arg=invalid'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Code', 1)\n      expect(res).to.have.nested.property('result.Message').that.is.a('string')\n    })\n\n    it('returns value', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.object.links.withArgs(cid, defaultOptions).returns(fileNode.Links)\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/object/links?arg=${cid}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.property('result', {\n        Hash: cid.toString(),\n        Links: [{\n          Name: '',\n          Hash: cid.toString(),\n          Size: 5\n        }]\n      })\n    })\n\n    it('should list object links and return a base64 encoded CID', async () => {\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n      ipfs.object.links.withArgs(cid.toV1(), defaultOptions)\n        .returns(fileNode.Links.map(l => ({\n          ...l,\n          Hash: l.Hash.toV1()\n        })))\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/object/links?arg=${cid.toV1()}&cid-base=base64`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.property('result', {\n        Hash: cid.toV1().toString(base64),\n        Links: [{\n          Name: '',\n          Hash: cid.toV1().toString(base64),\n          Size: 5\n        }]\n      })\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.object.links.withArgs(cid, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns(fileNode.Links)\n\n      const expectedResult = {\n        Hash: cid.toString(),\n        Links: [{\n          Name: '',\n          Hash: cid.toString(),\n          Size: 5\n        }]\n      }\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/object/links?arg=${cid}&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.property('result', expectedResult)\n    })\n  })\n\n  describe('/patch/append-data', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/object/patch/append-data')\n    })\n\n    it('returns 400 for request without key', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/object/patch/append-data'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Message').that.is.a('string')\n    })\n\n    it('returns 400 if no data is provided', async () => {\n      const form = new FormData()\n      const headers = form.getHeaders()\n\n      const payload = await streamToPromise(form)\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/object/patch/append-data?arg=QmVLUHkjGg3duGb5w3dnwK5w2P9QWuJmtVNuDPLc9ZDjzk',\n        headers,\n        payload\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n    })\n\n    it('returns 400 for request with invalid key', async () => {\n      const form = new FormData()\n      const filePath = 'test/fixtures/test-data/badconfig'\n      form.append('file', fs.createReadStream(filePath))\n      const headers = form.getHeaders()\n\n      const payload = await streamToPromise(form)\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/object/patch/append-data?arg=invalid',\n        headers,\n        payload\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n    })\n\n    it('updates value', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      const data = Buffer.from('TEST' + Date.now())\n\n      ipfs.object.patch.appendData.withArgs(cid, data, defaultOptions).returns(cid)\n      ipfs.object.get.withArgs(cid).returns(emptyDirectoryNode)\n\n      const form = new FormData()\n      form.append('data', data)\n      const headers = form.getHeaders()\n\n      const payload = await streamToPromise(form)\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/object/patch/append-data?arg=${cid}`,\n        headers,\n        payload\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res.result).to.deep.equal({\n        Data: base64pad.encode(emptyDirectoryNode.Data).substring(1),\n        Hash: cid.toString(),\n        Links: [],\n        Size: 4\n      })\n    })\n\n    it('should append data to object and return a base64 encoded CID', async () => {\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n      const data = Buffer.from('TEST' + Date.now())\n\n      ipfs.object.patch.appendData.withArgs(cid.toV1(), data, defaultOptions).returns(cid.toV1())\n      ipfs.object.get.withArgs(cid.toV1()).returns(emptyDirectoryNode)\n\n      const form = new FormData()\n      form.append('data', data)\n      const headers = form.getHeaders()\n\n      const payload = await streamToPromise(form)\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/object/patch/append-data?arg=${cid.toV1()}&cid-base=base64`,\n        headers,\n        payload\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res.result).to.deep.equal({\n        Data: base64pad.encode(emptyDirectoryNode.Data).substring(1),\n        Hash: cid.toV1().toString(base64),\n        Links: [],\n        Size: 4\n      })\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      const data = Buffer.from('TEST' + Date.now())\n\n      ipfs.object.patch.appendData.withArgs(cid, data, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns(cid)\n      ipfs.object.get.withArgs(cid).returns(emptyDirectoryNode)\n\n      const form = new FormData()\n      form.append('data', data)\n      const headers = form.getHeaders()\n\n      const payload = await streamToPromise(form)\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/object/patch/append-data?arg=${cid}&timeout=1s`,\n        headers,\n        payload\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res.result).to.deep.equal({\n        Data: base64pad.encode(emptyDirectoryNode.Data).substring(1),\n        Hash: cid.toString(),\n        Links: [],\n        Size: 4\n      })\n    })\n  })\n\n  describe('/patch/set-data', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/object/patch/set-data')\n    })\n\n    it('returns 400 for request without key', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/object/patch/set-data'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Message').that.is.a('string')\n    })\n\n    it('returns 400 if no data is provided', async () => {\n      const form = new FormData()\n      const headers = form.getHeaders()\n\n      const payload = await streamToPromise(form)\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/object/patch/set-data?arg=QmVLUHkjGg3duGb5w3dnwK5w2P9QWuJmtVNuDPLc9ZDjzk',\n        headers,\n        payload\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n    })\n\n    it('returns 400 for request with invalid key', async () => {\n      const form = new FormData()\n      const filePath = 'test/fixtures/test-data/badconfig'\n      form.append('file', fs.createReadStream(filePath))\n      const headers = form.getHeaders()\n\n      const payload = await streamToPromise(form)\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/object/patch/set-data?arg=invalid',\n        headers,\n        payload\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n    })\n\n    it('updates value', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      const data = Buffer.from('TEST' + Date.now())\n\n      ipfs.object.patch.setData.withArgs(cid, data, defaultOptions).returns(cid)\n      ipfs.object.get.withArgs(cid).returns(emptyDirectoryNode)\n\n      const form = new FormData()\n      form.append('data', data)\n      const headers = form.getHeaders()\n\n      const payload = await streamToPromise(form)\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/object/patch/set-data?arg=${cid}`,\n        headers,\n        payload\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res.result).to.deep.equal({\n        Hash: cid.toString(),\n        Links: []\n      })\n    })\n\n    it('should set data for object and return a base64 encoded CID', async () => {\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n      const data = Buffer.from('TEST' + Date.now())\n\n      ipfs.object.patch.setData.withArgs(cid.toV1(), data, defaultOptions).returns(cid.toV1())\n      ipfs.object.get.withArgs(cid.toV1()).returns(emptyDirectoryNode)\n\n      const form = new FormData()\n      form.append('data', data)\n      const headers = form.getHeaders()\n\n      const payload = await streamToPromise(form)\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/object/patch/set-data?arg=${cid.toV1()}&cid-base=base64`,\n        headers,\n        payload\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res.result).to.deep.equal({\n        Hash: cid.toV1().toString(base64),\n        Links: []\n      })\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      const data = Buffer.from('TEST' + Date.now())\n\n      ipfs.object.patch.setData.withArgs(cid, data, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns(cid)\n      ipfs.object.get.withArgs(cid).returns(emptyDirectoryNode)\n\n      const form = new FormData()\n      form.append('data', data)\n      const headers = form.getHeaders()\n\n      const payload = await streamToPromise(form)\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/object/patch/set-data?arg=${cid}&timeout=1s`,\n        headers,\n        payload\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res.result).to.deep.equal({\n        Hash: cid.toString(),\n        Links: []\n      })\n    })\n  })\n\n  describe('/patch/add-link', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/object/patch/add-link')\n    })\n\n    it('returns 400 for request without arguments', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/object/patch/add-link'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Message').that.is.a('string')\n    })\n\n    it('returns 400 for request with only one invalid argument', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/object/patch/add-link?arg=invalid'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Message').that.is.a('string')\n    })\n\n    it('returns 400 for request with invalid first argument', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/object/patch/add-link?arg=&arg=foo&arg=QmTz3oc4gdpRMKP2sdGUPZTAGRngqjsi99BPoztyP53JMM'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Code', 1)\n      expect(res).to.have.nested.property('result.Message').that.is.a('string')\n    })\n\n    it('returns 400 for request with empty second argument', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/object/patch/add-link?arg=QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn&arg=&arg=QmTz3oc4gdpRMKP2sdGUPZTAGRngqjsi99BPoztyP53JMM'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Code', 1)\n      expect(res).to.have.nested.property('result.Message').that.is.a('string')\n    })\n\n    it('returns value', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      const name = 'name'\n\n      ipfs.object.patch.addLink.withArgs(cid, sinon.match({\n        Name: name,\n        Hash: cid2\n      }), defaultOptions).returns(cid)\n      ipfs.object.get.withArgs(cid).returns(fileNode)\n      ipfs.object.get.withArgs(cid2).returns(fileNode)\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/object/patch/add-link?arg=${cid}&arg=${name}&arg=${cid2}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Hash', cid.toString())\n      expect(res).to.have.deep.nested.property('result.Links[0]', {\n        Name: '',\n        Hash: cid.toString(),\n        Size: 5\n      })\n    })\n\n    it('should add a link to an object and return a base64 encoded CID', async () => {\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n      const name = 'name'\n\n      ipfs.object.patch.addLink.withArgs(cid.toV1(), sinon.match({\n        Name: name,\n        Hash: cid2.toV1()\n      }), defaultOptions).returns(cid.toV1())\n      ipfs.object.get.withArgs(cid.toV1()).returns({\n        ...fileNode,\n        Links: fileNode.Links.map(l => ({\n          ...l,\n          Hash: l.Hash.toV1()\n        }))\n      })\n      ipfs.object.get.withArgs(cid2.toV1()).returns({\n        ...fileNode,\n        Links: fileNode.Links.map(l => ({\n          ...l,\n          Hash: l.Hash.toV1()\n        }))\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/object/patch/add-link?arg=${cid.toV1()}&arg=${name}&arg=${cid2.toV1()}&cid-base=base64`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Hash', cid.toV1().toString(base64))\n      expect(res).to.have.deep.nested.property('result.Links[0]', {\n        Name: '',\n        Hash: cid.toV1().toString(base64),\n        Size: 5\n      })\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      const name = 'name'\n\n      ipfs.object.patch.addLink.withArgs(cid, sinon.match({\n        Name: name,\n        Hash: cid2\n      }), {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns(cid)\n      ipfs.object.get.withArgs(cid).returns(fileNode)\n      ipfs.object.get.withArgs(cid2).returns(fileNode)\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/object/patch/add-link?arg=${cid}&arg=${name}&arg=${cid2}&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Hash', cid.toString())\n      expect(res).to.have.deep.nested.property('result.Links[0]', {\n        Name: '',\n        Hash: cid.toString(),\n        Size: 5\n      })\n    })\n  })\n\n  describe('/patch/rm-link', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/object/patch/rm-link')\n    })\n\n    it('returns 400 for request without arguments', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/object/patch/rm-link'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Message').that.is.a('string')\n    })\n\n    it('returns 400 for request with only one invalid argument', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/object/patch/rm-link?arg=invalid'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Message').that.is.a('string')\n    })\n\n    it('returns 400 for request with invalid first argument', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/object/patch/rm-link?arg=invalid&arg=foo'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Code', 1)\n      expect(res).to.have.nested.property('result.Message').that.is.a('string')\n    })\n\n    it('returns 400 for request with invalid second argument', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/object/patch/rm-link?arg=QmZKetgwm4o3LhNaoLSHv32wBhTwj9FBwAdSchDMKyFQEx&arg='\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n      expect(res).to.have.nested.property('result.Code', 1)\n      expect(res).to.have.nested.property('result.Message').that.is.a('string')\n    })\n\n    it('returns value', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      const name = 'name'\n\n      ipfs.object.patch.rmLink.withArgs(cid, name, {\n        ...defaultOptions\n      }).returns(cid2)\n      ipfs.object.get.withArgs(cid2).returns(emptyDirectoryNode)\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/object/patch/rm-link?arg=${cid}&arg=${name}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Hash', cid2.toString())\n    })\n\n    it('should remove a link from an object and return a base64 encoded CID', async () => {\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n      const name = 'name'\n\n      ipfs.object.patch.rmLink.withArgs(cid.toV1(), name, {\n        ...defaultOptions\n      }).returns(cid2.toV1())\n      ipfs.object.get.withArgs(cid2.toV1()).returns(emptyDirectoryNode)\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/object/patch/rm-link?arg=${cid.toV1()}&arg=${name}&cid-base=base64`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Hash', cid2.toV1().toString(base64))\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      const name = 'name'\n\n      ipfs.object.patch.rmLink.withArgs(cid, name, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns(cid2)\n      ipfs.object.get.withArgs(cid2).returns(emptyDirectoryNode)\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/object/patch/rm-link?arg=${cid}&arg=${name}&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Hash', cid2.toString())\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/pin.js",
    "content": "/* eslint-env mocha */\n/* eslint max-nested-callbacks: [\"error\", 8] */\n\nimport { expect } from 'aegir/chai'\nimport { testHttpMethod } from '../utils/test-http-method.js'\nimport { http } from '../utils/http.js'\nimport sinon from 'sinon'\nimport { CID } from 'multiformats/cid'\nimport { allNdjson } from '../utils/all-ndjson.js'\nimport { base58btc } from 'multiformats/bases/base58'\nimport { base64 } from 'multiformats/bases/base64'\n\ndescribe('/pin', () => {\n  const cid = CID.parse('QmfGBRT6BbWJd7yUc2uYdaUZJBbnEFvTqehPFoSMQ6wgdr')\n  const cid2 = CID.parse('QmZTR5bcpQD7cFgTorqxZDYaew1Wqgfbd2ud9QqGPAkK2V')\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      pin: {\n        ls: sinon.stub(),\n        addAll: sinon.stub(),\n        rmAll: sinon.stub(),\n        query: sinon.stub()\n      },\n      bases: {\n        getBase: sinon.stub()\n      }\n    }\n  })\n\n  describe('/rm', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod(`/api/v0/pin/rm?arg=${cid}`)\n    })\n\n    it('fails on invalid args', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/pin/rm?arg=invalid'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n    })\n\n    it('unpins recursive pins', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.pin.rmAll.withArgs([{ cid, recursive: true }], defaultOptions).returns([\n        cid\n      ])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/pin/rm?arg=${cid}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Pins', [cid.toString()])\n    })\n\n    it('unpins direct pins', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.pin.rmAll.withArgs([{\n        cid,\n        recursive: false\n      }], {\n        ...defaultOptions\n      }).returns([\n        cid\n      ])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/pin/rm?arg=${cid}&recursive=false`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Pins', [cid.toString()])\n    })\n\n    it('should remove pin and return base64 encoded CID', async () => {\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n      ipfs.pin.rmAll.withArgs([{ cid: cid.toV1(), recursive: true }], defaultOptions).returns([\n        cid.toV1()\n      ])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/pin/rm?arg=${cid.toV1()}&cid-base=base64`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      res.result.Pins.forEach(c => {\n        expect(c).to.equal(cid.toV1().toString(base64))\n      })\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.pin.rmAll.withArgs([{\n        cid,\n        recursive: true\n      }], {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns([\n        cid\n      ])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/pin/rm?arg=${cid}&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Pins', [cid.toString()])\n    })\n  })\n\n  describe('/add', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod(`/api/v0/pin/add?arg=${cid}`)\n    })\n\n    it('fails on invalid args', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/pin/add?arg=invalid'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n    })\n\n    it('recursively', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.pin.addAll.withArgs([{\n        cid,\n        recursive: true,\n        metadata: undefined\n      }], defaultOptions).returns([\n        cid\n      ])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/pin/add?arg=${cid}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Pins', [cid.toString()])\n    })\n\n    it('directly', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.pin.addAll.withArgs([{\n        cid,\n        recursive: false,\n        metadata: undefined\n      }], defaultOptions).returns([\n        cid\n      ])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/pin/add?arg=${cid}&recursive=false`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Pins', [cid.toString()])\n    })\n\n    it('should add pin and return base64 encoded CID', async () => {\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n      ipfs.pin.addAll.withArgs([{\n        cid: cid.toV1(),\n        recursive: true,\n        metadata: undefined\n      }], defaultOptions).returns([\n        cid.toV1()\n      ])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/pin/add?arg=${cid.toV1()}&cid-base=base64`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      res.result.Pins.forEach(c => {\n        expect(c).to.equal(cid.toV1().toString(base64))\n      })\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.pin.addAll.withArgs([{\n        cid,\n        recursive: true,\n        metadata: undefined\n      }], {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns([\n        cid\n      ])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/pin/add?arg=${cid}&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Pins', [cid.toString()])\n    })\n  })\n\n  describe('/ls', () => {\n    const defaultOptions = {\n      type: 'all',\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined,\n      paths: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/pin/ls')\n    })\n\n    it('fails on invalid args', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/pin/ls?arg=invalid'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n    })\n\n    it('finds all pinned objects', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.pin.ls.withArgs(defaultOptions).returns([{\n        cid,\n        type: 'recursive'\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/pin/ls'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Keys').that.includes.keys(cid.toString())\n    })\n\n    it('finds all pinned objects streaming', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.pin.ls.withArgs(defaultOptions).returns([{\n        cid: cid,\n        type: 'recursive'\n      }, {\n        cid: cid2,\n        type: 'recursive'\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/pin/ls?stream=true'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(allNdjson(res)).to.deep.equal([\n        { Cid: cid.toString(), Type: 'recursive' },\n        { Cid: cid2.toString(), Type: 'recursive' }\n      ])\n    })\n\n    it('finds specific pinned objects', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.pin.ls.withArgs({\n        ...defaultOptions,\n        paths: [`${cid}`]\n      }).returns([{\n        cid,\n        type: 'recursive'\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/pin/ls?arg=${cid}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Keys').that.deep.includes({\n        [cid.toString()]: {\n          Type: 'recursive'\n        }\n      })\n    })\n\n    it('finds pins of type', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.pin.ls.withArgs({\n        ...defaultOptions,\n        type: 'direct'\n      }).returns([{\n        cid,\n        type: 'direct'\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/pin/ls?type=direct'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Keys').that.deep.includes({\n        [cid.toString()]: {\n          Type: 'direct'\n        }\n      })\n    })\n\n    it('should list pins and return base64 encoded CIDs', async () => {\n      ipfs.bases.getBase.withArgs('base64').returns(base64)\n      ipfs.pin.ls.withArgs(defaultOptions).returns([{\n        cid: cid.toV1(),\n        type: 'direct'\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/pin/ls?cid-base=base64'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.deep.property(`result.Keys.${cid.toV1().toString(base64)}`, {\n        Type: 'direct'\n      })\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)\n      ipfs.pin.ls.withArgs({\n        ...defaultOptions,\n        timeout: 1000\n      }).returns([{\n        cid,\n        type: 'recursive'\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/pin/ls?timeout=1s'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Keys').that.includes.keys(cid.toString())\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/ping.js",
    "content": "/* eslint max-nested-callbacks: [\"error\", 8] */\n/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { testHttpMethod } from '../utils/test-http-method.js'\nimport { http } from '../utils/http.js'\nimport sinon from 'sinon'\nimport { allNdjson } from '../utils/all-ndjson.js'\nimport { peerIdFromString } from '@libp2p/peer-id'\n\nconst defaultOptions = {\n  count: 10,\n  signal: sinon.match.instanceOf(AbortSignal),\n  timeout: undefined\n}\n\ndescribe('/ping', function () {\n  const peerId = peerIdFromString('QmUBdnXXPyoDFXj3Hj39dNJ5VkN3QFRskXxcGaYFBB8CNR')\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      ping: sinon.stub()\n    }\n  })\n\n  it('only accepts POST', () => {\n    return testHttpMethod('/api/v0/ping')\n  })\n\n  it('returns 400 if arg is not provided', async () => {\n    const res = await http({\n      method: 'POST',\n      url: '/api/v0/ping?count=1'\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 400)\n  })\n\n  it('returns error for incorrect Peer Id', async () => {\n    ipfs.ping.withArgs(peerId)\n      .callsFake(async function * () { // eslint-disable-line require-yield\n        throw new Error('derp')\n      })\n\n    const res = await http({\n      method: 'POST',\n      url: `/api/v0/ping?arg=${peerId.toString()}`\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 500)\n  })\n\n  it('pings with a count', async () => {\n    ipfs.ping.withArgs(peerId, {\n      ...defaultOptions,\n      count: 5\n    }).callsFake(async function * () {})\n\n    const res = await http({\n      method: 'POST',\n      url: `/api/v0/ping?arg=${peerId.toString()}&count=5`\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n  })\n\n  it('pings with a count as n', async () => {\n    ipfs.ping.withArgs(peerId, {\n      ...defaultOptions,\n      count: 5\n    }).callsFake(async function * () {})\n\n    const res = await http({\n      method: 'POST',\n      url: `/api/v0/ping?arg=${peerId.toString()}&n=5`\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n  })\n\n  it('pings a remote peer', async () => {\n    ipfs.ping.withArgs(peerId, defaultOptions)\n      .callsFake(async function * () {\n        yield {\n          success: true,\n          time: 1,\n          text: 'hello'\n        }\n        yield {\n          success: true,\n          time: 2,\n          text: 'world'\n        }\n      })\n\n    const res = await http({\n      method: 'POST',\n      url: `/api/v0/ping?arg=${peerId.toString()}`\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n    expect(allNdjson(res)).to.deep.equal([{\n      Success: true,\n      Time: 1,\n      Text: 'hello'\n    }, {\n      Success: true,\n      Time: 2,\n      Text: 'world'\n    }])\n  })\n\n  it('accepts a timeout', async () => {\n    ipfs.ping.withArgs(peerId, {\n      ...defaultOptions,\n      timeout: 1000\n    }).callsFake(async function * () {})\n\n    const res = await http({\n      method: 'POST',\n      url: `/api/v0/ping?arg=${peerId.toString()}&timeout=1s`\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/pubsub.js",
    "content": "/* eslint max-nested-callbacks: [\"error\", 8] */\n/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { testHttpMethod } from '../utils/test-http-method.js'\nimport { http } from '../utils/http.js'\nimport FormData from 'form-data'\nimport sinon from 'sinon'\nimport { randomBytes } from 'iso-random-stream'\nimport streamToPromise from 'stream-to-promise'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\nimport { base64url } from 'multiformats/bases/base64'\n\nconst sendData = async (data) => {\n  const form = new FormData()\n  form.append('data', data)\n  const headers = form.getHeaders()\n  const payload = await streamToPromise(form)\n  return {\n    headers,\n    payload\n  }\n}\nconst textToUrlSafeRpc = text => base64url.encode(uint8ArrayFromString(text))\n\ndescribe('/pubsub', () => {\n  const buf = Buffer.from('some message')\n  const topic = 'nonScents'\n  const topicRpcEnc = textToUrlSafeRpc(topic)\n  const topicNotSubscribed = 'somethingRandom'\n  const topicNotSubscribedRpcEnc = textToUrlSafeRpc(topicNotSubscribed)\n\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      pubsub: {\n        subscribe: sinon.stub(),\n        unsubscribe: sinon.stub(),\n        publish: sinon.stub(),\n        ls: sinon.stub(),\n        peers: sinon.stub()\n      }\n    }\n  })\n\n  describe('/sub', () => {\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/pubsub/sub')\n    })\n\n    it('returns 400 if no topic is provided', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/pubsub/sub'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n    })\n\n    it.skip('returns 200 with topic', async () => {\n      // need to simulate 'disconnect' events somehow in order to close\n      // the response stream and let the request promise resolve\n      // https://github.com/hapijs/shot/issues/121\n      ipfs.pubsub.unsubscribe.withArgs(topic).resolves(undefined)\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/pubsub/sub?arg=${topicRpcEnc}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(ipfs.pubsub.subscribe.calledWith(topic)).to.be.true()\n    })\n  })\n\n  describe('/pub', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/pubsub/pub')\n    })\n\n    it('returns 400 if no buffer is provided', async () => {\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/pubsub/pub?arg=&arg='\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n    })\n\n    it('returns 200 with topic and buffer', async () => {\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/pubsub/pub?arg=${topicRpcEnc}`,\n        ...await sendData(buf)\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(ipfs.pubsub.publish.calledWith(topic, buf, defaultOptions)).to.be.true()\n    })\n\n    it('returns 200 with topic and unprintable buffer', async () => {\n      const buf = randomBytes(10)\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/pubsub/pub?arg=${topicRpcEnc}`,\n        ...await sendData(buf)\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(ipfs.pubsub.publish.calledWith(topic, buf, defaultOptions)).to.be.true()\n    })\n\n    it('returns 400 with topic and empty buffer', async () => {\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/pubsub/pub?arg=${topicRpcEnc}`,\n        ...await sendData(Buffer.from(''))\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 400)\n    })\n\n    it('accepts a timeout', async () => {\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/pubsub/pub?arg=${topicRpcEnc}&timeout=1s`,\n        ...await sendData(buf)\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(ipfs.pubsub.publish.calledWith(topic, buf, {\n        ...defaultOptions,\n        timeout: 1000\n      })).to.be.true()\n    })\n  })\n\n  describe('/ls', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/pubsub/ls')\n    })\n\n    it('returns 200', async () => {\n      ipfs.pubsub.ls.withArgs(defaultOptions).returns([\n        topic\n      ])\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/pubsub/ls'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Strings', [topicRpcEnc])\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.pubsub.ls.withArgs({\n        ...defaultOptions,\n        timeout: 1000\n      }).returns([\n        topic\n      ])\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/pubsub/ls?timeout=1s'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Strings', [topicRpcEnc])\n    })\n  })\n\n  describe('/peers', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/pubsub/peers')\n    })\n\n    it('returns 200 if not subscribed to a topic', async () => {\n      ipfs.pubsub.peers.withArgs(topicNotSubscribed, defaultOptions).returns([])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/pubsub/peers?arg=${topicNotSubscribedRpcEnc}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Strings', [])\n    })\n\n    it('returns 200 with topic', async () => {\n      ipfs.pubsub.peers.withArgs(topic, defaultOptions).returns([\n        'peer'\n      ])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/pubsub/peers?arg=${topicRpcEnc}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Strings', [\n        'peer'\n      ])\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.pubsub.peers.withArgs(topic, {\n        ...defaultOptions,\n        timeout: 1000\n      }).returns([\n        'peer'\n      ])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/pubsub/peers?arg=${topicRpcEnc}&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.deep.nested.property('result.Strings', [\n        'peer'\n      ])\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/repo.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { testHttpMethod } from '../utils/test-http-method.js'\nimport { http } from '../utils/http.js'\nimport sinon from 'sinon'\nimport { CID } from 'multiformats/cid'\nimport { allNdjson } from '../utils/all-ndjson.js'\n\ndescribe('/repo', () => {\n  const cid = CID.parse('QmfGBRT6BbWJd7yUc2uYdaUZJBbnEFvTqehPFoSMQ6wgdr')\n  const cid2 = CID.parse('QmfGBRT6BbWJd7yUc2uYdaUZJBbnEFvTqehPFoSMQ6wgda')\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      repo: {\n        gc: sinon.stub(),\n        version: sinon.stub(),\n        stat: sinon.stub()\n      }\n    }\n  })\n\n  describe('/gc', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/repo/gc')\n    })\n\n    it('should run gc', async () => {\n      ipfs.repo.gc.withArgs(defaultOptions).returns([{\n        cid: cid\n      }, {\n        cid: cid2\n      }, {\n        err: new Error('Derp')\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/repo/gc'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(allNdjson(res)).to.deep.equal([\n        { Key: { '/': cid.toString() } },\n        { Key: { '/': cid2.toString() } }\n      ])\n    })\n\n    it('should run gc and stream errors', async () => {\n      ipfs.repo.gc.withArgs(defaultOptions).returns([{\n        cid: cid\n      }, {\n        cid: cid2\n      }, {\n        err: new Error('Derp')\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/repo/gc?stream-errors=true'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(allNdjson(res)).to.deep.equal([\n        { Key: { '/': cid.toString() } },\n        { Key: { '/': cid2.toString() } },\n        { Error: 'Derp' }\n      ])\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.repo.gc.withArgs({\n        ...defaultOptions,\n        timeout: 1000\n      }).returns([{\n        cid: cid\n      }, {\n        cid: cid2\n      }, {\n        err: new Error('Derp')\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/repo/gc?timeout=1s'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(allNdjson(res)).to.deep.equal([\n        { Key: { '/': cid.toString() } },\n        { Key: { '/': cid2.toString() } }\n      ])\n    })\n  })\n\n  describe('/version', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/repo/version')\n    })\n\n    it('returns the version', async () => {\n      ipfs.repo.version.withArgs(defaultOptions).returns(5)\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/repo/version'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Version', 5)\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.repo.version.withArgs({\n        ...defaultOptions,\n        timeout: 1000\n      }).returns(5)\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/repo/version?timeout=1s'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Version', 5)\n    })\n  })\n\n  describe('/stat', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/repo/stat')\n    })\n\n    it('returns repo stats', async () => {\n      ipfs.repo.stat.withArgs(defaultOptions).returns({\n        numObjects: 'numObjects',\n        repoSize: 'repoSize',\n        repoPath: 'repoPath',\n        version: 'version',\n        storageMax: 'storageMax'\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/repo/stat'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.NumObjects', 'numObjects')\n      expect(res).to.have.nested.property('result.RepoSize', 'repoSize')\n      expect(res).to.have.nested.property('result.RepoPath', 'repoPath')\n      expect(res).to.have.nested.property('result.Version', 'version')\n      expect(res).to.have.nested.property('result.StorageMax', 'storageMax')\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.repo.stat.withArgs({\n        ...defaultOptions,\n        timeout: 1000\n      }).returns({\n        numObjects: 'numObjects',\n        repoSize: 'repoSize',\n        repoPath: 'repoPath',\n        version: 'version',\n        storageMax: 'storageMax'\n      })\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/repo/stat?timeout=1s'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.NumObjects', 'numObjects')\n      expect(res).to.have.nested.property('result.RepoSize', 'repoSize')\n      expect(res).to.have.nested.property('result.RepoPath', 'repoPath')\n      expect(res).to.have.nested.property('result.Version', 'version')\n      expect(res).to.have.nested.property('result.StorageMax', 'storageMax')\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/resolve.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { testHttpMethod } from '../utils/test-http-method.js'\nimport { http } from '../utils/http.js'\nimport sinon from 'sinon'\nimport { CID } from 'multiformats/cid'\n\nconst defaultOptions = {\n  recursive: true,\n  cidBase: 'base58btc',\n  signal: sinon.match.instanceOf(AbortSignal),\n  timeout: undefined\n}\n\ndescribe('/resolve', () => {\n  const cid = CID.parse('QmfGBRT6BbWJd7yUc2uYdaUZJBbnEFvTqehPFoSMQ6wgdr')\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      resolve: sinon.stub()\n    }\n  })\n\n  it('only accepts POST', () => {\n    return testHttpMethod('/api/v0/resolve')\n  })\n\n  it('resolves a name', async () => {\n    const result = 'result'\n    ipfs.resolve.withArgs(cid.toString(), defaultOptions).returns(result)\n\n    const res = await http({\n      method: 'POST',\n      url: `/api/v0/resolve?arg=${cid}`\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n    expect(res).to.have.nested.property('result.Path', result)\n  })\n\n  it('resolves an ipns name', async () => {\n    const result = 'result'\n    ipfs.resolve.withArgs(`/ipns/${cid}`, defaultOptions).returns(result)\n\n    const res = await http({\n      method: 'POST',\n      url: `/api/v0/resolve?arg=/ipns/${cid}`\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n    expect(res).to.have.nested.property('result.Path', result)\n  })\n\n  it('resolves non-recursively', async () => {\n    const result = 'result'\n    ipfs.resolve.withArgs(cid.toString(), {\n      ...defaultOptions,\n      recursive: false\n    }).returns(result)\n\n    const res = await http({\n      method: 'POST',\n      url: `/api/v0/resolve?arg=${cid}&recursive=false`\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n    expect(res).to.have.nested.property('result.Path', result)\n  })\n\n  it('specifies a cid-base', async () => {\n    const result = 'result'\n    ipfs.resolve.withArgs(cid.toString(), {\n      ...defaultOptions,\n      cidBase: 'base64'\n    }).returns(result)\n\n    const res = await http({\n      method: 'POST',\n      url: `/api/v0/resolve?arg=${cid}&cid-base=base64`\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n    expect(res).to.have.nested.property('result.Path', result)\n  })\n\n  it('accepts a timeout', async () => {\n    const result = 'result'\n    ipfs.resolve.withArgs(cid.toString(), {\n      ...defaultOptions,\n      timeout: 1000\n    }).returns(result)\n\n    const res = await http({\n      method: 'POST',\n      url: `/api/v0/resolve?arg=${cid}&timeout=1s`\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 200)\n    expect(res).to.have.nested.property('result.Path', result)\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/shutdown.js",
    "content": "/* eslint-env mocha */\n\nimport { testHttpMethod } from '../utils/test-http-method.js'\nimport { http } from '../utils/http.js'\n\ndescribe('/shutdown', () => {\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {}\n  })\n\n  it('only accepts POST', () => {\n    return testHttpMethod('/api/v0/shutdown')\n  })\n\n  it('should shut down', (done) => {\n    const listener = () => {\n      done()\n\n      process.removeListener('SIGTERM', listener)\n    }\n\n    process.on('SIGTERM', listener)\n\n    http({\n      method: 'POST',\n      url: '/api/v0/shutdown'\n    }, { ipfs })\n      .then(() => {}, err => done(err))\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/stats.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { testHttpMethod } from '../utils/test-http-method.js'\nimport { http } from '../utils/http.js'\nimport sinon from 'sinon'\nimport { allNdjson } from '../utils/all-ndjson.js'\nimport { peerIdFromString } from '@libp2p/peer-id'\n\ndescribe('/stats', () => {\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      stats: {\n        bw: sinon.stub()\n      }\n    }\n  })\n\n  describe('/bw', () => {\n    const defaultOptions = {\n      peer: undefined,\n      proto: undefined,\n      poll: false,\n      interval: '1s',\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/stats/bw')\n    })\n\n    it('should return bandwith stats', async () => {\n      ipfs.stats.bw.withArgs(defaultOptions).returns([{\n        totalIn: 'totalIn1',\n        totalOut: 'totalOut1',\n        rateIn: 'rateIn1',\n        rateOut: 'rateOut1'\n      }, {\n        totalIn: 'totalIn2',\n        totalOut: 'totalOut2',\n        rateIn: 'rateIn2',\n        rateOut: 'rateOut2'\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/stats/bw'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(allNdjson(res)).to.deep.equal([{\n        TotalIn: 'totalIn1',\n        TotalOut: 'totalOut1',\n        RateIn: 'rateIn1',\n        RateOut: 'rateOut1'\n      }, {\n        TotalIn: 'totalIn2',\n        TotalOut: 'totalOut2',\n        RateIn: 'rateIn2',\n        RateOut: 'rateOut2'\n      }])\n    })\n\n    it('should return bandwith stats for a peer', async () => {\n      const peer = peerIdFromString('QmfGBRT6BbWJd7yUc2uYdaUZJBbnEFvTqehPFoSMQ6wgdr')\n\n      ipfs.stats.bw.withArgs({\n        ...defaultOptions,\n        peer: peer\n      }).returns([{\n        totalIn: 'totalIn1',\n        totalOut: 'totalOut1',\n        rateIn: 'rateIn1',\n        rateOut: 'rateOut1'\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/stats/bw?peer=${peer.toString()}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(allNdjson(res)).to.deep.equal([{\n        TotalIn: 'totalIn1',\n        TotalOut: 'totalOut1',\n        RateIn: 'rateIn1',\n        RateOut: 'rateOut1'\n      }])\n    })\n\n    it('should return bandwith stats for a protocol', async () => {\n      const proto = 'proto/v1.1'\n\n      ipfs.stats.bw.withArgs({\n        ...defaultOptions,\n        proto\n      }).returns([{\n        totalIn: 'totalIn1',\n        totalOut: 'totalOut1',\n        rateIn: 'rateIn1',\n        rateOut: 'rateOut1'\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/stats/bw?proto=${proto}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(allNdjson(res)).to.deep.equal([{\n        TotalIn: 'totalIn1',\n        TotalOut: 'totalOut1',\n        RateIn: 'rateIn1',\n        RateOut: 'rateOut1'\n      }])\n    })\n\n    it('should poll for bandwith stats', async () => {\n      const poll = true\n\n      ipfs.stats.bw.withArgs({\n        ...defaultOptions,\n        poll\n      }).returns([{\n        totalIn: 'totalIn1',\n        totalOut: 'totalOut1',\n        rateIn: 'rateIn1',\n        rateOut: 'rateOut1'\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/stats/bw?poll=${poll}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(allNdjson(res)).to.deep.equal([{\n        TotalIn: 'totalIn1',\n        TotalOut: 'totalOut1',\n        RateIn: 'rateIn1',\n        RateOut: 'rateOut1'\n      }])\n    })\n\n    it('should set an interval for bandwidth stats', async () => {\n      const interval = '5s'\n\n      ipfs.stats.bw.withArgs({\n        ...defaultOptions,\n        interval\n      }).returns([{\n        totalIn: 'totalIn1',\n        totalOut: 'totalOut1',\n        rateIn: 'rateIn1',\n        rateOut: 'rateOut1'\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/stats/bw?interval=${interval}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(allNdjson(res)).to.deep.equal([{\n        TotalIn: 'totalIn1',\n        TotalOut: 'totalOut1',\n        RateIn: 'rateIn1',\n        RateOut: 'rateOut1'\n      }])\n    })\n\n    it('should accept a timeout', async () => {\n      ipfs.stats.bw.withArgs({\n        ...defaultOptions,\n        timeout: 1000\n      }).returns([{\n        totalIn: 'totalIn1',\n        totalOut: 'totalOut1',\n        rateIn: 'rateIn1',\n        rateOut: 'rateOut1'\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/stats/bw?timeout=1s'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(allNdjson(res)).to.deep.equal([{\n        TotalIn: 'totalIn1',\n        TotalOut: 'totalOut1',\n        RateIn: 'rateIn1',\n        RateOut: 'rateOut1'\n      }])\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/swarm.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { testHttpMethod } from '../utils/test-http-method.js'\nimport { http } from '../utils/http.js'\nimport sinon from 'sinon'\n\ndescribe('/swarm', () => {\n  const multiaddr = '/ip4/127.0.0.1/tcp/4002/p2p/QmfGBRT6BbWJd7yUc2uYdaUZJBbnEFvTqehPFoSMQ6wgdr'\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      swarm: {\n        peers: sinon.stub(),\n        addrs: sinon.stub(),\n        localAddrs: sinon.stub(),\n        connect: sinon.stub(),\n        disconnect: sinon.stub()\n      }\n    }\n  })\n\n  describe('/peers', () => {\n    const defaultOptions = {\n      verbose: false,\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/swarm/peers')\n    })\n\n    it('should return peers', async () => {\n      ipfs.swarm.peers.withArgs(defaultOptions).returns([{\n        peer: 'peerId',\n        addr: 'addr',\n        direction: 'direction',\n        muxer: 'muxer',\n        latency: 'latency'\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/swarm/peers'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Peers').with.lengthOf(1).that.deep.includes({\n        Peer: 'peerId',\n        Addr: 'addr',\n        Direction: undefined,\n        Muxer: undefined,\n        Latency: undefined\n      })\n    })\n\n    it('should return verbose peers', async () => {\n      ipfs.swarm.peers.withArgs({\n        ...defaultOptions,\n        verbose: true\n      }).returns([{\n        peer: 'peerId',\n        addr: 'addr',\n        direction: 'direction',\n        muxer: 'muxer',\n        latency: 'latency'\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/swarm/peers?verbose=true'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Peers').with.lengthOf(1).that.deep.includes({\n        Peer: 'peerId',\n        Addr: 'addr',\n        Direction: 'direction',\n        Muxer: 'muxer',\n        Latency: 'latency'\n      })\n    })\n\n    it('should return verbose peers (short option)', async () => {\n      ipfs.swarm.peers.withArgs({\n        ...defaultOptions,\n        verbose: true\n      }).returns([{\n        peer: 'peerId',\n        addr: 'addr',\n        direction: 'direction',\n        muxer: 'muxer',\n        latency: 'latency'\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/swarm/peers?v=true'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Peers').with.lengthOf(1).that.deep.includes({\n        Peer: 'peerId',\n        Addr: 'addr',\n        Direction: 'direction',\n        Muxer: 'muxer',\n        Latency: 'latency'\n      })\n    })\n\n    it('should return peers with direction', async () => {\n      ipfs.swarm.peers.withArgs(defaultOptions).returns([{\n        peer: 'peerId',\n        addr: 'addr',\n        direction: 'direction',\n        muxer: 'muxer',\n        latency: 'latency'\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/swarm/peers?direction=true'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Peers').with.lengthOf(1).that.deep.includes({\n        Peer: 'peerId',\n        Addr: 'addr',\n        Direction: 'direction',\n        Muxer: undefined,\n        Latency: undefined\n      })\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.swarm.peers.withArgs({\n        ...defaultOptions,\n        timeout: 1000\n      }).returns([{\n        peer: 'peerId',\n        addr: 'addr',\n        direction: 'direction',\n        muxer: 'muxer',\n        latency: 'latency'\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/swarm/peers?timeout=1s'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Peers').with.lengthOf(1).that.deep.includes({\n        Peer: 'peerId',\n        Addr: 'addr',\n        Direction: undefined,\n        Muxer: undefined,\n        Latency: undefined\n      })\n    })\n  })\n\n  describe('/addrs', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/swarm/addrs')\n    })\n\n    it('should return addresses', async () => {\n      ipfs.swarm.addrs.withArgs(defaultOptions).returns([{\n        id: 'peerId',\n        addrs: [\n          'addr'\n        ]\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/swarm/addrs'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Addrs').that.deep.equal({\n        peerId: [\n          'addr'\n        ]\n      })\n    })\n\n    it('accepts a timeout', async () => {\n      ipfs.swarm.addrs.withArgs({\n        ...defaultOptions,\n        timeout: 1000\n      }).returns([{\n        id: 'peerId',\n        addrs: [\n          'addr'\n        ]\n      }])\n\n      const res = await http({\n        method: 'POST',\n        url: '/api/v0/swarm/addrs?timeout=1s'\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Addrs').that.deep.equal({\n        peerId: [\n          'addr'\n        ]\n      })\n    })\n\n    describe('/local', () => {\n      const defaultOptions = {\n        signal: sinon.match.instanceOf(AbortSignal),\n        timeout: undefined\n      }\n\n      it('only accepts POST', () => {\n        return testHttpMethod('/api/v0/swarm/addrs/local')\n      })\n\n      it('should return local addresses', async () => {\n        ipfs.swarm.localAddrs.withArgs(defaultOptions).returns([\n          'addr'\n        ])\n\n        const res = await http({\n          method: 'POST',\n          url: '/api/v0/swarm/addrs/local'\n        }, { ipfs })\n\n        expect(res).to.have.property('statusCode', 200)\n        expect(res).to.have.nested.property('result.Strings').with.lengthOf(1).that.includes('addr')\n      })\n\n      it('accepts a timeout', async () => {\n        ipfs.swarm.localAddrs.withArgs({\n          ...defaultOptions,\n          timeout: 1000\n        }).returns([\n          'addr'\n        ])\n\n        const res = await http({\n          method: 'POST',\n          url: '/api/v0/swarm/addrs/local?timeout=1s'\n        }, { ipfs })\n\n        expect(res).to.have.property('statusCode', 200)\n        expect(res).to.have.nested.property('result.Strings').with.lengthOf(1).that.includes('addr')\n      })\n    })\n  })\n\n  describe('/connect', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/swarm/connect')\n    })\n\n    it('should connect', async () => {\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/swarm/connect?arg=${multiaddr}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Strings').with.lengthOf(1).that.includes(`connect ${multiaddr} success`)\n      expect(ipfs.swarm.connect.calledWith(sinon.match(ma => ma.toString() === multiaddr), defaultOptions)).to.be.true()\n    })\n\n    it('should accept timeout', async () => {\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/swarm/connect?arg=${multiaddr}&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Strings').with.lengthOf(1).that.includes(`connect ${multiaddr} success`)\n      expect(ipfs.swarm.connect.calledWith(sinon.match(ma => ma.toString() === multiaddr), {\n        ...defaultOptions,\n        timeout: 1000\n      })).to.be.true()\n    })\n  })\n\n  describe('/disconnect', () => {\n    const defaultOptions = {\n      signal: sinon.match.instanceOf(AbortSignal),\n      timeout: undefined\n    }\n\n    it('only accepts POST', () => {\n      return testHttpMethod('/api/v0/swarm/disconnect')\n    })\n\n    it('should disconnect', async () => {\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/swarm/disconnect?arg=${multiaddr}`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Strings').with.lengthOf(1).that.includes(`disconnect ${multiaddr} success`)\n      expect(ipfs.swarm.disconnect.calledWith(sinon.match(ma => ma.toString() === multiaddr), defaultOptions)).to.be.true()\n    })\n\n    it('should accept timeout', async () => {\n      const res = await http({\n        method: 'POST',\n        url: `/api/v0/swarm/disconnect?arg=${multiaddr}&timeout=1s`\n      }, { ipfs })\n\n      expect(res).to.have.property('statusCode', 200)\n      expect(res).to.have.nested.property('result.Strings').with.lengthOf(1).that.includes(`disconnect ${multiaddr} success`)\n      expect(ipfs.swarm.disconnect.calledWith(sinon.match(ma => ma.toString() === multiaddr), {\n        ...defaultOptions,\n        timeout: 1000\n      })).to.be.true()\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/inject/version.js",
    "content": "/* eslint-env mocha */\n\nimport { expect } from 'aegir/chai'\nimport { testHttpMethod } from '../utils/test-http-method.js'\nimport { http } from '../utils/http.js'\nimport sinon from 'sinon'\n\nconst defaultOptions = {\n  signal: sinon.match.instanceOf(AbortSignal),\n  timeout: undefined\n}\n\ndescribe('/version', () => {\n  let ipfs\n\n  beforeEach(() => {\n    ipfs = {\n      version: sinon.stub()\n    }\n  })\n\n  it('only accepts POST', () => {\n    return testHttpMethod('/api/v0/version')\n  })\n\n  it('get the version', async () => {\n    ipfs.version.withArgs(defaultOptions).returns({\n      version: 'version',\n      commit: 'commit',\n      repo: 'repo'\n    })\n\n    const res = await http({\n      method: 'POST',\n      url: '/api/v0/version'\n    }, { ipfs })\n\n    expect(res).to.have.nested.property('result.Version', 'version')\n    expect(res).to.have.nested.property('result.Commit', 'commit')\n    expect(res).to.have.nested.property('result.Repo', 'repo')\n  })\n\n  it('accepts a timeout', async () => {\n    ipfs.version.withArgs({\n      ...defaultOptions,\n      timeout: 1000\n    }).returns({\n      version: 'version',\n      commit: 'commit',\n      repo: 'repo'\n    })\n\n    const res = await http({\n      method: 'POST',\n      url: '/api/v0/version?timeout=1s'\n    }, { ipfs })\n\n    expect(res).to.have.nested.property('result.Version', 'version')\n    expect(res).to.have.nested.property('result.Commit', 'commit')\n    expect(res).to.have.nested.property('result.Repo', 'repo')\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-http-server/test/node.js",
    "content": "import './routes.js'\nimport './cors.js'\n"
  },
  {
    "path": "packages/ipfs-http-server/test/routes.js",
    "content": "/* eslint-env mocha */\n\nimport './inject/bitswap.js'\nimport './inject/block.js'\nimport './inject/bootstrap.js'\nimport './inject/browser-headers.js'\nimport './inject/config.js'\nimport './inject/dag.js'\nimport './inject/dht.js'\nimport './inject/dns.js'\nimport './inject/files.js'\nimport './inject/id.js'\nimport './inject/key.js'\nimport './inject/mfs/chmod.js'\nimport './inject/mfs/cp.js'\nimport './inject/mfs/flush.js'\nimport './inject/mfs/ls.js'\nimport './inject/mfs/mkdir.js'\nimport './inject/mfs/mv.js'\nimport './inject/mfs/read.js'\nimport './inject/mfs/rm.js'\nimport './inject/mfs/stat.js'\nimport './inject/mfs/touch.js'\nimport './inject/mfs/write.js'\nimport './inject/name.js'\nimport './inject/object.js'\nimport './inject/pin.js'\nimport './inject/ping.js'\nimport './inject/pubsub.js'\nimport './inject/repo.js'\nimport './inject/resolve.js'\nimport './inject/shutdown.js'\nimport './inject/stats.js'\nimport './inject/swarm.js'\nimport './inject/version.js'\n"
  },
  {
    "path": "packages/ipfs-http-server/test/utils/all-ndjson.js",
    "content": "\nexport function allNdjson (res) {\n  return res.result\n    .split('\\n')\n    .map(line => line.trim())\n    .filter(line => Boolean(line))\n    .map(line => JSON.parse(line))\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/test/utils/http.js",
    "content": "import { HttpApi } from '../../src/index.js'\n\nexport async function http (request, { ipfs, cors } = {}) {\n  const api = new HttpApi(ipfs)\n  const server = await api._createApiServer('127.0.0.1', 8080, ipfs, cors)\n\n  return server.inject(request)\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/test/utils/match-iterable.js",
    "content": "\nimport sinon from 'sinon'\n\nexport function matchIterable () {\n  return sinon.match((thing) => Boolean(thing[Symbol.asyncIterator]) || Boolean(thing[Symbol.iterator]))\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/test/utils/test-http-method.js",
    "content": "\nimport { expect } from 'aegir/chai'\nimport { http } from './http.js'\n\nconst METHODS = [\n  'GET',\n  'PUT',\n  'PATCH',\n  'DELETE',\n  'HEAD'\n]\n\nexport async function testHttpMethod (url, ipfs) {\n  for (let i = 0; i < METHODS.length; i++) {\n    const res = await http({\n      method: METHODS[i],\n      url\n    }, { ipfs })\n\n    expect(res).to.have.property('statusCode', 405)\n    expect(res).to.have.nested.property('headers.allow', 'OPTIONS, POST')\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-http-server/tsconfig.json",
    "content": "{\n  \"extends\": \"aegir/src/config/tsconfig.aegir.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"emitDeclarationOnly\": true\n  },\n  \"include\": [\n    \"src\",\n    \"package.json\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../ipfs-core-types\"\n    },\n    {\n      \"path\": \"../ipfs-core-utils\"\n    },\n    {\n      \"path\": \"../ipfs-http-client\"\n    },\n    {\n      \"path\": \"../ipfs-http-gateway\"\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-client/.aegir.js",
    "content": "import path from 'path'\nimport esbuild from 'esbuild'\nimport EchoServer from 'aegir/echo-server'\nimport { fileURLToPath } from 'url'\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url))\n\n/** @type {import('aegir').Options[\"build\"][\"config\"]} */\nconst buildConfig = {\n  inject: [path.join(__dirname, '../../scripts/node-globals.js')]\n}\n\n/** @type {import('aegir').PartialOptions} */\nexport default {\n  build: {\n    bundlesizeMax: '32KB',\n    config: buildConfig\n  },\n  test: {\n    browser: {\n      config: {\n        assets: '..',\n        buildConfig\n      }\n    },\n    async before () {\n      await buildWorker()\n      const echoServer = new EchoServer()\n      await echoServer.start()\n\n      return {\n        echoServer,\n        env: {\n          IPFS_WORKER_URL: '/ipfs-message-port-client/dist/worker.bundle.js',\n          ECHO_SERVER: `http://${echoServer.host}:${echoServer.port}`\n        }\n      }\n    },\n    async after (options, before) {\n      await before.echoServer.stop()\n    }\n  }\n}\n\nconst buildWorker = async () => {\n  await esbuild.build(\n    {\n      entryPoints: [path.join(__dirname, 'test/util/worker.js')],\n      bundle: true,\n      mainFields: ['browser', 'module', 'main'],\n      sourcemap: 'inline',\n      outfile: path.join(__dirname, 'dist/worker.bundle.js'),\n      define: {\n        global: 'globalThis',\n        'process.env.NODE_ENV': '\"production\"'\n      },\n      ...buildConfig\n    }\n  )\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-client/CHANGELOG.md",
    "content": "# Change Log\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n### [0.15.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-client-v0.15.0...ipfs-message-port-client-v0.15.1) (2023-05-25)\n\n\n### Bug Fixes\n\n* add deprecation notice to readmes ([#4362](https://www.github.com/ipfs/js-ipfs/issues/4362)) ([7b79c1b](https://www.github.com/ipfs/js-ipfs/commit/7b79c1b8df5c818dc124b346ea28330455732d5c))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.14.0 to ^0.14.1\n    * ipfs-message-port-protocol bumped from ^0.15.0 to ^0.15.1\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.158.0 to ^0.158.1\n    * ipfs-core bumped from ^0.18.0 to ^0.18.1\n    * ipfs-message-port-server bumped from ^0.15.0 to ^0.15.1\n\n## [0.15.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-client-v0.14.0...ipfs-message-port-client-v0.15.0) (2023-01-11)\n\n\n### ⚠ BREAKING CHANGES\n\n* update multiformats to v11.x.x and related depenendcies (#4277)\n\n### Bug Fixes\n\n* update multiformats to v11.x.x and related depenendcies ([#4277](https://www.github.com/ipfs/js-ipfs/issues/4277)) ([521c84a](https://www.github.com/ipfs/js-ipfs/commit/521c84a958b04d61702577a5adce28519c1b2a3b))\n* use aegir to publish RCs ([#4284](https://www.github.com/ipfs/js-ipfs/issues/4284)) ([6d90cbf](https://www.github.com/ipfs/js-ipfs/commit/6d90cbf321a7dbf4b1084ba20f0c514dc08d8d0a))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.13.0 to ^0.14.0\n    * ipfs-message-port-protocol bumped from ^0.14.0 to ^0.15.0\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.157.0 to ^0.158.0\n    * ipfs-core bumped from ^0.17.0 to ^0.18.0\n    * ipfs-message-port-server bumped from ^0.14.0 to ^0.15.0\n\n## [0.14.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-client-v0.13.1...ipfs-message-port-client-v0.14.0) (2022-10-24)\n\n\n### ⚠ BREAKING CHANGES\n\n* ipfs is now bundled with libp2p@0.40.x which has different config\n\n### Features\n\n* upgrade libp2p to 0.40.x ([#4237](https://www.github.com/ipfs/js-ipfs/issues/4237)) ([0cee4a4](https://www.github.com/ipfs/js-ipfs/commit/0cee4a4c55767022584dcbade0b0b9b43326f9c9))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.12.1 to ^0.13.0\n    * ipfs-message-port-protocol bumped from ^0.13.1 to ^0.14.0\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.156.1 to ^0.157.0\n    * ipfs-core bumped from ^0.16.1 to ^0.17.0\n    * ipfs-message-port-server bumped from ^0.13.1 to ^0.14.0\n\n### [0.13.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-client-v0.13.0...ipfs-message-port-client-v0.13.1) (2022-09-21)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.12.0 to ^0.12.1\n    * ipfs-message-port-protocol bumped from ^0.13.0 to ^0.13.1\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.156.0 to ^0.156.1\n    * ipfs-core bumped from ^0.16.0 to ^0.16.1\n    * ipfs-message-port-server bumped from ^0.13.0 to ^0.13.1\n\n## [0.13.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-client-v0.12.4...ipfs-message-port-client-v0.13.0) (2022-09-06)\n\n\n### ⚠ BREAKING CHANGES\n\n* update to libp2p@0.38.x (#4151)\n\n### Bug Fixes\n\n* query input types ([#4201](https://www.github.com/ipfs/js-ipfs/issues/4201)) ([83f9882](https://www.github.com/ipfs/js-ipfs/commit/83f9882eb6df25c5ce83f447a387e068ea917c0c))\n\n\n### deps\n\n* update to libp2p@0.38.x ([#4151](https://www.github.com/ipfs/js-ipfs/issues/4151)) ([39dbf70](https://www.github.com/ipfs/js-ipfs/commit/39dbf708ec31b263115e44f420651fa4e056a89e))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.11.0 to ^0.12.0\n    * ipfs-message-port-protocol bumped from ^0.12.0 to ^0.13.0\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.155.0 to ^0.156.0\n    * ipfs-core bumped from ^0.15.0 to ^0.16.0\n    * ipfs-message-port-server bumped from ^0.12.0 to ^0.13.0\n\n### [0.12.4](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-client-v0.12.3...ipfs-message-port-client-v0.12.4) (2022-06-24)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.155.1 to ^0.155.2\n    * ipfs-core bumped from ^0.15.3 to ^0.15.4\n\n### [0.12.3](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-client-v0.12.2...ipfs-message-port-client-v0.12.3) (2022-06-22)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.11.0 to ^0.11.1\n    * ipfs-message-port-protocol bumped from ^0.12.0 to ^0.12.1\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.155.0 to ^0.155.1\n    * ipfs-core bumped from ^0.15.2 to ^0.15.3\n    * ipfs-message-port-server bumped from ^0.12.0 to ^0.12.1\n\n### [0.12.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-client-v0.12.1...ipfs-message-port-client-v0.12.2) (2022-06-13)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * ipfs-core bumped from ^0.15.1 to ^0.15.2\n\n### [0.12.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-client-v0.12.0...ipfs-message-port-client-v0.12.1) (2022-06-01)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * devDependencies\n    * ipfs-core bumped from ^0.15.0 to ^0.15.1\n\n## [0.12.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-client-v0.11.3...ipfs-message-port-client-v0.12.0) (2022-05-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* This module is now ESM only and there return types of some methods have changed\n\n### Features\n\n* update to libp2p 0.37.x ([#4092](https://www.github.com/ipfs/js-ipfs/issues/4092)) ([74aee8b](https://www.github.com/ipfs/js-ipfs/commit/74aee8b3d78f233c3199a3e9a6c0ac628a31a433))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.3 to ^0.11.0\n    * ipfs-message-port-protocol bumped from ^0.11.3 to ^0.12.0\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.154.2 to ^0.155.0\n    * ipfs-core bumped from ^0.14.3 to ^0.15.0\n    * ipfs-message-port-server bumped from ^0.11.2 to ^0.12.0\n\n### [0.11.3](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-client-v0.11.2...ipfs-message-port-client-v0.11.3) (2022-04-20)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.2 to ^0.10.3\n    * ipfs-message-port-protocol bumped from ^0.11.2 to ^0.11.3\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.154.2 to ^0.154.3\n    * ipfs-core bumped from ^0.14.2 to ^0.14.3\n    * ipfs-message-port-server bumped from ^0.11.2 to ^0.11.3\n\n### [0.11.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-client-v0.11.1...ipfs-message-port-client-v0.11.2) (2022-03-01)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.1 to ^0.10.2\n    * ipfs-message-port-protocol bumped from ^0.11.1 to ^0.11.2\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.154.1 to ^0.154.2\n    * ipfs-core bumped from ^0.14.1 to ^0.14.2\n    * ipfs-message-port-server bumped from ^0.11.1 to ^0.11.2\n\n### [0.11.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-client-v0.11.0...ipfs-message-port-client-v0.11.1) (2022-02-06)\n\n\n### Bug Fixes\n\n* **dag:** replace custom dag walk with multiformats/traversal ([#3950](https://www.github.com/ipfs/js-ipfs/issues/3950)) ([596b1f4](https://www.github.com/ipfs/js-ipfs/commit/596b1f48a014083b1736e4ad7e746c652d2583b1))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.0 to ^0.10.1\n    * ipfs-message-port-protocol bumped from ^0.11.0 to ^0.11.1\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.154.0 to ^0.154.1\n    * ipfs-core bumped from ^0.14.0 to ^0.14.1\n    * ipfs-message-port-server bumped from ^0.11.0 to ^0.11.1\n\n## [0.11.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-client-v0.10.3...ipfs-message-port-client-v0.11.0) (2022-01-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* peerstore methods are now all async, the repo is migrated to v12\n\n### Features\n\n* libp2p async peerstore ([#4018](https://www.github.com/ipfs/js-ipfs/issues/4018)) ([a6b201a](https://www.github.com/ipfs/js-ipfs/commit/a6b201af2c3697430ab0ebe002dd573d185f1ac0))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.9.0 to ^0.10.0\n    * ipfs-message-port-protocol bumped from ^0.10.5 to ^0.11.0\n  * devDependencies\n    * interface-ipfs-core bumped from ^0.153.0 to ^0.154.0\n    * ipfs-core bumped from ^0.13.0 to ^0.14.0\n    * ipfs-message-port-server bumped from ^0.10.5 to ^0.11.0\n\n## [0.10.3](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.10.2...ipfs-message-port-client@0.10.3) (2021-12-15)\n\n**Note:** Version bump only for package ipfs-message-port-client\n\n\n\n\n\n## [0.10.2](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.10.1...ipfs-message-port-client@0.10.2) (2021-11-24)\n\n**Note:** Version bump only for package ipfs-message-port-client\n\n\n\n\n\n## [0.10.1](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.10.0...ipfs-message-port-client@0.10.1) (2021-11-19)\n\n**Note:** Version bump only for package ipfs-message-port-client\n\n\n\n\n\n## [0.10.0](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.9.1...ipfs-message-port-client@0.10.0) (2021-11-12)\n\n\n### Bug Fixes\n\n* do not accept single items for ipfs.add ([#3900](https://github.com/ipfs/js-ipfs/issues/3900)) ([04e3cf3](https://github.com/ipfs/js-ipfs/commit/04e3cf3f46b585c4644cba70516f375e95361f52))\n* transfer set ([#3573](https://github.com/ipfs/js-ipfs/issues/3573)) ([b09a18c](https://github.com/ipfs/js-ipfs/commit/b09a18cd98883662353d116a8ff25a3ddaa48fc2))\n\n\n### BREAKING CHANGES\n\n* errors will now be thrown if multiple items are passed to `ipfs.add` or single items to `ipfs.addAll` (n.b. you can still pass a list of a single item to `ipfs.addAll`)\n\n\n\n\n\n### [0.9.1](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.9.0...ipfs-message-port-client@0.9.1) (2021-09-28)\n\n**Note:** Version bump only for package ipfs-message-port-client\n\n\n\n\n\n## [0.9.0](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.8.8...ipfs-message-port-client@0.9.0) (2021-09-24)\n\n\n### Features\n\n* switch to esm ([#3879](https://github.com/ipfs/js-ipfs/issues/3879)) ([9a40109](https://github.com/ipfs/js-ipfs/commit/9a40109632e5b4837eb77a2f57dbc77fbf1fe099))\n\n\n### BREAKING CHANGES\n\n* There are no default exports and everything is now dual published as ESM/CJS\n\n\n\n\n\n### [0.8.8](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.8.7...ipfs-message-port-client@0.8.8) (2021-09-17)\n\n**Note:** Version bump only for package ipfs-message-port-client\n\n\n\n\n\n### [0.8.7](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.8.6...ipfs-message-port-client@0.8.7) (2021-09-17)\n\n**Note:** Version bump only for package ipfs-message-port-client\n\n\n\n\n\n### [0.8.6](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.8.5...ipfs-message-port-client@0.8.6) (2021-09-08)\n\n**Note:** Version bump only for package ipfs-message-port-client\n\n\n\n\n\n### [0.8.5](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.8.4...ipfs-message-port-client@0.8.5) (2021-09-02)\n\n\n### Bug Fixes\n\n* declare types in .ts files ([#3840](https://github.com/ipfs/js-ipfs/issues/3840)) ([eba5fe6](https://github.com/ipfs/js-ipfs/commit/eba5fe6832858107b3e1ae02c99de674622f12b4))\n* remove use of instanceof for CID class ([#3847](https://github.com/ipfs/js-ipfs/issues/3847)) ([ebbb12d](https://github.com/ipfs/js-ipfs/commit/ebbb12db523c53ce8e4ddae5266cd9acb3504431))\n\n\n\n\n\n### [0.8.4](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.8.3...ipfs-message-port-client@0.8.4) (2021-08-25)\n\n**Note:** Version bump only for package ipfs-message-port-client\n\n\n\n\n\n### [0.8.3](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.8.1...ipfs-message-port-client@0.8.3) (2021-08-17)\n\n**Note:** Version bump only for package ipfs-message-port-client\n\n\n\n\n\n### [0.8.1](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.8.0...ipfs-message-port-client@0.8.1) (2021-08-17)\n\n**Note:** Version bump only for package ipfs-message-port-client\n\n\n\n\n\n## [0.8.0](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.7.1...ipfs-message-port-client@0.8.0) (2021-08-11)\n\n\n### Features\n\n* make ipfs.get output tarballs ([#3785](https://github.com/ipfs/js-ipfs/issues/3785)) ([1ad6001](https://github.com/ipfs/js-ipfs/commit/1ad60018d39d5b46c484756631e30e1989fd8eba))\n\n\n### BREAKING CHANGES\n\n* the output type of `ipfs.get` has changed and the `recursive` option has been removed from `ipfs.ls` since it was not supported everywhere\n\n\n\n\n\n### [0.7.1](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.7.0...ipfs-message-port-client@0.7.1) (2021-07-30)\n\n**Note:** Version bump only for package ipfs-message-port-client\n\n\n\n\n\n## [0.7.0](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.6.4...ipfs-message-port-client@0.7.0) (2021-07-27)\n\n\n### Bug Fixes\n\n* flaky timeout test ([#3767](https://github.com/ipfs/js-ipfs/issues/3767)) ([55afc2f](https://github.com/ipfs/js-ipfs/commit/55afc2f8ee483f4b2807598b7371561d39229e17))\n\n\n### Features\n\n* implement dag import/export ([#3728](https://github.com/ipfs/js-ipfs/issues/3728)) ([700765b](https://github.com/ipfs/js-ipfs/commit/700765be2634fa5d2d71d8b87cf68c9cd328d2c4)), closes [#2953](https://github.com/ipfs/js-ipfs/issues/2953) [#2745](https://github.com/ipfs/js-ipfs/issues/2745)\n* upgrade to the new multiformats ([#3556](https://github.com/ipfs/js-ipfs/issues/3556)) ([d13d15f](https://github.com/ipfs/js-ipfs/commit/d13d15f022a87d04a35f0f7822142f9cb898479c))\n\n\n### BREAKING CHANGES\n\n* ipld-formats no longer supported, use multiformat BlockCodecs instead\n\nCo-authored-by: Rod Vagg <rod@vagg.org>\nCo-authored-by: achingbrain <alex@achingbrain.net>\n\n\n\n\n\n### [0.6.4](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.6.3...ipfs-message-port-client@0.6.4) (2021-06-18)\n\n**Note:** Version bump only for package ipfs-message-port-client\n\n\n\n\n\n### [0.6.3](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.6.2...ipfs-message-port-client@0.6.3) (2021-06-05)\n\n**Note:** Version bump only for package ipfs-message-port-client\n\n\n\n\n\n### [0.6.2](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.6.1...ipfs-message-port-client@0.6.2) (2021-05-26)\n\n**Note:** Version bump only for package ipfs-message-port-client\n\n\n\n\n\n### [0.6.1](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.6.0...ipfs-message-port-client@0.6.1) (2021-05-11)\n\n**Note:** Version bump only for package ipfs-message-port-client\n\n\n\n\n\n## [0.6.0](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.5.4...ipfs-message-port-client@0.6.0) (2021-05-10)\n\n\n### chore\n\n* update node version in docker build ([#3603](https://github.com/ipfs/js-ipfs/issues/3603)) ([087fd1e](https://github.com/ipfs/js-ipfs/commit/087fd1eb402d1b933730e09c1d0cfb21067e9992))\n* upgrade deps with new typedefs ([#3550](https://github.com/ipfs/js-ipfs/issues/3550)) ([a418a52](https://github.com/ipfs/js-ipfs/commit/a418a521574c878d7aabd0ad2fd8d516908a3756))\n\n\n### BREAKING CHANGES\n\n* Minimum supported node version is 14\n* all core api methods now have types, some method signatures have changed, named exports are now used by the http, grpc and ipfs client modules\n\n\n\n\n\n### [0.5.4](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.5.3...ipfs-message-port-client@0.5.4) (2021-03-10)\n\n**Note:** Version bump only for package ipfs-message-port-client\n\n\n\n\n\n### [0.5.3](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.5.2...ipfs-message-port-client@0.5.3) (2021-03-09)\n\n\n### Bug Fixes\n\n* update to new aegir ([#3528](https://github.com/ipfs/js-ipfs/issues/3528)) ([49f7880](https://github.com/ipfs/js-ipfs/commit/49f78807d7e26483bd926b45cc7e0f797d77e41b))\n\n\n\n\n\n### [0.5.2](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.5.1...ipfs-message-port-client@0.5.2) (2021-02-08)\n\n**Note:** Version bump only for package ipfs-message-port-client\n\n\n\n\n\n### [0.5.1](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.5.0...ipfs-message-port-client@0.5.1) (2021-02-02)\n\n**Note:** Version bump only for package ipfs-message-port-client\n\n\n\n\n\n## [0.5.0](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.4.6...ipfs-message-port-client@0.5.0) (2021-02-01)\n\n\n### chore\n\n* update deps ([#3514](https://github.com/ipfs/js-ipfs/issues/3514)) ([061d77c](https://github.com/ipfs/js-ipfs/commit/061d77cc03f40af5a3bc3590481e1e5836e7f0d8))\n\n\n### BREAKING CHANGES\n\n* ipfs-repo upgrade requires repo migration to v10\n\n\n\n\n\n### [0.4.6](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.4.5...ipfs-message-port-client@0.4.6) (2021-01-22)\n\n**Note:** Version bump only for package ipfs-message-port-client\n\n\n\n\n\n### [0.4.5](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.4.4...ipfs-message-port-client@0.4.5) (2021-01-20)\n\n**Note:** Version bump only for package ipfs-message-port-client\n\n\n\n\n\n### [0.4.4](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.4.3...ipfs-message-port-client@0.4.4) (2021-01-15)\n\n**Note:** Version bump only for package ipfs-message-port-client\n\n\n\n\n\n### [0.4.3](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.4.2...ipfs-message-port-client@0.4.3) (2020-12-16)\n\n\n### Bug Fixes\n\n* transfer unique set over message prort ([#3421](https://github.com/ipfs/js-ipfs/issues/3421)) ([da7bc55](https://github.com/ipfs/js-ipfs/commit/da7bc55e8dfbdc200ef43ccbf774bbc24af07785))\n\n\n\n\n\n### [0.4.2](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.4.1...ipfs-message-port-client@0.4.2) (2020-11-25)\n\n**Note:** Version bump only for package ipfs-message-port-client\n\n\n\n\n\n### [0.4.1](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.4.0...ipfs-message-port-client@0.4.1) (2020-11-16)\n\n\n### Bug Fixes\n\n* make message-port-protocol non dev dependency ([#3393](https://github.com/ipfs/js-ipfs/issues/3393)) ([cea7317](https://github.com/ipfs/js-ipfs/commit/cea7317569ed899c6a4476c17f54795e49b6db4d))\n\n\n\n\n\n## [0.4.0](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.3.0...ipfs-message-port-client@0.4.0) (2020-11-09)\n\n\n### Bug Fixes\n\n* typedef resolution & add examples that use types ([#3359](https://github.com/ipfs/js-ipfs/issues/3359)) ([dc2795a](https://github.com/ipfs/js-ipfs/commit/dc2795a4f3b515683d09967ce611bf87d5e67f86)), closes [#3356](https://github.com/ipfs/js-ipfs/issues/3356) [#3358](https://github.com/ipfs/js-ipfs/issues/3358)\n\n\n### Features\n\n* pass file name to add/addAll progress handler ([#3372](https://github.com/ipfs/js-ipfs/issues/3372)) ([69681a7](https://github.com/ipfs/js-ipfs/commit/69681a7d7a8434c11f6f10e370e324f5a3d31042)), closes [ipfs/js-ipfs-unixfs#87](https://github.com/ipfs/js-ipfs-unixfs/issues/87)\n\n\n\n\n\n## [0.3.0](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.2.2...ipfs-message-port-client@0.3.0) (2020-10-28)\n\n\n### Features\n\n* implement message-port ipfs.ls ([#3322](https://github.com/ipfs/js-ipfs/issues/3322)) ([4b8021d](https://github.com/ipfs/js-ipfs/commit/4b8021d389ac01f191d4fe87beead10088e53297))\n* type check & generate defs from jsdoc ([#3281](https://github.com/ipfs/js-ipfs/issues/3281)) ([bbcaf34](https://github.com/ipfs/js-ipfs/commit/bbcaf34111251b142273a5675f4754ff68bd9fa0))\n\n\n\n\n\n### [0.2.2](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.2.1...ipfs-message-port-client@0.2.2) (2020-09-09)\n\n**Note:** Version bump only for package ipfs-message-port-client\n\n\n\n\n\n### [0.2.1](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.2.0...ipfs-message-port-client@0.2.1) (2020-09-04)\n\n**Note:** Version bump only for package ipfs-message-port-client\n\n\n\n\n\n## [0.2.0](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.1.1...ipfs-message-port-client@0.2.0) (2020-09-03)\n\n\n### Features\n\n* store pins in datastore instead of a DAG ([#2771](https://github.com/ipfs/js-ipfs/issues/2771)) ([64b7fe4](https://github.com/ipfs/js-ipfs/commit/64b7fe41738cbe96d5a9075f0c01156c6f889c40))\n\n\n\n\n\n### [0.1.1](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-client@0.1.0...ipfs-message-port-client@0.1.1) (2020-08-24)\n\n**Note:** Version bump only for package ipfs-message-port-client\n\n\n\n\n\n# 0.1.0 (2020-08-12)\n\n\n### Features\n\n* share IPFS node between browser tabs ([#3081](https://github.com/ipfs/js-ipfs/issues/3081)) ([1b8b1b8](https://github.com/ipfs/js-ipfs/commit/1b8b1b822a252498889c54972a1f57e1fedc39d0)), closes [#3022](https://github.com/ipfs/js-ipfs/issues/3022)"
  },
  {
    "path": "packages/ipfs-message-port-client/CODE_OF_CONDUCT.md",
    "content": "# Contributor Code of Conduct\n\nThe `js-ipfs` project follows the [`IPFS Community Code of Conduct`](https://github.com/ipfs/community/blob/master/code-of-conduct.md)\n"
  },
  {
    "path": "packages/ipfs-message-port-client/CONTRIBUTING.md",
    "content": "# Contributing guidelines\n\nIPFS as a project, including js-ipfs and all of its modules, follows the [standard IPFS Community contributing guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md).\n\nWe also adhere to the [IPFS JavaScript Community contributing guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING_JS.md) which provide additional information of how to collaborate and contribute in the JavaScript implementation of IPFS.\n\nWe appreciate your time and attention for going over these. Please open an issue on [ipfs/community](https://github.com/ipfs/community) if you have any question.\n\nThank you.\n"
  },
  {
    "path": "packages/ipfs-message-port-client/COPYRIGHT",
    "content": "This project is transitioning from an MIT-only license to a dual MIT/Apache-2.0 license.\nUnless otherwise noted, all code contributed prior to 2019-11-21 and not contributed by\na user listed in [this signoff issue](https://github.com/ipfs/js-ipfs/issues/2624) is\nlicensed under MIT-only. All new contributions (and past contributions since 2019-11-21)\nare licensed under a dual MIT/Apache-2.0 license.\n"
  },
  {
    "path": "packages/ipfs-message-port-client/LICENSE",
    "content": "This project is dual licensed under MIT and Apache-2.0.\n\nMIT: https://www.opensource.org/licenses/mit\nApache-2.0: https://www.apache.org/licenses/license-2.0\n"
  },
  {
    "path": "packages/ipfs-message-port-client/LICENSE-APACHE",
    "content": "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\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless 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.\n"
  },
  {
    "path": "packages/ipfs-message-port-client/LICENSE-MIT",
    "content": "The MIT License (MIT)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "packages/ipfs-message-port-client/README.md",
    "content": "> # ⛔️ DEPRECATED: [js-IPFS](https://github.com/ipfs/js-ipfs) has been superseded by [Helia](https://github.com/ipfs/helia)\n>\n> 📚 [Learn more about this deprecation](https://github.com/ipfs/js-ipfs/issues/4336) or [how to migrate](https://github.com/ipfs/helia/wiki/Migrating-from-js-IPFS)\n>\n> ⚠️ If you continue using this repo, please note that security fixes will not be provided\n\n# ipfs-message-port-client <!-- omit in toc -->\n\n[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech)\n[![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech)\n[![codecov](https://img.shields.io/codecov/c/github/ipfs/js-ipfs.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipfs)\n[![CI](https://img.shields.io/github/actions/workflow/status/ipfs/js-ipfs/test.yml?branch=master\\&style=flat-square)](https://github.com/ipfs/js-ipfs/actions/workflows/test.yml?query=branch%3Amaster)\n\n> IPFS client library for accessing IPFS node over message port\n\n## Table of contents <!-- omit in toc -->\n\n- [Install](#install)\n- [Usage](#usage)\n  - [Notes on Performance](#notes-on-performance)\n- [License](#license)\n- [Contribute](#contribute)\n\n## Install\n\n```console\n$ npm i ipfs-message-port-client\n```\n\n## Usage\n\nThis client library works with IPFS node over the [message channel][] and assumes that IPFS node is provided via `ipfs-message-port-server` on the other end.\n\nIt provides following API subset:\n\n- [`ipfs.dag`](https://github.com/ipfs/js-ipfs/blob/master/docs/core-api/DAG.md)\n- [`ipfs.block`](https://github.com/ipfs/js-ipfs/blob/master/docs/core-api/BLOCK.md)\n- [`ipfs.add`](https://github.com/ipfs/js-ipfs/blob/master/docs/core-api/FILES.md#ipfsadddata-options)\n- [`ipfs.addAll`](https://github.com/ipfs/js-ipfs/blob/master/docs/core-api/FILES.md#ipfsaddallsource-options)\n- [`ipfs.cat`](https://github.com/ipfs/js-ipfs/blob/master/docs/core-api/FILES.md#ipfscatipfspath-options)\n- [`ipfs.files.stat`](https://github.com/ipfs/js-ipfs/blob/master/docs/core-api/FILES.md#ipfsfilesstatpath-options)\n\nA client can be instantiated from the [`MessagePort`][] instance. The primary\ngoal of this library is to allow sharing a node across browsing contexts (tabs,\niframes) and therefore most likely `ipfs-message-port-server` will be in a\nseparate JS bundle and loaded in the [SharedWorker][].\n\n```js\nimport { IPFSClient } from 'ipfs-message-port-client'\n// URL to the script containing ipfs-message-port-server.\nconst IPFS_SERVER_URL = '/bundle/ipfs-worker.js'\n\nconst main = async () => {\n  const worker = new SharedWorker(IPFS_SERVER_URL)\n  const ipfs = IPFSClient.from(worker.port)\n  const data = ipfs.cat('/ipfs/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n')\n\n  for await (const chunk of data) {\n    console.log(chunk)\n  }\n}\n```\n\nIt is also possible to instantiate a detached client, which can be attached to\nthe server later on. This is useful when a server port is received via a message\nfrom another JS context (e.g. iframe)\n\n> Note: Client will queue all API calls and only execute them once it is\n> attached (unless they time out or are aborted in the meantime).\n\n```js\nimport { IPFSClient } from 'ipfs-message-port-client'\n\nconst ipfs = IPFSClient.detached()\n\nconst main = async () => {\n  const data = ipfs.cat('/ipfs/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n')\n\n  for await (const chunk of data) {\n    console.log(chunk)\n  }\n}\n\nwindow.onload = main\nwindow.onmessage = ({ports}) => {\n  IPFSClient.attach(ports[0])\n}\n```\n\n### Notes on Performance\n\nSince client works with IPFS node over [message channel][] all the data passed\nis copied via [structured cloning algorithm][], which may lead to suboptimal\nresults (especially with large binary data). In order to avoid unnecessary\ncopying all API options have being extended with optional `transfer` property\nthat can be supplied [Transferable][]s which will be used to move corresponding\nvalues instead of copying.\n\n> **Note:** Transferring data will empty it on the sender side which can lead to\n> errors if that data is used again later. To avoid these errors transfer option\n> was added so user can explicitly give up reference when it is safe to do so.\n\n```js\n/**\n * @param {Uint8Array} data - Large data chunk\n */\nconst example = async (data) => {\n  // Passing `data.buffer` will cause underlying `ArrayBuffer` to be\n  // transferred emptying `data` in JS context.\n  ipfs.add(data, { transfer: [data.buffer] })\n}\n```\n\nIt is however recommended to prefer web native [Blob][] / [File][] instances as\nmost web APIs provide them as option & can be send across without copying\nunderlying memory.\n\n```js\nconst example = async (url) => {\n  const request = await fetch(url)\n  const blob = await request.blob()\n  ipfs.add(blob)\n}\n```\n\n## License\n\nLicensed under either of\n\n- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / <http://www.apache.org/licenses/LICENSE-2.0>)\n- MIT ([LICENSE-MIT](LICENSE-MIT) / <http://opensource.org/licenses/MIT>)\n\n## Contribute\n\nContributions welcome! Please check out [the issues](https://github.com/ipfs/js-ipfs/issues).\n\nAlso 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.\n\nPlease 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).\n\nUnless 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.\n\n[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md)\n\n[message channel]: https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel\n\n[SharedWorker]: https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker\n\n[`MessagePort`]: https://developer.mozilla.org/en-US/docs/Web/API/MessagePort\n\n[structured cloning algorithm]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm\n\n[Transferable]: https://developer.mozilla.org/en-US/docs/Web/API/Transferable\n\n[Blob]: https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob\n\n[File]: https://developer.mozilla.org/en-US/docs/Web/API/File\n"
  },
  {
    "path": "packages/ipfs-message-port-client/package.json",
    "content": "{\n  \"name\": \"ipfs-message-port-client\",\n  \"version\": \"0.15.1\",\n  \"description\": \"IPFS client library for accessing IPFS node over message port\",\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"homepage\": \"https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-message-port-client#readme\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ipfs/js-ipfs.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ipfs/js-ipfs/issues\"\n  },\n  \"keywords\": [\n    \"ipfs\",\n    \"message-port\",\n    \"worker\"\n  ],\n  \"engines\": {\n    \"node\": \">=16.0.0\",\n    \"npm\": \">=7.0.0\"\n  },\n  \"type\": \"module\",\n  \"types\": \"./dist/src/index.d.ts\",\n  \"typesVersions\": {\n    \"*\": {\n      \"*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ],\n      \"src/*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ]\n    }\n  },\n  \"files\": [\n    \"src\",\n    \"dist\",\n    \"!dist/test\",\n    \"!**/*.tsbuildinfo\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/src/index.d.ts\",\n      \"import\": \"./src/index.js\"\n    }\n  },\n  \"eslintConfig\": {\n    \"extends\": \"ipfs\",\n    \"parserOptions\": {\n      \"sourceType\": \"module\"\n    }\n  },\n  \"scripts\": {\n    \"build\": \"aegir build\",\n    \"test:interface:message-port-client\": \"aegir test -t browser --bail -f ./test/interface-message-port-client.js\",\n    \"lint\": \"aegir lint\",\n    \"clean\": \"aegir clean\",\n    \"dep-check\": \"aegir dep-check -i ipfs-core -i ipfs-core-types -i esbuild\"\n  },\n  \"dependencies\": {\n    \"browser-readablestream-to-it\": \"^2.0.0\",\n    \"err-code\": \"^3.0.1\",\n    \"ipfs-core-types\": \"^0.14.1\",\n    \"ipfs-message-port-protocol\": \"^0.15.1\",\n    \"ipfs-unixfs\": \"^9.0.0\",\n    \"it-peekable\": \"^2.0.0\",\n    \"multiformats\": \"^11.0.0\"\n  },\n  \"devDependencies\": {\n    \"aegir\": \"^37.11.0\",\n    \"interface-ipfs-core\": \"^0.158.1\",\n    \"ipfs-core\": \"^0.18.1\",\n    \"ipfs-message-port-server\": \"^0.15.1\"\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-client/src/block.js",
    "content": "import { Client } from './client.js'\nimport { encodeCID, decodeCID } from 'ipfs-message-port-protocol/cid'\nimport { decodeError } from 'ipfs-message-port-protocol/error'\nimport { encodeBlock } from 'ipfs-message-port-protocol/block'\n\n/**\n * @typedef {import('./client').MessageTransport} MessageTransport\n * @typedef {import('ipfs-message-port-server').BlockService} BlockService\n * @typedef {import('./interface').MessagePortClientOptions} MessagePortClientOptions\n * @typedef {import('ipfs-core-types/src/block').API<MessagePortClientOptions>} BlockAPI\n */\n\n/**\n * @class\n * @extends {Client<BlockService>}\n */\nexport class BlockClient extends Client {\n  /**\n   * @param {MessageTransport} transport\n   */\n  constructor (transport) {\n    super('block', ['put', 'get', 'rm', 'stat'], transport)\n  }\n}\n\n/**\n * @type {BlockAPI[\"get\"]}\n */\nBlockClient.prototype.get = async function get (cid, options = {}) {\n  const { transfer } = options\n  const { block } = await this.remote.get({\n    ...options,\n    cid: encodeCID(cid, transfer)\n  })\n  return block\n}\n\n/**\n * @type {BlockAPI[\"put\"]}\n */\nBlockClient.prototype.put = async function put (block, options = {}) {\n  const { transfer } = options\n  // @ts-expect-error - ipfs-unixfs-importer passes `progress` which causing errors\n  // because functions can't be transferred.\n  delete options.progress\n  const result = await this.remote.put({\n    ...options,\n    block: block instanceof Uint8Array ? block : encodeBlock(block, transfer)\n  })\n  return decodeCID(result.cid)\n}\n\n/**\n * @type {BlockAPI[\"rm\"]}\n */\nBlockClient.prototype.rm = async function * rm (cids, options = {}) {\n  const { transfer } = options\n  const entries = await this.remote.rm({\n    ...options,\n    cids: Array.isArray(cids)\n      ? cids.map(cid => encodeCID(cid, transfer))\n      : [encodeCID(cids, transfer)]\n  })\n\n  yield * entries.map(decodeRmEntry)\n}\n\n/**\n * @type {BlockAPI[\"stat\"]}\n */\nBlockClient.prototype.stat = async function stat (cid, options = {}) {\n  const { transfer } = options\n  const result = await this.remote.stat({\n    ...options,\n    cid: encodeCID(cid, transfer)\n  })\n\n  return { ...result, cid: decodeCID(result.cid) }\n}\n\n/**\n * @param {import('ipfs-message-port-protocol/src/block').EncodedRmResult} entry\n */\nconst decodeRmEntry = entry => {\n  const cid = decodeCID(entry.cid)\n  if (entry.error) {\n    return { cid, error: decodeError(entry.error) }\n  } else {\n    return { cid }\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-client/src/client/error.js",
    "content": "\nexport class TimeoutError extends Error {\n  get name () {\n    return this.constructor.name\n  }\n}\n\nexport class AbortError extends Error {\n  get name () {\n    return this.constructor.name\n  }\n}\n\nexport class DisconnectError extends Error {\n  get name () {\n    return this.constructor.name\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-client/src/client/query.js",
    "content": "/**\n * @typedef {object} QueryOptions\n * @property {AbortSignal} [signal]\n * @property {number} [timeout]\n * @property {Set<Transferable>} [transfer]\n */\n\n/**\n * @template I\n * @typedef {I & QueryOptions} QueryInput\n */\n\n/**\n * Represents server query, encapsulating inputs to the server endpoint and\n * promise of it's result.\n *\n * @template I,O\n */\nexport class Query {\n  /**\n   * @param {string} namespace - component namespace on the server.\n   * @param {string} method - remote method this is a query of.\n   * @param {QueryInput<I>} input - query input.\n   */\n  constructor (namespace, method, input) {\n    /** @type {Promise<O>} */\n    this.result = new Promise((resolve, reject) => {\n      this.succeed = resolve\n      this.fail = reject\n      this.signal = input.signal\n      this.input = input\n      this.namespace = namespace\n      this.method = method\n      this.timeout = input.timeout == null ? Infinity : input.timeout\n      /** @type {ReturnType<typeof setTimeout> | null} */\n      this.timerID = null\n    })\n  }\n\n  /**\n   * Data that will be structure cloned over message channel.\n   *\n   * @returns {object}\n   */\n  toJSON () {\n    return this.input\n  }\n\n  /**\n   * Data that will be transferred over message channel.\n   *\n   * @returns {Set<Transferable>|void}\n   */\n  transfer () {\n    return this.input.transfer\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-client/src/client/service.js",
    "content": "import { Query } from './query.js'\n\n/**\n * @typedef {import('./transport').MessageTransport} MessageTransport\n * @typedef {import('./query').QueryInput<any>} QueryInput\n */\n/**\n * @template T\n * @typedef {import('ipfs-message-port-protocol/src/rpc').ProcedureNames<T>} ProcedureNames\n */\n\n/**\n * Service represents an API to a remote service `T`. It will have all the\n * methods with the same signatures as `T`.\n *\n * @template T\n */\nexport class Service {\n  /**\n   * @param {string} namespace - Namespace that remote API is served under.\n   * @param {ProcedureNames<T>} methods - Method names of the remote API.\n   * @param {MessageTransport} transport - Transport to issue queries over.\n   */\n  constructor (namespace, methods, transport) {\n    this.transport = transport\n    // Type script does not like using classes as some dicitionaries, so\n    // we explicitly type it as dictionary.\n    /** @type {Object<ProcedureNames<T>, Function>} */\n    const api = this\n    for (const method of methods) {\n      /**\n       * @template I, O\n       * @param {QueryInput} input\n       * @returns {Promise<O>}\n       */\n      api[method] = input =>\n        this.transport.execute(new Query(namespace, method.toString(), input))\n    }\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-client/src/client/transport.js",
    "content": "import { decodeError } from 'ipfs-message-port-protocol/error'\nimport { DisconnectError, TimeoutError, AbortError } from './error.js'\n\n/**\n * @template I,O\n * @typedef {import('./query').Query<I, O>} Query\n */\n\n/**\n * RPC Transport over `MessagePort` that can execute queries. It takes care of\n * executing queries by issuing a message with unique ID and fullfilling a\n * query when corresponding response message is received. It also makes sure\n * that aborted / timed out queries are cancelled as needed.\n *\n * It is expected that there will be at most one transport for a message port\n * instance.\n *\n */\nexport class MessageTransport {\n  /**\n   * Create transport for the underlying message port.\n   *\n   * @param {MessagePort} [port]\n   */\n  constructor (port) {\n    this.port = null\n    // Assigining a random enough identifier to the transport, to ensure that\n    // query.id will be unique when multiple tabs are communicating with a\n    // a server in the SharedWorker.\n    this.id = Math.random()\n      .toString(32)\n      .slice(2)\n\n    // Local unique id on the transport which is incremented for each query.\n    this.nextID = 0\n\n    // Dictionary of pending requests\n    /** @type {Record<string, Query<any, any>>} */\n    this.queries = Object.create(null)\n\n    // If port is provided connect this transport to it. If not transport can\n    // queue queries and execute those once it's connected.\n    if (port) {\n      this.connect(port)\n    }\n  }\n\n  /**\n   * Executes given query with this transport and returns promise for it's\n   * result. Promise fails with an error if query fails.\n   *\n   * @template I, O\n   * @param {Query<I, O>} query\n   * @returns {Promise<O>}\n   */\n  execute (query) {\n    const id = `${this.id}@${this.nextID++}`\n    this.queries[id] = query\n\n    // If query has a timeout set a timer.\n    if (query.timeout > 0 && query.timeout < Infinity) {\n      query.timerID = setTimeout(MessageTransport.timeout, query.timeout, this, id)\n    }\n\n    if (query.signal) {\n      query.signal.addEventListener('abort', () => this.abort(id), {\n        once: true\n      })\n    }\n\n    // If transport is connected (it has port) post a query, otherwise it\n    // will remain in the pending queries queue.\n    if (this.port) {\n      MessageTransport.postQuery(this.port, id, query)\n    }\n\n    return query.result\n  }\n\n  /**\n   * Connects this transport to the given message port. Throws `Error` if\n   * transport is already connected. All the pending queries will be executed\n   * as connection occurs.\n   *\n   * @param {MessagePort} port\n   */\n  connect (port) {\n    if (this.port) {\n      throw new Error('Transport is already open')\n    } else {\n      this.port = port\n      this.port.addEventListener('message', this)\n      this.port.start()\n\n      // Go ever pending queries (that were submitted before transport was\n      // connected) and post them. This loop is safe because messages will not\n      // arrive while this loop is running so no mutation can occur.\n      for (const [id, query] of Object.entries(this.queries)) {\n        MessageTransport.postQuery(port, id, query)\n      }\n    }\n  }\n\n  /**\n   * Disconnects this transport. This will cause all the pending queries\n   * to be aborted and undelying message port to be closed.\n   *\n   * Once disconnected transport can not be reconnected back.\n   */\n  disconnect () {\n    const error = new DisconnectError()\n    for (const [id, query] of Object.entries(this.queries)) {\n      query.fail(error)\n      this.abort(id)\n    }\n\n    // Note that reference to port is kept that ensures that attempt to\n    // reconnect will throw an error.\n    if (this.port) {\n      this.port.removeEventListener('message', this)\n      this.port.close()\n    }\n  }\n\n  /**\n   * Invoked on query timeout. If query is still pending it will fail and\n   * abort message will be send to a the server.\n   *\n   * @param {MessageTransport} self\n   * @param {string} id\n   */\n  static timeout (self, id) {\n    const { queries } = self\n    const query = queries[id]\n    if (query) {\n      delete queries[id]\n      query.fail(new TimeoutError('request timed out'))\n      if (self.port) {\n        self.port.postMessage({ type: 'abort', id })\n      }\n    }\n  }\n\n  /**\n   * Aborts this query by failing with `AbortError` and sending an abort message\n   * to the server. If query is no longer pending this has no effect.\n   *\n   * @param {string} id\n   */\n  abort (id) {\n    const { queries } = this\n    const query = queries[id]\n    if (query) {\n      delete queries[id]\n\n      query.fail(new AbortError())\n      if (this.port) {\n        this.port.postMessage({ type: 'abort', id })\n      }\n\n      if (query.timerID != null) {\n        clearTimeout(query.timerID)\n      }\n    }\n  }\n\n  /**\n   * Sends a given `query` with a given `id` over the message channel.\n   *\n   * @param {MessagePort} port\n   * @param {string} id\n   * @param {Query<any, any>} query\n   */\n  static postQuery (port, id, query) {\n    port.postMessage(\n      {\n        type: 'query',\n        namespace: query.namespace,\n        method: query.method,\n        id,\n        input: query.toJSON()\n      },\n      // @ts-expect-error - Type signature does not expect 2nd undefined arg\n      query.transfer()\n    )\n  }\n\n  /**\n   * Handler is invoked when message on the message port is received.\n   *\n   * @param {MessageEvent} event\n   */\n  handleEvent (event) {\n    const { id, result } = event.data\n    const query = this.queries[id]\n    // If query with a the given ID is found it is completed with the result,\n    // otherwise it is cancelled.\n    // Note: query may not be found when it was aborted on the client and at the\n    // same time server posted response.\n    if (query) {\n      delete this.queries[id]\n      if (result.ok) {\n        query.succeed(result.value)\n      } else {\n        query.fail(decodeError(result.error))\n      }\n\n      if (query.timerID != null) {\n        clearTimeout(query.timerID)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-client/src/client.js",
    "content": "\n/* eslint-env browser */\n\nimport { Service } from './client/service.js'\n\n/**\n * @template T\n * @typedef {import('ipfs-message-port-protocol/src/rpc').Remote<T>} Remote\n */\n\n/**\n * @template T\n * @typedef {import('ipfs-message-port-protocol/src/rpc').ProcedureNames<T>} ProcedureNames\n */\n\n/**\n * @template T\n * @typedef {Array<keyof T>} Keys\n */\n\n/**\n * @template T\n * @typedef {Remote<T> & Service<T>} RemoteService\n */\n\n/**\n * @typedef {import('./client/transport').MessageTransport} MessageTransport\n */\n\n/**\n * Client represents the client to remote `T` service. It is a base clase that\n * specific API clients will subclass to provide a higher level API for end\n * user. Client implementations take care of encoding arguments into quries\n * and issing those to `remote` service.\n *\n * @class\n * @template T\n */\nexport class Client {\n  /**\n   * @param {string} namespace\n   * @param {ProcedureNames<T>} methods\n   * @param {MessageTransport} transport\n   */\n  constructor (namespace, methods, transport) {\n    /** @type {RemoteService<T>} */\n    // @ts-expect-error types again\n    this.remote = (new Service(namespace, methods, transport))\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-client/src/core.js",
    "content": "\n/* eslint-env browser */\n\nimport { Client } from './client.js'\nimport { CID } from 'multiformats/cid'\nimport { encodeCID, decodeCID } from 'ipfs-message-port-protocol/cid'\nimport {\n  decodeIterable,\n  encodeIterable,\n  encodeCallback\n} from 'ipfs-message-port-protocol/core'\n/** @type {<T>(stream:ReadableStream<T>) => AsyncIterable<T>} */\nimport iterateReadableStream from 'browser-readablestream-to-it'\nimport {\n  parseMode,\n  parseMtime\n} from 'ipfs-unixfs'\nimport itPeekable from 'it-peekable'\nimport errCode from 'err-code'\n\n/**\n * @template T\n * @typedef {import('ipfs-message-port-protocol/src/core').RemoteIterable<T>} RemoteIterable\n */\n\n/**\n * @typedef {import('ipfs-message-port-protocol/src/cid').EncodedCID} EncodedCID\n * @typedef {import('ipfs-message-port-protocol/src/root').EncodedAddInput} EncodedAddInput\n * @typedef {import('ipfs-message-port-protocol/src/root').EncodedAddAllInput} EncodedAddAllInput\n * @typedef {import('ipfs-message-port-protocol/src/root').EncodedAddResult} EncodedAddResult\n * @typedef {import('ipfs-message-port-protocol/src/root').EncodedIPFSEntry} EncodedIPFSEntry\n * @typedef {import('ipfs-message-port-protocol/src/root').EncodedFileInput} EncodedFileInput\n * @typedef {import('ipfs-message-port-protocol/src/root').EncodedFileContent} EncodedFileContent\n * @typedef {import('ipfs-message-port-protocol/src/root').EncodedDirectoryInput} EncodedDirectoryInput\n *\n * @typedef {import('ipfs-message-port-server').CoreService} CoreService\n *\n * @typedef {import('./client').MessageTransport} MessageTransport\n * @typedef {import('./interface').MessagePortClientOptions} MessagePortClientOptions\n * @typedef {import('ipfs-core-types/src/root').API<MessagePortClientOptions>} RootAPI\n *\n * @typedef {import('ipfs-core-types/src/utils').ImportCandidate} ImportCandidate\n * @typedef {import('ipfs-core-types/src/utils').ToFile} ToFile\n * @typedef {import('ipfs-core-types/src/utils').ToDirectory} ToDirectory\n * @typedef {import('ipfs-core-types/src/utils').ToContent} ToContent\n * @typedef {import('ipfs-core-types/src/utils').ImportCandidateStream} ImportCandidateStream\n */\n\n/**\n * @class\n * @extends {Client<CoreService>}\n */\nexport class CoreClient extends Client {\n  /**\n   * @param {MessageTransport} transport\n   */\n  constructor (transport) {\n    super('core', ['add', 'addAll', 'cat', 'ls'], transport)\n  }\n}\n\n/**\n * Import files and data into IPFS.\n *\n * If you pass binary data like `Uint8Array` it is recommended to provide\n * `transfer: [input.buffer]` which would allow transferring it instead of\n * copying.\n *\n * @type {RootAPI[\"addAll\"]}\n */\nCoreClient.prototype.addAll = async function * addAll (input, options = {}) {\n  const { timeout, signal } = options\n  const transfer = options.transfer || new Set()\n  const progressCallback = options.progress\n    ? encodeCallback(options.progress, transfer)\n    : undefined\n\n  const result = await this.remote.addAll({\n    ...options,\n    input: encodeAddAllInput(input, transfer),\n    progress: undefined,\n    progressCallback,\n    transfer,\n    timeout,\n    signal\n  })\n  yield * decodeIterable(result.data, decodeAddedData)\n}\n\n/**\n * Add file to IPFS.\n *\n * If you pass binary data like `Uint8Array` it is recommended to provide\n * `transfer: [input.buffer]` which would allow transferring it instead of\n * copying.\n *\n * @type {RootAPI[\"add\"]}\n */\nCoreClient.prototype.add = async function add (input, options = {}) {\n  const { timeout, signal } = options\n  const transfer = options.transfer || new Set()\n  const progressCallback = options.progress\n    ? encodeCallback(options.progress, transfer)\n    : undefined\n\n  const result = await this.remote.add({\n    ...options,\n    input: await encodeAddInput(input, transfer),\n    progress: undefined,\n    progressCallback,\n    transfer,\n    timeout,\n    signal\n  })\n\n  return decodeAddedData(result.data)\n}\n\n/**\n * Returns content addressed by a valid IPFS Path.\n *\n * @type {RootAPI[\"cat\"]}\n */\nCoreClient.prototype.cat = async function * cat (inputPath, options = {}) {\n  const cid = CID.asCID(inputPath)\n  const input = cid ? encodeCID(cid) : inputPath\n  const result = await this.remote.cat({ ...options, path: input })\n  yield * decodeIterable(result.data, identity)\n}\n\n/**\n * Returns content addressed by a valid IPFS Path.\n *\n * @type {RootAPI[\"ls\"]}\n */\nCoreClient.prototype.ls = async function * ls (inputPath, options = {}) {\n  const cid = CID.asCID(inputPath)\n  const input = cid ? encodeCID(cid) : inputPath\n  const result = await this.remote.ls({ ...options, path: input })\n\n  yield * decodeIterable(result.data, decodeLsEntry)\n}\n\n/**\n * Decodes values yield by `ipfs.add`.\n *\n * @param {EncodedAddResult} data\n * @returns {import('ipfs-core-types/src/root').AddResult}\n */\nconst decodeAddedData = ({ path, cid, mode, mtime, size }) => {\n  return {\n    path,\n    cid: decodeCID(cid),\n    mode,\n    mtime,\n    size\n  }\n}\n\n/**\n * @param {EncodedIPFSEntry} encodedEntry\n * @returns {import('ipfs-core-types/src/root').IPFSEntry}\n */\nconst decodeLsEntry = ({ name, path, size, cid, type, mode, mtime }) => ({\n  cid: decodeCID(cid),\n  type,\n  name,\n  path,\n  mode,\n  mtime,\n  size\n})\n\n/**\n * @template T\n * @param {T} v\n * @returns {T}\n */\nconst identity = (v) => v\n\n/**\n * Encodes input passed to the `ipfs.add` via the best possible strategy for the\n * given input.\n *\n * @param {ImportCandidate} input\n * @param {Set<Transferable>} transfer\n * @returns {Promise<EncodedAddInput>}\n */\nconst encodeAddInput = async (input, transfer) => {\n  // We want to get a Blob as input. If we got it we're set.\n  if (input instanceof Blob) {\n    return input\n  } else if (typeof input === 'string') {\n    return input\n  } else if (input instanceof ArrayBuffer) {\n    return input\n  } else if (ArrayBuffer.isView(input)) {\n    // Note we are not adding `input.buffer` into transfer list, it's on user.\n    return input\n  } else {\n    // If input is (async) iterable or `ReadableStream` or \"FileObject\" it will\n    // be encoded via own specific encoder.\n    const iterable = asIterable(input)\n    if (iterable) {\n      return encodeIterable(\n        await ensureIsByteStream(iterable),\n        encodeIterableContent,\n        transfer\n      )\n    }\n\n    const asyncIterable = asAsyncIterable(input)\n    if (asyncIterable) {\n      return encodeIterable(\n        await ensureIsByteStream(asyncIterable),\n        encodeAsyncIterableContent,\n        transfer\n      )\n    }\n\n    const readableStream = asReadableStream(input)\n    if (readableStream) {\n      return encodeIterable(\n        await ensureIsByteStream(iterateReadableStream(readableStream)),\n        encodeAsyncIterableContent,\n        transfer\n      )\n    }\n\n    const file = asFileObject(input)\n    if (file) {\n      return encodeFileObject(file, transfer)\n    }\n\n    throw TypeError('Unexpected input: ' + typeof input)\n  }\n}\n\n/**\n * Encodes input passed to the `ipfs.addAll` via the best possible strategy for the\n * given input.\n *\n * @param {ImportCandidateStream} input\n * @param {Set<Transferable>} transfer\n * @returns {EncodedAddAllInput}\n */\nconst encodeAddAllInput = (input, transfer) => {\n  // If input is (async) iterable or `ReadableStream` or \"FileObject\" it will\n  // be encoded via own specific encoder.\n  const iterable = asIterable(input)\n  if (iterable) {\n    return encodeIterable(iterable, encodeIterableContent, transfer)\n  }\n\n  const asyncIterable = asAsyncIterable(input)\n  if (asyncIterable) {\n    return encodeIterable(\n      asyncIterable,\n      encodeAsyncIterableContent,\n      transfer\n    )\n  }\n\n  const readableStream = asReadableStream(input)\n  if (readableStream) {\n    return encodeIterable(\n      iterateReadableStream(readableStream),\n      encodeAsyncIterableContent,\n      transfer\n    )\n  }\n\n  throw TypeError('Unexpected input: ' + typeof input)\n}\n\n/**\n * Function encodes individual item of some `AsyncIterable` by choosing most\n * effective strategy.\n *\n * @param {ImportCandidate} content\n * @param {Set<Transferable>} transfer\n * @returns {EncodedAddInput}\n */\nconst encodeAsyncIterableContent = (content, transfer) => {\n  if (content instanceof ArrayBuffer) {\n    return content\n  } else if (ArrayBuffer.isView(content)) {\n    return content\n  } else if (content instanceof Blob) {\n    return { path: '', content }\n  } else if (typeof content === 'string') {\n    return { path: '', content }\n  } else {\n    const file = asFileObject(content)\n    if (file) {\n      return encodeFileObject(file, transfer)\n    } else {\n      throw TypeError('Unexpected input: ' + typeof content)\n    }\n  }\n}\n\n/**\n * @param {ImportCandidate} content\n * @param {Set<Transferable>} transfer\n * @returns {EncodedAddInput}\n */\nconst encodeIterableContent = (content, transfer) => {\n  if (typeof content === 'number') {\n    throw TypeError('Iterable of numbers is not supported')\n  } else if (content instanceof ArrayBuffer) {\n    return content\n  } else if (ArrayBuffer.isView(content)) {\n    return content\n  } else if (content instanceof Blob) {\n    return { path: '', content }\n  } else if (typeof content === 'string') {\n    return { path: '', content }\n  } else {\n    const file = asFileObject(content)\n    if (file) {\n      return encodeFileObject(file, transfer)\n    } else {\n      throw TypeError('Unexpected input: ' + typeof content)\n    }\n  }\n}\n\n/**\n * @param {ToFile | ToDirectory} file\n * @param {Set<Transferable>} transfer\n * @returns {EncodedFileInput | EncodedDirectoryInput}\n */\nconst encodeFileObject = ({ path, mode, mtime, content }, transfer) => {\n  /** @type {any} */\n  const output = {\n    path,\n    mode: parseMode(mode),\n    mtime: parseMtime(mtime)\n  }\n\n  if (content) {\n    output.content = encodeFileContent(content, transfer)\n  }\n\n  return output\n}\n\n/**\n * @param {ToContent|undefined} content\n * @param {Set<Transferable>} transfer\n * @returns {EncodedFileContent}\n */\nconst encodeFileContent = (content, transfer) => {\n  if (content == null) {\n    return ''\n  } else if (content instanceof ArrayBuffer || ArrayBuffer.isView(content)) {\n    return content\n  } else if (content instanceof Blob) {\n    return content\n  } else if (typeof content === 'string') {\n    return content\n  } else {\n    const iterable = asIterable(content)\n    if (iterable) {\n      return encodeIterable(iterable, encodeIterableContent, transfer)\n    }\n\n    const asyncIterable = asAsyncIterable(content)\n    if (asyncIterable) {\n      return encodeIterable(\n        asyncIterable,\n        encodeAsyncIterableContent,\n        transfer\n      )\n    }\n\n    const readableStream = asReadableStream(content)\n    if (readableStream) {\n      return encodeIterable(\n        iterateReadableStream(readableStream),\n        encodeAsyncIterableContent,\n        transfer\n      )\n    }\n\n    throw TypeError('Unexpected input: ' + typeof content)\n  }\n}\n\n/**\n * Pattern matches given input as `Iterable<I>` and returns back either matched\n * iterable or `null`.\n *\n * @template I\n * @param {Iterable<I>|ImportCandidate|ImportCandidateStream} input\n * @returns {Iterable<I>|null}\n */\nconst asIterable = (input) => {\n  /** @type {*} */\n  const object = input\n  if (object && typeof object[Symbol.iterator] === 'function') {\n    return object\n  } else {\n    return null\n  }\n}\n\n/**\n * Pattern matches given `input` as `AsyncIterable<I>` and returns back either\n * matched `AsyncIterable` or `null`.\n *\n * @template I\n * @param {AsyncIterable<I>|ImportCandidate|ImportCandidateStream} input\n * @returns {AsyncIterable<I>|null}\n */\nconst asAsyncIterable = (input) => {\n  /** @type {*} */\n  const object = input\n  if (object && typeof object[Symbol.asyncIterator] === 'function') {\n    return object\n  } else {\n    return null\n  }\n}\n\n/**\n * Pattern matches given `input` as `ReadableStream` and return back either\n * matched input or `null`.\n *\n * @param {any} input\n * @returns {ReadableStream<Uint8Array>|null}\n */\nconst asReadableStream = (input) => {\n  if (input && typeof input.getReader === 'function') {\n    return input\n  } else {\n    return null\n  }\n}\n\n/**\n * Pattern matches given input as \"FileObject\" and returns back eithr matched\n * input or `null`.\n *\n * @param {*} input\n * @returns {ToFile|null}\n */\nconst asFileObject = (input) => {\n  if (typeof input === 'object' && (input.path || input.content)) {\n    return input\n  } else {\n    return null\n  }\n}\n\n/**\n * @template T\n * @param {AsyncIterable<T> | Iterable<T>} input\n * @returns {Promise<AsyncIterable<T> | Iterable<T>>}\n */\nconst ensureIsByteStream = async (input) => {\n  const peekable = itPeekable(input)\n\n  /** @type {any} value **/\n  const { value, done } = await peekable.peek()\n\n  if (done) {\n    // make sure empty iterators result in empty files\n    return []\n  }\n\n  peekable.push(value)\n\n  // (Async)Iterable<Number>\n  // (Async)Iterable<Bytes>\n  // (Async)Iterable<String>\n  if (Number.isInteger(value) || isBytes(value) || typeof value === 'string' || value instanceof String) {\n    return peekable\n  }\n\n  throw errCode(new Error('Unexpected input: multiple items passed - if you are using ipfs.add, please use ipfs.addAll instead'), 'ERR_UNEXPECTED_INPUT')\n}\n\n/**\n * @param {any} obj\n * @returns {obj is ArrayBufferView|ArrayBuffer}\n */\nfunction isBytes (obj) {\n  return ArrayBuffer.isView(obj) || obj instanceof ArrayBuffer\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-client/src/dag.js",
    "content": "import { Client } from './client.js'\nimport { encodeCID, decodeCID } from 'ipfs-message-port-protocol/cid'\nimport { encodeNode, decodeNode } from 'ipfs-message-port-protocol/dag'\n\n/**\n * @typedef {import('multiformats/cid').CID} CID\n * @typedef {import('ipfs-message-port-protocol/src/cid').EncodedCID} EncodedCID\n * @typedef {import('ipfs-message-port-server').DAGService} DagService\n * @typedef {import('./client').MessageTransport} MessageTransport\n * @typedef {import('./interface').MessagePortClientOptions} MessagePortClientOptions\n * @typedef {import('ipfs-core-types/src/dag').API<MessagePortClientOptions>} DAGAPI\n */\n\n/**\n * @class\n * @extends {Client<DagService>}\n */\nexport class DAGClient extends Client {\n  /**\n   * @param {MessageTransport} transport\n   */\n  constructor (transport) {\n    super('dag', ['put', 'get', 'resolve'], transport)\n  }\n}\n\n/**\n * @type {DAGAPI[\"put\"]}\n */\nDAGClient.prototype.put = async function put (dagNode, options = {}) {\n  const encodedCID = await this.remote.put({\n    ...options,\n    dagNode: encodeNode(dagNode, options.transfer)\n  })\n\n  return decodeCID(encodedCID)\n}\n\n/**\n * @type {DAGAPI[\"get\"]}\n */\nDAGClient.prototype.get = async function get (cid, options = {}) {\n  const { value, remainderPath } = await this.remote.get({\n    ...options,\n    cid: encodeCID(cid, options.transfer)\n  })\n\n  return { value: decodeNode(value), remainderPath }\n}\n\n/**\n * @type {DAGAPI[\"resolve\"]}\n */\nDAGClient.prototype.resolve = async function resolve (cid, options = {}) {\n  const { cid: encodedCID, remainderPath } = await this.remote.resolve({\n    ...options,\n    cid: encodeCIDOrPath(cid, options.transfer)\n  })\n\n  return { cid: decodeCID(encodedCID), remainderPath }\n}\n\n/**\n * @param {string|CID} input\n * @param {Set<Transferable>} [transfer]\n * @returns {string|EncodedCID}\n */\nconst encodeCIDOrPath = (input, transfer) => {\n  if (typeof input === 'string') {\n    return input\n  } else {\n    return encodeCID(input, transfer)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-client/src/files.js",
    "content": "\n/* eslint-env browser */\n\nimport { Client } from './client.js'\nimport { decodeCID } from 'ipfs-message-port-protocol/cid'\nimport { CID } from 'multiformats/cid'\n\n/**\n * @typedef {import('ipfs-message-port-server').FilesService} FilesService\n * @typedef {import('ipfs-message-port-protocol/src/files').EncodedStat} EncodedStat\n * @typedef {import('./client').MessageTransport} MessageTransport\n * @typedef {import('./interface').MessagePortClientOptions} MessagePortClientOptions\n * @typedef {import('ipfs-core-types/src/files').API<MessagePortClientOptions>} FilesAPI\n */\n\n/**\n * @class\n * @extends {Client<FilesService>}\n */\nexport class FilesClient extends Client {\n  /**\n   * @param {MessageTransport} transport\n   */\n  constructor (transport) {\n    super('files', ['stat'], transport)\n  }\n}\n\n/**\n * @type {FilesAPI[\"stat\"]}\n */\nFilesClient.prototype.stat = async function stat (pathOrCID, options = {}) {\n  const { size, hash, withLocal, timeout, signal } = options\n  const { stat } = await this.remote.stat({\n    path: encodeLocation(pathOrCID),\n    size,\n    hash,\n    withLocal,\n    timeout,\n    signal\n  })\n  return decodeStat(stat)\n}\n\n/**\n * Turns content address (path or CID) into path.\n *\n * @param {string|CID} pathOrCID\n */\nconst encodeLocation = pathOrCID => {\n  const cid = CID.asCID(pathOrCID)\n\n  return cid ? `/ipfs/${pathOrCID.toString()}` : pathOrCID.toString()\n}\n\n/**\n * @param {EncodedStat} data\n */\nconst decodeStat = data => {\n  return { ...data, cid: decodeCID(data.cid) }\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-client/src/index.js",
    "content": "\n/* eslint-env browser */\n\nimport { MessageTransport } from './client/transport.js'\nimport { BlockClient } from './block.js'\nimport { DAGClient } from './dag.js'\nimport { CoreClient } from './core.js'\nimport { FilesClient } from './files.js'\nexport class IPFSClient extends CoreClient {\n  /**\n   * @param {MessageTransport} transport\n   */\n  constructor (transport) {\n    super(transport)\n    this.transport = transport\n    this.dag = new DAGClient(this.transport)\n    this.files = new FilesClient(this.transport)\n    this.block = new BlockClient(this.transport)\n  }\n\n  /**\n   * Attaches IPFS client to the given message port. Throws\n   * exception if client is already attached.\n   *\n   * @param {IPFSClient} self\n   * @param {MessagePort} port\n   */\n  static attach (self, port) {\n    self.transport.connect(port)\n  }\n\n  /**\n   * Creates IPFS client that is detached from the `ipfs-message-port-service`.\n   * This can be useful when in a scenario where obtaining message port happens\n   * later on in the application logic. Datached IPFS client will queue all the\n   * API calls and flush them once client is attached.\n   *\n   * @returns {IPFSClient}\n   */\n  static detached () {\n    return new IPFSClient(new MessageTransport(undefined))\n  }\n\n  /**\n   * Creates IPFS client from the message port (assumes that\n   * `ipfs-message-port-service` is instantiated on the other end)\n   *\n   * @param {MessagePort} port\n   * @returns {IPFSClient}\n   */\n  static from (port) {\n    return new IPFSClient(new MessageTransport(port))\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-client/src/interface.ts",
    "content": "// This file contains some utility types that either can't be expressed in\n// JSDoc syntax or that result in a different behaviour when typed in JSDoc.\n\nexport interface MessagePortClientOptions {\n  transfer?: Set<Transferable>\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-client/test/interface-message-port-client.js",
    "content": "/* eslint-env mocha, browser */\n\nimport * as tests from 'interface-ipfs-core'\nimport { activate } from './util/client.js'\n\ndescribe('interface-ipfs-core tests', () => {\n  const factory = {\n    spawn () {\n      return { api: activate() }\n    },\n    clean () {}\n  }\n\n  tests.root(factory, {\n    skip: [\n      {\n        name: 'should add with only-hash=true',\n        reason: 'ipfs.object.get is not implemented'\n      },\n      {\n        name: 'should add a directory with only-hash=true',\n        reason: 'ipfs.object.get is not implemented'\n      },\n      {\n        name: 'should add with mtime as hrtime',\n        reason: 'process.hrtime is not a function in browser'\n      },\n      {\n        name: 'should add from a URL with only-hash=true',\n        reason: 'ipfs.object.get is not implemented'\n      },\n      {\n        name: 'should cat with a Uint8Array multihash',\n        reason: 'Passing CID as Uint8Array is not supported'\n      },\n      {\n        name: 'should add from a HTTP URL',\n        reason: 'https://github.com/ipfs/js-ipfs/issues/3195'\n      },\n      {\n        name: 'should add from a HTTP URL with redirection',\n        reason: 'https://github.com/ipfs/js-ipfs/issues/3195'\n      },\n      {\n        name: 'should add from a URL with only-hash=true',\n        reason: 'https://github.com/ipfs/js-ipfs/issues/3195'\n      },\n      {\n        name: 'should add from a URL with wrap-with-directory=true',\n        reason: 'https://github.com/ipfs/js-ipfs/issues/3195'\n      },\n      {\n        name: 'should add from a URL with wrap-with-directory=true and URL-escaped file name',\n        reason: 'https://github.com/ipfs/js-ipfs/issues/3195'\n      },\n      {\n        name: 'should not add from an invalid url',\n        reason: 'https://github.com/ipfs/js-ipfs/issues/3195'\n      },\n      {\n        name: 'should be able to add dir without sharding',\n        reason: 'Cannot spawn IPFS with different args'\n      },\n      {\n        name: 'with sharding',\n        reason: 'TODO: allow spawning new daemons with different config'\n      },\n      {\n        name: 'addAll',\n        reason: 'Not implemented'\n      },\n      {\n        name: 'get',\n        reason: 'Not implemented'\n      },\n      {\n        name: 'refs',\n        reason: 'Not implemented'\n      },\n      {\n        name: 'refsLocal',\n        reason: 'Not implemented'\n      }\n    ]\n  })\n\n  tests.dag(factory, {\n    skip: [\n      {\n        name: 'should get a dag-pb node',\n        reason: 'Nodes are not turned into dag-pb DAGNode instances'\n      },\n      {\n        name: 'should get a dag-pb node with path',\n        reason: 'Nodes are not turned into dag-pb DAGNode instances'\n      },\n      {\n        name: 'should get by CID string',\n        reason: 'Passing CID as strings is not supported'\n      },\n      {\n        name: 'should get by CID string + path',\n        reason: 'Passing CID as strings is not supported'\n      },\n      {\n        name: 'should get a node added as CIDv1 with a CIDv0',\n        reason: 'ipfs.block API is not implemented'\n      },\n      {\n        name: 'should be able to get part of a dag-cbor node',\n        reason: 'Passing CID as strings is not supported'\n      },\n      {\n        name: 'should get tree with CID and path as String',\n        reason: 'Passing CID as strings is not supported'\n      },\n      {\n        name: '.dag.export',\n        reason: 'Not implemented yet'\n      },\n      {\n        name: '.dag.import',\n        reason: 'Not implemented yet'\n      }\n    ]\n  })\n\n  tests.block(factory, {\n    skip: [\n      {\n        name: 'should get by CID in string',\n        reason: 'Passing CID as strings is not supported'\n      },\n      {\n        name: 'should return an error for an invalid CID',\n        reason: 'Passing CID as strings is not supported'\n      },\n      {\n        name: 'should put a buffer, using CID string',\n        reason: 'Passing CID as strings is not supported'\n      },\n      {\n        name: 'should put a buffer, using options',\n        reason: 'ipfs.pin.ls is not implemented'\n      },\n      {\n        name: 'should remove by CID object',\n        reason: 'ipfs.refs.local is not implemented'\n      },\n      {\n        name: 'should remove by CID in string',\n        reason: 'Passing CID as strings is not supported'\n      },\n      {\n        name: 'should remove by CID in buffer',\n        reason: 'Passing CID as Buffer is not supported'\n      },\n      {\n        name: 'should error when removing pinned blocks',\n        reason: 'ipfs.pin.add is not implemented'\n      },\n\n      {\n        name: 'should remove multiple CIDs',\n        reason: 'times out'\n      },\n      {\n        name: 'should error when removing non-existent blocks',\n        reason: 'times out'\n      },\n      {\n        name: 'should not error when force removing non-existent blocks',\n        reason: 'times out'\n      }\n    ]\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-message-port-client/test/util/client.js",
    "content": "/* eslint-env browser */\n\nimport { IPFSClient } from '../../src/index.js'\n\nexport const activate = () => {\n  const worker = new SharedWorker(process.env.IPFS_WORKER_URL, 'IPFSService')\n  const client = IPFSClient.from(worker.port)\n  return client\n}\n\nexport const detached = () => {\n  const client = IPFSClient.detached()\n  return client\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-client/test/util/worker.js",
    "content": "\nimport { create } from 'ipfs-core'\nimport { IPFSService, Server } from 'ipfs-message-port-server'\n\nconst main = async connections => {\n  const ipfs = await create()\n  const service = new IPFSService(ipfs)\n  const server = new Server(service)\n\n  for await (const event of connections) {\n    const port = event.ports[0]\n    if (port) {\n      server.connect(port)\n    }\n  }\n}\n\nconst listen = function (target, type, options) {\n  const events = []\n  let resume\n  let ready = new Promise(resolve => (resume = resolve))\n\n  const write = event => {\n    events.push(event)\n    resume()\n  }\n  const read = async () => {\n    await ready\n    ready = new Promise(resolve => (resume = resolve))\n    return events.splice(0)\n  }\n\n  const reader = async function * () {\n    try {\n      while (true) {\n        yield * await read()\n      }\n    } finally {\n      target.removeEventListener(type, write, options)\n    }\n  }\n\n  target.addEventListener(type, write, options)\n  return reader()\n}\n\nmain(listen(self, 'connect'))\n"
  },
  {
    "path": "packages/ipfs-message-port-client/tsconfig.json",
    "content": "{\n  \"extends\": \"aegir/src/config/tsconfig.aegir.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"emitDeclarationOnly\": true\n  },\n  \"include\": [\n    \"src\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../interface-ipfs-core\"\n    },\n    {\n      \"path\": \"../ipfs-core\"\n    },\n    {\n      \"path\": \"../ipfs-core-types\"\n    },\n    {\n      \"path\": \"../ipfs-message-port-protocol\"\n    },\n    {\n      \"path\": \"../ipfs-message-port-server\"\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-protocol/.aegir.js",
    "content": "\n/** @type {import('aegir').PartialOptions} */\nexport default {\n  build: {\n    bundlesizeMax: '547B'\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-protocol/CHANGELOG.md",
    "content": "# Change Log\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n### [0.15.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol-v0.15.0...ipfs-message-port-protocol-v0.15.1) (2023-05-25)\n\n\n### Bug Fixes\n\n* add deprecation notice to readmes ([#4362](https://www.github.com/ipfs/js-ipfs/issues/4362)) ([7b79c1b](https://www.github.com/ipfs/js-ipfs/commit/7b79c1b8df5c818dc124b346ea28330455732d5c))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.14.0 to ^0.14.1\n\n## [0.15.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol-v0.14.0...ipfs-message-port-protocol-v0.15.0) (2023-01-11)\n\n\n### ⚠ BREAKING CHANGES\n\n* update multiformats to v11.x.x and related depenendcies (#4277)\n\n### Bug Fixes\n\n* update multiformats to v11.x.x and related depenendcies ([#4277](https://www.github.com/ipfs/js-ipfs/issues/4277)) ([521c84a](https://www.github.com/ipfs/js-ipfs/commit/521c84a958b04d61702577a5adce28519c1b2a3b))\n* use aegir to publish RCs ([#4284](https://www.github.com/ipfs/js-ipfs/issues/4284)) ([6d90cbf](https://www.github.com/ipfs/js-ipfs/commit/6d90cbf321a7dbf4b1084ba20f0c514dc08d8d0a))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.13.0 to ^0.14.0\n\n## [0.14.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol-v0.13.1...ipfs-message-port-protocol-v0.14.0) (2022-10-24)\n\n\n### ⚠ BREAKING CHANGES\n\n* ipfs is now bundled with libp2p@0.40.x which has different config\n\n### Features\n\n* upgrade libp2p to 0.40.x ([#4237](https://www.github.com/ipfs/js-ipfs/issues/4237)) ([0cee4a4](https://www.github.com/ipfs/js-ipfs/commit/0cee4a4c55767022584dcbade0b0b9b43326f9c9))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.12.1 to ^0.13.0\n\n### [0.13.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol-v0.13.0...ipfs-message-port-protocol-v0.13.1) (2022-09-21)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.12.0 to ^0.12.1\n\n## [0.13.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol-v0.12.1...ipfs-message-port-protocol-v0.13.0) (2022-09-06)\n\n\n### ⚠ BREAKING CHANGES\n\n* update to libp2p@0.38.x (#4151)\n\n### deps\n\n* update to libp2p@0.38.x ([#4151](https://www.github.com/ipfs/js-ipfs/issues/4151)) ([39dbf70](https://www.github.com/ipfs/js-ipfs/commit/39dbf708ec31b263115e44f420651fa4e056a89e))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.11.0 to ^0.12.0\n\n### [0.12.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol-v0.12.0...ipfs-message-port-protocol-v0.12.1) (2022-06-22)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.11.0 to ^0.11.1\n\n## [0.12.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol-v0.11.3...ipfs-message-port-protocol-v0.12.0) (2022-05-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* This module is now ESM only and there return types of some methods have changed\n\n### Features\n\n* update to libp2p 0.37.x ([#4092](https://www.github.com/ipfs/js-ipfs/issues/4092)) ([74aee8b](https://www.github.com/ipfs/js-ipfs/commit/74aee8b3d78f233c3199a3e9a6c0ac628a31a433))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.3 to ^0.11.0\n\n### [0.11.3](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol-v0.11.2...ipfs-message-port-protocol-v0.11.3) (2022-04-20)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.2 to ^0.10.3\n\n### [0.11.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol-v0.11.1...ipfs-message-port-protocol-v0.11.2) (2022-03-01)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.1 to ^0.10.2\n\n### [0.11.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol-v0.11.0...ipfs-message-port-protocol-v0.11.1) (2022-02-06)\n\n\n### Bug Fixes\n\n* **dag:** replace custom dag walk with multiformats/traversal ([#3950](https://www.github.com/ipfs/js-ipfs/issues/3950)) ([596b1f4](https://www.github.com/ipfs/js-ipfs/commit/596b1f48a014083b1736e4ad7e746c652d2583b1))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.0 to ^0.10.1\n\n## [0.11.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol-v0.10.5...ipfs-message-port-protocol-v0.11.0) (2022-01-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* peerstore methods are now all async, the repo is migrated to v12\n\n### Features\n\n* libp2p async peerstore ([#4018](https://www.github.com/ipfs/js-ipfs/issues/4018)) ([a6b201a](https://www.github.com/ipfs/js-ipfs/commit/a6b201af2c3697430ab0ebe002dd573d185f1ac0))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.9.0 to ^0.10.0\n\n## [0.10.5](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol@0.10.4...ipfs-message-port-protocol@0.10.5) (2021-12-15)\n\n**Note:** Version bump only for package ipfs-message-port-protocol\n\n\n\n\n\n## [0.10.4](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol@0.10.3...ipfs-message-port-protocol@0.10.4) (2021-11-24)\n\n**Note:** Version bump only for package ipfs-message-port-protocol\n\n\n\n\n\n## [0.10.3](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol@0.10.2...ipfs-message-port-protocol@0.10.3) (2021-11-19)\n\n**Note:** Version bump only for package ipfs-message-port-protocol\n\n\n\n\n\n## [0.10.2](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol@0.10.1...ipfs-message-port-protocol@0.10.2) (2021-11-12)\n\n\n### Bug Fixes\n\n* transfer set ([#3573](https://github.com/ipfs/js-ipfs/issues/3573)) ([b09a18c](https://github.com/ipfs/js-ipfs/commit/b09a18cd98883662353d116a8ff25a3ddaa48fc2))\n\n\n\n\n\n## [0.10.1](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol@0.10.0...ipfs-message-port-protocol@0.10.1) (2021-09-28)\n\n**Note:** Version bump only for package ipfs-message-port-protocol\n\n\n\n\n\n## [0.10.0](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol@0.9.4...ipfs-message-port-protocol@0.10.0) (2021-09-24)\n\n\n### Features\n\n* switch to esm ([#3879](https://github.com/ipfs/js-ipfs/issues/3879)) ([9a40109](https://github.com/ipfs/js-ipfs/commit/9a40109632e5b4837eb77a2f57dbc77fbf1fe099))\n\n\n### BREAKING CHANGES\n\n* There are no default exports and everything is now dual published as ESM/CJS\n\n\n\n\n\n### [0.9.4](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol@0.9.3...ipfs-message-port-protocol@0.9.4) (2021-09-17)\n\n**Note:** Version bump only for package ipfs-message-port-protocol\n\n\n\n\n\n### [0.9.3](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol@0.9.2...ipfs-message-port-protocol@0.9.3) (2021-09-17)\n\n**Note:** Version bump only for package ipfs-message-port-protocol\n\n\n\n\n\n### [0.9.2](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol@0.9.1...ipfs-message-port-protocol@0.9.2) (2021-09-02)\n\n\n### Bug Fixes\n\n* declare types in .ts files ([#3840](https://github.com/ipfs/js-ipfs/issues/3840)) ([eba5fe6](https://github.com/ipfs/js-ipfs/commit/eba5fe6832858107b3e1ae02c99de674622f12b4))\n* remove use of instanceof for CID class ([#3847](https://github.com/ipfs/js-ipfs/issues/3847)) ([ebbb12d](https://github.com/ipfs/js-ipfs/commit/ebbb12db523c53ce8e4ddae5266cd9acb3504431))\n\n\n\n\n\n### [0.9.1](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol@0.9.0...ipfs-message-port-protocol@0.9.1) (2021-08-25)\n\n**Note:** Version bump only for package ipfs-message-port-protocol\n\n\n\n\n\n## [0.9.0](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol@0.8.1...ipfs-message-port-protocol@0.9.0) (2021-08-11)\n\n\n### Features\n\n* make ipfs.get output tarballs ([#3785](https://github.com/ipfs/js-ipfs/issues/3785)) ([1ad6001](https://github.com/ipfs/js-ipfs/commit/1ad60018d39d5b46c484756631e30e1989fd8eba))\n\n\n### BREAKING CHANGES\n\n* the output type of `ipfs.get` has changed and the `recursive` option has been removed from `ipfs.ls` since it was not supported everywhere\n\n\n\n\n\n### [0.8.1](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol@0.8.0...ipfs-message-port-protocol@0.8.1) (2021-07-30)\n\n**Note:** Version bump only for package ipfs-message-port-protocol\n\n\n\n\n\n## [0.8.0](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol@0.7.3...ipfs-message-port-protocol@0.8.0) (2021-07-27)\n\n\n### Features\n\n* upgrade to the new multiformats ([#3556](https://github.com/ipfs/js-ipfs/issues/3556)) ([d13d15f](https://github.com/ipfs/js-ipfs/commit/d13d15f022a87d04a35f0f7822142f9cb898479c))\n\n\n### BREAKING CHANGES\n\n* ipld-formats no longer supported, use multiformat BlockCodecs instead\n\nCo-authored-by: Rod Vagg <rod@vagg.org>\nCo-authored-by: achingbrain <alex@achingbrain.net>\n\n\n\n\n\n### [0.7.3](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol@0.7.2...ipfs-message-port-protocol@0.7.3) (2021-06-18)\n\n**Note:** Version bump only for package ipfs-message-port-protocol\n\n\n\n\n\n### [0.7.2](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol@0.7.1...ipfs-message-port-protocol@0.7.2) (2021-06-05)\n\n**Note:** Version bump only for package ipfs-message-port-protocol\n\n\n\n\n\n### [0.7.1](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol@0.7.0...ipfs-message-port-protocol@0.7.1) (2021-05-26)\n\n**Note:** Version bump only for package ipfs-message-port-protocol\n\n\n\n\n\n## [0.7.0](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol@0.6.1...ipfs-message-port-protocol@0.7.0) (2021-05-10)\n\n\n### chore\n\n* update node version in docker build ([#3603](https://github.com/ipfs/js-ipfs/issues/3603)) ([087fd1e](https://github.com/ipfs/js-ipfs/commit/087fd1eb402d1b933730e09c1d0cfb21067e9992))\n* upgrade deps with new typedefs ([#3550](https://github.com/ipfs/js-ipfs/issues/3550)) ([a418a52](https://github.com/ipfs/js-ipfs/commit/a418a521574c878d7aabd0ad2fd8d516908a3756))\n\n\n### BREAKING CHANGES\n\n* Minimum supported node version is 14\n* all core api methods now have types, some method signatures have changed, named exports are now used by the http, grpc and ipfs client modules\n\n\n\n\n\n### [0.6.1](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol@0.6.0...ipfs-message-port-protocol@0.6.1) (2021-03-09)\n\n\n### Bug Fixes\n\n* update to new aegir ([#3528](https://github.com/ipfs/js-ipfs/issues/3528)) ([49f7880](https://github.com/ipfs/js-ipfs/commit/49f78807d7e26483bd926b45cc7e0f797d77e41b))\n\n\n\n\n\n## [0.6.0](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol@0.5.0...ipfs-message-port-protocol@0.6.0) (2021-02-01)\n\n\n### chore\n\n* update deps ([#3514](https://github.com/ipfs/js-ipfs/issues/3514)) ([061d77c](https://github.com/ipfs/js-ipfs/commit/061d77cc03f40af5a3bc3590481e1e5836e7f0d8))\n\n\n### BREAKING CHANGES\n\n* ipfs-repo upgrade requires repo migration to v10\n\n\n\n\n\n## [0.5.0](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol@0.4.3...ipfs-message-port-protocol@0.5.0) (2021-01-15)\n\n\n### Features\n\n* allow passing a http.Agent to the grpc client ([#3477](https://github.com/ipfs/js-ipfs/issues/3477)) ([c5f0bc5](https://github.com/ipfs/js-ipfs/commit/c5f0bc5eeee15369b7d02901035b04184a8608d2)), closes [#3474](https://github.com/ipfs/js-ipfs/issues/3474)\n\n\n\n\n\n### [0.4.3](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol@0.4.2...ipfs-message-port-protocol@0.4.3) (2020-12-16)\n\n\n### Bug Fixes\n\n* regressions introduced by new releases of CID & multicodec ([#3442](https://github.com/ipfs/js-ipfs/issues/3442)) ([b5152d8](https://github.com/ipfs/js-ipfs/commit/b5152d8cc93ecc8d39fc353ea66d7eaf1661e3c0)), closes [/github.com/multiformats/js-cid/commit/0e11f035c9230e7f6d79c159ace9b80de88cb5eb#diff-25a6634263c1b1f6fc4697a04e2b9904ea4b042a89af59dc93ec1f5d44848a26](https://github.com//github.com/multiformats/js-cid/commit/0e11f035c9230e7f6d79c159ace9b80de88cb5eb/issues/diff-25a6634263c1b1f6fc4697a04e2b9904ea4b042a89af59dc93ec1f5d44848a26)\n* transfer unique set over message prort ([#3421](https://github.com/ipfs/js-ipfs/issues/3421)) ([da7bc55](https://github.com/ipfs/js-ipfs/commit/da7bc55e8dfbdc200ef43ccbf774bbc24af07785))\n\n\n\n\n\n### [0.4.2](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol@0.4.1...ipfs-message-port-protocol@0.4.2) (2020-11-25)\n\n**Note:** Version bump only for package ipfs-message-port-protocol\n\n\n\n\n\n### [0.4.1](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol@0.4.0...ipfs-message-port-protocol@0.4.1) (2020-11-16)\n\n**Note:** Version bump only for package ipfs-message-port-protocol\n\n\n\n\n\n## [0.4.0](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol@0.3.0...ipfs-message-port-protocol@0.4.0) (2020-11-09)\n\n\n### Bug Fixes\n\n* typedef resolution & add examples that use types ([#3359](https://github.com/ipfs/js-ipfs/issues/3359)) ([dc2795a](https://github.com/ipfs/js-ipfs/commit/dc2795a4f3b515683d09967ce611bf87d5e67f86)), closes [#3356](https://github.com/ipfs/js-ipfs/issues/3356) [#3358](https://github.com/ipfs/js-ipfs/issues/3358)\n\n\n### Features\n\n* pass file name to add/addAll progress handler ([#3372](https://github.com/ipfs/js-ipfs/issues/3372)) ([69681a7](https://github.com/ipfs/js-ipfs/commit/69681a7d7a8434c11f6f10e370e324f5a3d31042)), closes [ipfs/js-ipfs-unixfs#87](https://github.com/ipfs/js-ipfs-unixfs/issues/87)\n\n\n\n\n\n## [0.3.0](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol@0.2.0...ipfs-message-port-protocol@0.3.0) (2020-10-28)\n\n\n### Features\n\n* type check & generate defs from jsdoc ([#3281](https://github.com/ipfs/js-ipfs/issues/3281)) ([bbcaf34](https://github.com/ipfs/js-ipfs/commit/bbcaf34111251b142273a5675f4754ff68bd9fa0))\n\n\n\n\n\n## [0.2.0](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol@0.1.1...ipfs-message-port-protocol@0.2.0) (2020-09-03)\n\n\n### Features\n\n* store pins in datastore instead of a DAG ([#2771](https://github.com/ipfs/js-ipfs/issues/2771)) ([64b7fe4](https://github.com/ipfs/js-ipfs/commit/64b7fe41738cbe96d5a9075f0c01156c6f889c40))\n\n\n\n\n\n### [0.1.1](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-protocol@0.1.0...ipfs-message-port-protocol@0.1.1) (2020-08-24)\n\n**Note:** Version bump only for package ipfs-message-port-protocol\n\n\n\n\n\n# 0.1.0 (2020-08-12)\n\n\n### Features\n\n* share IPFS node between browser tabs ([#3081](https://github.com/ipfs/js-ipfs/issues/3081)) ([1b8b1b8](https://github.com/ipfs/js-ipfs/commit/1b8b1b822a252498889c54972a1f57e1fedc39d0)), closes [#3022](https://github.com/ipfs/js-ipfs/issues/3022)"
  },
  {
    "path": "packages/ipfs-message-port-protocol/CODE_OF_CONDUCT.md",
    "content": "# Contributor Code of Conduct\n\nThe `js-ipfs` project follows the [`IPFS Community Code of Conduct`](https://github.com/ipfs/community/blob/master/code-of-conduct.md)\n"
  },
  {
    "path": "packages/ipfs-message-port-protocol/CONTRIBUTING.md",
    "content": "# Contributing guidelines\n\nIPFS as a project, including js-ipfs and all of its modules, follows the [standard IPFS Community contributing guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md).\n\nWe also adhere to the [IPFS JavaScript Community contributing guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING_JS.md) which provide additional information of how to collaborate and contribute in the JavaScript implementation of IPFS.\n\nWe appreciate your time and attention for going over these. Please open an issue on [ipfs/community](https://github.com/ipfs/community) if you have any question.\n\nThank you.\n"
  },
  {
    "path": "packages/ipfs-message-port-protocol/COPYRIGHT",
    "content": "This project is transitioning from an MIT-only license to a dual MIT/Apache-2.0 license.\nUnless otherwise noted, all code contributed prior to 2019-11-21 and not contributed by\na user listed in [this signoff issue](https://github.com/ipfs/js-ipfs/issues/2624) is\nlicensed under MIT-only. All new contributions (and past contributions since 2019-11-21)\nare licensed under a dual MIT/Apache-2.0 license.\n"
  },
  {
    "path": "packages/ipfs-message-port-protocol/LICENSE",
    "content": "This project is dual licensed under MIT and Apache-2.0.\n\nMIT: https://www.opensource.org/licenses/mit\nApache-2.0: https://www.apache.org/licenses/license-2.0\n"
  },
  {
    "path": "packages/ipfs-message-port-protocol/LICENSE-APACHE",
    "content": "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\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless 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.\n"
  },
  {
    "path": "packages/ipfs-message-port-protocol/LICENSE-MIT",
    "content": "The MIT License (MIT)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "packages/ipfs-message-port-protocol/README.md",
    "content": "> # ⛔️ DEPRECATED: [js-IPFS](https://github.com/ipfs/js-ipfs) has been superseded by [Helia](https://github.com/ipfs/helia)\n>\n> 📚 [Learn more about this deprecation](https://github.com/ipfs/js-ipfs/issues/4336) or [how to migrate](https://github.com/ipfs/helia/wiki/Migrating-from-js-IPFS)\n>\n> ⚠️ If you continue using this repo, please note that security fixes will not be provided\n\n# ipfs-message-port-protocol <!-- omit in toc -->\n\n[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech)\n[![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech)\n[![codecov](https://img.shields.io/codecov/c/github/ipfs/js-ipfs.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipfs)\n[![CI](https://img.shields.io/github/actions/workflow/status/ipfs/js-ipfs/test.yml?branch=master\\&style=flat-square)](https://github.com/ipfs/js-ipfs/actions/workflows/test.yml?query=branch%3Amaster)\n\n> IPFS client/server protocol over message port\n\n## Table of contents <!-- omit in toc -->\n\n- [Install](#install)\n  - [Browser `<script>` tag](#browser-script-tag)\n- [Usage](#usage)\n- [Wire protocol codecs](#wire-protocol-codecs)\n  - [`CID`](#cid)\n  - [DAGNode](#dagnode)\n  - [AsyncIterable](#asynciterable)\n  - [Callback](#callback)\n- [License](#license)\n- [Contribute](#contribute)\n\n## Install\n\n```console\n$ npm i ipfs-message-port-protocol\n```\n\n### Browser `<script>` tag\n\nLoading this module through a script tag will make it's exports available as `IpfsMessagePortProtocol` in the global namespace.\n\n```html\n<script src=\"https://unpkg.com/ipfs-message-port-protocol/dist/index.min.js\"></script>\n```\n\n## Usage\n\n## Wire protocol codecs\n\nThis module provides encode / decode functions for types that are not supported by [structured cloning algorithm][] and therefore need to be encoded before being posted over the [message channel][] and decoded on the other end.\n\nAll encoders take an optional `transfer` array. If provided, the encoder will add all `Transferable` fields of the given value so they can be moved across threads without copying.\n\n### `CID`\n\nCodecs for [CID][] implementation in JavaScript.\n\n```js\nimport { CID, encodeCID, decodeCID } from 'ipfs-message-port-protocol/cid'\n\nconst cid = CID.parse('bafybeig6xv5nwphfmvcnektpnojts33jqcuam7bmye2pb54adnrtccjlsu')\n\nconst { port1, port2 } = new MessageChannel()\n\n// Will copy underlying memory\nport1.postMessage(encodeCID(cid))\n\n// Will transfer underlying memory (cid is corrupt on this thread)\nconst transfer = []\nport1.postMessage(encodeCID(cid, transfer), transfer)\n\n// On the receiver thread\nport2.onmessage = ({data}) => {\n  const cid = decodeCID(data)\n  data instanceof CID // => true\n}\n```\n\n### DAGNode\n\nCodec for DAGNodes accepted by `ipfs.dag.put` API.\n\n```js\nimport { encodeNode, decodeNode } from 'ipfs-message-port-protocol/dag'\n\nconst cid = CID('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n')\nconst dagNode = { hi: 'hello', link: cid }\n\nconst { port1, port2 } = new MessageChannel()\n\n// Will copy underlying memory\nport1.postMessage(encodeNode(dagNode))\n\n// Will transfer underlying memory (`dagNode.link` will be corrupt on this thread)\nconst transfer = []\nport1.postMessage(encodeNode(dagNode, transfer), transfer)\n\n\n// On the receiver thread\nport2.onmessage = ({data}) => {\n  const dagNode = decodeNode(data)\n  dagNode.link instanceof CID // true\n}\n```\n\n### AsyncIterable\n\nThis encoder encodes [async iterables][] such that they can be transferred\nacross threads and decoded by a consumer on the other end while taking care of\nall the IO coordination between two. It needs to be provided `encoder` /\n`decoder` function to encode / decode each yielded item of the async iterable.\nUnlike other encoders the `transfer` argument is mandatory (because async\niterable is encoded to a [MessagePort][] that can only be transferred).\n\n```js\nimport { encodeIterable, decodeIterable } from 'ipfs-message-port-protocol/core')\n\nconst data = ipfs.cat('/ipfs/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n')\n\nconst { port1, port2 } = new MessageChannel()\n\n// Will copy each chunk to the receiver thread\n{\n  const transfer = []\n  port1.postMessage(\n    encodeIterable(content, chunk => chunk, transfer),\n    transfer\n  )\n}\n\n\n// Will transfer each chunk to the receiver thread (corrupting it on this thread)\n{\n  const transfer = []\n  port1.postMessage(\n    encodeIterable(\n      content,\n      (chunk, transfer) => {\n        transfer.push(chunk.buffer)\n        return chunk\n      },\n      transfer\n    ),\n    transfer\n  )\n}\n\n\n// On the receiver thread\nport2.onmessage = async ({data}) => {\n  for await (const chunk of decodeIterable(data)) {\n    chunk instanceof Uint8Array\n  }\n}\n```\n\n### Callback\n\nPrimitive callbacks that take single parameter supported by [structured cloning algorithm][] like progress callback used across IPFS APIs can be encoded / decoded. Unlike most encoders `transfer` argument is required (because value is encoded to a [MessagePort][] that can only be transferred)\n\n```js\nimport { encodeCallback, decodeCallback } from 'ipfs-message-port-protocol/core'\n\nconst { port1, port2 } = new MessageChannel()\n\nconst progress = (value) => console.log(progress)\n\nconst transfer = []\nport1.postMessage(encodeCallback(progress, transfer))\n\n\n// On the receiver thread\nport2.onmessage = ({data}) => {\n  const progress = decodeCallback(data)\n  // Invokes `progress` on the other end\n  progress(20)\n}\n```\n\n## License\n\nLicensed under either of\n\n- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / <http://www.apache.org/licenses/LICENSE-2.0>)\n- MIT ([LICENSE-MIT](LICENSE-MIT) / <http://opensource.org/licenses/MIT>)\n\n## Contribute\n\nContributions welcome! Please check out [the issues](https://github.com/ipfs/js-ipfs/issues).\n\nAlso 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.\n\nPlease 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).\n\nUnless 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.\n\n[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md)\n\n[structured cloning algorithm]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm\n\n[message channel]: https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel\n\n[MessagePort]: https://developer.mozilla.org/en-US/docs/Web/API/MessagePort\n\n[Transferable]: https://developer.mozilla.org/en-US/docs/Web/API/Transferable\n\n[CID]: https://github.com/multiformats/js-cid\n\n[async iterables]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of\n"
  },
  {
    "path": "packages/ipfs-message-port-protocol/package.json",
    "content": "{\n  \"name\": \"ipfs-message-port-protocol\",\n  \"version\": \"0.15.1\",\n  \"description\": \"IPFS client/server protocol over message port\",\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"homepage\": \"https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-message-port-protocol#readme\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ipfs/js-ipfs.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ipfs/js-ipfs/issues\"\n  },\n  \"keywords\": [\n    \"ipfs\"\n  ],\n  \"engines\": {\n    \"node\": \">=16.0.0\",\n    \"npm\": \">=7.0.0\"\n  },\n  \"type\": \"module\",\n  \"types\": \"./dist/src/index.d.ts\",\n  \"typesVersions\": {\n    \"*\": {\n      \"*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ],\n      \"src/*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ]\n    }\n  },\n  \"files\": [\n    \"src\",\n    \"dist\",\n    \"!dist/test\",\n    \"!**/*.tsbuildinfo\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/src/index.d.ts\",\n      \"import\": \"./src/index.js\"\n    },\n    \"./block\": {\n      \"types\": \"./src/block.d.ts\",\n      \"import\": \"./src/block.js\"\n    },\n    \"./cid\": {\n      \"types\": \"./src/cid.d.ts\",\n      \"import\": \"./src/cid.js\"\n    },\n    \"./core\": {\n      \"types\": \"./src/core.d.ts\",\n      \"import\": \"./src/core.js\"\n    },\n    \"./dag\": {\n      \"types\": \"./src/dag.d.ts\",\n      \"import\": \"./src/dag.js\"\n    },\n    \"./error\": {\n      \"types\": \"./src/error.d.ts\",\n      \"import\": \"./src/error.js\"\n    }\n  },\n  \"eslintConfig\": {\n    \"extends\": \"ipfs\",\n    \"parserOptions\": {\n      \"sourceType\": \"module\"\n    }\n  },\n  \"scripts\": {\n    \"build\": \"aegir build\",\n    \"test\": \"aegir test\",\n    \"test:node\": \"aegir test -t node --cov\",\n    \"test:chrome\": \"aegir test -t browser --cov\",\n    \"test:chrome-webworker\": \"aegir test -t webworker\",\n    \"test:firefox\": \"aegir test -t browser -- --browser firefox\",\n    \"test:firefox-webworker\": \"aegir test -t webworker -- --browser firefox\",\n    \"lint\": \"aegir lint\",\n    \"clean\": \"aegir clean\",\n    \"dep-check\": \"aegir dep-check -i ipfs-core-types\"\n  },\n  \"dependencies\": {\n    \"ipfs-core-types\": \"^0.14.1\",\n    \"multiformats\": \"^11.0.0\"\n  },\n  \"devDependencies\": {\n    \"aegir\": \"^37.11.0\",\n    \"uint8arrays\": \"^4.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-protocol/src/block.js",
    "content": "/**\n * @typedef {import('./error').EncodedError} EncodedError\n * @typedef {import('./cid').EncodedCID} EncodedCID\n *\n * @typedef {object} EncodedRmResult\n * @property {EncodedCID} cid\n * @property {EncodedError|undefined} [error]\n */\n\n/**\n * Encodes Uint8Array for transfer over the message channel.\n *\n * If `transfer` array is provided all the encountered `ArrayBuffer`s within\n * this block will be added to the transfer so they are moved across without\n * copy.\n *\n * @param {Uint8Array} data\n * @param {Set<Transferable>} [transfer]\n */\nexport const encodeBlock = (data, transfer) => {\n  if (transfer) {\n    transfer.add(data.buffer)\n  }\n  return data\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-protocol/src/cid.js",
    "content": "import { CID } from 'multiformats/cid'\n\n/**\n * @typedef {object} EncodedCID\n * @property {number} code\n * @property {object} multihash\n * @property {Uint8Array} multihash.digest\n * @property {number} version\n */\n\n/**\n * Encodes CID (well not really encodes it as all own properties are going to be\n * be cloned anyway). If `transfer` array is passed underlying `ArrayBuffer`\n * will be added for the transfer list.\n *\n * @param {CID} cid\n * @param {Set<Transferable>} [transfer]\n * @returns {EncodedCID}\n */\nexport const encodeCID = (cid, transfer) => {\n  if (transfer) {\n    transfer.add(cid.multihash.bytes.buffer)\n  }\n  return cid\n}\n\n/**\n * Decodes encoded CID (well sort of instead it makes nasty mutations to turn\n * structure cloned CID back into itself).\n *\n * @param {EncodedCID} encodedCID\n * @returns {CID}\n */\nexport const decodeCID = encodedCID => {\n  /** @type {CID} */\n  // @ts-expect-error we are converting this into an object compatible with the CID class\n  const cid = (encodedCID)\n\n  if (!cid.asCID) {\n    Object.defineProperty(cid, 'asCID', {\n      get: () => cid\n    })\n  }\n\n  if (!cid['/']) {\n    Object.defineProperty(cid, '/', {\n      get: () => cid.bytes\n    })\n  }\n\n  Object.setPrototypeOf(cid.multihash.digest, Uint8Array.prototype)\n  Object.setPrototypeOf(cid.multihash.bytes, Uint8Array.prototype)\n  Object.setPrototypeOf(cid.bytes, Uint8Array.prototype)\n  Object.setPrototypeOf(cid, CID.prototype)\n\n  return cid\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-protocol/src/core.js",
    "content": "/* eslint-env browser */\n\nimport { encodeError, decodeError } from './error.js'\n\n/**\n * @template T\n * @typedef {object} RemoteIterable\n * @property {'RemoteIterable'} type\n * @property {MessagePort} port\n */\n\n/**\n * @typedef {object} RemoteCallback\n * @property {'RemoteCallback'} type\n * @property {MessagePort} port\n */\n\n/**\n * @template T\n * @typedef {object} RemoteYield\n * @property {false} done\n * @property {T} value\n * @property {void} error\n */\n\n/**\n * @template T\n * @typedef {object} RemoteDone\n * @property {true} done\n * @property {T|void} value\n * @property {void} error\n */\n\n/**\n * @typedef {import('./error').EncodedError} EncodedError\n * @typedef {object} RemoteError\n * @property {true} done\n * @property {void} value\n * @property {EncodedError} error\n */\n\n/**\n * @template T\n * @typedef {RemoteYield<T>|RemoteDone<T>|RemoteError} RemoteNext\n */\n\n/**\n * @template I, O\n * @param {RemoteIterable<I>} remote\n * @param {function(I):O} decode\n * @returns {AsyncIterable<O>}\n */\nexport const decodeIterable = async function * ({ port }, decode) {\n  /**\n   * @param {RemoteNext<I>} _data\n   */\n  let receive = _data => {}\n  /**\n   * @returns {Promise<RemoteNext<I>>}\n   */\n  const wait = () => new Promise(resolve => (receive = resolve))\n  const next = () => {\n    port.postMessage({ method: 'next' })\n    return wait()\n  }\n\n  /**\n   * @param {MessageEvent} event\n   * @returns {void}\n   */\n  port.onmessage = event => receive(event.data)\n\n  let isDone = false\n  try {\n    while (!isDone) {\n      const { done, value, error } = await next()\n      isDone = done\n      if (error != null) {\n        throw decodeError(error)\n      } else if (value != null) {\n        yield decode(value)\n      }\n    }\n  } finally {\n    if (!isDone) {\n      port.postMessage({ method: 'return' })\n    }\n    port.close()\n  }\n}\n\n/**\n * @template I,O\n * @param {AsyncIterable<I>|Iterable<I>} iterable\n * @param {function(I, Set<Transferable>):O} encode\n * @param {Set<Transferable>} transfer\n * @returns {RemoteIterable<O>}\n */\nexport const encodeIterable = (iterable, encode, transfer) => {\n  const { port1: port, port2: remote } = new MessageChannel()\n  /** @type {Iterator<I>|AsyncIterator<I>} */\n  const iterator = toIterator(iterable)\n  // Note that port.onmessage will receive multiple 'next' method messages.\n  // Instead of allocating set every time we allocate one here and recycle\n  // it on each 'next' message.\n  /** @type {Set<Transferable>} */\n  const itemTransfer = new Set()\n\n  port.onmessage = async ({ data: { method } }) => {\n    switch (method) {\n      case 'next': {\n        try {\n          const { done, value } = await iterator.next()\n          if (done) {\n            port.postMessage({ type: 'next', done: true })\n            port.close()\n          } else {\n            itemTransfer.clear()\n            const encodedValue = encode(value, itemTransfer)\n\n            postMessage(\n              port,\n              {\n                type: 'next',\n                done: false,\n                value: encodedValue\n              },\n              itemTransfer\n            )\n          }\n        } catch (/** @type {any} */ error) {\n          port.postMessage({\n            type: 'throw',\n            error: encodeError(error)\n          })\n          port.close()\n        }\n        break\n      }\n      case 'return': {\n        port.close()\n        if (iterator.return) {\n          iterator.return()\n        }\n        break\n      }\n      default: {\n        break\n      }\n    }\n  }\n  port.start()\n  transfer.add(remote)\n\n  return { type: 'RemoteIterable', port: remote }\n}\n\n/**\n * @template I\n * @param {any} iterable\n * @returns {Iterator<I>|AsyncIterator<I>}\n */\nconst toIterator = iterable => {\n  if (iterable != null) {\n    if (typeof iterable[Symbol.asyncIterator] === 'function') {\n      return iterable[Symbol.asyncIterator]()\n    }\n\n    if (typeof iterable[Symbol.iterator] === 'function') {\n      return iterable[Symbol.iterator]()\n    }\n  }\n\n  throw TypeError('Value must be async or sync iterable')\n}\n\n/**\n * @param {Function} callback\n * @param {Set<Transferable>} transfer\n * @returns {RemoteCallback}\n */\nexport const encodeCallback = (callback, transfer) => {\n  // eslint-disable-next-line no-undef\n  const { port1: port, port2: remote } = new MessageChannel()\n  port.onmessage = ({ data }) => callback.apply(null, data)\n  transfer.add(remote)\n  return { type: 'RemoteCallback', port: remote }\n}\n\n/**\n * @template T\n * @param {RemoteCallback} remote\n * @returns {function(T[]):void | function(T[], Set<Transferable>):void}\n */\nexport const decodeCallback = ({ port }) => {\n  /**\n   * @param {T[]} args\n   * @param {Set<Transferable>} [transfer]\n   * @returns {void}\n   */\n  const callback = (args, transfer) => {\n    postMessage(port, args, transfer)\n  }\n\n  return callback\n}\n\n/**\n * @param {MessagePort} port\n * @param {any} message\n * @param {Iterable<Transferable>} [transfer]\n */\nconst postMessage = (port, message, transfer) =>\n  // @ts-expect-error - Built in types expect Transferable[] but it really\n  // should be Iterable<Transferable>\n  port.postMessage(message, transfer)\n"
  },
  {
    "path": "packages/ipfs-message-port-protocol/src/dag.js",
    "content": "import { CID } from 'multiformats/cid'\nimport { encodeCID, decodeCID } from './cid.js'\n\n/**\n * @typedef {import('./data').JSONValue} JSONValue\n */\n\n/**\n * @template T\n * @typedef {import('./data').StringEncoded<T>} StringEncoded\n */\n\n/**\n * @typedef {JSONValue} DAGNode\n * @typedef {object} EncodedDAGNode\n * @property {DAGNode} dagNode\n * @property {CID[]} cids\n */\n\n/**\n * @param {EncodedDAGNode} encodedNode\n * @returns {DAGNode}\n */\nexport const decodeNode = ({ dagNode, cids }) => {\n  // It is not ideal to have to mutate prototype chains like\n  // this, but it removes a need of traversing node first on client\n  // and now on server.\n  for (const cid of cids) {\n    decodeCID(cid)\n  }\n\n  return dagNode\n}\n\n/**\n * Encodes DAG node for over the message channel transfer by collecting all\n * the CID instances into an array so they could be turned back into CIDs\n * without traversal on the other end.\n *\n * If `transfer` array is provided all the encountered `ArrayBuffer`s within\n * this node will be added to transfer so they are moved across without copy.\n *\n * @param {DAGNode} dagNode\n * @param {Set<Transferable>} [transfer]\n * @returns {EncodedDAGNode}\n */\nexport const encodeNode = (dagNode, transfer) => {\n  /** @type {CID[]} */\n  const cids = []\n  collectNode(dagNode, cids, transfer)\n  return { dagNode, cids }\n}\n\n/**\n * Recursively traverses passed `value` and collects encountered `CID` instances\n * into provided `cids` array. If `transfer` array is passed collects all the\n * `ArrayBuffer`s into it.\n *\n * @param {DAGNode} value\n * @param {CID[]} cids\n * @param {Set<Transferable>} [transfer]\n * @returns {void}\n */\nconst collectNode = (value, cids, transfer) => {\n  if (value != null && typeof value === 'object') {\n    const cid = CID.asCID(value)\n\n    if (cid) {\n      // @ts-expect-error - this has to be the same instance\n      cids.push(value)\n      encodeCID(cid, transfer)\n    } else if (value instanceof ArrayBuffer) {\n      if (transfer) {\n        transfer.add(value)\n      }\n    } else if (ArrayBuffer.isView(value)) {\n      if (transfer) {\n        transfer.add(value.buffer)\n      }\n    } else if (Array.isArray(value)) {\n      for (const member of value) {\n        collectNode(member, cids, transfer)\n      }\n    } else {\n      for (const member of Object.values(value)) {\n        collectNode(member, cids, transfer)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-protocol/src/data.ts",
    "content": "export interface JSONObject { [key: string]: JSONValue }\nexport type JSONArray = JSONValue[]\nexport type JSONValue =\n  | null\n  | boolean\n  | number\n  | string\n  | JSONArray\n  | JSONObject\n\nexport type Encoded<_Data, Representation> = Representation // eslint-disable-line @typescript-eslint/no-unused-vars\nexport type StringEncoded<T> = Encoded<T, string>\n\nexport type Result<X, T> = { ok: true, value: T } | { ok: false, error: X }\n\nexport interface EncodedError {\n  message: string\n  name: string\n  stack: string\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-protocol/src/error.js",
    "content": "\n/* eslint-env browser */\n\n/**\n * @typedef {Error|ErrorData} EncodedError\n *\n * Properties added by err-code library\n * @typedef {object} ErrorExtension\n * @property {string} [code]\n * @property {string} [detail]\n */\n\n/**\n * @typedef {Error & ErrorExtension} ExtendedError\n */\n\n/**\n * @typedef {object} ErrorData\n * @property {string} name\n * @property {string} message\n * @property {string|undefined} stack\n * @property {string|undefined} code\n * @property {string|undefined} detail\n *\n * @param {ExtendedError} error\n * @returns {EncodedError}\n */\nexport const encodeError = error => {\n  const { name, message, stack, code, detail } = error\n  return { name, message, stack, code, detail }\n}\n\n/**\n * @param {EncodedError} error\n * @returns {Error}\n */\nexport const decodeError = error => {\n  if (error instanceof Error) {\n    return error\n  } else {\n    const { name, message, stack, code } = error\n    return Object.assign(createError(name, message), { name, stack, code })\n  }\n}\n\n/**\n * Create error by error name.\n *\n * @param {string} name\n * @param {string} message\n * @returns {Error}\n */\nconst createError = (name, message) => {\n  switch (name) {\n    case 'RangeError': {\n      return new RangeError(message)\n    }\n    case 'ReferenceError': {\n      return ReferenceError(message)\n    }\n    case 'SyntaxError': {\n      return new SyntaxError(message)\n    }\n    case 'TypeError': {\n      return new TypeError(message)\n    }\n    case 'URIError': {\n      return new URIError(message)\n    }\n    default: {\n      return new Error(message)\n    }\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-protocol/src/files.ts",
    "content": "import type { Mtime } from 'ipfs-unixfs'\nimport type { EncodedCID } from './cid'\n\nexport interface EncodedStat {\n  cid: EncodedCID\n  size: number\n  cumulativeSize: number\n  type: 'directory' | 'file'\n  blocks: number\n  withLocality: boolean\n  local?: boolean\n  sizeLocal?: number\n  mode?: number\n  mtime?: Mtime\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-protocol/src/index.js",
    "content": ""
  },
  {
    "path": "packages/ipfs-message-port-protocol/src/root.ts",
    "content": "import type { Mtime } from 'ipfs-unixfs'\nimport type { RemoteIterable } from './core'\nimport type { EncodedCID } from './cid'\n\nexport type FileType = 'dir' | 'file'\n\nexport type EncodedFileContent = ArrayBufferView | ArrayBuffer | Blob | string | RemoteIterable<ArrayBufferView> | RemoteIterable<ArrayBuffer>\n\nexport interface EncodedFileInput {\n  path?: string\n  content: EncodedFileContent\n  mode?: number\n  mtime?: Mtime\n}\n\nexport interface EncodedDirectoryInput {\n  path: string\n  mode?: number\n  mtime?: Mtime\n}\n\nexport type EncodedAddInput = EncodedFileContent | EncodedFileInput | EncodedDirectoryInput\nexport type EncodedAddAllInput = RemoteIterable<EncodedAddInput>\n\nexport interface EncodedAddResult {\n  path: string\n  cid: EncodedCID\n  size: number\n  mode?: number\n  mtime?: Mtime\n}\n\nexport interface EncodedIPFSEntry {\n  cid: EncodedCID\n  type: FileType\n  name: string\n  path: string\n  depth: number\n  size: number\n  mode?: number\n  mtime?: Mtime\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-protocol/src/rpc.ts",
    "content": "export type Procedure<T> = T extends (arg: infer I) => infer O\n  ? (query: I & QueryOptions) => Return<O>\n  : undefined\n\nexport type Remote<T extends Record<string, unknown>> = {\n  [K in keyof T]: Procedure<T[K]>\n}\n\nexport type Return<T> = T extends Promise<infer U>\n  ? Promise<U & TransferOptions>\n  : Promise<T & TransferOptions>\n\nexport interface QueryOptions {\n  signal?: AbortSignal\n  timeout?: number\n  transfer?: Set<Transferable>\n}\n\nexport interface TransferOptions {\n  transfer?: Set<Transferable>\n}\n\nexport type NonUndefined<A> = A extends undefined ? never : A\n\nexport type ProcedureNames<T extends Record<string, unknown>> = Array<{\n  [K in keyof T]-?: NonUndefined<T[K]> extends Function ? K : never // eslint-disable-line @typescript-eslint/ban-types\n}[keyof T]>\n\n/**\n * Any method name of the associated with RPC service.\n */\nexport type Method<T extends Record<string, unknown>> = ServiceQuery<T>['method']\n\n/**\n * Namespace of the RCP service\n */\nexport type Namespace<T extends Record<string, unknown>> = ServiceQuery<T>['namespace']\n\nexport type Values<T extends Record<string, unknown>> = T[keyof T]\nexport type Keys<T extends Record<string, unknown>> = keyof T\n\nexport type Inn<T extends Record<string, unknown>> = ServiceQuery<T>['input']\nexport type Out<T extends Record<string, unknown>> = ServiceQuery<T>['result']\n\nexport type RPCQuery<T extends Record<string, unknown>> = Pick<\nServiceQuery<T>,\n'method' | 'namespace' | 'input' | 'timeout' | 'signal'\n>\n\nexport type ServiceQuery<T> = Values<\n{\n  [NS in keyof T]: NamespacedQuery<T[NS], NS>\n}\n>\n\nexport type NamespacedQuery<S, NS> = Values<\n{\n  [M in keyof S]-?: S[M] extends (input: infer I) => infer O\n    ? {\n        namespace: NS\n        method: M\n        input: I & QueryOptions\n        result: R<O>\n      } & QueryOptions\n    : never\n}\n>\n\ntype R<O> = O extends Promise<infer T>\n  ? Promise<WithTransferOptions<T>>\n  : Promise<WithTransferOptions<O>>\n\ntype WithTransferOptions<O> = O extends Record<string, unknown> ? O & TransferOptions : O\n\nexport type MultiService <T> = {\n  [NS in keyof T]: NamespacedService<T[NS]>\n}\n\ntype NamespacedService<S> = {\n  [M in keyof S]: NamespacedMethod<S[M]>\n}\n\nexport type NamespacedMethod<T> = T extends (arg: infer I) => infer O\n  ? (query: I & QueryOptions) => Return<O>\n  : never\n"
  },
  {
    "path": "packages/ipfs-message-port-protocol/test/block.browser.js",
    "content": "\n/* eslint-env mocha */\n\nimport { encodeBlock } from '../src/block.js'\nimport { ipc } from './util.js'\nimport { expect } from 'aegir/chai'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\n\ndescribe('block (browser)', function () {\n  this.timeout(10 * 1000)\n  const move = ipc()\n\n  describe('encodeBlock / decodeBlock', () => {\n    it('should decode Block over message channel', async () => {\n      const blockIn = uint8ArrayFromString('hello')\n\n      const blockOut = await move(encodeBlock(blockIn))\n\n      expect(blockOut).to.be.deep.equal(blockIn)\n    })\n\n    it('should decode Block over message channel & transfer bytes', async () => {\n      const blockIn = uint8ArrayFromString('hello')\n\n      const transfer = new Set()\n\n      const blockOut = await move(encodeBlock(blockIn, transfer), transfer)\n\n      expect(blockOut).to.equalBytes(uint8ArrayFromString('hello'))\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-message-port-protocol/test/browser.js",
    "content": "\nimport './cid.browser.js'\nimport './block.browser.js'\nimport './dag.browser.js'\nimport './core.browser.js'\n"
  },
  {
    "path": "packages/ipfs-message-port-protocol/test/cid.browser.js",
    "content": "\n/* eslint-env mocha */\n\nimport { CID } from 'multiformats/cid'\nimport { encodeCID, decodeCID } from '../src/cid.js'\nimport { ipc } from './util.js'\nimport { expect } from 'aegir/chai'\n\ndescribe('cid (browser)', function () {\n  this.timeout(10 * 1000)\n  const move = ipc()\n\n  describe('encodeCID / decodeCID', () => {\n    it('should decode to CID over message channel', async () => {\n      const cidIn = CID.parse('Qmd7xRhW5f29QuBFtqu3oSD27iVy35NRB91XFjmKFhtgMr')\n      const cidDataIn = encodeCID(cidIn)\n      const cidDataOut = await move(cidDataIn)\n      const cidOut = decodeCID(cidDataOut)\n\n      expect(cidOut).to.be.an.instanceof(CID)\n      expect(cidOut.equals(cidIn)).to.be.true()\n      expect(cidIn.bytes)\n        .property('byteLength')\n        .not.be.equal(0)\n    })\n\n    it('should decode CID and transfer bytes', async () => {\n      const cidIn = CID.parse('Qmd7xRhW5f29QuBFtqu3oSD27iVy35NRB91XFjmKFhtgMr')\n      const transfer = new Set()\n      const cidDataIn = encodeCID(cidIn, transfer)\n      const cidDataOut = await move(cidDataIn, transfer)\n      const cidOut = decodeCID(cidDataOut)\n\n      expect(cidOut).to.be.an.instanceof(CID)\n      expect(cidIn.bytes).property('byteLength', 0)\n      expect(cidOut.bytes)\n        .property('byteLength')\n        .to.not.be.equal(0)\n      expect(cidOut.toString()).to.be.equal(\n        'Qmd7xRhW5f29QuBFtqu3oSD27iVy35NRB91XFjmKFhtgMr'\n      )\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-message-port-protocol/test/cid.spec.js",
    "content": "\n/* eslint-env mocha */\n\nimport { CID } from 'multiformats/cid'\nimport { encodeCID, decodeCID } from '../src/cid.js'\nimport { expect } from 'aegir/chai'\n\ndescribe('cid', function () {\n  this.timeout(10 * 1000)\n\n  describe('encodeCID / decodeCID', () => {\n    it('should encode CID', () => {\n      const { multihash: { digest }, code, version } = encodeCID(\n        CID.parse('Qmd7xRhW5f29QuBFtqu3oSD27iVy35NRB91XFjmKFhtgMr')\n      )\n      expect(digest).to.be.an.instanceof(Uint8Array)\n      expect(version).to.be.a('number')\n      expect(code).to.be.a('number')\n    })\n\n    it('should decode CID', () => {\n      const encoded = encodeCID(\n        CID.parse('Qmd7xRhW5f29QuBFtqu3oSD27iVy35NRB91XFjmKFhtgMr')\n      )\n      const cid = CID.parse('Qmd7xRhW5f29QuBFtqu3oSD27iVy35NRB91XFjmKFhtgMr')\n      const decodedCID = decodeCID(encoded)\n\n      expect(cid.equals(decodedCID)).to.be.true()\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-message-port-protocol/test/core.browser.js",
    "content": "\n/* eslint-env mocha */\n\nimport {\n  encodeCallback,\n  decodeCallback,\n  encodeIterable,\n  decodeIterable\n} from '../src/core.js'\nimport { ipc } from './util.js'\nimport { expect } from 'aegir/chai'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\n\ndescribe('core', function () {\n  this.timeout(10 * 1000)\n  const move = ipc()\n\n  describe('remote callback', () => {\n    it('remote callback copies arguments', async () => {\n      let deliver = null\n      const callback = progress => {\n        deliver(progress)\n      }\n      const receive = () =>\n        new Promise(resolve => {\n          deliver = resolve\n        })\n\n      const transfer = new Set()\n      const remote = decodeCallback(\n        await move(encodeCallback(callback, transfer), transfer)\n      )\n\n      remote([54])\n      expect(await receive()).to.be.equal(54)\n\n      remote([{ hello: 'world' }])\n\n      expect(await receive()).to.be.deep.equal({ hello: 'world' })\n    })\n\n    it('remote callback transfers buffers', async () => {\n      let deliver = null\n      const callback = progress => {\n        deliver(progress)\n      }\n      const receive = () =>\n        new Promise(resolve => {\n          deliver = resolve\n        })\n\n      const transfer = new Set()\n      const remote = decodeCallback(\n        await move(encodeCallback(callback, transfer), transfer)\n      )\n\n      remote([{ hello: uint8ArrayFromString('world') }])\n      expect(await receive()).to.be.deep.equal({ hello: uint8ArrayFromString('world') })\n\n      const world = uint8ArrayFromString('world')\n      remote([{ hello: world }], [world.buffer])\n\n      expect(await receive()).to.be.deep.equal({ hello: uint8ArrayFromString('world') })\n      expect(world.buffer).property('byteLength', 0, 'buffer was cleared')\n    })\n  })\n\n  describe('remote async iterable', () => {\n    it('remote iterable copies yielded data', async () => {\n      const iterate = async function * () {\n        yield 1\n        await null\n        yield { hello: uint8ArrayFromString('world') }\n        yield { items: [uint8ArrayFromString('bla'), uint8ArrayFromString('bla')] }\n      }\n\n      const transfer = new Set()\n\n      const remote = decodeIterable(\n        await move(\n          encodeIterable(\n            iterate(),\n            (data, transfer) => {\n              return data\n            },\n            transfer\n          ),\n          transfer\n        ),\n        a => a\n      )\n\n      const incoming = [\n        1,\n        { hello: uint8ArrayFromString('world') },\n        { items: [uint8ArrayFromString('bla'), uint8ArrayFromString('bla')] }\n      ]\n\n      for await (const item of remote) {\n        expect(item).to.be.deep.equal(incoming.shift())\n      }\n\n      expect(incoming).to.have.property('length', 0, 'all items were received')\n    })\n\n    it('break in consumer loop propagates to producer loop', async () => {\n      const outgoing = [\n        1,\n        { hello: uint8ArrayFromString('world') },\n        { items: [uint8ArrayFromString('bla'), uint8ArrayFromString('bla')] },\n        { bye: 'Goodbye' }\n      ]\n\n      const iterate = async function * () {\n        await null\n        while (true) {\n          yield outgoing.shift()\n        }\n      }\n\n      const transfer = new Set()\n\n      const remote = decodeIterable(\n        await move(\n          encodeIterable(\n            iterate(),\n            (data, transfer) => {\n              return data\n            },\n            transfer\n          ),\n          transfer\n        ),\n        a => a\n      )\n\n      const incoming = [\n        1,\n        { hello: uint8ArrayFromString('world') },\n        { items: [uint8ArrayFromString('bla'), uint8ArrayFromString('bla')] }\n      ]\n\n      for await (const item of remote) {\n        expect(item).to.be.deep.equal(incoming.shift())\n        if (incoming.length === 0) {\n          break\n        }\n      }\n\n      expect(incoming).to.have.property('length', 0, 'all items were received')\n      expect(outgoing).to.have.property('length', 1, 'one item remained')\n    })\n\n    it('execption in producer propagate to consumer', async () => {\n      const iterate = async function * () {\n        await null\n        yield 1\n        yield 2\n        throw Error('Producer Boom!')\n      }\n\n      const transfer = new Set()\n\n      const remote = decodeIterable(\n        await move(\n          encodeIterable(\n            iterate(),\n            (data, transfer) => {\n              return data\n            },\n            transfer\n          ),\n          transfer\n        ),\n        a => a\n      )\n\n      const incoming = [1, 2]\n\n      const consume = async () => {\n        for await (const item of remote) {\n          expect(item).to.be.deep.equal(incoming.shift())\n        }\n      }\n\n      const result = await consume().catch(error => error)\n\n      expect(result).to.an.instanceOf(Error)\n      expect(result).to.have.property('message', 'Producer Boom!')\n      expect(incoming).to.have.property('length', 0, 'all items were recieved')\n    })\n\n    it('execption in consumer propagate to producer', async () => {\n      const outgoing = [1, 2, 3]\n\n      const iterate = async function * () {\n        await null\n        while (true) {\n          yield outgoing.shift()\n        }\n      }\n\n      const transfer = new Set()\n\n      const remote = decodeIterable(\n        await move(\n          encodeIterable(\n            iterate(),\n            (data, transfer) => {\n              return data\n            },\n            transfer\n          ),\n          transfer\n        ),\n        a => a\n      )\n\n      const incoming = [1, 2]\n\n      const consume = async () => {\n        for await (const item of remote) {\n          expect(item).to.be.deep.equal(incoming.shift())\n          if (incoming.length === 0) {\n            throw new Error('Consumer Boom!')\n          }\n        }\n      }\n\n      const result = await consume().catch(error => error)\n\n      expect(result).to.an.instanceOf(Error)\n      expect(result).to.have.property('message', 'Consumer Boom!')\n\n      expect(outgoing).to.be.deep.equal([3], 'Producer loop was broken')\n    })\n\n    it('iterable transfers yield data', async () => {\n      const hi = uint8ArrayFromString('hello world')\n      const body = uint8ArrayFromString('how are you')\n      const bye = uint8ArrayFromString('Bye')\n      const outgoing = [hi, body, bye]\n      const iterate = async function * () {\n        await null\n        yield * outgoing\n      }\n\n      const transfer = new Set()\n\n      const remote = decodeIterable(\n        await move(\n          encodeIterable(\n            iterate(),\n            (data, transfer) => {\n              transfer.add(data.buffer)\n              return data\n            },\n            transfer\n          ),\n          transfer\n        ),\n        a => a\n      )\n\n      const incoming = [\n        uint8ArrayFromString('hello world'),\n        uint8ArrayFromString('how are you'),\n        uint8ArrayFromString('Bye')\n      ]\n\n      for await (const data of remote) {\n        expect(data).to.be.deep.equal(incoming.shift())\n      }\n\n      expect(outgoing).property('length', 3)\n      expect(hi).property('byteLength', 0)\n      expect(body).property('byteLength', 0)\n      expect(bye).property('byteLength', 0)\n    })\n  })\n\n  describe('remote sync iterable', () => {\n    it('remote iterable copies yielded data', async () => {\n      const iterate = function * () {\n        yield 1\n        yield { hello: uint8ArrayFromString('world') }\n        yield { items: [uint8ArrayFromString('bla'), uint8ArrayFromString('bla')] }\n      }\n\n      const transfer = new Set()\n\n      const remote = decodeIterable(\n        await move(\n          encodeIterable(\n            iterate(),\n            (data, transfer) => {\n              return data\n            },\n            transfer\n          ),\n          transfer\n        ),\n        a => a\n      )\n\n      const incoming = [\n        1,\n        { hello: uint8ArrayFromString('world') },\n        { items: [uint8ArrayFromString('bla'), uint8ArrayFromString('bla')] }\n      ]\n\n      for await (const item of remote) {\n        expect(item).to.be.deep.equal(incoming.shift())\n      }\n\n      expect(incoming).to.have.property('length', 0, 'all items were received')\n    })\n\n    it('break in consumer loop propagates to producer loop', async () => {\n      const outgoing = [\n        1,\n        { hello: uint8ArrayFromString('world') },\n        { items: [uint8ArrayFromString('bla'), uint8ArrayFromString('bla')] },\n        { bye: 'Goodbye' }\n      ]\n\n      const iterate = async function * () {\n        await null\n        while (true) {\n          yield outgoing.shift()\n        }\n      }\n\n      const transfer = new Set()\n\n      const remote = decodeIterable(\n        await move(\n          encodeIterable(\n            iterate(),\n            (data, transfer) => {\n              return data\n            },\n            transfer\n          ),\n          transfer\n        ),\n        a => a\n      )\n\n      const incoming = [\n        1,\n        { hello: uint8ArrayFromString('world') },\n        { items: [uint8ArrayFromString('bla'), uint8ArrayFromString('bla')] }\n      ]\n\n      for await (const item of remote) {\n        expect(item).to.be.deep.equal(incoming.shift())\n        if (incoming.length === 0) {\n          break\n        }\n      }\n\n      expect(incoming).to.have.property('length', 0, 'all items were received')\n      expect(outgoing).to.have.property('length', 1, 'one item remained')\n    })\n\n    it('execption in producer propagate to consumer', async () => {\n      const iterate = function * () {\n        yield 1\n        yield 2\n        throw Error('Producer Boom!')\n      }\n\n      const transfer = new Set()\n\n      const remote = decodeIterable(\n        await move(\n          encodeIterable(\n            iterate(),\n            (data, transfer) => {\n              return data\n            },\n            transfer\n          ),\n          transfer\n        ),\n        a => a\n      )\n\n      const incoming = [1, 2]\n\n      const consume = async () => {\n        for await (const item of remote) {\n          expect(item).to.be.deep.equal(incoming.shift())\n        }\n      }\n\n      const result = await consume().catch(error => error)\n\n      expect(result).to.an.instanceOf(Error)\n      expect(result).to.have.property('message', 'Producer Boom!')\n      expect(incoming).to.have.property('length', 0, 'all items were recieved')\n    })\n\n    it('execption in consumer propagate to producer', async () => {\n      const outgoing = [1, 2, 3]\n\n      const iterate = function * () {\n        while (true) {\n          yield outgoing.shift()\n        }\n      }\n\n      const transfer = new Set()\n\n      const remote = decodeIterable(\n        await move(\n          encodeIterable(\n            iterate(),\n            (data, transfer) => {\n              return data\n            },\n            transfer\n          ),\n          transfer\n        ),\n        a => a\n      )\n\n      const incoming = [1, 2]\n\n      const consume = async () => {\n        for await (const item of remote) {\n          expect(item).to.be.deep.equal(incoming.shift())\n          if (incoming.length === 0) {\n            throw new Error('Consumer Boom!')\n          }\n        }\n      }\n\n      const result = await consume().catch(error => error)\n\n      expect(result).to.an.instanceOf(Error)\n      expect(result).to.have.property('message', 'Consumer Boom!')\n\n      expect(outgoing).to.be.deep.equal([3], 'Producer loop was broken')\n    })\n\n    it('iterable transfers yield data', async () => {\n      const hi = uint8ArrayFromString('hello world')\n      const body = uint8ArrayFromString('how are you')\n      const bye = uint8ArrayFromString('Bye')\n      const outgoing = [hi, body, bye]\n      const iterate = function * () {\n        yield * outgoing\n      }\n\n      const transfer = new Set()\n\n      const remote = decodeIterable(\n        await move(\n          encodeIterable(\n            iterate(),\n            (data, transfer) => {\n              transfer.add(data.buffer)\n              return data\n            },\n            transfer\n          ),\n          transfer\n        ),\n        a => a\n      )\n\n      const incoming = [\n        uint8ArrayFromString('hello world'),\n        uint8ArrayFromString('how are you'),\n        uint8ArrayFromString('Bye')\n      ]\n\n      for await (const data of remote) {\n        expect(data).to.be.deep.equal(incoming.shift())\n      }\n\n      expect(outgoing).property('length', 3)\n      expect(hi).property('byteLength', 0)\n      expect(body).property('byteLength', 0)\n      expect(bye).property('byteLength', 0)\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-message-port-protocol/test/dag.browser.js",
    "content": "\n/* eslint-env mocha */\n\nimport { CID } from 'multiformats/cid'\nimport { encodeNode, decodeNode } from '../src/dag.js'\nimport { ipc } from './util.js'\nimport { expect } from 'aegir/chai'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\n\ndescribe('dag (browser)', function () {\n  this.timeout(10 * 1000)\n  const move = ipc()\n\n  describe('encodeNode / decodeNode', () => {\n    it('should decode dagNode over message channel', async () => {\n      const cid1 = CID.parse(\n        'bafyreic6f672hnponukaacmk2mmt7vs324zkagvu4hcww6yba6kby25zce'\n      )\n      const cid2 = CID.parse('QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ')\n\n      const hi = uint8ArrayFromString('hello world')\n      const nodeIn = {\n        hi,\n        nested: {\n          structure: {\n            with: {\n              links: [cid1]\n            }\n          }\n        },\n        other: {\n          link: cid2\n        }\n      }\n\n      const nodeOut = decodeNode(await move(encodeNode(nodeIn)))\n\n      expect(nodeOut).to.be.deep.equal(nodeIn)\n    })\n\n    it('should decode dagNode over message channel & transfer bytes', async () => {\n      const cid1 = 'bafyreic6f672hnponukaacmk2mmt7vs324zkagvu4hcww6yba6kby25zce'\n      const cid2 = 'QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ'\n\n      const hi = uint8ArrayFromString('hello world')\n      const nodeIn = {\n        hi,\n        nested: {\n          structure: {\n            with: {\n              links: [\n                CID.parse(cid1)\n              ]\n            }\n          }\n        },\n        other: {\n          link: CID.parse(cid2)\n        }\n      }\n      const transfer = new Set()\n\n      const nodeOut = decodeNode(\n        await move(encodeNode(nodeIn, transfer), transfer)\n      )\n\n      expect(nodeOut).to.be.deep.equal({\n        hi: uint8ArrayFromString('hello world'),\n        nested: {\n          structure: {\n            with: {\n              links: [\n                CID.parse(cid1)\n              ]\n            }\n          }\n        },\n        other: {\n          link: CID.parse(cid2)\n        }\n      })\n\n      expect([...transfer]).to.containSubset(\n        [{ byteLength: 0 }, { byteLength: 0 }, { byteLength: 0 }],\n        'transferred buffers were cleared'\n      )\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-message-port-protocol/test/dag.spec.js",
    "content": "\n/* eslint-env mocha */\n\nimport { CID } from 'multiformats/cid'\nimport { encodeNode } from '../src/dag.js'\nimport { expect } from 'aegir/chai'\nimport { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'\n\ndescribe('dag', function () {\n  this.timeout(10 * 1000)\n\n  describe('encodeNode / decodeNode', () => {\n    it('should encode node', () => {\n      const cid1 = CID.parse(\n        'bafyreic6f672hnponukaacmk2mmt7vs324zkagvu4hcww6yba6kby25zce'\n      )\n      const cid2 = CID.parse('QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ')\n      const dagNode = {\n        hi: 'hello',\n        link: cid1,\n        nested: {\n          struff: {\n            here: cid2\n          }\n        }\n      }\n\n      const data = encodeNode(dagNode)\n\n      expect(data.dagNode).to.be.equal(dagNode)\n      expect(data.cids).to.be.an.instanceOf(Array)\n      expect(data.cids).to.have.property('length', 2)\n      expect(data.cids).to.include(cid1)\n      expect(data.cids).to.include(cid2)\n    })\n\n    it('should encode and add buffers to transfer list', () => {\n      const cid1 = CID.parse(\n        'bafyreic6f672hnponukaacmk2mmt7vs324zkagvu4hcww6yba6kby25zce'\n      )\n      const cid2 = CID.parse('QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ')\n\n      const hi = uint8ArrayFromString('hello world')\n      const dagNode = {\n        hi,\n        nested: {\n          structure: {\n            with: {\n              links: [cid1]\n            }\n          }\n        },\n        other: {\n          link: cid2\n        }\n      }\n\n      const transfer = new Set()\n      const data = encodeNode(dagNode, transfer)\n\n      expect(data.dagNode).to.be.equal(dagNode)\n      expect(data.cids).to.be.an.instanceOf(Array)\n      expect(data.cids).to.have.property('length', 2)\n      expect(data.cids).to.include(cid1)\n      expect(data.cids).to.include(cid2)\n\n      expect(transfer).to.be.an.instanceOf(Set)\n      expect(transfer).to.have.property('size', 3)\n      expect(transfer).to.include(cid1.multihash.bytes.buffer)\n      expect(transfer).to.include(cid2.multihash.bytes.buffer)\n      expect(transfer).to.include(hi.buffer)\n    })\n\n    it('should decode node', () => {\n      const cid1 = CID.parse(\n        'bafyreic6f672hnponukaacmk2mmt7vs324zkagvu4hcww6yba6kby25zce'\n      )\n      const cid2 = CID.parse('QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ')\n\n      const hi = uint8ArrayFromString('hello world')\n      const dagNode = {\n        hi,\n        nested: {\n          structure: {\n            with: {\n              links: [cid1]\n            }\n          }\n        },\n        other: {\n          link: cid2\n        }\n      }\n\n      const transfer = new Set()\n      const data = encodeNode(dagNode, transfer)\n\n      expect(data.dagNode).to.be.equal(dagNode)\n      expect(data.cids).to.be.an.instanceOf(Array)\n      expect(data.cids).to.have.property('length', 2)\n      expect(data.cids).to.include(cid1)\n      expect(data.cids).to.include(cid2)\n\n      expect(transfer).to.be.an.instanceOf(Set)\n      expect(transfer).to.have.property('size', 3)\n      expect(transfer).to.include(cid1.multihash.bytes.buffer)\n      expect(transfer).to.include(cid2.multihash.bytes.buffer)\n      expect(transfer).to.include(hi.buffer)\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-message-port-protocol/test/node.js",
    "content": "\nimport './cid.spec.js'\nimport './dag.spec.js'\n"
  },
  {
    "path": "packages/ipfs-message-port-protocol/test/util.js",
    "content": "\n/* eslint-env browser */\n\nexport const ipc = () => {\n  const { port1: sender, port2: receiver } = new MessageChannel()\n  let out = true\n  const move = async (data, transfer) => {\n    await out\n    return await new Promise(resolve => {\n      receiver.onmessage = event => resolve(event.data)\n      sender.postMessage(data, transfer)\n    })\n  }\n\n  /**\n   * @template T\n   * @param {T} data\n   * @param {Iterable<Transferable>} [transfer]\n   * @returns {Promise<T>}\n   */\n  const ipcMove = async (data, transfer = []) => {\n    out = move(data, transfer)\n    return await out\n  }\n\n  return ipcMove\n}\n\n/**\n * @returns {[Promise<T>, function(T):void, function(any):void]}\n */\nexport const defer = () => {\n  const result = []\n  result.unshift(\n    new Promise((resolve, reject) => {\n      result.push(resolve, reject)\n    })\n  )\n  return result\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-protocol/tsconfig.json",
    "content": "{\n  \"extends\": \"aegir/src/config/tsconfig.aegir.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"emitDeclarationOnly\": true\n  },\n  \"include\": [\n    \"src\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../ipfs-core-types\"\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-server/.aegir.js",
    "content": "\n/** @type {import('aegir').PartialOptions} */\nexport default {\n  build: {\n    bundlesizeMax: '8KB'\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-server/CHANGELOG.md",
    "content": "# Change Log\n\nAll notable changes to this project will be documented in this file.\nSee [Conventional Commits](https://conventionalcommits.org) for commit guidelines.\n\n### [0.15.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-server-v0.15.0...ipfs-message-port-server-v0.15.1) (2023-05-25)\n\n\n### Bug Fixes\n\n* add deprecation notice to readmes ([#4362](https://www.github.com/ipfs/js-ipfs/issues/4362)) ([7b79c1b](https://www.github.com/ipfs/js-ipfs/commit/7b79c1b8df5c818dc124b346ea28330455732d5c))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.14.0 to ^0.14.1\n    * ipfs-message-port-protocol bumped from ^0.15.0 to ^0.15.1\n\n## [0.15.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-server-v0.14.0...ipfs-message-port-server-v0.15.0) (2023-01-11)\n\n\n### ⚠ BREAKING CHANGES\n\n* update multiformats to v11.x.x and related depenendcies (#4277)\n\n### Bug Fixes\n\n* update multiformats to v11.x.x and related depenendcies ([#4277](https://www.github.com/ipfs/js-ipfs/issues/4277)) ([521c84a](https://www.github.com/ipfs/js-ipfs/commit/521c84a958b04d61702577a5adce28519c1b2a3b))\n* use aegir to publish RCs ([#4284](https://www.github.com/ipfs/js-ipfs/issues/4284)) ([6d90cbf](https://www.github.com/ipfs/js-ipfs/commit/6d90cbf321a7dbf4b1084ba20f0c514dc08d8d0a))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.13.0 to ^0.14.0\n    * ipfs-message-port-protocol bumped from ^0.14.0 to ^0.15.0\n\n## [0.14.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-server-v0.13.1...ipfs-message-port-server-v0.14.0) (2022-10-24)\n\n\n### ⚠ BREAKING CHANGES\n\n* ipfs is now bundled with libp2p@0.40.x which has different config\n\n### Features\n\n* upgrade libp2p to 0.40.x ([#4237](https://www.github.com/ipfs/js-ipfs/issues/4237)) ([0cee4a4](https://www.github.com/ipfs/js-ipfs/commit/0cee4a4c55767022584dcbade0b0b9b43326f9c9))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.12.1 to ^0.13.0\n    * ipfs-message-port-protocol bumped from ^0.13.1 to ^0.14.0\n\n### [0.13.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-server-v0.13.0...ipfs-message-port-server-v0.13.1) (2022-09-21)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.12.0 to ^0.12.1\n    * ipfs-message-port-protocol bumped from ^0.13.0 to ^0.13.1\n\n## [0.13.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-server-v0.12.1...ipfs-message-port-server-v0.13.0) (2022-09-06)\n\n\n### ⚠ BREAKING CHANGES\n\n* update to libp2p@0.38.x (#4151)\n\n### deps\n\n* update to libp2p@0.38.x ([#4151](https://www.github.com/ipfs/js-ipfs/issues/4151)) ([39dbf70](https://www.github.com/ipfs/js-ipfs/commit/39dbf708ec31b263115e44f420651fa4e056a89e))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.11.0 to ^0.12.0\n    * ipfs-message-port-protocol bumped from ^0.12.0 to ^0.13.0\n\n### [0.12.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-server-v0.12.0...ipfs-message-port-server-v0.12.1) (2022-06-22)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.11.0 to ^0.11.1\n    * ipfs-message-port-protocol bumped from ^0.12.0 to ^0.12.1\n\n## [0.12.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-server-v0.11.3...ipfs-message-port-server-v0.12.0) (2022-05-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* This module is now ESM only and there return types of some methods have changed\n\n### Features\n\n* update to libp2p 0.37.x ([#4092](https://www.github.com/ipfs/js-ipfs/issues/4092)) ([74aee8b](https://www.github.com/ipfs/js-ipfs/commit/74aee8b3d78f233c3199a3e9a6c0ac628a31a433))\n\n\n### Bug Fixes\n\n* update to latest libp2p interfaces ([#4111](https://www.github.com/ipfs/js-ipfs/issues/4111)) ([4e93dd5](https://www.github.com/ipfs/js-ipfs/commit/4e93dd5d4f4be397c2b1cd8ae5d17e593493e6a9))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.3 to ^0.11.0\n    * ipfs-message-port-protocol bumped from ^0.11.3 to ^0.12.0\n\n### [0.11.3](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-server-v0.11.2...ipfs-message-port-server-v0.11.3) (2022-04-20)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.2 to ^0.10.3\n    * ipfs-message-port-protocol bumped from ^0.11.2 to ^0.11.3\n\n### [0.11.2](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-server-v0.11.1...ipfs-message-port-server-v0.11.2) (2022-03-01)\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.1 to ^0.10.2\n    * ipfs-message-port-protocol bumped from ^0.11.1 to ^0.11.2\n\n### [0.11.1](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-server-v0.11.0...ipfs-message-port-server-v0.11.1) (2022-02-06)\n\n\n### Bug Fixes\n\n* **dag:** replace custom dag walk with multiformats/traversal ([#3950](https://www.github.com/ipfs/js-ipfs/issues/3950)) ([596b1f4](https://www.github.com/ipfs/js-ipfs/commit/596b1f48a014083b1736e4ad7e746c652d2583b1))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.10.0 to ^0.10.1\n    * ipfs-message-port-protocol bumped from ^0.11.0 to ^0.11.1\n\n## [0.11.0](https://www.github.com/ipfs/js-ipfs/compare/ipfs-message-port-server-v0.10.5...ipfs-message-port-server-v0.11.0) (2022-01-27)\n\n\n### ⚠ BREAKING CHANGES\n\n* peerstore methods are now all async, the repo is migrated to v12\n\n### Features\n\n* libp2p async peerstore ([#4018](https://www.github.com/ipfs/js-ipfs/issues/4018)) ([a6b201a](https://www.github.com/ipfs/js-ipfs/commit/a6b201af2c3697430ab0ebe002dd573d185f1ac0))\n\n\n### Dependencies\n\n* The following workspace dependencies were updated\n  * dependencies\n    * ipfs-core-types bumped from ^0.9.0 to ^0.10.0\n    * ipfs-message-port-protocol bumped from ^0.10.5 to ^0.11.0\n\n## [0.10.5](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.10.4...ipfs-message-port-server@0.10.5) (2021-12-15)\n\n**Note:** Version bump only for package ipfs-message-port-server\n\n\n\n\n\n## [0.10.4](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.10.3...ipfs-message-port-server@0.10.4) (2021-11-24)\n\n**Note:** Version bump only for package ipfs-message-port-server\n\n\n\n\n\n## [0.10.3](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.10.2...ipfs-message-port-server@0.10.3) (2021-11-19)\n\n**Note:** Version bump only for package ipfs-message-port-server\n\n\n\n\n\n## [0.10.2](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.10.1...ipfs-message-port-server@0.10.2) (2021-11-12)\n\n\n### Bug Fixes\n\n* transfer set ([#3573](https://github.com/ipfs/js-ipfs/issues/3573)) ([b09a18c](https://github.com/ipfs/js-ipfs/commit/b09a18cd98883662353d116a8ff25a3ddaa48fc2))\n\n\n\n\n\n## [0.10.1](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.10.0...ipfs-message-port-server@0.10.1) (2021-09-28)\n\n**Note:** Version bump only for package ipfs-message-port-server\n\n\n\n\n\n## [0.10.0](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.9.4...ipfs-message-port-server@0.10.0) (2021-09-24)\n\n\n### Features\n\n* switch to esm ([#3879](https://github.com/ipfs/js-ipfs/issues/3879)) ([9a40109](https://github.com/ipfs/js-ipfs/commit/9a40109632e5b4837eb77a2f57dbc77fbf1fe099))\n\n\n### BREAKING CHANGES\n\n* There are no default exports and everything is now dual published as ESM/CJS\n\n\n\n\n\n### [0.9.4](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.9.3...ipfs-message-port-server@0.9.4) (2021-09-17)\n\n**Note:** Version bump only for package ipfs-message-port-server\n\n\n\n\n\n### [0.9.3](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.9.2...ipfs-message-port-server@0.9.3) (2021-09-17)\n\n**Note:** Version bump only for package ipfs-message-port-server\n\n\n\n\n\n### [0.9.2](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.9.1...ipfs-message-port-server@0.9.2) (2021-09-02)\n\n\n### Bug Fixes\n\n* remove use of instanceof for CID class ([#3847](https://github.com/ipfs/js-ipfs/issues/3847)) ([ebbb12d](https://github.com/ipfs/js-ipfs/commit/ebbb12db523c53ce8e4ddae5266cd9acb3504431))\n\n\n\n\n\n### [0.9.1](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.9.0...ipfs-message-port-server@0.9.1) (2021-08-25)\n\n**Note:** Version bump only for package ipfs-message-port-server\n\n\n\n\n\n## [0.9.0](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.8.1...ipfs-message-port-server@0.9.0) (2021-08-11)\n\n\n### Features\n\n* make ipfs.get output tarballs ([#3785](https://github.com/ipfs/js-ipfs/issues/3785)) ([1ad6001](https://github.com/ipfs/js-ipfs/commit/1ad60018d39d5b46c484756631e30e1989fd8eba))\n\n\n### BREAKING CHANGES\n\n* the output type of `ipfs.get` has changed and the `recursive` option has been removed from `ipfs.ls` since it was not supported everywhere\n\n\n\n\n\n### [0.8.1](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.8.0...ipfs-message-port-server@0.8.1) (2021-07-30)\n\n**Note:** Version bump only for package ipfs-message-port-server\n\n\n\n\n\n## [0.8.0](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.7.3...ipfs-message-port-server@0.8.0) (2021-07-27)\n\n\n### Features\n\n* upgrade to the new multiformats ([#3556](https://github.com/ipfs/js-ipfs/issues/3556)) ([d13d15f](https://github.com/ipfs/js-ipfs/commit/d13d15f022a87d04a35f0f7822142f9cb898479c))\n\n\n### BREAKING CHANGES\n\n* ipld-formats no longer supported, use multiformat BlockCodecs instead\n\nCo-authored-by: Rod Vagg <rod@vagg.org>\nCo-authored-by: achingbrain <alex@achingbrain.net>\n\n\n\n\n\n### [0.7.3](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.7.2...ipfs-message-port-server@0.7.3) (2021-06-18)\n\n**Note:** Version bump only for package ipfs-message-port-server\n\n\n\n\n\n### [0.7.2](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.7.1...ipfs-message-port-server@0.7.2) (2021-06-05)\n\n**Note:** Version bump only for package ipfs-message-port-server\n\n\n\n\n\n### [0.7.1](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.7.0...ipfs-message-port-server@0.7.1) (2021-05-26)\n\n**Note:** Version bump only for package ipfs-message-port-server\n\n\n\n\n\n## [0.7.0](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.6.3...ipfs-message-port-server@0.7.0) (2021-05-10)\n\n\n### chore\n\n* update node version in docker build ([#3603](https://github.com/ipfs/js-ipfs/issues/3603)) ([087fd1e](https://github.com/ipfs/js-ipfs/commit/087fd1eb402d1b933730e09c1d0cfb21067e9992))\n* upgrade deps with new typedefs ([#3550](https://github.com/ipfs/js-ipfs/issues/3550)) ([a418a52](https://github.com/ipfs/js-ipfs/commit/a418a521574c878d7aabd0ad2fd8d516908a3756))\n\n\n### BREAKING CHANGES\n\n* Minimum supported node version is 14\n* all core api methods now have types, some method signatures have changed, named exports are now used by the http, grpc and ipfs client modules\n\n\n\n\n\n### [0.6.3](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.6.2...ipfs-message-port-server@0.6.3) (2021-03-10)\n\n**Note:** Version bump only for package ipfs-message-port-server\n\n\n\n\n\n### [0.6.2](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.6.1...ipfs-message-port-server@0.6.2) (2021-03-09)\n\n\n### Bug Fixes\n\n* update to new aegir ([#3528](https://github.com/ipfs/js-ipfs/issues/3528)) ([49f7880](https://github.com/ipfs/js-ipfs/commit/49f78807d7e26483bd926b45cc7e0f797d77e41b))\n\n\n\n\n\n### [0.6.1](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.6.0...ipfs-message-port-server@0.6.1) (2021-02-02)\n\n**Note:** Version bump only for package ipfs-message-port-server\n\n\n\n\n\n## [0.6.0](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.5.0...ipfs-message-port-server@0.6.0) (2021-02-01)\n\n\n### chore\n\n* update deps ([#3514](https://github.com/ipfs/js-ipfs/issues/3514)) ([061d77c](https://github.com/ipfs/js-ipfs/commit/061d77cc03f40af5a3bc3590481e1e5836e7f0d8))\n\n\n### BREAKING CHANGES\n\n* ipfs-repo upgrade requires repo migration to v10\n\n\n\n\n\n## [0.5.0](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.4.3...ipfs-message-port-server@0.5.0) (2021-01-15)\n\n\n### Features\n\n* allow passing a http.Agent to the grpc client ([#3477](https://github.com/ipfs/js-ipfs/issues/3477)) ([c5f0bc5](https://github.com/ipfs/js-ipfs/commit/c5f0bc5eeee15369b7d02901035b04184a8608d2)), closes [#3474](https://github.com/ipfs/js-ipfs/issues/3474)\n\n\n\n\n\n### [0.4.3](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.4.2...ipfs-message-port-server@0.4.3) (2020-12-16)\n\n\n### Bug Fixes\n\n* regressions introduced by new releases of CID & multicodec ([#3442](https://github.com/ipfs/js-ipfs/issues/3442)) ([b5152d8](https://github.com/ipfs/js-ipfs/commit/b5152d8cc93ecc8d39fc353ea66d7eaf1661e3c0)), closes [/github.com/multiformats/js-cid/commit/0e11f035c9230e7f6d79c159ace9b80de88cb5eb#diff-25a6634263c1b1f6fc4697a04e2b9904ea4b042a89af59dc93ec1f5d44848a26](https://github.com//github.com/multiformats/js-cid/commit/0e11f035c9230e7f6d79c159ace9b80de88cb5eb/issues/diff-25a6634263c1b1f6fc4697a04e2b9904ea4b042a89af59dc93ec1f5d44848a26)\n* transfer unique set over message prort ([#3421](https://github.com/ipfs/js-ipfs/issues/3421)) ([da7bc55](https://github.com/ipfs/js-ipfs/commit/da7bc55e8dfbdc200ef43ccbf774bbc24af07785))\n\n\n\n\n\n### [0.4.2](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.4.1...ipfs-message-port-server@0.4.2) (2020-11-25)\n\n**Note:** Version bump only for package ipfs-message-port-server\n\n\n\n\n\n### [0.4.1](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.4.0...ipfs-message-port-server@0.4.1) (2020-11-16)\n\n\n### Bug Fixes\n\n* make message-port-protocol non dev dependency ([#3393](https://github.com/ipfs/js-ipfs/issues/3393)) ([cea7317](https://github.com/ipfs/js-ipfs/commit/cea7317569ed899c6a4476c17f54795e49b6db4d))\n\n\n\n\n\n## [0.4.0](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.3.0...ipfs-message-port-server@0.4.0) (2020-11-09)\n\n\n### Bug Fixes\n\n* typedef resolution & add examples that use types ([#3359](https://github.com/ipfs/js-ipfs/issues/3359)) ([dc2795a](https://github.com/ipfs/js-ipfs/commit/dc2795a4f3b515683d09967ce611bf87d5e67f86)), closes [#3356](https://github.com/ipfs/js-ipfs/issues/3356) [#3358](https://github.com/ipfs/js-ipfs/issues/3358)\n\n\n### Features\n\n* pass file name to add/addAll progress handler ([#3372](https://github.com/ipfs/js-ipfs/issues/3372)) ([69681a7](https://github.com/ipfs/js-ipfs/commit/69681a7d7a8434c11f6f10e370e324f5a3d31042)), closes [ipfs/js-ipfs-unixfs#87](https://github.com/ipfs/js-ipfs-unixfs/issues/87)\n\n\n\n\n\n## [0.3.0](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.2.2...ipfs-message-port-server@0.3.0) (2020-10-28)\n\n\n### Features\n\n* implement message-port ipfs.ls ([#3322](https://github.com/ipfs/js-ipfs/issues/3322)) ([4b8021d](https://github.com/ipfs/js-ipfs/commit/4b8021d389ac01f191d4fe87beead10088e53297))\n* type check & generate defs from jsdoc ([#3281](https://github.com/ipfs/js-ipfs/issues/3281)) ([bbcaf34](https://github.com/ipfs/js-ipfs/commit/bbcaf34111251b142273a5675f4754ff68bd9fa0))\n\n\n\n\n\n### [0.2.2](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.2.1...ipfs-message-port-server@0.2.2) (2020-09-09)\n\n**Note:** Version bump only for package ipfs-message-port-server\n\n\n\n\n\n### [0.2.1](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.2.0...ipfs-message-port-server@0.2.1) (2020-09-04)\n\n**Note:** Version bump only for package ipfs-message-port-server\n\n\n\n\n\n## [0.2.0](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.1.1...ipfs-message-port-server@0.2.0) (2020-09-03)\n\n\n### Features\n\n* store pins in datastore instead of a DAG ([#2771](https://github.com/ipfs/js-ipfs/issues/2771)) ([64b7fe4](https://github.com/ipfs/js-ipfs/commit/64b7fe41738cbe96d5a9075f0c01156c6f889c40))\n\n\n\n\n\n### [0.1.1](https://github.com/ipfs/js-ipfs/compare/ipfs-message-port-server@0.1.0...ipfs-message-port-server@0.1.1) (2020-08-24)\n\n**Note:** Version bump only for package ipfs-message-port-server\n\n\n\n\n\n# 0.1.0 (2020-08-12)\n\n\n### Features\n\n* share IPFS node between browser tabs ([#3081](https://github.com/ipfs/js-ipfs/issues/3081)) ([1b8b1b8](https://github.com/ipfs/js-ipfs/commit/1b8b1b822a252498889c54972a1f57e1fedc39d0)), closes [#3022](https://github.com/ipfs/js-ipfs/issues/3022)"
  },
  {
    "path": "packages/ipfs-message-port-server/CODE_OF_CONDUCT.md",
    "content": "# Contributor Code of Conduct\n\nThe `js-ipfs` project follows the [`IPFS Community Code of Conduct`](https://github.com/ipfs/community/blob/master/code-of-conduct.md)\n"
  },
  {
    "path": "packages/ipfs-message-port-server/CONTRIBUTING.md",
    "content": "# Contributing guidelines\n\nIPFS as a project, including js-ipfs and all of its modules, follows the [standard IPFS Community contributing guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md).\n\nWe also adhere to the [IPFS JavaScript Community contributing guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING_JS.md) which provide additional information of how to collaborate and contribute in the JavaScript implementation of IPFS.\n\nWe appreciate your time and attention for going over these. Please open an issue on [ipfs/community](https://github.com/ipfs/community) if you have any question.\n\nThank you.\n"
  },
  {
    "path": "packages/ipfs-message-port-server/COPYRIGHT",
    "content": "This project is transitioning from an MIT-only license to a dual MIT/Apache-2.0 license.\nUnless otherwise noted, all code contributed prior to 2019-11-21 and not contributed by\na user listed in [this signoff issue](https://github.com/ipfs/js-ipfs/issues/2624) is\nlicensed under MIT-only. All new contributions (and past contributions since 2019-11-21)\nare licensed under a dual MIT/Apache-2.0 license.\n"
  },
  {
    "path": "packages/ipfs-message-port-server/LICENSE",
    "content": "This project is dual licensed under MIT and Apache-2.0.\n\nMIT: https://www.opensource.org/licenses/mit\nApache-2.0: https://www.apache.org/licenses/license-2.0\n"
  },
  {
    "path": "packages/ipfs-message-port-server/LICENSE-APACHE",
    "content": "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\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless 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.\n"
  },
  {
    "path": "packages/ipfs-message-port-server/LICENSE-MIT",
    "content": "The MIT License (MIT)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "packages/ipfs-message-port-server/README.md",
    "content": "> # ⛔️ DEPRECATED: [js-IPFS](https://github.com/ipfs/js-ipfs) has been superseded by [Helia](https://github.com/ipfs/helia)\n>\n> 📚 [Learn more about this deprecation](https://github.com/ipfs/js-ipfs/issues/4336) or [how to migrate](https://github.com/ipfs/helia/wiki/Migrating-from-js-IPFS)\n>\n> ⚠️ If you continue using this repo, please note that security fixes will not be provided\n\n# ipfs-message-port-server <!-- omit in toc -->\n\n[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech)\n[![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech)\n[![codecov](https://img.shields.io/codecov/c/github/ipfs/js-ipfs.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipfs)\n[![CI](https://img.shields.io/github/actions/workflow/status/ipfs/js-ipfs/test.yml?branch=master\\&style=flat-square)](https://github.com/ipfs/js-ipfs/actions/workflows/test.yml?query=branch%3Amaster)\n\n> IPFS server library for exposing IPFS node over message port\n\n## Table of contents <!-- omit in toc -->\n\n- [Install](#install)\n  - [Browser `<script>` tag](#browser-script-tag)\n- [Usage](#usage)\n  - [Notes on Performance](#notes-on-performance)\n- [License](#license)\n- [Contribute](#contribute)\n\n## Install\n\n```console\n$ npm i ipfs-message-port-server\n```\n\n### Browser `<script>` tag\n\nLoading this module through a script tag will make it's exports available as `IpfsMessagePortServer` in the global namespace.\n\n```html\n<script src=\"https://unpkg.com/ipfs-message-port-server/dist/index.min.js\"></script>\n```\n\n## Usage\n\nThis library can wrap a JS IPFS node and expose it over the [message channel][].\nIt assumes `ipfs-message-port-client` on the other end, however it is not\nstrictly necessary anything complying with the wire protocol will do.\n\nIt provides following API subset:\n\n- [`ipfs.dag`](https://github.com/ipfs/js-ipfs/blob/master/docs/core-api/DAG.md)\n- [`ipfs.block`](https://github.com/ipfs/js-ipfs/blob/master/docs/core-api/BLOCK.md)\n- [`ipfs.add`](https://github.com/ipfs/js-ipfs/blob/master/docs/core-api/FILES.md#ipfsadddata-options)\n- [`ipfs.cat`](https://github.com/ipfs/js-ipfs/blob/master/docs/core-api/FILES.md#ipfscatipfspath-options)\n- [`ipfs.files.stat`](https://github.com/ipfs/js-ipfs/blob/master/docs/core-api/FILES.md#ipfsfilesstatpath-options)\n\nThe server is designed to run in a [SharedWorker][] (although it is possible to\nrun it in the other JS contexts). The example below illustrates running a js-ipfs\nnode in a [SharedWorker][] and exposing it to all connected ports\n\n```js\nimport { create } from 'ipfs'\nimport { IPFSService, Server } from 'ipfs-message-port-server'\n\nconst main = async () => {\n  const connections = []\n  // queue connections that occur while node was starting.\n  self.onconnect = ({ports}) => connections.push(...ports)\n\n  const ipfs = await create()\n  const service = new IPFSService(ipfs)\n  const server = new Server(service)\n\n  // connect new ports and queued ports with the server.\n  self.onconnect = ({ports}) => server.connect(ports[0])\n  for (const port of connections.splice(0)) {\n    server.connect(port)\n  }\n}\n\nmain()\n```\n\n### Notes on Performance\n\nSince the data sent over the [message channel][] is copied via\nthe [structured cloning algorithm][] it may lead to suboptimal\nresults (especially with large binary data). In order to avoid unnecessary\ncopying the server will transfer all passed [Transferable][]s which will be emptied\non the server side. This should not be a problem in general as IPFS node itself\ndoes not retain references to returned values, but is something to keep in mind\nwhen doing something custom.\n\n## License\n\nLicensed under either of\n\n- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / <http://www.apache.org/licenses/LICENSE-2.0>)\n- MIT ([LICENSE-MIT](LICENSE-MIT) / <http://opensource.org/licenses/MIT>)\n\n## Contribute\n\nContributions welcome! Please check out [the issues](https://github.com/ipfs/js-ipfs/issues).\n\nAlso 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.\n\nPlease 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).\n\nUnless 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.\n\n[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md)\n\n[message channel]: https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel\n\n[SharedWorker]: https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker\n\n[`MessagePort`]: https://developer.mozilla.org/en-US/docs/Web/API/MessagePort\n\n[structured cloning algorithm]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm\n\n[Transferable]: https://developer.mozilla.org/en-US/docs/Web/API/Transferable\n\n[Blob]: https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob\n\n[File]: https://developer.mozilla.org/en-US/docs/Web/API/File\n"
  },
  {
    "path": "packages/ipfs-message-port-server/package.json",
    "content": "{\n  \"name\": \"ipfs-message-port-server\",\n  \"version\": \"0.15.1\",\n  \"description\": \"IPFS server library for exposing IPFS node over message port\",\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"homepage\": \"https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-message-port-server#readme\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ipfs/js-ipfs.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ipfs/js-ipfs/issues\"\n  },\n  \"keywords\": [\n    \"ipfs\",\n    \"message-port\",\n    \"worker\"\n  ],\n  \"engines\": {\n    \"node\": \">=16.0.0\",\n    \"npm\": \">=7.0.0\"\n  },\n  \"type\": \"module\",\n  \"types\": \"./dist/src/index.d.ts\",\n  \"typesVersions\": {\n    \"*\": {\n      \"*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ],\n      \"src/*\": [\n        \"*\",\n        \"dist/*\",\n        \"dist/src/*\",\n        \"dist/src/*/index\"\n      ]\n    }\n  },\n  \"files\": [\n    \"src\",\n    \"dist\",\n    \"!dist/test\",\n    \"!**/*.tsbuildinfo\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/src/index.d.ts\",\n      \"import\": \"./src/index.js\"\n    },\n    \"./block\": {\n      \"types\": \"./src/block.d.ts\",\n      \"import\": \"./src/block.js\"\n    },\n    \"./core\": {\n      \"types\": \"./src/core.d.ts\",\n      \"import\": \"./src/core.js\"\n    },\n    \"./dag\": {\n      \"types\": \"./src/dag.d.ts\",\n      \"import\": \"./src/dag.js\"\n    },\n    \"./files\": {\n      \"types\": \"./src/files.d.ts\",\n      \"import\": \"./src/files.js\"\n    },\n    \"./server\": {\n      \"types\": \"./src/server.d.ts\",\n      \"import\": \"./src/server.js\"\n    },\n    \"./service\": {\n      \"types\": \"./src/service.d.ts\",\n      \"import\": \"./src/service.js\"\n    }\n  },\n  \"eslintConfig\": {\n    \"extends\": \"ipfs\",\n    \"parserOptions\": {\n      \"sourceType\": \"module\"\n    }\n  },\n  \"scripts\": {\n    \"build\": \"aegir build\",\n    \"test\": \"aegir test\",\n    \"test:chrome\": \"aegir test -t browser --cov\",\n    \"test:chrome-webworker\": \"aegir test -t webworker\",\n    \"test:firefox\": \"aegir test -t browser -- --browser firefox\",\n    \"test:firefox-webworker\": \"aegir test -t webworker -- --browser firefox\",\n    \"lint\": \"aegir lint\",\n    \"clean\": \"aegir clean\",\n    \"dep-check\": \"aegir dep-check -i ipfs-core-types\"\n  },\n  \"dependencies\": {\n    \"ipfs-core-types\": \"^0.14.1\",\n    \"ipfs-message-port-protocol\": \"^0.15.1\",\n    \"it-all\": \"^2.0.0\"\n  },\n  \"devDependencies\": {\n    \"aegir\": \"^37.11.0\",\n    \"multiformats\": \"^11.0.0\"\n  },\n  \"browser\": {\n    \"worker_threads\": false\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-server/src/block.js",
    "content": "import all from 'it-all'\nimport { encodeError } from 'ipfs-message-port-protocol/error'\nimport { decodeCID, encodeCID } from 'ipfs-message-port-protocol/cid'\nimport { encodeBlock } from 'ipfs-message-port-protocol/block'\n\n/**\n * @typedef {import('ipfs-core-types').IPFS} IPFS\n * @typedef {import('multiformats/cid').CID} CID\n * @typedef {import('ipfs-message-port-protocol/error').EncodedError} EncodedError\n * @typedef {import('ipfs-message-port-protocol/cid').EncodedCID} EncodedCID\n * @typedef {import('ipfs-message-port-protocol/block').EncodedRmResult} EncodedRmResult\n * @typedef {import('ipfs-core-types/src/block').PutOptions} PutOptions\n */\n\nexport class BlockService {\n  /**\n   * @param {IPFS} ipfs\n   */\n  constructor (ipfs) {\n    this.ipfs = ipfs\n  }\n\n  /**\n   * @typedef {object} GetResult\n   * @property {Uint8Array} block\n   * @property {Set<Transferable>} transfer\n   *\n   * @typedef {object} GetQuery\n   * @property {EncodedCID} cid\n   * @property {number} [timeout]\n   * @property {AbortSignal} [query.signal]\n   *\n   * @param {GetQuery} query\n   * @returns {Promise<GetResult>}\n   */\n  async get (query) {\n    const cid = decodeCID(query.cid)\n    const block = await this.ipfs.block.get(cid, query)\n    /** @type {Set<Transferable>} */\n    const transfer = new Set()\n    return { transfer, block: encodeBlock(block, transfer) }\n  }\n\n  /**\n   * @typedef {object} PutResult\n   * @property {EncodedCID} cid\n   * @property {Set<Transferable>} transfer\n   *\n   * @typedef {object} PutQuery\n   * @property {Uint8Array} block\n   * @property {EncodedCID|undefined} [cid]\n   *\n   * Stores input as an IPFS block.\n   *\n   * @param {PutOptions & PutQuery} query\n   * @returns {Promise<PutResult>}\n   */\n  async put (query) {\n    const input = query.block\n    const result = await this.ipfs.block.put(input, query)\n    /** @type {Set<Transferable>} */\n    const transfer = new Set()\n\n    return { transfer, cid: encodeCID(result, transfer) }\n  }\n\n  /**\n   * @typedef {object} RmQuery\n   * @property {EncodedCID[]} cids\n   * @property {boolean} [force]\n   * @property {boolean} [quiet]\n   * @property {number} [timeout]\n   * @property {AbortSignal} [signal]\n   *\n   * Remove one or more IPFS block(s).\n   * @param {RmQuery} query\n   * @returns {Promise<EncodedRmResult[]>}\n   */\n  async rm (query) {\n    /** @type {Set<Transferable>} */\n    const transfer = new Set()\n    const result = await all(\n      this.ipfs.block.rm(query.cids.map(decodeCID), query)\n    )\n\n    return result.map(entry => encodeRmEntry(entry, transfer))\n  }\n\n  /**\n   * @typedef {object} StatQuery\n   * @property {EncodedCID} cid\n   * @property {number} [timeout]\n   * @property {AbortSignal} [signal]\n   *\n   * @typedef {object} EncodedStatResult\n   * @property {EncodedCID} cid\n   * @property {number} size\n   *\n   * Gets information of a raw IPFS block.\n   *\n   * @param {StatQuery} query\n   * @returns {Promise<EncodedStatResult>}\n   */\n  async stat (query) {\n    const cid = decodeCID(query.cid)\n    const result = await this.ipfs.block.stat(cid, query)\n    return { ...result, cid: encodeCID(result.cid) }\n  }\n}\n\n/**\n * @param {object} entry\n * @param {CID} entry.cid\n * @param {Error|void} [entry.error]\n * @param {Set<Transferable>} transfer\n */\nconst encodeRmEntry = (entry, transfer) => {\n  const cid = encodeCID(entry.cid, transfer)\n  if (entry.error) {\n    return { cid, error: encodeError(entry.error) }\n  } else {\n    return { cid }\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-server/src/core.js",
    "content": "\n/* eslint-env browser */\n\nimport {\n  decodeIterable,\n  encodeIterable,\n  decodeCallback\n} from 'ipfs-message-port-protocol/core'\nimport { decodeCID, encodeCID } from 'ipfs-message-port-protocol/cid'\n\n/**\n * @typedef {import('multiformats/cid').Version} CIDVersion\n * @typedef {import('ipfs-core-types').IPFS} IPFS\n * @typedef {import('ipfs-core-types/src/root').AddOptions} AddOptions\n * @typedef {import('ipfs-core-types/src/root').AddAllOptions} AddAllOptions\n * @typedef {import('ipfs-core-types/src/root').IPFSEntry} IPFSEntry\n * @typedef {import('ipfs-message-port-protocol/src/cid').EncodedCID} EncodedCID\n * @typedef {import('ipfs-core-types/src/utils').ImportCandidateStream} ImportCandidateStream\n * @typedef {import('ipfs-core-types/src/utils').ImportCandidate} ImportCandidate\n * @typedef {import('ipfs-core-types/src/root').AddResult} AddResult\n * @typedef {import('ipfs-message-port-protocol/src/root').EncodedAddInput} EncodedAddInput\n * @typedef {import('ipfs-message-port-protocol/src/root').EncodedAddAllInput} EncodedAddAllInput\n * @typedef {import('ipfs-message-port-protocol/src/root').EncodedFileContent} EncodedFileContent\n * @typedef {import('ipfs-message-port-protocol/src/root').EncodedIPFSEntry} EncodedIPFSEntry\n */\n\n/**\n * @typedef {import('ipfs-message-port-protocol/src/core').RemoteCallback} RemoteCallback\n */\n\n/**\n * @template T\n * @typedef {import('ipfs-message-port-protocol/src/core').RemoteIterable<T>} RemoteIterable\n */\n\n/**\n * @typedef {object} AddAllInput\n * @property {EncodedAddAllInput} input\n * @property {RemoteCallback} [progressCallback]\n *\n * @typedef {object} AddInput\n * @property {EncodedAddInput} input\n * @property {RemoteCallback} [progressCallback]\n *\n * @typedef {AddInput & AddOptions} AddQuery\n * @typedef {AddAllInput & AddAllOptions} AddAllQuery\n */\n\nexport class CoreService {\n  /**\n   * @param {IPFS} ipfs\n   */\n  constructor (ipfs) {\n    this.ipfs = ipfs\n  }\n\n  /**\n   * @param {AddAllQuery} query\n   */\n  addAll (query) {\n    const { input } = query\n    const {\n      chunker,\n      cidVersion,\n      enableShardingExperiment,\n      hashAlg,\n      onlyHash,\n      pin,\n      progressCallback,\n      rawLeaves,\n      shardSplitThreshold,\n      trickle,\n      wrapWithDirectory,\n      timeout,\n      signal\n    } = query\n\n    let progress\n\n    if (progressCallback) {\n      const fn = decodeCallback(progressCallback)\n      /** @type {import('ipfs-core-types/src/root').AddProgressFn} */\n      progress = (bytes, fileName) => { fn([bytes, fileName]) }\n    }\n\n    /** @type {AddAllOptions} */\n    const options = {\n      chunker,\n      cidVersion,\n      enableShardingExperiment,\n      hashAlg,\n      onlyHash,\n      pin,\n      rawLeaves,\n      shardSplitThreshold,\n      trickle,\n      wrapWithDirectory,\n      timeout,\n      progress,\n      signal\n    }\n\n    const content = decodeAddAllInput(input)\n    return encodeAddAllResult(this.ipfs.addAll(content, options))\n  }\n\n  /**\n   * @param {AddQuery} query\n   */\n  async add (query) {\n    const { input } = query\n    const {\n      chunker,\n      cidVersion,\n      hashAlg,\n      onlyHash,\n      pin,\n      progressCallback,\n      rawLeaves,\n      trickle,\n      wrapWithDirectory,\n      timeout,\n      signal\n    } = query\n\n    let progress\n\n    if (progressCallback) {\n      const fn = decodeCallback(progressCallback)\n      /** @type {import('ipfs-core-types/src/root').AddProgressFn} */\n      progress = (bytes, fileName) => { fn([bytes, fileName]) }\n    }\n\n    /** @type {AddOptions} */\n    const options = {\n      chunker,\n      cidVersion,\n      hashAlg,\n      onlyHash,\n      pin,\n      rawLeaves,\n      trickle,\n      wrapWithDirectory,\n      timeout,\n      progress,\n      signal\n    }\n\n    const content = decodeAddInput(input)\n    return encodeAddResult(await this.ipfs.add(content, options))\n  }\n\n  /**\n   * @typedef {object} CatQuery\n   * @property {string|EncodedCID} path\n   * @property {number} [offset]\n   * @property {number} [length]\n   * @property {number} [timeout]\n   * @property {AbortSignal} [signal]\n   *\n   * @param {CatQuery} query\n   */\n  cat (query) {\n    const { path, offset, length, timeout, signal } = query\n    const location = typeof path === 'string' ? path : decodeCID(path)\n    const content = this.ipfs.cat(location, { offset, length, timeout, signal })\n    return encodeCatResult(content)\n  }\n\n  /**\n   * @typedef {object} LsQuery\n   * @property {string|EncodedCID} path\n   * @property {boolean} [preload]\n   * @property {boolean} [recursive]\n   * @property {number} [timeout]\n   * @property {AbortSignal} [signal]\n   *\n   * @param {LsQuery} query\n   */\n  ls (query) {\n    const { path, recursive, preload, timeout, signal } = query\n    const location = typeof path === 'string' ? path : decodeCID(path)\n    const entries = this.ipfs.ls(location, { recursive, preload, timeout, signal })\n    return encodeLsResult(entries)\n  }\n}\n\n/**\n * @param {EncodedAddAllInput} input\n * @returns {ImportCandidateStream}\n */\nconst decodeAddAllInput = input =>\n  decodeIterable(input, decodeFileInput)\n\n/**\n * @param {*} input\n */\nconst decodeAddInput = input =>\n  matchInput(\n    input,\n    data => {\n      if (data.type === 'RemoteIterable') {\n        return { content: decodeIterable(data, decodeFileInput) }\n      } else {\n        return decodeFileInput(data)\n      }\n    }\n  )\n\n/**\n *\n * @param {*} input\n * @returns\n */\nconst decodeFileInput = input =>\n  matchInput(input, file => ({\n    ...file,\n    content: file.content && decodeFileContent(file.content)\n  }))\n\n/**\n * @param {EncodedFileContent} content\n */\nconst decodeFileContent = content =>\n  matchInput(content, input => decodeIterable(input, identity))\n\n/**\n * @template I, O\n * @param {I} input\n * @param {(input: any) => O} decode\n * @returns {I | O}\n */\nconst matchInput = (input, decode) => {\n  if (\n    typeof input === 'string' ||\n    input instanceof ArrayBuffer ||\n    input instanceof Blob ||\n    ArrayBuffer.isView(input)\n  ) {\n    return input\n  } else {\n    return decode(input)\n  }\n}\n\n/**\n * @param {AsyncIterable<AddResult>} out\n */\nconst encodeAddAllResult = out => {\n  /** @type {Set<Transferable>} */\n  const transfer = new Set()\n  return {\n    data: encodeIterable(out, encodeFileOutput, transfer),\n    transfer\n  }\n}\n\n/**\n * @param {AddResult} out\n */\nconst encodeAddResult = out => {\n  /** @type {Set<Transferable>} */\n  const transfer = new Set()\n  return {\n    data: encodeFileOutput(out, transfer),\n    transfer\n  }\n}\n\n/**\n * @param {AsyncIterable<Uint8Array>} content\n */\nconst encodeCatResult = content => {\n  /** @type {Set<Transferable>} */\n  const transfer = new Set()\n  return { data: encodeIterable(content, moveBuffer, transfer), transfer }\n}\n\n/**\n * @param {AsyncIterable<IPFSEntry>} entries\n */\nconst encodeLsResult = entries => {\n  /** @type {Set<Transferable>} */\n  const transfer = new Set()\n  return { data: encodeIterable(entries, encodeLsEntry, transfer), transfer }\n}\n\n/**\n * @param {IPFSEntry} entry\n */\nconst encodeLsEntry = ({ name, path, size, cid, type, mode, mtime }) => ({\n  cid: encodeCID(cid),\n  type,\n  name,\n  path,\n  mode,\n  mtime,\n  size\n})\n\n/**\n * Adds underlying `ArrayBuffer` to the transfer list.\n *\n * @param {Uint8Array} buffer\n * @param {Set<Transferable>} transfer\n * @returns {Uint8Array}\n */\nconst moveBuffer = (buffer, transfer) => {\n  transfer.add(buffer.buffer)\n  return buffer\n}\n\n/**\n * @param {AddResult} file\n * @param {Set<Transferable>} _transfer\n */\nconst encodeFileOutput = (file, _transfer) => ({\n  ...file,\n  cid: encodeCID(file.cid)\n})\n\n/**\n * @template T\n * @param {T} v\n * @returns {T}\n */\nconst identity = v => v\n"
  },
  {
    "path": "packages/ipfs-message-port-server/src/dag.js",
    "content": "import { encodeCID, decodeCID } from 'ipfs-message-port-protocol/cid'\nimport { decodeNode, encodeNode } from 'ipfs-message-port-protocol/dag'\n\n/**\n * @typedef {import('ipfs-core-types').IPFS} IPFS\n * @typedef {import('multiformats/cid').CID} CID\n * @typedef {import('ipfs-message-port-protocol/cid').EncodedCID} EncodedCID\n * @typedef {import('ipfs-message-port-protocol/dag').EncodedDAGNode} EncodedDAGNode\n * @typedef {import('ipfs-core-types/src/dag').PutOptions} PutOptions\n */\n\nexport class DAGService {\n  /**\n   * @param {IPFS} ipfs\n   */\n  constructor (ipfs) {\n    this.ipfs = ipfs\n  }\n\n  /**\n   * @typedef {object} PutDag\n   * @property {EncodedDAGNode} dagNode\n   * @property {EncodedCID} [encodedCid]\n   *\n   * @param {PutOptions & PutDag} query\n   * @returns {Promise<EncodedCID>}\n   */\n  async put (query) {\n    const dagNode = decodeNode(query.dagNode)\n    const cid = await this.ipfs.dag.put(dagNode, query)\n\n    return encodeCID(cid)\n  }\n\n  /**\n   * @typedef {object} EncodedGetResult\n   * @property {Set<Transferable>} transfer\n   * @property {string} [remainderPath]\n   * @property {EncodedDAGNode} value\n   *\n   * @typedef {object} GetDAG\n   * @property {EncodedCID} cid\n   * @property {string} [path]\n   * @property {boolean} [localResolve]\n   * @property {number} [timeout]\n   * @property {AbortSignal} [signal]\n   *\n   * @param {GetDAG} query\n   * @returns {Promise<EncodedGetResult>}\n   */\n  async get (query) {\n    const { cid, path, localResolve, timeout, signal } = query\n    const { value, remainderPath } = await this.ipfs.dag.get(\n      decodeCID(cid),\n      {\n        path,\n        localResolve,\n        timeout,\n        signal\n      }\n    )\n\n    /** @type {Set<Transferable>} */\n    const transfer = new Set()\n    return { remainderPath, value: encodeNode(value, transfer), transfer }\n  }\n\n  /**\n   * @typedef {object} ResolveQuery\n   * @property {EncodedCID|string} cid\n   * @property {string} [path]\n   * @property {number} [timeout]\n   * @property {AbortSignal} [signal]\n   *\n   * @typedef {object} EncodedResolveResult\n   * @property {EncodedCID} cid\n   * @property {string} [remainderPath]\n   *\n   * @param {ResolveQuery} query\n   * @returns {Promise<EncodedResolveResult>}\n   */\n  async resolve (query) {\n    const { cid, remainderPath } =\n      await this.ipfs.dag.resolve(decodePathOrCID(query.cid), query)\n\n    return {\n      cid: encodeCID(cid),\n      remainderPath\n    }\n  }\n}\n\n/**\n * @param {EncodedCID|string} input\n * @returns {CID|string}\n */\nconst decodePathOrCID = (input) => {\n  if (typeof input === 'string') {\n    return input\n  } else {\n    return decodeCID(input)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-server/src/files.js",
    "content": "\n/* eslint-env browser */\n\nimport { encodeCID } from 'ipfs-message-port-protocol/cid'\n\n/**\n * @typedef {import('ipfs-core-types').IPFS} IPFS\n * @typedef {import('ipfs-core-types/src/files').StatOptions} StatOptions\n * @typedef {import('ipfs-message-port-protocol/src/files').EncodedStat} EncodedStat\n */\n\nexport class FilesService {\n  /**\n   *\n   * @param {IPFS} ipfs\n   */\n  constructor (ipfs) {\n    this.ipfs = ipfs\n  }\n\n  /**\n   * @typedef {object} StatQuery\n   * @property {string} path\n   *\n   * @param {StatOptions & StatQuery} input\n   */\n  async stat (input) {\n    const stat = await this.ipfs.files.stat(input.path, input)\n    /** @type {Set<Transferable>} */\n    const transfer = new Set()\n    return { stat: { ...stat, cid: encodeCID(stat.cid, transfer) }, transfer }\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-server/src/index.js",
    "content": "\n/* eslint-env browser */\n\nexport { DAGService } from './dag.js'\nexport { CoreService } from './core.js'\nexport { FilesService } from './files.js'\nexport { BlockService } from './block.js'\nexport { IPFSService } from './service.js'\nexport { Server } from './server.js'\n"
  },
  {
    "path": "packages/ipfs-message-port-server/src/server.js",
    "content": "\n/* eslint-env browser */\n\nimport { encodeError } from 'ipfs-message-port-protocol/error'\n\n/**\n * @typedef {import('ipfs-message-port-protocol/src/data').EncodedError} EncodedError\n */\n\n/**\n * @template X, T\n * @typedef {import('ipfs-message-port-protocol/src/data').Result<X, T>} Result\n */\n\n/**\n * @template T\n * @typedef {import('ipfs-message-port-protocol/src/rpc').ProcedureNames<T>} ProcedureNames\n */\n\n/**\n * @template T\n * @typedef {import('ipfs-message-port-protocol/src/rpc').Method<T>} Method\n */\n\n/**\n * @template T\n * @typedef {import('ipfs-message-port-protocol/src/rpc').Namespace<T>} Namespace\n */\n\n/**\n * @template T\n * @typedef {import('ipfs-message-port-protocol/src/rpc').ServiceQuery<T>} ServiceQuery\n */\n\n/**\n * @template T\n * @typedef {import('ipfs-message-port-protocol/src/rpc').Return<T>} Return\n */\n\n/**\n * @template T\n * @typedef {import('ipfs-message-port-protocol/src/rpc').RPCQuery<T>} RPCQuery\n */\n\n/**\n * @template T\n * @typedef {import('ipfs-message-port-protocol/src/rpc').Inn<T>} Inn\n */\n\n/**\n * @template T\n * @typedef {import('ipfs-message-port-protocol/src/rpc').Out<T>} Out\n */\n\n/**\n * @template T\n * @typedef {object} QueryMessage\n * @property {'query'} type\n * @property {Namespace<T>} namespace\n * @property {Method<T>} method\n * @property {string} id\n * @property {Inn<T>} input\n */\n\n/**\n * @typedef {object} AbortMessage\n * @property {'abort'} type\n * @property {string} id\n */\n\n/**\n * @typedef {object} TransferOptions\n * @property {Set<Transferable>} [transfer]\n */\n\n/**\n * @template O\n * @typedef {O & TransferOptions} QueryResult\n */\n\n/**\n * @template T\n * @typedef {AbortMessage|QueryMessage<T>} Message\n */\n\n/**\n * @template T, K\n * @typedef {import('ipfs-message-port-protocol/src/rpc').NamespacedQuery<T, K>} NamespacedQuery\n */\n\n/**\n * Represents a client query received on the server.\n *\n * @template T\n * @extends {ServiceQuery<T>}\n */\n\nexport class Query {\n  /**\n   * @param {Namespace<T>} namespace\n   * @param {Method<T>} method\n   * @param {Inn<T>} input\n   */\n  constructor (namespace, method, input) {\n    /** @type {Return<any>} */\n    this.result = new Promise((resolve, reject) => {\n      this.succeed = resolve\n      this.fail = reject\n      this.namespace = namespace\n      this.method = method\n      this.input = input\n\n      this.abortController = new AbortController()\n      this.signal = this.abortController.signal\n    })\n  }\n\n  /**\n   * Aborts this query if it is still pending.\n   */\n  abort () {\n    this.abortController.abort()\n    this.fail(new AbortError())\n  }\n}\n\n/**\n * @template T\n * @typedef {import('ipfs-message-port-protocol/src/rpc').MultiService<T>} MultiService\n */\n\n/**\n * Server wraps `T` service and executes queries received from connected ports.\n *\n * @template T\n */\n\nexport class Server {\n  /**\n   * @param {MultiService<T>} services\n   */\n  constructor (services) {\n    this.services = services\n    /** @type {Record<string, Query<T>>} */\n    this.queries = Object.create(null)\n  }\n\n  /**\n   * @param {MessagePort} port\n   */\n  connect (port) {\n    port.addEventListener('message', this)\n    port.start()\n  }\n\n  /**\n   * @param {MessagePort} port\n   */\n  disconnect (port) {\n    port.removeEventListener('message', this)\n    port.close()\n  }\n\n  /**\n   * Handles messages received from connected clients\n   *\n   * @param {MessageEvent} event\n   * @returns {void}\n   */\n  handleEvent (event) {\n    /** @type {Message<T>} */\n    const data = event.data\n    switch (data.type) {\n      case 'query': {\n        this.handleQuery(\n          data.id,\n          new Query(data.namespace, data.method, data.input),\n          /** @type {MessagePort} */\n          (event.target)\n        )\n        return undefined\n      }\n      case 'abort': {\n        return this.abort(data.id)\n      }\n      default: {\n        throw new UnsupportedMessageError(event)\n      }\n    }\n  }\n\n  /**\n   * Abort query for the given id.\n   *\n   * @param {string} id\n   */\n  abort (id) {\n    const query = this.queries[id]\n    if (query) {\n      delete this.queries[id]\n      query.abort()\n    }\n  }\n\n  /**\n   * Handles query received from the client.\n   *\n   * @param {string} id\n   * @param {Query<T>} query\n   * @param {MessagePort} port\n   */\n  async handleQuery (id, query, port) {\n    this.queries[id] = query\n    await this.run(query)\n    delete this.queries[id]\n    if (!query.signal.aborted) {\n      try {\n        const value = await query.result\n        const transfer = value.transfer\n\n        // Don't need the transfer value in the result\n        delete value.transfer\n\n        port.postMessage(\n          { type: 'result', id, result: { ok: true, value } },\n          transfer\n        )\n      } catch (/** @type {any} */ error) {\n        port.postMessage({\n          type: 'result',\n          id,\n          result: { ok: false, error: encodeError(error) }\n        })\n      }\n    }\n  }\n\n  /**\n   * @param {Query<T>} query\n   * @returns {void}\n   */\n  run (query) {\n    const { services } = this\n    const { namespace, method } = query\n\n    const service = services[namespace]\n    if (service) {\n      if (typeof service[method] === 'function') {\n        try {\n          const result = service[method]({ ...query.input, signal: query.signal })\n          Promise.resolve(result).then(query.succeed, query.fail)\n        } catch (/** @type {any} */ error) {\n          query.fail(error)\n        }\n      } else {\n        query.fail(new RangeError(`Method '${String(method)}' is not found`))\n      }\n    } else {\n      query.fail(new RangeError(`Namespace '${String(namespace)}' is not found`))\n    }\n  }\n\n  /**\n   * @param {RPCQuery<T>} data\n   * @returns {Out<T>}\n   */\n  execute (data) {\n    const query = new Query(data.namespace, data.method, data.input)\n    this.run(query)\n\n    return query.result\n  }\n}\n\nexport class UnsupportedMessageError extends RangeError {\n  /**\n   * @param {MessageEvent} event\n   */\n  constructor (event) {\n    super('Unexpected message was received by the server')\n    this.event = event\n  }\n\n  get name () {\n    return this.constructor.name\n  }\n}\n\nexport const AbortError = class AbortError extends Error {\n  get name () {\n    return this.constructor.name\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-server/src/service.js",
    "content": "\n/* eslint-env browser */\n\nimport { DAGService } from './dag.js'\nimport { CoreService } from './core.js'\nimport { FilesService } from './files.js'\nimport { BlockService } from './block.js'\n\n/**\n * @typedef {import('ipfs-core-types').IPFS} IPFS\n */\n\nexport class IPFSService {\n  /**\n   * @param {IPFS} ipfs\n   */\n  constructor (ipfs) {\n    this.dag = new DAGService(ipfs)\n    this.core = new CoreService(ipfs)\n    this.files = new FilesService(ipfs)\n    this.block = new BlockService(ipfs)\n  }\n}\n"
  },
  {
    "path": "packages/ipfs-message-port-server/test/basic.spec.js",
    "content": "\n/* eslint-env mocha */\n\nimport { Server } from '../src/server.js'\nimport { IPFSService } from '../src/index.js'\nimport { expect } from 'aegir/chai'\n\ndescribe('dag', function () {\n  this.timeout(10 * 1000)\n\n  describe('Server', () => {\n    it('IPFSService', () => {\n      expect(IPFSService).to.be.a('function')\n      const service = new IPFSService()\n      expect(service).to.have.property('dag')\n      expect(service)\n        .to.have.nested.property('dag.put')\n        .be.a('function')\n      expect(service)\n        .to.have.nested.property('dag.get')\n        .be.a('function')\n    })\n    it('Server', () => {\n      expect(Server).to.be.a('function')\n      const service = new IPFSService()\n      const server = new Server(service)\n\n      expect(server)\n        .to.have.property('connect')\n        .be.a('function')\n\n      expect(server)\n        .to.have.property('disconnect')\n        .be.a('function')\n\n      expect(server)\n        .to.have.property('execute')\n        .to.be.a('function')\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-message-port-server/test/node.js",
    "content": "\n"
  },
  {
    "path": "packages/ipfs-message-port-server/test/transfer.spec.js",
    "content": "\n/* eslint-env mocha */\n\nimport { encodeCID } from 'ipfs-message-port-protocol/cid'\nimport { CID } from 'multiformats/cid'\nimport { Server } from '../src/server.js'\nimport { IPFSService } from '../src/index.js'\nimport { MessageChannel } from 'worker_threads'\n\ndescribe('Server', function () {\n  this.timeout(10 * 1000)\n\n  it('should be able to transfer multiple of the same CID instances', () => {\n    const cid = CID.parse('QmSnuWmxptJZdLJpKRarxBMS2Ju2oANVrgbr2xWbie9b2D')\n\n    return new Promise((resolve, reject) => {\n      const channel = globalThis.MessageChannel\n        ? new globalThis.MessageChannel()\n        : new MessageChannel()\n\n      channel.port1.onmessageerror = reject\n      channel.port1.onmessage = event => {\n        channel.port1.close()\n        channel.port2.close()\n\n        const result = event.data.result\n        result.ok ? resolve(result.value) : reject(new Error(result.error.message))\n      }\n\n      const service = new IPFSService()\n      const server = new Server(service)\n      const transfer = new Set()\n\n      server.run = a => a\n      server.handleQuery(\n        '',\n        {\n          result: {\n            value: [encodeCID(cid, transfer), encodeCID(cid, transfer)],\n            transfer: transfer\n          },\n          signal: { aborted: false }\n        },\n        channel.port2\n      )\n    })\n  })\n})\n"
  },
  {
    "path": "packages/ipfs-message-port-server/tsconfig.json",
    "content": "{\n  \"extends\": \"aegir/src/config/tsconfig.aegir.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"emitDeclarationOnly\": true\n  },\n  \"include\": [\n    \"src\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"../ipfs-core-types\"\n    },\n    {\n      \"path\": \"../ipfs-message-port-protocol\"\n    }\n  ]\n}\n"
  },
  {
    "path": "scripts/node-globals.js",
    "content": "// @ts-nocheck\nexport const { Buffer } = require('buffer')\nexport const process = require('process/browser')\n"
  }
]