diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..dc8bb6aa --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @DataDog/dd-trace-js @DataDog/libdatadog diff --git a/.github/actions/build-test-wasm/action.yaml b/.github/actions/build-test-wasm/action.yaml new file mode 100644 index 00000000..90cafd52 --- /dev/null +++ b/.github/actions/build-test-wasm/action.yaml @@ -0,0 +1,39 @@ +name: 'Build/Test WASM' +description: 'A simple composite GitHub Action sets-up WASM; then test & build relevant crates' +inputs: + crate: + description: 'The crate name. Must be in ./crates' + required: true +runs: + using: 'composite' + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + - name: Setup Node.js + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - run: yarn install + shell: bash + - name: Install wasm-pack + run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + shell: bash + - name: Build WASM + run: | + mkdir -p ./prebuilds/${{ inputs.crate }} + wasm-pack build --target nodejs ./crates/${{ inputs.crate }} --out-dir ../../prebuilds/${{ inputs.crate }} + shell: bash + - name: Test WASM + # The pipeline crate's tests are top-level node:test suites that need + # --test-force-exit (the wasm exporter keeps the event loop alive after + # a flush); the other wasm crates use plain test/wasm// scripts. + run: | + if [ "${{ inputs.crate }}" = "pipeline" ]; then + node --test --test-force-exit test/pipeline.js + else + node test-wasm.js ${{ inputs.crate }} + fi + shell: bash + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: prebuilds-wasm-${{ inputs.crate }} + if-no-files-found: ignore + path: ./prebuilds/* diff --git a/.github/chainguard/self.github.release.push-tags.sts.yaml b/.github/chainguard/self.github.release.push-tags.sts.yaml new file mode 100644 index 00000000..e8074403 --- /dev/null +++ b/.github/chainguard/self.github.release.push-tags.sts.yaml @@ -0,0 +1,12 @@ +issuer: https://token.actions.githubusercontent.com + +subject: repo:DataDog/libdatadog-nodejs:environment:npm + +claim_pattern: + event_name: push + job_workflow_ref: DataDog/libdatadog-nodejs/\.github/workflows/release\.yml@refs/heads/v[0-9]+\.x + ref: refs/heads/v[0-9]+\.x + repository: DataDog/libdatadog-nodejs + +permissions: + contents: write diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..abe2e959 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,70 @@ +# Dependabot version updates +# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + cooldown: + default-days: 5 + exclude: + - "@datadog/*" + groups: + gh-actions-packages: + patterns: + - "*" + + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + versioning-strategy: "increase" + labels: + - dependabot + - dependencies + - javascript + - semver-patch + groups: + patch-updates: + update-types: + - "patch" + minor-updates: + update-types: + - "minor" + + - package-ecosystem: "npm" + directory: "/test/crashtracker" + schedule: + interval: "weekly" + versioning-strategy: "increase" + labels: + - dependabot + - dependencies + - javascript + - semver-patch + groups: + patch-updates: + update-types: + - "patch" + minor-updates: + update-types: + - "minor" + + - package-ecosystem: "cargo" + directory: "/" + schedule: + interval: "weekly" + labels: + - dependabot + - dependencies + - rust + - semver-patch + groups: + patch-updates: + update-types: + - "patch" + minor-updates: + update-types: + - "minor" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 69903b13..de179fb7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,14 +7,45 @@ on: - main jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + - run: yarn install + - run: yarn install + working-directory: test/crashtracker + - run: yarn lint + + build-test-wasm: + runs-on: ubuntu-latest + strategy: + matrix: + crate: + - library_config + - datadog-js-zstd + - pipeline + - sketches + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - name: 'Use composite action' + uses: ./.github/actions/build-test-wasm + with: + crate: '${{ matrix.crate }}' + build: uses: Datadog/action-prebuildify/.github/workflows/build.yml@main + needs: build-test-wasm with: - package-manager: 'npm' + package-manager: 'yarn' cache: false - min-node-version: 14 + min-node-version: 18 rust: true - only: linux-arm64,linux-x64,linuxmusl-arm64,linuxmusl-x64 + only: darwin-arm64,darwin-x64,linux-arm64,linux-x64 + # Need this, now that libdatadog packages libunwind as a submodule + prebuild: '(command -v apk >/dev/null && apk add autoconf automake libtool) || (command -v apt-get >/dev/null && apt-get update && apt-get install -y autoconf automake libtool) || true' + package-size: runs-on: ubuntu-latest needs: build @@ -22,11 +53,11 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - name: Setup Node.js - uses: actions/setup-node@v4 - - run: yarn + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + - run: yarn install - name: Compute module size tree and report uses: qard/heaviest-objects-in-the-universe@v1 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 56955405..7533785d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,28 +5,65 @@ on: branches: - v0.x -concurrency: - group: ${{ github.workflow }}-${{ github.ref || github.run_id }} - cancel-in-progress: true - jobs: + build-test-wasm: + runs-on: ubuntu-latest + strategy: + matrix: + crate: + - library_config + - datadog-js-zstd + - pipeline + - sketches + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - name: 'Use composite action' + uses: ./.github/actions/build-test-wasm + with: + crate: '${{ matrix.crate }}' + + build: + uses: Datadog/action-prebuildify/.github/workflows/build.yml@main + needs: build-test-wasm + with: + package-manager: 'yarn' + cache: false + min-node-version: 18 + rust: true + only: darwin-arm64,darwin-x64,linux-arm64,linux-x64 + publish: runs-on: ubuntu-latest + needs: build environment: npm - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + permissions: + id-token: write # Required for OIDC + contents: read outputs: pkgjson: ${{ steps.pkg.outputs.json }} steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v3 + - uses: DataDog/dd-octo-sts-action@96a25462dbcb10ebf0bfd6e2ccc917d2ab235b9a # v1.0.4 + id: octo-sts + with: + scope: DataDog/libdatadog-nodejs + policy: self.github.release.push-tags + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false # drop GITHUB_TOKEN so the dd-octo-sts token is used for the tag push + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: prebuilds + path: prebuilds + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: + node-version: '24' registry-url: 'https://registry.npmjs.org' + - run: chmod -R +x ./prebuilds - run: npm publish - id: pkg run: | content=`cat ./package.json | tr '\n' ' '` - echo "::set-output name=json::$content" + echo "json=$content" >> $GITHUB_OUTPUT - run: | git tag v${{ fromJson(steps.pkg.outputs.json).version }} - git push origin v${{ fromJson(steps.pkg.outputs.json).version }} + git push https://x-access-token:${{ steps.octo-sts.outputs.token }}@github.com/${{ github.repository }}.git v${{ fromJson(steps.pkg.outputs.json).version }} diff --git a/.yarnrc b/.yarnrc new file mode 100644 index 00000000..4f14322d --- /dev/null +++ b/.yarnrc @@ -0,0 +1 @@ +--ignore-engines true diff --git a/Cargo.lock b/Cargo.lock index 547dde88..658e86be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,260 +1,264 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] -name = "addr2line" -version = "0.22.0" +name = "adler2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ - "gimli 0.29.0", + "memchr", ] [[package]] -name = "adler" -version = "1.0.2" +name = "allocator-api2" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] -name = "ahash" -version = "0.7.8" +name = "android_system_properties" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ - "getrandom", - "once_cell", - "version_check", + "libc", ] [[package]] -name = "aho-corasick" -version = "1.1.3" +name = "anyhow" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" [[package]] -name = "android-tzdata" -version = "0.1.1" +name = "arc-swap" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" +checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +dependencies = [ + "rustversion", +] [[package]] -name = "android_system_properties" -version = "0.1.5" +name = "async-trait" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ - "libc", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "anyhow" -version = "1.0.86" +name = "atomic-waker" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.3.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.8.1" +version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae74d9bd0a7530e8afd1770739ad34b36838829d6ad61818f9230f683f5ad77" +checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" dependencies = [ "aws-lc-sys", - "mirai-annotations", - "paste", "zeroize", ] [[package]] name = "aws-lc-sys" -version = "0.20.1" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f0e249228c6ad2d240c2dc94b714d711629d52bad946075d8e9b2f5391f0703" +checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" dependencies = [ - "bindgen", "cc", "cmake", "dunce", "fs_extra", - "libc", - "paste", ] [[package]] -name = "backtrace" -version = "0.3.73" +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "blazesym" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48ceccc54b9c3e60e5f36b0498908c8c0f87387229cb0e0e5d65a074e00a8ba4" dependencies = [ - "addr2line", - "cc", - "cfg-if", + "cpp_demangle 0.5.1", + "gimli", "libc", + "memmap2", "miniz_oxide", - "object", "rustc-demangle", ] [[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "base64" -version = "0.22.1" +name = "block-buffer" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] [[package]] -name = "bindgen" -version = "0.69.4" +name = "block2" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" dependencies = [ - "bitflags", - "cexpr", - "clang-sys", - "itertools", - "lazy_static", - "lazycell", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 2.0.72", - "which", + "objc2", ] [[package]] -name = "bitflags" -version = "2.6.0" +name = "borrow-or-share" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" [[package]] -name = "blazesym" -version = "0.2.0-rc.0" -source = "git+https://github.com/libbpf/blazesym.git?rev=v0.2.0-rc.0#2f393f66a448f46ea71889e81a8866799762463d" -dependencies = [ - "cpp_demangle", - "gimli 0.30.0", - "libc", - "miniz_oxide", - "rustc-demangle", -] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] -name = "bumpalo" -version = "3.16.0" +name = "bytes" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] -name = "byteorder" -version = "1.5.0" +name = "cadence" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "5ca08f6db9f0c963249cf23a27820f9d973d2acaad4d6bfaeb858b58ebd58a6a" +dependencies = [ + "crossbeam-channel", +] [[package]] -name = "bytes" -version = "1.7.1" +name = "cast" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.1.8" +version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504bdec147f2cc13c8b57ed9401fd8a147cc66b67ad5cb241394244f2c947549" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ + "find-msvc-tools", "jobserver", "libc", + "shlex", ] [[package]] -name = "cexpr" -version = "0.6.0" +name = "cesu8" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ - "android-tzdata", "iana-time-zone", "num-traits", "serde", - "windows-targets 0.52.6", + "windows-link 0.2.1", ] [[package]] -name = "clang-sys" -version = "1.8.1" +name = "cmake" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ - "glob", - "libc", - "libloading", + "cc", ] [[package]] -name = "cmake" -version = "0.1.50" +name = "combine" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ - "cc", + "bytes", + "memchr", ] [[package]] -name = "collector" -version = "0.1.0" +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" dependencies = [ - "collector 0.1.0 (git+https://github.com/DataDog/libdatadog.git?branch=rochdev/collector)", - "neon", + "cfg-if", + "wasm-bindgen", ] [[package]] -name = "collector" -version = "0.1.0" -source = "git+https://github.com/DataDog/libdatadog.git?branch=rochdev/collector#c0ac24e3f58c7b27d50c7593ca9f465a32ac9682" +name = "const_format" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" dependencies = [ - "hyper 0.14.30", - "once_cell", - "rmp", - "rmp-serde", - "serde", - "serde_json", - "tokio", + "const_format_proc_macros", + "konst", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", ] [[package]] @@ -268,9 +272,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.4" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", "libc", @@ -278,212 +282,121 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpp_demangle" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8227005286ec39567949b33df9896bcadfa6051bccca2488129f108ca23119" +checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253" dependencies = [ "cfg-if", ] [[package]] -name = "crashtracker" -version = "0.1.0" -dependencies = [ - "anyhow", - "datadog-crashtracker", - "napi", - "napi-derive", -] - -[[package]] -name = "crc32fast" -version = "1.4.2" +name = "cpp_demangle" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "0667304c32ea56cb4cd6d2d7c0cfe9a2f8041229db8c033af7f8d69492429def" dependencies = [ "cfg-if", ] [[package]] -name = "ctor" -version = "0.2.8" +name = "cpufeatures" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ - "quote", - "syn 2.0.72", + "libc", ] [[package]] -name = "data-pipeline" -version = "6.0.0" -source = "git+https://github.com/DataDog/libdatadog.git?branch=julio/nodejs-integration#bf883f325a716a3b28661224d496d31a3157abdf" +name = "crashtracker" +version = "0.2.0" dependencies = [ "anyhow", - "bytes", - "datadog-trace-normalization", - "datadog-trace-protobuf", - "datadog-trace-utils", - "ddcommon 6.0.0", - "futures", - "hyper 0.14.30", - "log", - "rmp-serde", - "tokio", + "libdd-crashtracker", + "napi", + "napi-derive", + "rustls", + "serde_json", ] [[package]] -name = "datadog-crashtracker" -version = "12.0.0" -source = "git+https://github.com/DataDog/libdatadog.git?branch=main#16528ffee456f7af5fe9ad80a6294fb5dcd38918" +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ - "anyhow", - "backtrace", - "blazesym", - "chrono", - "ddcommon 12.0.0", - "ddtelemetry", - "http 0.2.12", - "hyper 0.14.30", - "libc", - "nix", - "os_info", - "page_size", - "portable-atomic", - "rand", - "serde", - "serde_json", - "tokio", - "uuid", + "crossbeam-utils", ] [[package]] -name = "datadog-ddsketch" -version = "12.0.0" -source = "git+https://github.com/DataDog/libdatadog.git?branch=main#16528ffee456f7af5fe9ad80a6294fb5dcd38918" -dependencies = [ - "prost", -] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] -name = "datadog-trace-normalization" -version = "6.0.0" -source = "git+https://github.com/DataDog/libdatadog.git?branch=julio/nodejs-integration#bf883f325a716a3b28661224d496d31a3157abdf" +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ - "anyhow", - "datadog-trace-protobuf", + "generic-array", + "typenum", ] [[package]] -name = "datadog-trace-protobuf" -version = "6.0.0" -source = "git+https://github.com/DataDog/libdatadog.git?branch=julio/nodejs-integration#bf883f325a716a3b28661224d496d31a3157abdf" +name = "ctor" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ - "prost", - "serde", - "serde_bytes", + "quote", + "syn", ] [[package]] -name = "datadog-trace-utils" -version = "6.0.0" -source = "git+https://github.com/DataDog/libdatadog.git?branch=julio/nodejs-integration#bf883f325a716a3b28661224d496d31a3157abdf" +name = "datadog-js-zstd" +version = "0.1.0" dependencies = [ - "anyhow", - "datadog-trace-normalization", - "datadog-trace-protobuf", - "ddcommon 6.0.0", - "flate2", - "futures", - "hyper 0.14.30", - "hyper-rustls 0.23.2", - "log", - "prost", - "rmp-serde", - "serde", - "serde_json", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-test", + "zstd", ] [[package]] -name = "ddcommon" -version = "6.0.0" -source = "git+https://github.com/DataDog/libdatadog.git?branch=julio/nodejs-integration#bf883f325a716a3b28661224d496d31a3157abdf" +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" dependencies = [ - "anyhow", - "futures", - "futures-core", - "futures-util", - "hex", - "http 0.2.12", - "hyper 0.14.30", - "hyper-rustls 0.23.2", - "lazy_static", - "log", - "pin-project", - "regex", - "rustls 0.20.9", - "rustls-native-certs 0.6.3", - "serde", - "tokio", - "tokio-rustls 0.23.4", + "uuid", ] [[package]] -name = "ddcommon" -version = "12.0.0" -source = "git+https://github.com/DataDog/libdatadog.git?branch=main#16528ffee456f7af5fe9ad80a6294fb5dcd38918" +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "anyhow", - "futures", - "futures-core", - "futures-util", - "hex", - "http 0.2.12", - "hyper 0.14.30", - "hyper-rustls 0.27.2", - "hyper-util", - "lazy_static", - "log", - "pin-project", - "regex", - "rustls 0.23.12", - "rustls-native-certs 0.6.3", - "serde", - "static_assertions", - "tokio", - "tokio-rustls 0.26.0", + "block-buffer", + "crypto-common", ] [[package]] -name = "ddtelemetry" -version = "12.0.0" -source = "git+https://github.com/DataDog/libdatadog.git?branch=main#16528ffee456f7af5fe9ad80a6294fb5dcd38918" +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "anyhow", - "base64 0.22.1", - "datadog-ddsketch", - "ddcommon 12.0.0", - "futures", - "hashbrown 0.12.3", - "http 0.2.12", - "hyper 0.14.30", - "io-lifetimes", - "lazy_static", - "pin-project", - "regex", - "serde", - "serde_json", - "sys-info", - "tokio", - "tokio-util", - "tracing", - "uuid", + "bitflags", + "objc2", ] [[package]] @@ -492,26 +405,32 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -521,20 +440,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] -name = "flate2" -version = "1.0.31" +name = "find-msvc-tools" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fluent-uri" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc74ac4d8359ae70623506d512209619e5cf8f347124910440dbc221714b328e" dependencies = [ - "crc32fast", - "miniz_oxide", + "borrow-or-share", + "ref-cast", + "serde", ] [[package]] -name = "fnv" -version = "1.0.7" +name = "foldhash" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "fs_extra" @@ -544,9 +470,9 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -559,9 +485,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -569,15 +495,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -586,38 +512,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn", ] [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -627,64 +553,90 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] -name = "gimli" -version = "0.29.0" +name = "getrandom" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] [[package]] -name = "gimli" -version = "0.30.0" +name = "getrandom" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e1d97fbe9722ba9bbd0c97051c2956e726562b61f86a25a4360398a40edfc9" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ - "fallible-iterator", - "indexmap", - "stable_deref_trait", + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", ] [[package]] -name = "glob" -version = "0.3.1" +name = "gimli" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" +dependencies = [ + "fallible-iterator", + "indexmap", + "stable_deref_trait", +] [[package]] name = "hashbrown" -version = "0.12.3" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "ahash", + "allocator-api2", + "equivalent", + "foldhash", ] [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] -name = "hermit-abi" -version = "0.3.9" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hex" @@ -692,104 +644,57 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "home" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - [[package]] name = "http" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "fnv", "itoa", ] [[package]] name = "http-body" -version = "0.4.6" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 0.2.12", - "pin-project-lite", + "http", ] [[package]] -name = "http-body" -version = "1.0.1" +name = "http-body-util" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", - "http 1.1.0", + "futures-core", + "http", + "http-body", + "pin-project-lite", ] [[package]] name = "httparse" -version = "1.9.4" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hyper" -version = "0.14.30" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ + "atomic-waker", "bytes", "futures-channel", "futures-core", - "futures-util", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http 1.1.0", - "http-body 1.0.1", + "http", + "http-body", "httparse", "itoa", "pin-project-lite", @@ -800,68 +705,52 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" -dependencies = [ - "http 0.2.12", - "hyper 0.14.30", - "rustls 0.20.9", - "rustls-native-certs 0.6.3", - "tokio", - "tokio-rustls 0.23.4", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.2" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "futures-util", - "http 1.1.0", - "hyper 1.4.1", + "http", + "hyper", "hyper-util", - "rustls 0.23.12", - "rustls-native-certs 0.7.1", - "rustls-pki-types", + "rustls", "tokio", - "tokio-rustls 0.26.0", + "tokio-rustls", "tower-service", ] [[package]] name = "hyper-util" -version = "0.1.7" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.1.0", - "http-body 1.0.1", - "hyper 1.4.1", + "http", + "http-body", + "hyper", + "libc", "pin-project-lite", "socket2", "tokio", - "tower", "tower-service", "tracing", ] [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", - "windows-core", + "windows-core 0.62.2", ] [[package]] @@ -874,1142 +763,2273 @@ dependencies = [ ] [[package]] -name = "indexmap" -version = "2.4.0" +name = "id-arena" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" -dependencies = [ - "equivalent", - "hashbrown 0.14.5", -] +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" [[package]] -name = "io-lifetimes" -version = "1.0.11" +name = "indexmap" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.48.0", + "equivalent", + "hashbrown 0.17.0", + "serde", + "serde_core", ] [[package]] name = "itertools" -version = "0.10.5" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] -name = "jobserver" -version = "0.1.32" +name = "jni" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ - "libc", + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", ] [[package]] -name = "js-sys" -version = "0.3.69" +name = "jni-sys" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" dependencies = [ - "wasm-bindgen", + "jni-sys 0.4.1", ] [[package]] -name = "lazy_static" -version = "1.5.0" +name = "jni-sys" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] [[package]] -name = "lazycell" -version = "1.3.0" +name = "jni-sys-macros" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] [[package]] -name = "libc" -version = "0.2.155" +name = "jobserver" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] [[package]] -name = "libloading" -version = "0.8.5" +name = "js-sys" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "futures-util", + "once_cell", + "wasm-bindgen", ] [[package]] -name = "linux-raw-sys" -version = "0.4.14" +name = "konst" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb" +dependencies = [ + "konst_macro_rules", +] [[package]] -name = "log" -version = "0.4.22" +name = "konst_macro_rules" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" [[package]] -name = "memchr" -version = "2.7.4" +name = "leb128fmt" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] -name = "minimal-lexical" -version = "0.2.1" +name = "libc" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] -name = "miniz_oxide" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +name = "libdatadog-nodejs-capabilities" +version = "0.1.0" dependencies = [ - "adler", - "simd-adler32", + "anyhow", + "bytes", + "futures-core", + "http", + "js-sys", + "libdd-capabilities 3.0.0", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test", ] [[package]] -name = "mio" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" +name = "libdd-capabilities" +version = "2.0.0" +source = "git+https://github.com/DataDog/libdatadog.git?tag=v37.0.0#86b7f5700d3db4e58076794b5e8073a40d780083" dependencies = [ - "hermit-abi", - "libc", - "wasi", - "windows-sys 0.52.0", + "anyhow", + "bytes", + "http", + "thiserror", ] [[package]] -name = "mirai-annotations" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" - -[[package]] -name = "napi" -version = "2.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1277600d452e570cc83cf5f4e8efb389cc21e5cbefadcfba7239f4551e2e3e99" +name = "libdd-capabilities" +version = "3.0.0" +source = "git+https://github.com/DataDog/libdatadog.git?rev=1b9b7a26f54f116a0f6525abdcd2013b341921a7#1b9b7a26f54f116a0f6525abdcd2013b341921a7" dependencies = [ - "bitflags", - "ctor", - "napi-derive", - "napi-sys", - "once_cell", - "serde", - "serde_json", + "anyhow", + "bytes", + "futures-channel", + "futures-util", + "http", + "thiserror", ] [[package]] -name = "napi-derive" -version = "2.16.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "150d87c4440b9f4815cb454918db498b5aae9a57aa743d20783fe75381181d01" +name = "libdd-capabilities-impl" +version = "2.0.0" +source = "git+https://github.com/DataDog/libdatadog.git?tag=v37.0.0#86b7f5700d3db4e58076794b5e8073a40d780083" dependencies = [ - "cfg-if", - "convert_case", - "napi-derive-backend", - "proc-macro2", - "quote", - "syn 2.0.72", + "bytes", + "http", + "http-body-util", + "libdd-capabilities 2.0.0", + "libdd-common 5.0.0", + "tokio", ] [[package]] -name = "napi-derive-backend" -version = "1.0.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cd81b794fc1d6051acf8c4f3cb4f82833b0621272a232b4ff0cf3df1dbddb61" +name = "libdd-capabilities-impl" +version = "4.0.0" +source = "git+https://github.com/DataDog/libdatadog.git?rev=1b9b7a26f54f116a0f6525abdcd2013b341921a7#1b9b7a26f54f116a0f6525abdcd2013b341921a7" dependencies = [ - "convert_case", - "once_cell", - "proc-macro2", - "quote", - "regex", - "semver", - "syn 2.0.72", + "anyhow", + "bytes", + "http", + "http-body-util", + "libdd-capabilities 3.0.0", + "libdd-common 5.1.1", + "tokio", ] [[package]] -name = "napi-sys" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427802e8ec3a734331fec1035594a210ce1ff4dc5bc1950530920ab717964ea3" +name = "libdd-common" +version = "5.0.0" +source = "git+https://github.com/DataDog/libdatadog.git?tag=v37.0.0#86b7f5700d3db4e58076794b5e8073a40d780083" dependencies = [ - "libloading", + "anyhow", + "bytes", + "cc", + "const_format", + "futures", + "futures-core", + "futures-util", + "hex", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "libc", + "nix 0.29.0", + "pin-project", + "regex", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier", + "serde", + "static_assertions", + "thiserror", + "tokio", + "tokio-rustls", + "tower-service", + "windows-sys 0.52.0", ] [[package]] -name = "neon" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d75440242411c87dc39847b0e33e961ec1f10326a9d8ecf9c1ea64a3b3c13dc" +name = "libdd-common" +version = "5.1.1" +source = "git+https://github.com/DataDog/libdatadog.git?rev=1b9b7a26f54f116a0f6525abdcd2013b341921a7#1b9b7a26f54f116a0f6525abdcd2013b341921a7" dependencies = [ - "libloading", - "neon-macros", - "once_cell", - "semver", - "send_wrapper", - "smallvec", + "anyhow", + "bytes", + "cc", + "const_format", + "futures", + "futures-core", + "futures-util", + "hex", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "libc", + "nix 0.29.0", + "pin-project", + "regex", + "serde", + "static_assertions", + "thiserror", + "tokio", + "tower-service", + "windows-sys 0.52.0", ] [[package]] -name = "neon-macros" +name = "libdd-crashtracker" version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6813fde79b646e47e7ad75f480aa80ef76a5d9599e2717407961531169ee38b" +source = "git+https://github.com/DataDog/libdatadog.git?tag=v37.0.0#86b7f5700d3db4e58076794b5e8073a40d780083" dependencies = [ - "quote", - "syn 2.0.72", - "syn-mid", + "anyhow", + "blazesym", + "cc", + "chrono", + "errno", + "http", + "libc", + "libdd-common 5.0.0", + "libdd-libunwind-sys", + "libdd-telemetry 5.0.1", + "nix 0.29.0", + "num-derive", + "num-traits", + "os_info", + "page_size", + "portable-atomic", + "rand", + "schemars", + "serde", + "serde_json", + "symbolic-common", + "symbolic-demangle", + "thiserror", + "tokio", + "uuid", + "windows 0.59.0", ] [[package]] -name = "nix" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +name = "libdd-data-pipeline" +version = "7.0.0" +source = "git+https://github.com/DataDog/libdatadog.git?rev=1b9b7a26f54f116a0f6525abdcd2013b341921a7#1b9b7a26f54f116a0f6525abdcd2013b341921a7" dependencies = [ - "bitflags", - "cfg-if", - "libc", + "anyhow", + "arc-swap", + "async-trait", + "bytes", + "either", + "futures", + "getrandom 0.2.17", + "http", + "http-body-util", + "libdd-capabilities 3.0.0", + "libdd-capabilities-impl 4.0.0", + "libdd-common 5.1.1", + "libdd-ddsketch 1.1.0", + "libdd-dogstatsd-client", + "libdd-shared-runtime 2.0.0", + "libdd-telemetry 6.0.0", + "libdd-tinybytes", + "libdd-trace-normalization", + "libdd-trace-protobuf 4.0.1", + "libdd-trace-stats", + "libdd-trace-utils", + "rmp-serde", + "serde", + "serde_json", + "sha2", + "tokio", + "tokio-util", + "tracing", + "uuid", + "web-time", ] [[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +name = "libdd-ddsketch" +version = "1.0.1" +source = "git+https://github.com/DataDog/libdatadog.git?tag=v37.0.0#86b7f5700d3db4e58076794b5e8073a40d780083" dependencies = [ - "memchr", - "minimal-lexical", + "prost", ] [[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +name = "libdd-ddsketch" +version = "1.1.0" +source = "git+https://github.com/DataDog/libdatadog.git?rev=1b9b7a26f54f116a0f6525abdcd2013b341921a7#1b9b7a26f54f116a0f6525abdcd2013b341921a7" dependencies = [ - "autocfg", + "prost", ] [[package]] -name = "object" -version = "0.36.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" +name = "libdd-dogstatsd-client" +version = "4.0.0" +source = "git+https://github.com/DataDog/libdatadog.git?rev=1b9b7a26f54f116a0f6525abdcd2013b341921a7#1b9b7a26f54f116a0f6525abdcd2013b341921a7" dependencies = [ + "anyhow", + "async-trait", + "cadence", + "http", + "libdd-common 5.1.1", + "libdd-shared-runtime 2.0.0", + "serde", + "tokio", + "tracing", +] + +[[package]] +name = "libdd-library-config" +version = "3.0.0" +source = "git+https://github.com/DataDog/libdatadog.git?rev=0c6e2a5df2a163d34c4f385353ffc5d7257c72f4#0c6e2a5df2a163d34c4f385353ffc5d7257c72f4" +dependencies = [ + "anyhow", + "libc", + "libdd-trace-protobuf 4.0.0", + "memfd", + "prost", + "rand", + "rmp", + "rmp-serde", + "serde", + "serde_yaml", +] + +[[package]] +name = "libdd-libunwind-sys" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "227f0e30af5fbb91d1b3768db7997fdd1ded87c44b371663783c4ff3eb686908" +dependencies = [ + "cc", + "libc", + "paste", +] + +[[package]] +name = "libdd-shared-runtime" +version = "1.0.0" +source = "git+https://github.com/DataDog/libdatadog.git?tag=v37.0.0#86b7f5700d3db4e58076794b5e8073a40d780083" +dependencies = [ + "async-trait", + "futures", + "futures-util", + "libdd-capabilities 2.0.0", + "libdd-capabilities-impl 2.0.0", + "libdd-common 5.0.0", + "tokio", + "tokio-util", + "tracing", + "wasm-bindgen-futures", +] + +[[package]] +name = "libdd-shared-runtime" +version = "2.0.0" +source = "git+https://github.com/DataDog/libdatadog.git?rev=1b9b7a26f54f116a0f6525abdcd2013b341921a7#1b9b7a26f54f116a0f6525abdcd2013b341921a7" +dependencies = [ + "async-trait", + "futures", + "futures-util", + "libdd-capabilities 3.0.0", + "libdd-capabilities-impl 4.0.0", + "libdd-common 5.1.1", + "tokio", + "tokio-util", + "tracing", + "wasm-bindgen-futures", +] + +[[package]] +name = "libdd-telemetry" +version = "5.0.1" +source = "git+https://github.com/DataDog/libdatadog.git?tag=v37.0.0#86b7f5700d3db4e58076794b5e8073a40d780083" +dependencies = [ + "anyhow", + "async-trait", + "base64", + "bytes", + "futures", + "hashbrown 0.15.5", + "http", + "http-body-util", + "libc", + "libdd-common 5.0.0", + "libdd-ddsketch 1.0.1", + "libdd-shared-runtime 1.0.0", + "serde", + "serde_json", + "sys-info", + "tokio", + "tokio-util", + "tracing", + "uuid", + "winver", +] + +[[package]] +name = "libdd-telemetry" +version = "6.0.0" +source = "git+https://github.com/DataDog/libdatadog.git?rev=1b9b7a26f54f116a0f6525abdcd2013b341921a7#1b9b7a26f54f116a0f6525abdcd2013b341921a7" +dependencies = [ + "anyhow", + "async-trait", + "base64", + "bytes", + "futures", + "getrandom 0.2.17", + "hashbrown 0.15.5", + "http", + "libc", + "libdd-capabilities 3.0.0", + "libdd-common 5.1.1", + "libdd-ddsketch 1.1.0", + "libdd-shared-runtime 2.0.0", + "serde", + "serde_json", + "strum", + "strum_macros", + "sys-info", + "tokio", + "tokio-util", + "tracing", + "uuid", + "web-time", + "winver", +] + +[[package]] +name = "libdd-tinybytes" +version = "1.1.2" +source = "git+https://github.com/DataDog/libdatadog.git?rev=1b9b7a26f54f116a0f6525abdcd2013b341921a7#1b9b7a26f54f116a0f6525abdcd2013b341921a7" +dependencies = [ + "serde", +] + +[[package]] +name = "libdd-trace-normalization" +version = "3.0.1" +source = "git+https://github.com/DataDog/libdatadog.git?rev=1b9b7a26f54f116a0f6525abdcd2013b341921a7#1b9b7a26f54f116a0f6525abdcd2013b341921a7" +dependencies = [ + "anyhow", + "libdd-trace-protobuf 4.0.1", +] + +[[package]] +name = "libdd-trace-obfuscation" +version = "5.0.0" +source = "git+https://github.com/DataDog/libdatadog.git?rev=1b9b7a26f54f116a0f6525abdcd2013b341921a7#1b9b7a26f54f116a0f6525abdcd2013b341921a7" +dependencies = [ + "anyhow", + "fluent-uri", + "libdd-common 5.1.1", + "libdd-trace-protobuf 4.0.1", + "libdd-trace-utils", + "log", + "percent-encoding", + "serde", + "serde_json", +] + +[[package]] +name = "libdd-trace-protobuf" +version = "4.0.0" +source = "git+https://github.com/DataDog/libdatadog.git?rev=0c6e2a5df2a163d34c4f385353ffc5d7257c72f4#0c6e2a5df2a163d34c4f385353ffc5d7257c72f4" +dependencies = [ + "prost", + "serde", + "serde_bytes", +] + +[[package]] +name = "libdd-trace-protobuf" +version = "4.0.1" +source = "git+https://github.com/DataDog/libdatadog.git?rev=1b9b7a26f54f116a0f6525abdcd2013b341921a7#1b9b7a26f54f116a0f6525abdcd2013b341921a7" +dependencies = [ + "prost", + "serde", + "serde_bytes", +] + +[[package]] +name = "libdd-trace-stats" +version = "6.0.0" +source = "git+https://github.com/DataDog/libdatadog.git?rev=1b9b7a26f54f116a0f6525abdcd2013b341921a7#1b9b7a26f54f116a0f6525abdcd2013b341921a7" +dependencies = [ + "anyhow", + "arc-swap", + "async-trait", + "futures", + "hashbrown 0.15.5", + "http", + "libdd-capabilities 3.0.0", + "libdd-capabilities-impl 4.0.0", + "libdd-common 5.1.1", + "libdd-ddsketch 1.1.0", + "libdd-dogstatsd-client", + "libdd-shared-runtime 2.0.0", + "libdd-telemetry 6.0.0", + "libdd-trace-obfuscation", + "libdd-trace-protobuf 4.0.1", + "libdd-trace-utils", + "rmp-serde", + "serde", + "tokio", + "tokio-util", + "tracing", + "web-time", +] + +[[package]] +name = "libdd-trace-utils" +version = "10.0.0" +source = "git+https://github.com/DataDog/libdatadog.git?rev=1b9b7a26f54f116a0f6525abdcd2013b341921a7#1b9b7a26f54f116a0f6525abdcd2013b341921a7" +dependencies = [ + "anyhow", + "base64", + "bytes", + "futures", + "getrandom 0.2.17", + "hex", + "http", + "http-body", + "http-body-util", + "indexmap", + "itoa", + "libdd-capabilities 3.0.0", + "libdd-capabilities-impl 4.0.0", + "libdd-common 5.1.1", + "libdd-tinybytes", + "libdd-trace-normalization", + "libdd-trace-protobuf 4.0.1", + "prost", + "rand", + "rmp", + "rmp-serde", + "rmpv", + "rustc-hash", + "serde", + "serde-transcode", + "serde_json", + "thin-vec", + "tokio", + "tracing", +] + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "library-config" +version = "0.2.0" +dependencies = [ + "anyhow", + "getrandom 0.2.17", + "libdd-library-config", + "serde", + "serde-wasm-bindgen", + "wasm-bindgen", + "wasm-bindgen-test", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memfd" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad38eb12aea514a0466ea40a80fd8cc83637065948eb4a426e4aa46261175227" +dependencies = [ + "rustix", +] + +[[package]] +name = "memmap2" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minicov" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4869b6a491569605d66d3952bcdf03df789e5b536e5f0cf7758a7f08a55ae24d" +dependencies = [ + "cc", + "walkdir", +] + +[[package]] +name = "miniz_oxide" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "msvc-demangler" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeff6bd154a309b2ada5639b2661ca6ae4599b34e8487dc276d2cd637da2d76" +dependencies = [ + "bitflags", + "itoa", +] + +[[package]] +name = "napi" +version = "2.16.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55740c4ae1d8696773c78fdafd5d0e5fe9bc9f1b071c7ba493ba5c413a9184f3" +dependencies = [ + "bitflags", + "ctor", + "napi-derive", + "napi-sys", + "once_cell", + "serde", + "serde_json", +] + +[[package]] +name = "napi-derive" +version = "2.16.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cbe2585d8ac223f7d34f13701434b9d5f4eb9c332cccce8dee57ea18ab8ab0c" +dependencies = [ + "cfg-if", + "convert_case", + "napi-derive-backend", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "napi-derive-backend" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1639aaa9eeb76e91c6ae66da8ce3e89e921cd3885e99ec85f4abacae72fc91bf" +dependencies = [ + "convert_case", + "once_cell", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "napi-sys" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427802e8ec3a734331fec1035594a210ce1ff4dc5bc1950530920ab717964ea3" +dependencies = [ + "libloading", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags", + "block2", + "libc", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-location", + "objc2-core-text", + "objc2-foundation", + "objc2-quartz-core", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "os_info" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224" +dependencies = [ + "android_system_properties", + "log", + "nix 0.30.1", + "objc2", + "objc2-foundation", + "objc2-ui-kit", + "serde", + "windows-sys 0.61.2", +] + +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pipeline" +version = "0.1.0" +dependencies = [ + "bytes", + "console_error_panic_hook", + "getrandom 0.2.17", + "http", + "js-sys", + "libdatadog-nodejs-capabilities", + "libdd-capabilities 3.0.0", + "libdd-common 5.1.1", + "libdd-data-pipeline", + "libdd-shared-runtime 2.0.0", + "libdd-trace-protobuf 4.0.1", + "libdd-trace-stats", + "libdd-trace-utils", + "rmp-serde", + "serde", + "serde_json", + "uuid", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test", + "web-time", +] + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +dependencies = [ + "serde", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "process-discovery" +version = "0.1.0" +dependencies = [ + "anyhow", + "libdd-library-config", + "libdd-trace-protobuf 4.0.0", + "napi", + "napi-derive", +] + +[[package]] +name = "prost" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", "memchr", + "regex-automata", + "regex-syntax", ] [[package]] -name = "once_cell" -version = "1.19.0" +name = "regex-automata" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] [[package]] -name = "openssl-probe" -version = "0.1.5" +name = "regex-syntax" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] -name = "os_info" -version = "3.8.2" +name = "ring" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ - "log", - "serde", + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", "windows-sys 0.52.0", ] [[package]] -name = "page_size" -version = "0.6.0" +name = "rmp" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" dependencies = [ - "libc", - "winapi", + "num-traits", ] [[package]] -name = "paste" -version = "1.0.15" +name = "rmp-serde" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" +dependencies = [ + "rmp", + "serde", +] [[package]] -name = "pin-project" -version = "1.1.5" +name = "rmpv" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "7a4e1d4b9b938a26d2996af33229f0ca0956c652c1375067f0b45291c1df8417" dependencies = [ - "pin-project-internal", + "rmp", ] [[package]] -name = "pin-project-internal" -version = "1.1.5" +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + +[[package]] +name = "rustc-hash" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.72", + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", ] [[package]] -name = "pin-project-lite" -version = "0.2.14" +name = "rustls" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +dependencies = [ + "aws-lc-rs", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] [[package]] -name = "pin-utils" -version = "0.1.0" +name = "rustls-native-certs" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] [[package]] -name = "pipeline" -version = "0.1.0" +name = "rustls-pki-types" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ - "data-pipeline", - "neon", + "zeroize", ] [[package]] -name = "portable-atomic" -version = "1.7.0" +name = "rustls-platform-verifier" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" dependencies = [ - "serde", + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.52.0", ] [[package]] -name = "ppv-lite86" -version = "0.2.20" +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ - "zerocopy", + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] -name = "prettyplease" -version = "0.2.20" +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ - "proc-macro2", - "syn 2.0.72", + "winapi-util", ] [[package]] -name = "proc-macro2" -version = "1.0.86" +name = "schannel" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ - "unicode-ident", + "windows-sys 0.61.2", ] [[package]] -name = "prost" -version = "0.11.9" +name = "schemars" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" dependencies = [ - "bytes", - "prost-derive", + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", ] [[package]] -name = "prost-derive" -version = "0.11.9" +name = "schemars_derive" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" dependencies = [ - "anyhow", - "itertools", "proc-macro2", "quote", - "syn 1.0.109", + "serde_derive_internals", + "syn", ] [[package]] -name = "quote" -version = "1.0.36" +name = "security-framework" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "proc-macro2", + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", ] [[package]] -name = "rand" -version = "0.8.5" +name = "security-framework-sys" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ + "core-foundation-sys", "libc", - "rand_chacha", - "rand_core", ] [[package]] -name = "rand_chacha" -version = "0.3.1" +name = "semver" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ - "ppv-lite86", - "rand_core", + "serde_core", + "serde_derive", ] [[package]] -name = "rand_core" -version = "0.6.4" +name = "serde-transcode" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "590c0e25c2a5bb6e85bf5c1bce768ceb86b316e7a01bdf07d2cb4ec2271990e2" dependencies = [ - "getrandom", + "serde", ] [[package]] -name = "regex" -version = "1.10.6" +name = "serde-wasm-bindgen" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "e3b4c031cd0d9014307d82b8abf653c0290fbdaeb4c02d00c63cf52f728628bf" dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", + "js-sys", + "serde", + "wasm-bindgen", ] [[package]] -name = "regex-automata" -version = "0.4.7" +name = "serde_bytes" +version = "0.11.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", + "serde", + "serde_core", ] [[package]] -name = "regex-syntax" -version = "0.8.4" +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] [[package]] -name = "ring" -version = "0.16.20" +name = "serde_derive" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted 0.7.1", - "web-sys", - "winapi", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "ring" -version = "0.17.8" +name = "serde_derive_internals" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ - "cc", - "cfg-if", - "getrandom", - "libc", - "spin 0.9.8", - "untrusted 0.9.0", - "windows-sys 0.52.0", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "rmp" -version = "0.8.14" +name = "serde_json" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "byteorder", - "num-traits", - "paste", + "indexmap", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", ] [[package]] -name = "rmp-serde" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "sketches" +version = "0.1.0" dependencies = [ - "byteorder", - "rmp", - "serde", + "libdd-ddsketch 1.1.0", + "wasm-bindgen", ] [[package]] -name = "rustc-demangle" -version = "0.1.24" +name = "slab" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] -name = "rustc-hash" -version = "1.1.0" +name = "smallvec" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] -name = "rustix" -version = "0.38.34" +name = "socket2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ - "bitflags", - "errno", "libc", - "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] -name = "rustls" -version = "0.20.9" +name = "stable_deref_trait" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" -dependencies = [ - "log", - "ring 0.16.20", - "sct", - "webpki", -] +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] -name = "rustls" -version = "0.23.12" +name = "static_assertions" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" -dependencies = [ - "aws-lc-rs", - "once_cell", - "ring 0.17.8", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] -name = "rustls-native-certs" -version = "0.6.3" +name = "strum" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" -dependencies = [ - "openssl-probe", - "rustls-pemfile 1.0.4", - "schannel", - "security-framework", -] +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" [[package]] -name = "rustls-native-certs" -version = "0.7.1" +name = "strum_macros" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88d6d420651b496bdd98684116959239430022a115c1240e6c3993be0b15fba" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "openssl-probe", - "rustls-pemfile 2.1.3", - "rustls-pki-types", - "schannel", - "security-framework", + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", ] [[package]] -name = "rustls-pemfile" -version = "1.0.4" +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "symbolic-common" +version = "12.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +checksum = "332615d90111d8eeaf86a84dc9bbe9f65d0d8c5cf11b4caccedc37754eb0dcfd" dependencies = [ - "base64 0.21.7", + "debugid", + "memmap2", + "stable_deref_trait", + "uuid", ] [[package]] -name = "rustls-pemfile" -version = "2.1.3" +name = "symbolic-demangle" +version = "12.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +checksum = "912017718eb4d21930546245af9a3475c9dccf15675a5c215664e76621afc471" dependencies = [ - "base64 0.22.1", - "rustls-pki-types", + "cpp_demangle 0.4.5", + "msvc-demangler", + "rustc-demangle", + "symbolic-common", ] [[package]] -name = "rustls-pki-types" -version = "1.8.0" +name = "syn" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] [[package]] -name = "rustls-webpki" -version = "0.102.6" +name = "sys-info" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" +checksum = "0b3a0d0aba8bf96a0e1ddfdc352fc53b3df7f39318c71854910c3c4b024ae52c" dependencies = [ - "aws-lc-rs", - "ring 0.17.8", - "rustls-pki-types", - "untrusted 0.9.0", + "cc", + "libc", ] [[package]] -name = "ryu" -version = "1.0.18" +name = "thin-vec" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "b0f7e269b48f0a7dd0146680fa24b50cc67fc0373f086a5b2f99bd084639b482" [[package]] -name = "schannel" -version = "0.1.23" +name = "thiserror" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "windows-sys 0.52.0", + "thiserror-impl", ] [[package]] -name = "sct" -version = "0.7.1" +name = "thiserror-impl" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "security-framework" -version = "2.11.1" +name = "tokio" +version = "1.52.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", + "bytes", "libc", - "security-framework-sys", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", ] [[package]] -name = "security-framework-sys" -version = "2.11.1" +name = "tokio-macros" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ - "core-foundation-sys", - "libc", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "semver" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" - -[[package]] -name = "send_wrapper" -version = "0.6.0" +name = "tokio-rustls" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] [[package]] -name = "serde" -version = "1.0.197" +name = "tokio-util" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ - "serde_derive", + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", ] [[package]] -name = "serde_bytes" -version = "0.11.15" +name = "tower-service" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" -dependencies = [ - "serde", -] +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] -name = "serde_derive" -version = "1.0.197" +name = "tracing" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.72", + "pin-project-lite", + "tracing-core", ] [[package]] -name = "serde_json" -version = "1.0.122" +name = "tracing-core" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", + "once_cell", ] [[package]] -name = "shlex" -version = "1.3.0" +name = "try-lock" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] -name = "simd-adler32" -version = "0.3.7" +name = "typenum" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] -name = "slab" -version = "0.4.9" +name = "unicode-ident" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] -name = "smallvec" +name = "unicode-segmentation" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] -name = "socket2" -version = "0.5.7" +name = "unicode-xid" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] -name = "spin" -version = "0.5.2" +name = "unsafe-libyaml" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] -name = "spin" -version = "0.9.8" +name = "untrusted" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] -name = "stable_deref_trait" -version = "1.2.0" +name = "uuid" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "serde_core", + "wasm-bindgen", +] [[package]] -name = "static_assertions" -version = "1.1.0" +name = "version_check" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] -name = "subtle" -version = "2.6.1" +name = "walkdir" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] [[package]] -name = "syn" -version = "1.0.109" +name = "want" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "try-lock", ] [[package]] -name = "syn" -version = "2.0.72" +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "wit-bindgen 0.57.1", ] [[package]] -name = "syn-mid" -version = "0.6.0" +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5dc35bb08dd1ca3dfb09dce91fd2d13294d6711c88897d9a9d60acf39bce049" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.72", + "wit-bindgen 0.51.0", ] [[package]] -name = "sys-info" -version = "0.9.1" +name = "wasm-bindgen" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b3a0d0aba8bf96a0e1ddfdc352fc53b3df7f39318c71854910c3c4b024ae52c" +checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" dependencies = [ - "cc", - "libc", + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] -name = "tokio" -version = "1.39.2" +name = "wasm-bindgen-futures" +version = "0.4.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" +checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "pin-project-lite", - "socket2", - "tokio-macros", - "windows-sys 0.52.0", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "tokio-macros" -version = "2.4.0" +name = "wasm-bindgen-macro" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" dependencies = [ - "proc-macro2", "quote", - "syn 2.0.72", + "wasm-bindgen-macro-support", ] [[package]] -name = "tokio-rustls" -version = "0.23.4" +name = "wasm-bindgen-macro-support" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" dependencies = [ - "rustls 0.20.9", - "tokio", - "webpki", + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", ] [[package]] -name = "tokio-rustls" -version = "0.26.0" +name = "wasm-bindgen-shared" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" dependencies = [ - "rustls 0.23.12", - "rustls-pki-types", - "tokio", + "unicode-ident", ] [[package]] -name = "tokio-util" -version = "0.7.11" +name = "wasm-bindgen-test" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "29826f9d9ecaa314c480d376b276d1c790e6cb6a4681fab8532da69cbabf977d" dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", + "async-trait", + "cast", + "js-sys", + "libm", + "minicov", + "nu-ansi-term", + "num-traits", + "oorandom", + "serde", + "serde_json", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", + "wasm-bindgen-test-shared", ] [[package]] -name = "tower" -version = "0.4.13" +name = "wasm-bindgen-test-macro" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +checksum = "c610311887f9e6599a546d278d12d69dfd3a3e92639b2129e4b11ad6cf1961d6" dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tokio", - "tower-layer", - "tower-service", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "tower-layer" -version = "0.3.3" +name = "wasm-bindgen-test-shared" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" +checksum = "60238e5b4b1b295701d6f9a66d2a126fe19990348f5fb9dae3b623a370119d94" [[package]] -name = "tower-service" -version = "0.3.2" +name = "wasm-encoder" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] [[package]] -name = "tracing" -version = "0.1.40" +name = "wasm-metadata" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ - "pin-project-lite", - "tracing-core", + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", ] [[package]] -name = "tracing-core" -version = "0.1.32" +name = "wasmparser" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "once_cell", + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", ] [[package]] -name = "try-lock" -version = "0.2.5" +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] [[package]] -name = "unicode-ident" -version = "1.0.12" +name = "webpki-root-certs" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "0d46a5a140e6f7afeccd8eae97eff335163939eac8b929834875168b29b3d267" +dependencies = [ + "rustls-pki-types", +] [[package]] -name = "unicode-segmentation" -version = "1.11.0" +name = "winapi" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] [[package]] -name = "untrusted" -version = "0.7.1" +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] -name = "untrusted" -version = "0.9.0" +name = "winapi-util" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] [[package]] -name = "uuid" -version = "1.10.0" +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" -dependencies = [ - "getrandom", - "serde", -] +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "version_check" -version = "0.9.5" +name = "windows" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.5", +] [[package]] -name = "want" -version = "0.3.1" +name = "windows" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +checksum = "7f919aee0a93304be7f62e8e5027811bbba96bcb1de84d6618be56e43f8a32a1" dependencies = [ - "try-lock", + "windows-core 0.59.0", + "windows-targets 0.53.5", ] [[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +name = "windows-core" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "810ce18ed2112484b0d4e15d022e5f598113e220c53e373fb31e67e21670c1ce" +dependencies = [ + "windows-implement 0.59.0", + "windows-interface", + "windows-result 0.3.4", + "windows-strings 0.3.1", + "windows-targets 0.53.5", +] [[package]] -name = "wasm-bindgen" -version = "0.2.92" +name = "windows-core" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "cfg-if", - "wasm-bindgen-macro", + "windows-implement 0.60.2", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.92" +name = "windows-implement" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1" dependencies = [ - "bumpalo", - "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.72", - "wasm-bindgen-shared", + "syn", ] [[package]] -name = "wasm-bindgen-macro" -version = "0.2.92" +name = "windows-implement" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ + "proc-macro2", "quote", - "wasm-bindgen-macro-support", + "syn", ] [[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.92" +name = "windows-interface" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", - "wasm-bindgen-backend", - "wasm-bindgen-shared", + "syn", ] [[package]] -name = "wasm-bindgen-shared" -version = "0.2.92" +name = "windows-link" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] -name = "web-sys" -version = "0.3.69" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" -dependencies = [ - "js-sys", - "wasm-bindgen", -] +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "webpki" -version = "0.22.4" +name = "windows-result" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", + "windows-link 0.1.3", ] [[package]] -name = "which" -version = "4.4.2" +name = "windows-result" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "either", - "home", - "once_cell", - "rustix", + "windows-link 0.2.1", ] [[package]] -name = "winapi" -version = "0.3.9" +name = "windows-strings" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "windows-link 0.1.3", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "windows-strings" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "windows-sys" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] [[package]] -name = "windows-core" +name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-targets 0.48.5", + "windows-link 0.2.1", ] [[package]] -name = "windows-sys" -version = "0.52.0" +name = "windows-targets" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows-targets 0.52.6", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -2036,13 +3056,36 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -2055,6 +3098,18 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -2067,6 +3122,18 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -2079,12 +3146,30 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -2097,6 +3182,18 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -2109,6 +3206,18 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -2121,6 +3230,18 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -2133,43 +3254,171 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winver" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e0e7162b9e282fd75a0a832cce93994bdb21208d848a418cd05a5fdd9b9ab33" +dependencies = [ + "windows 0.48.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ - "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn", ] [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zstd" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ - "zeroize_derive", + "zstd-safe", ] [[package]] -name = "zeroize_derive" -version = "1.4.2" +name = "zstd-safe" +version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.72", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", ] diff --git a/Cargo.toml b/Cargo.toml index 7731b6ce..41a1142c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,11 @@ [workspace] +resolver = "2" default-members = [ - "crates/crashtracker" + "crates/crashtracker", + "crates/process_discovery", ] members = [ - "crates/*" + "crates/*", ] [profile.release] @@ -12,5 +14,3 @@ lto = true opt-level = "z" panic = "abort" strip = true - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 00000000..4adca209 --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,49 @@ +# Development + +## Contributing + +See [`CONTRIBUTING.md`](CONTRIBUTING.md). + +## Development setup + +To build `libdatadog-nodejs` locally (for example, to run tests or try out changes), you need Node.js, Yarn, and Rust. + +**Rust (required for native and WASM builds)** + +The project compiles Rust for both native Node.js addons and WebAssembly. Use [rustup](https://rustup.rs/) (the recommended and supported method): + +1. **Install rustup and Rust** (see https://rustup.rs/ for more options): + + ```bash + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + ``` + +2. **Ensure Rust is on `PATH`** — the rustup installer prints the command for your shell; run it or open a new terminal. + +3. **Add the WebAssembly target** (required for the full build): + + ```bash + rustup target add wasm32-unknown-unknown + ``` + +4. **On macOS only** — the WASM build requires LLVM from Homebrew (Apple's Clang has compatibility issues with some crates). Install it before building: + + ```bash + brew install llvm + ``` + +5. **Install dependencies:** + + ```bash + yarn install + ``` + +## Building + +* `yarn build`: Build the default workspaces in debug mode. +* `yarn build-release`: Build the default workspaces in release mode. +* `yarn build-all`: Build all workspaces in debug mode. This is useful when working on a workspace that is not a default member yet. + +## Run tests + +* `yarn test`: Run the JavaScript test suite diff --git a/README.md b/README.md index bbfd0216..69caebcd 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,3 @@ Node.js bindings for [libdatadog](https://github.com/DataDog/libdatadog). This project is currently meant to be used only by [dd-trace-js](https://github.com/DataDog/dd-trace-js) and installing it directly is not supported at the moment. - -## Building - -* `npm run build`: Build the default workspaces in debug mode. -* `npm run build-release`: Build the default workspaces in release mode. -* `npm run build-all`: Build all workspaces in debug mode. This is useful when working on a workspace that is not a default member yet. diff --git a/crates/capabilities/Cargo.toml b/crates/capabilities/Cargo.toml new file mode 100644 index 00000000..f4b46e6b --- /dev/null +++ b/crates/capabilities/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "libdatadog-nodejs-capabilities" +version = "0.1.0" +edition = "2021" +description = "Wasm capability implementations for libdatadog-nodejs (backed by JS transports)" + +[lib] +crate-type = ["rlib"] + +[dependencies] +wasm-bindgen = "0.2" +wasm-bindgen-futures = "0.4" +js-sys = "0.3" +http = "1" +bytes = "1.4" +futures-core = "0.3" +anyhow = "1" +libdd-capabilities = { git = "https://github.com/DataDog/libdatadog.git", rev = "1b9b7a26f54f116a0f6525abdcd2013b341921a7" } + +[dev-dependencies] +wasm-bindgen-test = "0.3" diff --git a/crates/capabilities/src/env.rs b/crates/capabilities/src/env.rs new file mode 100644 index 00000000..716d4b4a --- /dev/null +++ b/crates/capabilities/src/env.rs @@ -0,0 +1,28 @@ +// Copyright 2026-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +//! Wasm implementation of [`EnvCapability`] backed by Node.js `process.env`. + +use wasm_bindgen::prelude::*; + +use libdd_capabilities::env::{EnvCapability, EnvError}; + +#[wasm_bindgen(module = "/src/env_transport.js")] +extern "C" { + #[wasm_bindgen(js_name = "get")] + fn js_env_get(name: &str) -> JsValue; +} + +#[derive(Debug, Clone)] +pub struct WasmEnvCapability; + +impl EnvCapability for WasmEnvCapability { + fn new() -> Self { + Self + } + + fn get(&self, name: &str) -> Result, EnvError> { + // Node coerces every process.env value to a string, so NotUnicode is unreachable here. + Ok(js_env_get(name).as_string()) + } +} diff --git a/crates/capabilities/src/env_transport.js b/crates/capabilities/src/env_transport.js new file mode 100644 index 00000000..92d6504b --- /dev/null +++ b/crates/capabilities/src/env_transport.js @@ -0,0 +1,7 @@ +'use strict' + +const { env } = process + +module.exports.get = (name) => { + return env[name] +} diff --git a/crates/capabilities/src/file.rs b/crates/capabilities/src/file.rs new file mode 100644 index 00000000..fe8eb4e6 --- /dev/null +++ b/crates/capabilities/src/file.rs @@ -0,0 +1,156 @@ +// Copyright 2026-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +//! Wasm implementation of [`FileCapability`] backed by Node.js `fs`. +//! +//! The JS transport is imported via `wasm_bindgen(module = ...)` from +//! `filesystem.js`, which ships alongside the wasm output. + +use std::future::Future; + +use bytes::Bytes; +use js_sys::{self, Reflect, Uint8Array}; +use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::JsFuture; + +use libdd_capabilities::file::{FileCapability, FileError, FileMetadata}; +use libdd_capabilities::maybe_send::MaybeSend; + +#[wasm_bindgen(module = "/src/filesystem.js")] +extern "C" { + #[wasm_bindgen(js_name = "readFile", catch)] + fn js_read_file(path: &str) -> Result; + + #[wasm_bindgen(js_name = "writeFile", catch)] + fn js_write_file(path: &str, data: &[u8]) -> Result; + + #[wasm_bindgen(js_name = "metadata", catch)] + fn js_metadata(path: &str) -> Result; + + #[wasm_bindgen(js_name = "exists", catch)] + fn js_exists(path: &str) -> Result; +} + +#[derive(Debug, Clone)] +pub struct WasmFileCapability; + +impl FileCapability for WasmFileCapability { + fn new() -> Self { + Self + } + + #[allow(clippy::manual_async_fn)] + fn read(&self, path: &str) -> impl Future> + MaybeSend { + let path = path.to_owned(); + async move { + let promise = + js_read_file(&path).map_err(|e| map_js_error(&e, &path))?; + let value = JsFuture::from(promise) + .await + .map_err(|e| map_js_error(&e, &path))?; + let array = Uint8Array::unchecked_from_js(value); + Ok(Bytes::from(array.to_vec())) + } + } + + #[allow(clippy::manual_async_fn)] + fn write( + &self, + path: &str, + contents: Bytes, + ) -> impl Future> + MaybeSend { + let path = path.to_owned(); + async move { + let promise = js_write_file(&path, &contents) + .map_err(|e| map_js_error(&e, &path))?; + JsFuture::from(promise) + .await + .map_err(|e| map_js_error(&e, &path))?; + Ok(()) + } + } + + #[allow(clippy::manual_async_fn)] + fn metadata( + &self, + path: &str, + ) -> impl Future> + MaybeSend { + let path = path.to_owned(); + async move { + let promise = + js_metadata(&path).map_err(|e| map_js_error(&e, &path))?; + let value = JsFuture::from(promise) + .await + .map_err(|e| map_js_error(&e, &path))?; + parse_metadata(&value, &path) + } + } + + #[allow(clippy::manual_async_fn)] + fn exists(&self, path: &str) -> impl Future> + MaybeSend { + let path = path.to_owned(); + async move { + let promise = js_exists(&path).map_err(|e| map_js_error(&e, &path))?; + let value = JsFuture::from(promise) + .await + .map_err(|e| map_js_error(&e, &path))?; + value + .as_bool() + .ok_or_else(|| FileError::Io(anyhow::anyhow!("exists({path}) did not return a boolean"))) + } + } +} + +fn map_js_error(err: &JsValue, path: &str) -> FileError { + let code = Reflect::get(err, &JsValue::from_str("code")) + .ok() + .and_then(|v| v.as_string()); + match code.as_deref() { + Some("ENOENT") => FileError::NotFound(path.to_owned()), + Some("EACCES") | Some("EPERM") => FileError::PermissionDenied(path.to_owned()), + _ => { + let message = Reflect::get(err, &JsValue::from_str("message")) + .ok() + .and_then(|v| v.as_string()) + .unwrap_or_else(|| format!("{err:?}")); + FileError::Io(anyhow::anyhow!("{message} (path: {path})")) + } + } +} + +fn parse_metadata(value: &JsValue, path: &str) -> Result { + let size = read_bigint_u64(value, "size", path)?; + // Node populates `stat().ino` on every platform, so `inode` is always Some. + let inode = Some(read_bigint_u64(value, "inode", path)?); + let is_file = read_bool(value, "is_file", path)?; + let is_dir = read_bool(value, "is_dir", path)?; + Ok(FileMetadata { + size, + inode, + is_file, + is_dir, + }) +} + +fn read_bigint_u64(value: &JsValue, key: &str, path: &str) -> Result { + let v = Reflect::get(value, &JsValue::from_str(key)) + .map_err(|_| FileError::Io(anyhow::anyhow!("metadata({path}) could not read field `{key}`")))?; + if v.is_undefined() || v.is_null() { + return Err(FileError::Io(anyhow::anyhow!("metadata({path}) missing field `{key}`"))); + } + let bigint = js_sys::BigInt::try_from(v) + .map_err(|_| FileError::Io(anyhow::anyhow!("metadata({path}) field `{key}` is not a BigInt")))?; + u64::try_from(bigint) + .map_err(|_| FileError::Io(anyhow::anyhow!("metadata({path}) field `{key}` overflows u64"))) +} + +fn read_bool(value: &JsValue, key: &str, path: &str) -> Result { + Reflect::get(value, &JsValue::from_str(key)) + .ok() + .and_then(|v| v.as_bool()) + .ok_or_else(|| { + FileError::Io(anyhow::anyhow!( + "metadata({path}) is missing boolean field `{key}`" + )) + }) +} diff --git a/crates/capabilities/src/filesystem.js b/crates/capabilities/src/filesystem.js new file mode 100644 index 00000000..014049a0 --- /dev/null +++ b/crates/capabilities/src/filesystem.js @@ -0,0 +1,38 @@ +// Lazy `require('node:fs')` — see http_transport.js. The cached accessor +// avoids paying the module-resolution cost on every call. + +'use strict' + +let _fs +function fs () { + return _fs ??= require('node:fs') +} + +module.exports.readFile = function (path) { + return fs().promises.readFile(path) +} + +module.exports.writeFile = function (path, data) { + // Copy off the wasm-memory view before the async write; a memory grow would + // otherwise detach the underlying ArrayBuffer mid-write. + return fs().promises.writeFile(path, Buffer.from(data)) +} + +module.exports.metadata = function (path) { + return fs().promises.stat(path, { bigint: true }).then(s => ({ + size: s.size, + inode: s.ino, + is_file: s.isFile(), + is_dir: s.isDirectory(), + })) +} + +module.exports.exists = function (path) { + return fs().promises.stat(path).then( + () => true, + (error) => { + if (error && error.code === 'ENOENT') return false + throw error + }, + ) +} diff --git a/crates/capabilities/src/http.rs b/crates/capabilities/src/http.rs new file mode 100644 index 00000000..2ee1bf78 --- /dev/null +++ b/crates/capabilities/src/http.rs @@ -0,0 +1,263 @@ +// Copyright 2026-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +//! Wasm implementation of [`HttpClientCapability`] backed by Node.js `http.request`. +//! +//! The JS transport is imported via `wasm_bindgen(module = ...)` from +//! `http_transport.js`, which ships alongside the wasm output. + +use std::future::Future; +use std::io::Write as _; +use std::sync::LazyLock; + +use bytes::Bytes; +use http::{HeaderMap, HeaderName, HeaderValue}; +use js_sys::{self, Array, JsString, Number, Uint8Array}; +use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::JsFuture; + +use libdd_capabilities::http::{HttpClientCapability, HttpError}; +use libdd_capabilities::maybe_send::MaybeSend; + +// A `static` requires `Sync`, and `LazyLock: Sync` needs `JsValue: +// Sync` — which holds only because on wasm32 (single-threaded) wasm-bindgen +// implements `Send`/`Sync` for `JsValue`. This crate is wasm32-only, so it's sound. +static WASM_MEMORY: LazyLock = LazyLock::new(wasm_bindgen::memory); + +#[wasm_bindgen(module = "/src/http_transport.js")] +extern "C" { + #[wasm_bindgen(js_name = "httpRequest")] + fn http_request( + host: &str, + port: u16, + is_https: bool, + socket_path: &str, + head_ptr: *const u8, + head_len: u32, + body_ptr: *const u8, + body_len: u32, + wasm_memory: &JsValue, + ) -> js_sys::Promise; + + #[wasm_bindgen(js_name = "setStorage")] + pub fn set_storage(new_storage: &JsValue); + + #[wasm_bindgen(js_name = "setResponseHeaderObserver")] + pub fn set_response_header_observer(observer: &JsValue); +} + +/// Wasm [`HttpClientCapability`] that delegates HTTP to Node.js `http.request`. +/// +/// The wasm analogue of libdatadog's native `NativeHttpClient`. Bundled into +/// [`crate::WasmCapabilities`] alongside the sleep and log-output capabilities +/// that `TraceExporter` requires. +#[derive(Debug, Clone)] +pub struct WasmHttpClient; + +impl HttpClientCapability for WasmHttpClient { + fn new_client() -> Self { + Self + } + + fn new_without_connection_pooling() -> Self { + Self + } + + #[allow(clippy::manual_async_fn)] + fn request( + &self, + req: http::Request, + ) -> impl Future, HttpError>> + MaybeSend { + async move { + let scheme = req.uri().scheme_str().unwrap_or("http"); + + // Unix domain socket / Windows named pipe: ddcommon's `parse_uri` + // hex-encodes the socket path into the URI authority (there is no + // standard URL form for socket paths). On wasm the request bypasses + // ddcommon's native (hyper) connector and reaches us directly, so we + // decode the path here and route over the socket instead of TCP. + let (host, port, is_https, socket_path) = if scheme == "unix" || scheme == "windows" { + (String::new(), 0u16, false, decode_socket_path(req.uri())?) + } else { + let is_https = scheme == "https"; + let host = req.uri().host().ok_or_else(|| { + HttpError::InvalidRequest(anyhow::anyhow!("missing host in URI")) + })?; + let port = req + .uri() + .port_u16() + .unwrap_or(if is_https { 443 } else { 80 }); + (host.to_owned(), port, is_https, String::new()) + }; + + // For a socket request there is no meaningful network host; HTTP/1.1 + // still requires a Host header, so send a stable placeholder (the + // agent does not validate Host over a socket). + let head = if socket_path.is_empty() { + serialize_request_head(&req, &host, port, is_https, false)? + } else { + serialize_request_head(&req, "localhost", port, is_https, true)? + }; + let body = req.into_body(); + + let result = JsFuture::from(http_request( + &host, + port, + is_https, + &socket_path, + head.as_ptr(), + head.len() as u32, + body.as_ptr(), + body.len() as u32, + WASM_MEMORY.as_ref(), + )) + .await + .map_err(|e| HttpError::Network(anyhow::anyhow!("{:?}", e)))?; + + let result: js_sys::ArrayTuple<(Number, Array, Uint8Array)> = + js_sys::ArrayTuple::unchecked_from_js(result); + + let status = result + .get0() + .as_f64() + .ok_or_else(|| HttpError::Other(anyhow::anyhow!("status is not a number")))? + as u16; + + let headers = parse_response_headers(result.get1())?; + + let body = Bytes::from(result.get2().to_vec()); + + let mut builder = http::Response::builder().status(status); + // `headers_mut()` is `None` only when the builder already holds an + // error (e.g. an out-of-range status from the agent). Don't unwrap: + // skip the headers and let `body()` below surface that error. + if let Some(builder_headers) = builder.headers_mut() { + *builder_headers = headers; + } + builder.body(body).map_err(|e| HttpError::Other(e.into())) + } + } +} + +/// Parse response headers from Node's flat `[name, value, name, value, ...]` +/// array (`res.rawHeaders`): even indices are (lowercased) header names, odd +/// indices their string values. +fn parse_response_headers(header_js: Array) -> Result { + let len = header_js.length() as usize; + let mut headers = HeaderMap::with_capacity(len / 2); + for i in 0..(len / 2) { + let key = header_js.get((i * 2) as u32).as_string(); + let val = header_js.get((i * 2 + 1) as u32).as_string(); + if let (Some(key), Some(val)) = (key, val) { + // Response headers come from the agent (untrusted over plaintext + // HTTP); skip any the http crate rejects rather than unwrapping + // and trapping the whole wasm instance on one malformed header. + if let (Ok(name), Ok(value)) = ( + HeaderName::from_bytes(key.as_bytes()), + HeaderValue::from_maybe_shared(Bytes::from(val)), + ) { + headers.insert(name, value); + } + } + } + Ok(headers) +} + +/// Decode the socket path that ddcommon's `parse_uri` hex-encoded into the URI +/// authority for `unix://` / `windows:` agent URLs (see `encode_uri_path_in_authority` +/// in libdd-common). The authority is the lowercase hex of the raw path bytes. +fn decode_socket_path(uri: &http::Uri) -> Result { + let authority = uri + .authority() + .ok_or_else(|| HttpError::InvalidRequest(anyhow::anyhow!("socket URI missing authority")))? + .as_str(); + let bytes = hex_decode(authority).ok_or_else(|| { + HttpError::InvalidRequest(anyhow::anyhow!("socket path authority is not valid hex")) + })?; + String::from_utf8(bytes) + .map_err(|e| HttpError::InvalidRequest(anyhow::anyhow!("socket path is not utf-8: {e}"))) +} + +/// Minimal hex decoder for the socket-path authority. Returns `None` on any +/// malformed input (odd length or non-hex digit) rather than panicking. +fn hex_decode(s: &str) -> Option> { + let bytes = s.as_bytes(); + if !bytes.len().is_multiple_of(2) { + return None; + } + let mut out = Vec::with_capacity(bytes.len() / 2); + for pair in bytes.chunks_exact(2) { + let hi = (pair[0] as char).to_digit(16)?; + let lo = (pair[1] as char).to_digit(16)?; + out.push((hi * 16 + lo) as u8); + } + Some(out) +} + +/// Serialize the full HTTP/1.1 request head (request line + Host + Content-Length +/// + user headers + terminating CRLF) into a contiguous byte buffer. +/// +/// The buffer is handed to JS by pointer; JS parses it (`parseRequestHead` in +/// http_transport.js) into method/path/headers for `http.request(...)`. (It used +/// to be assigned to the Node internal `req._header`, but Bun's node:http +/// ignores that, so the head is parsed into request options instead.) +/// +/// `is_socket` requests (unix socket / named pipe) omit the `:port` suffix on +/// the Host header — there is no TCP port for a socket transport. +fn serialize_request_head( + req: &http::Request, + host: &str, + port: u16, + is_https: bool, + is_socket: bool, +) -> Result, HttpError> { + let method = req.method().as_str(); + let path_and_query = req + .uri() + .path_and_query() + .map(|pq| pq.as_str()) + .unwrap_or("/"); + let body_len = req.body().len(); + let headers = req.headers(); + + let mut buf = Vec::with_capacity(256 + headers.len() * 64); + + buf.extend_from_slice(method.as_bytes()); + buf.push(b' '); + buf.extend_from_slice(path_and_query.as_bytes()); + buf.extend_from_slice(b" HTTP/1.1\r\n"); + + buf.extend_from_slice(b"Host: "); + buf.extend_from_slice(host.as_bytes()); + if !is_socket { + let default_port = if is_https { 443 } else { 80 }; + if port != default_port { + write!(&mut buf, ":{port}").map_err(|e| HttpError::Other(e.into()))?; + } + } + buf.extend_from_slice(b"\r\n"); + + write!(&mut buf, "Content-Length: {body_len}\r\n").map_err(|e| HttpError::Other(e.into()))?; + + for (name, value) in headers.iter() { + // The request-framing headers above (Host, Content-Length) are + // authoritative. Skip any caller-supplied duplicates of them (and + // Transfer-Encoding) so they can't be emitted twice — duplicate/ + // conflicting framing headers are a request-smuggling vector when the + // agent URL is reached through a proxy. + if matches!( + name.as_str(), + "host" | "content-length" | "transfer-encoding" + ) { + continue; + } + buf.extend_from_slice(name.as_str().as_bytes()); + buf.extend_from_slice(b": "); + buf.extend_from_slice(value.as_bytes()); + buf.extend_from_slice(b"\r\n"); + } + + buf.extend_from_slice(b"\r\n"); + + Ok(buf) +} diff --git a/crates/capabilities/src/http_transport.js b/crates/capabilities/src/http_transport.js new file mode 100644 index 00000000..962b6ba4 --- /dev/null +++ b/crates/capabilities/src/http_transport.js @@ -0,0 +1,260 @@ +// NOTE: `node:http`, `node:https` and `node:fs` are deliberately NOT required at +// module load. This transport is loaded during the tracer's own init (before +// user code runs), and requiring an instrumented builtin here makes dd-trace +// wrap it in place immediately — so a user app that imports `http` afterwards +// (e.g. an ESM app under `--require`) sees the wrapped builtin and gets +// instrumented when it should not (breaks the init/guardrail expectations). The +// JS agent exporter requires these lazily (at first send) for the same reason; +// mirror that by requiring them inside the functions that use them below. + +let storage = f => f() + +// libdatadog's automatic container-id / entity-id detection (libdd-common's +// entity_id module) is gated `#[cfg(unix)]` and therefore inert on the +// `wasm32-unknown-unknown` target we build for, and `DD_EXTERNAL_ENV` is also +// unreachable from wasm. Node, however, can read `/proc` and `process.env`, so +// we detect the same values here and add them as the standard Datadog exporter +// headers (`datadog-container-id`, `datadog-entity-id`, `datadog-external-env`) +// — the headers native libdatadog adds via `Endpoint::set_standard_headers`. +// +// The detection mirrors dd-trace-js's `exporters/common/docker.js` (the proven +// legacy-exporter path) and libdd-common's `compute_entity_id` +// (`ci-` else `in-`). + +// The second alternative is the PCF / Garden regexp; no suffix ($) to avoid +// matching pod UIDs. See +// https://github.com/DataDog/datadog-agent/blob/7.40.x/pkg/util/cgroups/reader.go#L50 +const uuidSource = String.raw`[0-9a-f]{8}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{12}|[0-9a-f]{8}(?:-[0-9a-f]{4}){4}$` +const containerSource = '[0-9a-f]{64}' +const taskSource = String.raw`[0-9a-f]{32}-\d+` +const lineReg = /^(\d+):([^:]*):(.+)$/m +const entityReg = new RegExp(String.raw`.*(${uuidSource}|${containerSource}|${taskSource})(?:\.scope)?$`, 'm') + +// Detect the entity headers. Parameterized for unit testing; production callers +// use the cached `getEntityHeaders()` with the real cgroup paths / environment. +function detectEntityHeaders (opts = {}) { + const fs = require('node:fs') + const cgroupPath = opts.cgroupPath ?? '/proc/self/cgroup' + const cgroupMount = opts.cgroupMount ?? '/sys/fs/cgroup' + const externalEnv = 'externalEnv' in opts ? opts.externalEnv : process.env.DD_EXTERNAL_ENV + + const headers = {} + + let cgroup = '' + let containerId + try { + cgroup = fs.readFileSync(cgroupPath, 'utf8').trim() + containerId = cgroup.match(entityReg)?.[1] + } catch { /* not in a cgroup, or not Linux */ } + + let inode = 0 + const inodePath = cgroup.match(lineReg)?.[3] + if (inodePath) { + const strippedPath = inodePath.replaceAll(/^\/|\/$/g, '') + try { + inode = fs.statSync(`${cgroupMount}/${strippedPath}`).ino + } catch { /* mount not present */ } + } + + // `ci-` when a container id is found, else `in-` + // — matching libdd-common's `compute_entity_id`. + const entityId = containerId ? `ci-${containerId}` : (inode ? `in-${inode}` : undefined) + + if (containerId) headers['datadog-container-id'] = containerId + if (entityId) headers['datadog-entity-id'] = entityId + // Only emit external-env if it is a clean header value (visible ASCII + space/ + // tab). This rejects CR/LF (header-injection / request-smuggling vector) and + // non-latin1 that the latin1-encoded head rewrite couldn't represent — native + // libdatadog likewise rejects invalid bytes via the http crate's HeaderValue. + if (externalEnv && /^[\t\u0020-\u007E]*$/.test(externalEnv)) { + headers['datadog-external-env'] = externalEnv + } + + return headers +} + +let cachedEntityHeaders +function getEntityHeaders () { + if (cachedEntityHeaders === undefined) { + cachedEntityHeaders = detectEntityHeaders() + } + return cachedEntityHeaders +} + +// Rewrite the Rust-rendered HTTP/1.1 request head (a `\r\n`-delimited byte +// buffer terminated by a blank line) to carry the detected entity headers. +// Any pre-existing line for a name we set is dropped first, so libdatadog's +// empty `datadog-container-id` is replaced rather than duplicated. Header +// names/values are ASCII, so latin1 is a lossless round-trip. +function applyEntityHeaders (headView, entity = getEntityHeaders()) { + const names = Object.keys(entity) + if (names.length === 0) return Buffer.from(headView) + + const head = Buffer.from(headView).toString('latin1') + const term = head.indexOf('\r\n\r\n') + if (term === -1) return Buffer.from(headView) // malformed; leave untouched + + const drop = new Set(names) + const lines = head.slice(0, term).split('\r\n') + const kept = lines.filter((line, i) => { + if (i === 0) return true // request line + const colon = line.indexOf(':') + const name = (colon === -1 ? line : line.slice(0, colon)).trim().toLowerCase() + return !drop.has(name) + }) + for (const name of names) kept.push(`${name}: ${entity[name]}`) + + return Buffer.from(`${kept.join('\r\n')}\r\n\r\n`, 'latin1') +} + +// Parse the Rust-rendered (+ entity-merged) HTTP/1.1 request head into Node +// request options `{ method, path, headers }`. We pass these to +// `http.request(...)` rather than injecting the raw head via the Node-internal +// `req._header`: that internal is undocumented and Bun's `node:http` ignores it, +// so under Bun the request went out as `POST /` with no headers and the agent +// dropped it. Header names/values are ASCII (latin1 round-trips losslessly). +function parseRequestHead (headBuf) { + const head = Buffer.from(headBuf).toString('latin1') + const term = head.indexOf('\r\n\r\n') + const lines = (term === -1 ? head : head.slice(0, term)).split('\r\n') + // Request line: `METHOD request-target HTTP/1.1` (no spaces in the target). + const [method, path] = lines[0].split(' ') + const headers = {} + for (let i = 1; i < lines.length; i++) { + const colon = lines[i].indexOf(':') + if (colon === -1) continue + const name = lines[i].slice(0, colon).trim() + if (name) headers[name] = lines[i].slice(colon + 1).trim() + } + return { method, path, headers } +} + +// A retried write can race a wasm-memory detach; treat that as a transient error. +function isDetachedBufferError (err) { + return err instanceof TypeError && /detached/i.test(err.message) +} + +module.exports.sleep = function (ms) { + return new Promise((resolve) => { + const timer = setTimeout(resolve, ms) + // The exporter races this sleep against each request as a timeout guard (and + // reuses it for retry backoff). An in-flight request refs the event loop on + // its own, so an abandoned timeout timer (e.g. the 5-minute request-timeout + // guard after a fast success) must not keep the host process alive. + timer.unref?.() + }) +} + +module.exports.setStorage = function (new_storage) { + storage = new_storage +} + +// Optional observer invoked with each agent response's raw headers +// (Node's flat [name, value, name, value, ...] array). Lets the host tracer +// read response-only headers (e.g. Datadog-Container-Tags-Hash) that are not +// otherwise surfaced through the wasm response body. Never throws into the +// transport: a misbehaving observer must not break trace delivery. +// +// The observer runs synchronously on the response 'end' event, so it must be +// non-blocking and return quickly — long-running synchronous work here would +// stall the event loop. +let responseHeaderObserver + +module.exports.setResponseHeaderObserver = function (new_observer) { + responseHeaderObserver = new_observer +} + +// Exposed for unit tests. +module.exports.detectEntityHeaders = detectEntityHeaders +module.exports.applyEntityHeaders = applyEntityHeaders +module.exports._resetEntityHeadersCache = () => { + cachedEntityHeaders = undefined +} + +module.exports.httpRequest = function (host, port, isHttps, socketPath, head_ptr, head_len, body_ptr, body_len, wasm_memory) { + // A non-empty socketPath routes over a Unix domain socket (or Windows named + // pipe) instead of TCP. Sockets are always plaintext HTTP/1.1, so https is + // ignored in that mode. + const http = require('node:http') + const https = require('node:https') + // libdatadog derives `host` from the agent URI, which keeps the brackets for + // an IPv6 literal (e.g. `[::1]`). Node's `http.request` treats the `host` + // option as a hostname to resolve, so `[::1]` fails with ENOTFOUND. Strip the + // brackets so the IPv6 address is used directly (Node accepts `::1`). + if (typeof host === 'string' && host.length > 1 && host[0] === '[' && host.at(-1) === ']') { + host = host.slice(1, -1) + } + const useSocket = typeof socketPath === 'string' && socketPath.length > 0 + const transport = useSocket ? http : (isHttps ? https : http) + + function attempt () { + return new Promise((resolve, reject) => { + storage(() => { + // wasm_memory.buffer is replaced each time WebAssembly.Memory grows, so + // the views must be recreated on every attempt against the current buffer. + const headView = new Uint8Array(wasm_memory.buffer, head_ptr, head_len) + const bodyView = new Uint8Array(wasm_memory.buffer, body_ptr, body_len) + + // The Rust side already rendered the full HTTP/1.1 request head (real + // method, `/v0.4/traces` path, Content-Type/Length, datadog-meta-*); + // applyEntityHeaders merges in the detected entity headers. Parse it into + // request options so the connection uses the correct method/path/headers + // on both Node and Bun (host/port or socketPath drive the connection). + const { method, path, headers } = parseRequestHead(applyEntityHeaders(headView)) + const requestOptions = useSocket + ? { socketPath, method, path, headers } + : { host, port, method, path, headers } + const req = transport.request(requestOptions, (res) => { + const chunks = [] + res.on('data', chunk => chunks.push(chunk)) + res.on('end', () => { + const body = Buffer.concat(chunks) + if (responseHeaderObserver) { + try { + responseHeaderObserver(res.rawHeaders) + } catch (error) { + // Only read `err.message` (a string) rather than stringifying an + // arbitrary thrown value, so a hostile/throwing toString on the + // error can't turn the log line into its own failure path. + process.stderr.write('responseHeaderObserver error: ' + (error && error.message) + '\n') + } + } + resolve([ + res.statusCode, + res.rawHeaders, + // Copy the exact body bytes. `body` is a Buffer from Buffer.concat, + // which for small payloads is a view into Node's shared pool, so + // `body.buffer` is the whole pool — slicing by offset/length (via + // the Uint8Array(typedArray) copy ctor) is required to avoid + // handing the Rust side unrelated pooled memory. + new Uint8Array(body), + ]) + }) + }) + req.on('error', reject) + + // The request head (method/path/headers) was supplied via requestOptions + // above; just write the body. (No `req._header` injection — that Node + // internal is not honored by Bun.) + try { + req.write(bodyView) + req.end() + } catch (error) { + reject(error) + } + }) + }) + } + + function attemptWithRetry () { + return attempt().catch((error) => { + process.stderr.write('httpRequest error: ' + error + '\n') + if (isDetachedBufferError(error)) { + return attemptWithRetry() + } + throw error + }) + } + + return attemptWithRetry() +} diff --git a/crates/capabilities/src/lib.rs b/crates/capabilities/src/lib.rs new file mode 100644 index 00000000..399e9539 --- /dev/null +++ b/crates/capabilities/src/lib.rs @@ -0,0 +1,146 @@ +// Copyright 2026-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +//! Wasm capability implementations for libdatadog-nodejs. +//! +//! [`WasmCapabilities`] is the bundle struct that implements every capability +//! trait `TraceExporter` requires using wasm_bindgen and JS transports. The +//! wasm binding crate pins this type as the capability generic for libdatadog's +//! `TraceExporter`, mirroring libdatadog's native `NativeCapabilities`. + +use std::future::Future; +use std::time::Duration; + +use libdd_capabilities::env::{EnvCapability, EnvError}; +use libdd_capabilities::file::{FileCapability, FileError, FileMetadata}; +use libdd_capabilities::http::HttpError; +use libdd_capabilities::{HttpClientCapability, LogWriterCapability, MaybeSend, SleepCapability}; + +pub mod env; +pub mod file; +pub mod http; +pub mod sleep; + +pub use env::WasmEnvCapability; +pub use file::WasmFileCapability; +pub use http::WasmHttpClient; +pub use sleep::WasmSleepCapability; + +/// Bundle of wasm platform capabilities for libdatadog's `TraceExporter`. +/// +/// Mirrors libdatadog's native `NativeCapabilities`. Per-function bounds stay +/// minimal in libdatadog (e.g. stats-only code uses [`WasmHttpClient`] +/// directly), so this bundle is only needed where the full `TraceExporter` +/// capability set is. +#[derive(Clone, Debug)] +pub struct WasmCapabilities { + /// Outbound HTTP requests routed through the JS fetch/socket transport. + http: WasmHttpClient, + /// Async sleep backed by `setTimeout` via wasm-bindgen. + sleep: WasmSleepCapability, + /// Filesystem access delegated to the Node.js `fs` transport. + file: WasmFileCapability, + env: WasmEnvCapability, +} + +impl Default for WasmCapabilities { + fn default() -> Self { + Self::new() + } +} + +impl WasmCapabilities { + pub fn new() -> Self { + Self { + http: WasmHttpClient::new_client(), + sleep: WasmSleepCapability, + file: WasmFileCapability, + env: WasmEnvCapability, + } + } +} + +impl HttpClientCapability for WasmCapabilities { + fn new_client() -> Self { + Self::new() + } + + fn new_without_connection_pooling() -> Self { + Self { + http: WasmHttpClient::new_without_connection_pooling(), + sleep: WasmSleepCapability, + file: WasmFileCapability, + env: WasmEnvCapability, + } + } + + fn request( + &self, + req: ::http::Request<::bytes::Bytes>, + ) -> impl Future, HttpError>> + MaybeSend { + self.http.request(req) + } +} + +impl SleepCapability for WasmCapabilities { + fn new() -> Self { + Self::new() + } + + fn sleep(&self, duration: Duration) -> impl Future + MaybeSend { + self.sleep.sleep(duration) + } +} + +impl LogWriterCapability for WasmCapabilities { + fn write_log_output(&self, _bytes: &[u8]) -> std::io::Result<()> { + // The wasm binding runs exclusively in trace-export mode; the + // TraceExporter only invokes this in log-output mode, which this + // binding never enables. Implemented as a no-op to satisfy the + // capability bound. If log-output mode is added for wasm, route the + // buffer to a JS sink (e.g. process.stderr) here. + Ok(()) + } +} + +impl FileCapability for WasmCapabilities { + fn new() -> Self { + Self::new() + } + + fn read( + &self, + path: &str, + ) -> impl Future> + MaybeSend { + self.file.read(path) + } + + fn write( + &self, + path: &str, + contents: ::bytes::Bytes, + ) -> impl Future> + MaybeSend { + self.file.write(path, contents) + } + + fn metadata( + &self, + path: &str, + ) -> impl Future> + MaybeSend { + self.file.metadata(path) + } + + fn exists(&self, path: &str) -> impl Future> + MaybeSend { + self.file.exists(path) + } +} + +impl EnvCapability for WasmCapabilities { + fn new() -> Self { + Self::new() + } + + fn get(&self, name: &str) -> Result, EnvError> { + self.env.get(name) + } +} diff --git a/crates/capabilities/src/sleep.rs b/crates/capabilities/src/sleep.rs new file mode 100644 index 00000000..6a6af6c3 --- /dev/null +++ b/crates/capabilities/src/sleep.rs @@ -0,0 +1,43 @@ +// Copyright 2026-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +//! Wasm [`SleepCapability`] backed by JS `setTimeout`. +//! +//! `TraceExporter` requires its capability bundle to implement `SleepCapability` +//! (used for retry backoff). Native code uses `tokio::time::sleep`; in wasm we +//! delegate to `setTimeout` via a JS-returned Promise. + +use std::future::Future; +use std::time::Duration; + +use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::JsFuture; + +use libdd_capabilities::maybe_send::MaybeSend; +use libdd_capabilities::sleep::SleepCapability; + +#[wasm_bindgen(module = "/src/http_transport.js")] +extern "C" { + #[wasm_bindgen(js_name = "sleep")] + fn js_sleep(ms: f64) -> js_sys::Promise; +} + +/// Wasm sleep backed by JS `setTimeout`. +/// +/// The wasm analogue of libdatadog's native `NativeSleepCapability`. +#[derive(Debug, Clone)] +pub struct WasmSleepCapability; + +impl SleepCapability for WasmSleepCapability { + fn new() -> Self { + Self + } + + #[allow(clippy::manual_async_fn)] + fn sleep(&self, duration: Duration) -> impl Future + MaybeSend { + async move { + let ms = duration.as_millis() as f64; + let _ = JsFuture::from(js_sleep(ms)).await; + } + } +} diff --git a/crates/collector/Cargo.toml b/crates/collector/Cargo.toml deleted file mode 100644 index 1c8f255b..00000000 --- a/crates/collector/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "collector" -version = "0.1.0" -edition = "2018" - -[lib] -crate-type = ["cdylib"] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -collector = { git = "https://github.com/DataDog/libdatadog.git", branch = "rochdev/collector" } - -[dependencies.neon] -version = "1.0.0" -default-features = false -features = ["napi-6"] diff --git a/crates/collector/src/lib.rs b/crates/collector/src/lib.rs deleted file mode 100644 index fb1ef9c8..00000000 --- a/crates/collector/src/lib.rs +++ /dev/null @@ -1,55 +0,0 @@ -use neon::prelude::*; -use neon::thread::LocalKey; -use neon::types::buffer::TypedArray; -use collector::runtime::RUNTIME; -use collector::collector::Collector; - -// TODO: Use a single collector for all worker threads. -static COLLECTORS: LocalKey = LocalKey::new(); - -#[neon::main] -fn main (mut cx: ModuleContext) -> NeonResult<()> { - register(&mut cx) -} - -fn register (cx: &mut ModuleContext) -> NeonResult<()> { - COLLECTORS.get_or_init(cx, || Collector::new()); - - cx.export_function("send_events", send_events)?; - cx.export_function("receive_events", receive_events)?; - - Ok(()) -} - -fn send_events(mut cx: FunctionContext) -> JsResult { - let payload = cx.argument::(0).unwrap().as_slice(&mut cx).to_vec(); - let collector = COLLECTORS.get(&mut cx).unwrap(); - - collector.write(payload.as_slice()); - - Ok(cx.undefined()) -} - -// TODO: Do we need an unsubscribe? -fn receive_events(mut cx: FunctionContext) -> JsResult { - let collector = COLLECTORS.get(&mut cx).unwrap(); - let mut cb = cx.argument::(0)?.root(&mut cx); - let ch = cx.channel(); - let mut rx = collector.subscribe(); - - RUNTIME.spawn_blocking(move || { - while let Ok(payload) = rx.blocking_recv() { - cb = ch.send(move |mut cx| { - let buf = JsBuffer::from_slice(&mut cx, payload.as_slice()).unwrap(); - let this = cx.undefined(); - let args = vec![buf.upcast()]; - - cb.to_inner(&mut cx).call(&mut cx, this, args).unwrap(); - - Ok(cb) - }).join().unwrap(); - } - }); - - Ok(cx.undefined()) -} diff --git a/crates/crashtracker/Cargo.toml b/crates/crashtracker/Cargo.toml index ec398a5b..8bce6975 100644 --- a/crates/crashtracker/Cargo.toml +++ b/crates/crashtracker/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "crashtracker" -version = "0.1.0" +version = "0.2.0" edition = "2018" [lib] @@ -14,6 +14,8 @@ path = "src/bin/receiver.rs" [dependencies] anyhow = "1" -datadog-crashtracker = { git = "https://github.com/DataDog/libdatadog.git", branch = "main" } +libdd-crashtracker = { git = "https://github.com/DataDog/libdatadog.git", tag = "v37.0.0" } napi = { version = "2", features = ["serde-json"] } -napi-derive = { version = "2" } +napi-derive = { version = "2", default-features = false } +rustls = { version = "*", default-features = false, features = ["aws-lc-rs"] } +serde_json = "1" diff --git a/crates/crashtracker/src/bin/receiver.rs b/crates/crashtracker/src/bin/receiver.rs index f84198b5..4d39342f 100644 --- a/crates/crashtracker/src/bin/receiver.rs +++ b/crates/crashtracker/src/bin/receiver.rs @@ -3,5 +3,5 @@ fn main() {} #[cfg(unix)] fn main() -> anyhow::Result<()> { - datadog_crashtracker::receiver_entry_point_stdin() + libdd_crashtracker::receiver_entry_point_stdin() } diff --git a/crates/crashtracker/src/lib.rs b/crates/crashtracker/src/lib.rs index f4ed8465..eb906969 100644 --- a/crates/crashtracker/src/lib.rs +++ b/crates/crashtracker/src/lib.rs @@ -1,56 +1,72 @@ -use datadog_crashtracker::CrashtrackerReceiverConfig; use napi::{Env, JsUnknown}; use napi_derive::napi; -use std::{env::temp_dir, fs, path::{self}}; -#[cfg(unix)] -use std::os::unix::fs::PermissionsExt; +mod unhandled_exception; + +/// Ensures that if signals is empty, default signals are applied. +/// This is necessary because NAPI deserialization bypasses the +/// CrashtrackerConfiguration::new() constructor where the default +/// signals logic exists. +fn apply_default_signals( + config: libdd_crashtracker::CrashtrackerConfiguration, +) -> libdd_crashtracker::CrashtrackerConfiguration { + if config.signals().is_empty() { + let mut value = serde_json::to_value(&config).unwrap(); + value["signals"] = serde_json::to_value(libdd_crashtracker::default_signals()).unwrap(); + serde_json::from_value(value).unwrap() + } else { + config + } +} #[napi] -pub fn init_with_receiver(env: Env, config: JsUnknown, receiver_config: JsUnknown, metadata: JsUnknown) -> napi::Result<()> { - let config = env.from_js_value(config)?; - let mut receiver_config = env.from_js_value(receiver_config)?; +pub fn init( + env: Env, + config: JsUnknown, + receiver_config: JsUnknown, + metadata: JsUnknown, +) -> napi::Result<()> { + let config: libdd_crashtracker::CrashtrackerConfiguration = env.from_js_value(config)?; + let receiver_config = env.from_js_value(receiver_config)?; let metadata = env.from_js_value(metadata)?; - copy_receiver(&mut receiver_config).unwrap(); + let config = apply_default_signals(config); - datadog_crashtracker::init_with_receiver(config, receiver_config, metadata).unwrap(); + libdd_crashtracker::init(config, receiver_config, metadata).unwrap(); Ok(()) } #[napi] -pub fn update_config (env: Env, config: JsUnknown) -> napi::Result<()> { - let config = env.from_js_value(config)?; +pub fn update_config(env: Env, config: JsUnknown) -> napi::Result<()> { + let config: libdd_crashtracker::CrashtrackerConfiguration = env.from_js_value(config)?; + + let config = apply_default_signals(config); - datadog_crashtracker::update_config(config).unwrap(); + libdd_crashtracker::update_config(config).unwrap(); Ok(()) } #[napi] -pub fn update_metadata (env: Env, metadata: JsUnknown) -> napi::Result<()> { +pub fn update_metadata(env: Env, metadata: JsUnknown) -> napi::Result<()> { let metadata = env.from_js_value(metadata)?; - datadog_crashtracker::update_metadata(metadata).unwrap(); + libdd_crashtracker::update_metadata(metadata).unwrap(); Ok(()) } -pub fn copy_receiver (receiver_config: &mut CrashtrackerReceiverConfig) -> Result<(), anyhow::Error> { - let parts: Vec<_> = receiver_config.path_to_receiver_binary.rsplit(path::MAIN_SEPARATOR).collect(); - let dest = temp_dir().join(parts[0]); - - std::fs::copy(&receiver_config.path_to_receiver_binary, &dest)?; - - let mut perms = fs::metadata(&dest)?.permissions(); - - #[cfg(unix)] - perms.set_mode(0o777); +#[napi] +pub fn begin_profiler_serializing(_env: Env) -> napi::Result<()> { + let _ = libdd_crashtracker::begin_op(libdd_crashtracker::OpTypes::ProfilerSerializing); - fs::set_permissions(&dest, perms)?; + Ok(()) +} - receiver_config.path_to_receiver_binary = dest.to_string_lossy().to_string(); +#[napi] +pub fn end_profiler_serializing(_env: Env) -> napi::Result<()> { + let _ = libdd_crashtracker::end_op(libdd_crashtracker::OpTypes::ProfilerSerializing); Ok(()) } diff --git a/crates/crashtracker/src/unhandled_exception.rs b/crates/crashtracker/src/unhandled_exception.rs new file mode 100644 index 00000000..64dd8fcb --- /dev/null +++ b/crates/crashtracker/src/unhandled_exception.rs @@ -0,0 +1,236 @@ +use napi::{Env, JsFunction, JsObject, JsUnknown}; +use napi_derive::napi; + +fn get_optional_string_property(obj: &JsObject, key: &str) -> napi::Result> { + match obj.get_named_property::(key) { + Ok(val) => { + use napi::ValueType; + if val.get_type()? == ValueType::String { + let s: String = val.coerce_to_string()?.into_utf8()?.as_str()?.to_owned(); + if s.is_empty() { + Ok(None) + } else { + Ok(Some(s)) + } + } else { + Ok(None) + } + } + Err(_) => Ok(None), + } +} + +fn parse_v8_stack(stack: &str) -> libdd_crashtracker::StackTrace { + let mut frames = Vec::new(); + + for line in stack.lines().skip(1) { + let line = line.trim(); + let line = match line.strip_prefix("at ") { + Some(rest) => rest, + None => continue, + }; + + let mut frame = libdd_crashtracker::StackFrame::new(); + + // Formats: + // "functionName (file:line:col)" + // "functionName (file:line)" + // "file:line:col" + // "file:line" + if let Some(paren_start) = line.rfind('(') { + let func_name = line[..paren_start].trim(); + if !func_name.is_empty() { + frame.function = Some(func_name.to_string()); + } + let location = line[paren_start + 1..].trim_end_matches(')'); + parse_location(location, &mut frame); + } else { + parse_location(line, &mut frame); + } + + frames.push(frame); + } + + libdd_crashtracker::StackTrace::from_frames(frames, false) +} + +fn parse_location(location: &str, frame: &mut libdd_crashtracker::StackFrame) { + // location is "file:line:col" or "file:line" or just "native" etc. + // The file portion may contain ":" ("node:internal/...") + // so we split from the right. + let parts: Vec<&str> = location.rsplitn(3, ':').collect(); + match parts.len() { + 3 => { + // col, line, file + frame.column = parts[0].parse().ok(); + frame.line = parts[1].parse().ok(); + frame.file = Some(parts[2].to_string()); + } + 2 => { + if let Ok(line_num) = parts[0].parse::() { + frame.line = Some(line_num); + frame.file = Some(parts[1].to_string()); + } else { + frame.file = Some(location.to_string()); + } + } + _ => { + frame.file = Some(location.to_string()); + } + } +} + +fn is_error_instance(env: &Env, value: &JsUnknown) -> napi::Result { + let global = env.get_global()?; + let error_ctor: JsFunction = global.get_named_property("Error")?; + value.instanceof(error_ctor) +} + +fn stringify_js_value(value: JsUnknown) -> napi::Result { + let s = value.coerce_to_string()?.into_utf8()?; + Ok(s.as_str()?.to_owned()) +} + +fn report_unhandled(env: &Env, error: JsUnknown, fallback_type: &str) -> napi::Result<()> { + let is_error = is_error_instance(env, &error)?; + let (exception_type, exception_message, stacktrace) = if is_error { + let error_obj: JsObject = error.coerce_to_object()?; + let name = get_optional_string_property(&error_obj, "name")?; + let message = get_optional_string_property(&error_obj, "message")?; + let stack_string = get_optional_string_property(&error_obj, "stack")?; + let stacktrace = match &stack_string { + Some(s) => parse_v8_stack(s), + None => libdd_crashtracker::StackTrace::new_incomplete(), + }; + (name, message, stacktrace) + } else { + // This only fires for synchronous `throw `; node already + // wraps non-Error unhandled rejections in an Error object + let message = stringify_js_value(error).ok(); + ( + Some(fallback_type.to_string()), + message, + // libdatadog defines a missing stacktrace as incomplete + libdd_crashtracker::StackTrace::new_incomplete(), + ) + }; + + libdd_crashtracker::report_unhandled_exception( + exception_type.as_deref(), + exception_message.as_deref(), + stacktrace, + ) + .unwrap(); + + Ok(()) +} + +#[napi] +pub fn report_uncaught_exception_monitor( + env: Env, + error: JsUnknown, + origin: String, +) -> napi::Result<()> { + report_unhandled(&env, error, &origin) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_v8_stack_typical_error() { + let stack = "\ +TypeError: Cannot read properties of undefined (reading 'foo') + at Object.method (/app/src/index.js:10:15) + at Module._compile (node:internal/modules/cjs/loader:1234:14) + at /app/src/helper.js:5:3"; + + let trace = parse_v8_stack(stack); + assert_eq!(trace.frames.len(), 3); + assert!(!trace.incomplete); + + assert_eq!(trace.frames[0].function.as_deref(), Some("Object.method")); + assert_eq!(trace.frames[0].file.as_deref(), Some("/app/src/index.js")); + assert_eq!(trace.frames[0].line, Some(10)); + assert_eq!(trace.frames[0].column, Some(15)); + + assert_eq!(trace.frames[1].function.as_deref(), Some("Module._compile")); + assert_eq!( + trace.frames[1].file.as_deref(), + Some("node:internal/modules/cjs/loader") + ); + assert_eq!(trace.frames[1].line, Some(1234)); + assert_eq!(trace.frames[1].column, Some(14)); + + assert_eq!(trace.frames[2].function, None); + assert_eq!(trace.frames[2].file.as_deref(), Some("/app/src/helper.js")); + assert_eq!(trace.frames[2].line, Some(5)); + assert_eq!(trace.frames[2].column, Some(3)); + } + + #[test] + fn test_parse_v8_stack_anonymous_and_native() { + let stack = "\ +Error: boom + at :1:1 + at native"; + + let trace = parse_v8_stack(stack); + assert_eq!(trace.frames.len(), 2); + + assert_eq!(trace.frames[0].file.as_deref(), Some("")); + assert_eq!(trace.frames[0].line, Some(1)); + assert_eq!(trace.frames[0].column, Some(1)); + + assert_eq!(trace.frames[1].file.as_deref(), Some("native")); + assert_eq!(trace.frames[1].line, None); + } + + #[test] + fn test_parse_v8_stack_empty() { + let stack = "Error: something"; + let trace = parse_v8_stack(stack); + assert_eq!(trace.frames.len(), 0); + assert!(!trace.incomplete); + } + + #[test] + fn test_parse_location_file_line_col() { + let mut frame = libdd_crashtracker::StackFrame::new(); + parse_location("/app/index.js:42:7", &mut frame); + assert_eq!(frame.file.as_deref(), Some("/app/index.js")); + assert_eq!(frame.line, Some(42)); + assert_eq!(frame.column, Some(7)); + } + + #[test] + fn test_parse_location_node_internal() { + let mut frame = libdd_crashtracker::StackFrame::new(); + parse_location("node:internal/modules/cjs/loader:1234:14", &mut frame); + assert_eq!( + frame.file.as_deref(), + Some("node:internal/modules/cjs/loader") + ); + assert_eq!(frame.line, Some(1234)); + assert_eq!(frame.column, Some(14)); + } + + #[test] + fn test_parse_location_no_column() { + let mut frame = libdd_crashtracker::StackFrame::new(); + parse_location("/app/index.js:42", &mut frame); + assert_eq!(frame.file.as_deref(), Some("/app/index.js")); + assert_eq!(frame.line, Some(42)); + assert_eq!(frame.column, None); + } + + #[test] + fn test_parse_location_bare_path() { + let mut frame = libdd_crashtracker::StackFrame::new(); + parse_location("native", &mut frame); + assert_eq!(frame.file.as_deref(), Some("native")); + assert_eq!(frame.line, None); + assert_eq!(frame.column, None); + } +} diff --git a/crates/datadog-js-zstd/Cargo.toml b/crates/datadog-js-zstd/Cargo.toml new file mode 100644 index 00000000..6a4a626d --- /dev/null +++ b/crates/datadog-js-zstd/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "datadog-js-zstd" +version = "0.1.0" +edition = "2018" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +wasm-bindgen = "0.2.100" +zstd = "0.13.3" +js-sys = "0.3.77" + +[package.metadata.wasm-pack.profile.release] +wasm-opt = ["-O", "--enable-bulk-memory"] + +[dev-dependencies] +wasm-bindgen-test = "0.3.50" diff --git a/crates/datadog-js-zstd/src/lib.rs b/crates/datadog-js-zstd/src/lib.rs new file mode 100644 index 00000000..fed9b1c7 --- /dev/null +++ b/crates/datadog-js-zstd/src/lib.rs @@ -0,0 +1,9 @@ +use js_sys::Uint8Array; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub fn zstd_compress(data: Uint8Array, level: i32) -> Uint8Array { + let vecdata = data.to_vec(); + let compressed_data = zstd::encode_all(&vecdata[..], level).expect("Failed to compress data"); + Uint8Array::from(compressed_data.as_slice()) +} diff --git a/crates/library_config/Cargo.toml b/crates/library_config/Cargo.toml new file mode 100644 index 00000000..b6deab09 --- /dev/null +++ b/crates/library_config/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "library-config" +version = "0.2.0" +edition = "2018" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +anyhow = "1" +libdd-library-config = { git = "https://github.com/DataDog/libdatadog.git", rev = "0c6e2a5df2a163d34c4f385353ffc5d7257c72f4" } + +wasm-bindgen = "0.2.100" +serde = { version = "1.0", features = ["derive"] } +serde-wasm-bindgen = "0.4" + +[package.metadata.wasm-pack.profile.release] +wasm-opt = ["-O", "--enable-bulk-memory"] + +[dev-dependencies] +wasm-bindgen-test = "0.3.50" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +getrandom = { version = "0.2", features = ["js"] } diff --git a/crates/library_config/src/lib.rs b/crates/library_config/src/lib.rs new file mode 100644 index 00000000..6896a63c --- /dev/null +++ b/crates/library_config/src/lib.rs @@ -0,0 +1,137 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub struct JsConfigurator { + configurator: Box, + envp: Vec, + args: Vec, +} + +#[wasm_bindgen] +pub struct ConfigEntry { + name: String, + value: String, + source: String, + config_id: String, +} + +#[wasm_bindgen] +impl ConfigEntry { + #[wasm_bindgen(constructor)] + pub fn new(name: String, value: String, source: String, config_id: String) -> ConfigEntry { + ConfigEntry { + name, + value, + source, + config_id, + } + } + #[wasm_bindgen(getter)] + pub fn name(&self) -> String { + self.name.clone() + } + #[wasm_bindgen(getter)] + pub fn value(&self) -> String { + self.value.clone() + } + #[wasm_bindgen(getter)] + pub fn source(&self) -> String { + self.source.clone() + } + #[wasm_bindgen(getter)] + pub fn config_id(&self) -> String { + self.config_id.clone() + } +} + +#[wasm_bindgen] +impl JsConfigurator { + #[wasm_bindgen(constructor)] + pub fn new() -> Self { + JsConfigurator { + configurator: Box::new(libdd_library_config::Configurator::new(false)), // No debug log as WASM can't write to stdout + envp: Vec::new(), + args: Vec::new(), + } + } + + #[wasm_bindgen] + pub fn set_envp(&mut self, envp: Box<[JsValue]>) -> Result<(), JsValue> { + self.envp = envp.iter().filter_map(|val| val.as_string()).collect(); + Ok(()) + } + + #[wasm_bindgen] + pub fn set_args(&mut self, args: Box<[JsValue]>) -> Result<(), JsValue> { + self.args = args.iter().filter_map(|val| val.as_string()).collect(); + Ok(()) + } + + #[wasm_bindgen] + pub fn get_config_local_path(&self, target: String) -> Result { + let target_enum = match target.as_str() { + "linux" => libdd_library_config::Target::Linux, + "win32" => libdd_library_config::Target::Windows, + "darwin" => libdd_library_config::Target::Macos, + _ => return Err(JsValue::from_str("Unsupported target")), + }; + Ok( + libdd_library_config::Configurator::local_stable_configuration_path(target_enum) + .to_string(), + ) + } + + #[wasm_bindgen] + pub fn get_config_managed_path(&self, target: String) -> Result { + let target_enum = match target.as_str() { + "linux" => libdd_library_config::Target::Linux, + "win32" => libdd_library_config::Target::Windows, + "darwin" => libdd_library_config::Target::Macos, + _ => return Err(JsValue::from_str("Unsupported target")), + }; + Ok( + libdd_library_config::Configurator::fleet_stable_configuration_path(target_enum) + .to_string(), + ) + } + + #[wasm_bindgen] + pub fn get_configuration( + &self, + config_string_local: String, + config_string_managed: String, + ) -> Result, JsValue> { + let envp: Vec> = self.envp.iter().map(|s| s.as_bytes().to_vec()).collect(); + + let args: Vec> = self.args.iter().map(|s| s.as_bytes().to_vec()).collect(); + + let res_config = self.configurator.get_config_from_bytes( + config_string_local.as_bytes(), + config_string_managed.as_bytes(), + libdd_library_config::ProcessInfo { + envp: envp, + args: args, + language: b"nodejs".to_vec(), + }, + ); + + match res_config { + Ok(config) => { + let config_entries: Vec = config + .into_iter() + .map(|c| ConfigEntry { + name: c.name.to_string().into(), + value: c.value, + source: c.source.to_str().into(), + config_id: c.config_id.unwrap_or_default(), + }) + .collect(); + Ok(config_entries) + } + Err(e) => Err(JsValue::from_str(&format!( + "Failed to get configuration: {:?}", + e + ))), + } + } +} diff --git a/crates/pipeline/Cargo.toml b/crates/pipeline/Cargo.toml index e98036d8..0d914374 100644 --- a/crates/pipeline/Cargo.toml +++ b/crates/pipeline/Cargo.toml @@ -1,17 +1,42 @@ [package] name = "pipeline" version = "0.1.0" -edition = "2018" +edition = "2021" +description = "Wasm binding for pipeline span management and trace export" [lib] -crate-type = ["cdylib"] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +crate-type = ["cdylib", "rlib"] [dependencies] -data-pipeline = { git = "https://github.com/DataDog/libdatadog.git", branch = "julio/nodejs-integration" } +wasm-bindgen = "0.2" +wasm-bindgen-futures = "0.4" +js-sys = "0.3" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1" +libdatadog-nodejs-capabilities = { path = "../capabilities" } +libdd-capabilities = { git = "https://github.com/DataDog/libdatadog.git", rev = "1b9b7a26f54f116a0f6525abdcd2013b341921a7" } +libdd-common = { git = "https://github.com/DataDog/libdatadog.git", rev = "1b9b7a26f54f116a0f6525abdcd2013b341921a7", default-features = false } +libdd-data-pipeline = { git = "https://github.com/DataDog/libdatadog.git", rev = "1b9b7a26f54f116a0f6525abdcd2013b341921a7", default-features = false, features = ["telemetry"] } +libdd-trace-utils = { git = "https://github.com/DataDog/libdatadog.git", rev = "1b9b7a26f54f116a0f6525abdcd2013b341921a7", default-features = false, features = ["change-buffer"] } +libdd-trace-stats = { git = "https://github.com/DataDog/libdatadog.git", rev = "1b9b7a26f54f116a0f6525abdcd2013b341921a7", default-features = false } +libdd-trace-protobuf = { git = "https://github.com/DataDog/libdatadog.git", rev = "1b9b7a26f54f116a0f6525abdcd2013b341921a7", default-features = false } +libdd-shared-runtime = { git = "https://github.com/DataDog/libdatadog.git", rev = "1b9b7a26f54f116a0f6525abdcd2013b341921a7", default-features = false } +rmp-serde = "1" +bytes = "1" +http = "1" +web-time = "1" +console_error_panic_hook = "0.1" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +getrandom = { version = "0.2", features = ["js"] } +uuid = { version = "1", features = ["js"] } + +[dev-dependencies] +wasm-bindgen-test = "0.3" -[dependencies.neon] -version = "1.0.0" -default-features = false -features = ["napi-6"] +# The pipeline wasm uses post-MVP features (bulk-memory for change-buffer +# copies, sign-extension, etc.) that the wasm-opt bundled with wasm-pack does +# not enable by default. Pass --all-features so wasm-opt validation passes +# (the smaller wasm crates don't hit this, so it's set per-crate here). +[package.metadata.wasm-pack.profile.release] +wasm-opt = ['-O', '--all-features'] diff --git a/crates/pipeline/src/lib.rs b/crates/pipeline/src/lib.rs index 652d5c22..eb25866e 100644 --- a/crates/pipeline/src/lib.rs +++ b/crates/pipeline/src/lib.rs @@ -1,77 +1,983 @@ -use std::borrow::BorrowMut; -use std::sync::Mutex; -use std::cell::OnceCell; -use neon::prelude::*; -use neon::types::buffer::TypedArray; -use data_pipeline::trace_exporter::TraceExporter; -use data_pipeline::trace_exporter::TraceExporterBuilder; +use libdatadog_nodejs_capabilities::WasmCapabilities; +use libdd_data_pipeline::trace_exporter::agent_response::AgentResponse; +use libdd_data_pipeline::trace_exporter::{ + TelemetryConfig, TraceExporter, TraceExporterBuilder, TraceExporterOutputFormat, +}; +use libdd_data_pipeline::OtlpProtocol; +use libdd_shared_runtime::LocalRuntime; +use std::cell::{Cell, RefCell, UnsafeCell}; +use std::ffi::CStr; +use std::time::Duration; -static EXPORTER: Mutex> = Mutex::new(OnceCell::new()); +use wasm_bindgen::prelude::*; -#[neon::main] -fn main (mut cx: ModuleContext) -> NeonResult<()> { - register(&mut cx) +mod span_string; + +mod span_bytes; + +mod trace_data; +use trace_data::*; + +mod stats; + +use libdd_trace_utils::change_buffer::{ChangeBuffer, ChangeBufferState}; +use libdd_trace_utils::span::v04::{AttributeAnyValue, AttributeArrayValue, SpanEvent}; +use span_string::SpanString; +use std::collections::HashMap; + +mod utils; +use utils::*; + +#[wasm_bindgen(start)] +fn init() { + console_error_panic_hook::set_once(); } -fn register (cx: &mut ModuleContext) -> NeonResult<()> { - cx.export_function("init_trace_exporter", init_trace_exporter)?; - cx.export_function("send_traces", send_traces)?; +// --- span event attribute decoding --- +// +// `addSpanEvent` receives its attributes as a flat little-endian buffer built +// by dd-trace-js. Layout: repeated entries until the buffer is exhausted, each +// [key_len: u32][key: utf8][tag: u8] + value +// where the value depends on `tag`: +// 0 String [len: u32][utf8] +// 1 Boolean [u8 (0/1)] +// 2 Integer [i64] +// 3 Double [f64] +// 4 Array [count: u32] then `count` items, each [item_tag: u8][scalar] +// (item_tag must be 0..=3; nested arrays are rejected) +// The tags mirror libdatadog's `AttributeArrayValue` discriminants +// (String=0, Boolean=1, Integer=2, Double=3, Array=4). Every read is bounded +// against the buffer so a malformed/truncated buffer errors instead of +// panicking (matching the hardening in `stringTableInsertMany`/`prepareChunk`). +fn se_need(buf: &[u8], idx: usize, n: usize) -> Result<(), JsValue> { + // Avoid `idx + n` overflowing: on wasm32 `usize` is 32-bit, and `n` can be + // a u32-derived length (e.g. a crafted `key_len`) near `usize::MAX`, which + // would wrap and let a too-large read slip past the bound and trap on the + // slice. `idx` never exceeds `buf.len()` (it only advances after a checked + // read), so `buf.len() - idx` is the safe remaining-byte form. + if idx > buf.len() || n > buf.len() - idx { + return Err(JsValue::from_str( + "addSpanEvent: truncated span-event attribute buffer", + )); + } Ok(()) } -fn trace_exporter_init( - host: &str, - port: u16, - timeout: u64, - tracer_version: &str, - lang: &str, - lang_version: &str, - lang_interpreter: &str) { +fn se_read_u8(buf: &[u8], idx: &mut usize) -> Result { + se_need(buf, *idx, 1)?; + let b = buf[*idx]; + *idx += 1; + Ok(b) +} + +fn se_read_u32(buf: &[u8], idx: &mut usize) -> Result { + se_need(buf, *idx, 4)?; + get_num(buf, idx) + .ok_or_else(|| JsValue::from_str("addSpanEvent: truncated span-event attribute buffer")) +} + +fn se_read_str(buf: &[u8], idx: &mut usize) -> Result { + let len = se_read_u32(buf, idx)? as usize; + se_need(buf, *idx, len)?; + let s = std::str::from_utf8(&buf[*idx..*idx + len]) + .map_err(|e| JsValue::from_str(&format!("addSpanEvent: invalid utf8: {e}")))?; + *idx += len; + Ok(s.into()) +} + +fn se_read_scalar( + buf: &[u8], + idx: &mut usize, + tag: u8, +) -> Result, JsValue> { + match tag { + 0 => Ok(AttributeArrayValue::String(se_read_str(buf, idx)?)), + 1 => Ok(AttributeArrayValue::Boolean(se_read_u8(buf, idx)? != 0)), + 2 => { + se_need(buf, *idx, 8)?; + let n = get_num(buf, idx).ok_or_else(|| { + JsValue::from_str("addSpanEvent: truncated span-event attribute buffer") + })?; + Ok(AttributeArrayValue::Integer(n)) + } + 3 => { + se_need(buf, *idx, 8)?; + let n = get_num(buf, idx).ok_or_else(|| { + JsValue::from_str("addSpanEvent: truncated span-event attribute buffer") + })?; + Ok(AttributeArrayValue::Double(n)) + } + _ => Err(JsValue::from_str( + "addSpanEvent: invalid span-event attribute tag", + )), + } +} + +fn decode_span_event_attributes( + buf: &[u8], +) -> Result>, JsValue> { + let mut attributes = HashMap::new(); + let mut idx = 0usize; + while idx < buf.len() { + let key = se_read_str(buf, &mut idx)?; + let tag = se_read_u8(buf, &mut idx)?; + let value = if tag == 4 { + let count = se_read_u32(buf, &mut idx)? as usize; + // Each item is at least 1 byte (its tag), so cap the pre-allocation + // to the remaining buffer: an inflated count can't force a huge + // allocation, and the per-item bounded reads catch truncation. + let mut items = Vec::with_capacity(count.min(buf.len().saturating_sub(idx))); + for _ in 0..count { + let item_tag = se_read_u8(buf, &mut idx)?; + if item_tag == 4 { + return Err(JsValue::from_str( + "addSpanEvent: nested arrays are not supported", + )); + } + items.push(se_read_scalar(buf, &mut idx, item_tag)?); + } + AttributeAnyValue::Array(items) + } else { + AttributeAnyValue::SingleValue(se_read_scalar(buf, &mut idx, tag)?) + }; + attributes.insert(key, value); + } + Ok(attributes) +} + +#[wasm_bindgen] +/// All mutable state is behind RefCell to allow `&self` methods on the +/// wasm-bindgen wrapper. This prevents re-entrant borrow panics when: +/// - Plugin instrumentation triggers span creation inside another span's +/// creation (e.g., http client span created during express handler) +/// - Async `flushChunk` holds a borrow across await points while other +/// span operations need access +pub struct WasmSpanState { + change_queue: Vec, + string_table_input: Vec, + /// UnsafeCell because send_trace_chunks_async needs &mut self across an + /// await point. WASM is single-threaded so this is safe — we just need + /// to ensure no overlapping mutable borrows (guaranteed by the JS-side + /// _flushInFlight guard which serializes sendPreparedChunk calls). + /// On wasm the exporter must be built asynchronously (`build_async`), but + /// the wasm-bindgen constructor is synchronous. We stash the configured + /// builder here and build the exporter lazily on the first send (the only + /// path that needs it), inside an async context. + // On wasm the exporter runs on a single-threaded `LocalRuntime` (workers + // spawned via wasm_bindgen_futures::spawn_local); the multi-thread + // ForkSafeRuntime/BasicRuntime are native-only. + exporter: UnsafeCell>>, + builder: UnsafeCell>>, + cbs: RefCell>, + stats_collector: RefCell>, + /// Chunks staged by `prepareChunk`, one per trace (segment), sent together by + /// `sendPreparedChunk` as a single multi-trace request. The exporter groups a + /// flush batch by trace and calls `prepareChunk` once per trace so each chunk + /// carries exactly one segment's spans with correct per-trace tags. + prepared_spans: RefCell>>>, + /// Re-entrancy guard for `sendPreparedChunk`. wasm-bindgen async exports + /// can be invoked again from JS before the prior future resolves; without + /// this, two calls would each take `&mut` out of `exporter`/`builder` and + /// alias across the await (UB). The guard makes a re-entrant call return + /// an error instead. + sending: Cell, + /// When true, the lazily-built exporter is configured for v0.5 output + /// (`/v0.5/traces`) instead of the default v0.4. v0.5 is a smaller, fixed + /// 12-field schema with NO slots for `meta_struct`/`span_events`/`span_links`, + /// so libdatadog's v0.5 serializer silently drops them — this mirrors + /// dd-trace-js master's v0.5 encoder and is intentional. Caller (dd-trace-js) + /// must only enable this after confirming the agent advertises `/v0.5/traces` + /// (libdd does NOT downgrade V05 the way it does V1). The output format is + /// fixed once the exporter is built on the first send, so `setUseV05` only + /// takes effect if called before then. + use_v05: Cell, + /// When set, the lazily-built exporter is configured to export traces via + /// OTLP HTTP to this endpoint (e.g. an OTel Collector) INSTEAD of the + /// Datadog agent. libdatadog maps its internal traces to OTLP, so no + /// JS-formatted spans are involved. Like `use_v05`, only takes effect if + /// set before the first send (when the exporter is built). + otlp_endpoint: Cell>, + /// OTLP wire protocol (`http/json` default, or `http/protobuf`). Only + /// applied when `otlp_endpoint` is set. + otlp_protocol: Cell>, + /// Extra HTTP headers for OTLP export (e.g. collector auth), as key/value + /// pairs. Only applied when `otlp_endpoint` is set. + otlp_headers: Cell>, + /// When set, the lazily-built exporter has telemetry enabled with this + /// config. Only takes effect if set before the first send + /// (when the exporter is built). + telemetry_config: Cell>, + /// Latched message from a failed lazy `build_async`. Building is one-shot and + /// a failure is fatal (bad config), so once set every send returns it (as a + /// distinguishable error) instead of a misleading "builder already consumed", + /// letting the host stop retrying. + build_error: RefCell>, +} + +/// Clears an in-flight flag on drop, so an early return or a dropped future +/// still resets it. +struct InFlightGuard<'a>(&'a Cell); +impl Drop for InFlightGuard<'_> { + fn drop(&mut self) { + self.0.set(false); + } +} - EXPORTER.lock().unwrap().get_or_init(|| { - TraceExporterBuilder::default() - .set_host(host) - .set_port(port) +fn stats_flush_result(sent: bool, collapsed_spans: u64) -> Result { + let result = js_sys::Object::new(); + js_sys::Reflect::set( + &result, + &JsValue::from_str("sent"), + &JsValue::from_bool(sent), + )?; + js_sys::Reflect::set( + &result, + &JsValue::from_str("collapsedSpans"), + &JsValue::from_f64(collapsed_spans as f64), + )?; + Ok(result.into()) +} + +#[wasm_bindgen] +impl WasmSpanState { + #[wasm_bindgen(constructor)] + pub fn new( + url: &str, + tracer_version: &str, + lang: &str, + lang_version: &str, + lang_interpreter: &str, + change_queue_size: u32, + string_table_input_size: u32, + pid: u32, + tracer_service: &str, + stats_enabled: bool, + hostname: &str, + env: &str, + app_version: &str, + runtime_id: &str, + client_computed_stats: bool, + ) -> Result { + let mut builder = TraceExporterBuilder::::new(); + builder + .set_url(url) .set_tracer_version(tracer_version) .set_language(lang) .set_language_version(lang_version) .set_language_interpreter(lang_interpreter) - .set_timeout(timeout) - .build() - .unwrap() + .set_otlp_instrumentation_scope("dd-trace-js", tracer_version) + // Populate the payload-level TracerMetadata (service/env/hostname/ + // app_version) the agent receives. These values are already passed + // in for the stats collector; without these calls the trace + // payload's tracer metadata is sent empty. + .set_service(tracer_service) + .set_env(env) + .set_hostname(hostname) + .set_app_version(app_version) + .set_runtime_id(runtime_id) + .enable_agent_rates_payload_version(); - }); -} + // Advertise `Datadog-Client-Computed-Stats` so the agent skips its own + // APM stats/sampling for these traces. This is required in two cases: + // - `stats_enabled`: we build a StatsCollector and send client-side + // stats, so the agent MUST NOT also compute them (double counting); + // - `client_computed_stats`: set independently for APM-standalone + // (apmTracingEnabled=false), where the agent should skip APM stats + // even though we don't compute them client-side. + // Enabling stats therefore always implies the header, so OR the flags + // rather than relying on the caller to keep them in sync. + if client_computed_stats || stats_enabled { + builder.set_client_computed_stats(); + } + + let mut change_queue = vec![0u8; change_queue_size as usize]; + let change_buffer = unsafe { + ChangeBuffer::from_raw_parts( + std::ptr::NonNull::new(change_queue.as_mut_ptr()) + .expect("Vec::as_mut_ptr is never null"), + change_queue.len(), + ) + }; + let change_buffer_state = ChangeBufferState::new( + change_buffer, + tracer_service.into(), + // The change buffer stamps this onto every span's `language` meta tag. + // For the Node.js tracer that must be "javascript" (matching the JS + // pipeline's span_format), NOT the `Datadog-Meta-Lang: nodejs` header + // value (`lang`) that identifies the tracer library to the agent. The + // two legitimately differ for Node, so we can't reuse `lang` here. + "javascript".into(), + pid, + ); + + let stats_collector = if stats_enabled { + Some(stats::StatsCollector::new( + Duration::from_secs(10), + url.to_string(), + stats::StatsMeta { + hostname: hostname.to_string(), + env: env.to_string(), + version: app_version.to_string(), + lang: lang.to_string(), + tracer_version: tracer_version.to_string(), + runtime_id: runtime_id.to_string(), + service: tracer_service.to_string(), + }, + )) + } else { + None + }; + + Ok(WasmSpanState { + change_queue, + string_table_input: vec![0u8; string_table_input_size as usize], + exporter: UnsafeCell::new(None), + builder: UnsafeCell::new(Some(builder)), + cbs: RefCell::new(change_buffer_state), + stats_collector: RefCell::new(stats_collector), + prepared_spans: RefCell::new(Vec::new()), + sending: Cell::new(false), + use_v05: Cell::new(false), + otlp_endpoint: Cell::new(None), + otlp_protocol: Cell::new(None), + otlp_headers: Cell::new(Vec::new()), + telemetry_config: Cell::new(None), + build_error: RefCell::new(None), + }) + } + + /// Select v0.5 output for the trace exporter. Must be called before the + /// first `sendPreparedChunk` (the exporter is built lazily on first send and + /// the output format is fixed at build time; later calls have no effect). + /// + /// v0.5 silently drops `meta_struct` (and top-level `span_events`/`span_links`) + /// because the v0.5 wire schema has no slots for them — the caller is + /// responsible for only enabling this when the agent supports `/v0.5/traces`. + #[wasm_bindgen(js_name = "setUseV05")] + pub fn set_use_v05(&self, v: bool) { + self.use_v05.set(v); + } + + /// Route trace export through libdatadog's OTLP HTTP exporter to `url` + /// instead of the Datadog agent. Must be called before the first send. + /// Takes precedence over `setUseV05` (OTLP bypasses the agent entirely). + #[wasm_bindgen(js_name = "setOtlpEndpoint")] + pub fn set_otlp_endpoint(&self, url: String) { + self.otlp_endpoint.set(Some(url)); + } + + /// Select the OTLP wire protocol: `http/json` (default) or `http/protobuf`. + /// Rejects unsupported values (e.g. `grpc`). Only takes effect with an OTLP + /// endpoint set, before the first send. + #[wasm_bindgen(js_name = "setOtlpProtocol")] + pub fn set_otlp_protocol(&self, protocol: String) -> Result<(), JsValue> { + let parsed = protocol + .parse::() + .map_err(|e| JsValue::from_str(&format!("setOtlpProtocol: {e}")))?; + self.otlp_protocol.set(Some(parsed)); + Ok(()) + } + + /// Enable telemetry on the lazily-built trace exporter. + /// + /// Must be called before the first `sendPreparedChunk`. Later calls have + /// no effect. + /// + /// # Arguments + /// + /// - `heartbeat_ms`: sets the metric-flush cadence. Set to 0 to defer to + /// libdatadog's default interval. + /// - `runtime_id`: tags telemetry payloads with the tracer's runtime id when provided + /// - `debug_enabled`: toggles libdd-telemetry's verbose logging + #[wasm_bindgen(js_name = "enableTelemetry")] + pub fn enable_telemetry( + &self, + heartbeat_ms: u32, + runtime_id: Option, + debug_enabled: bool, + ) { + self.telemetry_config.set(Some(TelemetryConfig { + heartbeat: u64::from(heartbeat_ms), + runtime_id, + debug_enabled, + })); + } + + /// Set extra HTTP headers for OTLP export as a flat `[key, value, ...]` + /// array (the host flattens its key/value map). Only takes effect with an + /// OTLP endpoint set, before the first send. A trailing unpaired element on + /// an odd-length array is ignored. Each call replaces any previously set headers. + #[wasm_bindgen(js_name = "setOtlpHeaders")] + pub fn set_otlp_headers(&self, kv: Vec) { + // chunks_exact drops a trailing unpaired element; the host always passes + // complete [key, value] pairs. + let headers = kv + .chunks_exact(2) + .map(|pair| (pair[0].clone(), pair[1].clone())) + .collect(); + self.otlp_headers.set(headers); + } + + #[wasm_bindgen] + pub fn change_queue_ptr(&self) -> *const u8 { + self.change_queue.as_ptr() + } + + #[wasm_bindgen] + pub fn change_queue_len(&self) -> u32 { + self.change_queue.len() as u32 + } + + #[wasm_bindgen] + pub fn string_table_input_ptr(&self) -> *const u8 { + self.string_table_input.as_ptr() + } + + #[wasm_bindgen] + pub fn string_table_input_len(&self) -> u32 { + self.string_table_input.len() as u32 + } -fn init_trace_exporter(mut cx: FunctionContext) -> JsResult{ - let host = cx.argument::(0)?.value(cx.borrow_mut()); - let port = cx.argument::(1)?.value(cx.borrow_mut()); - let timeout = cx.argument::(2)?.value(cx.borrow_mut()); - let tracer_version = cx.argument::(3)?.value(cx.borrow_mut()); - let lang = cx.argument::(4)?.value(cx.borrow_mut()); - let lang_version = cx.argument::(5)?.value(cx.borrow_mut()); - let lang_interpreter = cx.argument::(5)?.value(cx.borrow_mut()); + /// Prepare a chunk of spans for sending. Flushes the change buffer, + /// extracts spans, feeds stats. Returns `true` if a chunk was prepared + /// (there are spans to send) and `false` if there was nothing to send. + /// Must be followed by `sendPreparedChunk()` to actually send. + #[wasm_bindgen(js_name = "prepareChunk")] + pub fn prepare_chunk( + &self, + len: u32, + first_is_local_root: bool, + chunk: &[u8], + ) -> Result { + // Validate the JS-supplied count against the actual buffer size before + // doing any work: each span id is a u64 (8 bytes). This prevents an + // out-of-bounds read panic (and a huge `Vec::with_capacity`) when the + // caller passes a `len` larger than the chunk can hold. + if (len as usize).saturating_mul(8) > chunk.len() { + return Err(JsValue::from_str( + "prepareChunk: len exceeds the span-id bytes available in chunk", + )); + } + if len == 0 { + // Nothing to prepare for this trace; leave any chunks already staged + // for other traces in this same flush untouched. + return Ok(false); + } - trace_exporter_init( - &host, - port as u16, - timeout as u64, - &tracer_version, - &lang, - &lang_version, - &lang_interpreter); + self.cbs + .borrow_mut() + .flush_change_buffer() + .map_err(|e| JsValue::from_str(&e.to_string()))?; - Ok(cx.undefined()) + let mut count = len; + let mut index = 0; + let mut span_ids = Vec::with_capacity(count as usize); + while count > 0 { + let span_id: u64 = get_num(chunk, &mut index) + .ok_or_else(|| JsValue::from_str("prepareChunk: span id index out of bounds"))?; + span_ids.push(span_id); + count -= 1; + } + + let spans_vec = self + .cbs + .borrow_mut() + .flush_chunk(&span_ids, first_is_local_root) + .map_err(|e| JsValue::from_str(&e.to_string()))?; + + if let Some(collector) = self.stats_collector.borrow_mut().as_mut() { + collector.add_spans(&spans_vec); + } + + // Stage this trace's chunk for the subsequent sendPreparedChunk call. + // Multiple prepareChunk calls (one per trace) accumulate here and are + // sent together as one multi-trace request. An empty result (e.g. every + // span already extracted) is not staged. + let has_spans = !spans_vec.is_empty(); + if has_spans { + self.prepared_spans.borrow_mut().push(spans_vec); + } + Ok(has_spans) + } + + /// Send the previously prepared chunk. + /// + /// Uses `&self` (not `&mut self`); exclusive access to the exporter is + /// enforced at runtime by the `sending` re-entrancy guard below rather + /// than by the borrow checker. WASM is single-threaded, so the only way + /// two `&mut` to the exporter could co-exist is async re-entrancy (JS + /// calling this again before the prior future resolves) — the guard + /// rejects that with an error instead of allowing aliasing (UB). + #[wasm_bindgen(js_name = "sendPreparedChunk")] + pub async fn send_prepared_chunk(&self) -> Result { + if self.sending.get() { + return Err(JsValue::from_str("sendPreparedChunk is already in flight")); + } + self.sending.set(true); + let _in_flight = InFlightGuard(&self.sending); + + let chunks = std::mem::take(&mut *self.prepared_spans.borrow_mut()); + if chunks.is_empty() { + return Err(JsValue::from_str("no prepared chunk to send")); + } + + // SAFETY: WASM is single-threaded and the `sending` guard above + // guarantees no overlapping invocation, so this is the only live + // reference to the exporter for the duration of the awaits. + let exporter_slot = unsafe { &mut *self.exporter.get() }; + if exporter_slot.is_none() { + // A previous build attempt failed. Building is one-shot and the + // failure is fatal (bad config won't fix itself), so return it + // consistently — as a distinguishable error the host bails on — + // rather than a misleading "builder already consumed". + if let Some(msg) = self.build_error.borrow().clone() { + return Err(build_failure_error(&msg)); + } + // First send: build the exporter asynchronously. `build` is not + // available on wasm (it needs a blocking runtime), so we drive + // `build_async` here where we already have an async context. + let mut builder = unsafe { &mut *self.builder.get() } + .take() + .ok_or_else(|| JsValue::from_str("exporter builder already consumed"))?; + // Output format is decided here, at first build, and then fixed. + // v0.5 drops meta_struct/span_events/span_links by design (the v0.5 + // schema has no slots for them); dd-trace-js only enables this after + // confirming agent `/v0.5/traces` support via `/info`. + if self.use_v05.get() { + builder.set_output_format(TraceExporterOutputFormat::V05); + } + // When an OTLP endpoint is configured, libdatadog exports traces via + // OTLP HTTP to that endpoint instead of the Datadog agent (mutually + // exclusive with the agent v0.4/v0.5 path). + if let Some(url) = self.otlp_endpoint.take() { + builder.set_otlp_endpoint(&url); + if let Some(protocol) = self.otlp_protocol.take() { + builder.set_otlp_protocol(protocol); + } + let headers = self.otlp_headers.take(); + if !headers.is_empty() { + builder.set_otlp_headers(headers); + } + } + if let Some(cfg) = self.telemetry_config.take() { + builder.enable_telemetry(cfg); + } + match builder.build_async::().await { + Ok(built) => *exporter_slot = Some(built), + Err(e) => { + // Latch the failure: the builder is now consumed and the + // config won't change, so every later send must fail fast. + let msg = format!("native exporter build failed: {e:?}"); + *self.build_error.borrow_mut() = Some(msg.clone()); + return Err(build_failure_error(&msg)); + } + } + } + let exporter = match exporter_slot.as_mut() { + Some(exporter) => exporter, + // Unreachable: the block above either set `Some` or returned early. + None => return Err(build_failure_error("native exporter unavailable")), + }; + let resp = exporter.send_trace_chunks_async(chunks).await; + let response_str = resp.map(|resp| match resp { + AgentResponse::Unchanged => "unchanged".to_string(), + AgentResponse::Changed { body } => body, + }); + + response_str + .map(|s| JsValue::from_str(&s)) + .map_err(|e| JsValue::from_str(&format!("{:?}", e))) + } + + /// Flush aggregated stats to the agent's /v0.6/stats endpoint. + /// + /// Should be called periodically (e.g. every 10s) from JS, and with + /// `force=true` on shutdown. Returns `{ sent, collapsedSpans }` so JS can + /// emit the same collapsed-span health metric as libdd-trace-stats's native + /// exporter. + #[wasm_bindgen(js_name = "flushStats")] + pub async fn flush_stats(&self, force: bool) -> Result { + // Build the stats request under a brief *synchronous* borrow, then drop + // the borrow BEFORE the async send. The collector therefore stays in + // `stats_collector`, so a concurrent `prepareChunk` during the in-flight + // send still reaches `add_spans` and those spans are counted. (Taking + // the collector out for the whole await would silently drop them from + // client-side stats.) No borrow is held across the await, so there is + // no double-borrow hazard from overlapping calls. + let prepared = { + let mut guard = self.stats_collector.borrow_mut(); + match guard.as_mut() { + Some(collector) => collector + .prepare_request(force) + .map_err(|e| JsValue::from_str(&e))?, + None => return stats_flush_result(false, 0), + } + }; + let sent = match prepared.request { + Some(req) => { + stats::StatsCollector::send_request(req) + .await + .map_err(|e| JsValue::from_str(&e))?; + true + } + None => false, + }; + + stats_flush_result(sent, prepared.collapsed_spans) + } + + /// Flush the queued change-buffer operations. On success always returns + /// `true` (the bool exists only for signature symmetry with the other + /// flush methods); failures surface as a thrown error. + #[wasm_bindgen(js_name = "flushChangeQueue")] + pub fn flush_change_queue(&self) -> Result { + self.cbs + .borrow_mut() + .flush_change_buffer() + .map_err(|e| JsValue::from_str(&e.to_string()))?; + Ok(true) + } + + /// Set default meta tags applied to every new span. + /// Takes a flat array of key-value pairs: [key1, val1, key2, val2, ...] + #[wasm_bindgen(js_name = "setDefaultMeta")] + pub fn set_default_meta(&self, pairs: Vec) -> Result<(), JsValue> { + let mut tags = Vec::with_capacity(pairs.len() / 2); + let mut i = 0; + while i + 1 < pairs.len() { + let key = pairs[i] + .as_string() + .ok_or_else(|| JsValue::from_str("default meta key must be a string"))?; + let val = pairs[i + 1] + .as_string() + .ok_or_else(|| JsValue::from_str("default meta value must be a string"))?; + tags.push((key.into(), val.into())); + i += 2; + } + self.cbs.borrow_mut().set_default_meta(tags); + Ok(()) + } + + #[wasm_bindgen(js_name = "stringTableInsertOne")] + pub fn string_table_insert_one(&self, key: u32, val: &str) { + self.cbs + .borrow_mut() + .string_table_insert_one(key, val.into()); + } + + #[wasm_bindgen(js_name = "stringTableInsertMany")] + pub fn string_table_insert_many(&self, count: u32) -> Result<(), JsValue> { + let mut index: usize = 0; + let mut remaining = count as usize; + // Hold one mutable borrow for the whole bulk insert rather than + // re-borrowing the RefCell once per string. + let mut cbs = self.cbs.borrow_mut(); + let buf = &self.string_table_input; + while remaining > 0 { + // Bound the read against the untrusted `count`: a count larger than + // the encoded entries must error, not index out of bounds. get_num + // does the (overflow-safe) bounds check and returns None past the end. + let key: u32 = get_num(buf, &mut index).ok_or_else(|| { + JsValue::from_str( + "stringTableInsertMany: count exceeds the entries in the input buffer", + ) + })?; + let str_slice = &buf[index..]; + // Bound the NUL scan to the input slice so a non-terminated string + // can't read past the buffer, and advance past the NUL terminator + // (+ 1) so the next entry parses from the right offset. + let cstr = CStr::from_bytes_until_nul(str_slice) + .map_err(|e| JsValue::from_str(&format!("{}", e)))?; + let val = cstr + .to_str() + .map_err(|e| JsValue::from_str(&format!("{}", e)))?; + index += val.len() + 1; + // From<&str> for SpanString is a single Arc allocation — no + // intermediate owned String. + cbs.string_table_insert_one(key, val.into()); + remaining -= 1; + } + Ok(()) + } + + #[wasm_bindgen(js_name = "stringTableEvict")] + pub fn string_table_evict(&self, key: u32) { + self.cbs.borrow_mut().string_table_evict_one(key); + } + + // Absent-entity convention: span-level getters return an error (JS throw) + // for an unknown span_id, while trace-level getters and attribute lookups + // return null for an unknown segment / unset attribute. + #[wasm_bindgen(js_name = "getServiceName")] + pub fn get_service_name(&self, span_id: u64) -> Result { + self.flush_change_queue()?; + let cbs = self.cbs.borrow(); + let span = cbs + .get_span(span_id) + .map_err(|e| JsValue::from_str(&e.to_string()))?; + Ok(span.service.to_string()) + } + + #[wasm_bindgen(js_name = "getResourceName")] + pub fn get_resource_name(&self, span_id: u64) -> Result { + self.flush_change_queue()?; + let cbs = self.cbs.borrow(); + let span = cbs + .get_span(span_id) + .map_err(|e| JsValue::from_str(&e.to_string()))?; + Ok(span.resource.to_string()) + } + + #[wasm_bindgen(js_name = "getMetaAttr")] + pub fn get_meta_attr(&self, span_id: u64, name: &str) -> Result { + self.flush_change_queue()?; + let cbs = self.cbs.borrow(); + let span = cbs + .get_span(span_id) + .map_err(|e| JsValue::from_str(&e.to_string()))?; + // VecMap::get accepts &str directly (SpanString: Borrow), so no + // SpanString allocation is needed for the lookup. + Ok(span + .meta + .get(name) + .map(|v| JsValue::from_str(v.0.as_ref())) + .unwrap_or(JsValue::NULL)) + } + + #[wasm_bindgen(js_name = "getMetricAttr")] + pub fn get_metric_attr(&self, span_id: u64, name: &str) -> Result { + self.flush_change_queue()?; + let cbs = self.cbs.borrow(); + let span = cbs + .get_span(span_id) + .map_err(|e| JsValue::from_str(&e.to_string()))?; + Ok(span + .metrics + .get(name) + .map(|v| JsValue::from_f64(*v)) + .unwrap_or(JsValue::NULL)) + } + + #[wasm_bindgen(js_name = "getError")] + pub fn get_error(&self, span_id: u64) -> Result { + self.flush_change_queue()?; + let cbs = self.cbs.borrow(); + let span = cbs + .get_span(span_id) + .map_err(|e| JsValue::from_str(&e.to_string()))?; + Ok(span.error) + } + + // start/duration are i64 nanoseconds. Returning them as JS BigInt (not + // f64) preserves full precision — real epoch-ns values exceed 2^53 and + // would be silently truncated as f64. + #[wasm_bindgen(js_name = "getStart")] + pub fn get_start(&self, span_id: u64) -> Result { + self.flush_change_queue()?; + let cbs = self.cbs.borrow(); + let span = cbs + .get_span(span_id) + .map_err(|e| JsValue::from_str(&e.to_string()))?; + Ok(span.start) + } + + #[wasm_bindgen(js_name = "getDuration")] + pub fn get_duration(&self, span_id: u64) -> Result { + self.flush_change_queue()?; + let cbs = self.cbs.borrow(); + let span = cbs + .get_span(span_id) + .map_err(|e| JsValue::from_str(&e.to_string()))?; + Ok(span.duration) + } + + #[wasm_bindgen(js_name = "getType")] + pub fn get_type(&self, span_id: u64) -> Result { + self.flush_change_queue()?; + let cbs = self.cbs.borrow(); + let span = cbs + .get_span(span_id) + .map_err(|e| JsValue::from_str(&e.to_string()))?; + Ok(span.r#type.to_string()) + } + + #[wasm_bindgen(js_name = "getName")] + pub fn get_name(&self, span_id: u64) -> Result { + self.flush_change_queue()?; + let cbs = self.cbs.borrow(); + let span = cbs + .get_span(span_id) + .map_err(|e| JsValue::from_str(&e.to_string()))?; + Ok(span.name.to_string()) + } + + // `meta_struct` carries msgpack-encoded structured data (e.g. AppSec, Code + // Origin, Dynamic Instrumentation). There is no change-buffer opcode for it, + // so the value is written directly onto the span after draining the queue — + // meta_struct does not depend on any other queued op, so bypassing the queue + // ordering is safe (subsequent ops are applied on the next flush and never + // touch meta_struct). + #[wasm_bindgen(js_name = "setMetaStruct")] + pub fn set_meta_struct(&self, span_id: u64, key: &str, value: &[u8]) -> Result<(), JsValue> { + self.flush_change_queue()?; + let mut cbs = self.cbs.borrow_mut(); + let span = cbs + .span_mut(span_id) + .map_err(|e| JsValue::from_str(&e.to_string()))?; + span.meta_struct + .insert(key.into(), span_bytes::SpanBytesImpl(value.to_vec())); + Ok(()) + } + + #[wasm_bindgen(js_name = "getMetaStruct")] + pub fn get_meta_struct(&self, span_id: u64, key: &str) -> Result { + self.flush_change_queue()?; + let cbs = self.cbs.borrow(); + let span = cbs + .get_span(span_id) + .map_err(|e| JsValue::from_str(&e.to_string()))?; + // VecMap::get accepts &str directly (SpanString: Borrow). + Ok(span + .meta_struct + .get(key) + .map(|v| JsValue::from(js_sys::Uint8Array::from(v.0.as_slice()))) + .unwrap_or(JsValue::NULL)) + } + + // Span events (OpenTelemetry-style) are serialized by libdatadog as the + // top-level v0.4 `span_events` field when present. Like meta_struct there + // is no change-buffer opcode, so the event is appended directly to the span + // after draining the queue (span_events do not depend on any other queued + // op, so bypassing queue ordering is safe). `attrs_buf` is the flat typed + // attribute encoding decoded by `decode_span_event_attributes`. + #[wasm_bindgen(js_name = "addSpanEvent")] + pub fn add_span_event( + &self, + span_id: u64, + name: &str, + time_unix_nano: u64, + attrs_buf: &[u8], + ) -> Result<(), JsValue> { + self.flush_change_queue()?; + // Decode before borrowing cbs mutably so a malformed buffer errors + // without holding the borrow. + let attributes = decode_span_event_attributes(attrs_buf)?; + let mut cbs = self.cbs.borrow_mut(); + let span = cbs + .span_mut(span_id) + .map_err(|e| JsValue::from_str(&e.to_string()))?; + span.span_events.push(SpanEvent { + time_unix_nano, + name: name.into(), + attributes, + }); + Ok(()) + } + + // Test/inspection helper: serialize the span's events to JSON via the same + // serde `Serialize` impl libdatadog uses for the msgpack wire format, so + // the `type`/`*_value` shape mirrors exactly what is sent to the agent + // (String=0, Boolean=1, Integer=2, Double=3, Array=4). + #[wasm_bindgen(js_name = "getSpanEventsJson")] + pub fn get_span_events_json(&self, span_id: u64) -> Result { + self.flush_change_queue()?; + let cbs = self.cbs.borrow(); + let span = cbs + .get_span(span_id) + .map_err(|e| JsValue::from_str(&e.to_string()))?; + serde_json::to_string(&span.span_events) + .map_err(|e| JsValue::from_str(&format!("getSpanEventsJson: {e}"))) + } + + // Trace-level attributes live on the Segment (keyed by segment_id, which + // JS allocates and shares across spans in the same local trace). + #[wasm_bindgen(js_name = "getTraceMetaAttr")] + pub fn get_trace_meta_attr(&self, segment_id: u64, name: &str) -> Result { + self.flush_change_queue()?; + let cbs = self.cbs.borrow(); + Ok(cbs + .get_segment(&segment_id) + .and_then(|s| s.meta.get(name)) + .map(|v| JsValue::from_str(v.0.as_ref())) + .unwrap_or(JsValue::NULL)) + } + + #[wasm_bindgen(js_name = "getTraceMetricAttr")] + pub fn get_trace_metric_attr(&self, segment_id: u64, name: &str) -> Result { + self.flush_change_queue()?; + let cbs = self.cbs.borrow(); + Ok(cbs + .get_segment(&segment_id) + .and_then(|s| s.metrics.get(name)) + .map(|v| JsValue::from_f64(*v)) + .unwrap_or(JsValue::NULL)) + } + + #[wasm_bindgen(js_name = "getTraceOrigin")] + pub fn get_trace_origin(&self, segment_id: u64) -> Result { + self.flush_change_queue()?; + let cbs = self.cbs.borrow(); + Ok(cbs + .get_segment(&segment_id) + .and_then(|s| s.origin.as_ref()) + .map(|v| JsValue::from_str(v.0.as_ref())) + .unwrap_or(JsValue::NULL)) + } } -fn send_traces(mut cx: FunctionContext) -> JsResult { - let trace_count = cx.argument::(1)?.value(cx.borrow_mut()); - let data = cx.argument::(0)?.as_slice(cx.borrow_mut()); +/// Export WASM memory so JS can create views into it +#[wasm_bindgen(js_name = "getWasmMemory")] +pub fn get_wasm_memory() -> JsValue { + wasm_bindgen::memory() +} - let response = EXPORTER.lock().unwrap().get().unwrap().send(data, trace_count as usize); +/// Export OpCode values as a JS object. +/// Values match the `#[repr(u64)]` OpCode enum in libdd-trace-utils. +#[wasm_bindgen(js_name = "getOpCodes")] +pub fn get_op_codes() -> JsValue { + let obj = js_sys::Object::new(); + let entries: &[(&str, u32)] = &[ + ("Create", 0), + ("SetMetaAttr", 1), + ("SetMetricAttr", 2), + ("SetServiceName", 3), + ("SetResourceName", 4), + ("SetError", 5), + ("SetStart", 6), + ("SetDuration", 7), + ("SetType", 8), + ("SetName", 9), + ("SetTraceMetaAttr", 10), + ("SetTraceMetricsAttr", 11), + ("SetTraceOrigin", 12), + ]; + for (name, val) in entries { + js_sys::Reflect::set( + &obj, + &JsValue::from_str(name), + &JsValue::from_f64(*val as f64), + ) + .expect("Reflect::set on a freshly created object cannot fail"); + } + obj.into() +} - Ok(cx.string(response.unwrap_or("Error sending traces".to_string()))) +/// Build a JS `Error` tagged `NativeExporterBuildError` so the host can +/// recognise a fatal exporter-build failure (bad config) and stop retrying, +/// rather than treating it as a transient send error. +fn build_failure_error(msg: &str) -> JsValue { + let err = js_sys::Error::new(msg); + err.set_name("NativeExporterBuildError"); + err.into() } +#[wasm_bindgen(js_name = "setStorage")] +pub fn set_storage(new_storage: &JsValue) { + libdatadog_nodejs_capabilities::http::set_storage(new_storage); +} +#[wasm_bindgen(js_name = "setResponseHeaderObserver")] +pub fn set_response_header_observer(observer: &JsValue) { + libdatadog_nodejs_capabilities::http::set_response_header_observer(observer); +} diff --git a/crates/pipeline/src/span_bytes.rs b/crates/pipeline/src/span_bytes.rs new file mode 100644 index 00000000..3dfaf1a4 --- /dev/null +++ b/crates/pipeline/src/span_bytes.rs @@ -0,0 +1,25 @@ +use libdd_trace_utils::span::SpanBytes; +use serde::Serialize; +use std::borrow::Borrow; +use std::hash::{Hash, Hasher}; + +#[derive(Default, Debug, Eq, PartialEq, Clone, Serialize)] +pub struct SpanBytesImpl(pub Vec); + +impl Hash for SpanBytesImpl { + fn hash(&self, state: &mut H) { + self.0.hash(state); + } +} + +impl Borrow<[u8]> for SpanBytesImpl { + fn borrow(&self) -> &[u8] { + &self.0 + } +} + +impl SpanBytes for SpanBytesImpl { + fn from_static_bytes(value: &'static [u8]) -> Self { + SpanBytesImpl(value.to_vec()) + } +} diff --git a/crates/pipeline/src/span_string.rs b/crates/pipeline/src/span_string.rs new file mode 100644 index 00000000..ee752805 --- /dev/null +++ b/crates/pipeline/src/span_string.rs @@ -0,0 +1,46 @@ +use libdd_trace_utils::span::SpanText; +use serde::Serialize; +use std::borrow::Borrow; +use std::fmt::*; +use std::hash::{Hash, Hasher}; +use std::sync::Arc; + +#[derive(Default, Debug, Eq, PartialEq, Serialize, Clone)] +pub struct SpanString(pub Arc); + +impl Hash for SpanString { + fn hash(&self, state: &mut H) { + // Hash the string content, not the Arc pointer + self.0.as_ref().hash(state); + } +} + +impl Borrow for SpanString { + fn borrow(&self) -> &str { + &self.0 + } +} + +impl SpanText for SpanString { + fn from_static_str(value: &'static str) -> Self { + SpanString(Arc::from(value)) + } +} + +impl From for SpanString { + fn from(s: String) -> SpanString { + SpanString(Arc::from(s)) + } +} + +impl From<&str> for SpanString { + fn from(value: &str) -> SpanString { + SpanString(Arc::from(value)) + } +} + +impl Display for SpanString { + fn fmt(&self, formatter: &mut Formatter) -> Result { + write!(formatter, "{}", self.0) + } +} diff --git a/crates/pipeline/src/stats.rs b/crates/pipeline/src/stats.rs new file mode 100644 index 00000000..3e65cb46 --- /dev/null +++ b/crates/pipeline/src/stats.rs @@ -0,0 +1,201 @@ +// Copyright 2026-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +//! Native stats collection for the pipeline WASM module. +//! +//! Wraps `SpanConcentrator` from `libdd-trace-stats` and provides encoding + +//! HTTP transport for flushing stats to the Datadog agent's `/v0.6/stats` +//! endpoint. + +use web_time::{Duration, SystemTime}; + +/// Wall-clock now() for wasm. Delegates to `web_time::SystemTime::now()`, +/// which routes to JS `Date.now()` on `wasm32-unknown-unknown` (native +/// `std::time::SystemTime::now()` is unimplemented on that target and traps). +fn now() -> SystemTime { + SystemTime::now() +} + +use bytes::Bytes; +use libdatadog_nodejs_capabilities::WasmHttpClient; +use libdd_capabilities::http::HttpClientCapability; +use libdd_common::parse_uri; +use libdd_trace_protobuf::pb; +use libdd_trace_stats::span_concentrator::SpanConcentrator; + +use crate::trace_data::WasmTraceData; + +const STATS_ENDPOINT_PATH: &str = "/v0.6/stats"; + +/// Metadata for the stats payload envelope. +pub struct StatsMeta { + pub hostname: String, + pub env: String, + pub version: String, + pub lang: String, + pub tracer_version: String, + pub runtime_id: String, + pub service: String, +} + +/// Stats data prepared by a synchronous concentrator flush. +pub struct PreparedStatsFlush { + pub request: Option>, + pub collapsed_spans: u64, +} + +/// Manages stats aggregation and flushing. +pub struct StatsCollector { + concentrator: SpanConcentrator, + meta: StatsMeta, + agent_url: String, + sequence: u64, +} + +impl StatsCollector { + /// Create a new stats collector. + pub fn new(bucket_size: Duration, agent_url: String, meta: StatsMeta) -> Self { + StatsCollector { + concentrator: SpanConcentrator::new( + bucket_size, + now(), + vec![ + "client".to_string(), + "server".to_string(), + "producer".to_string(), + "consumer".to_string(), + ], + Vec::new(), + None, + Vec::new(), + ), + meta, + agent_url, + sequence: 0, + } + } + + /// Add spans to the concentrator for stats aggregation. + /// + /// The spans should already have `_dd.top_level` and `_dd.measured` metrics + /// set (done by `ChangeBufferState::flush_chunk`). + pub fn add_spans(&mut self, spans: &[libdd_trace_utils::span::v04::Span]) { + for span in spans { + self.concentrator.add_span(span); + } + } + + /// Drain aggregated stats into a ready-to-send request plus flush metadata, + /// **synchronously**. + /// + /// Returns `request: None` when there is no stats payload to send. The + /// concentrator is drained and the sequence advanced as part of this call, + /// so a returned request must be sent (see `send_request`). Kept + /// synchronous and separate from the send so a caller can build the request + /// under a brief borrow and release the collector *before* the async send — + /// leaving it available for `add_spans` while the stats request is in + /// flight. + pub fn prepare_request(&mut self, force: bool) -> Result { + let mut flush = self.concentrator.flush(now(), force); + let collapsed_spans = flush.collapsed_spans; + if !flush.obfuscated_buckets.is_empty() { + // TODO: stats obfuscation is currently disabled. Obfuscated stats + // require the datadog-obfuscation-version header, which + // prepare_request doesn't emit yet. Add that header before enabling + // stats-obfuscation. + return Err( + "stats flush produced obfuscated buckets without obfuscation header support" + .to_string(), + ); + } + if flush.unobfuscated_buckets.is_empty() { + return Ok(PreparedStatsFlush { + request: None, + collapsed_spans, + }); + } + + self.sequence += 1; + let buckets = std::mem::take(&mut flush.unobfuscated_buckets); + let payload = encode_stats_payload(&buckets, &self.meta, self.sequence); + + let body = rmp_serde::encode::to_vec_named(&payload) + .map_err(|e| format!("stats msgpack encode error: {e}"))?; + + // Build the base agent URI exactly like the trace exporter does, via + // libdatadog's `parse_uri`. For a `unix://` / `windows:` agent URL that + // hex-encodes the socket path into the URI *authority* (there is no + // standard URL form for socket paths), which the WASM HTTP client's + // `decode_socket_path` reverses to route over the socket. A raw parse + // instead leaves the socket path in the URI *path* with an empty/invalid + // authority, so the stats request never reaches the socket — client stats + // silently never arrive over UDS (dd-trace-js #9139, uds-express4). + let base = parse_uri(&self.agent_url).map_err(|e| format!("invalid agent URL: {e}"))?; + // Append `/v0.6/stats` to the base path while preserving the (hex) + // authority, mirroring libdd-data-pipeline's `add_path`. For `unix://` + // the base path is "/" and the authority holds the hex socket path; for + // TCP it's `http://host:port/`. Trim a trailing slash so the path is + // exactly `/v0.6/stats` (a double slash makes the agent miss the request). + let base_path = base.path().strip_suffix('/').unwrap_or_else(|| base.path()); + let new_path_and_query = format!("{base_path}{STATS_ENDPOINT_PATH}"); + let mut parts = base.into_parts(); + parts.path_and_query = Some( + new_path_and_query + .parse() + .map_err(|e| format!("invalid stats path: {e}"))?, + ); + let uri = http::Uri::from_parts(parts).map_err(|e| format!("invalid stats URL: {e}"))?; + + let req = http::Request::builder() + .method(http::Method::PUT) + .uri(uri) + .header("Content-Type", "application/msgpack") + .header("Datadog-Meta-Lang", &self.meta.lang) + .header("Datadog-Meta-Tracer-Version", &self.meta.tracer_version) + .body(Bytes::from(body)) + .map_err(|e| format!("failed to build stats request: {e}"))?; + + Ok(PreparedStatsFlush { + request: Some(req), + collapsed_spans, + }) + } + + /// Send a prepared stats request to the agent. Does **not** borrow the + /// collector, so trace export (`add_spans`) can proceed during the await. + pub async fn send_request(req: http::Request) -> Result<(), String> { + let client = WasmHttpClient::new_client(); + client + .request(req) + .await + .map_err(|e| format!("stats send error: {e:?}"))?; + Ok(()) + } +} + +/// Encode flushed stats buckets into a `ClientStatsPayload` for msgpack +/// serialization. +fn encode_stats_payload( + buckets: &[pb::ClientStatsBucket], + meta: &StatsMeta, + sequence: u64, +) -> pb::ClientStatsPayload { + pb::ClientStatsPayload { + hostname: meta.hostname.clone(), + env: meta.env.clone(), + version: meta.version.clone(), + lang: meta.lang.clone(), + tracer_version: meta.tracer_version.clone(), + runtime_id: meta.runtime_id.clone(), + sequence, + stats: buckets.to_vec(), + service: meta.service.clone(), + container_id: String::new(), + tags: Vec::new(), + agent_aggregation: String::new(), + git_commit_sha: String::new(), + image_tag: String::new(), + process_tags: String::new(), + process_tags_hash: 0, + } +} diff --git a/crates/pipeline/src/trace_data.rs b/crates/pipeline/src/trace_data.rs new file mode 100644 index 00000000..94cbcfe2 --- /dev/null +++ b/crates/pipeline/src/trace_data.rs @@ -0,0 +1,16 @@ +use libdd_trace_utils::span::TraceData; +use serde::Serialize; + +use crate::span_bytes::SpanBytesImpl; +use crate::span_string::SpanString; + +// `Serialize` is derived only so the test helper `getSpanEventsJson` can +// serialize `Vec>` (serde's derive on the generic +// `SpanEvent` requires `T: Serialize`). The unit struct carries no data. +#[derive(Clone, Default, Debug, PartialEq, Serialize)] +pub struct WasmTraceData; + +impl TraceData for WasmTraceData { + type Text = SpanString; + type Bytes = SpanBytesImpl; +} diff --git a/crates/pipeline/src/utils.rs b/crates/pipeline/src/utils.rs new file mode 100644 index 00000000..11922621 --- /dev/null +++ b/crates/pipeline/src/utils.rs @@ -0,0 +1,44 @@ +pub trait FromBytes: Sized { + type Bytes: ?Sized; + fn from_bytes(bytes: &[u8]) -> Self; +} + +macro_rules! impl_from_bytes { + ($ty:ty, $len:expr) => { + impl FromBytes for $ty { + type Bytes = $ty; + + // Note that this always does a copy into a new variable. This is + // because the values in the buffer are not aligned. We could save + // ourselves a copy by ensuring alignment from the managed side. + fn from_bytes(bytes: &[u8]) -> Self { + let mut code_buf = [0u8; $len]; + code_buf.copy_from_slice(bytes); + <$ty>::from_le_bytes(code_buf) + } + } + }; +} + +impl_from_bytes!(u128, 16); +impl_from_bytes!(u64, 8); +impl_from_bytes!(f64, 8); +impl_from_bytes!(i64, 8); +impl_from_bytes!(i32, 4); +impl_from_bytes!(u32, 4); + +/// Read a `T` from `buf` at `*index` (little-endian) and advance `*index`. +/// +/// Returns `None` if the buffer is too short, so callers can't index out of +/// bounds — the bounds check lives here rather than relying on every call site. +/// The remaining-bytes form (`size > buf.len() - id`) is overflow-safe. +pub(crate) fn get_num(buf: &[u8], index: &mut usize) -> Option { + let id: usize = *index; + let size = std::mem::size_of::(); + if id > buf.len() || size > buf.len() - id { + return None; + } + let result: T = T::from_bytes(&buf[id..id + size]); + *index += size; + Some(result) +} diff --git a/crates/process_discovery/Cargo.toml b/crates/process_discovery/Cargo.toml new file mode 100644 index 00000000..32e9449e --- /dev/null +++ b/crates/process_discovery/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "process-discovery" +version = "0.1.0" +edition = "2018" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +anyhow = "1" +libdd-library-config = { git = "https://github.com/DataDog/libdatadog.git", rev = "0c6e2a5df2a163d34c4f385353ffc5d7257c72f4", features = ["otel-thread-ctx"] } +libdd-trace-protobuf = { git = "https://github.com/DataDog/libdatadog.git", rev = "0c6e2a5df2a163d34c4f385353ffc5d7257c72f4" } + +napi = { version = "2" } +napi-derive = { version = "2", default-features = false } diff --git a/crates/process_discovery/src/lib.rs b/crates/process_discovery/src/lib.rs new file mode 100644 index 00000000..6e042e8f --- /dev/null +++ b/crates/process_discovery/src/lib.rs @@ -0,0 +1,136 @@ +use napi::{Error, Status}; +use napi_derive::napi; + +use libdd_library_config::tracer_metadata; +use libdd_trace_protobuf::opentelemetry::proto::common::v1::any_value; + +#[napi] +pub struct NapiAnonymousFileHandle { + _internal: tracer_metadata::AnonymousFileHandle, +} + +#[napi] +impl NapiAnonymousFileHandle {} + +/// Additional OTel process-context attribute the threadlocal writer wants to +/// publish alongside the key map (e.g. language-runtime layout constants). Set +/// exactly one of `string_value` / `int_value` — the other variants of OTel's +/// `AnyValue` (bool, double, bytes, array, kvlist) are not yet exposed. +/// Passing both set or neither set is rejected as invalid input. +#[derive(Clone)] +#[napi(object)] +pub struct ExtraAttribute { + pub key: String, + pub string_value: Option, + pub int_value: Option, +} + +/// Thread-level context metadata the tracer wants to publish as part of the +/// OTel process context. When present on a [`TracerMetadata`], drives the +/// `threadlocal.*` block in the emitted process context; when absent, no such +/// block is emitted. +#[derive(Clone)] +#[napi(object)] +pub struct ThreadLocalMetadata { + /// Ordered list of attribute key names for thread-level OTEP-4947 context + /// records. Wire key indices index into this list. libdatadog implicitly + /// prepends `datadog.local_root_span_id` at wire index 0, so entry 0 here + /// is wire key index 1. + pub attribute_keys: Vec, + + /// Value of the `threadlocal.schema_version` attribute. Identifies the + /// on-the-wire record schema (e.g. `"tlsdesc_v1_dev"` for libdatadog's own + /// TLSDESC writer, `"nodejs_v1_dev"` for a Node.js writer). Defaults to + /// `"tlsdesc_v1_dev"` when omitted. + pub schema_version: Option, + + /// Extra `threadlocal.*` attributes to publish alongside the key map (e.g. + /// V8 layout constants a Node.js reader needs to walk from the discovery + /// TLS symbol into the record). + pub extra_attributes: Vec, +} + +#[napi(constructor)] +pub struct TracerMetadata { + pub runtime_id: Option, + pub tracer_version: String, + pub hostname: String, + pub service_name: Option, + pub service_env: Option, + pub service_version: Option, + pub process_tags: Option, + pub container_id: Option, + /// Optional thread-level context metadata; see [`ThreadLocalMetadata`]. + /// `null`/omitted (the default) disables the `threadlocal.*` block in the + /// emitted OTel process context entirely. + pub threadlocal_metadata: Option, +} + +fn convert_extra_attribute(ea: &ExtraAttribute) -> napi::Result<(String, any_value::Value)> { + let value = match (&ea.string_value, ea.int_value) { + (Some(s), None) => any_value::Value::StringValue(s.clone()), + (None, Some(i)) => any_value::Value::IntValue(i), + (Some(_), Some(_)) => { + return Err(Error::new( + Status::InvalidArg, + format!( + "ExtraAttribute {:?}: exactly one of stringValue / intValue must be set, both are", + ea.key, + ), + )); + } + (None, None) => { + return Err(Error::new( + Status::InvalidArg, + format!( + "ExtraAttribute {:?}: exactly one of stringValue / intValue must be set, neither is", + ea.key, + ), + )); + } + }; + Ok((ea.key.clone(), value)) +} + +fn convert_threadlocal_metadata( + tlm: &ThreadLocalMetadata, +) -> napi::Result { + Ok(tracer_metadata::ThreadLocalMetadata { + attribute_keys: tlm.attribute_keys.clone(), + schema_version: tlm.schema_version.clone(), + extra_attributes: tlm + .extra_attributes + .iter() + .map(convert_extra_attribute) + .collect::>()?, + }) +} + +#[napi] +pub fn store_metadata(data: &TracerMetadata) -> napi::Result { + let res = tracer_metadata::store_tracer_metadata(&tracer_metadata::TracerMetadata{ + schema_version: 1, + runtime_id: data.runtime_id.clone(), + tracer_language: String::from("nodejs"), + tracer_version: data.tracer_version.clone(), + hostname: data.hostname.clone(), + service_name: data.service_name.clone(), + service_env: data.service_env.clone(), + service_version: data.service_version.clone(), + process_tags: data.process_tags.clone(), + container_id: data.container_id.clone(), + threadlocal_metadata: data + .threadlocal_metadata + .as_ref() + .map(convert_threadlocal_metadata) + .transpose()?, + }); + + match res { + Ok(handle) => Ok(NapiAnonymousFileHandle{ _internal: handle }), + Err(e) => { + let err_msg = format!("Failed to store the tracer configuration: {:?}", e); + Err(Error::new(Status::GenericFailure, err_msg)) + } + } +} diff --git a/crates/sketches/Cargo.toml b/crates/sketches/Cargo.toml new file mode 100644 index 00000000..43d14deb --- /dev/null +++ b/crates/sketches/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "sketches" +version = "0.1.0" +edition = "2021" +description = "Wasm bindings for Datadog's DDSketch" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +libdd-ddsketch = { git = "https://github.com/DataDog/libdatadog.git", rev = "1b9b7a26f54f116a0f6525abdcd2013b341921a7" } +wasm-bindgen = "0.2" + +[package.metadata.wasm-pack.profile.release] +wasm-opt = ["-O", "--enable-bulk-memory", "--enable-nontrapping-float-to-int"] diff --git a/crates/sketches/src/lib.rs b/crates/sketches/src/lib.rs new file mode 100644 index 00000000..0b55db3d --- /dev/null +++ b/crates/sketches/src/lib.rs @@ -0,0 +1,37 @@ +use libdd_ddsketch::DDSketch as InnerDDSketch; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +#[derive(Default)] +pub struct DDSketch { + inner: InnerDDSketch, +} + +#[wasm_bindgen] +impl DDSketch { + #[wasm_bindgen(constructor)] + pub fn new() -> Self { + Self::default() + } + + pub fn add(&mut self, point: f64) -> Result<(), JsError> { + self.inner + .add(point) + .map_err(|error| JsError::new(&error.to_string())) + } + + #[wasm_bindgen(js_name = addWithCount)] + pub fn add_with_count(&mut self, point: f64, count: f64) -> Result<(), JsError> { + self.inner + .add_with_count(point, count) + .map_err(|error| JsError::new(&error.to_string())) + } + + pub fn count(&self) -> f64 { + self.inner.count() + } + + pub fn encode(&self) -> Vec { + self.inner.clone().encode_to_vec() + } +} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 00000000..690debfb --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,93 @@ +'use strict' + +const eslintPluginImportX = require('eslint-plugin-import-x') +const eslintPluginJs = require('@eslint/js') +const eslintPluginN = require('eslint-plugin-n') +const eslintPluginStylistic = require('@stylistic/eslint-plugin') +const eslintPluginUnicorn = require('eslint-plugin-unicorn').default +const globals = require('globals') + +module.exports = [ + eslintPluginJs.configs.recommended, + eslintPluginImportX.flatConfigs.recommended, + eslintPluginN.configs['flat/recommended-script'], + eslintPluginStylistic.configs.recommended, + eslintPluginUnicorn.configs.recommended, + { + languageOptions: { + ecmaVersion: 2022, + sourceType: 'commonjs', + globals: { + ...globals.es2022, + ...globals.node, + }, + }, + settings: { + // Used by `eslint-plugin-n` to determine the minimum version of Node.js to support. + // Normally setting this in the `package.json` engines field is enough, but we can't use that as it will fail + // when running `yarn copy-artifacts` inside the prebuildify Docker container which uses Node.js 12. + node: { version: '>=18.0.0' }, + }, + rules: { + '@stylistic/brace-style': ['error', '1tbs'], + '@stylistic/space-before-function-paren': ['error', 'always'], + 'import-x/extensions': ['error', 'never', { json: 'always' }], + 'import-x/no-absolute-path': 'error', + 'import-x/no-webpack-loader-syntax': 'error', + 'import-x/order': ['error', { + 'newlines-between': 'always', + }], + 'n/no-process-exit': 'off', // Duplicate of unicorn/no-process-exit + 'prefer-const': 'error', + 'unicorn/prefer-module': 'off', // We use CJS + 'unicorn/prevent-abbreviations': 'off', + }, + }, + { + files: ['load.js'], + languageOptions: { + globals: { + __webpack_require__: 'readonly', + __non_webpack_require__: 'readonly', + }, + }, + }, + { + // This script runs inside the prebuildify Docker container which uses Node.js 12 + files: ['scripts/copy-artifacts.js'], + languageOptions: { + ecmaVersion: 2019, + }, + settings: { + // Used by `eslint-plugin-n` to determine the minimum version of Node.js to support. + node: { version: '>=12.0.0' }, + }, + rules: { + 'unicorn/prefer-node-protocol': 'off', + }, + }, + { + // Test files use the `node:test` runner (describe/it/before/...). eslint-plugin-n + // flags these as "experimental" for the >=18 floor, but they are available on + // every Node version the test matrix runs (18.20+). Test harnesses also pass + // `null` to mirror the real inputs the wasm bindings receive. + files: ['test/**/*.js'], + rules: { + 'n/no-unsupported-features/node-builtins': 'off', + 'unicorn/no-null': 'off', + // Test helpers are commonly scoped inside their describe block. + 'unicorn/consistent-function-scoping': 'off', + }, + }, + { + // Loaded by Rust via `wasm_bindgen(module = ".../_transport.js")`, so + // the snake_case filename must match the Rust module path. + files: ['**/*_transport.js'], + rules: { + 'unicorn/filename-case': 'off', + }, + }, + { + ignores: ['build/', 'target/', 'prebuilds/'], + }, +] diff --git a/load.js b/load.js index 6c7b41e3..69cdca51 100644 --- a/load.js +++ b/load.js @@ -2,13 +2,13 @@ // TODO: Extract this file to an external library. -const { existsSync, readdirSync } = require('fs') -const os = require('os') -const path = require('path') +const { existsSync, readdirSync } = require('node:fs') +const os = require('node:os') +const path = require('node:path') const PLATFORM = os.platform() const ARCH = process.arch -const LIBC = PLATFORM === 'linux' ? existsSync('/etc/alpine-release') ? 'musl' : 'libc' : '' +const LIBC = PLATFORM === 'linux' ? (existsSync('/etc/alpine-release') ? 'musl' : 'glibc') : '' const ABI = process.versions.modules const inWebpack = typeof __webpack_require__ === 'function' @@ -17,24 +17,42 @@ const runtimeRequire = inWebpack ? __non_webpack_require__ : require function maybeLoad (name) { try { return load(name) - } catch (e) { + } catch { // Not found, skip. } } function load (name) { const filename = find(name) + if (filename) { + return runtimeRequire(filename) + } - if (!filename) { - throw new Error(`Could not find a ${name} binary for ${PLATFORM}${LIBC}-${ARCH}.`) + const filenameWASM = findWASM(name) + if (filenameWASM) { + return runtimeRequire(filenameWASM) } - return runtimeRequire(filename) + throw new Error(`Could not find a ${name} binary for ${PLATFORM}${LIBC}-${ARCH} nor a ${name} WASM module.`) +} + +function findWASM (name) { + const root = __dirname + const prebuilds = path.join(root, 'prebuilds') + const folders = readdirSync(prebuilds) + if (folders.includes(name)) { + return path.join(prebuilds, name, `${name.replaceAll('-', '_')}.js`) + } } function find (name, binary = false) { const root = __dirname - const filename = binary ? name : `${name}.node` + + // see https://github.com/rust-lang/cargo/issues/12780 + // Only apply hyphen-to-underscore conversion for .node libraries, not binaries + const transformedName = binary ? name : name.replaceAll('-', '_') + + const filename = binary ? transformedName : `${transformedName}.node` const build = `${root}/build/Release/${filename}` if (existsSync(build)) return build @@ -44,7 +62,7 @@ function find (name, binary = false) { if (!folder) return const prebuildFolder = path.join(root, 'prebuilds', folder) - const file = findFile(prebuildFolder, name, binary) + const file = findFile(prebuildFolder, transformedName, binary) if (!file) return @@ -52,10 +70,15 @@ function find (name, binary = false) { } function findFolder (root) { - const folders = readdirSync(path.join(root, 'prebuilds')) + try { + const prebuilds = path.join(root, 'prebuilds') + const folders = readdirSync(prebuilds) - return folders.find(f => f === `${PLATFORM}${LIBC}-${ARCH}`) - || folders.find(f => f === `${PLATFORM}-${ARCH}`) + return folders.find(f => f === `${PLATFORM}${LIBC}-${ARCH}`) + || folders.find(f => f === `${PLATFORM}-${ARCH}`) + } catch { + // Ignore + } } function findFile (root, name, binary = false) { diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 796ca79f..00000000 --- a/package-lock.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "libdatadog", - "version": "0.1.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "libdatadog", - "version": "0.1.0", - "license": "Apache-2.0" - } - } -} diff --git a/package.json b/package.json index 4eb55fa6..60154703 100644 --- a/package.json +++ b/package.json @@ -4,14 +4,17 @@ "description": "Node.js binding for libdatadog", "main": "index.js", "scripts": { - "build": "npm run -s build-debug", - "build-debug": "mkdir -p target && npm run -s cargo-build > ./target/out.ndjson && npm run -s copy-artifacts", - "build-release": "mkdir -p target && npm run -s cargo-build-release > ./target/out.ndjson && npm run -s copy-artifacts", - "build-all": "mkdir -p target && npm run -s cargo-build -- --workspace > ./target/out.ndjson && npm run -s copy-artifacts", - "cargo-build-release": "npm run -s cargo-build -- --release", + "install-wasm-pack": "curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh", + "build": "yarn -s build-debug && yarn -s build-wasm", + "build-debug": "mkdir -p target && yarn -s cargo-build > ./target/out.ndjson && yarn -s copy-artifacts", + "build-release": "mkdir -p target && yarn -s cargo-build-release > ./target/out.ndjson && yarn -s copy-artifacts", + "build-all": "mkdir -p target && yarn -s cargo-build -- --workspace > ./target/out.ndjson && yarn -s copy-artifacts && yarn -s build-wasm", + "build-wasm": "yarn -s install-wasm-pack && node scripts/build-wasm.js", + "cargo-build-release": "yarn -s cargo-build -- --release", "cargo-build": "cargo build --message-format=json-render-diagnostics", "copy-artifacts": "node ./scripts/copy-artifacts", - "test": "node test" + "lint": "eslint .", + "test": "bash scripts/test.sh" }, "author": "Datadog Inc. ", "license": "Apache-2.0", @@ -22,5 +25,17 @@ "bugs": { "url": "https://github.com/DataDog/libdatadog-nodejs/issues" }, - "homepage": "https://github.com/DataDog/libdatadog-nodejs#readme" + "homepage": "https://github.com/DataDog/libdatadog-nodejs#readme", + "publishConfig": { + "access": "public" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@stylistic/eslint-plugin": "^5.9.0", + "eslint": "^10.6.0", + "eslint-plugin-import-x": "^4.17.1", + "eslint-plugin-n": "^17.24.0", + "eslint-plugin-unicorn": "^63.0.0", + "globals": "^17.7.0" + } } diff --git a/rust-toolchain.toml b/rust-toolchain.toml index e25453f5..4001ea7a 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.80.0" +channel = "1.90.0" profile = "minimal" components = ["clippy", "rustfmt", "rust-src"] diff --git a/scripts/build-wasm.js b/scripts/build-wasm.js new file mode 100644 index 00000000..93b338e6 --- /dev/null +++ b/scripts/build-wasm.js @@ -0,0 +1,58 @@ +// This script builds a WebAssembly module using wasm-pack. It is essentially invoking +// wasm-pack build. All the special handling is for macOS, because Apple's Clang version suffers +// from some issues that prevent it from compiling at least the zstd crate. +// See https://github.com/gyscos/zstd-rs/issues/302 +// This is solved by requiring the homebrew version of LLVM to be installed and available in the +// PATH. Unfortunately, this version then suffers from a different issue that requires wasm-opt to +// be disabled. +// See https://github.com/WebAssembly/wasi-sdk/issues/254 +// See https://github.com/llvm/llvm-project/issues/64909 +// Our releases are built on Linux, and fortunately no special handling is required there. This +// script only allows development to happen on macOS. + +const os = require('node:os') +const childProcess = require('node:child_process') + +const isMacOS = os.platform() === 'darwin' +const noWasmOpt = isMacOS ? '--no-opt' : '' +const libraries = [ + 'library_config', + 'datadog-js-zstd', + 'pipeline', + 'sketches', +] + +const env = { + ...process.env, +} + +if (isMacOS) { + const homebrewDir = env.HOMEBREW_DIR ?? '/opt/homebrew' + const llvmDir = `${homebrewDir}/opt/llvm/` + const llvmBinDir = `${llvmDir}/bin` + + try { + childProcess.execSync(`${llvmBinDir}/llvm-config --version`) + } catch { + console.error(`‼️ LLVM not found in ${llvmDir}.\n‼️ Please install LLVM using Homebrew:\n📝 brew install llvm`) + process.exit(1) // eslint-disable-line unicorn/no-process-exit + } + + if (!env.PATH.includes(llvmBinDir)) { + // Add LLVM to PATH if not already included + env.PATH = `${llvmBinDir}:${env.PATH}` + } + + // Force C/C++ code (e.g. zstd-sys) to use Homebrew's clang for wasm32. Otherwise a global + // CC (e.g. ccache cc) can point at Apple Clang, which does not support wasm32-unknown-unknown. + env.CC_wasm32_unknown_unknown = `${llvmBinDir}/clang` + env.CXX_wasm32_unknown_unknown = `${llvmBinDir}/clang++` +} + +for (const library of libraries) { + childProcess.execSync( + `wasm-pack build ${noWasmOpt} --target nodejs ./crates/${library} --out-dir ../../prebuilds/${library}`, { + env, + }, + ) +} diff --git a/scripts/copy-artifacts.js b/scripts/copy-artifacts.js index ecca6bfd..e7651234 100644 --- a/scripts/copy-artifacts.js +++ b/scripts/copy-artifacts.js @@ -10,7 +10,7 @@ const outPath = path.join(rootPath, 'target', 'out.ndjson') const buildPath = path.join(rootPath, 'build', 'Release') const lineReader = readline.createInterface({ - input: fs.createReadStream(outPath) + input: fs.createReadStream(outPath), }) lineReader.on('line', function (line) { diff --git a/scripts/test.sh b/scripts/test.sh new file mode 100755 index 00000000..181afd39 --- /dev/null +++ b/scripts/test.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +set -e + +run_test() { + local dir + dir=$(dirname "$1") + if [ -f "${dir}/package.json" ]; then + echo "Installing dependencies for $1" + yarn --cwd "$dir" install + fi + echo "Running $1" + # node:test does not force the process to exit when the event loop is kept + # active by async work that has already settled (e.g. the wasm trace + # exporter's runtime machinery after a flush). For the long-lived real + # consumer that is expected; for the test runner we force a clean exit once + # all tests have finished. Only applies to files that use node:test. + # + # `--test-force-exit` exists on Node >= 20.14/22 but Node 18 rejects it as an + # unknown option. The wasm transport unref's its timeout/backoff timers so the + # process still exits cleanly without the flag; probe for support and degrade + # gracefully on Node 18. + if grep -q "node:test" "$1"; then + if node --test-force-exit --eval '' >/dev/null 2>&1; then + node --test-force-exit "$1" + else + node "$1" + fi + else + node "$1" + fi +} + +# Run top-level test files +for f in test/*.js; do + # pipeline.js's wasm exporter keeps the event loop alive after a flush, so it + # needs --test-force-exit. Node 18 lacks that flag AND the wasm HTTP client + # leaves a mock-agent socket open, so node:test cannot exit cleanly there. The + # pipeline wasm is fully exercised by the build-test-wasm job and by the + # Node 20/22/24/26 runs here, so skip it on a Node without --test-force-exit. + if [ "$f" = "test/pipeline.js" ] && ! node --test-force-exit --eval '' >/dev/null 2>&1; then + echo "Skipping $f (no --test-force-exit on this Node; covered by build-test-wasm + newer Node)" + continue + fi + run_test "$f" +done + +# Run index.js in test subdirectories (except wasm) +for d in test/*/; do + case "$d" in + *wasm*) ;; + *) + [ -f "${d}index.js" ] && run_test "${d}index.js" + ;; + esac +done diff --git a/test-wasm.js b/test-wasm.js new file mode 100644 index 00000000..47aadf04 --- /dev/null +++ b/test-wasm.js @@ -0,0 +1,10 @@ +'use strict' + +const fs = require('node:fs') + +const crateTestsDir = `./test/wasm/${process.argv[2]}` +const files = fs.readdirSync(crateTestsDir).filter(file => file.endsWith('.js') || !file.includes('.')) + +for (const file of files) { + require(`${crateTestsDir}/${file}`) +} diff --git a/test.js b/test.js deleted file mode 100644 index c4a5f148..00000000 --- a/test.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict' - -const fs = require('fs') - -fs.readdirSync('test').forEach(file => { - require('./test/' + file) -}) diff --git a/test/crashtracker.js b/test/crashtracker.js deleted file mode 100644 index 66dedacc..00000000 --- a/test/crashtracker.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict' - -const libdatadog = require('..') -const crashtracker = libdatadog.load('crashtracker') - -crashtracker.initWithReceiver({ - additional_files: [], - create_alt_stack: false, - endpoint: { - url: { - scheme: 'http', - authority: 'localhost:8126', - path_and_query: '' - }, - timeout_ms: 3000 - }, - resolve_frames: 'Disabled', - wait_for_receiver: false -}, { - args: [], - env: [], - path_to_receiver_binary: libdatadog.find('crashtracker-receiver', true), - stderr_filename: null, - stdout_filename: null, -}, { - library_name: "dd-trace-js", - library_version: '0.0.0', - family: 'nodejs', - tags: [] -}) diff --git a/test/crashtracker/.gitignore b/test/crashtracker/.gitignore new file mode 100644 index 00000000..378eac25 --- /dev/null +++ b/test/crashtracker/.gitignore @@ -0,0 +1 @@ +build diff --git a/test/crashtracker/app-seg-fault.js b/test/crashtracker/app-seg-fault.js new file mode 100644 index 00000000..8eb94c19 --- /dev/null +++ b/test/crashtracker/app-seg-fault.js @@ -0,0 +1,11 @@ +'use strict' + +const libdatadog = require('../..') + +const { initTestCrashtracker } = require('./test-utils') + +const crashtracker = libdatadog.load('crashtracker') + +initTestCrashtracker() +crashtracker.beginProfilerSerializing() +require('@datadog/segfaultify').segfaultify() diff --git a/test/crashtracker/app-uncaught-exception-non-error.js b/test/crashtracker/app-uncaught-exception-non-error.js new file mode 100644 index 00000000..cc5962d9 --- /dev/null +++ b/test/crashtracker/app-uncaught-exception-non-error.js @@ -0,0 +1,16 @@ +'use strict' + +const libdatadog = require('../..') + +const { initTestCrashtracker } = require('./test-utils') + +const crashtracker = libdatadog.load('crashtracker') + +initTestCrashtracker() +crashtracker.beginProfilerSerializing() + +process.on('uncaughtExceptionMonitor', (e, origin) => { + crashtracker.reportUncaughtExceptionMonitor(e, origin) +}) + +throw 'a plain string error' diff --git a/test/crashtracker/app-uncaught-exception.js b/test/crashtracker/app-uncaught-exception.js new file mode 100644 index 00000000..0fa04558 --- /dev/null +++ b/test/crashtracker/app-uncaught-exception.js @@ -0,0 +1,20 @@ +'use strict' + +const libdatadog = require('../..') + +const { initTestCrashtracker } = require('./test-utils') + +const crashtracker = libdatadog.load('crashtracker') + +initTestCrashtracker() +crashtracker.beginProfilerSerializing() + +process.on('uncaughtExceptionMonitor', (e, origin) => { + crashtracker.reportUncaughtExceptionMonitor(e, origin) +}) + +function myFaultyFunction () { + throw new TypeError('something went wrong') +} + +myFaultyFunction() diff --git a/test/crashtracker/app-unhandled-rejection-non-error.js b/test/crashtracker/app-unhandled-rejection-non-error.js new file mode 100644 index 00000000..bb79f2d1 --- /dev/null +++ b/test/crashtracker/app-unhandled-rejection-non-error.js @@ -0,0 +1,16 @@ +'use strict' + +const libdatadog = require('../..') + +const { initTestCrashtracker } = require('./test-utils') + +const crashtracker = libdatadog.load('crashtracker') + +initTestCrashtracker() +crashtracker.beginProfilerSerializing() + +process.on('uncaughtExceptionMonitor', (e, origin) => { + crashtracker.reportUncaughtExceptionMonitor(e, origin) +}) + +Promise.reject('a plain string rejection') diff --git a/test/crashtracker/app-unhandled-rejection.js b/test/crashtracker/app-unhandled-rejection.js new file mode 100644 index 00000000..ed735719 --- /dev/null +++ b/test/crashtracker/app-unhandled-rejection.js @@ -0,0 +1,20 @@ +'use strict' + +const libdatadog = require('../..') + +const { initTestCrashtracker } = require('./test-utils') + +const crashtracker = libdatadog.load('crashtracker') + +initTestCrashtracker() +crashtracker.beginProfilerSerializing() + +process.on('uncaughtExceptionMonitor', (e, origin) => { + crashtracker.reportUncaughtExceptionMonitor(e, origin) +}) + +async function myAsyncFaultyFunction () { + throw new Error('async went wrong') +} + +myAsyncFaultyFunction() // eslint-disable-line unicorn/prefer-top-level-await diff --git a/test/crashtracker/index.js b/test/crashtracker/index.js new file mode 100644 index 00000000..9c6c2c00 --- /dev/null +++ b/test/crashtracker/index.js @@ -0,0 +1,171 @@ +'use strict' + +const assert = require('node:assert') +const { existsSync, rmSync } = require('node:fs') +const path = require('node:path') +const { execSync, exec } = require('node:child_process') + +const bodyParser = require('body-parser') +const express = require('express') + +const cwd = __dirname +const stdio = ['inherit', 'inherit', 'inherit'] +const uid = process.getuid() +const gid = process.getgid() +const opts = { cwd, stdio, uid, gid } + +const app = express() + +rmSync(path.join(cwd, 'stdout.log'), { force: true }) +rmSync(path.join(cwd, 'stderr.log'), { force: true }) + +const timeout = setTimeout(() => { + const stdoutLog = path.join(cwd, 'stdout.log') + const stderrLog = path.join(cwd, 'stderr.log') + if (existsSync(stdoutLog)) { + execSync(`cat ${stdoutLog}`, opts) + } else { + console.error('stdout.log not found (crashtracker-receiver may not have started)') + } + if (existsSync(stderrLog)) { + execSync(`cat ${stderrLog}`, opts) + } else { + console.error('stderr.log not found (crashtracker-receiver may not have started)') + } + + throw new Error('No crash report received before timing out.') +}, 20_000) + +let currentTest + +app.use(bodyParser.json({ limit: '10mb' })) + +app.post('/telemetry/proxy/api/v2/apmtelemetry', (req, res) => { + res.status(200).send() + + const logPayload = req.body.payload.logs[0] + const tags = logPayload.tags ? logPayload.tags.split(',') : [] + + // Only process crash reports (not pings) + if (!logPayload.is_crash) { + return + } + + if (!currentTest) { + throw new Error('Received unexpected crash report with no active test.') + } + + currentTest(logPayload, tags) +}) + +let PORT + +function runApp (script) { + return new Promise((resolve, reject) => { + let closeTimer + let done = false + + const child = exec(`node ${script}`, { + ...opts, + env: { ...process.env, PORT }, + }) + + child.on('error', (err) => { + cleanup() + reject(new Error(`Child process for "${script}" failed to start`, { cause: err })) + }) + + child.on('close', (code, signal) => { + if (done) return + // Allow a grace period for the crash report HTTP request to arrive + // after the child process exits (e.g. segfault sends report then dies). + closeTimer = setTimeout(() => { + const reason = signal ? `signal ${signal}` : `exit code ${code}` + reject(new Error(`Child process for "${script}" exited with ${reason} before sending a crash report`)) + }, 5000) + }) + + currentTest = (logPayload, tags) => { + cleanup() + currentTest = undefined + resolve({ logPayload, tags }) + } + + function cleanup () { + clearTimeout(closeTimer) + done = true + } + }) +} + +async function testSegfault () { + console.log('Running test: testSegfault') + + const { logPayload, tags } = await runApp('app-seg-fault') + const stackTrace = JSON.parse(logPayload.message).error.stack.frames + const boomFrame = stackTrace.find(frame => frame.function?.toLowerCase().includes('segfaultify')) + + if (existsSync('/etc/alpine-release')) { + console.log('[segfault] Received crash report. Skipping stack trace test since it is currently unsupported for Alpine.') + } else { + assert(boomFrame, '[segfault] Expected stack frame for crashing function not found.') + } + + assert(tags.includes('profiler_serializing:1'), '[segfault] Expected profiler_serializing:1 tag not found.') +} + +async function testUnhandledError (label, script, { expectedType, expectedMessage, expectedFrame }) { + console.log('Running test: testUnhandledError', label) + + const { logPayload } = await runApp(script) + const crashReport = JSON.parse(logPayload.message) + + assert(crashReport.error.message.includes(expectedType), `[${label}] Expected exception type "${expectedType}" not found in message.`) + assert(crashReport.error.message.includes(expectedMessage), `[${label}] Expected exception message "${expectedMessage}" not found.`) + if (expectedFrame) { + const frame = crashReport.error.stack.frames.find(f => f.function && f.function.includes(expectedFrame)) + assert(frame, `[${label}] Expected stack frame for ${expectedFrame} not found.`) + } +} + +async function testUnhandledNonError (label, script, { expectedFallbackType, expectedValue }) { + console.log('Running test: testUnhandledNonError', label) + + const { logPayload } = await runApp(script) + const crashReport = JSON.parse(logPayload.message) + + assert(crashReport.error.message.includes(expectedFallbackType), `[${label}] Expected fallback type "${expectedFallbackType}" not found in message.`) + assert(crashReport.error.message.includes(expectedValue), `[${label}] Expected stringified value "${expectedValue}" not found in message.`) + assert.strictEqual(crashReport.error.stack.frames.length, 0, `[${label}] Expected empty stack trace but got ${crashReport.error.stack.frames.length} frames.`) +} + +const server = app.listen(async () => { + PORT = server.address().port + + await testSegfault() + await testUnhandledError('uncaught-exception', 'app-uncaught-exception', { + expectedType: 'TypeError', + expectedMessage: 'something went wrong', + expectedFrame: 'myFaultyFunction', + }) + await testUnhandledNonError('uncaught-exception-non-error', 'app-uncaught-exception-non-error', { + expectedFallbackType: 'uncaughtException', + expectedValue: 'a plain string error', + }) + await testUnhandledError('unhandled-rejection', 'app-unhandled-rejection', { + expectedType: 'Error', + expectedMessage: 'async went wrong', + expectedFrame: 'myAsyncFaultyFunction', + }) + // Node wraps non-Error rejections in an Error with name 'UnhandledPromiseRejection' + // before passing to uncaughtExceptionMonitor, so this hits the Error path. + // However, this test case rejects with a plain string, so the wrapped Error object has useless + // stack trace + await testUnhandledError('unhandled-rejection-non-error', 'app-unhandled-rejection-non-error', { + expectedType: 'UnhandledPromiseRejection', + expectedMessage: 'a plain string rejection', + }) + + clearTimeout(timeout) + server.close() +}) diff --git a/test/crashtracker/package-lock.json b/test/crashtracker/package-lock.json new file mode 100644 index 00000000..92fc554d --- /dev/null +++ b/test/crashtracker/package-lock.json @@ -0,0 +1,1128 @@ +{ + "name": "crashtracker", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@datadog/segfaultify": "^0.1.1", + "body-parser": "^1.20.3", + "express": "^5.2.1" + } + }, + "node_modules/@datadog/segfaultify": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@datadog/segfaultify/-/segfaultify-0.1.1.tgz", + "integrity": "sha512-wYfDBfS9VlsTOF10HkWgu7abTWsoGTFkaHxjiw8V6I2pXbfb+D3KNdjb4H+0jD7vnChs4Jm7Rgetg0j89jrlYw==", + "license": "Apache-2.0", + "dependencies": { + "node-gyp-build": "^3.9.0" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/body-parser/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/express/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/express/node_modules/qs": { + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/express/node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/express/node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-gyp-build": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-3.9.0.tgz", + "integrity": "sha512-zLcTg6P4AbcHPq465ZMFNXx7XpKKJh+7kkN699NiQWisR2uWYOWNWqRHAmbnmKiL4e9aLSlmy5U7rEMUXV59+A==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/router/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/send/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/send/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + } + } +} diff --git a/test/crashtracker/package.json b/test/crashtracker/package.json new file mode 100644 index 00000000..d5af9436 --- /dev/null +++ b/test/crashtracker/package.json @@ -0,0 +1,9 @@ +{ + "private": true, + "main": "index.js", + "dependencies": { + "@datadog/segfaultify": "^0.1.1", + "body-parser": "^1.20.3", + "express": "^5.2.1" + } +} diff --git a/test/crashtracker/test-utils.js b/test/crashtracker/test-utils.js new file mode 100644 index 00000000..32d135c0 --- /dev/null +++ b/test/crashtracker/test-utils.js @@ -0,0 +1,47 @@ +'use strict' + +const libdatadog = require('../..') +const crashtracker = libdatadog.load('crashtracker') + +function initTestCrashtracker () { + crashtracker.init({ + additional_files: [], + collect_all_threads: true, + create_alt_stack: true, + use_alt_stack: true, + endpoint: { + url: { + scheme: 'http', + authority: `127.0.0.1:${process.env.PORT || 8126}`, + path_and_query: '', + }, + timeout_ms: 3000, + }, + timeout: { secs: 15, nanos: 0 }, + // In process symbol resolution can crash the CT process itself. + resolve_frames: 'EnabledWithSymbolsInReceiver', + wait_for_receiver: true, + demangle_names: true, + signals: [], + }, { + args: [], + env: [], + path_to_receiver_binary: libdatadog.find('crashtracker-receiver', true), + stderr_filename: 'stderr.log', + stdout_filename: 'stdout.log', + }, { + library_name: 'dd-trace-js', + library_version: '6.0.0-pre', + family: 'javascript', + tags: [ + 'language:javascript', + 'runtime:nodejs', + 'runtime-id:8a8fef6433a849b3bc3171198831d102', + 'library_version:6.0.0-pre', + 'is_crash:true', + 'severity:crash', + ], + }) +} + +module.exports = { initTestCrashtracker } diff --git a/test/crashtracker/yarn.lock b/test/crashtracker/yarn.lock new file mode 100644 index 00000000..34ee2463 --- /dev/null +++ b/test/crashtracker/yarn.lock @@ -0,0 +1,579 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@datadog/segfaultify@^0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@datadog/segfaultify/-/segfaultify-0.1.1.tgz#bd12d31ad26c5d15dc1b8c32572ceb37dbb12651" + integrity sha512-wYfDBfS9VlsTOF10HkWgu7abTWsoGTFkaHxjiw8V6I2pXbfb+D3KNdjb4H+0jD7vnChs4Jm7Rgetg0j89jrlYw== + dependencies: + node-gyp-build "^3.9.0" + +accepts@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895" + integrity sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng== + dependencies: + mime-types "^3.0.0" + negotiator "^1.0.0" + +body-parser@^1.20.3: + version "1.20.4" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.4.tgz#f8e20f4d06ca8a50a71ed329c15dccad1cdc547f" + integrity sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA== + dependencies: + bytes "~3.1.2" + content-type "~1.0.5" + debug "2.6.9" + depd "2.0.0" + destroy "~1.2.0" + http-errors "~2.0.1" + iconv-lite "~0.4.24" + on-finished "~2.4.1" + qs "~6.14.0" + raw-body "~2.5.3" + type-is "~1.6.18" + unpipe "~1.0.0" + +body-parser@^2.2.1: + version "2.3.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.3.0.tgz#6d8662f4d8c336028b8ac9aa24251b0ca64ba437" + integrity sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw== + dependencies: + bytes "^3.1.2" + content-type "^2.0.0" + debug "^4.4.3" + http-errors "^2.0.1" + iconv-lite "^0.7.2" + on-finished "^2.4.1" + qs "^6.15.2" + raw-body "^3.0.2" + type-is "^2.1.0" + +bytes@^3.1.2, bytes@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + +call-bound@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" + +content-disposition@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-1.1.0.tgz#f3db789c752d45564cc7e9e1e0b31790d4a38e17" + integrity sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g== + +content-type@^1.0.5, content-type@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +content-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-2.0.0.tgz#2fb3ede69dffa0af78ca7c4ce7589680638b56df" + integrity sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ== + +cookie-signature@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793" + integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg== + +cookie@^0.7.1: + version "0.7.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" + integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^4.4.0, debug@^4.4.3: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + +depd@2.0.0, depd@^2.0.0, depd@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +destroy@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +encodeurl@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + +escape-html@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +etag@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +express@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/express/-/express-5.2.1.tgz#8f21d15b6d327f92b4794ecf8cb08a72f956ac04" + integrity sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw== + dependencies: + accepts "^2.0.0" + body-parser "^2.2.1" + content-disposition "^1.0.0" + content-type "^1.0.5" + cookie "^0.7.1" + cookie-signature "^1.2.1" + debug "^4.4.0" + depd "^2.0.0" + encodeurl "^2.0.0" + escape-html "^1.0.3" + etag "^1.8.1" + finalhandler "^2.1.0" + fresh "^2.0.0" + http-errors "^2.0.0" + merge-descriptors "^2.0.0" + mime-types "^3.0.0" + on-finished "^2.4.1" + once "^1.4.0" + parseurl "^1.3.3" + proxy-addr "^2.0.7" + qs "^6.14.0" + range-parser "^1.2.1" + router "^2.2.0" + send "^1.1.0" + serve-static "^2.2.0" + statuses "^2.0.1" + type-is "^2.0.1" + vary "^1.1.2" + +finalhandler@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-2.1.1.tgz#a2c517a6559852bcdb06d1f8bd7f51b68fad8099" + integrity sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA== + dependencies: + debug "^4.4.0" + encodeurl "^2.0.0" + escape-html "^1.0.3" + on-finished "^2.4.1" + parseurl "^1.3.3" + statuses "^2.0.1" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4" + integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + +has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +http-errors@^2.0.0, http-errors@^2.0.1, http-errors@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.1.tgz#36d2f65bc909c8790018dd36fb4d93da6caae06b" + integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ== + dependencies: + depd "~2.0.0" + inherits "~2.0.4" + setprototypeof "~1.2.0" + statuses "~2.0.2" + toidentifier "~1.0.1" + +iconv-lite@^0.7.2, iconv-lite@~0.7.0: + version "0.7.2" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.7.2.tgz#d0bdeac3f12b4835b7359c2ad89c422a4d1cc72e" + integrity sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +iconv-lite@~0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +inherits@~2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +is-promise@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3" + integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== + +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +media-typer@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561" + integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw== + +merge-descriptors@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz#ea922f660635a2249ee565e0449f951e6b603808" + integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-db@^1.54.0: + version "1.54.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" + integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== + +mime-types@^3.0.0, mime-types@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.2.tgz#39002d4182575d5af036ffa118100f2524b2e2ab" + integrity sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A== + dependencies: + mime-db "^1.54.0" + +mime-types@~2.1.24: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +negotiator@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" + integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== + +node-gyp-build@^3.9.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.9.0.tgz#53a350187dd4d5276750da21605d1cb681d09e25" + integrity sha512-zLcTg6P4AbcHPq465ZMFNXx7XpKKJh+7kkN699NiQWisR2uWYOWNWqRHAmbnmKiL4e9aLSlmy5U7rEMUXV59+A== + +object-inspect@^1.13.3: + version "1.13.4" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== + +on-finished@^2.4.1, on-finished@~2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +parseurl@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-to-regexp@^8.0.0: + version "8.4.2" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.4.2.tgz#795c420c4f7ca45c5b887366f622ee0c9852cccd" + integrity sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA== + +proxy-addr@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +qs@^6.14.0, qs@^6.15.2: + version "6.15.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.15.2.tgz#fd55426d710403ddccc45e0f9eab16db7727ece9" + integrity sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw== + dependencies: + side-channel "^1.1.0" + +qs@~6.14.0: + version "6.14.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.2.tgz#b5634cf9d9ad9898e31fba3504e866e8efb6798c" + integrity sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q== + dependencies: + side-channel "^1.1.0" + +range-parser@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-3.0.2.tgz#3e3ada5ae5568f9095d84376fd3a49b8fb000a51" + integrity sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA== + dependencies: + bytes "~3.1.2" + http-errors "~2.0.1" + iconv-lite "~0.7.0" + unpipe "~1.0.0" + +raw-body@~2.5.3: + version "2.5.3" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.3.tgz#11c6650ee770a7de1b494f197927de0c923822e2" + integrity sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA== + dependencies: + bytes "~3.1.2" + http-errors "~2.0.1" + iconv-lite "~0.4.24" + unpipe "~1.0.0" + +router@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/router/-/router-2.2.0.tgz#019be620b711c87641167cc79b99090f00b146ef" + integrity sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ== + dependencies: + debug "^4.4.0" + depd "^2.0.0" + is-promise "^4.0.0" + parseurl "^1.3.3" + path-to-regexp "^8.0.0" + +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +send@^1.1.0, send@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/send/-/send-1.2.1.tgz#9eab743b874f3550f40a26867bf286ad60d3f3ed" + integrity sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ== + dependencies: + debug "^4.4.3" + encodeurl "^2.0.0" + escape-html "^1.0.3" + etag "^1.8.1" + fresh "^2.0.0" + http-errors "^2.0.1" + mime-types "^3.0.2" + ms "^2.1.3" + on-finished "^2.4.1" + range-parser "^1.2.1" + statuses "^2.0.2" + +serve-static@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-2.2.1.tgz#7f186a4a4e5f5b663ad7a4294ff1bf37cf0e98a9" + integrity sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw== + dependencies: + encodeurl "^2.0.0" + escape-html "^1.0.3" + parseurl "^1.3.3" + send "^1.2.0" + +setprototypeof@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +side-channel-list@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" + integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + +side-channel@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" + integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + side-channel-list "^1.0.0" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" + +statuses@^2.0.1, statuses@^2.0.2, statuses@~2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382" + integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== + +toidentifier@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +type-is@^2.0.1, type-is@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-2.1.0.tgz#71d1a7053293582e16ac9f3ebaf1ab9aa49e5570" + integrity sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA== + dependencies: + content-type "^2.0.0" + media-typer "^1.1.0" + mime-types "^3.0.0" + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +vary@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== diff --git a/test/env-transport.js b/test/env-transport.js new file mode 100644 index 00000000..f45a3779 --- /dev/null +++ b/test/env-transport.js @@ -0,0 +1,31 @@ +'use strict' + +// The transport shim is plain CommonJS, so drive it directly. + +const { describe, it, before, after } = require('node:test') +const assert = require('node:assert') + +const envTransport = require('../crates/capabilities/src/env_transport') + +describe('env_transport', () => { + const NAME = 'LIBDD_CAP_TEST_ENV_TRANSPORT' + let savedValue + + before(() => { + savedValue = process.env[NAME] + }) + after(() => { + if (savedValue === undefined) delete process.env[NAME] + else process.env[NAME] = savedValue + }) + + it('returns undefined for an unset var', () => { + delete process.env[NAME] + assert.strictEqual(envTransport.get(NAME), undefined) + }) + + it('returns the value when the var is set', () => { + process.env[NAME] = 'value1' + assert.strictEqual(envTransport.get(NAME), 'value1') + }) +}) diff --git a/test/filesystem.js b/test/filesystem.js new file mode 100644 index 00000000..ec14bccd --- /dev/null +++ b/test/filesystem.js @@ -0,0 +1,51 @@ +'use strict' + +// The shim is plain CommonJS, so drive it directly. + +const { describe, it, before, after } = require('node:test') +const assert = require('node:assert') +const fs = require('node:fs') +const os = require('node:os') +const path = require('node:path') + +const filesystem = require('../crates/capabilities/src/filesystem') + +describe('filesystem', () => { + let tmp + before(() => { + tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'libdd-file-')) + }) + after(() => { + fs.rmSync(tmp, { recursive: true, force: true }) + }) + + it('writes and reads a file round-trip', async () => { + const p = path.join(tmp, 'hello.bin') + await filesystem.writeFile(p, Buffer.from('hello')) + const got = await filesystem.readFile(p) + assert.strictEqual(Buffer.from(got).toString('utf8'), 'hello') + }) + + it('readFile on a missing path rejects with ENOENT', async () => { + const p = path.join(tmp, 'does-not-exist') + await assert.rejects(filesystem.readFile(p), error => error && error.code === 'ENOENT') + }) + + it('metadata reports size, kind, and a positive inode', async () => { + const p = path.join(tmp, 'meta.bin') + fs.writeFileSync(p, '0123456789') + const m = await filesystem.metadata(p) + assert.strictEqual(m.size, 10n) + assert.strictEqual(m.is_file, true) + assert.strictEqual(m.is_dir, false) + assert.ok(m.inode > 0n, `expected positive inode, got ${m.inode}`) + }) + + it('exists returns true for a present path and false for a missing one', async () => { + const present = path.join(tmp, 'here') + fs.writeFileSync(present, '') + const absent = path.join(tmp, 'gone') + assert.strictEqual(await filesystem.exists(present), true) + assert.strictEqual(await filesystem.exists(absent), false) + }) +}) diff --git a/test/http_transport.js b/test/http_transport.js new file mode 100644 index 00000000..5a97a58a --- /dev/null +++ b/test/http_transport.js @@ -0,0 +1,411 @@ +'use strict' + +// Unit tests for the response-header observer hook in the WASM HTTP transport +// shim. The shim is plain CommonJS (no wasm needed), so we drive `httpRequest` +// directly against a local HTTP server. `httpRequest` reads the request head +// from a Uint8Array view over `wasm_memory.buffer`, so we hand it a fake memory +// object containing a well-formed HTTP/1.1 request head. + +const { describe, it, before, after, beforeEach } = require('node:test') +const assert = require('node:assert') +const http = require('node:http') +const os = require('node:os') +const path = require('node:path') +const fs = require('node:fs') + +const transport = require('../crates/capabilities/src/http_transport') + +// Distinctive, multi-byte body so the pooled-buffer slicing in httpRequest +// (the reason for `new Uint8Array(body)` over `body.buffer`) is exercised: +// a small Buffer.concat result lands at a non-zero offset in Node's shared pool. +const RESPONSE_BODY = '{"rate_by_service":{"service:test,env:":0.5}}' + +function fakeWasmMemory (headBytes) { + const buf = new ArrayBuffer(headBytes.length) + new Uint8Array(buf).set(headBytes) + return { buffer: buf } +} + +describe('http_transport response header observer', () => { + let server + let port + + before(async () => { + server = http.createServer((req, res) => { + req.on('data', () => {}) + req.on('end', () => { + res.setHeader('Datadog-Container-Tags-Hash', 'testhash123') + res.end(RESPONSE_BODY) + }) + }) + await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) + port = server.address().port + }) + + after(() => new Promise(resolve => server.close(resolve))) + + beforeEach(() => { + transport.setResponseHeaderObserver(null) + }) + + function doRequest () { + const head = Buffer.from( + `POST /v0.4/traces HTTP/1.1\r\nHost: 127.0.0.1:${port}\r\n` + + 'Content-Length: 0\r\nConnection: close\r\n\r\n', + 'utf8', + ) + // head occupies [0, head.length); body is empty (offset 0, length 0). + // Empty socketPath -> TCP transport. + return transport.httpRequest('127.0.0.1', port, false, '', 0, head.length, 0, 0, fakeWasmMemory(head)) + } + + it('invokes the observer with the raw response headers', async () => { + let observed + transport.setResponseHeaderObserver((rawHeaders) => { + observed = rawHeaders + }) + + await doRequest() + + assert.ok(Array.isArray(observed), 'observer received the raw headers array') + const idx = observed.findIndex(h => h.toLowerCase() === 'datadog-container-tags-hash') + assert.notStrictEqual(idx, -1, 'container-tags hash header present') + assert.strictEqual(observed[idx + 1], 'testhash123') + }) + + it('still delivers the response when the observer throws, logging the error', async () => { + transport.setResponseHeaderObserver(() => { + throw new Error('boom') + }) + + const originalWrite = process.stderr.write + let logged = '' + process.stderr.write = (chunk) => { + logged += chunk + return true + } + try { + const [status] = await doRequest() + assert.strictEqual(status, 200) + } finally { + process.stderr.write = originalWrite + } + assert.match(logged, /responseHeaderObserver error: boom/) + }) + + it('tolerates an observer throwing a non-Error value', async () => { + // Hardened logging reads only err.message, so a thrown string must not + // crash the transport (it logs `undefined` for the missing message). + transport.setResponseHeaderObserver(() => { + throw 'boom' + }) + + const originalWrite = process.stderr.write + let logged = '' + process.stderr.write = (chunk) => { + logged += chunk + return true + } + try { + const [status] = await doRequest() + assert.strictEqual(status, 200) + } finally { + process.stderr.write = originalWrite + } + assert.match(logged, /responseHeaderObserver error: undefined/) + }) + + it('works when no observer is registered', async () => { + const [status] = await doRequest() + assert.strictEqual(status, 200) + }) + + it('returns the exact response body bytes', async () => { + const [status, , body] = await doRequest() + assert.strictEqual(status, 200) + assert.ok(body instanceof Uint8Array, 'body is a Uint8Array') + // Must be exactly the agent's body — not whole-pool bytes or wrong length. + assert.strictEqual(body.length, Buffer.byteLength(RESPONSE_BODY)) + assert.strictEqual(Buffer.from(body).toString('utf8'), RESPONSE_BODY) + }) +}) + +// libdatadog derives the request host from the agent URI, which keeps the +// brackets for an IPv6 literal (`[::1]`). Node's http.request treats `host` as a +// name to resolve, so `[::1]` fails with ENOTFOUND; the transport must strip the +// brackets. Verified by connecting to an IPv6 loopback server with a bracketed +// host. Skipped where IPv6 loopback isn't available. +describe('http_transport IPv6 host', () => { + let server + let port + let ipv6Available = true + + before(async () => { + server = http.createServer((req, res) => { + req.on('data', () => {}) + req.on('end', () => res.end(RESPONSE_BODY)) + }) + try { + await new Promise((resolve, reject) => { + server.once('error', reject) + server.listen(0, '::1', resolve) + }) + port = server.address().port + } catch { + ipv6Available = false + } + }) + + after(() => new Promise(resolve => (server ? server.close(resolve) : resolve()))) + + it('strips brackets from an IPv6 host so http.request can connect', async function () { + if (!ipv6Available) return this.skip?.() + const head = Buffer.from( + `POST /v0.4/traces HTTP/1.1\r\nHost: [::1]:${port}\r\n` + + 'Content-Length: 0\r\nConnection: close\r\n\r\n', + 'utf8', + ) + // Bracketed IPv6 host, exactly as libdatadog passes it from the agent URI. + const [status] = await transport.httpRequest('[::1]', port, false, '', 0, head.length, 0, 0, fakeWasmMemory(head)) + assert.strictEqual(status, 200) + }) +}) + +// The transport must NOT require instrumentable builtins (node:http/https/fs) at +// module load: it is loaded during the tracer's own init, before user code, so +// an eager require makes dd-trace wrap the builtin in place and leaks +// instrumentation into a user app that imports it afterwards (breaks the +// dd-trace-js init/guardrail expectations). They must be required lazily, inside +// the functions that use them. +describe('http_transport lazy builtin requires', () => { + it('does not require node:http/https/fs at module load', () => { + const Module = require('node:module') + const modPath = require.resolve('../crates/capabilities/src/http_transport') + const orig = Module.prototype.require + const seen = [] + Module.prototype.require = function (id) { + seen.push(id) + return Reflect.apply(orig, this, arguments) + } + try { + delete require.cache[modPath] + require(modPath) + } finally { + Module.prototype.require = orig + delete require.cache[modPath] + } + for (const builtin of ['node:http', 'node:https', 'node:fs', 'http', 'https', 'fs']) { + assert.ok(!seen.includes(builtin), `${builtin} must not be required at module load`) + } + }) +}) + +// Unix-domain-socket transport: a non-empty socketPath must route the request +// over the socket instead of TCP. Skipped on Windows (no AF_UNIX path here). +describe('http_transport unix socket', { skip: process.platform === 'win32' }, () => { + let server + let socketPath + + before(async () => { + socketPath = path.join(os.tmpdir(), `libdd-uds-test-${process.pid}-${Date.now()}.sock`) + try { + fs.unlinkSync(socketPath) + } catch { /* unlink is best-effort */ } + server = http.createServer((req, res) => { + req.on('data', () => {}) + req.on('end', () => { + res.end(RESPONSE_BODY) + }) + }) + await new Promise(resolve => server.listen(socketPath, resolve)) + }) + + after(() => new Promise(resolve => server.close(() => { + try { + fs.unlinkSync(socketPath) + } catch { /* unlink is best-effort */ } + resolve() + }))) + + it('delivers the request over a unix socket and returns the response', async () => { + const head = Buffer.from( + 'POST /v0.4/traces HTTP/1.1\r\nHost: localhost\r\n' + + 'Content-Length: 0\r\nConnection: close\r\n\r\n', + 'utf8', + ) + // host/port empty/0; socketPath drives the connection. + const [status, , body] = await transport.httpRequest( + '', 0, false, socketPath, 0, head.length, 0, 0, fakeWasmMemory(head), + ) + assert.strictEqual(status, 200) + assert.strictEqual(Buffer.from(body).toString('utf8'), RESPONSE_BODY) + }) +}) + +// Entity-header injection: container-id / entity-id / external-env detection +// (Node reads /proc + env; libdatadog's own detection is inert on wasm) and the +// rewrite of the Rust-rendered request head that carries them. +const { detectEntityHeaders, applyEntityHeaders } = transport + +const DOCKER_CGROUP = '12:memory:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860' +const DOCKER_ID = '3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860' + +function writeTmpCgroup (contents) { + const p = path.join(os.tmpdir(), `ldn-cgroup-${process.pid}-${Math.random().toString(36).slice(2)}`) + fs.writeFileSync(p, contents) + return p +} + +function headBytes (lines) { + return Buffer.from(lines.join('\r\n') + '\r\n\r\n', 'latin1') +} + +describe('http_transport entity headers', () => { + describe('detectEntityHeaders', () => { + it('extracts a docker container-id and derives ci- entity-id', () => { + const cgroupPath = writeTmpCgroup(DOCKER_CGROUP) + try { + const h = detectEntityHeaders({ cgroupPath, cgroupMount: '/nonexistent', externalEnv: undefined }) + assert.strictEqual(h['datadog-container-id'], DOCKER_ID) + assert.strictEqual(h['datadog-entity-id'], `ci-${DOCKER_ID}`) + assert.strictEqual('datadog-external-env' in h, false) + } finally { + fs.rmSync(cgroupPath, { force: true }) + } + }) + + it('falls back to in- entity-id when no container-id is present', () => { + const cgroupPath = writeTmpCgroup('0::/') + try { + const h = detectEntityHeaders({ cgroupPath, cgroupMount: os.tmpdir(), externalEnv: undefined }) + assert.strictEqual('datadog-container-id' in h, false) + assert.match(h['datadog-entity-id'], /^in-\d+$/) + } finally { + fs.rmSync(cgroupPath, { force: true }) + } + }) + + it('emits datadog-external-env from the provided value', () => { + const h = detectEntityHeaders({ cgroupPath: '/nonexistent', cgroupMount: '/nonexistent', externalEnv: 'it-false,cn-svc,pu-x' }) + assert.strictEqual(h['datadog-external-env'], 'it-false,cn-svc,pu-x') + }) + + it('emits nothing without cgroup, mount, or external-env', () => { + const h = detectEntityHeaders({ cgroupPath: '/nonexistent', cgroupMount: '/nonexistent', externalEnv: undefined }) + assert.deepStrictEqual(h, {}) + }) + + it('rejects an external-env containing CR/LF (header-injection guard)', () => { + const h = detectEntityHeaders({ + cgroupPath: '/nonexistent', + cgroupMount: '/nonexistent', + externalEnv: 'ok\r\nx-evil: 1', + }) + assert.strictEqual('datadog-external-env' in h, false) + }) + }) + + describe('applyEntityHeaders (head rewrite)', () => { + const entity = { + 'datadog-container-id': DOCKER_ID, + 'datadog-entity-id': `ci-${DOCKER_ID}`, + 'datadog-external-env': 'it-false,cn-svc,pu-x', + } + + it('appends entity headers and preserves the request line + framing headers', () => { + const head = headBytes([ + 'POST /v0.4/traces HTTP/1.1', + 'Host: localhost:8126', + 'Content-Length: 42', + 'datadog-meta-lang: nodejs', + ]) + const out = applyEntityHeaders(head, entity).toString('latin1') + const lines = out.split('\r\n') + assert.strictEqual(lines[0], 'POST /v0.4/traces HTTP/1.1') + assert.ok(lines.includes('Host: localhost:8126')) + assert.ok(lines.includes('Content-Length: 42')) + assert.ok(lines.includes('datadog-meta-lang: nodejs')) + assert.ok(lines.includes(`datadog-container-id: ${DOCKER_ID}`)) + assert.ok(lines.includes(`datadog-entity-id: ci-${DOCKER_ID}`)) + assert.ok(lines.includes('datadog-external-env: it-false,cn-svc,pu-x')) + assert.ok(out.endsWith('\r\n\r\n')) + }) + + it('replaces libdatadog\'s empty datadog-container-id instead of duplicating it', () => { + const head = headBytes([ + 'POST /v0.4/traces HTTP/1.1', + 'Host: localhost', + 'Content-Length: 0', + 'datadog-container-id: ', + ]) + const out = applyEntityHeaders(head, entity).toString('latin1') + const count = out.split('\r\n').filter(l => l.toLowerCase().startsWith('datadog-container-id:')).length + assert.strictEqual(count, 1) + assert.ok(out.includes(`datadog-container-id: ${DOCKER_ID}`)) + }) + + it('returns the head unchanged when no entity headers are detected', () => { + const head = headBytes(['POST / HTTP/1.1', 'Host: x', 'Content-Length: 0']) + const out = applyEntityHeaders(head, {}) + assert.deepStrictEqual(out, Buffer.from(head)) + }) + + it('leaves a malformed head (no terminator) untouched', () => { + const bad = Buffer.from('POST / HTTP/1.1\r\nHost: x', 'latin1') + const out = applyEntityHeaders(bad, entity) + assert.deepStrictEqual(out, Buffer.from(bad)) + }) + }) + + describe('httpRequest end-to-end (real transport)', () => { + let server + let port + let received + const prevExternalEnv = process.env.DD_EXTERNAL_ENV + + before(async () => { + // Set the env then clear the memoized detection so this request re-reads + // it (earlier tests may have already populated the cache). + process.env.DD_EXTERNAL_ENV = 'it-false,cn-e2e,pu-1' + transport._resetEntityHeadersCache() + server = http.createServer((req, res) => { + received = req.headers + res.writeHead(200, { 'content-type': 'application/json' }) + res.end('{}') + }) + await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) + port = server.address().port + }) + + after(() => new Promise(resolve => server.close(() => { + if (prevExternalEnv === undefined) delete process.env.DD_EXTERNAL_ENV + else process.env.DD_EXTERNAL_ENV = prevExternalEnv + transport._resetEntityHeadersCache() + resolve() + }))) + + it('sends the detected entity headers on the wire (via the Rust-rendered head)', async () => { + const body = Buffer.from('[]', 'latin1') + const head = Buffer.from( + `POST /v0.4/traces HTTP/1.1\r\nHost: 127.0.0.1:${port}\r\nContent-Length: ${body.length}\r\n` + + 'datadog-meta-lang: nodejs\r\ndatadog-container-id: \r\n\r\n', + 'latin1', + ) + const mem = new ArrayBuffer(head.length + body.length) + const view = new Uint8Array(mem) + view.set(head, 0) + view.set(body, head.length) + + const [status] = await transport.httpRequest( + '127.0.0.1', port, false, '', 0, head.length, head.length, body.length, { buffer: mem }, + ) + assert.strictEqual(status, 200) + assert.strictEqual(received['datadog-meta-lang'], 'nodejs') + assert.strictEqual(received['datadog-external-env'], 'it-false,cn-e2e,pu-1') + const detected = detectEntityHeaders() + if (detected['datadog-container-id']) { + assert.strictEqual(received['datadog-container-id'], detected['datadog-container-id']) + } + }) + }) +}) diff --git a/test/pipeline.js b/test/pipeline.js index a9de0606..bd4c8977 100644 --- a/test/pipeline.js +++ b/test/pipeline.js @@ -1,10 +1,1290 @@ 'use strict' +const { describe, it, before, beforeEach } = require('node:test') +const assert = require('node:assert') +const crypto = require('node:crypto') + const pipeline = require('..').maybeLoad('pipeline') +// The pipeline binding is wasm-only and is absent in the native +// (action-prebuildify) test matrix, where `maybeLoad` returns undefined. Skip +// the suite there instead of crashing on the destructure below; the pipeline +// wasm is built and these tests run for real in the `build-test-wasm` job. +const skip = pipeline === undefined +const { WasmSpanState } = pipeline ?? {} +const OpCode = pipeline ? pipeline.getOpCodes() : {} +const wasmMemory = pipeline ? pipeline.getWasmMemory() : undefined + +function getRandomBytes (byteCount) { + return new Uint8Array(crypto.randomBytes(byteCount)) +} + +function bytesToBigInt (bytes) { + let val = 0n + for (const byte of bytes) { + val = (val << 8n) | BigInt(byte) + } + return val +} + +// The Span and NativeSpansInterface classes act as a sketch of what should +// be implemented in dd-trace-js. + +// TODO should NativeSpansInterface actually be implemented in this package? + +class Span { + constructor (nativeSpans, traceId, parentId) { + this.nativeSpans = nativeSpans + this.traceId = traceId || [getRandomBytes(8), getRandomBytes(8)] + this.parentId = parentId || new Uint8Array(8) + this.spanId = getRandomBytes(8) + // Spans are addressed by their span_id (u64). Operations carry the raw + // 8-byte id in their header; getters take the numeric id as a BigInt. + this.spanIdBig = bytesToBigInt(this.spanId) + // Trace-level attributes live on a Segment (a local trace chunk). JS owns + // segment_id allocation and shares it across spans in the same trace. + this.segmentId = nativeSpans.allocSegment(this.traceId) + this._startTime = BigInt(Date.now()) * 1_000_000n + + this.nativeSpans.queueOp(OpCode.Create, this.spanId, ['u128', this.traceId], ['u64n', this.segmentId], ['u64', this.parentId]) + this.nativeSpans.queueOp(OpCode.SetStart, this.spanId, ['i64', this._startTime]) + } + + setTag (key, value) { + if (typeof value === 'number') { + this.nativeSpans.queueOp(OpCode.SetMetricAttr, this.spanId, key, ['f64', value]) + } else { + this.nativeSpans.queueOp(OpCode.SetMetaAttr, this.spanId, key, value) + } + return this + } + + getTag (key) { + return this.nativeSpans.state.getMetaAttr(this.spanIdBig, key) + ?? this.nativeSpans.state.getMetricAttr(this.spanIdBig, key) + } + + setTraceTag (key, value) { + const opcode = OpCode[typeof value === 'number' ? 'SetTraceMetricsAttr' : 'SetTraceMetaAttr'] + if (typeof value === 'number') { + value = ['f64', value] + } + this.nativeSpans.queueOp(opcode, this.spanId, key, value) + return this + } + + getTraceTag (key) { + return this.nativeSpans.state.getTraceMetaAttr(this.segmentId, key) + ?? this.nativeSpans.state.getTraceMetricAttr(this.segmentId, key) + } + + setTraceOrigin (origin) { + this.nativeSpans.queueOp(OpCode.SetTraceOrigin, this.spanId, origin) + return this + } + + getTraceOrigin () { + return this.nativeSpans.state.getTraceOrigin(this.segmentId) + } + + setMetaStruct (key, bytes) { + this.nativeSpans.state.setMetaStruct(this.spanIdBig, key, bytes) + return this + } + + getMetaStruct (key) { + return this.nativeSpans.state.getMetaStruct(this.spanIdBig, key) + } + + addSpanEvent (name, timeUnixNano, attributes = {}) { + this.nativeSpans.state.addSpanEvent( + this.spanIdBig, + name, + BigInt(timeUnixNano), + encodeSpanEventAttrs(attributes), + ) + return this + } -if (pipeline) { - pipeline.init_trace_exporter("127.0.0.1", 8126, 10000, "1.0", "nodejs", "18.0", "v8") + getSpanEvents () { + return JSON.parse(this.nativeSpans.state.getSpanEventsJson(this.spanIdBig)) + } - let ret = pipeline.send_traces(Buffer.alloc(1), 1) - console.log(ret) + finish () { + this.duration = BigInt(Date.now()) * 1_000_000n - this._startTime + return this + } } + +const spanAccessors = { + // [getterName, opCode, valueType (null for string)] + name: ['getName', 'SetName', null], + service: ['getServiceName', 'SetServiceName', null], + resource: ['getResourceName', 'SetResourceName', null], + type: ['getType', 'SetType', null], + error: ['getError', 'SetError', 'i32'], + start: ['getStart', null, null], + duration: ['getDuration', 'SetDuration', 'i64'], +} + +for (const [prop, [getter, setter, valueType]] of Object.entries(spanAccessors)) { + Object.defineProperty(Span.prototype, prop, { + get () { + return this.nativeSpans.state[getter](this.spanIdBig) + }, + set (val) { + val = valueType ? [valueType, val] : val + this.nativeSpans.queueOp(OpCode[setter], this.spanId, val) + }, + }) +} + +const CHANGE_QUEUE_SIZE = 64 * 1024 +const STRING_TABLE_INPUT_SIZE = 10 * 1024 + +class NativeSpansInterface { + constructor (options = {}) { + this.flushBuffer = Buffer.alloc(10 * 1024) + + this.cqbIndex = 8 // Start at 8 since first u64 is count + this.cqbCount = 0 + this.stibCount = 0 + this.segmentCount = 0 // Monotonic segment_id allocator + this.segmentByTrace = new Map() // trace key -> segment_id (BigInt) + this.stringMap = new Map() + + this.state = new WasmSpanState( + options.agentUrl || process.env.AGENT_URL || 'http://127.0.0.1:8126', + options.tracerVersion || '1.0.0', + options.lang || 'nodejs', + options.langVersion || process.version, + options.langInterpreter || 'v8', + CHANGE_QUEUE_SIZE, + STRING_TABLE_INPUT_SIZE, + options.pid ?? process.pid, + options.tracerService || 'test-service', + options.statsEnabled ?? false, + options.hostname || 'test-host', + options.env || 'test-env', + options.appVersion || '1.0.0', + options.runtimeId || '00000000-0000-0000-0000-000000000000', + options.clientComputedStats ?? false, + ) + + // Get pointers into WASM memory for direct buffer access + this._wasmMemory = wasmMemory + this._cqbPtr = this.state.change_queue_ptr() + this._refreshViews() + } + + _refreshViews () { + this._cqbView = new DataView(this._wasmMemory.buffer, this._cqbPtr) + this._cqbBytes = new Uint8Array(this._wasmMemory.buffer, this._cqbPtr) + } + + // Any Rust call can trigger a WASM memory.grow(), which detaches the + // JS-side ArrayBuffer. Callers must invoke this before every read/write to + // the shared buffers when a Rust call may have happened since the last check. + _ensureViews () { + if (this._wasmMemory.buffer !== this._cqbView.buffer) { + this._refreshViews() + } + } + + resetChangeQueue () { + this.cqbIndex = 8 + this.cqbCount = 0 + this._ensureViews() + this._cqbView.setUint32(0, 0, true) + this._cqbView.setUint32(4, 0, true) + } + + flushChangeQueue () { + this.state.flushChangeQueue() + this.resetChangeQueue() + } + + getStringId (str) { + let id = this.stringMap.get(str) + if (typeof id === 'number') return id + + id = this.stibCount++ + this.stringMap.set(str, id) + this.state.stringTableInsertOne(id, str) + return id + } + + // Write 8 big-endian bytes as a little-endian u64 into the change buffer + _writeBytesLE (bytes, offset) { + const buf = this._cqbBytes + for (let i = 0; i < 8; i++) { + buf[offset + i] = bytes[7 - i] + } + } + + // Allocate (or reuse) a segment_id for a given trace. Spans sharing a trace + // share a segment so trace-level attributes are visible across them. + allocSegment (traceId) { + const key = traceId.map(b => Buffer.from(b).toString('hex')).join('') + let id = this.segmentByTrace.get(key) + if (id === undefined) { + id = BigInt(this.segmentCount++) + this.segmentByTrace.set(key, id) + } + return id + } + + queueOp (op, spanId, ...args) { + this._ensureViews() + + // Check if Rust flushed the queue (wrote 0 to count position) + if (this._cqbView.getUint32(0, true) === 0 && this.cqbCount > 0) { + this.cqbIndex = 8 + this.cqbCount = 0 + } + + // Op header: opcode (u16 LE) + span_id (u64 LE) = 10 bytes. Rust reads the + // opcode as a u16, then the span_id as a u64. + this._cqbView.setUint16(this.cqbIndex, op, true) + this.cqbIndex += 2 + this._writeBytesLE(spanId, this.cqbIndex) + this.cqbIndex += 8 + + for (const arg of args) { + if (typeof arg === 'string') { + // getStringId may call into Rust (stringTableInsertOne), which can + // grow WASM memory and detach our views. Re-check after the call. + const stringId = this.getStringId(arg) + this._ensureViews() + this._cqbView.setUint32(this.cqbIndex, stringId, true) + this.cqbIndex += 4 + } else { + const [typ, num] = arg + switch (typ) { + case 'u64': { + this._writeBytesLE(num, this.cqbIndex) + this.cqbIndex += 8 + break + } + case 'u64n': { + this._cqbView.setBigUint64(this.cqbIndex, BigInt(num), true) + this.cqbIndex += 8 + break + } + case 'u32n': { // raw, pre-resolved string-table id + this._cqbView.setUint32(this.cqbIndex, num, true) + this.cqbIndex += 4 + break + } + case 'u128': { + this._writeBytesLE(num[0], this.cqbIndex) + this.cqbIndex += 8 + this._writeBytesLE(num[1], this.cqbIndex) + this.cqbIndex += 8 + break + } + case 'i64': { + this._cqbView.setBigInt64(this.cqbIndex, num, true) + this.cqbIndex += 8 + break + } + case 'i32': { + this._cqbView.setInt32(this.cqbIndex, num, true) + this.cqbIndex += 4 + break + } + case 'f64': { + this._cqbView.setFloat64(this.cqbIndex, num, true) + this.cqbIndex += 8 + break + } + default: { + throw new Error('unsupported number type: ' + typ) + } + } + } + } + + this.cqbCount++ + this._cqbView.setBigUint64(0, BigInt(this.cqbCount), true) + } + + createSpan (traceId, parentId) { + return new Span(this, traceId, parentId) + } + + async flushSpans (...spans) { + this.flushBuffer.fill(0) // TODO is this necessary, since we're sending the length? + let index = 0 + for (const span of spans) { + // The chunk buffer carries u64 span IDs (8 bytes LE each). + const spanId = span.spanId ?? span + for (let i = 0; i < 8; i++) { + this.flushBuffer[index + i] = spanId[7 - i] + } + index += 8 + } + const hasSpans = this.state.prepareChunk(spans.length, true, this.flushBuffer) + if (!hasSpans) return false + return this.state.sendPreparedChunk() + } +} + +// Build the flat span-event attribute buffer consumed by the Rust decoder +// (`decode_span_event_attributes` in crates/pipeline/src/lib.rs). This mirrors +// what dd-trace-js's `addSpanEvent` wrapper produces. Tags: String=0, +// Boolean=1, Integer=2, Double=3, Array=4 (matching libdatadog's +// AttributeArrayValue discriminants). +function encodeSpanEventAttrs (attributes) { + const enc = new TextEncoder() + const chunks = [] + const u32 = (n) => { + const b = Buffer.alloc(4) + b.writeUInt32LE(n >>> 0, 0) + return b + } + const i64 = (n) => { + const b = Buffer.alloc(8) + b.writeBigInt64LE(BigInt(n), 0) + return b + } + const f64 = (n) => { + const b = Buffer.alloc(8) + b.writeDoubleLE(n, 0) + return b + } + const str = (s) => { + const sb = Buffer.from(enc.encode(s)) + return Buffer.concat([u32(sb.length), sb]) + } + // Returns `[tag][value]` — used both for single values and array items. + const scalar = (v) => { + if (typeof v === 'string') return Buffer.concat([Buffer.from([0]), str(v)]) + if (typeof v === 'boolean') return Buffer.concat([Buffer.from([1]), Buffer.from([v ? 1 : 0])]) + if (typeof v === 'number') { + return Number.isInteger(v) + ? Buffer.concat([Buffer.from([2]), i64(v)]) + : Buffer.concat([Buffer.from([3]), f64(v)]) + } + throw new TypeError(`unsupported span-event attribute value: ${typeof v}`) + } + for (const [key, value] of Object.entries(attributes)) { + chunks.push(str(key)) + if (Array.isArray(value)) { + chunks.push(Buffer.from([4]), u32(value.length)) + for (const item of value) chunks.push(scalar(item)) + } else { + chunks.push(scalar(value)) + } + } + return new Uint8Array(Buffer.concat(chunks)) +} + +// Read just the length of the outer msgpack array (the v0.4 trace payload is an +// array of trace chunks). Enough to assert how many separate traces were sent. +function msgpackOuterArrayLen (buf) { + const b = buf[0] + if (b >= 0x90 && b <= 0x9F) return b & 0x0F // fixarray + if (b === 0xDC) return buf.readUInt16BE(1) // array16 + if (b === 0xDD) return buf.readUInt32BE(1) // array32 + throw new Error('payload is not a msgpack array: 0x' + b.toString(16)) +} + +describe('pipeline', { skip }, () => { + let nativeSpans + + before(() => { + nativeSpans = new NativeSpansInterface() + }) + + beforeEach(() => { + nativeSpans.resetChangeQueue() + }) + + describe('module exports', () => { + it('should export WasmSpanState', () => { + assert(WasmSpanState) + }) + + it('should export OpCode', () => { + assert(OpCode) + }) + + it('should export all OpCodes', () => { + const expectedOpCodes = [ + 'Create', 'SetMetaAttr', 'SetMetricAttr', 'SetServiceName', + 'SetResourceName', 'SetError', 'SetStart', 'SetDuration', + 'SetType', 'SetName', 'SetTraceMetaAttr', 'SetTraceMetricsAttr', + 'SetTraceOrigin', + ] + for (const opCode of expectedOpCodes) { + assert.strictEqual(typeof OpCode[opCode], 'number') + } + }) + }) + + describe('WasmSpanState', () => { + it('should create an instance', () => { + assert(nativeSpans.state instanceof WasmSpanState) + }) + }) + + describe('span creation', () => { + it('should create a span with basic attributes', () => { + const span = nativeSpans.createSpan() + span.name = 'test-span' + span.service = 'test-service' + span.resource = '/api/test' + span.type = 'web' + span.error = 0 + + assert.strictEqual(span.name, 'test-span') + assert.strictEqual(span.service, 'test-service') + assert.strictEqual(span.resource, '/api/test') + assert.strictEqual(span.type, 'web') + assert.strictEqual(span.error, 0) + }) + + it('should create a child span with parent', () => { + const parent = nativeSpans.createSpan() + parent.name = 'parent-span' + + const child = nativeSpans.createSpan(parent.traceId, parent.spanId) + child.name = 'child-span' + + assert.strictEqual(child.name, 'child-span') + }) + }) + + describe('span attributes', () => { + it('should set and get string tags', () => { + const span = nativeSpans.createSpan() + span.setTag('http.method', 'GET') + span.setTag('http.url', 'http://example.com/api') + + assert.strictEqual(span.getTag('http.method'), 'GET') + assert.strictEqual(span.getTag('http.url'), 'http://example.com/api') + }) + + it('should set and get numeric tags', () => { + const span = nativeSpans.createSpan() + span.setTag('http.status_code', 200) + span.setTag('custom.metric', 3.141_59) + + assert.strictEqual(span.getTag('http.status_code'), 200) + assert.strictEqual(span.getTag('custom.metric'), 3.141_59) + }) + + it('should set and get error state', () => { + const span = nativeSpans.createSpan() + span.error = 0 + assert.strictEqual(span.error, 0) + + span.error = 1 + assert.strictEqual(span.error, 1) + }) + }) + + describe('meta_struct', () => { + it('round-trips raw bytes by key', () => { + const span = nativeSpans.createSpan() + const value = new Uint8Array([0x82, 0xA1, 0x61, 0x01, 0xA1, 0x62, 0x02]) + span.setMetaStruct('appsec', value) + + assert.deepStrictEqual(span.getMetaStruct('appsec'), value) + }) + + it('returns null for an unset meta_struct key', () => { + const span = nativeSpans.createSpan() + assert.strictEqual(span.getMetaStruct('missing'), null) + }) + + it('overwrites an existing key on repeated set', () => { + const span = nativeSpans.createSpan() + span.setMetaStruct('k', new Uint8Array([1, 2, 3])) + span.setMetaStruct('k', new Uint8Array([9])) + + assert.deepStrictEqual(span.getMetaStruct('k'), new Uint8Array([9])) + }) + }) + + describe('span_events', () => { + it('appends an event with no attributes', () => { + const span = nativeSpans.createSpan() + span.addSpanEvent('exception', 1_727_211_691_770_716_000n) + + const events = span.getSpanEvents() + assert.strictEqual(events.length, 1) + assert.strictEqual(events[0].name, 'exception') + assert.strictEqual(events[0].time_unix_nano, 1_727_211_691_770_716_000) + // Empty attributes are skipped by libdatadog's serializer. + assert.strictEqual(events[0].attributes, undefined) + }) + + it('round-trips scalar attributes of every type with correct type tags', () => { + const span = nativeSpans.createSpan() + span.addSpanEvent('evt', 1000n, { + s: 'hello', + b: true, + i: 42, + d: 3.5, + }) + + const [event] = span.getSpanEvents() + assert.strictEqual(event.name, 'evt') + assert.strictEqual(event.time_unix_nano, 1000) + // type tags: String=0, Boolean=1, Integer=2, Double=3 + assert.deepStrictEqual(event.attributes.s, { type: 0, string_value: 'hello' }) + assert.deepStrictEqual(event.attributes.b, { type: 1, bool_value: true }) + assert.deepStrictEqual(event.attributes.i, { type: 2, int_value: 42 }) + assert.deepStrictEqual(event.attributes.d, { type: 3, double_value: 3.5 }) + }) + + it('round-trips an array attribute (type 4) with typed items', () => { + const span = nativeSpans.createSpan() + span.addSpanEvent('evt', 1n, { tags: ['a', 'b'], nums: [1, 2, 3] }) + + const [event] = span.getSpanEvents() + assert.deepStrictEqual(event.attributes.tags, { + type: 4, + array_value: { values: [{ type: 0, string_value: 'a' }, { type: 0, string_value: 'b' }] }, + }) + assert.deepStrictEqual(event.attributes.nums, { + type: 4, + array_value: { values: [{ type: 2, int_value: 1 }, { type: 2, int_value: 2 }, { type: 2, int_value: 3 }] }, + }) + }) + + it('appends multiple events in order', () => { + const span = nativeSpans.createSpan() + span.addSpanEvent('first', 1n) + span.addSpanEvent('second', 2n, { k: 'v' }) + + const events = span.getSpanEvents() + assert.strictEqual(events.length, 2) + assert.strictEqual(events[0].name, 'first') + assert.strictEqual(events[1].name, 'second') + assert.deepStrictEqual(events[1].attributes.k, { type: 0, string_value: 'v' }) + }) + + it('returns an empty array for a span with no events', () => { + const span = nativeSpans.createSpan() + assert.deepStrictEqual(span.getSpanEvents(), []) + }) + + it('rejects a truncated attribute buffer instead of panicking', () => { + const span = nativeSpans.createSpan() + // key_len=5 but no key bytes follow → bounded read must error. + const bad = new Uint8Array([5, 0, 0, 0]) + assert.throws( + () => span.nativeSpans.state.addSpanEvent(span.spanIdBig, 'evt', 1n, bad), + /truncated span-event attribute buffer/, + ) + }) + + it('rejects an overflowing key_len without trapping (wasm32 usize)', () => { + const span = nativeSpans.createSpan() + // key_len = 0xFFFFFFFF: on wasm32 `idx + key_len` would wrap and slip + // past the bound, trapping on the slice. The remaining-byte form must + // reject it as a truncated buffer instead. + const bad = new Uint8Array([0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00]) + assert.throws( + () => span.nativeSpans.state.addSpanEvent(span.spanIdBig, 'evt', 1n, bad), + /truncated span-event attribute buffer/, + ) + }) + }) + + describe('span timing', () => { + it('should set and get start time', () => { + const span = nativeSpans.createSpan() + assert(span.start > 0n) // populated from the constructor's SetStart (BigInt ns) + + // Verify an exact round-trip. getStart returns an f64, so real ns + // timestamps (> 2^53) can't be checked to the nanosecond; use a small, + // exactly-representable value so an off-by-one would actually be caught. + nativeSpans.queueOp(OpCode.SetStart, span.spanId, ['i64', 12_345n]) + assert.strictEqual(span.start, 12_345n) + }) + + it('should set and get duration', () => { + const duration = 1_000_000n + const span = nativeSpans.createSpan() + span.duration = duration + // getDuration returns i64 nanoseconds as a BigInt (no f64 truncation). + assert.strictEqual(span.duration, duration) + }) + }) + + describe('trace-level attributes', () => { + it('should set and get trace string tags', () => { + const span = nativeSpans.createSpan() + span.setTraceTag('_dd.p.dm', '-0') + assert.strictEqual(span.getTraceTag('_dd.p.dm'), '-0') + }) + + it('should set and get trace numeric tags', () => { + const span = nativeSpans.createSpan() + span.setTraceTag('_sampling_priority_v1', 1) + assert.strictEqual(span.getTraceTag('_sampling_priority_v1'), 1) + }) + + it('should set and get trace origin', () => { + const span = nativeSpans.createSpan() + span.setTraceOrigin('synthetics') + assert.strictEqual(span.getTraceOrigin(), 'synthetics') + }) + + it('should share trace attributes across spans in same trace', () => { + const parent = nativeSpans.createSpan() + parent.setTraceTag('shared_key', 'shared_value') + parent.setTraceTag('shared_metric', 42) + parent.setTraceOrigin('lambda') + + const child = nativeSpans.createSpan(parent.traceId, parent.spanId) + + assert.strictEqual(child.getTraceTag('shared_key'), 'shared_value') + assert.strictEqual(child.getTraceTag('shared_metric'), 42) + assert.strictEqual(child.getTraceOrigin(), 'lambda') + }) + + it('should isolate trace attributes across different traces', () => { + const a = nativeSpans.createSpan() + a.setTraceTag('iso_key', 'a_value') + a.setTraceOrigin('origin-a') + + // A span in a DIFFERENT trace must not see trace a's segment data. + const b = nativeSpans.createSpan() + assert.notStrictEqual(a.segmentId, b.segmentId) + assert.strictEqual(b.getTraceTag('iso_key'), null) + assert.strictEqual(b.getTraceOrigin(), null) + }) + }) + + describe('absent values and error handling', () => { + it('returns null for tags that were never set', () => { + const span = nativeSpans.createSpan() + assert.strictEqual(nativeSpans.state.getMetaAttr(span.spanIdBig, 'never-set'), null) + assert.strictEqual(nativeSpans.state.getMetricAttr(span.spanIdBig, 'never-set'), null) + assert.strictEqual(span.getTag('never-set'), null) + }) + + it('throws when reading an unknown span id', () => { + // Convention: span-level getters throw on an unknown span_id, while + // trace-level getters return null for an unknown segment. All span + // getters share the get_span error path, so assert each one throws. + const bogus = 0xDE_AD_BE_EFn + for (const getter of [ + 'getName', 'getServiceName', 'getResourceName', + 'getType', 'getError', 'getStart', 'getDuration', + ]) { + assert.throws(() => nativeSpans.state[getter](bogus), `${getter} should throw`) + } + }) + }) + + describe('default meta', () => { + it('applies default meta to new spans and validates inputs', () => { + // Fresh interface so the default doesn't leak into the shared instance. + const ns = new NativeSpansInterface() + ns.state.setDefaultMeta(['dk', 'dv']) + const span = ns.createSpan() + assert.strictEqual(span.getTag('dk'), 'dv') + + // Non-string key or value must throw. + assert.throws(() => ns.state.setDefaultMeta(['k', 123])) + assert.throws(() => ns.state.setDefaultMeta([123, 'v'])) + // A trailing unpaired key is ignored, not an error. + assert.doesNotThrow(() => ns.state.setDefaultMeta(['lonely'])) + }) + }) + + describe('sampling', () => { + it('should not expose sample() in WASM module', () => { + // Sampling is handled JS-side; the WASM module does not expose a sample() method + assert.strictEqual(typeof nativeSpans.state.sample, 'undefined') + }) + }) + + describe('string table', () => { + it('should evict strings from the table', () => { + const testKey = 'eviction-test-key-' + Math.random() + const testVal = 'eviction-test-value' + const span = nativeSpans.createSpan() + span.setTag(testKey, testVal) + + assert.strictEqual(span.getTag(testKey), testVal) + + const keyId = nativeSpans.stringMap.get(testKey) + assert.strictEqual(typeof keyId, 'number', 'key was interned in the string table') + nativeSpans.state.stringTableEvict(keyId) + + // Evicting the key from the string table must not affect spans that have + // already resolved it: the tag was materialized onto the span at flush + // time, so the span keeps its own copy of the value. + assert.strictEqual(span.getTag(testKey), testVal) + }) + + it('bulk-inserts strings via stringTableInsertMany', () => { + // Wire format per entry: [key:u32 LE][cstr bytes][NUL]. Two entries + // exercise the NUL-terminator advance (a missing +1 would misparse the + // second entry). + const ptr = nativeSpans.state.string_table_input_ptr() + const view = new DataView(wasmMemory.buffer, ptr) + const bytes = new Uint8Array(wasmMemory.buffer, ptr) + const entries = [[60_001, 'bulk-key'], [60_002, 'bulk-val']] + let off = 0 + for (const [key, str] of entries) { + view.setUint32(off, key, true) + off += 4 + for (let i = 0; i < str.length; i++) bytes[off++] = str.codePointAt(i) + bytes[off++] = 0 + } + nativeSpans.state.stringTableInsertMany(entries.length) + + // Reference the pre-inserted ids directly (raw u32, not via getStringId) + // in a SetMetaAttr op; at flush they must resolve to the bulk strings. + const span = nativeSpans.createSpan() + nativeSpans.queueOp(OpCode.SetMetaAttr, span.spanId, ['u32n', 60_001], ['u32n', 60_002]) + assert.strictEqual(span.getTag('bulk-key'), 'bulk-val') + }) + + it('rejects a malformed (non-terminated) stringTableInsertMany entry', () => { + const ptr = nativeSpans.state.string_table_input_ptr() + const len = nativeSpans.state.string_table_input_len() + const view = new DataView(wasmMemory.buffer, ptr) + const bytes = new Uint8Array(wasmMemory.buffer, ptr, len) + bytes.fill(0xFF) // no NUL terminator anywhere in the buffer + view.setUint32(0, 70_001, true) + // from_bytes_until_nul finds no terminator -> error surfaced as a throw, + // not an out-of-bounds read. + assert.throws(() => nativeSpans.state.stringTableInsertMany(1)) + }) + + it('rejects a stringTableInsertMany count larger than the buffer holds', () => { + const ptr = nativeSpans.state.string_table_input_ptr() + const len = nativeSpans.state.string_table_input_len() + const view = new DataView(wasmMemory.buffer, ptr) + const bytes = new Uint8Array(wasmMemory.buffer, ptr, len) + bytes.fill(0) + // One valid entry consuming almost the whole buffer, so claiming a count + // of 2 makes the second u32 key read run past the end -> bounded error, + // not an out-of-bounds panic. + view.setUint32(0, 71_000, true) + for (let i = 4; i < len - 1; i++) bytes[i] = 0x61 // 'a' + bytes[len - 1] = 0 // NUL terminator at the very end + assert.throws(() => nativeSpans.state.stringTableInsertMany(2), /exceeds the entries/) + }) + }) + + describe('input validation', () => { + it('throws when prepareChunk len exceeds the chunk size', () => { + // 100 span ids would need 800 bytes; the chunk only has 8. + assert.throws(() => nativeSpans.state.prepareChunk(100, true, Buffer.alloc(8))) + }) + + it('flushSpans with no spans is a no-op returning false', async () => { + assert.strictEqual(await nativeSpans.flushSpans(), false) + }) + }) + + describe('flush to agent', () => { + it('should flush spans to a (mock) agent', async () => { + // Stand up a throwaway HTTP server acting as the agent so the flush path + // (prepareChunk -> build exporter -> serialize -> send) is exercised + // end-to-end in CI, instead of being skipped when no agent is present. + const http = require('node:http') + const payloads = [] + const server = http.createServer((req, res) => { + const chunks = [] + req.on('data', c => chunks.push(c)) + req.on('end', () => { + payloads.push(Buffer.concat(chunks)) + res.writeHead(200, { 'content-type': 'application/json' }) + res.end('{}') + }) + }) + await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) + const { port } = server.address() + + const ns = new NativeSpansInterface({ agentUrl: `http://127.0.0.1:${port}` }) + const span = ns.createSpan() + span.name = 'flush-test-span' + span.service = 'test-service' + span.resource = 'test-resource' + span.type = 'web' + span.duration = 1_000_000n + + try { + const result = await ns.flushSpans(span) + assert(result, 'exporter returned an agent response') + assert(payloads.length > 0, 'agent received a trace payload') + assert(payloads[0].length > 0, 'trace payload is non-empty') + // process_span stamps the `language` meta at flush. For the Node tracer + // it must be "javascript" (matching the JS pipeline), NOT the `nodejs` + // header/tracer-lang. The v0.4 span payload has no other nodejs/javascript + // string, so a byte check unambiguously distinguishes the two. + assert(payloads[0].includes(Buffer.from('javascript')), 'span meta carries language=javascript') + assert(!payloads[0].includes(Buffer.from('nodejs')), 'span meta must not carry language=nodejs') + } finally { + server.closeAllConnections?.() + server.close() + } + }) + + it('accumulates one chunk per trace into a single multi-trace request', async () => { + // prepareChunk stages one chunk per call; a single sendPreparedChunk sends + // them all as one request. Before accumulation, a second prepareChunk + // overwrote the first, so only one trace shipped per request. Here two + // spans in two DISTINCT traces must arrive as two separate trace chunks. + const http = require('node:http') + const payloads = [] + const server = http.createServer((req, res) => { + const chunks = [] + req.on('data', c => chunks.push(c)) + req.on('end', () => { + payloads.push({ url: req.url, body: Buffer.concat(chunks) }) + res.writeHead(200, { 'content-type': 'application/json' }) + res.end('{}') + }) + }) + await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) + const { port } = server.address() + const ns = new NativeSpansInterface({ agentUrl: `http://127.0.0.1:${port}` }) + + const mk = (name) => { + const s = ns.createSpan() // distinct random trace id per span + s.name = name + s.service = 'svc' + s.resource = 'res' + s.type = 'web' + s.duration = 1_000_000n + return s + } + const a = mk('trace-a') + const b = mk('trace-b') + + // Prepare one chunk per trace (span id written LE), then send once. + const prepareOne = (span) => { + const buf = Buffer.alloc(8) + for (let i = 0; i < 8; i++) buf[i] = span.spanId[7 - i] + return ns.state.prepareChunk(1, true, buf) + } + + try { + assert.strictEqual(prepareOne(a), true, 'first chunk staged') + assert.strictEqual(prepareOne(b), true, 'second chunk staged') + const result = await ns.state.sendPreparedChunk() + assert(result, 'agent responded') + const post = payloads.find(p => p.url.includes('/v0.4/traces')) + assert.ok(post, 'received a v0.4 POST') + assert.strictEqual( + msgpackOuterArrayLen(post.body), 2, + 'payload carries two separate trace chunks, not one lumped chunk', + ) + } finally { + server.closeAllConnections?.() + server.close() + } + }) + }) + + describe('v0.5 output format', () => { + // Spin up a mock agent that records the request path, so we can assert the + // exporter targets /v0.4/traces by default and /v0.5/traces after + // setUseV05(true). (v0.5 itself drops meta_struct/span_events by design; + // here we only verify endpoint routing, which is the observable behavior.) + async function flushAndCapturePath (useV05) { + const http = require('node:http') + const seen = [] + const server = http.createServer((req, res) => { + req.on('data', () => {}) + req.on('end', () => { + seen.push({ method: req.method, url: req.url }) + res.writeHead(200, { 'content-type': 'application/json' }) + res.end('{}') + }) + }) + await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) + const { port } = server.address() + const ns = new NativeSpansInterface({ agentUrl: `http://127.0.0.1:${port}` }) + if (useV05) ns.state.setUseV05(true) + const span = ns.createSpan() + span.name = 'v05-span' + span.service = 'test-service' + span.resource = 'test-resource' + span.type = 'web' + span.duration = 1_000_000n + try { + await ns.flushSpans(span) + return seen.find(r => r.method === 'POST') + } finally { + server.closeAllConnections?.() + server.close() + } + } + + it('targets /v0.4/traces by default', async () => { + const req = await flushAndCapturePath(false) + assert.ok(req, 'agent received a POST') + assert.strictEqual(req.url, '/v0.4/traces') + }) + + it('targets /v0.5/traces after setUseV05(true)', async () => { + const req = await flushAndCapturePath(true) + assert.ok(req, 'agent received a POST') + assert.strictEqual(req.url, '/v0.5/traces') + }) + + it('exports via OTLP HTTP after setOtlpEndpoint(url)', async () => { + // libdatadog maps its internal traces to OTLP and POSTs them to the + // configured endpoint instead of the Datadog agent. Confirms the OTLP + // path runs end-to-end over the wasm HTTP transport. + const http = require('node:http') + const seen = [] + const server = http.createServer((req, res) => { + const chunks = [] + req.on('data', c => chunks.push(c)) + req.on('end', () => { + seen.push({ + method: req.method, + url: req.url, + ct: req.headers['content-type'], + len: Buffer.concat(chunks).length, + body: Buffer.concat(chunks).toString(), + }) + res.writeHead(200, { 'content-type': 'application/json' }) + res.end('{}') + }) + }) + await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) + const { port } = server.address() + const ns = new NativeSpansInterface({ + agentUrl: `http://127.0.0.1:${port}`, + tracerVersion: '7.0.0-pre', + }) + ns.state.setOtlpEndpoint(`http://127.0.0.1:${port}/v1/traces`) + const span = ns.createSpan() + span.name = 'otlp-span' + span.service = 'test-service' + span.resource = 'test-resource' + span.type = 'web' + span.duration = 1_000_000n + try { + await ns.flushSpans(span) + const req = seen.find(r => r.method === 'POST') + assert.ok(req, 'OTLP endpoint received a POST') + assert.strictEqual(req.url, '/v1/traces') + // No setOtlpProtocol call — pins the default wire protocol (http/json). + assert.match(req.ct || '', /json/) + assert.ok(req.len > 0, 'OTLP body is non-empty') + const body = JSON.parse(req.body) + assert.strictEqual(body.resourceSpans[0].scopeSpans[0].scope.name, 'dd-trace-js') + assert.strictEqual(body.resourceSpans[0].scopeSpans[0].scope.version, '7.0.0-pre') + } finally { + server.closeAllConnections?.() + server.close() + } + }) + + it('honors setOtlpProtocol(http/protobuf) and setOtlpHeaders', async () => { + const http = require('node:http') + let captured + const server = http.createServer((req, res) => { + req.on('data', () => {}) + req.on('end', () => { + if (req.method === 'POST') { + captured = { + ct: req.headers['content-type'], + auth: req.headers.authorization, + custom: req.headers['x-custom'], + } + } + res.writeHead(200, { 'content-type': 'application/json' }) + res.end('{}') + }) + }) + await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) + const { port } = server.address() + const ns = new NativeSpansInterface({ agentUrl: `http://127.0.0.1:${port}` }) + ns.state.setOtlpEndpoint(`http://127.0.0.1:${port}/v1/traces`) + ns.state.setOtlpProtocol('http/protobuf') + // Two header pairs plus a trailing unpaired element (odd length): both + // pairs are applied and the stray 'ignored-no-pair' is dropped. + ns.state.setOtlpHeaders(['authorization', 'Bearer test-token', 'x-custom', 'cval', 'ignored-no-pair']) + const span = ns.createSpan() + span.name = 'otlp-span' + span.service = 'test-service' + span.resource = 'test-resource' + span.type = 'web' + span.duration = 1_000_000n + try { + await ns.flushSpans(span) + assert.ok(captured, 'OTLP endpoint received a POST') + assert.match(captured.ct || '', /protobuf/) + assert.strictEqual(captured.auth, 'Bearer test-token') + assert.strictEqual(captured.custom, 'cval') + } finally { + server.closeAllConnections?.() + server.close() + } + }) + + it('rejects unsupported OTLP protocols (e.g. grpc)', () => { + const ns = new NativeSpansInterface({ agentUrl: 'http://127.0.0.1:8126' }) + assert.throws(() => ns.state.setOtlpProtocol('grpc'), /setOtlpProtocol|not supported/) + }) + }) + + describe('client-computed-stats header', () => { + async function captureTraceHeader (nsOptions) { + const http = require('node:http') + let header + let sawTraces = false + const server = http.createServer((req, res) => { + req.on('data', () => {}) + req.on('end', () => { + if (req.url === '/v0.4/traces') { + sawTraces = true + header = req.headers['datadog-client-computed-stats'] + } + res.writeHead(200, { 'content-type': 'application/json' }) + res.end('{}') + }) + }) + await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) + const { port } = server.address() + const ns = new NativeSpansInterface({ agentUrl: `http://127.0.0.1:${port}`, ...nsOptions }) + const span = ns.createSpan() + span.name = 'span' + span.service = 'test-service' + span.resource = 'test-resource' + span.type = 'web' + span.duration = 1_000_000n + try { + await ns.flushSpans(span) + return { header, sawTraces } + } finally { + server.closeAllConnections?.() + server.close() + } + } + + it('sends Datadog-Client-Computed-Stats: true when enabled', async () => { + const { header, sawTraces } = await captureTraceHeader({ clientComputedStats: true }) + assert.ok(sawTraces, 'expected a POST to /v0.4/traces') + assert.strictEqual(header, 'true') + }) + + it('omits the header when both flags are disabled', async () => { + const { header, sawTraces } = await captureTraceHeader({ clientComputedStats: false, statsEnabled: false }) + assert.ok(sawTraces, 'expected a POST to /v0.4/traces') + assert.strictEqual(header, undefined) + }) + + it('sends the header when stats are enabled (client-side stats imply it)', async () => { + // Enabling client-side stats without clientComputedStats must still send + // the header, otherwise the agent double-counts APM stats. + const { header, sawTraces } = await captureTraceHeader({ statsEnabled: true, clientComputedStats: false }) + assert.ok(sawTraces, 'expected a POST to /v0.4/traces') + assert.strictEqual(header, 'true') + }) + }) + + describe('client-side stats', () => { + it('aggregates and flushes stats to /v0.6/stats', async () => { + const http = require('node:http') + const seen = [] + const server = http.createServer((req, res) => { + const chunks = [] + req.on('data', c => chunks.push(c)) + req.on('end', () => { + seen.push({ method: req.method, url: req.url, len: Buffer.concat(chunks).length }) + res.writeHead(200, { 'content-type': 'application/json' }) + res.end('{}') + }) + }) + await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) + const { port } = server.address() + + // statsEnabled:true builds the StatsCollector; prepareChunk feeds spans + // into it, and flushStats(true) force-flushes to /v0.6/stats. + const ns = new NativeSpansInterface({ agentUrl: `http://127.0.0.1:${port}`, statsEnabled: true }) + const span = ns.createSpan() + span.name = 'stats-span' + span.service = 'stats-svc' + span.resource = '/stats' + span.type = 'web' + span.duration = 5_000_000n + + try { + await ns.flushSpans(span) + const result = await ns.state.flushStats(true) + assert.deepStrictEqual(result, { sent: true, collapsedSpans: 0 }, 'flushStats reported a send') + const statsReq = seen.find(r => r.url === '/v0.6/stats') + assert.ok(statsReq, 'agent received a /v0.6/stats request') + assert.strictEqual(statsReq.method, 'PUT') + assert.ok(statsReq.len > 0, 'stats payload is non-empty') + + // Nothing new aggregated -> a second forced flush is a no-op. + assert.deepStrictEqual(await ns.state.flushStats(true), { sent: false, collapsedSpans: 0 }, 'second flush has nothing to send') + } finally { + server.closeAllConnections?.() + server.close() + } + }) + + it('returns collapsed span count when stats cardinality overflows', async () => { + const http = require('node:http') + const seen = [] + const server = http.createServer((req, res) => { + const chunks = [] + req.on('data', c => chunks.push(c)) + req.on('end', () => { + seen.push({ method: req.method, url: req.url, len: Buffer.concat(chunks).length }) + res.writeHead(200, { 'content-type': 'application/json' }) + res.end('{}') + }) + }) + await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) + const { port } = server.address() + + const ns = new NativeSpansInterface({ agentUrl: `http://127.0.0.1:${port}`, statsEnabled: true }) + let batch = [] + + try { + for (let i = 0; i < 15_000; i++) { + const span = ns.createSpan() + span.name = `stats-span-${i}` + span.service = 'stats-svc' + span.resource = '/stats' + span.type = 'web' + span.setTag('span.kind', 'server') + span.duration = 5_000_000n + ns.flushChangeQueue() + batch.push(span) + if (batch.length === 500) { + await ns.flushSpans(...batch) + batch = [] + } + } + if (batch.length > 0) { + await ns.flushSpans(...batch) + } + + const result = await ns.state.flushStats(true) + assert.strictEqual(result.sent, true) + assert.ok(result.collapsedSpans > 0, 'stats cardinality overflow reported collapsed spans') + assert.ok(seen.some(r => r.url === '/v0.6/stats'), 'agent received a /v0.6/stats request') + } finally { + server.closeAllConnections?.() + server.close() + } + }) + + it('flushStats reports no send when stats are disabled', async () => { + const ns = new NativeSpansInterface({ statsEnabled: false }) + assert.deepStrictEqual(await ns.state.flushStats(true), { sent: false, collapsedSpans: 0 }) + }) + + it('flushes stats to /v0.6/stats over a Unix domain socket', { skip: process.platform === 'win32' }, async () => { + // A `unix://` agent URL must route /v0.6/stats over the socket, like + // traces do. parse_uri hex-encodes the socket path into the URI authority + // (which the transport's decode_socket_path reverses); a raw parse would + // leave the path in the URI path and never reach the socket. + const http = require('node:http') + const os = require('node:os') + const fs = require('node:fs') + const nodePath = require('node:path') + // Keep the path short — AF_UNIX paths are capped (~104 bytes on macOS). + const sockPath = nodePath.join(os.tmpdir(), `dd-st-${process.pid}.sock`) + try { + fs.unlinkSync(sockPath) + } catch { + // not present + } + const seen = [] + const server = http.createServer((req, res) => { + const chunks = [] + req.on('data', c => chunks.push(c)) + req.on('end', () => { + seen.push({ method: req.method, url: req.url, len: Buffer.concat(chunks).length }) + res.writeHead(200, { 'content-type': 'application/json' }) + res.end('{}') + }) + }) + await new Promise((resolve, reject) => { + server.once('error', reject) + server.listen(sockPath, resolve) + }) + + const ns = new NativeSpansInterface({ agentUrl: `unix://${sockPath}`, statsEnabled: true }) + const span = ns.createSpan() + span.name = 'stats-span' + span.service = 'stats-svc' + span.resource = '/stats' + span.type = 'web' + span.duration = 5_000_000n + + try { + await ns.flushSpans(span) + const result = await ns.state.flushStats(true) + assert.deepStrictEqual( + result, + { sent: true, collapsedSpans: 0 }, + 'flushStats reported a send over the socket', + ) + const statsReq = seen.find(r => r.url === '/v0.6/stats') + assert.ok(statsReq, 'agent received a /v0.6/stats request over the socket') + assert.ok(statsReq.len > 0, 'stats payload is non-empty') + } finally { + server.closeAllConnections?.() + server.close() + try { + fs.unlinkSync(sockPath) + } catch { + // already gone + } + } + }) + }) + + describe('send re-entrancy', () => { + it('rejects an overlapping sendPreparedChunk call', async () => { + const http = require('node:http') + const server = http.createServer((req, res) => { + req.resume() + req.on('end', () => { + res.writeHead(200, { 'content-type': 'application/json' }) + res.end('{}') + }) + }) + await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) + const { port } = server.address() + const ns = new NativeSpansInterface({ agentUrl: `http://127.0.0.1:${port}` }) + const span = ns.createSpan() + span.name = 'reentrancy' + ns.flushBuffer.fill(0) + for (let i = 0; i < 8; i++) ns.flushBuffer[i] = span.spanId[7 - i] + assert.ok(ns.state.prepareChunk(1, true, ns.flushBuffer)) + + try { + // Two calls without awaiting the first: the in-flight guard must reject + // the second instead of aliasing the exporter (UB). + const settled = await Promise.allSettled([ + ns.state.sendPreparedChunk(), + ns.state.sendPreparedChunk(), + ]) + const reasons = settled + .filter(s => s.status === 'rejected') + .map(s => String(s.reason)) + assert.ok( + reasons.some(r => /already in flight/.test(r)), + 'one overlapping call rejected as already-in-flight', + ) + } finally { + server.closeAllConnections?.() + server.close() + } + }) + }) +}) diff --git a/test/process-discovery.js b/test/process-discovery.js new file mode 100644 index 00000000..9f202350 --- /dev/null +++ b/test/process-discovery.js @@ -0,0 +1,116 @@ +'use strict' + +const assert = require('node:assert') +const fs = require('node:fs') +const process = require('node:process') + +const libdatadog = require('..') +const process_discovery = libdatadog.load('process-discovery') +assert(process_discovery !== undefined) + +const metadata = new process_discovery.TracerMetadata( + '7938685c-19dd-490f-b9b3-8aae4c22f897', + '1.0.0', + 'my_hostname', + 'my_svc', + 'my_env', + 'my_version', + 'entrypoint.name:server,svc.auto:my_svc', + 'abc123def456abc123def456abc123def456abc123def456abc123def456abc123', +) + +const cfg_handle = process_discovery.storeMetadata(metadata) +assert(cfg_handle !== undefined) + +// Same shape, plus a thread-local metadata block (OTEP-4947). libdatadog +// implicitly prepends `datadog.local_root_span_id` at wire index 0 in the +// attribute key map; entries here start at wire index 1. `schemaVersion` and +// `extraAttributes` describe the on-the-wire record schema for readers. +const metadata_with_threadlocal = new process_discovery.TracerMetadata( + '7938685c-19dd-490f-b9b3-8aae4c22f898', + '1.0.0', + 'my_hostname', + 'my_svc', + 'my_env', + 'my_version', + undefined, + undefined, + { + attributeKeys: ['endpoint', 'http.status'], + schemaVersion: 'nodejs_v1_dev', + extraAttributes: [ + { key: 'threadlocal.wrapped_object_offset', intValue: 24 }, + { key: 'threadlocal.tagged_size', intValue: 8 }, + { key: 'threadlocal.runtime.name', stringValue: 'nodejs' }, + ], + }, +) +assert.deepStrictEqual( + metadata_with_threadlocal.threadlocalMetadata.attributeKeys, + ['endpoint', 'http.status'], +) +assert.strictEqual( + metadata_with_threadlocal.threadlocalMetadata.schemaVersion, + 'nodejs_v1_dev', +) +assert.strictEqual( + metadata_with_threadlocal.threadlocalMetadata.extraAttributes.length, + 3, +) +const cfg_handle_threadlocal = process_discovery.storeMetadata(metadata_with_threadlocal) +assert(cfg_handle_threadlocal !== undefined) + +// An ExtraAttribute with neither stringValue nor intValue set is a caller +// error — one of them has to be picked. +const bad_metadata_neither = new process_discovery.TracerMetadata( + '7938685c-19dd-490f-b9b3-8aae4c22f899', + '1.0.0', + 'my_hostname', + undefined, undefined, undefined, undefined, undefined, + { + attributeKeys: [], + schemaVersion: undefined, + extraAttributes: [{ key: 'threadlocal.bogus' }], + }, +) +assert.throws( + () => process_discovery.storeMetadata(bad_metadata_neither), + /neither is/, +) + +// Setting both stringValue and intValue is also a caller error — the intent +// is ambiguous, so reject. +const bad_metadata_both = new process_discovery.TracerMetadata( + '7938685c-19dd-490f-b9b3-8aae4c22f89a', + '1.0.0', + 'my_hostname', + undefined, undefined, undefined, undefined, undefined, + { + attributeKeys: [], + schemaVersion: undefined, + extraAttributes: [{ key: 'threadlocal.bogus', stringValue: 's', intValue: 1 }], + }, +) +assert.throws( + () => process_discovery.storeMetadata(bad_metadata_both), + /both are/, +) + +if (process.platform === 'linux') { + const contains_datadog_memfd = (fds) => { + for (const fd in fds) { + try { + const fd_name = fs.readlinkSync(`/proc/${process.pid}/fd/${fd}`) + if (fd_name.includes('datadog-tracer-info-')) { + return true + } + } catch { + continue + } + } + return false + } + + const fds = fs.readdirSync(`/proc/${process.pid}/fd`) + assert(contains_datadog_memfd(fds)) +} diff --git a/test/wasm/datadog-js-zstd/index.js b/test/wasm/datadog-js-zstd/index.js new file mode 100644 index 00000000..734dfeb8 --- /dev/null +++ b/test/wasm/datadog-js-zstd/index.js @@ -0,0 +1,51 @@ +const assert = require('node:assert') + +const loader = require('../../../load') + +const zstd = loader.load('datadog-js-zstd') +assert(zstd !== undefined) + +// Create some compressible data +const SAMPLE_SIZE = 512 +const SAMPLE_COUNT = 1024 +const DATA_SIZE = SAMPLE_COUNT * 4 * SAMPLE_SIZE + +const samples = [] +for (let i = 0; i < SAMPLE_COUNT; i++) { + const sample = Array.from({ length: SAMPLE_SIZE }) + for (let j = 0; j < SAMPLE_SIZE; j++) { + sample[j] = Math.trunc(Math.random() * 256) + } + samples.push(sample) +} +const data = Array.from({ length: DATA_SIZE }) +for (let i = 0; i < DATA_SIZE; i += SAMPLE_SIZE) { + data.push(...samples[Math.trunc(Math.random() * SAMPLE_COUNT)]) +} +// Introduce some irregularities +for (let i = 0; i < SAMPLE_COUNT; i++) { + data[Math.trunc(Math.random() * DATA_SIZE)] = 0 +} +const dataArr = new Uint8Array(data) +const compressed3 = zstd.zstd_compress(dataArr, 3) +ensureCompressed(compressed3) + +// Test that 0 means default compression level +const compressed0 = zstd.zstd_compress(dataArr, 0) +ensureCompressed(compressed3) +assert(compressed0.length == compressed3.length) + +// Test that compression levels are correctly passed on. +// Level 18 should produce a smaller output than level 3. +// We can go all the way up to 22, but it is significantly slower. +const compressed18 = zstd.zstd_compress(dataArr, 18) +ensureCompressed(compressed18) +assert(compressed18.length < compressed3.length) + +function ensureCompressed (compressed) { + assert(compressed.length > 4) + assert.equal(compressed[0], 0x28) + assert.equal(compressed[1], 0xB5) + assert.equal(compressed[2], 0x2F) + assert.equal(compressed[3], 0xFD) +} diff --git a/test/wasm/library_config/.gitignore b/test/wasm/library_config/.gitignore new file mode 100644 index 00000000..5fff1d9c --- /dev/null +++ b/test/wasm/library_config/.gitignore @@ -0,0 +1 @@ +pkg diff --git a/test/wasm/library_config/README.md b/test/wasm/library_config/README.md new file mode 100644 index 00000000..ca98e968 --- /dev/null +++ b/test/wasm/library_config/README.md @@ -0,0 +1,8 @@ +# Libconfig example + +## How to run +From repository root +```bash +yarn build-wasm +node test/wasm/library_config/index.js +``` diff --git a/test/wasm/library_config/config_local_phase1.yaml b/test/wasm/library_config/config_local_phase1.yaml new file mode 100644 index 00000000..f9c6b621 --- /dev/null +++ b/test/wasm/library_config/config_local_phase1.yaml @@ -0,0 +1,2 @@ +apm_configuration_default: + DD_RUNTIME_METRICS_ENABLED: true diff --git a/test/wasm/library_config/config_local_phase2.yaml b/test/wasm/library_config/config_local_phase2.yaml new file mode 100644 index 00000000..f9c6b621 --- /dev/null +++ b/test/wasm/library_config/config_local_phase2.yaml @@ -0,0 +1,2 @@ +apm_configuration_default: + DD_RUNTIME_METRICS_ENABLED: true diff --git a/test/wasm/library_config/config_managed_phase2.yaml b/test/wasm/library_config/config_managed_phase2.yaml new file mode 100644 index 00000000..28c3302e --- /dev/null +++ b/test/wasm/library_config/config_managed_phase2.yaml @@ -0,0 +1,9 @@ +rules: + - selectors: + - origin: language + matches: + - nodejs + operator: equals + configuration: + DD_SERVICE: my-service_butremote +config_id: abc diff --git a/test/wasm/library_config/index.js b/test/wasm/library_config/index.js new file mode 100644 index 00000000..6904d00d --- /dev/null +++ b/test/wasm/library_config/index.js @@ -0,0 +1,63 @@ +const assert = require('node:assert') +const fs = require('node:fs') +const path = require('node:path') + +const loader = require('../../../load') + +const libconfig = loader.load('library_config') +assert(libconfig !== undefined) + +// Test 1: phase 1 (host selection) +function test_host_wide () { + const rawConfigLocal = fs.readFileSync(path.join(__dirname, 'config_local_phase1.yaml')) + const configurator = new libconfig.JsConfigurator() + + configurator.set_envp(Object.entries(process.env).map(([key, value]) => `${key}=${value}`)) + configurator.set_args(process.argv) + + const values = configurator.get_configuration(rawConfigLocal.toString(), '') + for (const value of values) { + console.log(`(phase 1) name: ${value.name}, value: ${value.value}, source: ${value.source}, config_id: ${value.config_id}`) + } + + assert.strictEqual(values.length, 1) + assert.strictEqual(values[0].name, 'DD_RUNTIME_METRICS_ENABLED') + assert.strictEqual(values[0].value, 'true') + assert.strictEqual(values[0].source, 'local_stable_config') +} + +// Test 2: managed > local, phase 2 (service selection) +function test_service_selector () { + const rawConfigLocal = fs.readFileSync(path.join(__dirname, 'config_local_phase2.yaml')) + const rawConfigManaged = fs.readFileSync(path.join(__dirname, 'config_managed_phase2.yaml')) + const configurator = new libconfig.JsConfigurator() + + configurator.set_envp(Object.entries(process.env).map(([key, value]) => `${key}=${value}`)) + configurator.set_args(process.argv) + + const values = configurator.get_configuration(rawConfigLocal.toString(), rawConfigManaged.toString()) + for (const value of values) { + console.log(`(phase 2) name: ${value.name}, value: ${value.value}, source: ${value.source}, config_id: ${value.config_id}`) + } + + assert.strictEqual(values.length, 2) + // We can't rely on ordering, so sort it by name to make it deterministic + values.sort((a, b) => a.name.localeCompare(b.name)) + assert.strictEqual(values[0].name, 'DD_RUNTIME_METRICS_ENABLED') + assert.strictEqual(values[0].value, 'true') + assert.strictEqual(values[0].source, 'local_stable_config') + assert.strictEqual(values[1].name, 'DD_SERVICE') + assert.strictEqual(values[1].value, 'my-service_butremote') + assert.strictEqual(values[1].source, 'fleet_stable_config') + + if (process.platform == 'linux') { + assert.strictEqual(configurator.get_config_local_path(process.platform), '/etc/datadog-agent/application_monitoring.yaml') + } else if (process.platform == 'darwin') { + assert.strictEqual(configurator.get_config_local_path(process.platform), '/opt/datadog-agent/etc/application_monitoring.yaml') + } else if (process.platform == 'win32') { + assert.strictEqual(configurator.get_config_local_path(process.platform), String.raw`C:\ProgramData\Datadog\application_monitoring.yaml`) + } +} + +test_host_wide() +test_service_selector() diff --git a/test/wasm/sketches/index.js b/test/wasm/sketches/index.js new file mode 100644 index 00000000..dfae84f6 --- /dev/null +++ b/test/wasm/sketches/index.js @@ -0,0 +1,29 @@ +'use strict' + +const assert = require('node:assert') + +const loader = require('../../../load') +const { DDSketch } = loader.load('sketches') + +const sketch = new DDSketch() +assert.strictEqual(sketch.count(), 0) + +sketch.add(1) +sketch.addWithCount(2, 3) +assert.strictEqual(sketch.count(), 4) + +assert.throws(() => sketch.add(-1), /point is invalid/) +assert.throws(() => sketch.addWithCount(1, Number.NaN), /count is invalid/) + +const encoded = sketch.encode() +assert(encoded instanceof Uint8Array) +assert(encoded.length > 0) + +assert.strictEqual(sketch.count(), 4) +sketch.add(3) +assert.strictEqual(sketch.count(), 5) + +const reencoded = sketch.encode() +assert(reencoded instanceof Uint8Array) +assert(reencoded.length > 0) +assert.strictEqual(sketch.count(), 5) diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..85f5c9f5 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,949 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/helper-validator-identifier@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" + integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== + +"@emnapi/core@^1.4.3": + version "1.8.1" + resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.8.1.tgz#fd9efe721a616288345ffee17a1f26ac5dd01349" + integrity sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg== + dependencies: + "@emnapi/wasi-threads" "1.1.0" + tslib "^2.4.0" + +"@emnapi/runtime@^1.4.3": + version "1.8.1" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.8.1.tgz#550fa7e3c0d49c5fb175a116e8cd70614f9a22a5" + integrity sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg== + dependencies: + tslib "^2.4.0" + +"@emnapi/wasi-threads@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz#60b2102fddc9ccb78607e4a3cf8403ea69be41bf" + integrity sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ== + dependencies: + tslib "^2.4.0" + +"@eslint-community/eslint-utils@^4.1.2", "@eslint-community/eslint-utils@^4.5.0", "@eslint-community/eslint-utils@^4.8.0", "@eslint-community/eslint-utils@^4.9.0", "@eslint-community/eslint-utils@^4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595" + integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== + dependencies: + eslint-visitor-keys "^3.4.3" + +"@eslint-community/regexpp@^4.11.0", "@eslint-community/regexpp@^4.12.2": + version "4.12.2" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b" + integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== + +"@eslint/config-array@^0.23.5": + version "0.23.5" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.23.5.tgz#56e86d243049195d8acc0c06a1b3dfdc3fa3de95" + integrity sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA== + dependencies: + "@eslint/object-schema" "^3.0.5" + debug "^4.3.1" + minimatch "^10.2.4" + +"@eslint/config-helpers@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.6.0.tgz#ef9a36881d39dfd5dbeac22b0da997fabfb08b03" + integrity sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA== + dependencies: + "@eslint/core" "^1.2.1" + +"@eslint/core@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-1.2.1.tgz#c1da7cd1b82fa8787f98b5629fb811848a1b63ce" + integrity sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ== + dependencies: + "@types/json-schema" "^7.0.15" + +"@eslint/js@^10.0.1": + version "10.0.1" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-10.0.1.tgz#1e8a876f50117af8ab67e47d5ad94d38d6622583" + integrity sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA== + +"@eslint/object-schema@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-3.0.5.tgz#88e9bf4d11d2b19c082e78ebe7ce88724a5eb091" + integrity sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw== + +"@eslint/plugin-kit@^0.7.2": + version "0.7.2" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz#4b0962f3f2c7ce8bc98b3ecfe34525c09d2cb729" + integrity sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A== + dependencies: + "@eslint/core" "^1.2.1" + levn "^0.4.1" + +"@humanfs/core@^0.19.1": + version "0.19.1" + resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" + integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== + +"@humanfs/node@^0.16.6": + version "0.16.7" + resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.7.tgz#822cb7b3a12c5a240a24f621b5a2413e27a45f26" + integrity sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ== + dependencies: + "@humanfs/core" "^0.19.1" + "@humanwhocodes/retry" "^0.4.0" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/retry@^0.4.0", "@humanwhocodes/retry@^0.4.2": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba" + integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== + +"@napi-rs/wasm-runtime@^0.2.11": + version "0.2.12" + resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz#3e78a8b96e6c33a6c517e1894efbd5385a7cb6f2" + integrity sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ== + dependencies: + "@emnapi/core" "^1.4.3" + "@emnapi/runtime" "^1.4.3" + "@tybys/wasm-util" "^0.10.0" + +"@stylistic/eslint-plugin@^5.9.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin/-/eslint-plugin-5.10.0.tgz#471bbd9f7a27ceaac4a217e7f5b3890855e5640c" + integrity sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ== + dependencies: + "@eslint-community/eslint-utils" "^4.9.1" + "@typescript-eslint/types" "^8.56.0" + eslint-visitor-keys "^4.2.1" + espree "^10.4.0" + estraverse "^5.3.0" + picomatch "^4.0.3" + +"@tybys/wasm-util@^0.10.0": + version "0.10.1" + resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz#ecddd3205cf1e2d5274649ff0eedd2991ed7f414" + integrity sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg== + dependencies: + tslib "^2.4.0" + +"@types/esrecurse@^4.3.1": + version "4.3.1" + resolved "https://registry.yarnpkg.com/@types/esrecurse/-/esrecurse-4.3.1.tgz#6f636af962fbe6191b830bd676ba5986926bccec" + integrity sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw== + +"@types/estree@^1.0.6", "@types/estree@^1.0.8": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" + integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== + +"@types/json-schema@^7.0.15": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@typescript-eslint/types@^8.56.0": + version "8.57.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.57.0.tgz#4fa5385ffd1cd161fa5b9dce93e0493d491b8dc6" + integrity sha512-dTLI8PEXhjUC7B9Kre+u0XznO696BhXcTlOn0/6kf1fHaQW8+VjJAVHJ3eTI14ZapTxdkOmc80HblPQLaEeJdg== + +"@unrs/resolver-binding-android-arm-eabi@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz#9f5b04503088e6a354295e8ea8fe3cb99e43af81" + integrity sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw== + +"@unrs/resolver-binding-android-arm64@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz#7414885431bd7178b989aedc4d25cccb3865bc9f" + integrity sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g== + +"@unrs/resolver-binding-darwin-arm64@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz#b4a8556f42171fb9c9f7bac8235045e82aa0cbdf" + integrity sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g== + +"@unrs/resolver-binding-darwin-x64@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz#fd4d81257b13f4d1a083890a6a17c00de571f0dc" + integrity sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ== + +"@unrs/resolver-binding-freebsd-x64@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz#d2513084d0f37c407757e22f32bd924a78cfd99b" + integrity sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw== + +"@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz#844d2605d057488d77fab09705f2866b86164e0a" + integrity sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw== + +"@unrs/resolver-binding-linux-arm-musleabihf@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz#204892995cefb6bd1d017d52d097193bc61ddad3" + integrity sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw== + +"@unrs/resolver-binding-linux-arm64-gnu@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz#023eb0c3aac46066a10be7a3f362e7b34f3bdf9d" + integrity sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ== + +"@unrs/resolver-binding-linux-arm64-musl@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz#9e6f9abb06424e3140a60ac996139786f5d99be0" + integrity sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w== + +"@unrs/resolver-binding-linux-ppc64-gnu@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz#b111417f17c9d1b02efbec8e08398f0c5527bb44" + integrity sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA== + +"@unrs/resolver-binding-linux-riscv64-gnu@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz#92ffbf02748af3e99873945c9a8a5ead01d508a9" + integrity sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ== + +"@unrs/resolver-binding-linux-riscv64-musl@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz#0bec6f1258fc390e6b305e9ff44256cb207de165" + integrity sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew== + +"@unrs/resolver-binding-linux-s390x-gnu@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz#577843a084c5952f5906770633ccfb89dac9bc94" + integrity sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg== + +"@unrs/resolver-binding-linux-x64-gnu@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz#36fb318eebdd690f6da32ac5e0499a76fa881935" + integrity sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w== + +"@unrs/resolver-binding-linux-x64-musl@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz#bfb9af75f783f98f6a22c4244214efe4df1853d6" + integrity sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA== + +"@unrs/resolver-binding-wasm32-wasi@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz#752c359dd875684b27429500d88226d7cc72f71d" + integrity sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ== + dependencies: + "@napi-rs/wasm-runtime" "^0.2.11" + +"@unrs/resolver-binding-win32-arm64-msvc@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz#ce5735e600e4c2fbb409cd051b3b7da4a399af35" + integrity sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw== + +"@unrs/resolver-binding-win32-ia32-msvc@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz#72fc57bc7c64ec5c3de0d64ee0d1810317bc60a6" + integrity sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ== + +"@unrs/resolver-binding-win32-x64-msvc@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz#538b1e103bf8d9864e7b85cc96fa8d6fb6c40777" + integrity sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g== + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.15.0, acorn@^8.16.0: + version "8.16.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a" + integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw== + +ajv@^6.14.0: + version "6.14.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.14.0.tgz#fd067713e228210636ebb08c60bd3765d6dbe73a" + integrity sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +balanced-match@^4.0.2: + version "4.0.4" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a" + integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== + +baseline-browser-mapping@^2.9.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz#5b09935025bf8a80e29130251e337c6a7fc8cbb9" + integrity sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA== + +brace-expansion@^5.0.2: + version "5.0.6" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.6.tgz#ec68fe0a641a29d8711579caf641d05bae1f2285" + integrity sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g== + dependencies: + balanced-match "^4.0.2" + +browserslist@^4.28.1: + version "4.28.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.1.tgz#7f534594628c53c63101079e27e40de490456a95" + integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA== + dependencies: + baseline-browser-mapping "^2.9.0" + caniuse-lite "^1.0.30001759" + electron-to-chromium "^1.5.263" + node-releases "^2.0.27" + update-browserslist-db "^1.2.0" + +builtin-modules@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-5.0.0.tgz#9be95686dedad2e9eed05592b07733db87dcff1a" + integrity sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg== + +caniuse-lite@^1.0.30001759: + version "1.0.30001777" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz#028f21e4b2718d138b55e692583e6810ccf60691" + integrity sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ== + +change-case@^5.4.4: + version "5.4.4" + resolved "https://registry.yarnpkg.com/change-case/-/change-case-5.4.4.tgz#0d52b507d8fb8f204343432381d1a6d7bff97a02" + integrity sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w== + +ci-info@^4.3.1: + version "4.4.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.4.0.tgz#7d54eff9f54b45b62401c26032696eb59c8bd18c" + integrity sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg== + +clean-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clean-regexp/-/clean-regexp-1.0.0.tgz#8df7c7aae51fd36874e8f8d05b9180bc11a3fed7" + integrity sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw== + dependencies: + escape-string-regexp "^1.0.5" + +comment-parser@^1.4.1: + version "1.4.5" + resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.4.5.tgz#6c595cd090737a1010fe5ff40d86e1d21b7bd6ce" + integrity sha512-aRDkn3uyIlCFfk5NUA+VdwMmMsh8JGhc4hapfV4yxymHGQ3BVskMQfoXGpCo5IoBuQ9tS5iiVKhCpTcB4pW4qw== + +core-js-compat@^3.46.0: + version "3.48.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.48.0.tgz#7efbe1fc1cbad44008190462217cc5558adaeaa6" + integrity sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q== + dependencies: + browserslist "^4.28.1" + +cross-spawn@^7.0.6: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@^4.3.1, debug@^4.3.2, debug@^4.4.1: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +electron-to-chromium@^1.5.263: + version "1.5.307" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz#09f8973100c39fb0d003b890393cd1d58932b1c8" + integrity sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg== + +enhanced-resolve@^5.17.1: + version "5.20.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz#323c2a70d2aa7fb4bdfd6d3c24dfc705c581295d" + integrity sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.3.0" + +escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-compat-utils@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz#7fc92b776d185a70c4070d03fd26fde3d59652e4" + integrity sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q== + dependencies: + semver "^7.5.4" + +eslint-import-context@^0.1.9: + version "0.1.9" + resolved "https://registry.yarnpkg.com/eslint-import-context/-/eslint-import-context-0.1.9.tgz#967b0b2f0a90ef4b689125e088f790f0b7756dbe" + integrity sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg== + dependencies: + get-tsconfig "^4.10.1" + stable-hash-x "^0.2.0" + +eslint-plugin-es-x@^7.8.0: + version "7.8.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz#a207aa08da37a7923f2a9599e6d3eb73f3f92b74" + integrity sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ== + dependencies: + "@eslint-community/eslint-utils" "^4.1.2" + "@eslint-community/regexpp" "^4.11.0" + eslint-compat-utils "^0.5.1" + +eslint-plugin-import-x@^4.17.1: + version "4.17.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-import-x/-/eslint-plugin-import-x-4.17.1.tgz#6e9211acd8e98d2da11f96c12c9c00aa49e3e035" + integrity sha512-4cdstYkKCyjumM2Q9NSI03K8D2a9F4Ssz33K2lv2hQa4KmR9jPLwk3uWGtNvclfqBrPGfGuMBwsGMbe6dMRbfg== + dependencies: + "@typescript-eslint/types" "^8.56.0" + comment-parser "^1.4.1" + debug "^4.4.1" + eslint-import-context "^0.1.9" + is-glob "^4.0.3" + minimatch "^9.0.3 || ^10.1.2" + semver "^7.7.2" + stable-hash-x "^0.2.0" + unrs-resolver "^1.9.2" + +eslint-plugin-n@^17.24.0: + version "17.24.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-n/-/eslint-plugin-n-17.24.0.tgz#b66fa05f7a6c1ba16768f0921b8974147dddd060" + integrity sha512-/gC7/KAYmfNnPNOb3eu8vw+TdVnV0zhdQwexsw6FLXbhzroVj20vRn2qL8lDWDGnAQ2J8DhdfvXxX9EoxvERvw== + dependencies: + "@eslint-community/eslint-utils" "^4.5.0" + enhanced-resolve "^5.17.1" + eslint-plugin-es-x "^7.8.0" + get-tsconfig "^4.8.1" + globals "^15.11.0" + globrex "^0.1.2" + ignore "^5.3.2" + semver "^7.6.3" + ts-declaration-location "^1.0.6" + +eslint-plugin-unicorn@^63.0.0: + version "63.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-63.0.0.tgz#db210b87bb66f0f15ab675ba13d9f1fb61016b22" + integrity sha512-Iqecl9118uQEXYh7adylgEmGfkn5es3/mlQTLLkd4pXkIk9CTGrAbeUux+YljSa2ohXCBmQQ0+Ej1kZaFgcfkA== + dependencies: + "@babel/helper-validator-identifier" "^7.28.5" + "@eslint-community/eslint-utils" "^4.9.0" + change-case "^5.4.4" + ci-info "^4.3.1" + clean-regexp "^1.0.0" + core-js-compat "^3.46.0" + find-up-simple "^1.0.1" + globals "^16.4.0" + indent-string "^5.0.0" + is-builtin-module "^5.0.0" + jsesc "^3.1.0" + pluralize "^8.0.0" + regexp-tree "^0.1.27" + regjsparser "^0.13.0" + semver "^7.7.3" + strip-indent "^4.1.1" + +eslint-scope@^9.1.2: + version "9.1.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-9.1.2.tgz#b9de6ace2fab1cff24d2e58d85b74c8fcea39802" + integrity sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ== + dependencies: + "@types/esrecurse" "^4.3.1" + "@types/estree" "^1.0.8" + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint-visitor-keys@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1" + integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== + +eslint-visitor-keys@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz#9e3c9489697824d2d4ce3a8ad12628f91e9f59be" + integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA== + +eslint@^10.6.0: + version "10.6.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-10.6.0.tgz#e1b4059c582be950c7088c9b55f984738b243c27" + integrity sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg== + dependencies: + "@eslint-community/eslint-utils" "^4.8.0" + "@eslint-community/regexpp" "^4.12.2" + "@eslint/config-array" "^0.23.5" + "@eslint/config-helpers" "^0.6.0" + "@eslint/core" "^1.2.1" + "@eslint/plugin-kit" "^0.7.2" + "@humanfs/node" "^0.16.6" + "@humanwhocodes/module-importer" "^1.0.1" + "@humanwhocodes/retry" "^0.4.2" + "@types/estree" "^1.0.6" + ajv "^6.14.0" + cross-spawn "^7.0.6" + debug "^4.3.2" + escape-string-regexp "^4.0.0" + eslint-scope "^9.1.2" + eslint-visitor-keys "^5.0.1" + espree "^11.2.0" + esquery "^1.7.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^8.0.0" + find-up "^5.0.0" + glob-parent "^6.0.2" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + json-stable-stringify-without-jsonify "^1.0.1" + minimatch "^10.2.4" + natural-compare "^1.4.0" + optionator "^0.9.3" + +espree@^10.4.0: + version "10.4.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.4.0.tgz#d54f4949d4629005a1fa168d937c3ff1f7e2a837" + integrity sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== + dependencies: + acorn "^8.15.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^4.2.1" + +espree@^11.2.0: + version "11.2.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-11.2.0.tgz#01d5e47dc332aaba3059008362454a8cc34ccaa5" + integrity sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw== + dependencies: + acorn "^8.16.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^5.0.1" + +esquery@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.7.0.tgz#08d048f261f0ddedb5bae95f46809463d9c9496d" + integrity sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +file-entry-cache@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" + integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== + dependencies: + flat-cache "^4.0.0" + +find-up-simple@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/find-up-simple/-/find-up-simple-1.0.1.tgz#18fb90ad49e45252c4d7fca56baade04fa3fca1e" + integrity sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ== + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" + integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.4" + +flatted@^3.2.9: + version "3.4.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.2.tgz#f5c23c107f0f37de8dbdf24f13722b3b98d52726" + integrity sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA== + +get-tsconfig@^4.10.1, get-tsconfig@^4.8.1: + version "4.13.6" + resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.13.6.tgz#2fbfda558a98a691a798f123afd95915badce876" + integrity sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw== + dependencies: + resolve-pkg-maps "^1.0.0" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +globals@^15.11.0: + version "15.15.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-15.15.0.tgz#7c4761299d41c32b075715a4ce1ede7897ff72a8" + integrity sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg== + +globals@^16.4.0: + version "16.5.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-16.5.0.tgz#ccf1594a437b97653b2be13ed4d8f5c9f850cac1" + integrity sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ== + +globals@^17.7.0: + version "17.7.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-17.7.0.tgz#553d55090b4dde8209ec2da42580d6e7e7d8b10d" + integrity sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg== + +globrex@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" + integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== + +graceful-fs@^4.2.4: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +ignore@^5.2.0, ignore@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +indent-string@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-5.0.0.tgz#4fd2980fccaf8622d14c64d694f4cf33c81951a5" + integrity sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg== + +is-builtin-module@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-5.0.0.tgz#19df4b9c7451149b68176b0e06d18646db6308dd" + integrity sha512-f4RqJKBUe5rQkJ2eJEJBXSticB3hGbN9j0yxxMQFqIW89Jp9WYFtzfTcRlstDKVUTRzSOTLKRfO9vIztenwtxA== + dependencies: + builtin-modules "^5.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.0, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +jsesc@^3.1.0, jsesc@~3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +keyv@^4.5.4: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +minimatch@^10.2.4, "minimatch@^9.0.3 || ^10.1.2": + version "10.2.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.4.tgz#465b3accbd0218b8281f5301e27cedc697f96fde" + integrity sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg== + dependencies: + brace-expansion "^5.0.2" + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +napi-postinstall@^0.3.0: + version "0.3.4" + resolved "https://registry.yarnpkg.com/napi-postinstall/-/napi-postinstall-0.3.4.tgz#7af256d6588b5f8e952b9190965d6b019653bbb9" + integrity sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +node-releases@^2.0.27: + version "2.0.36" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.36.tgz#99fd6552aaeda9e17c4713b57a63964a2e325e9d" + integrity sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA== + +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^4.0.2, picomatch@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.4.tgz#fd6f5e00a143086e074dffe4c924b8fb293b0589" + integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A== + +pluralize@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" + integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +regexp-tree@^0.1.27: + version "0.1.27" + resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.27.tgz#2198f0ef54518ffa743fe74d983b56ffd631b6cd" + integrity sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA== + +regjsparser@^0.13.0: + version "0.13.0" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.13.0.tgz#01f8351335cf7898d43686bc74d2dd71c847ecc0" + integrity sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q== + dependencies: + jsesc "~3.1.0" + +resolve-pkg-maps@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" + integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== + +semver@^7.5.4, semver@^7.6.3, semver@^7.7.2, semver@^7.7.3: + version "7.7.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a" + integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +stable-hash-x@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/stable-hash-x/-/stable-hash-x-0.2.0.tgz#dfd76bfa5d839a7470125c6a6b3c8b22061793e9" + integrity sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ== + +strip-indent@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-4.1.1.tgz#aba13de189d4ad9a17f6050e76554ac27585c7af" + integrity sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA== + +tapable@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.0.tgz#7e3ea6d5ca31ba8e078b560f0d83ce9a14aa8be6" + integrity sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg== + +ts-declaration-location@^1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/ts-declaration-location/-/ts-declaration-location-1.0.7.tgz#d4068fe9975828b3b453b3ab112b4711d8267688" + integrity sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA== + dependencies: + picomatch "^4.0.2" + +tslib@^2.4.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +unrs-resolver@^1.9.2: + version "1.11.1" + resolved "https://registry.yarnpkg.com/unrs-resolver/-/unrs-resolver-1.11.1.tgz#be9cd8686c99ef53ecb96df2a473c64d304048a9" + integrity sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg== + dependencies: + napi-postinstall "^0.3.0" + optionalDependencies: + "@unrs/resolver-binding-android-arm-eabi" "1.11.1" + "@unrs/resolver-binding-android-arm64" "1.11.1" + "@unrs/resolver-binding-darwin-arm64" "1.11.1" + "@unrs/resolver-binding-darwin-x64" "1.11.1" + "@unrs/resolver-binding-freebsd-x64" "1.11.1" + "@unrs/resolver-binding-linux-arm-gnueabihf" "1.11.1" + "@unrs/resolver-binding-linux-arm-musleabihf" "1.11.1" + "@unrs/resolver-binding-linux-arm64-gnu" "1.11.1" + "@unrs/resolver-binding-linux-arm64-musl" "1.11.1" + "@unrs/resolver-binding-linux-ppc64-gnu" "1.11.1" + "@unrs/resolver-binding-linux-riscv64-gnu" "1.11.1" + "@unrs/resolver-binding-linux-riscv64-musl" "1.11.1" + "@unrs/resolver-binding-linux-s390x-gnu" "1.11.1" + "@unrs/resolver-binding-linux-x64-gnu" "1.11.1" + "@unrs/resolver-binding-linux-x64-musl" "1.11.1" + "@unrs/resolver-binding-wasm32-wasi" "1.11.1" + "@unrs/resolver-binding-win32-arm64-msvc" "1.11.1" + "@unrs/resolver-binding-win32-ia32-msvc" "1.11.1" + "@unrs/resolver-binding-win32-x64-msvc" "1.11.1" + +update-browserslist-db@^1.2.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" + integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==