diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 40d84c187..a576d5c0a 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -9,13 +9,13 @@ jobs: CARGO_TERM_COLOR: always # Force Cargo to use colors TERM: xterm-256color steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Checkout base branch - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: ref: ${{ github.base_ref }} path: main/ - - uses: actions/setup-python@v4 + - uses: actions/setup-python@7f4fc3e22c37d6ff65e88745f38bd3157c663f7c # v4.9.1 with: python-version: "3.10" - name: Install Python dependencies @@ -23,7 +23,7 @@ jobs: python -m pip install --upgrade pip pip install pyyaml - name: Cache cargo build - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: | ~/.cargo @@ -53,7 +53,7 @@ jobs: echo "" } > /tmp/message.txt - name: Post comment - uses: thollander/actions-comment-pull-request@v2 + uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0 with: filePath: /tmp/message.txt comment_tag: canbench diff --git a/.github/workflows/coq.yml b/.github/workflows/coq.yml index ec56f00df..fd6961682 100644 --- a/.github/workflows/coq.yml +++ b/.github/workflows/coq.yml @@ -11,8 +11,8 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: cachix/install-nix-action@v12 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - uses: cachix/install-nix-action@07da2520eebede906fbeefa9dd0a2b635323909d # v12 #- run: nix-build s coq # The above would also build the shell, includling niv. # This would be useful behaviour if our CI seeds some cache. diff --git a/.github/workflows/license.yml b/.github/workflows/license.yml index 8ac5228b8..41b4ff7cf 100644 --- a/.github/workflows/license.yml +++ b/.github/workflows/license.yml @@ -15,7 +15,7 @@ jobs: name: license-check:required runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: EmbarkStudios/cargo-deny-action@v1 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + - uses: EmbarkStudios/cargo-deny-action@3f4a782664881cf5725d0ffd23969fcce89fd868 # v1.6.3 with: command: check bans licenses sources diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 000000000..de511b840 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,65 @@ +name: Publish crates to crates.io + +on: + workflow_dispatch: + inputs: + ic_principal: + description: "Publish ic_principal" + type: boolean + default: false + candid: + description: "Publish candid and candid_derive" + type: boolean + default: false + candid_parser: + description: "Publish candid_parser" + type: boolean + default: false + +jobs: + publish: + name: Publish selected crates + runs-on: ubuntu-24.04 + if: >- + inputs.ic_principal || inputs.candid || inputs.candid_parser + + permissions: + contents: read + id-token: write # Required for trusted publishing via OIDC + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Install Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@150fca883cd4034361b621bd4e6a9d34e5143606 # v1.15.4 + with: + cache: false + + - name: Authenticate with crates.io + id: auth + uses: rust-lang/crates-io-auth-action@b7e9a28eded4986ec6b1fa40eeee8f8f165559ec # v1 + + # Crates are ordered by dependency: publish dependencies before dependents. + # ic_principal has no workspace deps. + # candid_derive has no workspace deps. + # candid depends on ic_principal, candid_derive. + # candid_parser depends on candid. + + - name: Publish ic_principal + if: inputs.ic_principal + run: cargo publish -p ic_principal + env: + CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} + + - name: Publish candid and candid_derive + if: inputs.candid + run: cargo publish -p candid_derive && cargo publish -p candid + env: + CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} + + - name: Publish candid_parser + if: inputs.candid_parser + run: cargo publish -p candid_parser + env: + CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5f31bfe0b..06db62bd4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,7 @@ jobs: name: linux64 artifact_name: target/x86_64-unknown-linux-musl/release/didc asset_name: didc-linux64 - - os: macos-13-large + - os: macos-15-intel name: macos artifact_name: target/release/didc asset_name: didc-macos @@ -25,22 +25,12 @@ jobs: artifact_name: target/arm-unknown-linux-gnueabihf/release/didc asset_name: didc-arm32 steps: - - uses: actions/checkout@v4 - - name: Install stable toolchain - if: matrix.name != 'arm' - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - - name: Install stable toolchain + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - name: Install Rust toolchain + run: rustup show active-toolchain || rustup toolchain install + - name: Add arm target if: matrix.name == 'arm' - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - target: arm-unknown-linux-gnueabihf + run: rustup target add arm-unknown-linux-gnueabihf - name: Build if: matrix.name == 'linux64' run: | @@ -54,13 +44,13 @@ jobs: run: cargo build --package didc --release --locked - name: Cross build if: matrix.name == 'arm' - uses: actions-rs/cargo@v1 + uses: actions-rs/cargo@844f36862e911db73fe0815f00a4a2602c279505 # v1.0.3 with: use-cross: true command: build args: --package didc --target arm-unknown-linux-gnueabihf --release --locked - name: "Upload assets" - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: ${{ matrix.asset_name }} path: ${{ matrix.artifact_name }} @@ -77,14 +67,10 @@ jobs: asset_name: didc-linux64 - os: ubuntu-22.04 asset_name: didc-linux64 - - os: macos-14-large - asset_name: didc-macos - - os: macos-13-large - asset_name: didc-macos steps: - name: Get executable id: download - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: ${{ matrix.asset_name }} - name: Executable runs @@ -104,13 +90,46 @@ jobs: runs-on: ubuntu-latest steps: - name: Get executable - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: ${{ matrix.asset_name }} - name: Upload binaries to release - uses: svenstaro/upload-release-action@v2 + uses: svenstaro/upload-release-action@29e53e917877a24fad85510ded594ab3c9ca12de # 2.11.5 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: didc asset_name: ${{ matrix.asset_name }} tag: ${{ github.ref }} + + candid-ui: + name: Build and publish candid_ui canister + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - name: Install Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: '20' + - name: Install Rust toolchain + run: rustup show active-toolchain || rustup toolchain install + - name: Install binaryen + run: | + sudo apt-get update -yy + sudo apt-get install -yy binaryen + - name: Build candid_ui + working-directory: tools/ui + run: | + npm install + npm run build + cargo build --target wasm32-unknown-unknown --profile canister --package didjs + wasm-opt --strip-debug -Oz target/wasm32-unknown-unknown/canister/didjs.wasm -o target/wasm32-unknown-unknown/canister/didjs_opt.wasm + - name: Prepare asset + run: | + cp tools/ui/target/wasm32-unknown-unknown/canister/didjs_opt.wasm candid_ui.wasm + - name: Upload candid_ui to release + uses: svenstaro/upload-release-action@29e53e917877a24fad85510ded594ab3c9ca12de # 2.11.5 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: candid_ui.wasm + asset_name: candid_ui.wasm + tag: ${{ github.ref }} diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index fecd072e7..eedfff8a9 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -13,9 +13,9 @@ jobs: rust: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Cache cargo build - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: | ~/.cargo/registry @@ -39,7 +39,7 @@ jobs: fuzzing: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Install cargo-fuzz run: | cargo install cargo-fuzz diff --git a/.github/workflows/tools.yml b/.github/workflows/tools.yml index 1c312b843..534812e8f 100644 --- a/.github/workflows/tools.yml +++ b/.github/workflows/tools.yml @@ -15,7 +15,7 @@ jobs: build-didc: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Build didc run: cargo build -p didc --release @@ -26,16 +26,16 @@ jobs: run: working-directory: ${{ env.WORKING_DIR }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 # TODO: use dfinity/ci-tools/actions/setup-pnpm once the repo has a standard structure with a package.json file in the root - name: Install pnpm - uses: pnpm/action-setup@v3 + uses: pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d # v3 with: package_json_file: ${{ env.WORKING_DIR }}/package.json - name: Install Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version-file: ${{ env.WORKING_DIR }}/.node-version registry-url: "https://registry.npmjs.org" diff --git a/.gitignore b/.gitignore index fa8bf2662..75e44429f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ target/ .canbench/ +canbench_results.yml # IDEs .idea/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 5753510c7..b575cc6bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,109 @@ * Non-breaking changes: + Supports parsing the arguments' names for `func` and `service` (init args). +## 2026-05-27 + +### Candid 0.10.29 + +* Bug fixes: + + Fix `text_fast_path` leakage between nested maps: an inner map with non-text keys would fail with a "Type mismatch" error when enclosed in an outer map with text keys + +## 2026-05-20 + +### Candid 0.10.28 + +* Bug fixes: + + Fix LEB128/SLEB128 fast path silently truncating `Nat`/`Int` values near the `u64`/`i64` boundary during decoding + + Fix `Int::decode` truncating large magnitudes due to fast-path leakage + +### candid_parser 0.3.2 + +* Bug fixes: + + Motoko binding: emit `Float32` for Candid `float32` instead of panicking. `float32` support was added to Motoko in version 1.4.0. + +## 2026-04-08 + +### didc 0.6.1 + +* Non-breaking changes: + + `didc check` now reports **all** incompatible changes at once, grouped by method, instead of stopping at the first error + + Clearer error messages: e.g. "missing in new interface" and "function annotation changed from query to update" + +### candid_parser 0.3.1 + +* Non-breaking changes: + + Add `service_compatibility_report()` returning a full grouped compatibility report as a string + +### Candid 0.10.27 + +* Non-breaking changes: + + Add `subtype_check_all()` to collect all subtype errors in one pass (previously stopped at the first) + + Add `Incompatibility` type and `format_report()` for structured, hierarchical error reporting + +## 2026-03-18 + +### Candid 0.10.26 + +* Bug fixes: + + Fix decoding failure when a trailing argument is a primitive vector + +## 2026-03-16 + +### Candid 0.10.25 + +* Non-breaking changes: + + Preserve Rust doc comments on exported Candid types, record fields, and variant members when generating `.did` files via `#[derive(CandidType)]` + +## 2026-02-27 + +### Candid 0.10.24 + +* Non-breaking changes: + + Implement `DataSize` for `Principal`, enabling `Principal` as an element type in `BoundedVec` + +## 2026-02-20 + +### Candid 0.10.23 + +* Non-breaking changes: + + Add `BoundedVec` type to `candid::types::bounded_vec` for bounding a vector by number of elements, total data size, and per-element data size during deserialization + +## 2026-02-10 + +### Candid 0.10.22 + +* Non-breaking changes: + + Enhance recursion guard + - Use `target_family = "wasm"` for platform detection to cover both wasm32 and wasm64; skip recursion check on wasm (sandboxed), use stack-based check on native platforms and a conservative depth limit on other niche platforms + - Apply recursion guard to all recursive functions on deserialization path: type environment operations (`TypeEnv::is_empty`, `trace_type`, `rec_find_type`, `as_func`, `as_service`), value type annotation (`IDLValue::annotate_type`), subtype checking (`subtype_()`, `equal()`), and all deserializer methods (`deserialize_option`, `deserialize_seq`, `deserialize_map`, `deserialize_tuple`, `deserialize_tuple_struct`, `deserialize_struct`, `deserialize_enum`) + - Refactor to use RAII guard pattern (`RecursionDepth` with `DepthGuard`) for automatic depth management, eliminating manual increment/decrement operations + +## 2026-02-03 + +### Candid 0.10.21 + +* Non-breaking changes: + + Add `max_type_len` to `DecoderConfig` to configure the type table size limit (default: 10,000) during binary parsing. + +## 2026-01-09 + +### candid_parser 0.3.0 & didc 0.6.0 + +* Breaking changes: + + Changed imports generated by `candid_parser::bindings::typescript::compile` from `@dfinity/*` to `@icp-sdk/core/*` + +### ic_principal 0.1.2 + +* Non-breaking changes: + + Implement `rangemap::StepLite` for `Principal` (requires the optional `rangemap` feature flag) + +## 2025-12-18 + +### candid_parser 0.2.4 & didc 0.5.4 + +* Non-breaking changes: + + fix: escape `*/` to prevent premature JS doc comment termination + ## 2025-10-26 ### Candid 0.10.20 diff --git a/Cargo.lock b/Cargo.lock index d79409b55..52b23c12d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -201,7 +201,7 @@ dependencies = [ "byteorder", "candid_derive 0.6.6", "hex", - "ic_principal 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ic_principal 0.1.1", "leb128", "num-bigint", "num-traits", @@ -215,18 +215,18 @@ dependencies = [ [[package]] name = "candid" -version = "0.10.20" +version = "0.10.29" dependencies = [ "anyhow", "bincode", "binread", "byteorder", - "candid_derive 0.10.20", - "candid_parser 0.2.3", + "candid_derive 0.10.29", + "candid_parser 0.3.2", "foldhash", - "hashbrown 0.15.2", + "hashbrown 0.15.5", "hex", - "ic_principal 0.1.1", + "ic_principal 0.1.3", "leb128", "num-bigint", "num-traits", @@ -255,7 +255,7 @@ dependencies = [ [[package]] name = "candid_derive" -version = "0.10.20" +version = "0.10.29" dependencies = [ "lazy_static", "proc-macro2 1.0.86", @@ -284,11 +284,11 @@ dependencies = [ [[package]] name = "candid_parser" -version = "0.2.3" +version = "0.3.2" dependencies = [ "anyhow", "arbitrary", - "candid 0.10.20", + "candid 0.10.29", "codespan-reporting", "console", "convert_case", @@ -487,10 +487,10 @@ dependencies = [ [[package]] name = "didc" -version = "0.5.3" +version = "0.6.1" dependencies = [ "anyhow", - "candid_parser 0.2.3", + "candid_parser 0.3.2", "clap", "console", "hex", @@ -550,6 +550,12 @@ dependencies = [ "winapi", ] +[[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" @@ -617,9 +623,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "generic-array" @@ -690,9 +696,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", @@ -714,29 +720,31 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "ic_principal" version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1762deb6f7c8d8c2bdee4b6c5a47b60195b74e9b5280faa5ba29692f8e17429c" dependencies = [ "arbitrary", "crc32fast", "data-encoding", - "impls", "serde", - "serde_cbor", - "serde_json", - "serde_test", "sha2", "thiserror", ] [[package]] name = "ic_principal" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1762deb6f7c8d8c2bdee4b6c5a47b60195b74e9b5280faa5ba29692f8e17429c" +version = "0.1.3" dependencies = [ "arbitrary", "crc32fast", "data-encoding", + "impls", + "rangemap", + "schemars", "serde", + "serde_cbor", + "serde_json", + "serde_test", "sha2", "thiserror", ] @@ -1195,6 +1203,12 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rangemap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68" + [[package]] name = "redox_syscall" version = "0.5.3" @@ -1215,6 +1229,26 @@ dependencies = [ "thiserror", ] +[[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 1.0.86", + "quote 1.0.36", + "syn 2.0.87", +] + [[package]] name = "regex" version = "1.10.5" @@ -1269,12 +1303,6 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" -[[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" - [[package]] name = "same-file" version = "1.0.6" @@ -1284,6 +1312,31 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" +dependencies = [ + "proc-macro2 1.0.86", + "quote 1.0.36", + "serde_derive_internals", + "syn 2.0.87", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -1350,16 +1403,28 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2 1.0.86", + "quote 1.0.36", + "syn 2.0.87", +] + [[package]] name = "serde_json" -version = "1.0.121" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ab380d7d9f22ef3f21ad3e6c1ebe8e4fc7a2000ccba2e4d71fc96f15b2cb609" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", ] [[package]] @@ -1860,3 +1925,9 @@ name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/rust/bench/Cargo.lock b/rust/bench/Cargo.lock index 376eb8ef1..d6b0fc946 100644 --- a/rust/bench/Cargo.lock +++ b/rust/bench/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -19,9 +19,18 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "anyhow" -version = "1.0.99" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "ar_archive_writer" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b" +dependencies = [ + "object", +] [[package]] name = "arbitrary" @@ -109,9 +118,9 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitflags" -version = "2.9.4" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "block-buffer" @@ -153,14 +162,14 @@ dependencies = [ [[package]] name = "candid" -version = "0.10.20" +version = "0.10.29" dependencies = [ "anyhow", "binread", "byteorder", "candid_derive", "foldhash", - "hashbrown", + "hashbrown 0.15.5", "hex", "ic_principal", "leb128", @@ -176,17 +185,17 @@ dependencies = [ [[package]] name = "candid_derive" -version = "0.10.20" +version = "0.10.29" dependencies = [ "lazy_static", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "candid_parser" -version = "0.2.3" +version = "0.3.2" dependencies = [ "anyhow", "candid", @@ -206,9 +215,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.37" +version = "1.2.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65193589c6404eb80b450d618eaf9a2cafaaafd57ecce47370519ef674a7bd44" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ "find-msvc-tools", "shlex", @@ -216,9 +225,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "codespan-reporting" @@ -227,7 +236,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" dependencies = [ "termcolor", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] @@ -265,9 +274,9 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", @@ -294,7 +303,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -305,14 +314,14 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "data-encoding" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "derive_builder" @@ -332,7 +341,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -342,7 +351,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -399,9 +408,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "find-msvc-tools" -version = "0.1.1" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fixedbitset" @@ -433,9 +442,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", @@ -444,9 +453,9 @@ dependencies = [ [[package]] name = "handlebars" -version = "6.3.2" +version = "6.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759e2d5aea3287cb1190c8ec394f42866cb5bf74fcbf213f354e3c856ea26098" +checksum = "9b3f9296c208515b87bd915a2f5d1163d4b3f863ba83337d7713cf478055948e" dependencies = [ "derive_builder", "log", @@ -455,7 +464,7 @@ dependencies = [ "pest_derive", "serde", "serde_json", - "thiserror 2.0.16", + "thiserror 2.0.18", ] [[package]] @@ -469,6 +478,12 @@ dependencies = [ "foldhash", ] +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + [[package]] name = "hex" version = "0.4.3" @@ -506,7 +521,7 @@ dependencies = [ "quote", "serde", "serde_tokenstream", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -517,7 +532,7 @@ checksum = "8de254dd67bbd58073e23dc1c8553ba12fa1dc610a19de94ad2bbcd0460c067f" [[package]] name = "ic_principal" -version = "0.1.1" +version = "0.1.3" dependencies = [ "arbitrary", "crc32fast", @@ -535,13 +550,12 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" -version = "2.11.2" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24855426e2ca0eb0c416e7e1f548b11f7547805faeed2a3d3e961286b51f7d53" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown", - "serde", + "hashbrown 0.16.1", ] [[package]] @@ -555,9 +569,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "lalrpop" @@ -604,15 +618,15 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.175" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libredox" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags", "libc", @@ -620,19 +634,18 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "logos" @@ -655,7 +668,7 @@ dependencies = [ "proc-macro2", "quote", "regex-syntax", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -669,9 +682,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "new_debug_unreachable" @@ -723,11 +736,20 @@ dependencies = [ "autocfg", ] +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -735,15 +757,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-link", ] [[package]] @@ -754,20 +776,19 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pest" -version = "2.8.2" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e0a3a33733faeaf8651dfee72dd0f388f0c8e5ad496a3478fa5a922f49cfa8" +checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" dependencies = [ "memchr", - "thiserror 2.0.16", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.8.2" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc58706f770acb1dbd0973e6530a3cff4746fb721207feb3a8a6064cd0b6c663" +checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" dependencies = [ "pest", "pest_generator", @@ -775,22 +796,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.2" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d4f36811dfe07f7b8573462465d5cb8965fffc2e71ae377a33aecf14c2c9a2f" +checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "pest_meta" -version = "2.8.2" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42919b05089acbd0a5dcd5405fb304d17d1053847b81163d09c4ad18ce8e8420" +checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" dependencies = [ "pest", "sha2", @@ -829,47 +850,48 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "pretty" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac98773b7109bc75f475ab5a134c9b64b87e59d776d31098d8f346922396a477" +checksum = "0d22152487193190344590e4f30e219cf3fe140d9e7a3fdb683d82aa2c5f4156" dependencies = [ "arrayvec", "typed-arena", - "unicode-width", + "unicode-width 0.2.2", ] [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "psm" -version = "0.1.26" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e944464ec8536cd1beb0bbfd96987eb5e3b72f2ecdafdc5c769a37f1fa2ae1f" +checksum = "1fa96cb91275ed31d6da3e983447320c4eb219ac180fa1679a0889ff32861e2d" dependencies = [ + "ar_archive_writer", "cc", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] @@ -887,9 +909,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -899,9 +921,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.10" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -910,9 +932,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.6" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "rustversion" @@ -920,12 +942,6 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - [[package]] name = "same-file" version = "1.0.6" @@ -943,9 +959,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.224" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aaeb1e94f53b16384af593c71e20b095e958dab1d26939c1b70645c5cfbcc0b" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", @@ -963,35 +979,35 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.224" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f39390fa6346e24defbcdd3d9544ba8a19985d0af74df8501fbfe9a64341ab" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.224" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ff78ab5e8561c9a675bfc1785cb07ae721f0ee53329a595cefd8c04c2ac4e0" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -1012,7 +1028,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -1034,9 +1050,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "siphasher" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" [[package]] name = "smallvec" @@ -1046,9 +1062,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "stacker" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cddb07e32ddb770749da91081d8d0ac3a16f1a569a18b20348cd371f5dead06b" +checksum = "e1f8b29fb42aafcea4edeeb6b2f2d7ecd0d969c48b4cf0d2e64aafc471dd6e59" dependencies = [ "cc", "cfg-if", @@ -1088,9 +1104,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.106" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -1128,11 +1144,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.16", + "thiserror-impl 2.0.18", ] [[package]] @@ -1143,18 +1159,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -1208,9 +1224,9 @@ checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "ucd-trie" @@ -1220,9 +1236,9 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-segmentation" @@ -1236,6 +1252,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -1286,7 +1308,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -1297,9 +1319,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-link" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" @@ -1312,9 +1334,9 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.61.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] @@ -1385,9 +1407,15 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] + +[[package]] +name = "zmij" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" diff --git a/rust/bench/Cargo.toml b/rust/bench/Cargo.toml index fbe32c4cc..3ef135b3e 100644 --- a/rust/bench/Cargo.toml +++ b/rust/bench/Cargo.toml @@ -9,7 +9,7 @@ name = "bench" path = "bench.rs" [dependencies] -candid = { path = "../candid" } +candid = { path = "../candid", features = ["value"] } candid_parser = { path = "../candid_parser" } canbench-rs = "0.2.0" ic-cdk = "0.17" diff --git a/rust/bench/bench.rs b/rust/bench/bench.rs index 5f6e0272d..5aeb941c2 100644 --- a/rust/bench/bench.rs +++ b/rust/bench/bench.rs @@ -1,12 +1,14 @@ use canbench_rs::{bench, bench_fn, bench_scope, BenchResult}; -use candid::{CandidType, Decode, DecoderConfig, Deserialize, Encode, Int, Nat}; +use candid::{ + CandidType, Decode, DecoderConfig, Deserialize, Encode, IDLArgs, IDLValue, Int, Nat, Principal, +}; use std::collections::BTreeMap; #[allow(clippy::all)] mod nns; const N: usize = 2097152; -const COST: usize = 20_000_000; +const COST: usize = 25_000_000; const SKIP: usize = 10_000; #[bench(raw)] @@ -62,6 +64,57 @@ fn vec_int16() -> BenchResult { }) } +#[bench(raw)] +fn vec_nat() -> BenchResult { + let vec: Vec = (0u64..262144).map(Nat::from).collect(); + let mut config = DecoderConfig::new(); + config.set_decoding_quota(COST).set_skipping_quota(SKIP); + bench_fn(|| { + let bytes = { + let _p = bench_scope("1. Encoding"); + Encode!(&vec).unwrap() + }; + { + let _p = bench_scope("2. Decoding"); + Decode!([config]; &bytes, Vec).unwrap(); + } + }) +} + +#[bench(raw)] +fn vec_nat64() -> BenchResult { + let vec: Vec = (0u64..N as u64).collect(); + let mut config = DecoderConfig::new(); + config.set_decoding_quota(COST).set_skipping_quota(SKIP); + bench_fn(|| { + let bytes = { + let _p = bench_scope("1. Encoding"); + Encode!(&vec).unwrap() + }; + { + let _p = bench_scope("2. Decoding"); + Decode!([config]; &bytes, Vec).unwrap(); + } + }) +} + +#[bench(raw)] +fn vec_nat32() -> BenchResult { + let vec: Vec = (0u32..N as u32).collect(); + let mut config = DecoderConfig::new(); + config.set_decoding_quota(COST).set_skipping_quota(SKIP); + bench_fn(|| { + let bytes = { + let _p = bench_scope("1. Encoding"); + Encode!(&vec).unwrap() + }; + { + let _p = bench_scope("2. Decoding"); + Decode!([config]; &bytes, Vec).unwrap(); + } + }) +} + #[bench(raw)] fn btreemap() -> BenchResult { let mut config = DecoderConfig::new(); @@ -284,4 +337,588 @@ fn extra_args() -> BenchResult { }) } +// Vec of fully populated complex structs (21 fields, nested vecs/maps/opts) +// Exercises the dominant real-world payload shape: list endpoints returning large records. +// The existing nns_list_proposal benchmark uses mostly-empty ProposalInfo; this uses +// fully-populated Neurons which are ~5-10x heavier per element. +#[bench(raw)] +fn nns_list_neurons() -> BenchResult { + use crate::nns::*; + let mut config = DecoderConfig::new(); + config.set_decoding_quota(COST).set_skipping_quota(SKIP); + + let make_neuron = |i: u64| Neuron { + id: Some(NeuronId { id: i }), + staked_maturity_e8s_equivalent: Some(1_000_000), + controller: Some(Principal::from_slice(&i.to_be_bytes())), + recent_ballots: (0..100) + .map(|j| BallotInfo { + vote: if j % 2 == 0 { 1 } else { 2 }, + proposal_id: Some(NeuronId { id: j }), + }) + .collect(), + kyc_verified: true, + neuron_type: Some(1), + not_for_profit: false, + maturity_e8s_equivalent: 500_000, + cached_neuron_stake_e8s: 10_000_000_000, + created_timestamp_seconds: 1_700_000_000 + i, + auto_stake_maturity: Some(true), + aging_since_timestamp_seconds: 1_700_000_000, + hot_keys: (0..5) + .map(|j| Principal::from_slice(&[j as u8; 10])) + .collect(), + account: serde_bytes::ByteBuf::from(vec![i as u8; 32]), + joined_community_fund_timestamp_seconds: Some(1_700_000_000), + dissolve_state: Some(DissolveState::DissolveDelaySeconds(15_778_800)), + followees: (0..10) + .map(|topic| { + ( + topic, + Followees { + followees: (0..5).map(|f| NeuronId { id: f as u64 }).collect(), + }, + ) + }) + .collect(), + neuron_fees_e8s: 10_000, + transfer: Some(NeuronStakeTransfer { + to_subaccount: serde_bytes::ByteBuf::from(vec![0u8; 32]), + neuron_stake_e8s: 10_000_000_000, + from: Some(Principal::from_slice(&[1u8; 10])), + memo: 42, + from_subaccount: serde_bytes::ByteBuf::from(vec![0u8; 32]), + transfer_timestamp: 1_700_000_000, + block_height: 1_000_000, + }), + known_neuron_data: Some(KnownNeuronData { + name: format!("neuron-{}", i), + description: Some(format!("A known neuron #{}", i)), + }), + spawn_at_timestamp_seconds: None, + }; + + let neuron_infos: Vec<(u64, nns::NeuronInfo)> = (0..100) + .map(|i| { + ( + i, + nns::NeuronInfo { + dissolve_delay_seconds: 15_778_800, + recent_ballots: (0..100) + .map(|j| BallotInfo { + vote: 1, + proposal_id: Some(NeuronId { id: j }), + }) + .collect(), + neuron_type: Some(1), + created_timestamp_seconds: 1_700_000_000, + state: 1, + stake_e8s: 10_000_000_000, + joined_community_fund_timestamp_seconds: Some(1_700_000_000), + retrieved_at_timestamp_seconds: 1_700_000_000, + known_neuron_data: Some(KnownNeuronData { + name: format!("neuron-{}", i), + description: Some(format!("Known neuron #{}", i)), + }), + voting_power: 20_000_000_000, + age_seconds: 31_557_600, + }, + ) + }) + .collect(); + + let response = nns::ListNeuronsResponse { + neuron_infos, + full_neurons: (0..100).map(make_neuron).collect(), + }; + + bench_fn(|| { + let bytes = { + let _p = bench_scope("1. Encoding"); + Encode!(&response).unwrap() + }; + { + let _p = bench_scope("2. Decoding"); + Decode!([config]; &bytes, nns::ListNeuronsResponse).unwrap(); + } + }) +} + +// Schema evolution — encode with a newer type (16 fields), decode with an older +// type (4 fields). Forces the decoder to skip 12 unknown fields per record, exercising +// the field-skipping mechanism that has zero coverage in the existing benchmark. +#[bench(raw)] +fn subtype_decode() -> BenchResult { + #[derive(CandidType)] + struct RecordV2 { + id: u64, + name: String, + balance: u64, + active: bool, + score: f64, + owner: Principal, + data: serde_bytes::ByteBuf, + count: u32, + extra_field_1: String, + extra_field_2: u64, + extra_field_3: Option, + extra_field_4: Vec, + extra_field_5: bool, + extra_field_6: f64, + extra_field_7: Principal, + extra_field_8: u32, + } + + #[derive(CandidType, Deserialize)] + struct RecordV1 { + id: u64, + name: String, + balance: u64, + active: bool, + } + + let mut config = DecoderConfig::new(); + config.set_decoding_quota(COST); + + let records: Vec = (0..1000) + .map(|i| RecordV2 { + id: i as u64, + name: format!("record-{}", i), + balance: i as u64 * 1000, + active: i % 2 == 0, + score: i as f64 * 1.5, + owner: Principal::from_slice(&(i as u32).to_be_bytes()), + data: serde_bytes::ByteBuf::from(vec![i as u8; 64]), + count: i as u32, + extra_field_1: format!("extra-{}", i), + extra_field_2: i as u64 * 42, + extra_field_3: Some(format!("optional-{}", i)), + extra_field_4: vec![i as u8; 16], + extra_field_5: i % 3 == 0, + extra_field_6: i as f64 * 2.7, + extra_field_7: Principal::from_slice(&(i as u32 + 1000).to_be_bytes()), + extra_field_8: i as u32 * 7, + }) + .collect(); + + bench_fn(|| { + let bytes = { + let _p = bench_scope("1. Encoding"); + Encode!(&records).unwrap() + }; + { + let _p = bench_scope("2. Decoding"); + Decode!([config]; &bytes, Vec).unwrap(); + } + }) +} + +// Vec of service references (subscriber/registry collection pattern). +// This is the ONLY decode path where check_subtype() fires (deserialize_service +// and deserialize_function). The existing benchmark has zero coverage of this +// code path. See https://github.com/dfinity/candid/issues/603 +#[bench(raw)] +fn vec_service() -> BenchResult { + use candid::types::{ArgType, Function, TypeEnv, TypeInner}; + + let mut config = DecoderConfig::new(); + config.set_decoding_quota(COST); + + let method_type: candid::types::Type = TypeInner::Func(Function { + modes: vec![], + args: vec![ArgType { + name: None, + typ: TypeInner::Text.into(), + }], + rets: vec![ArgType { + name: None, + typ: TypeInner::Nat64.into(), + }], + }) + .into(); + + let methods: Vec<(String, candid::types::Type)> = (0..20) + .map(|i| (format!("method_{:02}", i), method_type.clone())) + .collect(); + + let service_type: candid::types::Type = TypeInner::Service(methods).into(); + let vec_service_type: candid::types::Type = TypeInner::Vec(service_type).into(); + + let services: Vec = (0..1000) + .map(|i| { + let bytes = (i as u64).to_be_bytes(); + IDLValue::Service(Principal::from_slice(&bytes)) + }) + .collect(); + + let args = IDLArgs::new(&[IDLValue::Vec(services)]); + let env = TypeEnv::new(); + let types = vec![vec_service_type.clone()]; + + bench_fn(|| { + let bytes = { + let _p = bench_scope("1. Encoding"); + args.to_bytes_with_types(&env, &types).unwrap() + }; + { + let _p = bench_scope("2. Decoding"); + IDLArgs::from_bytes_with_types_with_config(&bytes, &env, &types, &config).unwrap(); + } + }) +} + +// Ok/Err discriminated union — the canonical ICP canister return type. +// Every canister method returns variant { Ok: Record; Err: ErrorEnum }. +// Tests heterogeneous variant payloads (struct vs enum) which the existing +// variant_list benchmark (homogeneous recursive) does not cover. +#[bench(raw)] +fn result_variant() -> BenchResult { + #[derive(CandidType, Deserialize)] + struct AccountResponse { + account: Principal, + balance_real: i128, + balance_fake: i128, + balance_ledger: i128, + last_update: u64, + overdraft_limit: u128, + name: String, + } + + #[derive(CandidType, Deserialize)] + enum AccountError { + NotAuthorized(Principal), + AccountNotFound, + InsufficientBalance { needed: u128, available: i128 }, + InternalError(String), + } + + #[derive(CandidType, Deserialize)] + enum AccountResult { + Ok(AccountResponse), + Err(AccountError), + } + + let mut config = DecoderConfig::new(); + config.set_decoding_quota(COST).set_skipping_quota(SKIP); + + let results: Vec = (0..1000) + .map(|i| { + if i % 4 == 0 { + AccountResult::Err(AccountError::InsufficientBalance { + needed: i as u128 * 1000, + available: i as i128 * 100, + }) + } else if i % 4 == 1 { + AccountResult::Err(AccountError::NotAuthorized(Principal::from_slice( + &(i as u32).to_be_bytes(), + ))) + } else { + AccountResult::Ok(AccountResponse { + account: Principal::from_slice(&(i as u32).to_be_bytes()), + balance_real: i as i128 * 1_000_000, + balance_fake: 0, + balance_ledger: i as i128 * 1_000_000, + last_update: 1_700_000_000 + i as u64, + overdraft_limit: 10_000_000_000, + name: format!("account-{}", i), + }) + } + }) + .collect(); + + bench_fn(|| { + let bytes = { + let _p = bench_scope("1. Encoding"); + Encode!(&results).unwrap() + }; + { + let _p = bench_scope("2. Decoding"); + Decode!([config]; &bytes, Vec).unwrap(); + } + }) +} + +// GovernanceCachedMetrics — 34+ fields including ~10 bucket maps of +// vec record { nat64; float64 }. Tests field hash matching overhead at scale +// and exercises float64 encoding (not covered elsewhere). +#[bench(raw)] +fn wide_record() -> BenchResult { + use crate::nns::GovernanceCachedMetrics; + let mut config = DecoderConfig::new(); + config.set_decoding_quota(COST).set_skipping_quota(SKIP); + + let buckets: Vec<(u64, f64)> = (0..20) + .map(|i| (i * 15_778_800, i as f64 * 1_000_000.0)) + .collect(); + let count_buckets: Vec<(u64, u64)> = (0..20).map(|i| (i * 15_778_800, i * 100)).collect(); + + let metrics = GovernanceCachedMetrics { + total_maturity_e8s_equivalent: 50_000_000_000_000, + not_dissolving_neurons_e8s_buckets: buckets.clone(), + dissolving_neurons_staked_maturity_e8s_equivalent_sum: 1_000_000_000, + garbage_collectable_neurons_count: 42, + dissolving_neurons_staked_maturity_e8s_equivalent_buckets: buckets.clone(), + neurons_with_invalid_stake_count: 3, + not_dissolving_neurons_count_buckets: count_buckets.clone(), + ect_neuron_count: 100, + total_supply_icp: 500_000_000_000_000_000, + neurons_with_less_than_6_months_dissolve_delay_count: 5_000, + dissolved_neurons_count: 10_000, + community_fund_total_maturity_e8s_equivalent: 1_000_000_000_000, + total_staked_e8s_seed: 2_000_000_000_000, + total_staked_maturity_e8s_equivalent_ect: 500_000_000, + total_staked_e8s: 100_000_000_000_000_000, + not_dissolving_neurons_count: 30_000, + total_locked_e8s: 80_000_000_000_000_000, + neurons_fund_total_active_neurons: 200, + total_staked_maturity_e8s_equivalent: 5_000_000_000_000, + not_dissolving_neurons_e8s_buckets_ect: buckets.clone(), + total_staked_e8s_ect: 3_000_000_000_000, + not_dissolving_neurons_staked_maturity_e8s_equivalent_sum: 2_000_000_000, + dissolved_neurons_e8s: 500_000_000_000, + dissolving_neurons_e8s_buckets_seed: buckets.clone(), + neurons_with_less_than_6_months_dissolve_delay_e8s: 1_000_000_000_000, + not_dissolving_neurons_staked_maturity_e8s_equivalent_buckets: buckets.clone(), + dissolving_neurons_count_buckets: count_buckets.clone(), + dissolving_neurons_e8s_buckets_ect: buckets.clone(), + dissolving_neurons_count: 15_000, + dissolving_neurons_e8s_buckets: buckets.clone(), + total_staked_maturity_e8s_equivalent_seed: 700_000_000, + community_fund_total_staked_e8s: 5_000_000_000_000, + not_dissolving_neurons_e8s_buckets_seed: buckets, + timestamp_seconds: 1_700_000_000, + seed_neuron_count: 500, + }; + + let metrics_vec: Vec = std::iter::repeat(metrics).take(100).collect(); + + bench_fn(|| { + let bytes = { + let _p = bench_scope("1. Encoding"); + Encode!(&metrics_vec).unwrap() + }; + { + let _p = bench_scope("2. Decoding"); + Decode!([config]; &bytes, Vec).unwrap(); + } + }) +} + +// 21-arm enum with mixed payload shapes (unit, tuple, struct variants). +// The existing variant_list only tests a 2-arm recursive variant. This tests +// variant tag matching overhead at scale with heterogeneous payloads. +#[bench(raw)] +fn large_variant() -> BenchResult { + #[derive(CandidType, Deserialize, Clone)] + enum LargeAction { + Transfer { + from: Principal, + to: Principal, + amount: u64, + memo: Option, + }, + Approve { + spender: Principal, + amount: u64, + expires_at: Option, + }, + Burn { + amount: u64, + }, + Mint { + to: Principal, + amount: u64, + }, + SetFee(u64), + SetAdmin(Principal), + Pause, + Unpause, + Upgrade(serde_bytes::ByteBuf), + AddMinter(Principal), + RemoveMinter(Principal), + SetName(String), + SetSymbol(String), + SetLogo(String), + SetMetadata { + key: String, + value: String, + }, + CreateProposal { + title: String, + summary: String, + url: String, + }, + Vote { + proposal_id: u64, + vote: bool, + }, + Execute(u64), + Reject(u64), + RegisterNeuron { + stake: u64, + dissolve_delay: u64, + }, + DisburseNeuron { + neuron_id: u64, + to: Principal, + amount: Option, + }, + } + + let mut config = DecoderConfig::new(); + config.set_decoding_quota(COST).set_skipping_quota(SKIP); + + let actions: Vec = (0..2100) + .map(|i| { + let p = Principal::from_slice(&(i as u32).to_be_bytes()); + match i % 21 { + 0 => LargeAction::Transfer { + from: p, + to: p, + amount: i as u64 * 100, + memo: Some(42), + }, + 1 => LargeAction::Approve { + spender: p, + amount: i as u64, + expires_at: Some(1_700_000_000), + }, + 2 => LargeAction::Burn { + amount: i as u64 * 50, + }, + 3 => LargeAction::Mint { + to: p, + amount: i as u64 * 1000, + }, + 4 => LargeAction::SetFee(i as u64), + 5 => LargeAction::SetAdmin(p), + 6 => LargeAction::Pause, + 7 => LargeAction::Unpause, + 8 => LargeAction::Upgrade(serde_bytes::ByteBuf::from(vec![i as u8; 32])), + 9 => LargeAction::AddMinter(p), + 10 => LargeAction::RemoveMinter(p), + 11 => LargeAction::SetName(format!("name-{}", i)), + 12 => LargeAction::SetSymbol(format!("SYM{}", i)), + 13 => LargeAction::SetLogo(format!("https://example.com/logo-{}.png", i)), + 14 => LargeAction::SetMetadata { + key: format!("key-{}", i), + value: format!("val-{}", i), + }, + 15 => LargeAction::CreateProposal { + title: format!("Proposal {}", i), + summary: format!("Summary for {}", i), + url: format!("https://example.com/{}", i), + }, + 16 => LargeAction::Vote { + proposal_id: i as u64, + vote: i % 2 == 0, + }, + 17 => LargeAction::Execute(i as u64), + 18 => LargeAction::Reject(i as u64), + 19 => LargeAction::RegisterNeuron { + stake: i as u64 * 100_000_000, + dissolve_delay: 15_778_800, + }, + _ => LargeAction::DisburseNeuron { + neuron_id: i as u64, + to: p, + amount: Some(i as u64 * 100), + }, + } + }) + .collect(); + + bench_fn(|| { + let bytes = { + let _p = bench_scope("1. Encoding"); + Encode!(&actions).unwrap() + }; + { + let _p = bench_scope("2. Decoding"); + Decode!([config]; &bytes, Vec).unwrap(); + } + }) +} + +// Option> — "3-state update" semantics used in real ICP APIs. +// None = don't change, Some(None) = clear, Some(Some(x)) = set. +// Exercises Candid's opt subtyping rules with double-wrapped optionals. +#[bench(raw)] +fn double_option() -> BenchResult { + #[derive(CandidType, Deserialize)] + struct UpdateRequest { + target_balance: Option>, + expiration: Option>, + spending_limit: Option>, + description: Option>, + account: Principal, + } + + let mut config = DecoderConfig::new(); + config.set_decoding_quota(COST).set_skipping_quota(SKIP); + + let requests: Vec = (0..1000) + .map(|i| match i % 3 { + 0 => UpdateRequest { + target_balance: None, + expiration: None, + spending_limit: None, + description: None, + account: Principal::from_slice(&(i as u32).to_be_bytes()), + }, + 1 => UpdateRequest { + target_balance: Some(None), + expiration: Some(None), + spending_limit: Some(None), + description: Some(None), + account: Principal::from_slice(&(i as u32).to_be_bytes()), + }, + _ => UpdateRequest { + target_balance: Some(Some(i as i128 * 1_000_000)), + expiration: Some(Some(1_700_000_000 + i as u64)), + spending_limit: Some(Some(i as u128 * 500)), + description: Some(Some(format!("update-{}", i))), + account: Principal::from_slice(&(i as u32).to_be_bytes()), + }, + }) + .collect(); + + bench_fn(|| { + let bytes = { + let _p = bench_scope("1. Encoding"); + Encode!(&requests).unwrap() + }; + { + let _p = bench_scope("2. Decoding"); + Decode!([config]; &bytes, Vec).unwrap(); + } + }) +} + +// Multi-argument Encode!/Decode! — real canister calls often pass multiple +// arguments (e.g., principal + amount + memo). This exercises a different +// internal code path than single-struct encoding. +#[bench(raw)] +fn multi_arg() -> BenchResult { + let mut config = DecoderConfig::new(); + config.set_decoding_quota(COST).set_skipping_quota(SKIP); + + let principals: Vec = (0..1000) + .map(|i| Principal::from_slice(&(i as u64).to_be_bytes())) + .collect(); + let amounts: Vec = (0..1000).map(|i| i * 1_000_000).collect(); + let memos: Vec = (0..1000).map(|i| format!("memo-{}", i)).collect(); + + bench_fn(|| { + let bytes = { + let _p = bench_scope("1. Encoding"); + Encode!(&principals, &amounts, &memos).unwrap() + }; + { + let _p = bench_scope("2. Decoding"); + Decode!([config]; &bytes, Vec, Vec, Vec).unwrap(); + } + }) +} + fn main() {} diff --git a/rust/candid/Cargo.toml b/rust/candid/Cargo.toml index cadf07b13..038a5eace 100644 --- a/rust/candid/Cargo.toml +++ b/rust/candid/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "candid" # sync with the version in `candid_derive/Cargo.toml` -version = "0.10.20" +version = "0.10.29" edition = "2021" rust-version.workspace = true authors = ["DFINITY Team"] @@ -16,7 +16,7 @@ keywords = ["internet-computer", "idl", "candid", "dfinity"] include = ["src", "Cargo.toml", "LICENSE", "README.md"] [dependencies] -candid_derive = { path = "../candid_derive", version = "=0.10.20" } +candid_derive = { path = "../candid_derive", version = "=0.10.29" } ic_principal = { path = "../ic_principal", version = "0.1.0" } binread = { version = "2.2", features = ["debug_template"] } byteorder = "1.5.0" @@ -34,7 +34,7 @@ pretty = { workspace = true, optional = true } num-bigint = { workspace = true, optional = true } num-traits = { workspace = true, optional = true } -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +[target.'cfg(not(target_family = "wasm"))'.dependencies] stacker = "0.1" [dev-dependencies] diff --git a/rust/candid/fuzz/Cargo.lock b/rust/candid/fuzz/Cargo.lock index 59f1cd8bc..69cc5fbaf 100644 --- a/rust/candid/fuzz/Cargo.lock +++ b/rust/candid/fuzz/Cargo.lock @@ -1,6 +1,12 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "anyhow" @@ -66,12 +72,14 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "candid" -version = "0.10.8" +version = "0.10.29" dependencies = [ "anyhow", "binread", "byteorder", "candid_derive", + "foldhash", + "hashbrown", "hex", "ic_principal", "leb128", @@ -97,7 +105,7 @@ dependencies = [ [[package]] name = "candid_derive" -version = "0.6.6" +version = "0.10.29" dependencies = [ "lazy_static", "proc-macro2", @@ -172,6 +180,18 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "generic-array" version = "0.14.7" @@ -182,6 +202,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "hex" version = "0.4.3" @@ -190,7 +221,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "ic_principal" -version = "0.1.1" +version = "0.1.3" dependencies = [ "arbitrary", "crc32fast", diff --git a/rust/candid/src/binary_parser.rs b/rust/candid/src/binary_parser.rs index 1226f0272..545701161 100644 --- a/rust/candid/src/binary_parser.rs +++ b/rust/candid/src/binary_parser.rs @@ -6,6 +6,8 @@ use binread::io::{Read, Seek}; use binread::{BinRead, BinResult, Error as BError, ReadOptions}; use std::convert::TryInto; +const MAX_TYPE_TABLE_LEN: u64 = 10_000; // Max type entries + fn read_leb(reader: &mut R, ro: &ReadOptions, (): ()) -> BinResult { let pos = reader.stream_position()?; leb128::read::unsigned(reader).map_err(|_| BError::Custom { @@ -23,16 +25,21 @@ fn read_sleb(reader: &mut R, ro: &ReadOptions, (): ()) -> BinRes #[derive(BinRead, Debug)] #[br(magic = b"DIDL")] +#[br(import(max_type_len: Option))] pub struct Header { + #[br(args(max_type_len))] table: Table, #[br(parse_with = read_leb)] len: u64, #[br(count = len)] args: Vec, } + #[derive(BinRead, Debug)] +#[br(import(max_type_len: Option))] struct Table { - #[br(parse_with = read_leb, assert(len <= i64::MAX as u64, "type table size out of range"))] + #[br(parse_with = read_leb)] + #[br(assert(len <= max_type_len.unwrap_or(MAX_TYPE_TABLE_LEN as usize) as u64, "type table size exceeded"))] len: u64, #[br(count = len)] table: Vec, diff --git a/rust/candid/src/de.rs b/rust/candid/src/de.rs index ff4f20b72..f18a334be 100644 --- a/rust/candid/src/de.rs +++ b/rust/candid/src/de.rs @@ -9,7 +9,7 @@ use super::{ #[cfg(feature = "bignum")] use super::{Int, Nat}; use crate::{ - binary_parser::{BoolValue, Header, Len, PrincipalBytes}, + binary_parser::{Header, Len, PrincipalBytes}, types::subtype::{subtype_with_config, Gamma, OptReport}, }; use anyhow::{anyhow, Context}; @@ -67,7 +67,10 @@ impl<'de> IDLDeserialize<'de> { where T: de::Deserialize<'de> + CandidType, { - let expected_type = self.de.table.trace_type(&expected_type)?; + let expected_type = self + .de + .table + .trace_type_with_depth(&expected_type, &self.de.recursion_depth)?; if self.de.types.is_empty() { if matches!( expected_type.as_ref(), @@ -143,6 +146,7 @@ impl<'de> IDLDeserialize<'de> { pub struct DecoderConfig { pub decoding_quota: Option, pub skipping_quota: Option, + pub max_type_len: Option, full_error_message: bool, } impl DecoderConfig { @@ -153,6 +157,7 @@ impl DecoderConfig { Self { decoding_quota: None, skipping_quota: None, + max_type_len: None, #[cfg(not(target_arch = "wasm32"))] full_error_message: true, #[cfg(target_arch = "wasm32")] @@ -212,6 +217,11 @@ impl DecoderConfig { self.skipping_quota = Some(n); self } + /// Set the max type table size + pub fn set_max_type_len(&mut self, n: usize) -> &mut Self { + self.max_type_len = Some(n); + self + } /// When set to false, error message only displays the concrete type when the type is small. /// The error message also doesn't include the decoding states. /// When set to true, error message always shows the full type and decoding states. @@ -230,6 +240,7 @@ impl DecoderConfig { Self { decoding_quota, skipping_quota, + max_type_len: original.max_type_len, full_error_message: original.full_error_message, } } @@ -273,27 +284,6 @@ macro_rules! check { } }}; } -#[cfg(not(target_arch = "wasm32"))] -macro_rules! check_recursion { - ($this:ident $($body:tt)*) => { - $this.recursion_depth += 1; - match stacker::remaining_stack() { - Some(size) if size < 32768 => return Err(Error::msg(format!("Recursion limit exceeded at depth {}", $this.recursion_depth))), - None if $this.recursion_depth > 512 => return Err(Error::msg(format!("Recursion limit exceeded at depth {}. Cannot detect stack size, use a conservative bound", $this.recursion_depth))), - _ => (), - } - let __ret = { $this $($body)* }; - $this.recursion_depth -= 1; - __ret - }; -} -// No need to check recursion depth for wasm32, because canisters are running in a sandbox -#[cfg(target_arch = "wasm32")] -macro_rules! check_recursion { - ($this:ident $($body:tt)*) => { - $this $($body)* - }; -} #[derive(Clone)] struct Deserializer<'de> { @@ -311,14 +301,17 @@ struct Deserializer<'de> { // It only affects the field id generation in enum type. is_untyped: bool, config: DecoderConfig, - #[cfg(not(target_arch = "wasm32"))] - recursion_depth: u16, + recursion_depth: crate::utils::RecursionDepth, + primitive_vec_fast_path: Option, + #[cfg(feature = "bignum")] + bignum_vec_fast_path: Option, + text_fast_path: bool, } impl<'de> Deserializer<'de> { fn from_bytes(bytes: &'de [u8], config: &DecoderConfig) -> Result { let mut reader = Cursor::new(bytes); - let header = Header::read(&mut reader)?; + let header = Header::read_args(&mut reader, (config.max_type_len,))?; let (env, types) = header.to_types()?; Ok(Deserializer { input: reader, @@ -330,8 +323,11 @@ impl<'de> Deserializer<'de> { field_name: None, is_untyped: false, config: config.clone(), - #[cfg(not(target_arch = "wasm32"))] - recursion_depth: 0, + recursion_depth: crate::utils::RecursionDepth::new(), + primitive_vec_fast_path: None, + #[cfg(feature = "bignum")] + bignum_vec_fast_path: None, + text_fast_path: false, }) } fn dump_state(&self) -> String { @@ -353,6 +349,90 @@ impl<'de> Deserializer<'de> { } res } + #[inline] + fn read_leb_u64(&mut self) -> Result { + self.try_read_leb_u64()? + .ok_or_else(|| Error::msg("LEB128 overflow")) + } + /// Returns `Ok(None)` on overflow (value may not fit in u64), `Err` on I/O error (e.g. EOF). + /// This lets callers fall through to a bignum path on overflow without swallowing real errors. + /// + /// The fast path covers up to 9 LEB128 bytes (values that fit in 63 bits, i.e. < 2^63). + /// Larger values bail to the bignum path, which keeps this hot loop simple — values needing + /// the 10th byte may or may not fit in u64, and the boundary check would just slow it down. + #[inline] + fn try_read_leb_u64(&mut self) -> Result> { + let slice = self.input.get_ref(); + let mut pos = self.input.position() as usize; + let end = slice.len(); + let mut result: u64 = 0; + let mut shift: u32 = 0; + loop { + if pos >= end { + return Err(Error::msg("unexpected end of LEB128")); + } + let byte = slice[pos]; + pos += 1; + result |= ((byte & 0x7f) as u64) << shift; + if byte & 0x80 == 0 { + self.input.set_position(pos as u64); + return Ok(Some(result)); + } + shift += 7; + if shift >= 63 { + return Ok(None); + } + } + } + /// Returns `Ok(None)` on overflow (value may not fit in i64), `Err` on I/O error (e.g. EOF). + /// This lets callers fall through to a bignum path on overflow without swallowing real errors. + /// + /// The fast path covers up to 9 LEB128 bytes (values in roughly -2^62 .. 2^62). Larger + /// values bail to the bignum path, keeping this hot loop simple. + #[inline] + fn try_read_leb_i64(&mut self) -> Result> { + let slice = self.input.get_ref(); + let mut pos = self.input.position() as usize; + let end = slice.len(); + let mut result: i64 = 0; + let mut shift: u32 = 0; + let mut byte; + loop { + if pos >= end { + return Err(Error::msg("unexpected end of LEB128")); + } + byte = slice[pos]; + pos += 1; + result |= ((byte & 0x7f) as i64) << shift; + shift += 7; + if byte & 0x80 == 0 { + break; + } + if shift >= 63 { + return Ok(None); + } + } + if byte & 0x40 != 0 { + result |= !0i64 << shift; + } + self.input.set_position(pos as u64); + Ok(Some(result)) + } + #[inline] + fn read_len(&mut self) -> Result { + let val = self.read_leb_u64()?; + usize::try_from(val).map_err(|_| Error::msg("length out of usize range")) + } + #[inline] + fn read_bool_val(&mut self) -> Result { + let byte = self.input.read_u8()?; + match byte { + 0 => Ok(false), + 1 => Ok(true), + _ => Err(Error::msg("Expect 00 or 01")), + } + } + #[inline] fn borrow_bytes(&mut self, len: usize) -> Result<&'de [u8]> { let pos = self.input.position() as usize; let slice = self.input.get_ref(); @@ -389,23 +469,29 @@ impl<'de> Deserializer<'de> { .map_err(Error::subtype)?; Ok(()) } + #[inline] fn unroll_type(&mut self) -> Result<()> { if matches!( self.expect_type.as_ref(), TypeInner::Var(_) | TypeInner::Knot(_) ) { self.add_cost(1)?; - self.expect_type = self.table.trace_type(&self.expect_type)?; + self.expect_type = self + .table + .trace_type_with_depth(&self.expect_type, &self.recursion_depth)?; } if matches!( self.wire_type.as_ref(), TypeInner::Var(_) | TypeInner::Knot(_) ) { self.add_cost(1)?; - self.wire_type = self.table.trace_type(&self.wire_type)?; + self.wire_type = self + .table + .trace_type_with_depth(&self.wire_type, &self.recursion_depth)?; } Ok(()) } + #[inline] fn add_cost(&mut self, cost: usize) -> Result<()> { if let Some(n) = self.config.decoding_quota { let cost = if self.is_untyped { @@ -448,16 +534,50 @@ impl<'de> Deserializer<'de> { where V: Visitor<'de>, { - self.unroll_type()?; - assert!(*self.expect_type == TypeInner::Int); + if self.bignum_vec_fast_path.is_none() { + self.unroll_type()?; + assert!(*self.expect_type == TypeInner::Int); + } + if !self.is_untyped { + let is_nat = matches!(self.wire_type.as_ref(), TypeInner::Nat); + let is_int = matches!(self.wire_type.as_ref(), TypeInner::Int); + if is_int { + let pos = self.input.position(); + match self.try_read_leb_i64()? { + Some(value) => { + self.add_cost((self.input.position() - pos) as usize)?; + return visitor.visit_i64(value); + } + None => { + self.input.set_position(pos); + } + } + } else if is_nat { + let pos = self.input.position(); + match self.try_read_leb_u64()? { + Some(value) => { + self.add_cost((self.input.position() - pos) as usize)?; + return visitor.visit_u64(value); + } + None => { + self.input.set_position(pos); + } + } + } else { + return Err(Error::subtype(format!( + "{} cannot be deserialized to int", + self.wire_type + ))); + } + } + let bignum_pos = self.input.position(); let mut bytes = vec![0u8]; - let pos = self.input.position(); let int = match self.wire_type.as_ref() { TypeInner::Int => Int::decode(&mut self.input).map_err(Error::msg)?, TypeInner::Nat => Int(Nat::decode(&mut self.input).map_err(Error::msg)?.0.into()), t => return Err(Error::subtype(format!("{t} cannot be deserialized to int"))), }; - self.add_cost((self.input.position() - pos) as usize)?; + self.add_cost((self.input.position() - bignum_pos) as usize)?; bytes.extend_from_slice(&int.0.to_signed_bytes_le()); visitor.visit_byte_buf(bytes) } @@ -467,13 +587,27 @@ impl<'de> Deserializer<'de> { where V: Visitor<'de>, { - self.unroll_type()?; - check!( - *self.expect_type == TypeInner::Nat && *self.wire_type == TypeInner::Nat, - "nat" - ); - let mut bytes = vec![1u8]; + if self.bignum_vec_fast_path.is_none() { + self.unroll_type()?; + check!( + *self.expect_type == TypeInner::Nat && *self.wire_type == TypeInner::Nat, + "nat" + ); + } + if !self.is_untyped { + let pos = self.input.position(); + match self.try_read_leb_u64()? { + Some(value) => { + self.add_cost((self.input.position() - pos) as usize)?; + return visitor.visit_u64(value); + } + None => { + self.input.set_position(pos); + } + } + } let pos = self.input.position(); + let mut bytes = vec![1u8]; let nat = Nat::decode(&mut self.input).map_err(Error::msg)?; self.add_cost((self.input.position() - pos) as usize)?; bytes.extend_from_slice(&nat.0.to_bytes_le()); @@ -520,12 +654,12 @@ impl<'de> Deserializer<'de> { { self.unroll_type()?; self.check_subtype()?; - if !BoolValue::read(&mut self.input)?.0 { + if !self.read_bool_val()? { return Err(Error::msg("Opaque reference not supported")); } let mut bytes = vec![5u8]; let id = PrincipalBytes::read(&mut self.input)?; - let len = Len::read(&mut self.input)?.0; + let len = self.read_len()?; let meth = self.borrow_bytes(len)?; self.add_cost( std::cmp::max(30, id.len as usize) @@ -547,7 +681,7 @@ impl<'de> Deserializer<'de> { self.expect_type.is_blob(&self.table) && self.wire_type.is_blob(&self.table), "blob" ); - let len = Len::read(&mut self.input)?.0; + let len = self.read_len()?; self.add_cost(len.saturating_add(1))?; let blob = self.borrow_bytes(len)?; let mut bytes = Vec::with_capacity(len + 1); @@ -569,9 +703,9 @@ impl<'de> Deserializer<'de> { where V: Visitor<'de>, { - let len = Len::read(&mut self.input)?.0 as u64; + let len = self.read_len()? as u64; self.add_cost((len as usize).saturating_add(1))?; - Len::read(&mut self.input)?; + self.read_len()?; let slice_len = self.input.get_ref().len() as u64; let pos = self.input.position(); if len > slice_len || pos + len > slice_len { @@ -624,11 +758,64 @@ impl<'de> Deserializer<'de> { } } +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +enum PrimitiveType { + Bool, + Int8, + Int16, + Int32, + Int64, + Nat8, + Nat16, + Nat32, + Nat64, + Float32, + Float64, +} + +fn primitive_byte_cost(p: PrimitiveType) -> usize { + match p { + PrimitiveType::Bool | PrimitiveType::Int8 | PrimitiveType::Nat8 => 1, + PrimitiveType::Int16 | PrimitiveType::Nat16 => 2, + PrimitiveType::Int32 | PrimitiveType::Nat32 | PrimitiveType::Float32 => 4, + PrimitiveType::Int64 | PrimitiveType::Nat64 | PrimitiveType::Float64 => 8, + } +} + +#[cfg(feature = "bignum")] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +enum BigNumFastPath { + Nat, + Int, + NatAsInt, +} + +fn exact_primitive_type(expect: &Type, wire: &Type) -> Option { + match (expect.as_ref(), wire.as_ref()) { + (TypeInner::Bool, TypeInner::Bool) => Some(PrimitiveType::Bool), + (TypeInner::Int8, TypeInner::Int8) => Some(PrimitiveType::Int8), + (TypeInner::Int16, TypeInner::Int16) => Some(PrimitiveType::Int16), + (TypeInner::Int32, TypeInner::Int32) => Some(PrimitiveType::Int32), + (TypeInner::Int64, TypeInner::Int64) => Some(PrimitiveType::Int64), + (TypeInner::Nat8, TypeInner::Nat8) => Some(PrimitiveType::Nat8), + (TypeInner::Nat16, TypeInner::Nat16) => Some(PrimitiveType::Nat16), + (TypeInner::Nat32, TypeInner::Nat32) => Some(PrimitiveType::Nat32), + (TypeInner::Nat64, TypeInner::Nat64) => Some(PrimitiveType::Nat64), + (TypeInner::Float32, TypeInner::Float32) => Some(PrimitiveType::Float32), + (TypeInner::Float64, TypeInner::Float64) => Some(PrimitiveType::Float64), + _ => None, + } +} + macro_rules! primitive_impl { - ($ty:ident, $type:expr, $cost:literal, $($value:tt)*) => { + ($ty:ident, $type:expr, $fast:expr, $cost:literal, $($value:tt)*) => { paste::item! { fn [](self, visitor: V) -> Result where V: Visitor<'de> { + if self.primitive_vec_fast_path == Some($fast) { + let val = self.input.$($value)*().map_err(|_| Error::msg(format!("Cannot read {} value", stringify!($type))))?; + return visitor.[](val); + } self.unroll_type()?; check!(*self.expect_type == $type && *self.wire_type == $type, stringify!($type)); self.add_cost($cost)?; @@ -648,6 +835,13 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { if self.field_name.is_some() { return self.deserialize_identifier(visitor); } + #[cfg(feature = "bignum")] + if let Some(fast) = self.bignum_vec_fast_path { + return match fast { + BigNumFastPath::Nat => self.deserialize_nat(visitor), + BigNumFastPath::Int | BigNumFastPath::NatAsInt => self.deserialize_int(visitor), + }; + } self.unroll_type()?; match self.expect_type.as_ref() { #[cfg(feature = "bignum")] @@ -705,16 +899,64 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { v } - primitive_impl!(i8, TypeInner::Int8, 1, read_i8); - primitive_impl!(i16, TypeInner::Int16, 2, read_i16::); - primitive_impl!(i32, TypeInner::Int32, 4, read_i32::); - primitive_impl!(i64, TypeInner::Int64, 8, read_i64::); - primitive_impl!(u8, TypeInner::Nat8, 1, read_u8); - primitive_impl!(u16, TypeInner::Nat16, 2, read_u16::); - primitive_impl!(u32, TypeInner::Nat32, 4, read_u32::); - primitive_impl!(u64, TypeInner::Nat64, 8, read_u64::); - primitive_impl!(f32, TypeInner::Float32, 4, read_f32::); - primitive_impl!(f64, TypeInner::Float64, 8, read_f64::); + primitive_impl!(i8, TypeInner::Int8, PrimitiveType::Int8, 1, read_i8); + primitive_impl!( + i16, + TypeInner::Int16, + PrimitiveType::Int16, + 2, + read_i16:: + ); + primitive_impl!( + i32, + TypeInner::Int32, + PrimitiveType::Int32, + 4, + read_i32:: + ); + primitive_impl!( + i64, + TypeInner::Int64, + PrimitiveType::Int64, + 8, + read_i64:: + ); + primitive_impl!(u8, TypeInner::Nat8, PrimitiveType::Nat8, 1, read_u8); + primitive_impl!( + u16, + TypeInner::Nat16, + PrimitiveType::Nat16, + 2, + read_u16:: + ); + primitive_impl!( + u32, + TypeInner::Nat32, + PrimitiveType::Nat32, + 4, + read_u32:: + ); + primitive_impl!( + u64, + TypeInner::Nat64, + PrimitiveType::Nat64, + 8, + read_u64:: + ); + primitive_impl!( + f32, + TypeInner::Float32, + PrimitiveType::Float32, + 4, + read_f32:: + ); + primitive_impl!( + f64, + TypeInner::Float64, + PrimitiveType::Float64, + 8, + read_f64:: + ); fn is_human_readable(&self) -> bool { false @@ -760,44 +1002,43 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { self.add_cost(1)?; visitor.visit_unit() } + // Bool is handled separately from `primitive_impl!` because its wire encoding + // uses `BoolValue::read` rather than a plain numeric read. fn deserialize_bool(self, visitor: V) -> Result where V: Visitor<'de>, { + if self.primitive_vec_fast_path == Some(PrimitiveType::Bool) { + let val = self.read_bool_val()?; + return visitor.visit_bool(val); + } self.unroll_type()?; check!( *self.expect_type == TypeInner::Bool && *self.wire_type == TypeInner::Bool, "bool" ); self.add_cost(1)?; - let res = BoolValue::read(&mut self.input)?; - visitor.visit_bool(res.0) + let val = self.read_bool_val()?; + visitor.visit_bool(val) } fn deserialize_string(self, visitor: V) -> Result where V: Visitor<'de>, { - self.unroll_type()?; - check!( - *self.expect_type == TypeInner::Text && *self.wire_type == TypeInner::Text, - "text" - ); - let len = Len::read(&mut self.input)?.0; - self.add_cost(len.saturating_add(1))?; - let bytes = self.borrow_bytes(len)?.to_owned(); - let value = String::from_utf8(bytes).map_err(Error::msg)?; - visitor.visit_string(value) + self.deserialize_str(visitor) } fn deserialize_str(self, visitor: V) -> Result where V: Visitor<'de>, { - self.unroll_type()?; - check!( - *self.expect_type == TypeInner::Text && *self.wire_type == TypeInner::Text, - "text" - ); - let len = Len::read(&mut self.input)?.0; + if !self.text_fast_path { + self.unroll_type()?; + check!( + *self.expect_type == TypeInner::Text && *self.wire_type == TypeInner::Text, + "text" + ); + } + let len = self.read_len()?; self.add_cost(len.saturating_add(1))?; let slice = self.borrow_bytes(len)?; let value: &str = std::str::from_utf8(slice).map_err(Error::msg)?; @@ -828,19 +1069,19 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { (TypeInner::Opt(t1), TypeInner::Opt(t2)) => { self.wire_type = t1.clone(); self.expect_type = t2.clone(); - if BoolValue::read(&mut self.input)?.0 { - check_recursion! { - self.recoverable_visit_some(visitor) - } + if self.read_bool_val()? { + let _guard = self.recursion_depth.guard()?; + self.recoverable_visit_some(visitor) } else { visitor.visit_none() } } (_, TypeInner::Opt(t2)) => { - self.expect_type = self.table.trace_type(t2)?; - check_recursion! { - self.recoverable_visit_some(visitor) - } + self.expect_type = self + .table + .trace_type_with_depth(t2, &self.recursion_depth)?; + let _guard = self.recursion_depth.guard()?; + self.recoverable_visit_some(visitor) } (_, _) => check!(false), } @@ -849,19 +1090,91 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { where V: Visitor<'de>, { - check_recursion! { + let _guard = self.recursion_depth.guard()?; self.unroll_type()?; self.add_cost(1)?; match (self.expect_type.as_ref(), self.wire_type.as_ref()) { (TypeInner::Vec(e), TypeInner::Vec(w)) => { let expect = e.clone(); - let wire = self.table.trace_type(w)?; - let len = Len::read(&mut self.input)?.0; - visitor.visit_seq(Compound::new(self, Style::Vector { len, expect, wire })) + let wire = self.table.trace_type_with_depth(w, &self.recursion_depth)?; + let len = self.read_len()?; + let exact_primitive = exact_primitive_type(&expect, &wire); + if let Some(prim) = exact_primitive { + let per_element_cost = 3 + primitive_byte_cost(prim); + self.add_cost( + len.checked_mul(per_element_cost) + .ok_or_else(|| Error::msg("Vec length overflow"))?, + )?; + + #[cfg(target_endian = "little")] + { + let byte_size = primitive_byte_cost(prim); + let total_bytes = len + .checked_mul(byte_size) + .ok_or_else(|| Error::msg("Vec byte length overflow"))?; + let pos = self.input.position() as usize; + let slice = self.input.get_ref(); + if pos + total_bytes > slice.len() { + return Err(Error::msg(format!( + "Not enough bytes for primitive vec: need {total_bytes}, have {}", + slice.len() - pos + ))); + } + let data = &slice[pos..pos + total_bytes]; + let mut access = PrimitiveVecAccess { + data, + offset: 0, + remaining: len, + element_size: byte_size, + prim, + }; + let result = visitor.visit_seq(&mut access); + // Advance by bytes actually consumed, not total_bytes, so + // the cursor is correct if the visitor short-circuits. + self.input.set_position((pos + access.offset) as u64); + return result; + } + + #[cfg(not(target_endian = "little"))] + { + self.primitive_vec_fast_path = exact_primitive; + } + } + #[cfg(feature = "bignum")] + let bignum_fast = if exact_primitive.is_none() { + match (expect.as_ref(), wire.as_ref()) { + (TypeInner::Nat, TypeInner::Nat) => Some(BigNumFastPath::Nat), + (TypeInner::Int, TypeInner::Int) => Some(BigNumFastPath::Int), + (TypeInner::Int, TypeInner::Nat) => Some(BigNumFastPath::NatAsInt), + _ => None, + } + } else { + None + }; + #[cfg(feature = "bignum")] + if let Some(fast) = bignum_fast { + self.add_cost( + len.checked_mul(3) + .ok_or_else(|| Error::msg("Vec length overflow"))?, + )?; + self.bignum_vec_fast_path = Some(fast); + self.expect_type = expect.clone(); + self.wire_type = wire.clone(); + } + let result = visitor.visit_seq(Compound::new( + self, + Style::Vector { + len, + expect, + wire, + exact_primitive, + }, + )); + result } - (TypeInner::Record(e), TypeInner::Record(w)) => { - let expect = e.clone().into(); - let wire = w.clone().into(); + (TypeInner::Record(_), TypeInner::Record(_)) => { + let expect = self.expect_type.clone(); + let wire = self.wire_type.clone(); check!(self.expect_type.is_tuple(), "seq_tuple"); if !self.wire_type.is_tuple() { return Err(Error::subtype(format!( @@ -869,13 +1182,19 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { self.wire_type ))); } - let value = - visitor.visit_seq(Compound::new(self, Style::Struct { expect, wire }))?; + let value = visitor.visit_seq(Compound::new( + self, + Style::Struct { + expect, + wire, + expect_idx: 0, + wire_idx: 0, + }, + ))?; Ok(value) } _ => check!(false), } - } } fn deserialize_byte_buf>(self, visitor: V) -> Result { self.unroll_type()?; @@ -884,7 +1203,7 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { && *self.wire_type == TypeInner::Vec(TypeInner::Nat8.into()), "vec nat8" ); - let len = Len::read(&mut self.input)?.0; + let len = self.read_len()?; self.add_cost(len.saturating_add(1))?; let bytes = self.borrow_bytes(len)?.to_owned(); visitor.visit_byte_buf(bytes) @@ -894,7 +1213,7 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { match self.expect_type.as_ref() { TypeInner::Principal => self.deserialize_principal(visitor), TypeInner::Vec(t) if **t == TypeInner::Nat8 => { - let len = Len::read(&mut self.input)?.0; + let len = self.read_len()?; self.add_cost(len.saturating_add(1))?; let slice = self.borrow_bytes(len)?; visitor.visit_borrowed_bytes(slice) @@ -906,13 +1225,13 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { where V: Visitor<'de>, { - check_recursion! { + let _guard = self.recursion_depth.guard()?; self.unroll_type()?; self.add_cost(1)?; match (self.expect_type.as_ref(), self.wire_type.as_ref()) { (TypeInner::Vec(e), TypeInner::Vec(w)) => { - let e = self.table.trace_type(e)?; - let w = self.table.trace_type(w)?; + let e = self.table.trace_type_with_depth(e, &self.recursion_depth)?; + let w = self.table.trace_type_with_depth(w, &self.recursion_depth)?; match (e.as_ref(), w.as_ref()) { (TypeInner::Record(ref e), TypeInner::Record(ref w)) => { match (&e[..], &w[..]) { @@ -926,11 +1245,54 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { { let expect = (ek.clone(), ev.clone()); let wire = (wk.clone(), wv.clone()); - let len = Len::read(&mut self.input)?.0; - visitor.visit_map(Compound::new( + let len = self.read_len()?; + + let key_text_fast = matches!(ek.as_ref(), TypeInner::Text) + && matches!(wk.as_ref(), TypeInner::Text); + #[cfg(feature = "bignum")] + let value_bignum_fast = match (ev.as_ref(), wv.as_ref()) { + (TypeInner::Nat, TypeInner::Nat) => Some(BigNumFastPath::Nat), + (TypeInner::Int, TypeInner::Int) => Some(BigNumFastPath::Int), + (TypeInner::Int, TypeInner::Nat) => { + Some(BigNumFastPath::NatAsInt) + } + _ => None, + }; + #[cfg(feature = "bignum")] + let any_fast = key_text_fast || value_bignum_fast.is_some(); + #[cfg(not(feature = "bignum"))] + let any_fast = key_text_fast; + + if any_fast { + self.add_cost( + len.checked_mul(7) + .ok_or_else(|| Error::msg("Map length overflow"))?, + )?; + if key_text_fast { + self.text_fast_path = true; + } + #[cfg(feature = "bignum")] + if let Some(fast) = value_bignum_fast { + self.bignum_vec_fast_path = Some(fast); + self.wire_type = wv.clone(); + } + } + + let result = visitor.visit_map(Compound::new( self, - Style::Map { len, expect, wire }, - )) + Style::Map { + len, + expect, + wire, + key_text_fast, + }, + )); + self.text_fast_path = false; + #[cfg(feature = "bignum")] + { + self.bignum_vec_fast_path = None; + } + result } _ => Err(Error::subtype("expect a key-value pair")), } @@ -940,16 +1302,14 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { } _ => check!(false), } - } } fn deserialize_tuple(self, _len: usize, visitor: V) -> Result where V: Visitor<'de>, { - check_recursion! { - self.add_cost(1)?; - self.deserialize_seq(visitor) - } + let _guard = self.recursion_depth.guard()?; + self.add_cost(1)?; + self.deserialize_seq(visitor) } fn deserialize_tuple_struct( self, @@ -960,10 +1320,9 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { where V: Visitor<'de>, { - check_recursion! { - self.add_cost(1)?; - self.deserialize_seq(visitor) - } + let _guard = self.recursion_depth.guard()?; + self.add_cost(1)?; + self.deserialize_seq(visitor) } fn deserialize_struct( self, @@ -974,20 +1333,24 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { where V: Visitor<'de>, { - check_recursion! { + let _guard = self.recursion_depth.guard()?; self.unroll_type()?; self.add_cost(1)?; match (self.expect_type.as_ref(), self.wire_type.as_ref()) { - (TypeInner::Record(e), TypeInner::Record(w)) => { - let expect = e.clone().into(); - let wire = w.clone().into(); - let value = - visitor.visit_map(Compound::new(self, Style::Struct { expect, wire }))?; + (TypeInner::Record(_), TypeInner::Record(_)) => { + let value = visitor.visit_map(Compound::new( + self, + Style::Struct { + expect: self.expect_type.clone(), + wire: self.wire_type.clone(), + expect_idx: 0, + wire_idx: 0, + }, + ))?; Ok(value) } _ => check!(false), } - } } fn deserialize_enum( self, @@ -998,7 +1361,7 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { where V: Visitor<'de>, { - check_recursion! { + let _guard = self.recursion_depth.guard()?; self.unroll_type()?; self.add_cost(1)?; match (self.expect_type.as_ref(), self.wire_type.as_ref()) { @@ -1021,7 +1384,6 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { } _ => check!(false), } - } } fn deserialize_identifier(self, visitor: V) -> Result where @@ -1053,10 +1415,13 @@ enum Style { len: usize, expect: Type, wire: Type, + exact_primitive: Option, }, Struct { - expect: VecDeque, - wire: VecDeque, + expect: Type, + wire: Type, + expect_idx: usize, + wire_idx: usize, }, Enum { expect: Field, @@ -1066,14 +1431,110 @@ enum Style { len: usize, expect: (Type, Type), wire: (Type, Type), + key_text_fast: bool, }, } +#[cfg(target_endian = "little")] +struct PrimitiveVecAccess<'de> { + data: &'de [u8], + offset: usize, + remaining: usize, + element_size: usize, + prim: PrimitiveType, +} + +#[cfg(target_endian = "little")] +impl<'de> de::SeqAccess<'de> for PrimitiveVecAccess<'de> { + type Error = Error; + + fn next_element_seed(&mut self, seed: T) -> Result> + where + T: de::DeserializeSeed<'de>, + { + use serde::de::IntoDeserializer; + if self.remaining == 0 { + return Ok(None); + } + self.remaining -= 1; + let bytes = &self.data[self.offset..self.offset + self.element_size]; + self.offset += self.element_size; + + match self.prim { + PrimitiveType::Bool => match bytes[0] { + 0 => seed.deserialize(false.into_deserializer()).map(Some), + 1 => seed.deserialize(true.into_deserializer()).map(Some), + _ => Err(Error::msg("Expect 00 or 01")), + }, + PrimitiveType::Nat8 => seed.deserialize(bytes[0].into_deserializer()).map(Some), + PrimitiveType::Int8 => seed + .deserialize((bytes[0] as i8).into_deserializer()) + .map(Some), + PrimitiveType::Nat16 => { + let v = u16::from_le_bytes(bytes.try_into().unwrap()); + seed.deserialize(v.into_deserializer()).map(Some) + } + PrimitiveType::Int16 => { + let v = i16::from_le_bytes(bytes.try_into().unwrap()); + seed.deserialize(v.into_deserializer()).map(Some) + } + PrimitiveType::Nat32 => { + let v = u32::from_le_bytes(bytes.try_into().unwrap()); + seed.deserialize(v.into_deserializer()).map(Some) + } + PrimitiveType::Int32 => { + let v = i32::from_le_bytes(bytes.try_into().unwrap()); + seed.deserialize(v.into_deserializer()).map(Some) + } + PrimitiveType::Float32 => { + let v = f32::from_le_bytes(bytes.try_into().unwrap()); + seed.deserialize(v.into_deserializer()).map(Some) + } + PrimitiveType::Nat64 => { + let v = u64::from_le_bytes(bytes.try_into().unwrap()); + seed.deserialize(v.into_deserializer()).map(Some) + } + PrimitiveType::Int64 => { + let v = i64::from_le_bytes(bytes.try_into().unwrap()); + seed.deserialize(v.into_deserializer()).map(Some) + } + PrimitiveType::Float64 => { + let v = f64::from_le_bytes(bytes.try_into().unwrap()); + seed.deserialize(v.into_deserializer()).map(Some) + } + } + } + + fn size_hint(&self) -> Option { + Some(self.remaining) + } +} + struct Compound<'a, 'de> { de: &'a mut Deserializer<'de>, style: Style, } +impl Style { + fn struct_remaining(&self) -> Option { + match self { + Style::Struct { + expect, + wire, + expect_idx, + wire_idx, + } => { + let remaining = |ty: &Type, idx: usize| match ty.as_ref() { + TypeInner::Record(fields) => fields.len().saturating_sub(idx), + _ => 0, + }; + Some(remaining(expect, *expect_idx).min(remaining(wire, *wire_idx))) + } + _ => None, + } + } +} + impl<'a, 'de> Compound<'a, 'de> { fn new(de: &'a mut Deserializer<'de>, style: Style) -> Self { Compound { de, style } @@ -1087,12 +1548,12 @@ impl<'de> de::SeqAccess<'de> for Compound<'_, 'de> { where T: de::DeserializeSeed<'de>, { - self.de.add_cost(3)?; match self.style { Style::Vector { ref mut len, ref expect, ref wire, + exact_primitive, } => { if *len == 0 { return Ok(None); @@ -1100,22 +1561,46 @@ impl<'de> de::SeqAccess<'de> for Compound<'_, 'de> { *len -= 1; self.de.expect_type = expect.clone(); self.de.wire_type = wire.clone(); + #[cfg(feature = "bignum")] + let is_fast = exact_primitive.is_some() || self.de.bignum_vec_fast_path.is_some(); + #[cfg(not(feature = "bignum"))] + let is_fast = exact_primitive.is_some(); + if !is_fast { + self.de.add_cost(3)?; + } seed.deserialize(&mut *self.de).map(Some) } Style::Struct { - ref mut expect, - ref mut wire, + ref expect, + ref wire, + ref mut expect_idx, + ref mut wire_idx, } => { - if expect.is_empty() && wire.is_empty() { + self.de.add_cost(3)?; + let expect_fields = match expect.as_ref() { + TypeInner::Record(fields) => fields, + _ => unreachable!(), + }; + let wire_fields = match wire.as_ref() { + TypeInner::Record(fields) => fields, + _ => unreachable!(), + }; + if *expect_idx >= expect_fields.len() && *wire_idx >= wire_fields.len() { return Ok(None); } - self.de.expect_type = expect - .pop_front() - .map(|f| f.ty) + self.de.expect_type = expect_fields + .get(*expect_idx) + .map(|f| { + *expect_idx += 1; + f.ty.clone() + }) .unwrap_or_else(|| TypeInner::Reserved.into()); - self.de.wire_type = wire - .pop_front() - .map(|f| f.ty) + self.de.wire_type = wire_fields + .get(*wire_idx) + .map(|f| { + *wire_idx += 1; + f.ty.clone() + }) .unwrap_or_else(|| TypeInner::Null.into()); seed.deserialize(&mut *self.de).map(Some) } @@ -1126,8 +1611,18 @@ impl<'de> de::SeqAccess<'de> for Compound<'_, 'de> { fn size_hint(&self) -> Option { match &self.style { Style::Vector { len, .. } => Some(*len), - Style::Struct { expect, wire, .. } => Some(expect.len().min(wire.len())), - _ => None, + _ => self.style.struct_remaining(), + } + } +} + +impl Drop for Compound<'_, '_> { + fn drop(&mut self) { + self.de.primitive_vec_fast_path = None; + self.de.text_fast_path = false; + #[cfg(feature = "bignum")] + { + self.de.bignum_vec_fast_path = None; } } } @@ -1141,24 +1636,40 @@ impl<'de> de::MapAccess<'de> for Compound<'_, 'de> { self.de.add_cost(4)?; match self.style { Style::Struct { - ref mut expect, - ref mut wire, + ref expect, + ref wire, + ref mut expect_idx, + ref mut wire_idx, } => { - match (expect.front(), wire.front()) { + let expect_fields = match expect.as_ref() { + TypeInner::Record(fields) => fields, + _ => unreachable!(), + }; + let wire_fields = match wire.as_ref() { + TypeInner::Record(fields) => fields, + _ => unreachable!(), + }; + match (expect_fields.get(*expect_idx), wire_fields.get(*wire_idx)) { (Some(e), Some(w)) => { use std::cmp::Ordering; match e.id.get_id().cmp(&w.id.get_id()) { Ordering::Equal => { self.de.set_field_name(e.id.clone()); - self.de.expect_type = expect.pop_front().unwrap().ty; - self.de.wire_type = wire.pop_front().unwrap().ty; + self.de.expect_type = e.ty.clone(); + self.de.wire_type = w.ty.clone(); + *expect_idx += 1; + *wire_idx += 1; } Ordering::Less => { // by subtyping rules, expect_type can only be opt, reserved or null. let field = e.id.clone(); self.de.set_field_name(field.clone()); - let expect = expect.pop_front().unwrap().ty; - self.de.expect_type = self.de.table.trace_type(&expect)?; + let expect = e.ty.clone(); + *expect_idx += 1; + self.de.expect_type = self + .de + .table + .trace_type_with_depth(&expect, &self.de.recursion_depth)?; check!( matches!( self.de.expect_type.as_ref(), @@ -1170,20 +1681,23 @@ impl<'de> de::MapAccess<'de> for Compound<'_, 'de> { } Ordering::Greater => { self.de.set_field_name(Label::Named("_".to_owned()).into()); - self.de.wire_type = wire.pop_front().unwrap().ty; + self.de.wire_type = w.ty.clone(); self.de.expect_type = TypeInner::Reserved.into(); + *wire_idx += 1; } } } (None, Some(_)) => { self.de.set_field_name(Label::Named("_".to_owned()).into()); - self.de.wire_type = wire.pop_front().unwrap().ty; + self.de.wire_type = wire_fields[*wire_idx].ty.clone(); self.de.expect_type = TypeInner::Reserved.into(); + *wire_idx += 1; } (Some(e), None) => { self.de.set_field_name(e.id.clone()); - self.de.expect_type = expect.pop_front().unwrap().ty; + self.de.expect_type = e.ty.clone(); self.de.wire_type = TypeInner::Null.into(); + *expect_idx += 1; } (None, None) => return Ok(None), } @@ -1193,14 +1707,29 @@ impl<'de> de::MapAccess<'de> for Compound<'_, 'de> { ref mut len, ref expect, ref wire, + key_text_fast, } => { - // This only comes from deserialize_map if *len == 0 { return Ok(None); } - self.de.expect_type = expect.0.clone(); - self.de.wire_type = wire.0.clone(); *len -= 1; + #[cfg(feature = "bignum")] + let any_fast = key_text_fast || self.de.bignum_vec_fast_path.is_some(); + #[cfg(not(feature = "bignum"))] + let any_fast = key_text_fast; + if !any_fast { + self.de.add_cost(4)?; + } + // Always set text_fast_path based on THIS map's key type. The global + // text_fast_path may be true from an enclosing map with text keys; using + // it directly would skip setting expect_type/wire_type for non-text keys + // of this (inner) map, leading to a "Type mismatch" when deserializing + // those keys. + self.de.text_fast_path = key_text_fast; + if !key_text_fast { + self.de.expect_type = expect.0.clone(); + self.de.wire_type = wire.0.clone(); + } seed.deserialize(&mut *self.de).map(Some) } _ => Err(Error::msg("expect struct or map")), @@ -1212,9 +1741,21 @@ impl<'de> de::MapAccess<'de> for Compound<'_, 'de> { { match &self.style { Style::Map { expect, wire, .. } => { - self.de.add_cost(3)?; - self.de.expect_type = expect.1.clone(); - self.de.wire_type = wire.1.clone(); + #[cfg(feature = "bignum")] + let any_fast = self.de.text_fast_path || self.de.bignum_vec_fast_path.is_some(); + #[cfg(not(feature = "bignum"))] + let any_fast = self.de.text_fast_path; + if !any_fast { + self.de.add_cost(3)?; + } + #[cfg(feature = "bignum")] + let value_fast = self.de.bignum_vec_fast_path.is_some(); + #[cfg(not(feature = "bignum"))] + let value_fast = false; + if !value_fast { + self.de.expect_type = expect.1.clone(); + self.de.wire_type = wire.1.clone(); + } seed.deserialize(&mut *self.de) } _ => { @@ -1227,8 +1768,7 @@ impl<'de> de::MapAccess<'de> for Compound<'_, 'de> { fn size_hint(&self) -> Option { match &self.style { Style::Map { len, .. } => Some(*len), - Style::Struct { expect, wire, .. } => Some(expect.len().min(wire.len())), - _ => None, + _ => self.style.struct_remaining(), } } } @@ -1284,7 +1824,7 @@ impl<'de> de::VariantAccess<'de> for Compound<'_, 'de> { T: de::DeserializeSeed<'de>, { self.de.add_cost(1)?; - seed.deserialize(self.de) + seed.deserialize(&mut *self.de) } fn tuple_variant(self, len: usize, visitor: V) -> Result @@ -1292,7 +1832,7 @@ impl<'de> de::VariantAccess<'de> for Compound<'_, 'de> { V: Visitor<'de>, { self.de.add_cost(1)?; - de::Deserializer::deserialize_tuple(self.de, len, visitor) + de::Deserializer::deserialize_tuple(&mut *self.de, len, visitor) } fn struct_variant(self, fields: &'static [&'static str], visitor: V) -> Result @@ -1300,6 +1840,6 @@ impl<'de> de::VariantAccess<'de> for Compound<'_, 'de> { V: Visitor<'de>, { self.de.add_cost(1)?; - de::Deserializer::deserialize_struct(self.de, "_", fields, visitor) + de::Deserializer::deserialize_struct(&mut *self.de, "_", fields, visitor) } } diff --git a/rust/candid/src/pretty/candid.rs b/rust/candid/src/pretty/candid.rs index 9f0089663..ddbaba342 100644 --- a/rust/candid/src/pretty/candid.rs +++ b/rust/candid/src/pretty/candid.rs @@ -2,7 +2,8 @@ use std::collections::HashMap; use crate::pretty::utils::*; use crate::types::{ - ArgType, Field, FuncMode, Function, Label, SharedLabel, Type, TypeEnv, TypeInner, + ArgType, Field, FieldDoc, FuncMode, Function, Label, SharedLabel, Type, TypeDoc, TypeDocs, + TypeEnv, TypeInner, }; use pretty::RcDoc; @@ -127,6 +128,12 @@ pub fn pp_docs<'a>(docs: &'a [String]) -> RcDoc<'a> { lines(docs.iter().map(|line| RcDoc::text("// ").append(line))) } +fn maybe_pp_docs<'a>(docs: Option<&'a [String]>) -> RcDoc<'a> { + docs.filter(|docs| !docs.is_empty()) + .map(pp_docs) + .unwrap_or_else(RcDoc::nil) +} + /// This function is kept for backward compatibility. /// /// It is recommended to use [`pp_label_raw`] instead, which accepts a [`Label`]. @@ -154,6 +161,61 @@ fn pp_fields(fs: &[Field], is_variant: bool) -> RcDoc<'_> { sep_enclose_space(fs.iter().map(|f| pp_field(f, is_variant)), ";", "{", "}") } +fn pp_field_with_doc<'a>( + field: &'a Field, + is_variant: bool, + doc: Option<&'a FieldDoc>, +) -> RcDoc<'a> { + let docs = maybe_pp_docs(doc.map(|doc| doc.docs.as_slice())); + let ty_doc = if is_variant && *field.ty == TypeInner::Null { + RcDoc::nil() + } else { + kwd(" :").append(pp_ty_with_doc( + &field.ty, + doc.and_then(|doc| doc.ty.as_deref()), + )) + }; + docs.append(pp_label_raw(&field.id)).append(ty_doc) +} + +fn pp_fields_with_doc<'a>( + fs: &'a [Field], + is_variant: bool, + doc: Option<&'a TypeDoc>, +) -> RcDoc<'a> { + let fields = fs.iter().map(|field| { + let field_doc = doc.and_then(|doc| doc.fields.get(&field.id.get_id())); + pp_field_with_doc(field, is_variant, field_doc) + }); + sep_enclose_space(fields, ";", "{", "}") +} + +fn has_field_docs(doc: Option<&TypeDoc>) -> bool { + doc.map(|doc| doc.fields.values().any(|field| !field.is_empty())) + .unwrap_or(false) +} + +fn pp_ty_with_doc<'a>(ty: &'a Type, doc: Option<&'a TypeDoc>) -> RcDoc<'a> { + use TypeInner::*; + match ty.as_ref() { + Record(ref fs) => { + if ty.is_tuple() && !has_field_docs(doc) { + let tuple = sep_enclose_space( + fs.iter().map(|f| pp_ty_with_doc(&f.ty, None)), + ";", + "{", + "}", + ); + kwd("record").append(tuple) + } else { + kwd("record").append(pp_fields_with_doc(fs, false, doc)) + } + } + Variant(ref fs) => kwd("variant").append(pp_fields_with_doc(fs, true, doc)), + ty => pp_ty_inner(ty), + } +} + pub fn pp_function(func: &Function) -> RcDoc<'_> { let args = pp_named_args(&func.args); let rets = pp_named_args(&func.rets); @@ -212,7 +274,7 @@ fn pp_service<'a>(serv: &'a [(String, Type)], docs: Option<&'a DocComments>) -> sep_enclose_space(methods, ";", "{", "}") } -fn pp_defs(env: &TypeEnv) -> RcDoc<'_> { +fn pp_defs_plain(env: &TypeEnv) -> RcDoc<'_> { lines(env.to_sorted_iter().map(|(id, ty)| { kwd("type") .append(ident(id.as_str())) @@ -222,6 +284,18 @@ fn pp_defs(env: &TypeEnv) -> RcDoc<'_> { })) } +fn pp_defs<'a>(env: &'a TypeEnv, docs: &'a DocComments) -> RcDoc<'a> { + lines(env.to_sorted_iter().map(|(id, ty)| { + let type_doc = docs.lookup_type_def(id.as_str()); + maybe_pp_docs(type_doc.map(|doc| doc.docs.as_slice())) + .append(kwd("type")) + .append(ident(id.as_str())) + .append(kwd("=")) + .append(pp_ty_with_doc(ty, type_doc)) + .append(";") + })) +} + fn pp_class<'a>(args: &'a [ArgType], t: &'a Type, docs: Option<&'a DocComments>) -> RcDoc<'a> { let doc = pp_named_args(args).append(" ->").append(RcDoc::space()); match t.as_ref() { @@ -245,18 +319,19 @@ fn pp_actor<'a>(ty: &'a Type, docs: &'a DocComments) -> RcDoc<'a> { /// This function is kept for backward compatibility. /// It is recommended to use [`pp_named_init_args`] instead, which prints named arguments. pub fn pp_init_args<'a>(env: &'a TypeEnv, args: &'a [Type]) -> RcDoc<'a> { - pp_defs(env).append(pp_args(args)) + pp_defs_plain(env).append(pp_args(args)) } /// Pretty-prints the initialization arguments for a Candid actor with named arguments. pub fn pp_named_init_args<'a>(env: &'a TypeEnv, args: &'a [ArgType]) -> RcDoc<'a> { - pp_defs(env).append(pp_named_args(args)) + pp_defs_plain(env).append(pp_named_args(args)) } /// Collects doc comments that can be passed to the [compile_with_docs] function. #[derive(Default, Debug)] pub struct DocComments { service_methods: HashMap>, + type_defs: HashMap, } impl DocComments { @@ -271,6 +346,22 @@ impl DocComments { pub fn lookup_service_method(&self, method: &str) -> Option<&Vec> { self.service_methods.get(method) } + + pub fn add_type_def(&mut self, name: String, doc: TypeDoc) { + if !doc.is_empty() { + self.type_defs.insert(name, doc); + } + } + + pub fn lookup_type_def(&self, name: &str) -> Option<&TypeDoc> { + self.type_defs.get(name) + } + + pub fn extend_types(&mut self, docs: TypeDocs) { + for (name, doc) in docs.named { + self.add_type_def(name, doc); + } + } } pub fn compile(env: &TypeEnv, actor: &Option) -> String { @@ -297,9 +388,9 @@ pub fn compile(env: &TypeEnv, actor: &Option) -> String { /// ``` pub fn compile_with_docs(env: &TypeEnv, actor: &Option, docs: &DocComments) -> String { match actor { - None => pp_defs(env).pretty(LINE_WIDTH).to_string(), + None => pp_defs(env, docs).pretty(LINE_WIDTH).to_string(), Some(actor) => { - let defs = pp_defs(env); + let defs = pp_defs(env, docs); let actor = kwd("service :").append(pp_actor(actor, docs)); let doc = defs.append(actor); doc.pretty(LINE_WIDTH).to_string() diff --git a/rust/candid/src/ser.rs b/rust/candid/src/ser.rs index 986789eed..6713b1db7 100644 --- a/rust/candid/src/ser.rs +++ b/rust/candid/src/ser.rs @@ -230,6 +230,10 @@ impl types::Compound for Compound<'_> { use crate::types::Serializer; self.ser.serialize_blob(blob) } + fn try_write_raw_elements(&mut self, bytes: &[u8]) -> Result { + self.ser.write(bytes)?; + Ok(true) + } } /// A structure for serializing Rust values to IDL types. diff --git a/rust/candid/src/types/bounded_vec.rs b/rust/candid/src/types/bounded_vec.rs new file mode 100644 index 000000000..d0a850fc7 --- /dev/null +++ b/rust/candid/src/types/bounded_vec.rs @@ -0,0 +1,474 @@ +use data_size::DataSize; +use serde::{Deserialize, Deserializer}; +use std::fmt; + +use crate::{types::TypeInner, CandidType}; + +/// Indicates that `BoundedVec<...>` template parameter (eg. length, total data size, etc) is unbounded. +pub const UNBOUNDED: usize = usize::MAX; + +/// Struct for bounding a vector by different parameters: +/// - number of elements +/// - total data size in bytes +/// - single element data size in bytes +/// +/// ``` +/// # use candid::{Decode, Encode}; +/// # use candid::types::bounded_vec::{BoundedVec, UNBOUNDED}; +/// // E.g., a user of your service sends candid-encoded bytes: +/// let too_long = vec![13u64; 11]; +/// let bytes_too_long = Encode!(&too_long).unwrap(); +/// // Your service should decode the untrusted bytes with care, by decoding to BoundedVec<10, _, _, _>. +/// // Since the user sent 11 elements and you allow at most 10, this will fail, keeping the service safe from certain exploits. +/// let error = +/// Decode!(&bytes_too_long, BoundedVec<10, UNBOUNDED, UNBOUNDED, u64>).unwrap_err(); +/// assert!(format!("{error:?}").contains("exceeds maximum allowed")); +/// ``` +/// +#[derive(Clone, Eq, PartialEq, Debug, Default)] +pub struct BoundedVec< + const MAX_ALLOWED_LEN: usize, + const MAX_ALLOWED_TOTAL_DATA_SIZE: usize, + const MAX_ALLOWED_ELEMENT_DATA_SIZE: usize, + T, +>(Vec); + +impl< + const MAX_ALLOWED_LEN: usize, + const MAX_ALLOWED_TOTAL_DATA_SIZE: usize, + const MAX_ALLOWED_ELEMENT_DATA_SIZE: usize, + T: CandidType, + > CandidType + for BoundedVec +{ + fn _ty() -> super::Type { + TypeInner::Vec(T::_ty()).into() + } + + fn idl_serialize(&self, serializer: S) -> Result<(), S::Error> + where + S: super::Serializer, + { + self.0.idl_serialize(serializer) + } +} + +impl< + const MAX_ALLOWED_LEN: usize, + const MAX_ALLOWED_TOTAL_DATA_SIZE: usize, + const MAX_ALLOWED_ELEMENT_DATA_SIZE: usize, + T, + > BoundedVec +{ + pub fn new(data: Vec) -> Self { + assert!( + MAX_ALLOWED_LEN != UNBOUNDED + || MAX_ALLOWED_TOTAL_DATA_SIZE != UNBOUNDED + || MAX_ALLOWED_ELEMENT_DATA_SIZE != UNBOUNDED, + "BoundedVec must be bounded by at least one parameter." + ); + + Self(data) + } + + pub fn get(&self) -> &Vec { + &self.0 + } +} + +impl< + 'de, + const MAX_ALLOWED_LEN: usize, + const MAX_ALLOWED_TOTAL_DATA_SIZE: usize, + const MAX_ALLOWED_ELEMENT_DATA_SIZE: usize, + T: Deserialize<'de> + DataSize, + > Deserialize<'de> + for BoundedVec +{ + fn deserialize>(deserializer: D) -> Result { + struct SeqVisitor< + const MAX_ALLOWED_LEN: usize, + const MAX_ALLOWED_TOTAL_DATA_SIZE: usize, + const MAX_ALLOWED_ELEMENT_DATA_SIZE: usize, + T, + > { + _marker: std::marker::PhantomData, + } + + use serde::de::{SeqAccess, Visitor}; + + impl< + 'de, + const MAX_ALLOWED_LEN: usize, + const MAX_ALLOWED_TOTAL_DATA_SIZE: usize, + const MAX_ALLOWED_ELEMENT_DATA_SIZE: usize, + T: Deserialize<'de> + DataSize, + > Visitor<'de> + for SeqVisitor< + MAX_ALLOWED_LEN, + MAX_ALLOWED_TOTAL_DATA_SIZE, + MAX_ALLOWED_ELEMENT_DATA_SIZE, + T, + > + { + type Value = BoundedVec< + MAX_ALLOWED_LEN, + MAX_ALLOWED_TOTAL_DATA_SIZE, + MAX_ALLOWED_ELEMENT_DATA_SIZE, + T, + >; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!( + formatter, + "{}", + describe_sequence( + MAX_ALLOWED_LEN, + MAX_ALLOWED_TOTAL_DATA_SIZE, + MAX_ALLOWED_ELEMENT_DATA_SIZE, + ) + ) + } + + fn visit_seq(self, mut seq: S) -> Result + where + S: SeqAccess<'de>, + { + let mut total_data_size = 0; + let mut elements = if MAX_ALLOWED_LEN == UNBOUNDED { + Vec::new() + } else { + Vec::with_capacity(MAX_ALLOWED_LEN) + }; + while let Some(element) = seq.next_element::()? { + if elements.len() >= MAX_ALLOWED_LEN { + return Err(serde::de::Error::custom(format!( + "The number of elements exceeds maximum allowed {MAX_ALLOWED_LEN}" + ))); + } + // Check that the new element data size is below the maximum allowed limit. + let new_element_data_size = element.data_size(); + if new_element_data_size > MAX_ALLOWED_ELEMENT_DATA_SIZE { + return Err(serde::de::Error::custom(format!( + "The single element data size exceeds maximum allowed {MAX_ALLOWED_ELEMENT_DATA_SIZE}" + ))); + } + // Check that the new total data size (including new element data size) + // is below the maximum allowed limit. + let new_total_data_size = total_data_size + new_element_data_size; + if new_total_data_size > MAX_ALLOWED_TOTAL_DATA_SIZE { + return Err(serde::de::Error::custom(format!( + "The total data size exceeds maximum allowed {MAX_ALLOWED_TOTAL_DATA_SIZE}" + ))); + } + total_data_size = new_total_data_size; + elements.push(element); + } + Ok(BoundedVec::new(elements)) + } + } + + deserializer.deserialize_seq(SeqVisitor::< + MAX_ALLOWED_LEN, + MAX_ALLOWED_TOTAL_DATA_SIZE, + MAX_ALLOWED_ELEMENT_DATA_SIZE, + T, + > { + _marker: std::marker::PhantomData, + }) + } +} + +fn describe_sequence( + max_allowed_len: usize, + max_allowed_total_data_size: usize, + max_allowed_element_data_size: usize, +) -> String { + let mut msg = String::new(); + if max_allowed_len != UNBOUNDED { + msg.push_str(&format!("max {max_allowed_len} elements")); + }; + if max_allowed_total_data_size != UNBOUNDED { + if !msg.is_empty() { + msg.push_str(", "); + } + msg.push_str(&format!("max {max_allowed_total_data_size} bytes total")); + }; + if max_allowed_element_data_size != UNBOUNDED { + if !msg.is_empty() { + msg.push_str(", "); + } + msg.push_str(&format!( + "max {max_allowed_element_data_size} bytes per element" + )); + }; + format!("a sequence with {msg}") +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{Decode, Encode}; + + #[test] + fn test_describe_sequence() { + assert_eq!( + describe_sequence(42, UNBOUNDED, UNBOUNDED), + "a sequence with max 42 elements".to_string() + ); + assert_eq!( + describe_sequence(UNBOUNDED, 256, UNBOUNDED), + "a sequence with max 256 bytes total".to_string(), + ); + assert_eq!( + describe_sequence(UNBOUNDED, UNBOUNDED, 64), + "a sequence with max 64 bytes per element".to_string(), + ); + assert_eq!( + describe_sequence(42, 256, UNBOUNDED), + "a sequence with max 42 elements, max 256 bytes total".to_string(), + ); + assert_eq!( + describe_sequence(42, UNBOUNDED, 64), + "a sequence with max 42 elements, max 64 bytes per element".to_string(), + ); + assert_eq!( + describe_sequence(UNBOUNDED, 256, 64), + "a sequence with max 256 bytes total, max 64 bytes per element".to_string(), + ); + assert_eq!( + describe_sequence(42, 256, 64), + "a sequence with max 42 elements, max 256 bytes total, max 64 bytes per element" + .to_string(), + ); + } + + #[test] + #[should_panic] + fn test_not_bounded_vector_fails() { + type NotBoundedVec = BoundedVec; + + let _ = NotBoundedVec::new(vec![1, 2, 3]); + } + + #[test] + fn test_bounded_vector_lengths() { + // This test verifies that the structures containing BoundedVec correctly + // throw an error when the number of elements exceeds the maximum allowed. + type BoundedLen = BoundedVec; + + const MAX_ALLOWED_LEN: usize = 30; + const TEST_START: usize = 20; + const TEST_END: usize = 40; + for i in TEST_START..=TEST_END { + // Arrange. + let data = BoundedLen::new(vec![42; i]); + + // Act. + let bytes = Encode!(&data).unwrap(); + let result = Decode!(&bytes, BoundedLen); + + // Assert. + if i <= MAX_ALLOWED_LEN { + // Verify decoding without errors for allowed sizes. + assert!(result.is_ok()); + assert_eq!(result.unwrap(), data); + } else { + // Verify decoding with errors for disallowed sizes. + assert!(result.is_err()); + let error = result.unwrap_err(); + assert!( + format!("{error:?}").contains(&format!( + "Deserialize error: The number of elements exceeds maximum allowed {MAX_ALLOWED_LEN}" + )), + "Actual: {}", + error + ); + } + } + } + + #[test] + fn test_bounded_vector_total_data_sizes() { + // This test verifies that the structures containing BoundedVec correctly + // throw an error when the total data size exceeds the maximum allowed. + const MAX_ALLOWED_TOTAL_DATA_SIZE: usize = 100; + const ELEMENT_SIZE: usize = 37; + // Assert element size is not a multiple of total size. + assert_ne!(MAX_ALLOWED_TOTAL_DATA_SIZE % ELEMENT_SIZE, 0); + for aimed_total_size in 64..=256 { + // Arrange. + type BoundedSize = + BoundedVec>; + let element = vec![b'a'; ELEMENT_SIZE - std::mem::size_of::>()]; + let elements_count = aimed_total_size / element.data_size(); + let data = BoundedSize::new(vec![element; elements_count]); + let actual_total_size = data.get().data_size(); + + // Act. + let bytes = Encode!(&data).unwrap(); + let result = Decode!(&bytes, BoundedSize); + + // Assert. + if actual_total_size <= MAX_ALLOWED_TOTAL_DATA_SIZE { + // Verify decoding without errors for allowed sizes. + assert!(result.is_ok()); + assert_eq!(result.unwrap(), data); + } else { + // Verify decoding with errors for disallowed sizes. + assert!(result.is_err()); + let error = result.unwrap_err(); + assert!( + format!("{error:?}").contains(&format!( + "Deserialize error: The total data size exceeds maximum allowed {MAX_ALLOWED_TOTAL_DATA_SIZE}" + )), + "Actual: {}", + error + ); + } + } + } + + #[test] + fn test_bounded_vector_element_data_sizes() { + // This test verifies that the structures containing BoundedVec correctly + // throw an error when the element data size exceeds the maximum allowed. + const MAX_ALLOWED_ELEMENT_DATA_SIZE: usize = 100; + for element_size in 64..=256 { + // Arrange. + type BoundedSize = + BoundedVec>; + let element = vec![b'a'; element_size - std::mem::size_of::>()]; + let data = BoundedSize::new(vec![element; 42]); + + // Act. + let bytes = Encode!(&data).unwrap(); + let result = Decode!(&bytes, BoundedSize); + + // Assert. + if element_size <= MAX_ALLOWED_ELEMENT_DATA_SIZE { + // Verify decoding without errors for allowed sizes. + assert!(result.is_ok()); + assert_eq!(result.unwrap(), data); + } else { + // Verify decoding with errors for disallowed sizes. + assert!(result.is_err()); + let error = result.unwrap_err(); + assert!( + format!("{error:?}").contains(&format!( + "Deserialize error: The single element data size exceeds maximum allowed {MAX_ALLOWED_ELEMENT_DATA_SIZE}" + )), + "Actual: {}", + error + ); + } + } + } +} + +mod data_size { + /// Trait to reasonably estimate the memory usage of a value in bytes. + /// + /// Default implementation returns zero. + pub trait DataSize { + /// Default implementation returns zero. + fn data_size(&self) -> usize { + 0 + } + } + + impl DataSize for u8 { + fn data_size(&self) -> usize { + std::mem::size_of::() + } + } + + impl DataSize for [u8] { + fn data_size(&self) -> usize { + std::mem::size_of_val(self) + } + } + + impl DataSize for u64 { + fn data_size(&self) -> usize { + std::mem::size_of::() + } + } + + impl DataSize for &str { + fn data_size(&self) -> usize { + self.as_bytes().data_size() + } + } + + impl DataSize for String { + fn data_size(&self) -> usize { + self.as_bytes().data_size() + } + } + + impl DataSize for Vec { + fn data_size(&self) -> usize { + std::mem::size_of::() + self.iter().map(|x| x.data_size()).sum::() + } + } + + impl DataSize for ic_principal::Principal { + fn data_size(&self) -> usize { + self.as_slice().len() + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn test_data_size_u8() { + assert_eq!(0_u8.data_size(), 1); + assert_eq!(42_u8.data_size(), 1); + } + + #[test] + fn test_data_size_u8_slice() { + let a: [u8; 0] = []; + assert_eq!(a.data_size(), 0); + assert_eq!([1_u8].data_size(), 1); + assert_eq!([1_u8, 2_u8].data_size(), 2); + } + + #[test] + fn test_data_size_u64() { + assert_eq!(0_u64.data_size(), 8); + assert_eq!(42_u64.data_size(), 8); + } + + #[test] + fn test_data_size_u8_vec() { + let base = 24; + assert_eq!(Vec::::from([]).data_size(), base); + assert_eq!(Vec::::from([1]).data_size(), base + 1); + assert_eq!(Vec::::from([1, 2]).data_size(), base + 2); + } + + #[test] + fn test_data_size_str() { + assert_eq!("a".data_size(), 1); + assert_eq!("ab".data_size(), 2); + } + + #[test] + fn test_data_size_string() { + assert_eq!(String::from("a").data_size(), 1); + assert_eq!(String::from("ab").data_size(), 2); + for size_bytes in 0..1_024 { + assert_eq!( + String::from_utf8(vec![b'x'; size_bytes]) + .unwrap() + .data_size(), + size_bytes + ); + } + } + } +} diff --git a/rust/candid/src/types/impls.rs b/rust/candid/src/types/impls.rs index 0682a4659..33092b760 100644 --- a/rust/candid/src/types/impls.rs +++ b/rust/candid/src/types/impls.rs @@ -122,6 +122,25 @@ where } } +#[cfg(target_endian = "little")] +fn fixed_primitive_byte_size() -> Option { + use super::internal::TypeId; + let tid = TypeId::of::(); + if tid == TypeId::of::() || tid == TypeId::of::() || tid == TypeId::of::() { + Some(1) + } else if tid == TypeId::of::() || tid == TypeId::of::() { + Some(2) + } else if tid == TypeId::of::() || tid == TypeId::of::() || tid == TypeId::of::() + { + Some(4) + } else if tid == TypeId::of::() || tid == TypeId::of::() || tid == TypeId::of::() + { + Some(8) + } else { + None + } +} + impl CandidType for [T] where T: CandidType, @@ -134,6 +153,15 @@ where S: Serializer, { let mut ser = serializer.serialize_vec(self.len())?; + #[cfg(target_endian = "little")] + if let Some(element_size) = fixed_primitive_byte_size::() { + let bytes = unsafe { + std::slice::from_raw_parts(self.as_ptr() as *const u8, self.len() * element_size) + }; + if Compound::try_write_raw_elements(&mut ser, bytes)? { + return Ok(()); + } + } for e in self { Compound::serialize_element(&mut ser, &e)?; } @@ -242,7 +270,17 @@ use std::hash::{BuildHasher, Hash}; map_impl!(BTreeMap); map_impl!(HashMap); -seq_impl!(Vec); +impl CandidType for Vec { + fn _ty() -> Type { + TypeInner::Vec(K::ty()).into() + } + fn idl_serialize(&self, serializer: S) -> Result<(), S::Error> + where + S: Serializer, + { + self.as_slice().idl_serialize(serializer) + } +} seq_impl!(VecDeque); seq_impl!(LinkedList); seq_impl!(BinaryHeap); @@ -257,11 +295,7 @@ impl CandidType for [T; N] { where S: Serializer, { - let mut ser = serializer.serialize_vec(N)?; - for e in self { - Compound::serialize_element(&mut ser, &e)?; - } - Ok(()) + self.as_slice().idl_serialize(serializer) } } diff --git a/rust/candid/src/types/internal.rs b/rust/candid/src/types/internal.rs index 742c87e30..cd5f11a88 100644 --- a/rust/candid/src/types/internal.rs +++ b/rust/candid/src/types/internal.rs @@ -22,7 +22,7 @@ impl TypeId { pub fn of() -> Self { let name = std::any::type_name::(); TypeId { - id: TypeId::of:: as usize, + id: TypeId::of:: as *const () as usize, name, } } @@ -75,6 +75,39 @@ impl TypeName { } } +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct TypeDocs { + pub named: BTreeMap, +} + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct TypeDoc { + pub docs: Vec, + pub fields: BTreeMap, +} + +impl TypeDoc { + pub fn is_empty(&self) -> bool { + self.docs.is_empty() && self.fields.is_empty() + } +} + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct FieldDoc { + pub docs: Vec, + pub ty: Option>, +} + +impl FieldDoc { + pub fn is_empty(&self) -> bool { + self.docs.is_empty() + && match self.ty.as_deref() { + None => true, + Some(doc) => doc.is_empty(), + } + } +} + /// Used for `candid_derive::export_service` to generate `TypeEnv` from `Type`. /// /// It performs a global rewriting of `Type` to resolve: @@ -89,11 +122,13 @@ impl TypeName { #[derive(Default)] pub struct TypeContainer { pub env: crate::TypeEnv, + pub docs: TypeDocs, } impl TypeContainer { pub fn new() -> Self { TypeContainer { env: crate::TypeEnv::new(), + docs: TypeDocs::default(), } } pub fn add(&mut self) -> Type { @@ -119,8 +154,10 @@ impl TypeContainer { } let id = ID.with(|n| n.borrow().get(t).cloned()); if let Some(id) = id { - let type_key = TypeKey::from(id.to_string()); + let name = id.to_string(); + let type_key = TypeKey::from(name.clone()); self.env.0.insert(type_key.clone(), res); + self.remember_named_doc(&id, &name); TypeInner::Var(type_key) } else { // if the type is part of an enum, the id won't be recorded. @@ -140,8 +177,10 @@ impl TypeContainer { .into(); let id = ID.with(|n| n.borrow().get(t).cloned()); if let Some(id) = id { - let type_key = TypeKey::from(id.to_string()); + let name = id.to_string(); + let type_key = TypeKey::from(name.clone()); self.env.0.insert(type_key.clone(), res); + self.remember_named_doc(&id, &name); TypeInner::Var(type_key) } else { return res; @@ -149,8 +188,10 @@ impl TypeContainer { } TypeInner::Knot(id) => { let ty = ENV.with(|e| e.borrow().get(id).unwrap().clone()); - let type_key = TypeKey::from(id.to_string()); + let name = id.to_string(); + let type_key = TypeKey::from(name.clone()); self.env.0.insert(type_key.clone(), ty); + self.remember_named_doc(id, &name); TypeInner::Var(type_key) } TypeInner::Func(func) => TypeInner::Func(Function { @@ -191,6 +232,14 @@ impl TypeContainer { } .into() } + + fn remember_named_doc(&mut self, id: &TypeId, name: &str) { + if let Some(doc) = find_type_doc(id) { + if !doc.is_empty() { + self.docs.named.entry(name.to_string()).or_insert(doc); + } + } + } } #[derive(Debug, PartialEq, Hash, Eq, Clone, PartialOrd, Ord)] @@ -704,6 +753,7 @@ pub fn unroll(t: &Type) -> Type { thread_local! { static ENV: RefCell> = const { RefCell::new(BTreeMap::new()) }; + static DOC_ENV: RefCell> = const { RefCell::new(BTreeMap::new()) }; // only used for TypeContainer static ID: RefCell> = const { RefCell::new(BTreeMap::new()) }; static NAME: RefCell = RefCell::new(TypeName::default()); @@ -713,6 +763,10 @@ pub fn find_type(id: &TypeId) -> Option { ENV.with(|e| e.borrow().get(id).cloned()) } +pub fn find_type_doc(id: &TypeId) -> Option { + DOC_ENV.with(|e| e.borrow().get(id).cloned()) +} + // only for debugging #[allow(dead_code)] pub(crate) fn show_env() { @@ -724,6 +778,7 @@ pub(crate) fn env_add(id: TypeId, t: Type) { } pub fn env_clear() { ENV.with(|e| e.borrow_mut().clear()); + DOC_ENV.with(|e| e.borrow_mut().clear()); } pub(crate) fn env_id(id: TypeId, t: Type) { @@ -744,6 +799,10 @@ pub(crate) fn env_id(id: TypeId, t: Type) { }); } +pub(crate) fn env_doc(id: TypeId, doc: TypeDoc) { + DOC_ENV.with(|e| e.borrow_mut().insert(id, doc)); +} + pub fn get_type(_v: &T) -> Type where T: CandidType, diff --git a/rust/candid/src/types/mod.rs b/rust/candid/src/types/mod.rs index 84a4b9a78..4102a9198 100644 --- a/rust/candid/src/types/mod.rs +++ b/rust/candid/src/types/mod.rs @@ -15,10 +15,12 @@ pub mod type_env; pub mod value; pub use self::internal::{ - get_type, ArgType, Field, FuncMode, Function, Label, SharedLabel, Type, TypeId, TypeInner, + get_type, ArgType, Field, FieldDoc, FuncMode, Function, Label, SharedLabel, Type, TypeDoc, + TypeDocs, TypeId, TypeInner, }; pub use type_env::TypeEnv; +pub mod bounded_vec; pub mod leb128; #[cfg(feature = "bignum")] pub mod number; @@ -44,7 +46,8 @@ pub trait CandidType { self::internal::env_add(id.clone(), TypeInner::Unknown.into()); let t = Self::_ty(); self::internal::env_add(id.clone(), t.clone()); - self::internal::env_id(id, t.clone()); + self::internal::env_id(id.clone(), t.clone()); + self::internal::env_doc(id, Self::_ty_doc()); t } } @@ -52,6 +55,9 @@ pub trait CandidType { TypeId::of::() } fn _ty() -> Type; + fn _ty_doc() -> internal::TypeDoc { + internal::TypeDoc::default() + } // only serialize the value encoding fn idl_serialize(&self, serializer: S) -> Result<(), S::Error> where @@ -103,4 +109,9 @@ pub trait Compound { // or generalize this function to take a closure for with. #[doc(hidden)] fn serialize_blob(&mut self, blob: &[u8]) -> Result<(), Self::Error>; + /// Write raw element bytes directly for a primitive vector. + /// Returns Ok(true) if written, Ok(false) if not supported (caller should use per-element fallback). + fn try_write_raw_elements(&mut self, _bytes: &[u8]) -> Result { + Ok(false) + } } diff --git a/rust/candid/src/types/number.rs b/rust/candid/src/types/number.rs index a92b4744a..64e3501dd 100644 --- a/rust/candid/src/types/number.rs +++ b/rust/candid/src/types/number.rs @@ -214,6 +214,10 @@ impl Nat { W: ?Sized + io::Write, { use num_traits::cast::ToPrimitive; + if let Some(value) = self.0.to_u64() { + leb128::write::unsigned(w, value)?; + return Ok(()); + } let zero = BigUint::from(0u8); let mut value = self.0.clone(); loop { @@ -234,17 +238,39 @@ impl Nat { where R: io::Read, { - let mut result = BigUint::from(0u8); - let mut shift = 0; + let mut small = 0u64; + let mut shift = 0u32; loop { let mut buf = [0]; r.read_exact(&mut buf)?; - let low_bits = BigUint::from(buf[0] & 0x7fu8); - result |= low_bits << shift; - if buf[0] & 0x80u8 == 0 { + let byte = buf[0]; + let low_bits = u64::from(byte & 0x7f); + if shift == 0 || (shift < 64 && low_bits < (1u64 << (64 - shift))) { + small |= low_bits << shift; + if byte & 0x80u8 == 0 { + return Ok(Nat(BigUint::from(small))); + } + shift += 7; + continue; + } + + let mut result = BigUint::from(small); + result |= BigUint::from(low_bits) << shift; + if byte & 0x80u8 == 0 { return Ok(Nat(result)); } shift += 7; + loop { + let mut buf = [0]; + r.read_exact(&mut buf)?; + let byte = buf[0]; + let low_bits = BigUint::from(byte & 0x7fu8); + result |= low_bits << shift; + if byte & 0x80u8 == 0 { + return Ok(Nat(result)); + } + shift += 7; + } } } } @@ -255,6 +281,10 @@ impl Int { W: ?Sized + io::Write, { use num_traits::cast::ToPrimitive; + if let Some(value) = self.0.to_i64() { + leb128::write::signed(w, value)?; + return Ok(()); + } let zero = BigInt::from(0); let mut value = self.0.clone(); loop { @@ -279,24 +309,68 @@ impl Int { where R: io::Read, { - let mut result = BigInt::from(0); - let mut shift = 0; - let mut byte; + let mut small = 0i64; + let mut shift = 0u32; loop { let mut buf = [0]; r.read_exact(&mut buf)?; - byte = buf[0]; - let low_bits = BigInt::from(byte & 0x7fu8); - result |= low_bits << shift; + let byte = buf[0]; + let low_bits = i64::from(byte & 0x7f); + + let fits_i64 = if shift < 57 { + true + } else if shift < 64 && byte & 0x80 == 0 { + // Only the terminal byte can confirm the value fits in i64: + // if continuation is set, more bytes follow at shifts >= 64 + // and the value's high bits won't fit. Without this guard, + // `low_bits << shift` would silently truncate bits 1..6. + let remaining_bits = 64 - shift; + if (byte & 0x40) != 0 { + (low_bits | !0x7f) >> (remaining_bits - 1) == -1 + } else { + low_bits >> (remaining_bits - 1) == 0 + } + } else { + false + }; + + if fits_i64 { + small |= low_bits << shift; + shift += 7; + if byte & 0x80 == 0 { + if shift < 64 && (byte & 0x40) != 0 { + small |= !0i64 << shift; + } + return Ok(Int(BigInt::from(small))); + } + continue; + } + + let mut result = BigInt::from(small); + let big_low_bits = BigInt::from(byte & 0x7fu8); + result |= big_low_bits << shift; shift += 7; - if byte & 0x80u8 == 0 { - break; + if byte & 0x80 == 0 { + if (byte & 0x40) != 0 { + result |= BigInt::from(-1) << shift; + } + return Ok(Int(result)); + } + loop { + let mut buf = [0]; + r.read_exact(&mut buf)?; + let byte = buf[0]; + let big_low_bits = BigInt::from(byte & 0x7fu8); + result |= big_low_bits << shift; + shift += 7; + if byte & 0x80 == 0 { + if (byte & 0x40) != 0 { + result |= BigInt::from(-1) << shift; + } + return Ok(Int(result)); + } } } - if (0x40u8 & byte) == 0x40u8 { - result |= BigInt::from(-1) << shift; - } - Ok(Int(result)) } } diff --git a/rust/candid/src/types/subtype.rs b/rust/candid/src/types/subtype.rs index 5723a25f6..11e535df2 100644 --- a/rust/candid/src/types/subtype.rs +++ b/rust/candid/src/types/subtype.rs @@ -1,8 +1,10 @@ use super::internal::{find_type, Field, Label, Type, TypeInner}; use crate::types::{ArgType, TypeEnv}; +use crate::utils::RecursionDepth; use crate::{Error, Result}; use anyhow::Context; use std::collections::{HashMap, HashSet}; +use std::fmt; pub type Gamma = HashSet<(Type, Type)>; @@ -15,7 +17,14 @@ pub enum OptReport { } /// Check if t1 <: t2 pub fn subtype(gamma: &mut Gamma, env: &TypeEnv, t1: &Type, t2: &Type) -> Result<()> { - subtype_(OptReport::Warning, gamma, env, t1, t2) + subtype_( + OptReport::Warning, + gamma, + env, + t1, + t2, + &RecursionDepth::new(), + ) } /// Check if t1 <: t2, and report the special opt rule as `Slience`, `Warning` or `Error`. pub fn subtype_with_config( @@ -25,7 +34,478 @@ pub fn subtype_with_config( t1: &Type, t2: &Type, ) -> Result<()> { - subtype_(report, gamma, env, t1, t2) + subtype_(report, gamma, env, t1, t2, &RecursionDepth::new()) +} + +/// A single incompatibility found during subtype checking. +#[derive(Debug, Clone)] +pub struct Incompatibility { + /// Path to the incompatible element, from outermost to innermost. + /// e.g., `["method \"transfer\"", "return type", "record field \"status\""]` + pub path: Vec, + /// Description of the specific incompatibility. + pub message: String, +} + +impl fmt::Display for Incompatibility { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.path.is_empty() { + write!(f, "{}", self.message) + } else { + for (i, segment) in self.path.iter().enumerate() { + if i > 0 { + write!(f, " > ")?; + } + write!(f, "{segment}")?; + } + write!(f, ": {}", self.message) + } + } +} + +/// Format a list of incompatibilities as a grouped, hierarchical report. +/// +/// Errors are grouped by their shared path prefixes so that, for example, +/// five errors under `method "transfer"` appear together rather than as +/// five separate top-level items. +/// +/// ```text +/// method "transfer": +/// return type: +/// - record field a: text is not a subtype of nat +/// - record field b: nat is not a subtype of bool +/// input type: +/// - missing required field amount (type nat) +/// +/// method "get_user": +/// - missing in new interface +/// ``` +pub fn format_report(errors: &[Incompatibility]) -> String { + if errors.is_empty() { + return String::new(); + } + + // Build a tree: each node is either a branch (has children keyed by path segment) + // or a leaf (has a message). + struct Node { + children: Vec<(String, Node)>, + messages: Vec, + } + + impl Node { + fn new() -> Self { + Node { + children: Vec::new(), + messages: Vec::new(), + } + } + fn child(&mut self, key: &str) -> &mut Node { + if let Some(pos) = self.children.iter().position(|(k, _)| k == key) { + &mut self.children[pos].1 + } else { + self.children.push((key.to_string(), Node::new())); + let last = self.children.len() - 1; + &mut self.children[last].1 + } + } + fn insert(&mut self, path: &[String], message: &str) { + if path.is_empty() { + self.messages.push(message.to_string()); + } else { + self.child(&path[0]).insert(&path[1..], message); + } + } + fn render(&self, out: &mut String, indent: usize) { + let pad = " ".repeat(indent); + for msg in &self.messages { + out.push_str(&pad); + out.push_str("- "); + out.push_str(msg); + out.push('\n'); + } + for (key, child) in &self.children { + // If the child has exactly one message and no sub-children, inline it. + if child.children.is_empty() && child.messages.len() == 1 { + out.push_str(&pad); + out.push_str(key); + out.push_str(": "); + out.push_str(&child.messages[0]); + out.push('\n'); + } else { + out.push_str(&pad); + out.push_str(key); + out.push_str(":\n"); + child.render(out, indent + 1); + } + } + } + } + + let mut root = Node::new(); + for e in errors { + root.insert(&e.path, &e.message); + } + + let mut out = String::new(); + root.render(&mut out, 0); + // Remove trailing newline + if out.ends_with('\n') { + out.pop(); + } + out +} + +/// Check if `t1 <: t2`, collecting **all** incompatibilities instead of stopping at the first. +/// +/// Returns an empty `Vec` when `t1` is indeed a subtype of `t2`. +/// This is intended for upgrade compatibility reports where users need to see +/// every breaking change at once. +pub fn subtype_check_all( + gamma: &mut Gamma, + env: &TypeEnv, + t1: &Type, + t2: &Type, +) -> Vec { + let mut errors = Vec::new(); + subtype_collect_( + OptReport::Warning, + gamma, + env, + t1, + t2, + &RecursionDepth::new(), + &mut Vec::new(), + &mut errors, + false, + ); + errors +} + +/// Internal collecting variant of `subtype_`. Instead of short-circuiting on +/// the first error, this continues through all fields/methods/args and pushes +/// every incompatibility it finds into `errors`. +#[allow(clippy::too_many_arguments)] +fn subtype_collect_( + report: OptReport, + gamma: &mut Gamma, + env: &TypeEnv, + t1: &Type, + t2: &Type, + depth: &RecursionDepth, + path: &mut Vec, + errors: &mut Vec, + is_input: bool, +) { + let _guard = match depth.guard() { + Ok(g) => g, + Err(_) => { + errors.push(Incompatibility { + path: path.clone(), + message: "recursion limit exceeded".to_string(), + }); + return; + } + }; + use TypeInner::*; + if t1 == t2 { + return; + } + // Handle Var/Knot (type variables / recursive types) + if matches!(t1.as_ref(), Var(_) | Knot(_)) || matches!(t2.as_ref(), Var(_) | Knot(_)) { + if !gamma.insert((t1.clone(), t2.clone())) { + return; // co-inductive: assume OK + } + let before = errors.len(); + match (t1.as_ref(), t2.as_ref()) { + (Var(id), _) => subtype_collect_( + report, + gamma, + env, + env.rec_find_type_with_depth(id, depth).unwrap(), + t2, + depth, + path, + errors, + is_input, + ), + (_, Var(id)) => subtype_collect_( + report, + gamma, + env, + t1, + env.rec_find_type_with_depth(id, depth).unwrap(), + depth, + path, + errors, + is_input, + ), + (Knot(id), _) => subtype_collect_( + report, + gamma, + env, + &find_type(id).unwrap(), + t2, + depth, + path, + errors, + is_input, + ), + (_, Knot(id)) => subtype_collect_( + report, + gamma, + env, + t1, + &find_type(id).unwrap(), + depth, + path, + errors, + is_input, + ), + (_, _) => unreachable!(), + }; + if errors.len() > before { + gamma.remove(&(t1.clone(), t2.clone())); + } + return; + } + match (t1.as_ref(), t2.as_ref()) { + (_, Reserved) => (), + (Empty, _) => (), + (Nat, Int) => (), + (Vec(ty1), Vec(ty2)) => { + subtype_collect_(report, gamma, env, ty1, ty2, depth, path, errors, is_input); + } + (Null, Opt(_)) => (), + // For opt rules we delegate to the existing subtype_ to test the condition, + // since these are probes, not things that generate multiple independent errors. + (Opt(ty1), Opt(ty2)) if subtype_(report, gamma, env, ty1, ty2, depth).is_ok() => {} + (_, Opt(ty2)) + if subtype_(report, gamma, env, t1, ty2, depth).is_ok() + && !matches!( + env.trace_type_with_depth(ty2, depth) + .map(|t| t.as_ref().clone()), + Ok(Null | Reserved | Opt(_)) + ) => {} + (_, Opt(_)) => { + let msg = format!("WARNING: {t1} <: {t2} due to special subtyping rules involving optional types/fields (see https://github.com/dfinity/candid/blob/c7659ca/spec/Candid.md#upgrading-and-subtyping). This means the two interfaces have diverged, which could cause data loss."); + match report { + OptReport::Silence => (), + OptReport::Warning => eprintln!("{msg}"), + OptReport::Error => { + errors.push(Incompatibility { + path: path.clone(), + message: msg, + }); + } + }; + } + (Record(fs1), Record(fs2)) => { + let fields: HashMap<_, _> = fs1.iter().map(|Field { id, ty }| (id, ty)).collect(); + for Field { id, ty: ty2 } in fs2 { + match fields.get(id) { + Some(ty1) => { + path.push(format!("record field {id}")); + subtype_collect_( + report, gamma, env, ty1, ty2, depth, path, errors, is_input, + ); + path.pop(); + } + None => { + let is_optional = env + .trace_type_with_depth(ty2, depth) + .map(|t| matches!(t.as_ref(), Null | Reserved | Opt(_))) + .unwrap_or(false); + if !is_optional { + errors.push(Incompatibility { + path: path.clone(), + message: if is_input { + format!( + "new service requires field {id} (type {ty2}), \ + which old callers don't provide and is not optional" + ) + } else { + format!( + "new type is missing required field {id} (type {ty2}), \ + which is expected by the old type and is not optional" + ) + }, + }); + } + } + } + } + } + (Variant(fs1), Variant(fs2)) => { + let fields: HashMap<_, _> = fs2.iter().map(|Field { id, ty }| (id, ty)).collect(); + for Field { id, ty: ty1 } in fs1 { + match fields.get(id) { + Some(ty2) => { + path.push(format!("variant field {id}")); + subtype_collect_( + report, gamma, env, ty1, ty2, depth, path, errors, is_input, + ); + path.pop(); + } + None => { + errors.push(Incompatibility { + path: path.clone(), + message: if is_input { + format!( + "old callers may send variant case {id}, \ + which the new service no longer handles" + ) + } else { + format!( + "new variant has field {id} that does not exist in the old type" + ) + }, + }); + } + } + } + } + (Service(ms1), Service(ms2)) => { + let meths: HashMap<_, _> = ms1.iter().cloned().collect(); + for (name, ty2) in ms2 { + match meths.get(name) { + Some(ty1) => { + path.push(format!("method \"{name}\"")); + subtype_collect_(report, gamma, env, ty1, ty2, depth, path, errors, false); + path.pop(); + } + None => { + errors.push(Incompatibility { + path: path.clone(), + message: format!( + "method \"{name}\" is expected by the old interface but missing in the new one" + ), + }); + } + } + } + } + (Func(f1), Func(f2)) => { + if f1.modes != f2.modes { + errors.push(Incompatibility { + path: path.clone(), + message: format!( + "function annotation changed from {old} to {new}", + old = if f2.modes.is_empty() { + "update".to_string() + } else { + pp_modes(&f2.modes) + }, + new = if f1.modes.is_empty() { + "update".to_string() + } else { + pp_modes(&f1.modes) + }, + ), + }); + // Don't return early - also check arg/ret compatibility + } + // Check each argument directly instead of wrapping in a tuple record, + // so we get clean error paths like "input argument 1" instead of "record field 0". + check_func_params( + report, gamma, env, &f2.args, &f1.args, depth, path, errors, "input", true, + ); + check_func_params( + report, gamma, env, &f1.rets, &f2.rets, depth, path, errors, "return", false, + ); + } + (Class(_, t), _) => { + subtype_collect_(report, gamma, env, t, t2, depth, path, errors, is_input); + } + (_, Class(_, t)) => { + subtype_collect_(report, gamma, env, t1, t, depth, path, errors, is_input); + } + (Unknown, _) => unreachable!(), + (_, Unknown) => unreachable!(), + (_, _) => { + errors.push(Incompatibility { + path: path.clone(), + message: format!("{t1} is not a subtype of {t2}"), + }); + } + } +} + +/// Check function parameters (args or rets) for subtype compatibility, +/// collecting all errors. Handles the record-tuple wrapping so that single-arg +/// functions don't produce misleading "record field 0" paths. +/// +/// For inputs (contravariant): `sub_params` = old args, `sup_params` = new args. +/// For outputs (covariant): `sub_params` = new rets, `sup_params` = old rets. +#[allow(clippy::too_many_arguments)] +fn check_func_params( + report: OptReport, + gamma: &mut Gamma, + env: &TypeEnv, + sub_params: &[ArgType], + sup_params: &[ArgType], + depth: &RecursionDepth, + path: &mut Vec, + errors: &mut Vec, + label: &str, // "input" or "return" + is_input: bool, // affects wording +) { + // Use the same tuple wrapping as the original subtype_ for correctness, + // but when there's a single parameter, check it directly to avoid noise. + if sub_params.len() == 1 && sup_params.len() == 1 { + path.push(format!("{label} type")); + subtype_collect_( + report, + gamma, + env, + &sub_params[0].typ, + &sup_params[0].typ, + depth, + path, + errors, + is_input, + ); + path.pop(); + } else { + let sub_tuple = to_tuple(sub_params); + let sup_tuple = to_tuple(sup_params); + path.push(if sub_params.len() == sup_params.len() { + format!("{label} types") + } else if is_input { + // sub_params = old args, sup_params = new args (contravariant swap) + format!( + "{label} types (old has {} arg{}, new has {})", + sub_params.len(), + if sub_params.len() == 1 { "" } else { "s" }, + sup_params.len() + ) + } else { + format!( + "{label} types (old has {} value{}, new has {})", + sup_params.len(), + if sup_params.len() == 1 { "" } else { "s" }, + sub_params.len() + ) + }); + subtype_collect_( + report, gamma, env, &sub_tuple, &sup_tuple, depth, path, errors, is_input, + ); + path.pop(); + } +} + +fn pp_modes(modes: &[super::internal::FuncMode]) -> String { + if modes.is_empty() { + return String::new(); + } + modes + .iter() + .map(|m| match m { + super::internal::FuncMode::Oneway => "oneway", + super::internal::FuncMode::Query => "query", + super::internal::FuncMode::CompositeQuery => "composite_query", + }) + .collect::>() + .join(" ") } fn subtype_( @@ -34,7 +514,9 @@ fn subtype_( env: &TypeEnv, t1: &Type, t2: &Type, + depth: &RecursionDepth, ) -> Result<()> { + let _guard = depth.guard()?; use TypeInner::*; if t1 == t2 { return Ok(()); @@ -44,10 +526,24 @@ fn subtype_( return Ok(()); } let res = match (t1.as_ref(), t2.as_ref()) { - (Var(id), _) => subtype_(report, gamma, env, env.rec_find_type(id).unwrap(), t2), - (_, Var(id)) => subtype_(report, gamma, env, t1, env.rec_find_type(id).unwrap()), - (Knot(id), _) => subtype_(report, gamma, env, &find_type(id).unwrap(), t2), - (_, Knot(id)) => subtype_(report, gamma, env, t1, &find_type(id).unwrap()), + (Var(id), _) => subtype_( + report, + gamma, + env, + env.rec_find_type_with_depth(id, depth).unwrap(), + t2, + depth, + ), + (_, Var(id)) => subtype_( + report, + gamma, + env, + t1, + env.rec_find_type_with_depth(id, depth).unwrap(), + depth, + ), + (Knot(id), _) => subtype_(report, gamma, env, &find_type(id).unwrap(), t2, depth), + (_, Knot(id)) => subtype_(report, gamma, env, t1, &find_type(id).unwrap(), depth), (_, _) => unreachable!(), }; if res.is_err() { @@ -59,12 +555,15 @@ fn subtype_( (_, Reserved) => Ok(()), (Empty, _) => Ok(()), (Nat, Int) => Ok(()), - (Vec(ty1), Vec(ty2)) => subtype_(report, gamma, env, ty1, ty2), + (Vec(ty1), Vec(ty2)) => subtype_(report, gamma, env, ty1, ty2, depth), (Null, Opt(_)) => Ok(()), - (Opt(ty1), Opt(ty2)) if subtype_(report, gamma, env, ty1, ty2).is_ok() => Ok(()), + (Opt(ty1), Opt(ty2)) if subtype_(report, gamma, env, ty1, ty2, depth).is_ok() => Ok(()), (_, Opt(ty2)) - if subtype_(report, gamma, env, t1, ty2).is_ok() - && !matches!(env.trace_type(ty2)?.as_ref(), Null | Reserved | Opt(_)) => + if subtype_(report, gamma, env, t1, ty2, depth).is_ok() + && !matches!( + env.trace_type_with_depth(ty2, depth)?.as_ref(), + Null | Reserved | Opt(_) + ) => { Ok(()) } @@ -81,11 +580,16 @@ fn subtype_( let fields: HashMap<_, _> = fs1.iter().map(|Field { id, ty }| (id, ty)).collect(); for Field { id, ty: ty2 } in fs2 { match fields.get(id) { - Some(ty1) => subtype_(report, gamma, env, ty1, ty2).with_context(|| { - format!("Record field {id}: {ty1} is not a subtype of {ty2}") - })?, + Some(ty1) => { + subtype_(report, gamma, env, ty1, ty2, depth).with_context(|| { + format!("Record field {id}: {ty1} is not a subtype of {ty2}") + })? + } None => { - if !matches!(env.trace_type(ty2)?.as_ref(), Null | Reserved | Opt(_)) { + if !matches!( + env.trace_type_with_depth(ty2, depth)?.as_ref(), + Null | Reserved | Opt(_) + ) { return Err(Error::msg(format!("Record field {id}: {ty2} is only in the expected type and is not of type opt, null or reserved"))); } } @@ -97,13 +601,15 @@ fn subtype_( let fields: HashMap<_, _> = fs2.iter().map(|Field { id, ty }| (id, ty)).collect(); for Field { id, ty: ty1 } in fs1 { match fields.get(id) { - Some(ty2) => subtype_(report, gamma, env, ty1, ty2).with_context(|| { - format!("Variant field {id}: {ty1} is not a subtype_ of {ty2}") - })?, + Some(ty2) => { + subtype_(report, gamma, env, ty1, ty2, depth).with_context(|| { + format!("Variant field {id}: {ty1} is not a subtype of {ty2}") + })? + } None => { return Err(Error::msg(format!( "Variant field {id} not found in the expected type" - ))) + ))); } } } @@ -113,13 +619,15 @@ fn subtype_( let meths: HashMap<_, _> = ms1.iter().cloned().collect(); for (name, ty2) in ms2 { match meths.get(name) { - Some(ty1) => subtype_(report, gamma, env, ty1, ty2).with_context(|| { - format!("Method {name}: {ty1} is not a subtype of {ty2}") - })?, + Some(ty1) => { + subtype_(report, gamma, env, ty1, ty2, depth).with_context(|| { + format!("Method {name}: {ty1} is not a subtype of {ty2}") + })? + } None => { return Err(Error::msg(format!( "Method {name} is only in the expected type" - ))) + ))); } } } @@ -133,15 +641,15 @@ fn subtype_( let args2 = to_tuple(&f2.args); let rets1 = to_tuple(&f1.rets); let rets2 = to_tuple(&f2.rets); - subtype_(report, gamma, env, &args2, &args1) + subtype_(report, gamma, env, &args2, &args1, depth) .context("Subtype fails at function input type")?; - subtype_(report, gamma, env, &rets1, &rets2) + subtype_(report, gamma, env, &rets1, &rets2, depth) .context("Subtype fails at function return type")?; Ok(()) } // This only works in the first order case, but service constructor only appears at the top level according to the spec. - (Class(_, t), _) => subtype_(report, gamma, env, t, t2), - (_, Class(_, t)) => subtype_(report, gamma, env, t1, t), + (Class(_, t), _) => subtype_(report, gamma, env, t, t2, depth), + (_, Class(_, t)) => subtype_(report, gamma, env, t1, t, depth), (Unknown, _) => unreachable!(), (_, Unknown) => unreachable!(), (_, _) => Err(Error::msg(format!("{t1} is not a subtype of {t2}"))), @@ -151,6 +659,17 @@ fn subtype_( /// Check if t1 and t2 are structurally equivalent, ignoring the variable naming differences. /// Note that this is more strict than `t1 <: t2` and `t2 <: t1`, because of the special opt rule. pub fn equal(gamma: &mut Gamma, env: &TypeEnv, t1: &Type, t2: &Type) -> Result<()> { + equal_impl(gamma, env, t1, t2, &RecursionDepth::new()) +} + +fn equal_impl( + gamma: &mut Gamma, + env: &TypeEnv, + t1: &Type, + t2: &Type, + depth: &RecursionDepth, +) -> Result<()> { + let _guard = depth.guard()?; use TypeInner::*; if t1 == t2 { return Ok(()); @@ -160,10 +679,22 @@ pub fn equal(gamma: &mut Gamma, env: &TypeEnv, t1: &Type, t2: &Type) -> Result<( return Ok(()); } let res = match (t1.as_ref(), t2.as_ref()) { - (Var(id), _) => equal(gamma, env, env.rec_find_type(id).unwrap(), t2), - (_, Var(id)) => equal(gamma, env, t1, env.rec_find_type(id).unwrap()), - (Knot(id), _) => equal(gamma, env, &find_type(id).unwrap(), t2), - (_, Knot(id)) => equal(gamma, env, t1, &find_type(id).unwrap()), + (Var(id), _) => equal_impl( + gamma, + env, + env.rec_find_type_with_depth(id, depth).unwrap(), + t2, + depth, + ), + (_, Var(id)) => equal_impl( + gamma, + env, + t1, + env.rec_find_type_with_depth(id, depth).unwrap(), + depth, + ), + (Knot(id), _) => equal_impl(gamma, env, &find_type(id).unwrap(), t2, depth), + (_, Knot(id)) => equal_impl(gamma, env, t1, &find_type(id).unwrap(), depth), (_, _) => unreachable!(), }; if res.is_err() { @@ -172,8 +703,8 @@ pub fn equal(gamma: &mut Gamma, env: &TypeEnv, t1: &Type, t2: &Type) -> Result<( return res; } match (t1.as_ref(), t2.as_ref()) { - (Opt(ty1), Opt(ty2)) => equal(gamma, env, ty1, ty2), - (Vec(ty1), Vec(ty2)) => equal(gamma, env, ty1, ty2), + (Opt(ty1), Opt(ty2)) => equal_impl(gamma, env, ty1, ty2, depth), + (Vec(ty1), Vec(ty2)) => equal_impl(gamma, env, ty1, ty2, depth), (Record(fs1), Record(fs2)) | (Variant(fs1), Variant(fs2)) => { assert_length(fs1, fs2, |x| x.id.clone(), |x| x.to_string()) .context("Different field length")?; @@ -184,7 +715,7 @@ pub fn equal(gamma: &mut Gamma, env: &TypeEnv, t1: &Type, t2: &Type) -> Result<( f1.id, f2.id ))); } - equal(gamma, env, &f1.ty, &f2.ty).context(format!( + equal_impl(gamma, env, &f1.ty, &f2.ty, depth).context(format!( "Field {} has different types: {} and {}", f1.id, f1.ty, f2.ty ))?; @@ -201,7 +732,7 @@ pub fn equal(gamma: &mut Gamma, env: &TypeEnv, t1: &Type, t2: &Type) -> Result<( m1.0, m2.0 ))); } - equal(gamma, env, &m1.1, &m2.1).context(format!( + equal_impl(gamma, env, &m1.1, &m2.1, depth).context(format!( "Method {} has different types: {} and {}", m1.0, m1.1, m2.1 ))?; @@ -216,19 +747,21 @@ pub fn equal(gamma: &mut Gamma, env: &TypeEnv, t1: &Type, t2: &Type) -> Result<( let args2 = to_tuple(&f2.args); let rets1 = to_tuple(&f1.rets); let rets2 = to_tuple(&f2.rets); - equal(gamma, env, &args1, &args2).context("Mismatch in function input type")?; - equal(gamma, env, &rets1, &rets2).context("Mismatch in function return type")?; + equal_impl(gamma, env, &args1, &args2, depth) + .context("Mismatch in function input type")?; + equal_impl(gamma, env, &rets1, &rets2, depth) + .context("Mismatch in function return type")?; Ok(()) } (Class(init1, ty1), Class(init2, ty2)) => { let init_1 = to_tuple(init1); let init_2 = to_tuple(init2); - equal(gamma, env, &init_1, &init_2).context(format!( + equal_impl(gamma, env, &init_1, &init_2, depth).context(format!( "Mismatch in init args: {} and {}", pp_args(init1), pp_args(init2) ))?; - equal(gamma, env, ty1, ty2) + equal_impl(gamma, env, ty1, ty2, depth) } (Unknown, _) => unreachable!(), (_, Unknown) => unreachable!(), diff --git a/rust/candid/src/types/type_env.rs b/rust/candid/src/types/type_env.rs index 5f5aef1ce..0d22f7900 100644 --- a/rust/candid/src/types/type_env.rs +++ b/rust/candid/src/types/type_env.rs @@ -1,5 +1,6 @@ use crate::types::internal::TypeKey; use crate::types::{Function, Type, TypeInner}; +use crate::utils::RecursionDepth; use crate::{Error, Result}; use foldhash::fast::FixedState; use hashbrown::HashMap; @@ -48,33 +49,65 @@ impl TypeEnv { } pub fn rec_find_type(&self, name: &TypeKey) -> Result<&Type> { + self.rec_find_type_with_depth(name, &RecursionDepth::new()) + } + pub(crate) fn rec_find_type_with_depth( + &self, + name: &TypeKey, + depth: &RecursionDepth, + ) -> Result<&Type> { + let _guard = depth.guard()?; let t = self.find_type(name)?; match t.as_ref() { - TypeInner::Var(id) => self.rec_find_type(id), + TypeInner::Var(id) => self.rec_find_type_with_depth(id, depth), _ => Ok(t), } } pub fn trace_type<'a>(&'a self, t: &'a Type) -> Result { + self.trace_type_with_depth(t, &RecursionDepth::new()) + } + pub(crate) fn trace_type_with_depth<'a>( + &'a self, + t: &'a Type, + depth: &RecursionDepth, + ) -> Result { + let _guard = depth.guard()?; match t.as_ref() { - TypeInner::Var(id) => self.trace_type(self.find_type(id)?), + TypeInner::Var(id) => self.trace_type_with_depth(self.find_type(id)?, depth), TypeInner::Knot(ref id) => { - self.trace_type(&crate::types::internal::find_type(id).unwrap()) + self.trace_type_with_depth(&crate::types::internal::find_type(id).unwrap(), depth) } _ => Ok(t.clone()), } } pub fn as_func<'a>(&'a self, t: &'a Type) -> Result<&'a Function> { + self.as_func_with_depth(t, &RecursionDepth::new()) + } + fn as_func_with_depth<'a>( + &'a self, + t: &'a Type, + depth: &RecursionDepth, + ) -> Result<&'a Function> { + let _guard = depth.guard()?; match t.as_ref() { TypeInner::Func(f) => Ok(f), - TypeInner::Var(id) => self.as_func(self.find_type(id)?), + TypeInner::Var(id) => self.as_func_with_depth(self.find_type(id)?, depth), _ => Err(Error::msg(format!("not a function type: {t}"))), } } pub fn as_service<'a>(&'a self, t: &'a Type) -> Result<&'a [(String, Type)]> { + self.as_service_with_depth(t, &RecursionDepth::new()) + } + fn as_service_with_depth<'a>( + &'a self, + t: &'a Type, + depth: &RecursionDepth, + ) -> Result<&'a [(String, Type)]> { + let _guard = depth.guard()?; match t.as_ref() { TypeInner::Service(s) => Ok(s), - TypeInner::Var(id) => self.as_service(self.find_type(id)?), - TypeInner::Class(_, ty) => self.as_service(ty), + TypeInner::Var(id) => self.as_service_with_depth(self.find_type(id)?, depth), + TypeInner::Class(_, ty) => self.as_service_with_depth(ty, depth), _ => Err(Error::msg(format!("not a service type: {t}"))), } } @@ -90,8 +123,10 @@ impl TypeEnv { &'a self, res: &mut HashMap<&'a TypeKey, Option, FixedState>, id: &'a TypeKey, + depth: &RecursionDepth, ) -> Result { - match res.get(id) { + let _guard = depth.guard()?; + let result = match res.get(id) { None => { res.insert(id, None); let t = self.find_type(id)?; @@ -100,7 +135,7 @@ impl TypeEnv { for f in fs { // Assume env only comes from type table, f.ty is either primitive or var. if let TypeInner::Var(f_id) = f.ty.as_ref() { - if self.is_empty(res, f_id)? { + if self.is_empty(res, f_id, depth)? { res.insert(id, Some(true)); return Ok(true); } @@ -108,23 +143,24 @@ impl TypeEnv { } false } - TypeInner::Var(id) => self.is_empty(res, id)?, + TypeInner::Var(id) => self.is_empty(res, id, depth)?, _ => false, }; res.insert(id, Some(result)); - Ok(result) + result } Some(None) => { res.insert(id, Some(true)); - Ok(true) + true } - Some(Some(b)) => Ok(*b), - } + Some(Some(b)) => *b, + }; + Ok(result) } pub fn replace_empty(&mut self) -> Result<()> { let mut res = HashMap::default(); for key in self.0.keys() { - self.is_empty(&mut res, key)?; + self.is_empty(&mut res, key, &RecursionDepth::new())?; } let ids: Vec<_> = res .into_iter() diff --git a/rust/candid/src/types/value.rs b/rust/candid/src/types/value.rs index 4720d6569..efdb87186 100644 --- a/rust/candid/src/types/value.rs +++ b/rust/candid/src/types/value.rs @@ -160,14 +160,24 @@ impl IDLValue { /// string, we need to set `from_parser` to true to enable converting numbers to the expected /// types, and disable the opt rules. pub fn annotate_type(&self, from_parser: bool, env: &TypeEnv, t: &Type) -> Result { + self.annotate_type_with_depth(from_parser, env, t, &crate::utils::RecursionDepth::new()) + } + fn annotate_type_with_depth( + &self, + from_parser: bool, + env: &TypeEnv, + t: &Type, + depth: &crate::utils::RecursionDepth, + ) -> Result { + let _guard = depth.guard()?; Ok(match (self, t.as_ref()) { (_, TypeInner::Var(id)) => { - let ty = env.rec_find_type(id)?; - self.annotate_type(from_parser, env, ty)? + let ty = env.rec_find_type_with_depth(id, depth)?; + self.annotate_type_with_depth(from_parser, env, ty, depth)? } (_, TypeInner::Knot(ref id)) => { let ty = crate::types::internal::find_type(id).unwrap(); - self.annotate_type(from_parser, env, &ty)? + self.annotate_type_with_depth(from_parser, env, &ty, depth)? } (_, TypeInner::Reserved) => IDLValue::Reserved, (IDLValue::Float64(n), TypeInner::Float32) if from_parser => { @@ -193,23 +203,23 @@ impl IDLValue { (IDLValue::Null, TypeInner::Opt(_)) => IDLValue::None, (IDLValue::Reserved, TypeInner::Opt(_)) => IDLValue::None, (IDLValue::None, TypeInner::Opt(_)) => IDLValue::None, - (IDLValue::Opt(v), TypeInner::Opt(ty)) if from_parser => { - IDLValue::Opt(Box::new(v.annotate_type(from_parser, env, ty)?)) - } + (IDLValue::Opt(v), TypeInner::Opt(ty)) if from_parser => IDLValue::Opt(Box::new( + v.annotate_type_with_depth(from_parser, env, ty, depth)?, + )), // liberal decoding of optionals (IDLValue::Opt(v), TypeInner::Opt(ty)) if !from_parser => v - .annotate_type(from_parser, env, ty) + .annotate_type_with_depth(from_parser, env, ty, depth) .map(|v| IDLValue::Opt(Box::new(v))) .unwrap_or(IDLValue::None), // try consituent type (v, TypeInner::Opt(ty)) if !from_parser && !matches!( - env.trace_type(ty)?.as_ref(), + env.trace_type_with_depth(ty, depth)?.as_ref(), TypeInner::Null | TypeInner::Reserved | TypeInner::Opt(_) ) => { - v.annotate_type(from_parser, env, ty) + v.annotate_type_with_depth(from_parser, env, ty, depth) .map(|v| IDLValue::Opt(Box::new(v))) .unwrap_or(IDLValue::None) } @@ -234,7 +244,7 @@ impl IDLValue { (IDLValue::Vec(vec), TypeInner::Vec(ty)) => { let mut res = Vec::with_capacity(vec.len()); for e in vec.iter() { - let v = e.annotate_type(from_parser, env, ty)?; + let v = e.annotate_type_with_depth(from_parser, env, ty, depth)?; res.push(v); } IDLValue::Vec(res) @@ -247,14 +257,16 @@ impl IDLValue { let val = fields .get(id.as_ref()) .cloned() - .or_else(|| match env.trace_type(ty).unwrap().as_ref() { - TypeInner::Null => Some(&IDLValue::Null), - TypeInner::Opt(_) => Some(&IDLValue::None), - TypeInner::Reserved => Some(&IDLValue::Reserved), - _ => None, - }) + .or_else( + || match env.trace_type_with_depth(ty, depth).unwrap().as_ref() { + TypeInner::Null => Some(&IDLValue::Null), + TypeInner::Opt(_) => Some(&IDLValue::None), + TypeInner::Reserved => Some(&IDLValue::Reserved), + _ => None, + }, + ) .ok_or_else(|| Error::msg(format!("record field {id} not found")))?; - let val = val.annotate_type(from_parser, env, ty)?; + let val = val.annotate_type_with_depth(from_parser, env, ty, depth)?; res.push(IDLField { id: id.as_ref().clone(), val, @@ -265,7 +277,9 @@ impl IDLValue { (IDLValue::Variant(v), TypeInner::Variant(fs)) => { for (i, f) in fs.iter().enumerate() { if v.0.id == *f.id { - let val = v.0.val.annotate_type(from_parser, env, &f.ty)?; + let val = + v.0.val + .annotate_type_with_depth(from_parser, env, &f.ty, depth)?; let field = IDLField { id: f.id.as_ref().clone(), val, diff --git a/rust/candid/src/utils.rs b/rust/candid/src/utils.rs index 39061244c..1f5638a85 100644 --- a/rust/candid/src/utils.rs +++ b/rust/candid/src/utils.rs @@ -3,6 +3,78 @@ use crate::ser::IDLBuilder; use crate::{CandidType, Error, Result}; use serde::de::Deserialize; +/// Guard against stack overflow from deep recursion. +/// +/// On native platforms, uses `stacker::remaining_stack()` to check the actual +/// remaining stack space (< 32 KiB triggers an error). If the stack size cannot +/// be determined (exotic platforms), falls back to a conservative depth limit of 512. +/// +/// On wasm targets, this is a no-op. Wasm execution is always sandboxed (both in +/// canisters and in browsers), so a stack overflow cannot compromise the host. +/// Imposing a fixed depth limit would risk rejecting legitimate deeply-nested +/// payloads, since `stacker` cannot measure the actual remaining stack on wasm. +fn check_recursion_depth(_depth: u16) -> Result<()> { + #[cfg(not(target_family = "wasm"))] + match stacker::remaining_stack() { + Some(size) if size < 32768 => { + return Err(Error::msg(format!( + "Recursion limit exceeded at depth {_depth}" + ))) + } + None if _depth > 512 => { + return Err(Error::msg(format!( + "Recursion limit exceeded at depth {_depth}. Cannot detect stack size, use a conservative bound" + ))) + } + _ => (), + } + Ok(()) +} + +/// Tracks recursion depth with overflow protection. +/// +/// Use `guard()` to increment depth with automatic decrement via RAII. +/// Uses interior mutability to allow passing to recursive functions and cloning. +#[derive(Debug, Clone)] +pub(crate) struct RecursionDepth(std::rc::Rc>); + +impl RecursionDepth { + /// Creates a new recursion depth tracker starting at 0. + pub(crate) fn new() -> Self { + Self(std::rc::Rc::new(std::cell::Cell::new(0))) + } + + /// Increments depth and returns a guard that will automatically decrement on drop. + /// + /// Returns an error if recursion limit is exceeded. + pub(crate) fn guard(&self) -> Result { + let current = self.0.get(); + let new_depth = current + .checked_add(1) + .ok_or_else(|| Error::msg("Recursion depth overflow"))?; + self.0.set(new_depth); + let guard = DepthGuard(self.0.clone()); + check_recursion_depth(new_depth)?; + Ok(guard) + } +} + +/// RAII guard that automatically decrements recursion depth on drop. +/// +/// Created by calling `RecursionDepth::guard()`. +#[derive(Debug)] +pub(crate) struct DepthGuard(std::rc::Rc>); + +impl Drop for DepthGuard { + fn drop(&mut self) { + let current = self.0.get(); + let new_depth = current + .checked_sub(1) + .expect("Recursion depth underflow - this is a bug"); + self.0.set(new_depth); + } +} + pub fn check_unique<'a, I, T>(sorted: I) -> Result<()> where T: 'a + PartialEq + std::fmt::Display, @@ -90,6 +162,9 @@ macro_rules! Decode { $de.get_value::<$ty>() .and_then(|val| Decode!(@GetValue [$($ans)* val] $de $($tail,)* )) }}; + (@GetValue [$ans:ident] $de:ident) => {{ + Ok($ans) + }}; (@GetValue [$($ans:ident)*] $de:ident) => {{ Ok(($($ans),*)) }}; @@ -526,3 +601,18 @@ encode_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l encode_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L, m: M, n: N, o: O); #[rustfmt::skip] encode_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L, m: M, n: N, o: O, p: P); + +#[cfg(test)] +mod tests { + + /// Ensures that the code does not trigger the `clippy::double_parens` lint. + /// This test was added to prevent regressions where unnecessary parentheses + /// could cause Clippy warnings or errors. + #[test] + fn test_decode_single_value() { + let bytes = Encode!(&"hello").unwrap(); + // This code used to get `clippy::double_parens` error. + let value = Decode!(&bytes, String).unwrap(); + assert_eq!(value, "hello"); + } +} diff --git a/rust/candid/tests/compatibility_numbers.rs b/rust/candid/tests/compatibility_numbers.rs new file mode 100644 index 000000000..c5f756a01 --- /dev/null +++ b/rust/candid/tests/compatibility_numbers.rs @@ -0,0 +1,75 @@ +use candid::{Decode, Encode, Int, Nat}; + +#[test] +fn number_fast_paths_preserve_small_and_large_values() { + let nat_values = vec![ + Nat::from(0u8), + Nat::from(42u64), + Nat::from(u64::MAX), + Nat::parse(b"24197857200151251861972493965091130863").unwrap(), + ]; + let nat_bytes = Encode!(&nat_values).unwrap(); + let decoded_nat_values = Decode!(&nat_bytes, Vec).unwrap(); + assert_eq!(decoded_nat_values, nat_values); + + let int_values = vec![ + Int::from(0), + Int::from(-1), + Int::from(42), + Int::from(i64::MIN), + Int::from(i64::MAX), + Int::parse(b"24197857200151251861972493965091130863").unwrap(), + Int::parse(b"-600000000000000000000000000000000000").unwrap(), + ]; + let int_bytes = Encode!(&int_values).unwrap(); + let decoded_int_values = Decode!(&int_bytes, Vec).unwrap(); + assert_eq!(decoded_int_values, int_values); +} + +/// Test the exact fast-path/BigInt transition boundaries using raw encode/decode. +#[test] +fn nat_decode_fast_path_boundaries() { + fn roundtrip(n: Nat) -> Nat { + let mut buf = Vec::new(); + n.encode(&mut buf).unwrap(); + Nat::decode(&mut buf.as_slice()).unwrap() + } + + // Last value handled entirely by the u64 fast path. + assert_eq!(roundtrip(Nat::from(u64::MAX)), Nat::from(u64::MAX)); + + // First value that overflows u64: must fall back to BigInt. + // u64::MAX + 1 = 18446744073709551616 + let just_over = Nat::parse(b"18446744073709551616").unwrap(); + assert_eq!(roundtrip(just_over.clone()), just_over); + + // A few values straddling the boundary. + for delta in 0u64..=8 { + let base = Nat::from(u64::MAX) - Nat::from(delta); + assert_eq!(roundtrip(base.clone()), base); + } +} + +/// Test the exact fast-path/BigInt transition boundaries for signed LEB128. +#[test] +fn int_decode_fast_path_boundaries() { + fn roundtrip(n: Int) -> Int { + let mut buf = Vec::new(); + n.encode(&mut buf).unwrap(); + Int::decode(&mut buf.as_slice()).unwrap() + } + + // Largest positive i64: handled by fast path. + assert_eq!(roundtrip(Int::from(i64::MAX)), Int::from(i64::MAX)); + + // 2^63 = i64::MAX + 1: fits u64 but not i64 (positive BigInt branch). + let just_over_pos = Int::parse(b"9223372036854775808").unwrap(); + assert_eq!(roundtrip(just_over_pos.clone()), just_over_pos); + + // Most negative i64: fast path with sign extension. + assert_eq!(roundtrip(Int::from(i64::MIN)), Int::from(i64::MIN)); + + // i64::MIN - 1: must fall back to BigInt negative path. + let just_under_neg = Int::parse(b"-9223372036854775809").unwrap(); + assert_eq!(roundtrip(just_under_neg.clone()), just_under_neg); +} diff --git a/rust/candid/tests/compatibility_records.rs b/rust/candid/tests/compatibility_records.rs new file mode 100644 index 000000000..e67fdc2c6 --- /dev/null +++ b/rust/candid/tests/compatibility_records.rs @@ -0,0 +1,52 @@ +use candid::{CandidType, Decode, Deserialize, Encode}; + +#[test] +fn record_vectors_remain_backward_and_forward_compatible() { + #[derive(Clone, Debug, PartialEq, Eq, Deserialize, CandidType)] + struct Old { + id: u32, + flag: bool, + } + + #[derive(Clone, Debug, PartialEq, Eq, Deserialize, CandidType)] + struct New { + extra: Option, + id: u32, + flag: bool, + } + + let old_values = vec![Old { id: 1, flag: true }, Old { id: 2, flag: false }]; + let old_bytes = Encode!(&old_values).unwrap(); + let decoded_new_values = Decode!(&old_bytes, Vec).unwrap(); + assert_eq!( + decoded_new_values, + vec![ + New { + extra: None, + id: 1, + flag: true, + }, + New { + extra: None, + id: 2, + flag: false, + }, + ] + ); + + let new_values = vec![ + New { + extra: Some(10), + id: 1, + flag: true, + }, + New { + extra: None, + id: 2, + flag: false, + }, + ]; + let new_bytes = Encode!(&new_values).unwrap(); + let decoded_old_values = Decode!(&new_bytes, Vec).unwrap(); + assert_eq!(decoded_old_values, old_values); +} diff --git a/rust/candid/tests/compatibility_vectors.rs b/rust/candid/tests/compatibility_vectors.rs new file mode 100644 index 000000000..b76e9904d --- /dev/null +++ b/rust/candid/tests/compatibility_vectors.rs @@ -0,0 +1,87 @@ +use candid::{CandidType, Decode, Deserialize, Encode}; + +#[test] +fn primitive_vector_decode_stays_compatible_with_extra_args() { + let values = vec![-1i16, 0, 1, 42, i16::MIN, i16::MAX]; + let bytes = Encode!(&values, &123u8).unwrap(); + + let decoded_values = Decode!(&bytes, Vec).unwrap(); + assert_eq!(decoded_values, values); + + let (decoded_values, trailing) = Decode!(&bytes, Vec, u8).unwrap(); + assert_eq!(decoded_values, values); + assert_eq!(trailing, 123); +} + +#[test] +fn nested_primitive_vector_decode() { + // Outer vec is non-primitive so exact_primitive is None; inner vecs use the fast path. + let values: Vec> = vec![vec![1, 2], vec![], vec![3, i16::MIN, i16::MAX]]; + let bytes = Encode!(&values).unwrap(); + let decoded: Vec> = Decode!(&bytes, Vec>).unwrap(); + assert_eq!(decoded, values); +} + +#[test] +fn struct_with_primitive_vector_field() { + // Ensures primitive_vec_fast_path is correctly restored when a vec + // appears as a struct field alongside other fields. + #[derive(CandidType, Deserialize, PartialEq, Debug)] + struct S { + xs: Vec, + y: u8, + } + let s = S { + xs: vec![1, -1, i32::MAX], + y: 42, + }; + let bytes = Encode!(&s).unwrap(); + let decoded = Decode!(&bytes, S).unwrap(); + assert_eq!(decoded, s); +} + +#[test] +fn mismatched_rust_type_does_not_use_fast_path() { + // Wire type is vec nat16 but Rust target is Vec: expect and wire differ, + // so exact_primitive is None and the normal type-checking path rejects it. + let values: Vec = vec![1, 2, 3]; + let bytes = Encode!(&values).unwrap(); + assert!(Decode!(&bytes, Vec).is_err()); +} + +#[test] +fn bulk_encode_primitive_vectors_round_trip() { + let u8s: Vec = vec![0, 1, 127, 255]; + assert_eq!(Decode!(&Encode!(&u8s).unwrap(), Vec).unwrap(), u8s); + + let i16s: Vec = vec![i16::MIN, -1, 0, 1, i16::MAX]; + assert_eq!(Decode!(&Encode!(&i16s).unwrap(), Vec).unwrap(), i16s); + + let u32s: Vec = vec![0, 1, u32::MAX]; + assert_eq!(Decode!(&Encode!(&u32s).unwrap(), Vec).unwrap(), u32s); + + let f64s: Vec = vec![0.0, -1.0, f64::INFINITY, f64::NAN]; + let decoded = Decode!(&Encode!(&f64s).unwrap(), Vec).unwrap(); + assert_eq!(decoded[0], 0.0); + assert_eq!(decoded[1], -1.0); + assert_eq!(decoded[2], f64::INFINITY); + assert!(decoded[3].is_nan()); + + let bools: Vec = vec![true, false, true]; + assert_eq!( + Decode!(&Encode!(&bools).unwrap(), Vec).unwrap(), + bools + ); + + let empty: Vec = vec![]; + assert_eq!(Decode!(&Encode!(&empty).unwrap(), Vec).unwrap(), empty); +} + +#[test] +fn primitive_vector_is_extra_args() { + let extra = vec![1i16, 2, 3]; + let bytes = Encode!(&123u8, &extra).unwrap(); + + let decoded_values = Decode!(&bytes, u8).unwrap(); + assert_eq!(decoded_values, 123); +} diff --git a/rust/candid/tests/serde.rs b/rust/candid/tests/serde.rs index 6ae16206a..c521fb0a9 100644 --- a/rust/candid/tests/serde.rs +++ b/rust/candid/tests/serde.rs @@ -76,6 +76,87 @@ fn test_integer() { check_error(|| test_decode(&hex("4449444c00017c2a"), &42i64), "Int64"); } +// Regression for the v0.10.27 round-trip bug: the LEB128 fast path in +// `deserialize_nat`/`deserialize_int` silently truncated values that didn't +// fit in u64/i64 (e.g. `1 << 64` decoded as 0). The fast path now bails to +// the bignum decoder for any value spanning bit 63 or beyond. +#[test] +fn test_bignum_roundtrip_across_fast_path_boundary() { + use std::ops::Mul; + + // The minimal reproducer from the original bug report. + #[derive(CandidType, Deserialize, Debug, PartialEq)] + struct Foo { + amount: Option, + } + let foo = Foo { + amount: Some(Nat::from(1_u128 << 64)), + }; + let bytes = Encode!(&foo).unwrap(); + assert_eq!(Decode!(&bytes, Foo).unwrap(), foo); + + // Sweep Nat across the 63/64/65/... bit boundary plus a value past u128. + for k in 60..=68 { + for v in [(1_u128 << k) - 1, 1_u128 << k, (1_u128 << k) + 1] { + let n = Nat::from(v); + let bytes = Encode!(&n).unwrap(); + assert_eq!(Decode!(&bytes, Nat).unwrap(), n, "Nat roundtrip {v}"); + } + } + let big = Nat::from(u128::MAX).mul(Nat::from(7u32)); + assert_eq!(Decode!(&Encode!(&big).unwrap(), Nat).unwrap(), big); + + // Int boundary: values straddling i64 limits and a 65-bit value. + for v in [ + i64::MAX as i128 - 1, + i64::MAX as i128, + i64::MAX as i128 + 1, + i64::MIN as i128 - 1, + i64::MIN as i128, + i64::MIN as i128 + 1, + 1_i128 << 65, + -(1_i128 << 65), + ] { + let n = Int::from(v); + let bytes = Encode!(&n).unwrap(); + assert_eq!(Decode!(&bytes, Int).unwrap(), n, "Int roundtrip {v}"); + } +} + +// Regression for a pre-existing bug in `Int::decode`'s small-value fast +// path (#717): at shift=63 the `fits_i64` check treated a chunk whose +// non-data bits matched the sign-extension pattern as "fits", even when +// the byte's continuation bit was set. The shift `low_bits << 63` then +// silently truncated, and the resulting `i64` value was carried into the +// `BigInt` slow path as a corrupt seed — e.g. `i128::MAX` round-tripped +// to -1. +#[test] +fn test_int_decode_large_magnitude_roundtrip() { + use std::ops::Mul; + for v in [ + i128::MAX, + i128::MIN, + i128::MAX - 1, + i128::MIN + 1, + 1_i128 << 100, + -(1_i128 << 100), + (1_i128 << 126) - 1, + -(1_i128 << 126), + ] { + let n = Int::from(v); + let bytes = Encode!(&n).unwrap(); + assert_eq!(Decode!(&bytes, Int).unwrap(), n, "Int roundtrip {v}"); + } + // Beyond i128: ±(i128::MAX * 3). + let huge = Int::from(i128::MAX).mul(Int::from(3)); + assert_eq!(Decode!(&Encode!(&huge).unwrap(), Int).unwrap(), huge); + let huge_neg = Int::from(i128::MIN).mul(Int::from(3)); + assert_eq!( + Decode!(&Encode!(&huge_neg).unwrap(), Int).unwrap(), + huge_neg + ); +} + #[test] fn test_fixed_number() { all_check(42u8, "4449444c00017b2a"); @@ -715,6 +796,45 @@ fn test_vector() { ); } +/// Regression test: decoding a `BTreeMap` where `V` is an enum containing +/// a `BTreeMap` with a non-text enum key used to fail with "Type mismatch". +/// +/// The bug was that `text_fast_path`, set to `true` by the outer string-keyed map's +/// `deserialize_map`, leaked into the inner map's `next_key_seed`. There it +/// suppressed the update of `expect_type`/`wire_type` for the inner key, so that +/// `deserialize_enum` on the inner key saw a stale (non-variant) `expect_type` and +/// raised a subtyping error at `de.rs:1380`. +#[test] +fn test_nested_map_non_text_key() { + use std::collections::BTreeMap; + + #[derive(CandidType, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] + enum MapKey { + A, + B, + } + + #[derive(CandidType, Deserialize, Debug, Clone, PartialEq)] + enum Value { + Fun(BTreeMap), + Lit(String), + } + + let mut inner: BTreeMap = BTreeMap::new(); + inner.insert(MapKey::A, 1); + inner.insert(MapKey::B, 2); + + let mut outer: BTreeMap = BTreeMap::new(); + outer.insert("fun".to_string(), Value::Fun(inner)); + outer.insert("lit".to_string(), Value::Lit("hello".to_string())); + + // encode → decode must round-trip without "Type mismatch" panic + let bytes = encode_one(&outer).unwrap(); + let config = get_config(); + let decoded = decode_one_with_config::>(&bytes, &config).unwrap(); + assert_eq!(outer, decoded); +} + #[test] fn test_collection() { use std::collections::{BTreeMap, BTreeSet, HashMap}; diff --git a/rust/candid/tests/types.rs b/rust/candid/tests/types.rs index da0742f08..0d37f8f04 100644 --- a/rust/candid/tests/types.rs +++ b/rust/candid/tests/types.rs @@ -354,3 +354,120 @@ fn test_init_named_args() { let expected = r#"service : (a : nat8) -> {}"#; assert_eq!(expected, __export_service()); } + +#[test] +fn test_export_service_type_and_field_docs() { + /// Status docs. + #[derive(CandidType, Deserialize)] + enum Status { + /// Account is active. + Active, + /// Account is banned. + Banned(String), + } + + /// User payload docs. + #[derive(CandidType, Deserialize)] + struct User { + /// Stable identifier. + id: u64, + /// Display name. + name: String, + } + + /// Lookup docs. + #[candid_method(query)] + fn lookup(_: User) -> Status { + unreachable!() + } + + candid::export_service!(); + let expected = r#"// Status docs. +type Status = variant { + // Account is active. + Active; + // Account is banned. + Banned : text; +}; +// User payload docs. +type User = record { + // Stable identifier. + id : nat64; + // Display name. + name : text; +}; +service : { + // Lookup docs. + lookup : (User) -> (Status) query; +}"#; + assert_eq!(expected, __export_service()); +} + +#[test] +fn test_exported_did_parses_with_docs_attached() { + use candid_parser::syntax::{Dec, IDLProg, IDLType}; + + /// Status docs. + #[derive(CandidType, Deserialize)] + enum Status { + /// Account is active. + Active, + /// Account is banned. + Banned(String), + } + + /// User payload docs. + #[derive(CandidType, Deserialize)] + struct User { + /// Stable identifier. + id: u64, + /// Display name. + name: String, + } + + /// Lookup docs. + #[candid_method(query)] + fn lookup(_: User) -> Status { + unreachable!() + } + + candid::export_service!(); + let ast: IDLProg = __export_service().parse().unwrap(); + + let status = ast + .decs + .iter() + .find_map(|dec| match dec { + Dec::TypD(binding) if binding.id == "Status" => Some(binding), + _ => None, + }) + .unwrap(); + assert_eq!(status.docs, vec!["Status docs."]); + let IDLType::VariantT(status_fields) = &status.typ else { + panic!("expected Status to be a variant"); + }; + assert_eq!(status_fields[0].docs, vec!["Account is active."]); + assert_eq!(status_fields[1].docs, vec!["Account is banned."]); + + let user = ast + .decs + .iter() + .find_map(|dec| match dec { + Dec::TypD(binding) if binding.id == "User" => Some(binding), + _ => None, + }) + .unwrap(); + assert_eq!(user.docs, vec!["User payload docs."]); + let IDLType::RecordT(user_fields) = &user.typ else { + panic!("expected User to be a record"); + }; + assert_eq!(user_fields[0].docs, vec!["Stable identifier."]); + assert_eq!(user_fields[1].docs, vec!["Display name."]); + + let actor = ast.actor.unwrap(); + let IDLType::ServT(methods) = &actor.typ else { + panic!("expected actor to be a service"); + }; + assert_eq!(methods[0].id, "lookup"); + assert_eq!(methods[0].docs, vec!["Lookup docs."]); +} diff --git a/rust/candid_derive/Cargo.toml b/rust/candid_derive/Cargo.toml index f9dcc0f21..6cc239299 100644 --- a/rust/candid_derive/Cargo.toml +++ b/rust/candid_derive/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "candid_derive" # sync with the version in `candid/Cargo.toml` -version = "0.10.20" +version = "0.10.29" edition = "2021" rust-version.workspace = true authors = ["DFINITY Team"] diff --git a/rust/candid_derive/src/derive.rs b/rust/candid_derive/src/derive.rs index 7f2838ff1..8da7fa26f 100644 --- a/rust/candid_derive/src/derive.rs +++ b/rust/candid_derive/src/derive.rs @@ -1,4 +1,4 @@ -use super::{candid_path, get_lit_str, idl_hash}; +use super::{candid_path, docs::extract_doc_comments, get_lit_str, idl_hash}; use proc_macro2::TokenStream; use quote::quote; use std::collections::BTreeSet; @@ -11,14 +11,22 @@ pub(crate) fn derive_idl_type( custom_candid_path: &Option, ) -> TokenStream { let candid = candid_path(custom_candid_path); + let root_docs = extract_doc_comments(&input.attrs); let name = input.ident; let generics = add_trait_bounds(input.generics, custom_candid_path); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - let (ty_body, ser_body) = match input.data { - Data::Enum(ref data) => enum_from_ast(&name, &data.variants, custom_candid_path), + let (ty_body, ty_doc_body, ser_body) = match input.data { + Data::Enum(ref data) => { + enum_from_ast(&name, &root_docs, &data.variants, custom_candid_path) + } Data::Struct(ref data) => { - let (ty, idents, is_bytes, _) = struct_from_ast(&data.fields, custom_candid_path); - (ty, serialize_struct(&idents, &is_bytes, custom_candid_path)) + let mut shape = struct_from_ast(&data.fields, custom_candid_path); + shape.doc.docs = root_docs; + ( + shape.ty, + quote_type_doc(&candid, &shape.doc), + serialize_struct(&shape.members, &shape.is_bytes, custom_candid_path), + ) } Data::Union(_) => unimplemented!("doesn't derive union type"), }; @@ -27,6 +35,9 @@ pub(crate) fn derive_idl_type( fn _ty() -> #candid::types::Type { #ty_body } + fn _ty_doc() -> #candid::types::TypeDoc { + #ty_doc_body + } fn id() -> #candid::types::TypeId { #candid::types::TypeId::of::<#name #ty_generics>() } fn idl_serialize<__S>(&self, __serializer: __S) -> ::std::result::Result<(), __S::Error> @@ -41,11 +52,50 @@ pub(crate) fn derive_idl_type( gen } +#[derive(Clone, Default)] +struct TypeDocSpec { + docs: Vec, + fields: Vec, +} + +impl TypeDocSpec { + fn is_empty(&self) -> bool { + self.docs.is_empty() && self.fields.iter().all(DocField::is_empty) + } +} + +#[derive(Clone)] +struct DocField { + key: u32, + docs: Vec, + ty: Option>, +} + +impl DocField { + fn is_empty(&self) -> bool { + self.docs.is_empty() + && match self.ty.as_deref() { + None => true, + Some(doc) => doc.is_empty(), + } + } +} + +struct Shape { + ty: TokenStream, + doc: TypeDocSpec, + members: Vec, + is_bytes: Vec, + style: Style, +} + struct Variant { real_ident: syn::Ident, renamed_ident: String, hash: u32, ty: TokenStream, + docs: Vec, + payload_doc: Option, members: Vec, with_bytes: bool, style: Style, @@ -91,9 +141,10 @@ impl Variant { fn enum_from_ast( name: &syn::Ident, + root_docs: &[String], variants: &Punctuated, custom_candid_path: &Option, -) -> (TokenStream, TokenStream) { +) -> (TokenStream, TokenStream, TokenStream) { let mut fs: Vec<_> = variants .iter() .map(|variant| { @@ -107,15 +158,17 @@ fn enum_from_ast( (id, hash) } }; - let (ty, idents, _, style) = struct_from_ast(&variant.fields, custom_candid_path); + let shape = struct_from_ast(&variant.fields, custom_candid_path); Variant { real_ident: id, renamed_ident, hash, - ty, - members: idents, + ty: shape.ty, + docs: extract_doc_comments(&variant.attrs), + payload_doc: (!shape.doc.is_empty()).then_some(shape.doc), + members: shape.members, with_bytes: attrs.with_bytes, - style, + style: shape.style, } }) .collect(); @@ -136,6 +189,20 @@ fn enum_from_ast( ] ).into() }; + let ty_doc_gen = quote_type_doc( + &candid, + &TypeDocSpec { + docs: root_docs.to_vec(), + fields: fs + .iter() + .map(|variant| DocField { + key: variant.hash, + docs: variant.docs.clone(), + ty: variant.payload_doc.clone().map(Box::new), + }) + .collect(), + }, + ); let id = fs.iter().map(|Variant { real_ident, .. }| { syn::parse_str::(&format!("{name}::{real_ident}")).unwrap() @@ -168,7 +235,7 @@ fn enum_from_ast( }; Ok(()) }; - (ty_gen, variant_gen) + (ty_gen, ty_doc_gen, variant_gen) } fn serialize_struct( @@ -192,41 +259,49 @@ fn serialize_struct( } } -fn struct_from_ast( - fields: &syn::Fields, - custom_candid_path: &Option, -) -> (TokenStream, Vec, Vec, Style) { +fn struct_from_ast(fields: &syn::Fields, custom_candid_path: &Option) -> Shape { let candid = candid_path(custom_candid_path); match *fields { syn::Fields::Named(ref fields) => { - let (fs, idents, is_bytes) = fields_from_ast(&fields.named, custom_candid_path); - ( - quote! { #candid::types::TypeInner::Record(#fs).into() }, - idents, + let (fs, doc, idents, is_bytes) = fields_from_ast(&fields.named, custom_candid_path); + Shape { + ty: quote! { #candid::types::TypeInner::Record(#fs).into() }, + doc, + members: idents, is_bytes, - Style::Struct, - ) + style: Style::Struct, + } } syn::Fields::Unnamed(ref fields) => { - let (fs, idents, is_bytes) = fields_from_ast(&fields.unnamed, custom_candid_path); + let (fs, doc, idents, is_bytes) = fields_from_ast(&fields.unnamed, custom_candid_path); if idents.len() == 1 { + // Newtypes are inlined to the inner type (no record wrapper), + // so field-level docs are not representable in the output. let newtype = derive_type(&fields.unnamed[0].ty, custom_candid_path); - (quote! { #newtype }, idents, is_bytes, Style::Tuple) + Shape { + ty: quote! { #newtype }, + doc: TypeDocSpec::default(), + members: idents, + is_bytes, + style: Style::Tuple, + } } else { - ( - quote! { #candid::types::TypeInner::Record(#fs).into() }, - idents, + Shape { + ty: quote! { #candid::types::TypeInner::Record(#fs).into() }, + doc, + members: idents, is_bytes, - Style::Tuple, - ) + style: Style::Tuple, + } } } - syn::Fields::Unit => ( - quote! { #candid::types::TypeInner::Null.into() }, - Vec::new(), - Vec::new(), - Style::Unit, - ), + syn::Fields::Unit => Shape { + ty: quote! { #candid::types::TypeInner::Null.into() }, + doc: TypeDocSpec::default(), + members: Vec::new(), + is_bytes: Vec::new(), + style: Style::Unit, + }, } } @@ -261,6 +336,7 @@ struct Field { renamed_ident: Ident, hash: u32, ty: TokenStream, + docs: Vec, with_bytes: bool, } @@ -327,7 +403,7 @@ fn get_attrs(attrs: &[syn::Attribute]) -> Attributes { fn fields_from_ast( fields: &Punctuated, custom_candid_path: &Option, -) -> (TokenStream, Vec, Vec) { +) -> (TokenStream, TypeDocSpec, Vec, Vec) { let candid = candid_path(custom_candid_path); let mut fs: Vec<_> = fields .iter() @@ -356,6 +432,7 @@ fn fields_from_ast( renamed_ident, hash, ty: derive_type(&field.ty, custom_candid_path), + docs: extract_doc_comments(&field.attrs), with_bytes: attrs.with_bytes, } }) @@ -390,7 +467,64 @@ fn fields_from_ast( .map(|Field { real_ident, .. }| real_ident.clone()) .collect(); let is_bytes: Vec<_> = fs.iter().map(|f| f.with_bytes).collect(); - (ty_gen, idents, is_bytes) + let doc = TypeDocSpec { + docs: Vec::new(), + fields: fs + .iter() + .map(|field| DocField { + key: field.hash, + docs: field.docs.clone(), + ty: None, + }) + .collect(), + }; + (ty_gen, doc, idents, is_bytes) +} + +fn quote_doc_lines(docs: &[String]) -> Vec { + docs.iter().map(|doc| quote! { #doc.to_string() }).collect() +} + +fn quote_type_doc(candid: &TokenStream, doc: &TypeDocSpec) -> TokenStream { + if doc.is_empty() { + return quote! { #candid::types::TypeDoc::default() }; + } + let docs = quote_doc_lines(&doc.docs); + let field_inserts: Vec<_> = doc + .fields + .iter() + .filter(|field| !field.is_empty()) + .map(|field| { + let key = field.key; + let field_doc = quote_field_doc(candid, field); + quote! { + doc.fields.insert(#key, #field_doc); + } + }) + .collect(); + quote! {{ + let mut doc = #candid::types::TypeDoc::default(); + doc.docs = vec![#(#docs,)*]; + #(#field_inserts)* + doc + }} +} + +fn quote_field_doc(candid: &TokenStream, doc: &DocField) -> TokenStream { + let docs = quote_doc_lines(&doc.docs); + let ty = match doc.ty.as_deref() { + Some(ty) if !ty.is_empty() => { + let ty = quote_type_doc(candid, ty); + quote! { Some(Box::new(#ty)) } + } + _ => quote! { None }, + }; + quote! { + #candid::types::FieldDoc { + docs: vec![#(#docs,)*], + ty: #ty, + } + } } fn derive_type(t: &syn::Type, custom_candid_path: &Option) -> TokenStream { diff --git a/rust/candid_derive/src/docs.rs b/rust/candid_derive/src/docs.rs new file mode 100644 index 000000000..4fb4b0e46 --- /dev/null +++ b/rust/candid_derive/src/docs.rs @@ -0,0 +1,23 @@ +use crate::get_lit_str; +use syn::Attribute; + +pub(crate) fn extract_doc_comments(attrs: &[Attribute]) -> Vec { + let mut docs = Vec::new(); + for attr in attrs { + if attr.path().is_ident("doc") { + if let syn::Meta::NameValue(meta) = &attr.meta { + if let Ok(lit) = get_lit_str(&meta.value) { + let doc_content = lit.value(); + if !doc_content.is_empty() { + for line in doc_content.lines() { + docs.push(line.trim().to_string()); + } + } else { + docs.push(String::new()); + } + } + } + } + } + docs +} diff --git a/rust/candid_derive/src/func.rs b/rust/candid_derive/src/func.rs index 7e5443ccd..ed31f9ec1 100644 --- a/rust/candid_derive/src/func.rs +++ b/rust/candid_derive/src/func.rs @@ -1,10 +1,10 @@ -use super::{candid_path, get_lit_str}; +use super::{candid_path, docs::extract_doc_comments, get_lit_str}; use lazy_static::lazy_static; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use std::collections::BTreeMap; use std::sync::Mutex; -use syn::{Attribute, Error, ItemFn, Meta, Result, ReturnType, Signature, Type}; +use syn::{Error, ItemFn, Meta, Result, ReturnType, Signature, Type}; type RawArgs = Vec<(Option, String)>; type RawRets = Vec; @@ -169,6 +169,7 @@ pub(crate) fn export_service(path: Option) -> TokenStream { fn __export_service() -> String { #service #actor + docs.extend_types(env.docs); let result = #candid::pretty::candid::compile_with_docs(&env.env, &actor, &docs); format!("{}", result) } @@ -268,25 +269,3 @@ fn get_candid_attribute(attrs: Vec) -> Result { } Ok(res) } - -fn extract_doc_comments(attrs: &[Attribute]) -> Vec { - let mut docs = Vec::new(); - for attr in attrs { - if attr.path().is_ident("doc") { - if let syn::Meta::NameValue(meta) = &attr.meta { - if let Ok(lit) = get_lit_str(&meta.value) { - let doc_content = lit.value(); - if !doc_content.is_empty() { - for line in doc_content.lines() { - let trimmed = line.trim().to_string(); - docs.push(trimmed); - } - } else { - docs.push("".to_string()); - } - } - } - } - } - docs -} diff --git a/rust/candid_derive/src/lib.rs b/rust/candid_derive/src/lib.rs index eb26e4263..4b55ed7d3 100644 --- a/rust/candid_derive/src/lib.rs +++ b/rust/candid_derive/src/lib.rs @@ -2,6 +2,7 @@ use proc_macro::TokenStream; use syn::{parse_macro_input, Result}; mod derive; +mod docs; mod func; #[proc_macro_derive(CandidType, attributes(candid_path))] diff --git a/rust/candid_parser/Cargo.toml b/rust/candid_parser/Cargo.toml index f7342923f..ebbd5cd19 100644 --- a/rust/candid_parser/Cargo.toml +++ b/rust/candid_parser/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "candid_parser" -version = "0.2.3" +version = "0.3.2" edition = "2021" rust-version.workspace = true authors = ["DFINITY Team"] diff --git a/rust/candid_parser/src/bindings/motoko.rs b/rust/candid_parser/src/bindings/motoko.rs index 0408b3a94..500f5baed 100644 --- a/rust/candid_parser/src/bindings/motoko.rs +++ b/rust/candid_parser/src/bindings/motoko.rs @@ -133,7 +133,7 @@ fn pp_ty(ty: &Type) -> RcDoc<'_> { Int16 => str("Int16"), Int32 => str("Int32"), Int64 => str("Int64"), - Float32 => panic!("float32 not supported in Motoko"), + Float32 => str("Float32"), Float64 => str("Float"), Text => str("Text"), Reserved => str("Any"), diff --git a/rust/candid_parser/src/bindings/typescript.rs b/rust/candid_parser/src/bindings/typescript.rs index f5991791a..5fe2e4ca1 100644 --- a/rust/candid_parser/src/bindings/typescript.rs +++ b/rust/candid_parser/src/bindings/typescript.rs @@ -244,14 +244,19 @@ fn pp_service<'a>( sep_enclose_space(methods, ",", "{", "}") } +/// Escapes doc comment content to prevent comment injection attacks. +/// Replaces `*/` with `*\/` to prevent premature comment termination. +fn escape_doc_comment(line: &str) -> String { + line.replace("*/", r"*\/") +} + fn pp_docs<'a>(docs: &'a [String]) -> RcDoc<'a> { if docs.is_empty() { RcDoc::nil() } else { - let docs = lines( - docs.iter() - .map(|line| RcDoc::text(DOC_COMMENT_LINE_PREFIX).append(line)), - ); + let docs = lines(docs.iter().map(|line| { + RcDoc::text(DOC_COMMENT_LINE_PREFIX).append(RcDoc::text(escape_doc_comment(line))) + })); RcDoc::text(DOC_COMMENT_PREFIX) .append(RcDoc::hardline()) .append(docs) @@ -317,9 +322,9 @@ fn pp_actor<'a>(env: &'a TypeEnv, ty: &'a Type, syntax: Option<&'a IDLType>) -> } pub fn compile(env: &TypeEnv, actor: &Option, prog: &IDLMergedProg) -> String { - let header = r#"import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; -import type { Principal } from '@dfinity/principal'; + let header = r#"import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; "#; let syntax_actor = prog.resolve_actor().ok().flatten(); let def_list: Vec<_> = env.to_sorted_iter().map(|pair| pair.0.as_str()).collect(); diff --git a/rust/candid_parser/src/token.rs b/rust/candid_parser/src/token.rs index 37ea787fa..0281c31b0 100644 --- a/rust/candid_parser/src/token.rs +++ b/rust/candid_parser/src/token.rs @@ -246,12 +246,11 @@ impl Iterator for Tokenizer<'_> { Some(Err(LexicalError::new(err, span))) } Ok(Token::LineComment) => { - let content = parse_doc_comment(&self.lex); if self.trivia.is_some() && !self.is_inline_comment(span.start) { if self.has_blank_line_before_token(span.start) { self.comment_buffer.clear(); } - self.comment_buffer.push(content.to_string()); + self.comment_buffer.push(parse_doc_comment(&self.lex)); self.last_comment_end = Some(span.end); } self.next() @@ -278,9 +277,11 @@ impl Iterator for Tokenizer<'_> { } } self.lex = lex.morph::(); - // Update last_comment_end to skip over the block comment - // This prevents block comments from breaking doc comment continuity - self.last_comment_end = Some(self.lex.span().start); + if self.trivia.is_some() { + // Update last_comment_end to skip over the block comment. + // This prevents block comments from breaking doc comment continuity. + self.last_comment_end = Some(self.lex.span().start); + } self.next() } Ok(Token::StartString) => { @@ -359,21 +360,21 @@ impl Iterator for Tokenizer<'_> { Some(Ok((span.start, Token::Text(result), self.lex.span().end))) } Ok(token) => { - // Check for blank line before getting mutable reference - let has_blank_line = self.has_blank_line_before_token(span.start); - - if let Some(trivia) = &mut self.trivia { - if !self.comment_buffer.is_empty() { - if !has_blank_line { - let content: Vec = mem::take(&mut self.comment_buffer); - trivia.borrow_mut().insert(span.start, content); - } else { - self.comment_buffer.clear(); + if self.trivia.is_some() { + let has_blank_line = self.has_blank_line_before_token(span.start); + if let Some(trivia) = &mut self.trivia { + if !self.comment_buffer.is_empty() { + if !has_blank_line { + let content: Vec = mem::take(&mut self.comment_buffer); + trivia.borrow_mut().insert(span.start, content); + } else { + self.comment_buffer.clear(); + } } + self.last_comment_end = None; + self.last_token_line_end = Some(self.find_line_end(span.end)); } - self.last_comment_end = None; } - self.last_token_line_end = Some(self.find_line_end(span.end)); Some(Ok((span.start, token, span.end))) } diff --git a/rust/candid_parser/src/typing.rs b/rust/candid_parser/src/typing.rs index 67c2c8249..6d0e7336a 100644 --- a/rust/candid_parser/src/typing.rs +++ b/rust/candid_parser/src/typing.rs @@ -6,6 +6,7 @@ use crate::{ }, Error, Result, }; +use candid::types::internal::TypeKey; use candid::types::{ArgType, Field, Function, Type, TypeEnv, TypeInner}; use std::collections::{BTreeMap, BTreeSet}; use std::path::{Path, PathBuf}; @@ -178,6 +179,80 @@ fn check_cycle(env: &TypeEnv) -> Result<()> { Ok(()) } +fn validate_func(env: &TypeEnv, seen: &mut BTreeMap, func: &Function) -> Result<()> { + for arg in func.args.iter() { + validate_type(env, seen, &arg.typ)?; + } + for ret in func.rets.iter() { + validate_type(env, seen, &ret.typ)?; + } + Ok(()) +} + +fn validate_type(env: &TypeEnv, seen: &mut BTreeMap, t: &Type) -> Result<()> { + match t.as_ref() { + TypeInner::Null + | TypeInner::Bool + | TypeInner::Nat + | TypeInner::Int + | TypeInner::Nat8 + | TypeInner::Nat16 + | TypeInner::Nat32 + | TypeInner::Nat64 + | TypeInner::Int8 + | TypeInner::Int16 + | TypeInner::Int32 + | TypeInner::Int64 + | TypeInner::Float32 + | TypeInner::Float64 + | TypeInner::Text + | TypeInner::Reserved + | TypeInner::Empty + | TypeInner::Unknown + | TypeInner::Principal + | TypeInner::Future + | TypeInner::Knot(_) => Ok(()), + TypeInner::Var(id) => match seen.get(id) { + Some(true) | Some(false) => Ok(()), + None => { + seen.insert(id.clone(), false); + let res = validate_type(env, seen, env.find_type(id)?); + seen.insert(id.clone(), true); + res + } + }, + TypeInner::Opt(ty) | TypeInner::Vec(ty) => validate_type(env, seen, ty), + TypeInner::Record(fields) | TypeInner::Variant(fields) => { + for field in fields.iter() { + validate_type(env, seen, &field.ty)?; + } + Ok(()) + } + TypeInner::Func(func) => validate_func(env, seen, func), + TypeInner::Service(methods) => { + for (_, ty) in methods.iter() { + let func = env.as_func(ty)?; + validate_func(env, seen, func)?; + } + Ok(()) + } + TypeInner::Class(args, ty) => { + for arg in args.iter() { + validate_type(env, seen, &arg.typ)?; + } + validate_type(env, seen, ty) + } + } +} + +fn validate_decs(env: &TypeEnv) -> Result<()> { + let mut seen = BTreeMap::new(); + for ty in env.0.values() { + validate_type(env, &mut seen, ty)?; + } + Ok(()) +} + fn check_decs(env: &mut Env, decs: &[Dec]) -> Result<()> { for dec in decs.iter() { if let Dec::TypD(Binding { id, .. }) = dec { @@ -194,7 +269,7 @@ fn check_decs(env: &mut Env, decs: &[Dec]) -> Result<()> { check_defs(env, decs)?; check_cycle(env.te)?; env.pre = false; - check_defs(env, decs)?; + validate_decs(env.te)?; Ok(()) } diff --git a/rust/candid_parser/src/utils.rs b/rust/candid_parser/src/utils.rs index d6e69aa51..b9aa58c5e 100644 --- a/rust/candid_parser/src/utils.rs +++ b/rust/candid_parser/src/utils.rs @@ -40,6 +40,25 @@ pub fn service_compatible(new: CandidSource, old: CandidSource) -> Result<()> { Ok(()) } +/// Check compatibility of two service types, returning **all** incompatibilities +/// instead of stopping at the first one. +/// +/// Returns an empty `Vec` when the new interface is backward-compatible with the old one. +pub fn service_compatibility_report( + new: CandidSource, + old: CandidSource, +) -> Result> { + let (mut env, t1) = new.load()?; + let t1 = t1.ok_or_else(|| Error::msg("new interface has no main service type"))?; + let (env2, t2) = old.load()?; + let t2 = t2.ok_or_else(|| Error::msg("old interface has no main service type"))?; + let mut gamma = std::collections::HashSet::new(); + let t2 = env.merge_type(env2, t2); + Ok(candid::types::subtype::subtype_check_all( + &mut gamma, &env, &t1, &t2, + )) +} + /// Check structural equality of two service types pub fn service_equal(left: CandidSource, right: CandidSource) -> Result<()> { let (mut env, t1) = left.load()?; diff --git a/rust/candid_parser/tests/assets/float.did b/rust/candid_parser/tests/assets/float.did new file mode 100644 index 000000000..0bc5fef10 --- /dev/null +++ b/rust/candid_parser/tests/assets/float.did @@ -0,0 +1,5 @@ +service : { + to_f32 : (float64) -> (float32); + to_f64 : (float32) -> (float64); + identity32 : (float32) -> (float32); +} diff --git a/rust/candid_parser/tests/assets/malicious_doc.did b/rust/candid_parser/tests/assets/malicious_doc.did new file mode 100644 index 000000000..f8d586d00 --- /dev/null +++ b/rust/candid_parser/tests/assets/malicious_doc.did @@ -0,0 +1,13 @@ +// */ import { malicious } from 'attacker'; console.log('injected!'); /* +// Normal doc line +// Another */ attempt /* to inject +type MaliciousType = record { + // Doc comment for field with */ malicious code /* in it + field : text +}; + +// Service with */ malicious */ doc +service : { + // Method with */ in doc comment + get : () -> (MaliciousType) query +} diff --git a/rust/candid_parser/tests/assets/ok/actor.d.ts b/rust/candid_parser/tests/assets/ok/actor.d.ts index fd1c863e8..80ef1a413 100644 --- a/rust/candid_parser/tests/assets/ok/actor.d.ts +++ b/rust/candid_parser/tests/assets/ok/actor.d.ts @@ -1,6 +1,6 @@ -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; -import type { Principal } from '@dfinity/principal'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; export type f = ActorMethod<[number], number>; export type g = f; diff --git a/rust/candid_parser/tests/assets/ok/class.d.ts b/rust/candid_parser/tests/assets/ok/class.d.ts index f10f30bcf..7a68e4073 100644 --- a/rust/candid_parser/tests/assets/ok/class.d.ts +++ b/rust/candid_parser/tests/assets/ok/class.d.ts @@ -1,6 +1,6 @@ -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; -import type { Principal } from '@dfinity/principal'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; export type List = [] | [[bigint, List]]; export interface Profile { 'age' : number, 'name' : string } diff --git a/rust/candid_parser/tests/assets/ok/comment.d.ts b/rust/candid_parser/tests/assets/ok/comment.d.ts index f9d1f7b58..7e4bc2ee7 100644 --- a/rust/candid_parser/tests/assets/ok/comment.d.ts +++ b/rust/candid_parser/tests/assets/ok/comment.d.ts @@ -1,6 +1,6 @@ -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; -import type { Principal } from '@dfinity/principal'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; /** * line comment diff --git a/rust/candid_parser/tests/assets/ok/cyclic.d.ts b/rust/candid_parser/tests/assets/ok/cyclic.d.ts index ba6ef8f36..3c9470181 100644 --- a/rust/candid_parser/tests/assets/ok/cyclic.d.ts +++ b/rust/candid_parser/tests/assets/ok/cyclic.d.ts @@ -1,6 +1,6 @@ -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; -import type { Principal } from '@dfinity/principal'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; export type A = [] | [B]; export type B = [] | [C]; diff --git a/rust/candid_parser/tests/assets/ok/empty.d.ts b/rust/candid_parser/tests/assets/ok/empty.d.ts index 2897b15fd..ab794a341 100644 --- a/rust/candid_parser/tests/assets/ok/empty.d.ts +++ b/rust/candid_parser/tests/assets/ok/empty.d.ts @@ -1,6 +1,6 @@ -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; -import type { Principal } from '@dfinity/principal'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; export type T = [T]; export interface _SERVICE { diff --git a/rust/candid_parser/tests/assets/ok/escape.d.ts b/rust/candid_parser/tests/assets/ok/escape.d.ts index 325019950..08ff97894 100644 --- a/rust/candid_parser/tests/assets/ok/escape.d.ts +++ b/rust/candid_parser/tests/assets/ok/escape.d.ts @@ -1,6 +1,6 @@ -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; -import type { Principal } from '@dfinity/principal'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; export interface t { '\"' : bigint, diff --git a/rust/candid_parser/tests/assets/ok/example.d.ts b/rust/candid_parser/tests/assets/ok/example.d.ts index f8c61ea71..c8fdceff7 100644 --- a/rust/candid_parser/tests/assets/ok/example.d.ts +++ b/rust/candid_parser/tests/assets/ok/example.d.ts @@ -1,6 +1,6 @@ -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; -import type { Principal } from '@dfinity/principal'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; export type A = B; export type B = [] | [A]; diff --git a/rust/candid_parser/tests/assets/ok/fieldnat.d.ts b/rust/candid_parser/tests/assets/ok/fieldnat.d.ts index 348175545..bbd788e03 100644 --- a/rust/candid_parser/tests/assets/ok/fieldnat.d.ts +++ b/rust/candid_parser/tests/assets/ok/fieldnat.d.ts @@ -1,6 +1,6 @@ -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; -import type { Principal } from '@dfinity/principal'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; export interface non_tuple { _1_ : string, _2_ : string } export type tuple = [string, string]; diff --git a/rust/candid_parser/tests/assets/ok/float.d.ts b/rust/candid_parser/tests/assets/ok/float.d.ts new file mode 100644 index 000000000..54cfd6a24 --- /dev/null +++ b/rust/candid_parser/tests/assets/ok/float.d.ts @@ -0,0 +1,13 @@ +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; + +export interface _SERVICE { + 'identity32' : ActorMethod<[number], number>, + 'to_f32' : ActorMethod<[number], number>, + 'to_f64' : ActorMethod<[number], number>, +} +export declare const idlService: IDL.ServiceClass; +export declare const idlInitArgs: IDL.Type[]; +export declare const idlFactory: IDL.InterfaceFactory; +export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/rust/candid_parser/tests/assets/ok/float.did b/rust/candid_parser/tests/assets/ok/float.did new file mode 100644 index 000000000..3b258dd2f --- /dev/null +++ b/rust/candid_parser/tests/assets/ok/float.did @@ -0,0 +1,5 @@ +service : { + identity32 : (float32) -> (float32); + to_f32 : (float64) -> (float32); + to_f64 : (float32) -> (float64); +} diff --git a/rust/candid_parser/tests/assets/ok/float.js b/rust/candid_parser/tests/assets/ok/float.js new file mode 100644 index 000000000..011e5700a --- /dev/null +++ b/rust/candid_parser/tests/assets/ok/float.js @@ -0,0 +1,19 @@ +import { IDL } from '@dfinity/candid'; + +export const idlService = IDL.Service({ + 'identity32' : IDL.Func([IDL.Float32], [IDL.Float32], []), + 'to_f32' : IDL.Func([IDL.Float64], [IDL.Float32], []), + 'to_f64' : IDL.Func([IDL.Float32], [IDL.Float64], []), +}); + +export const idlInitArgs = []; + +export const idlFactory = ({ IDL }) => { + return IDL.Service({ + 'identity32' : IDL.Func([IDL.Float32], [IDL.Float32], []), + 'to_f32' : IDL.Func([IDL.Float64], [IDL.Float32], []), + 'to_f64' : IDL.Func([IDL.Float32], [IDL.Float64], []), + }); +}; + +export const init = ({ IDL }) => { return []; }; diff --git a/rust/candid_parser/tests/assets/ok/float.mo b/rust/candid_parser/tests/assets/ok/float.mo new file mode 100644 index 000000000..2ba13f2d1 --- /dev/null +++ b/rust/candid_parser/tests/assets/ok/float.mo @@ -0,0 +1,10 @@ +// This is a generated Motoko binding. +// Please use `import service "ic:canister_id"` instead to call canisters on the IC if possible. + +module { + public type Self = actor { + identity32 : shared Float32 -> async Float32; + to_f32 : shared Float -> async Float32; + to_f64 : shared Float32 -> async Float; + } +} diff --git a/rust/candid_parser/tests/assets/ok/float.rs b/rust/candid_parser/tests/assets/ok/float.rs new file mode 100644 index 000000000..8d2967b88 --- /dev/null +++ b/rust/candid_parser/tests/assets/ok/float.rs @@ -0,0 +1,23 @@ +// This is an experimental feature to generate Rust binding from Candid. +// You may want to manually adjust some of the types. +#![allow(dead_code, unused_imports)] +use candid::{self, CandidType, Deserialize, Principal}; +use ic_cdk::api::call::CallResult as Result; + + +pub struct Service(pub Principal); +impl Service { + pub async fn identity32(&self, arg0: &f32) -> Result<(f32,)> { + ic_cdk::call(self.0, "identity32", (arg0,)).await + } + pub async fn to_f32(&self, arg0: &f64) -> Result<(f32,)> { + ic_cdk::call(self.0, "to_f32", (arg0,)).await + } + pub async fn to_f64(&self, arg0: &f32) -> Result<(f64,)> { + ic_cdk::call(self.0, "to_f64", (arg0,)).await + } +} +/// Canister ID: `aaaaa-aa` +pub const CANISTER_ID : Principal = Principal::from_slice(&[]); +pub const service : Service = Service(CANISTER_ID); + diff --git a/rust/candid_parser/tests/assets/ok/inline_methods.d.ts b/rust/candid_parser/tests/assets/ok/inline_methods.d.ts index 0b3bd61e1..f3f82eea0 100644 --- a/rust/candid_parser/tests/assets/ok/inline_methods.d.ts +++ b/rust/candid_parser/tests/assets/ok/inline_methods.d.ts @@ -1,6 +1,6 @@ -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; -import type { Principal } from '@dfinity/principal'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; export type Fn = ActorMethod<[bigint], bigint>; export type Gn = Fn; diff --git a/rust/candid_parser/tests/assets/ok/keyword.d.ts b/rust/candid_parser/tests/assets/ok/keyword.d.ts index 72e5c8720..0df86b31e 100644 --- a/rust/candid_parser/tests/assets/ok/keyword.d.ts +++ b/rust/candid_parser/tests/assets/ok/keyword.d.ts @@ -1,6 +1,6 @@ -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; -import type { Principal } from '@dfinity/principal'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; export type if_ = { 'branch' : { 'val' : bigint, 'left' : if_, 'right' : if_ } diff --git a/rust/candid_parser/tests/assets/ok/malicious_doc.d.ts b/rust/candid_parser/tests/assets/ok/malicious_doc.d.ts new file mode 100644 index 000000000..7491955ba --- /dev/null +++ b/rust/candid_parser/tests/assets/ok/malicious_doc.d.ts @@ -0,0 +1,28 @@ +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; + +/** + * *\/ import { malicious } from 'attacker'; console.log('injected!'); /* + * Normal doc line + * Another *\/ attempt /* to inject + */ +export interface MaliciousType { + /** + * Doc comment for field with *\/ malicious code /* in it + */ + 'field' : string, +} +/** + * Service with *\/ malicious *\/ doc + */ +export interface _SERVICE { + /** + * Method with *\/ in doc comment + */ + 'get' : ActorMethod<[], MaliciousType>, +} +export declare const idlService: IDL.ServiceClass; +export declare const idlInitArgs: IDL.Type[]; +export declare const idlFactory: IDL.InterfaceFactory; +export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/rust/candid_parser/tests/assets/ok/malicious_doc.did b/rust/candid_parser/tests/assets/ok/malicious_doc.did new file mode 100644 index 000000000..714759924 --- /dev/null +++ b/rust/candid_parser/tests/assets/ok/malicious_doc.did @@ -0,0 +1,12 @@ +// */ import { malicious } from 'attacker'; console.log('injected!'); /* +// Normal doc line +// Another */ attempt /* to inject +type MaliciousType = record { + // Doc comment for field with */ malicious code /* in it + field : text; +}; +// Service with */ malicious */ doc +service : { + // Method with */ in doc comment + get : () -> (MaliciousType) query; +} diff --git a/rust/candid_parser/tests/assets/ok/malicious_doc.js b/rust/candid_parser/tests/assets/ok/malicious_doc.js new file mode 100644 index 000000000..552475ffe --- /dev/null +++ b/rust/candid_parser/tests/assets/ok/malicious_doc.js @@ -0,0 +1,17 @@ +import { IDL } from '@dfinity/candid'; + +export const MaliciousType = IDL.Record({ 'field' : IDL.Text }); + +export const idlService = IDL.Service({ + 'get' : IDL.Func([], [MaliciousType], ['query']), +}); + +export const idlInitArgs = []; + +export const idlFactory = ({ IDL }) => { + const MaliciousType = IDL.Record({ 'field' : IDL.Text }); + + return IDL.Service({ 'get' : IDL.Func([], [MaliciousType], ['query']) }); +}; + +export const init = ({ IDL }) => { return []; }; diff --git a/rust/candid_parser/tests/assets/ok/malicious_doc.mo b/rust/candid_parser/tests/assets/ok/malicious_doc.mo new file mode 100644 index 000000000..046143246 --- /dev/null +++ b/rust/candid_parser/tests/assets/ok/malicious_doc.mo @@ -0,0 +1,17 @@ +// This is a generated Motoko binding. +// Please use `import service "ic:canister_id"` instead to call canisters on the IC if possible. + +module { + /// */ import { malicious } from 'attacker'; console.log('injected!'); /* + /// Normal doc line + /// Another */ attempt /* to inject + public type MaliciousType = { + /// Doc comment for field with */ malicious code /* in it + field : Text; + }; + /// Service with */ malicious */ doc + public type Self = actor { + /// Method with */ in doc comment + get : shared query () -> async MaliciousType; + } +} diff --git a/rust/candid_parser/tests/assets/ok/malicious_doc.rs b/rust/candid_parser/tests/assets/ok/malicious_doc.rs new file mode 100644 index 000000000..5b4a5d48f --- /dev/null +++ b/rust/candid_parser/tests/assets/ok/malicious_doc.rs @@ -0,0 +1,27 @@ +// This is an experimental feature to generate Rust binding from Candid. +// You may want to manually adjust some of the types. +#![allow(dead_code, unused_imports)] +use candid::{self, CandidType, Deserialize, Principal}; +use ic_cdk::api::call::CallResult as Result; + +/// */ import { malicious } from 'attacker'; console.log('injected!'); /* +/// Normal doc line +/// Another */ attempt /* to inject +#[derive(CandidType, Deserialize)] +pub struct MaliciousType { + /// Doc comment for field with */ malicious code /* in it + pub field: String, +} + +/// Service with */ malicious */ doc +pub struct Service(pub Principal); +impl Service { + /// Method with */ in doc comment + pub async fn get(&self) -> Result<(MaliciousType,)> { + ic_cdk::call(self.0, "get", ()).await + } +} +/// Canister ID: `aaaaa-aa` +pub const CANISTER_ID : Principal = Principal::from_slice(&[]); +pub const service : Service = Service(CANISTER_ID); + diff --git a/rust/candid_parser/tests/assets/ok/management.d.ts b/rust/candid_parser/tests/assets/ok/management.d.ts index 63cd4a439..c49b43676 100644 --- a/rust/candid_parser/tests/assets/ok/management.d.ts +++ b/rust/candid_parser/tests/assets/ok/management.d.ts @@ -1,6 +1,6 @@ -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; -import type { Principal } from '@dfinity/principal'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; export type bitcoin_address = string; export type bitcoin_network = { 'mainnet' : null } | diff --git a/rust/candid_parser/tests/assets/ok/recursion.d.ts b/rust/candid_parser/tests/assets/ok/recursion.d.ts index 163a6de82..36368cbea 100644 --- a/rust/candid_parser/tests/assets/ok/recursion.d.ts +++ b/rust/candid_parser/tests/assets/ok/recursion.d.ts @@ -1,6 +1,6 @@ -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; -import type { Principal } from '@dfinity/principal'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; export type A = B; export type B = [] | [A]; diff --git a/rust/candid_parser/tests/assets/ok/recursive_class.d.ts b/rust/candid_parser/tests/assets/ok/recursive_class.d.ts index f2c92fd06..373f7d527 100644 --- a/rust/candid_parser/tests/assets/ok/recursive_class.d.ts +++ b/rust/candid_parser/tests/assets/ok/recursive_class.d.ts @@ -1,6 +1,6 @@ -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; -import type { Principal } from '@dfinity/principal'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; export interface s { 'next' : ActorMethod<[], Principal> } export interface _SERVICE extends s {} diff --git a/rust/candid_parser/tests/assets/ok/service.d.ts b/rust/candid_parser/tests/assets/ok/service.d.ts index 0378a7f42..b5b415749 100644 --- a/rust/candid_parser/tests/assets/ok/service.d.ts +++ b/rust/candid_parser/tests/assets/ok/service.d.ts @@ -1,6 +1,6 @@ -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; -import type { Principal } from '@dfinity/principal'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; export type Func = ActorMethod<[], Principal>; export interface Service { 'f' : Func } diff --git a/rust/candid_parser/tests/assets/ok/unicode.d.ts b/rust/candid_parser/tests/assets/ok/unicode.d.ts index 1adc4b11b..6944f36d0 100644 --- a/rust/candid_parser/tests/assets/ok/unicode.d.ts +++ b/rust/candid_parser/tests/assets/ok/unicode.d.ts @@ -1,6 +1,6 @@ -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; -import type { Principal } from '@dfinity/principal'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; export interface A { '\u{e000}' : bigint, diff --git a/rust/candid_parser/tests/compatibility.rs b/rust/candid_parser/tests/compatibility.rs new file mode 100644 index 000000000..2671fe924 --- /dev/null +++ b/rust/candid_parser/tests/compatibility.rs @@ -0,0 +1,538 @@ +use candid::types::subtype::{format_report, Incompatibility}; +use candid_parser::utils::{service_compatibility_report, service_compatible, CandidSource}; + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +fn check_compatible(new: &str, old: &str, desc: &str) { + service_compatible(CandidSource::Text(new), CandidSource::Text(old)) + .unwrap_or_else(|e| panic!("[{desc}] expected compatible, got error: {e}")); +} + +fn check_incompatible(new: &str, old: &str, desc: &str) { + assert!( + service_compatible(CandidSource::Text(new), CandidSource::Text(old)).is_err(), + "[{desc}] expected incompatible, but check passed" + ); +} + +fn get_errors(new: &str, old: &str) -> Vec { + service_compatibility_report(CandidSource::Text(new), CandidSource::Text(old)) + .expect("failed to load interfaces") +} + +fn error_strings(new: &str, old: &str) -> Vec { + get_errors(new, old) + .into_iter() + .map(|e| e.to_string()) + .collect() +} + +// =========================================================================== +// Backward-compatible changes must PASS (no false positives) +// =========================================================================== + +#[test] +fn compatible_service_changes() { + let cases: &[(&str, &str, &str)] = &[ + ( + "identical service", + "service : { greet : (text) -> (text) }", + "service : { greet : (text) -> (text) }", + ), + ( + "add new method", + "service : { greet : (text) -> (text); hello : () -> (text) }", + "service : { greet : (text) -> (text) }", + ), + ( + "widen input nat→int (contravariant)", + "service : { pay : (int) -> () }", + "service : { pay : (nat) -> () }", + ), + ( + "narrow return int→nat (covariant, nat <: int)", + "service : { get : () -> (nat) }", + "service : { get : () -> (int) }", + ), + ]; + for &(desc, new, old) in cases { + check_compatible(new, old, desc); + } +} + +#[test] +fn compatible_record_field_changes() { + let cases: &[(&str, &str, &str)] = &[ + ( + "add opt field to input record", + "type R = record { name : text; age : opt nat }; service : { f : (R) -> () }", + "type R = record { name : text }; service : { f : (R) -> () }", + ), + ( + "add opt field to return record", + "type R = record { id : nat; extra : opt text }; service : { f : () -> (R) }", + "type R = record { id : nat }; service : { f : () -> (R) }", + ), + ( + "add null field to return record", + "service : { f : () -> (record { a : nat; b : null }) }", + "service : { f : () -> (record { a : nat }) }", + ), + ( + "add reserved field to return record", + "service : { f : () -> (record { a : nat; b : reserved }) }", + "service : { f : () -> (record { a : nat }) }", + ), + ( + "remove non-opt field from input record (extra fields in subtype are fine)", + "type R = record { name : text }; service : { f : (R) -> () }", + "type R = record { name : text; age : nat }; service : { f : (R) -> () }", + ), + ]; + for &(desc, new, old) in cases { + check_compatible(new, old, desc); + } +} + +#[test] +fn compatible_variant_and_vec_changes() { + let cases: &[(&str, &str, &str)] = &[ + ( + "add variant case to input (contravariant: old callers still match)", + "type V = variant { a; b; c }; service : { f : (V) -> () }", + "type V = variant { a; b }; service : { f : (V) -> () }", + ), + ( + "identical vec types", + "service : { f : () -> (vec nat) }", + "service : { f : () -> (vec nat) }", + ), + ]; + for &(desc, new, old) in cases { + check_compatible(new, old, desc); + } +} + +// =========================================================================== +// Backward-INCOMPATIBLE changes must be caught (no false negatives) +// =========================================================================== + +#[test] +fn incompatible_method_changes() { + let cases: &[(&str, &str, &str)] = &[ + ( + "remove method", + "service : { greet : (text) -> (text) }", + "service : { greet : (text) -> (text); hello : () -> (text) }", + ), + ( + "change func mode query→update", + "service : { get : () -> (nat) }", + "service : { get : () -> (nat) query }", + ), + ( + "change return type nat→text", + "service : { get : () -> (text) }", + "service : { get : () -> (nat) }", + ), + ( + "change input type nat→text", + "service : { set : (text) -> () }", + "service : { set : (nat) -> () }", + ), + ( + "widen return nat→int (covariant: int (int) }", + "service : { get : () -> (nat) }", + ), + ]; + for &(desc, new, old) in cases { + check_incompatible(new, old, desc); + } +} + +#[test] +fn incompatible_record_and_variant_changes() { + let cases: &[(&str, &str, &str)] = &[ + ( + "add required field to input record", + "type R = record { name : text; age : nat }; service : { f : (R) -> () }", + "type R = record { name : text }; service : { f : (R) -> () }", + ), + ( + "remove required field from return record", + "type R = record { name : text }; service : { f : () -> (R) }", + "type R = record { name : text; age : nat }; service : { f : () -> (R) }", + ), + ( + "remove variant case from input (old callers may send it)", + "type V = variant { start; stop }; service : { f : (V) -> () }", + "type V = variant { start; stop; pause }; service : { f : (V) -> () }", + ), + ( + "change vec element type", + "service : { f : () -> (vec text) }", + "service : { f : () -> (vec nat) }", + ), + ]; + for &(desc, new, old) in cases { + check_incompatible(new, old, desc); + } +} + +// =========================================================================== +// ALL incompatibilities collected (not just the first) +// =========================================================================== + +#[test] +fn collects_all_incompatible_methods() { + let old = r#"service : { + method_a : (nat) -> (nat); + method_b : (text) -> (text); + method_c : () -> (nat); + method_d : () -> (); + }"#; + // method_a: OK, method_b: removed, method_c: return changed, method_d: removed + let new = r#"service : { + method_a : (nat) -> (nat); + method_c : () -> (text); + }"#; + let errors = error_strings(new, old); + assert_eq!(errors.len(), 3, "got: {errors:?}"); + + let joined = errors.join("\n"); + for name in ["method_b", "method_c", "method_d"] { + assert!(joined.contains(name), "missing {name} in: {joined}"); + } + assert!( + !joined.contains("method_a"), + "method_a is compatible, should not appear: {joined}" + ); +} + +#[test] +fn collects_all_incompatible_record_fields() { + let old = "type R = record { a : nat; b : text; c : bool }; service : { f : () -> (R) }"; + let new = "type R = record { a : text; b : nat; c : bool }; service : { f : () -> (R) }"; + let errors = error_strings(new, old); + assert_eq!(errors.len(), 2, "got: {errors:?}"); + + let joined = errors.join("\n"); + assert!( + joined.contains("record field a"), + "missing field a: {joined}" + ); + assert!( + joined.contains("record field b"), + "missing field b: {joined}" + ); +} + +#[test] +fn collects_both_input_and_return_errors() { + let old = "service : { call : (nat) -> (nat) }"; + let new = "service : { call : (text) -> (bool) }"; + let errors = error_strings(new, old); + assert_eq!(errors.len(), 2, "got: {errors:?}"); + + let joined = errors.join("\n"); + assert!( + joined.contains("input type"), + "missing input error: {joined}" + ); + assert!( + joined.contains("return type"), + "missing return error: {joined}" + ); +} + +#[test] +fn collects_variant_field_errors() { + // Old callers may send variant cases b or c; new input must accept them + let old = "type V = variant { a; b; c }; service : { f : (V) -> () }"; + let new = "type V = variant { a }; service : { f : (V) -> () }"; + let errors = error_strings(new, old); + assert_eq!(errors.len(), 2, "got: {errors:?}"); + + let joined = errors.join("\n"); + assert!(joined.contains("b"), "missing variant b: {joined}"); + assert!(joined.contains("c"), "missing variant c: {joined}"); +} + +// =========================================================================== +// Error message quality + path context +// =========================================================================== + +#[test] +fn missing_method_error_is_clear() { + let old = "service : { transfer : (nat) -> (); balance : () -> (nat) }"; + let new = "service : { balance : () -> (nat) }"; + let errors = error_strings(new, old); + assert_eq!(errors.len(), 1); + assert!(errors[0].contains("transfer"), "should name the method"); + assert!(errors[0].contains("missing"), "should say 'missing'"); +} + +#[test] +fn type_mismatch_error_names_both_types() { + let old = "service : { f : () -> (nat) }"; + let new = "service : { f : () -> (text) }"; + let errors = error_strings(new, old); + assert_eq!(errors.len(), 1); + assert!( + errors[0].contains("text") && errors[0].contains("nat"), + "should mention both types: {}", + errors[0] + ); +} + +#[test] +fn missing_required_field_error_is_clear() { + let old = "type R = record { name : text; age : nat }; service : { f : () -> (R) }"; + let new = "type R = record { name : text }; service : { f : () -> (R) }"; + let errors = error_strings(new, old); + assert_eq!(errors.len(), 1); + let msg = &errors[0]; + assert!(msg.contains("age"), "should mention field name: {msg}"); + assert!( + msg.contains("missing") || msg.contains("not optional"), + "should explain the problem: {msg}" + ); +} + +#[test] +fn nested_path_shows_full_context() { + let old = r#" + type Inner = record { x : nat }; + type Outer = record { inner : Inner }; + service : { get : () -> (Outer) } + "#; + let new = r#" + type Inner = record { x : text }; + type Outer = record { inner : Inner }; + service : { get : () -> (Outer) } + "#; + let errors = error_strings(new, old); + assert_eq!(errors.len(), 1, "got: {errors:?}"); + let msg = &errors[0]; + assert!(msg.contains("method"), "path should include method: {msg}"); + assert!( + msg.contains("return type"), + "path should include return type: {msg}" + ); + assert!( + msg.contains("record field inner"), + "path should include outer field: {msg}" + ); + assert!( + msg.contains("record field x"), + "path should include inner field: {msg}" + ); +} + +// =========================================================================== +// Multi-arg functions +// =========================================================================== + +#[test] +fn multi_arg_function_incompatibilities() { + let old = "service : { f : (nat, text) -> (bool, nat) }"; + let new = "service : { f : (nat, bool) -> (bool, text) }"; + // Input: old (nat, text) must <: new (nat, bool) → field 1 text (text); g : () -> () }", + "service : { f : (text) -> (text) }", + ), + ( + "service : { f : (int) -> () }", + "service : { f : (nat) -> () }", + ), + ]; + for (new, old) in compatible { + let report = get_errors(new, old); + assert!(report.is_empty(), "report should be empty for compatible"); + assert!( + service_compatible(CandidSource::Text(new), CandidSource::Text(old)).is_ok(), + "service_compatible should pass for compatible" + ); + } + + let incompatible = [ + ( + "service : { f : (text) -> (text) }", + "service : { f : (text) -> (text); g : () -> () }", + ), + ( + "service : { f : (nat) -> () }", + "service : { f : (int) -> () }", + ), + ]; + for (new, old) in incompatible { + let report = get_errors(new, old); + assert!(!report.is_empty(), "report should have errors"); + assert!( + service_compatible(CandidSource::Text(new), CandidSource::Text(old)).is_err(), + "service_compatible should fail" + ); + } +} + +// =========================================================================== +// Hierarchical report formatting +// =========================================================================== + +#[test] +fn format_report_groups_by_method_and_nests() { + let old = r#"service : { + transfer : (record { from : text; to : text; amount : nat }) -> (record { ok : bool; balance : nat }); + balance : () -> (nat); + audit : () -> (record { count : nat; log : text }) query; + config : () -> (record { flag : bool }); + }"#; + let new = r#"service : { + transfer : (record { from : text; to : text; amount : text }) -> (record { ok : text; balance : text }); + balance : () -> (text); + audit : () -> (record { count : text; log : nat }); + }"#; + let errors = get_errors(new, old); + assert!(errors.len() >= 6, "expected many errors, got: {errors:?}"); + + let report = format_report(&errors); + + // Each method should appear exactly once as a group header + for method in ["transfer", "balance", "config"] { + let header_count = report + .lines() + .filter(|l| { + let trimmed = l.trim_start(); + trimmed.starts_with(&format!("method \"{method}\"")) + || trimmed.contains(&format!("method \"{method}\"")) + }) + .count(); + assert!( + header_count <= 1, + "{method} should appear at most once as header, got {header_count} in:\n{report}" + ); + } + + // Should have indented sub-groups + assert!( + report.contains(" return type") || report.contains(" input type"), + "should have indented sub-groups:\n{report}" + ); +} + +#[test] +fn format_report_inlines_single_leaf() { + let old = "service : { get : () -> (nat) }"; + let new = "service : { get : () -> (text) }"; + let report = format_report(&get_errors(new, old)); + // Single error under method > return type should inline compactly + assert!( + report.lines().count() <= 3, + "single error should be compact:\n{report}" + ); +} + +#[test] +fn format_report_handles_path_and_pathless_errors() { + // Pathless errors (e.g. top-level type mismatch) should render as "- message" + let errors = vec![ + Incompatibility { + path: vec![], + message: "top-level mismatch".to_string(), + }, + Incompatibility { + path: vec!["method \"foo\"".to_string()], + message: "missing in new interface".to_string(), + }, + ]; + let report = format_report(&errors); + assert!( + report.contains("- top-level mismatch"), + "pathless error should render with bullet:\n{report}" + ); + assert!( + report.contains("method \"foo\""), + "pathed error should render:\n{report}" + ); +} + +// =========================================================================== +// Input-side message wording +// =========================================================================== + +#[test] +fn input_record_required_field_added_message_names_correct_side() { + let old = "type R = record { name : text }; service : { f : (R) -> () }"; + let new = "type R = record { name : text; age : nat }; service : { f : (R) -> () }"; + let errors = error_strings(new, old); + assert_eq!( + errors.len(), + 1, + "expected exactly one error, got: {errors:?}" + ); + let msg = &errors[0]; + assert!( + !msg.contains("new type is missing"), + "message incorrectly blames the new type for the missing field: {msg}" + ); + assert!( + msg.contains("age"), + "message should mention the field name: {msg}" + ); +} + +#[test] +fn input_variant_case_removed_message_names_correct_side() { + let old = "type V = variant { a; b }; service : { f : (V) -> () }"; + let new = "type V = variant { a }; service : { f : (V) -> () }"; + let errors = error_strings(new, old); + assert_eq!( + errors.len(), + 1, + "expected exactly one error, got: {errors:?}" + ); + let msg = &errors[0]; + assert!( + !msg.contains("new variant has field"), + "message incorrectly says the new variant has the dropped case: {msg}" + ); + assert!( + msg.contains("b"), + "message should mention the variant case name: {msg}" + ); +} + +#[test] +fn input_arg_count_increase_message_has_correct_counts() { + let old = "service : { f : (nat) -> () }"; + let new = "service : { f : (nat, text) -> () }"; + let errors = error_strings(new, old); + assert!(!errors.is_empty(), "expected at least one error, got none"); + let msg = errors + .iter() + .find(|e| e.contains("arg")) + .unwrap_or_else(|| panic!("no arg-count error found in: {errors:?}")); + assert!( + msg.contains("old has 1") && msg.contains("new has 2"), + "arg-count message has wrong old/new values: {msg}" + ); +} diff --git a/rust/ic_principal/Cargo.toml b/rust/ic_principal/Cargo.toml index 1e5f3ed48..1d0a323fc 100644 --- a/rust/ic_principal/Cargo.toml +++ b/rust/ic_principal/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ic_principal" -version = "0.1.1" +version = "0.1.3" authors = ["DFINITY Stiftung "] edition = "2021" rust-version.workspace = true @@ -18,6 +18,8 @@ include = ["src", "Cargo.toml", "LICENSE", "README.md"] arbitrary = { workspace = true, optional = true } crc32fast = { version = "1.3.0", optional = true } data-encoding = { version = "2.3.2", optional = true } +rangemap = { version = "1.6.0", optional = true } +schemars = { version = "1.2.1", optional = true } serde = { workspace = true, optional = true } sha2 = { version = "0.10.1", optional = true } thiserror = { version = "1.0", optional = true } @@ -34,7 +36,9 @@ default = ['convert', 'self_authenticating', 'serde'] arbitrary = ['dep:arbitrary', 'serde'] convert = ['dep:crc32fast', 'dep:data-encoding', 'dep:thiserror'] self_authenticating = ['dep:sha2'] +schemars = ['dep:schemars', 'serde'] serde = ['dep:serde', 'convert'] +rangemap = ['dep:rangemap'] [lints] workspace = true diff --git a/rust/ic_principal/src/lib.rs b/rust/ic_principal/src/lib.rs index b30465a57..bc473fc5f 100644 --- a/rust/ic_principal/src/lib.rs +++ b/rust/ic_principal/src/lib.rs @@ -414,3 +414,43 @@ impl<'a> Arbitrary<'a> for Principal { Ok(principal) } } + +#[cfg(feature = "rangemap")] +impl rangemap::StepLite for Principal { + fn add_one(&self) -> Self { + let mut new = *self; + // do not increment the tag byte + for byte in new.bytes[..new.len as usize - 1].iter_mut().rev() { + *byte = byte.wrapping_add(1); + if *byte != 0 { + break; + } + } + new + } + fn sub_one(&self) -> Self { + let mut new = *self; + // do not decrement the tag byte + for byte in new.bytes[..new.len as usize - 1].iter_mut().rev() { + *byte = byte.wrapping_sub(1); + if *byte != 255 { + break; + } + } + new + } +} + +#[cfg(feature = "schemars")] +impl schemars::JsonSchema for Principal { + fn schema_name() -> std::borrow::Cow<'static, str> { + "Principal".into() + } + + fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema { + schemars::json_schema!({ + "type": "string", + "description": "ICP principal in text format." + }) + } +} diff --git a/rust/ic_principal/tests/principal.rs b/rust/ic_principal/tests/principal.rs index 541e719e3..1732fa156 100644 --- a/rust/ic_principal/tests/principal.rs +++ b/rust/ic_principal/tests/principal.rs @@ -267,6 +267,9 @@ fn impl_traits() { #[cfg(feature = "serde")] assert!(impls::impls!(Principal: Serialize & Deserialize<'static>)); + + #[cfg(feature = "rangemap")] + assert!(impls::impls!(Principal: rangemap::StepLite)); } #[test] @@ -293,3 +296,40 @@ fn self_authenticating_ok() { ]); assert_eq!(p1, p2); } + +#[test] +#[cfg(feature = "rangemap")] +fn rangemap_steplite_impl_works() { + use rangemap::{RangeInclusiveSet, StepLite}; + + let p1 = Principal::from_slice(&[0, 1, 2, 0]); + let p2 = Principal::from_slice(&[0, 1, 3, 0]); + + assert_eq!(p1.add_one(), p2); + assert_eq!(p2.sub_one(), p1); + + let mut set = RangeInclusiveSet::new(); + set.insert(p1..=p2); + assert!(set.contains(&p1)); + assert!(set.contains(&p2)); + assert!(!set.contains(&Principal::from_slice(&[0, 1, 4, 0]))); +} + +#[test] +fn as_slice_len_principal() { + let anonymous_principal = Principal::anonymous(); + assert_eq!(anonymous_principal.as_slice().len(), 1); + + let management_canister = Principal::management_canister(); + assert_eq!(management_canister.as_slice().len(), 0); + + let key = b"42"; + let self_authenticating = Principal::self_authenticating(key); + assert_eq!(self_authenticating.as_slice().len(), 29); + + let from_slice = Principal::from_slice(&TEST_CASE_BYTES); + assert_eq!(from_slice.as_slice().len(), 9); + + let from_text = Principal::from_text(TEST_CASE_TEXT).unwrap(); + assert_eq!(from_text.as_slice().len(), 9); +} diff --git a/spec/Type-doc-comments.md b/spec/Type-doc-comments.md new file mode 100644 index 000000000..ce266ba23 --- /dev/null +++ b/spec/Type-doc-comments.md @@ -0,0 +1,270 @@ +# Candid Type Doc Comments + +Date: Mar 12, 2026 + +## Motivation + +Rust canisters often use Rust doc comments as the primary source of interface +documentation. This works well for service methods exported with +`#[candid_method]`, because these doc comments are preserved when generating +textual Candid. However, the same is not true for data types exported through +`#[derive(CandidType)]`. + +As a result, generated `.did` files contain method-level documentation, but +drop documentation for: + +* type definitions +* record fields +* variant members + +This is problematic because much of the semantic meaning of a canister +interface is expressed on the data model rather than on the methods alone. +Request and response types often carry the important descriptions, constraints, +and invariants that downstream users need to understand. + +The goal of this proposal is to preserve Rust doc comments on exported Candid +types and their members so that generated `.did` files can act as a more +complete interface document. + +## Design + +The implementation follows the same overall pattern that is already used for +service method docs: doc comments are collected from Rust source and carried to +the textual Candid printer through a side channel. + +The crucial design choice is that docs are **not** stored on the structural type +representation itself. The runtime types in `candid::types` are used for +normalization, equality, hashing, and name assignment. Making docs part of +those structures would either perturb structural equality or require special +rules to ignore docs during comparison. Both would be awkward and fragile. + +Instead, the implementation introduces a parallel metadata flow: + +1. `candid_derive` extracts Rust doc comments while deriving `CandidType`. +2. `candid::types` memoizes that metadata per Rust `TypeId`. +3. `TypeContainer` translates that metadata to the final exported Candid type + names when constructing a `TypeEnv`. +4. `candid::pretty::candid` renders the docs when pretty-printing named type + definitions and their members. + +This preserves the current type pipeline while allowing documentation to follow +the exported interface. + +### Capturing docs in Rust derives + +When deriving `CandidType`, the derive machinery now extracts docs from: + +* the struct or enum item itself +* struct fields +* enum variants +* fields nested inside inline record payloads of variants + +The same extraction logic is shared with `#[candid_method]`, so line order and +blank lines are preserved consistently. + +The derive implementation emits both the existing structural type description +and a second metadata description. Concretely, the `CandidType` trait gains a +new default hook: + +```rust +fn _ty_doc() -> internal::TypeDoc { + internal::TypeDoc::default() +} +``` + +Manual implementations of `CandidType` remain valid because the new method has +a default implementation. + +### Representing type docs + +The additional metadata is represented separately from `TypeInner`: + +```rust +pub struct TypeDocs { + pub named: BTreeMap, +} + +pub struct TypeDoc { + pub docs: Vec, + pub fields: BTreeMap, +} + +pub struct FieldDoc { + pub docs: Vec, + pub ty: Option>, +} +``` + +`TypeDoc.docs` stores doc lines for a named type definition. + +`TypeDoc.fields` stores docs for members of that type. The key is the canonical +field identity used by Candid: + +* for named labels, `idl_hash(label)` +* for tuple labels, the numeric field id + +`FieldDoc.docs` stores the docs attached to a record field or variant member. + +`FieldDoc.ty` stores docs for nested inline record or variant payloads when +those payloads belong to a named exported type. + +### Type naming and association + +One challenge is that Rust names are not the final names used in textual +Candid. `TypeContainer` already rewrites the type graph to choose stable export +names and to normalize recursive and generic types. Therefore, doc attachment is +resolved at the same stage. + +The metadata is first memoized per Rust `TypeId`. When `TypeContainer` decides +to emit a named Candid type in `TypeEnv`, it also stores the corresponding doc +metadata under the exported type name. + +This ensures that doc comments remain attached correctly even when: + +* fields are sorted canonically by field id +* fields or variants are renamed through `serde(rename = "...")` +* generic or recursive types receive rewritten export names such as `List_1` + +### Rendering docs in textual Candid + +The existing `DocComments` structure currently stores only service method docs. +It is extended to also carry docs for named type definitions. + +When pretty-printing textual Candid, docs are emitted: + +* immediately above `type Foo = ...;` +* immediately above record fields +* immediately above variant members +* immediately above fields nested in inline record or variant payloads that are + rooted under a named type definition + +The existing service method behavior remains unchanged. + +The result is that Rust definitions such as + +```rust +/// Arguments for creating a cashier. +#[derive(CandidType, Deserialize)] +pub struct CashierArgs { + /// Human-readable display name. + pub name: String, + + /// Whether this cashier starts enabled. + pub enabled: bool, +} +``` + +produce Candid of the form + +```did +// Arguments for creating a cashier. +type CashierArgs = record { + // Whether this cashier starts enabled. + enabled : bool; + // Human-readable display name. + name : text; +}; +``` + +where field order still follows the canonical Candid ordering rules. + +## Special Cases + +### Tuples + +Tuple records are normally printed using tuple shorthand, e.g. + +```did +record { nat; text } +``` + +This syntax has no place to attach docs to individual tuple positions. Therefore +the printer falls back to explicit numeric fields when tuple members carry docs: + +```did +record { + // First element. + 0 : nat; + // Second element. + 1 : text; +} +``` + +This preserves the docs without changing the meaning of the Candid type. + +### Newtypes + +Single-field tuple structs continue to use their existing structural encoding. +Docs on the outer named type are preserved, but docs on the inner field are not +generally representable without changing the generated Candid shape. This +proposal deliberately preserves the existing shape. + +### Anonymous inline types in service signatures + +This proposal focuses on docs rooted at named exported types. Anonymous inline +record or variant types that appear only directly in service method arguments or +results are not given a separate documentation channel here. Supporting those +would require a second namespace keyed by service methods and argument/result +positions. + +## Alternatives + +### Docs on `TypeInner` + +One alternative is to store docs directly on `TypeInner` and `Field`, and let +the pretty-printer read them from the type tree itself. + +This was rejected because those structures are used for structural identity. +Adding docs there would either make otherwise-equal types compare differently, +or require custom equality semantics that ignore docs. Both options would make +the type system more brittle for the sake of a feature that only matters during +export. + +### Key docs directly in the pretty-printer + +Another option is to keep the core type system unchanged and let +`candid_derive` populate a richer `DocComments` map directly. + +This was rejected because `candid_derive` does not know the final exported +Candid names. If docs were keyed too early, they could become detached when the +type graph is normalized, fields are sorted, or export names are rewritten. + +### Rebuild a syntax AST + +The `candid_parser` crate already has a syntax-level pretty-printer that stores +docs directly on AST nodes. Reusing that machinery by reconstructing an AST from +the runtime type graph was considered as well. + +This was rejected as too invasive. It would require a larger refactoring of the +printer pipeline and still would not eliminate the need to transport doc +metadata from Rust derives into that AST. + +## Properties + +This design preserves the following properties: + +* structural type identity is unchanged +* service method docs continue to work as before +* doc extraction preserves line order and blank lines +* doc attachment is stable under canonical field sorting +* doc attachment is stable under renamed fields and variants +* generated `.did` output is deterministic + +## Tests + +The expected behavior is verified with end-to-end tests in +`rust/candid/tests/types.rs`. + +The tests cover: + +* docs on named record types +* docs on record fields +* docs on variant types +* docs on variant members +* coexistence with service method docs +* parsing the generated `.did` back through `candid_parser` and verifying that + docs are attached to the correct AST nodes + +These tests complement the existing parser-side doc comment tests in +`rust/candid_parser/tests`, which already cover syntax-level parsing and +pretty-printing of Candid comments. diff --git a/tools/didc/Cargo.toml b/tools/didc/Cargo.toml index b52511f3a..3eecbdd71 100644 --- a/tools/didc/Cargo.toml +++ b/tools/didc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "didc" -version = "0.5.3" +version = "0.6.1" authors = ["DFINITY Team"] edition = "2021" diff --git a/tools/didc/README.md b/tools/didc/README.md index 9c5d83e50..5fcb2dda9 100644 --- a/tools/didc/README.md +++ b/tools/didc/README.md @@ -3,7 +3,7 @@ A multi-purpose tool for Candid. ``` -didc 0.4.0 +didc 0.6.0 USAGE: didc diff --git a/tools/didc/src/main.rs b/tools/didc/src/main.rs index 5ae280008..76e6416d1 100644 --- a/tools/didc/src/main.rs +++ b/tools/didc/src/main.rs @@ -191,7 +191,25 @@ fn main() -> Result<()> { if strict { subtype::equal(&mut gamma, &env, &t1, &t2)?; } else { - subtype::subtype(&mut gamma, &env, &t1, &t2)?; + let errors = subtype::subtype_check_all(&mut gamma, &env, &t1, &t2); + if !errors.is_empty() { + let report = subtype::format_report(&errors); + eprintln!( + "{} {} incompatible change{} found:\n", + style("Error:").red().bold(), + errors.len(), + if errors.len() == 1 { "" } else { "s" } + ); + for line in report.lines() { + eprintln!(" {line}"); + } + eprintln!(); + bail!( + "new interface is not backward compatible ({} breaking change{})", + errors.len(), + if errors.len() == 1 { "" } else { "s" } + ); + } } } _ => { diff --git a/tools/ui/.gitignore b/tools/ui/.gitignore index b95879510..11f5b07bf 100644 --- a/tools/ui/.gitignore +++ b/tools/ui/.gitignore @@ -7,10 +7,15 @@ .DS_Store **/.DS_Store -# dfx temporary files -.dfx/ +# icp-cli ephemeral state (local network + build cache). +# Persistent data such as mainnet canister id mappings lives in .icp/data/ +# and IS committed. +.icp/cache/ # frontend code node_modules/ dist/ ts-out/ + +.dfx +.icp/cache diff --git a/tools/ui/.icp/data/mappings/ic.ids.json b/tools/ui/.icp/data/mappings/ic.ids.json new file mode 100644 index 000000000..18e5489dd --- /dev/null +++ b/tools/ui/.icp/data/mappings/ic.ids.json @@ -0,0 +1,3 @@ +{ + "didjs": "a4gq6-oaaaa-aaaab-qaa4q-cai" +} diff --git a/tools/ui/Cargo.lock b/tools/ui/Cargo.lock index 418d7950f..dfc1789f2 100644 --- a/tools/ui/Cargo.lock +++ b/tools/ui/Cargo.lock @@ -1,27 +1,36 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] [[package]] name = "anyhow" -version = "1.0.89" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "ar_archive_writer" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c269894b6fe5e9d7ada0cf69b5bf847ff35bc25fc271f08e1d080fce80339a" +dependencies = [ + "object", +] [[package]] name = "arbitrary" -version = "1.3.2" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" [[package]] name = "arrayvec" @@ -40,9 +49,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "base64" @@ -96,9 +105,9 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "block-buffer" @@ -117,13 +126,13 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.2" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "candid" -version = "0.10.13" +version = "0.10.29" dependencies = [ "anyhow", "binread", @@ -139,17 +148,17 @@ dependencies = [ "serde", "serde_bytes", "stacker", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "candid_derive" -version = "0.6.6" +version = "0.10.29" dependencies = [ "lazy_static", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.111", ] [[package]] @@ -168,23 +177,24 @@ dependencies = [ "logos", "num-bigint", "pretty", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "cc" -version = "1.1.22" +version = "1.2.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9540e661f81799159abee814118cc139a2004b3a3aa3ea37724a1b66530b90e0" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" dependencies = [ + "find-msvc-tools", "shlex", ] [[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 = "codespan-reporting" @@ -193,7 +203,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" dependencies = [ "termcolor", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] @@ -207,43 +217,77 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", ] +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.111", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.111", +] + [[package]] name = "data-encoding" -version = "2.6.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "didjs" @@ -251,10 +295,11 @@ version = "0.1.0" dependencies = [ "candid", "candid_parser", + "hex", "ic-cdk", "ic-http-certification", + "percent-encoding", "serde", - "serde_bytes", ] [[package]] @@ -290,9 +335,9 @@ dependencies = [ [[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 = "ena" @@ -305,9 +350,15 @@ dependencies = [ [[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 = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "fixedbitset" @@ -333,9 +384,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", @@ -350,9 +401,15 @@ checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hex" @@ -373,29 +430,42 @@ dependencies = [ [[package]] name = "ic-cdk" -version = "0.16.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8ecacd682fa05a985253592963306cb9799622d7b1cce4b1edb89c6ec85be1" +checksum = "6a7971f4983db147afbbc4e7f87f60b09fcd60ac707af37ff3d2468dcddbf551" dependencies = [ "candid", + "ic-cdk-executor", "ic-cdk-macros", + "ic-error-types", "ic0", + "pin-project-lite", "serde", - "serde_bytes", + "thiserror 2.0.18", +] + +[[package]] +name = "ic-cdk-executor" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33716b730ded33690b8a704bff3533fda87d229e58046823647d28816e9bcee7" +dependencies = [ + "ic0", + "slotmap", + "smallvec", ] [[package]] name = "ic-cdk-macros" -version = "0.16.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d4d857135deef20cc7ea8f3869a30cd9cfeb1392b3a81043790b2cd82adc3e0" +checksum = "a7c20c002200c720958f321bb78b4d4987fc2c380bf9ef69ed4404730810f45f" dependencies = [ "candid", + "darling", "proc-macro2", "quote", - "serde", - "serde_tokenstream", - "syn 2.0.79", + "syn 2.0.111", ] [[package]] @@ -409,6 +479,17 @@ dependencies = [ "sha2", ] +[[package]] +name = "ic-error-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbeeb3d91aa179d6496d7293becdacedfc413c825cac79fd54ea1906f003ee55" +dependencies = [ + "serde", + "strum", + "strum_macros", +] + [[package]] name = "ic-http-certification" version = "2.6.0" @@ -421,7 +502,7 @@ dependencies = [ "ic-representation-independent-hash", "serde", "serde_cbor", - "thiserror", + "thiserror 1.0.69", "urlencoding", ] @@ -436,27 +517,33 @@ dependencies = [ [[package]] name = "ic0" -version = "0.23.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de254dd67bbd58073e23dc1c8553ba12fa1dc610a19de94ad2bbcd0460c067f" +checksum = "c77c8932bff1f09502d0d8c079d5a206a06afe523e35e816162cf4d30b5bf80d" [[package]] name = "ic_principal" -version = "0.1.1" +version = "0.1.3" dependencies = [ "arbitrary", "crc32fast", "data-encoding", "serde", "sha2", - "thiserror", + "thiserror 1.0.69", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "indexmap" -version = "2.5.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", "hashbrown", @@ -473,9 +560,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "lalrpop" @@ -491,7 +578,7 @@ dependencies = [ "petgraph", "pico-args", "regex", - "regex-syntax 0.8.4", + "regex-syntax 0.8.8", "string_cache", "term", "tiny-keccak", @@ -522,15 +609,15 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.159" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ "bitflags", "libc", @@ -538,19 +625,18 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.22" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "logos" @@ -572,7 +658,7 @@ dependencies = [ "proc-macro2", "quote", "regex-syntax 0.6.29", - "syn 2.0.79", + "syn 2.0.111", ] [[package]] @@ -586,9 +672,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "new_debug_unreachable" @@ -626,16 +712,19 @@ dependencies = [ ] [[package]] -name = "once_cell" -version = "1.19.0" +name = "object" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -643,15 +732,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-link", ] [[package]] @@ -660,6 +749,12 @@ 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 = "petgraph" version = "0.6.5" @@ -672,9 +767,9 @@ dependencies = [ [[package]] name = "phf_shared" -version = "0.10.0" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", ] @@ -685,6 +780,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + [[package]] name = "precomputed-hash" version = "0.1.1" @@ -693,47 +794,48 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "pretty" -version = "0.12.3" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55c4d17d994b637e2f4daf6e5dc5d660d209d5642377d675d7a1c3ab69fa579" +checksum = "0d22152487193190344590e4f30e219cf3fe140d9e7a3fdb683d82aa2c5f4156" dependencies = [ "arrayvec", "typed-arena", - "unicode-width", + "unicode-width 0.2.2", ] [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "psm" -version = "0.1.23" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa37f80ca58604976033fae9515a8a2989fc13797d953f7c04fb8fa36a11f205" +checksum = "d11f2fedc3b7dafdc2851bc52f277377c5473d378859be234bc7ebb593144d01" dependencies = [ + "ar_archive_writer", "cc", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" -version = "0.5.6" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "355ae415ccd3a04315d3f8246e86d67689ea74d88d915576e1589a351062a13b" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] @@ -746,30 +848,30 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "regex" -version = "1.10.6" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax 0.8.4", + "regex-syntax 0.8.8", ] [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.8", ] [[package]] @@ -780,15 +882,15 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "same-file" @@ -807,20 +909,22 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ + "serde_core", "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.15" +version = "0.11.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" dependencies = [ "serde", + "serde_core", ] [[package]] @@ -834,33 +938,30 @@ dependencies = [ ] [[package]] -name = "serde_derive" -version = "1.0.210" +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.79", + "serde_derive", ] [[package]] -name = "serde_tokenstream" -version = "0.2.2" +name = "serde_derive" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64060d864397305347a78851c51588fd283767e7e7589829e8121d65512340f1" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "serde", - "syn 2.0.79", + "syn 2.0.111", ] [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -875,42 +976,78 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "siphasher" -version = "0.3.11" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "stacker" -version = "0.1.17" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799c883d55abdb5e98af1a7b3f23b9b6de8ecada0ecac058672d7635eb48ca7b" +checksum = "e1f8b29fb42aafcea4edeeb6b2f2d7ecd0d969c48b4cf0d2e64aafc471dd6e59" dependencies = [ "cc", "cfg-if", "libc", "psm", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] name = "string_cache" -version = "0.8.7" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" dependencies = [ "new_debug_unreachable", - "once_cell", "parking_lot", "phf_shared", "precomputed-hash", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.111", +] + [[package]] name = "syn" version = "1.0.109" @@ -924,9 +1061,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.79" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -955,22 +1092,42 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.18", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.111", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", ] [[package]] @@ -990,15 +1147,15 @@ checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" [[package]] name = "typenum" -version = "1.17.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-segmentation" @@ -1012,6 +1169,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -1042,9 +1205,9 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "winapi" @@ -1064,11 +1227,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1077,6 +1240,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-sys" version = "0.59.0" @@ -1086,6 +1255,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-targets" version = "0.52.6" diff --git a/tools/ui/Cargo.toml b/tools/ui/Cargo.toml index 417b27642..54191cf34 100644 --- a/tools/ui/Cargo.toml +++ b/tools/ui/Cargo.toml @@ -4,9 +4,10 @@ members = [ ] resolver = "2" -[profile.release] -lto = true -opt-level = 2 +[profile.canister] +inherits = "release" +opt-level = "z" +strip = "debuginfo" [patch.crates-io.candid] path = "../../rust/candid" diff --git a/tools/ui/README.md b/tools/ui/README.md index a648abe7b..6a6db54be 100644 --- a/tools/ui/README.md +++ b/tools/ui/README.md @@ -2,24 +2,41 @@ This canister generates a front-end UI for any canister running on the Internet Computer. -The backend `didjs` is build with the [Rust CDK](https://github.com/dfinity/cdk-rs) and the [Candid](../../rust/) crate to convert did file into JavaScript. -The frontend `ui` fetches the Candid interface from the running canister (currently only canisters built by Motoko expose the Candid interface) and renders the UI based on the interface signature. +Thi canister `didjs` is built with the [Rust CDK](https://github.com/dfinity/cdk-rs) and the [Candid](../../rust/) crate to convert did file into JavaScript. + +It bundles the certified frontend assets and serves them with a cookie compatible with [CanisterEnv](https://js.icp.build/core/v5.3/libs/agent/canister-env/api/interfaces/canisterenv). The CanisterEnv will contain a `CANISTER_ID` value that the frontend can use to call back to the canister. + +The frontend fetches the Candid interface from the target canister by looking it up in the metadata under `candid:service` and renders the UI based on the interface signature. + +## Q&A + +### Why a single canister? +This canister is also meant to be deployed on the local network during local development. It's easier if everything is self contained. ## Build -You need `dfx`, `cargo`, `npm` and `wasm-opt` for building the canister. +You need [`icp`](https://cli.internetcomputer.org), `cargo`, `npm`, `wasm-opt` and +`ic-wasm` for building the canister. The frontend is bundled with [Vite](https://vite.dev). ```bash cd ui/ npm install -dfx start --background -dfx canister create --all -dfx build -dfx canister install --all +icp network start -d # start a local replica in the background +icp deploy # build + deploy the didjs canister locally ``` +To deploy to mainnet (the `ic` environment), which targets the existing +`a4gq6-oaaaa-aaaab-qaa4q-cai` canister: + +```bash +icp deploy -e ic +``` + +Project configuration lives in `icp.yaml`. Canister id mappings are stored under +`.icp/`: the mainnet mapping (`.icp/data/mappings/ic.ids.json`) is committed, while +local replica state under `.icp/cache/` is ephemeral and git-ignored. + ## Usage -Open the `ui` canister in a browser and supply the id parameter in the URL to render a specific canister, -e.g., `localhost:8000/?canisterId=ui-canister-id&id=target-canister-id` for local dev, -or `ui-canister-id.ic0.app/?id=target-canister-id` for the main network. +Open the canister in a browser and supply the id parameter in the URL to render a specific canister, +e.g., `didjs.local.localhost:8000/?id=target-canister-id` for local dev diff --git a/tools/ui/canister_ids.json b/tools/ui/canister_ids.json deleted file mode 100644 index 016284e12..000000000 --- a/tools/ui/canister_ids.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "didjs": { - "ic": "a4gq6-oaaaa-aaaab-qaa4q-cai" - } -} \ No newline at end of file diff --git a/tools/ui/dfx.json b/tools/ui/dfx.json deleted file mode 100644 index 5fa1ceef7..000000000 --- a/tools/ui/dfx.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "canisters": { - "didjs": { - "type": "custom", - "candid": "src/didjs/didjs.did", - "wasm": "target/wasm32-unknown-unknown/release/didjs_opt.wasm", - "build": [ - "npm run build", - "cargo build --target wasm32-unknown-unknown --release --package didjs", - "wasm-opt --strip-debug -Oz target/wasm32-unknown-unknown/release/didjs.wasm -o target/wasm32-unknown-unknown/release/didjs_opt.wasm" - ], - "metadata": [ - { - "name": "candid:service" - } - ] - } - }, - "version": 1 -} diff --git a/tools/ui/icp.yaml b/tools/ui/icp.yaml new file mode 100644 index 000000000..d00091fde --- /dev/null +++ b/tools/ui/icp.yaml @@ -0,0 +1,11 @@ +canisters: + - name: didjs + build: + steps: + - type: script + commands: + - npm run build + - cargo build --target wasm32-unknown-unknown --profile canister --package didjs + - wasm-opt --strip-debug -Oz target/wasm32-unknown-unknown/canister/didjs.wasm -o target/wasm32-unknown-unknown/canister/didjs_opt.wasm + - ic-wasm target/wasm32-unknown-unknown/canister/didjs_opt.wasm -o target/wasm32-unknown-unknown/canister/didjs_opt.wasm metadata candid:service -f src/didjs/didjs.did -v public + - cp target/wasm32-unknown-unknown/canister/didjs_opt.wasm "$ICP_WASM_OUTPUT_PATH" diff --git a/tools/ui/src/candid.html b/tools/ui/index.html similarity index 99% rename from tools/ui/src/candid.html rename to tools/ui/index.html index cd681f3de..12fb4aac2 100644 --- a/tools/ui/src/candid.html +++ b/tools/ui/index.html @@ -137,5 +137,6 @@

Candid UI

outputButton.click(); } + diff --git a/tools/ui/package-lock.json b/tools/ui/package-lock.json index 68498652f..a8dae33e1 100644 --- a/tools/ui/package-lock.json +++ b/tools/ui/package-lock.json @@ -1,4935 +1,1324 @@ { "name": "ui", "version": "0.1.0", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ui", "version": "0.1.0", "devDependencies": { - "@dfinity/agent": "2.1.3", - "@dfinity/auth-client": "2.1.3", - "@dfinity/candid": "2.1.3", - "@dfinity/identity": "2.1.3", - "@dfinity/principal": "2.1.3", - "buffer": "6.0.3", - "copy-webpack-plugin": "^12.0.2", - "css-loader": "^7.1.2", - "html-webpack-plugin": "^5.6.0", - "style-loader": "^4.0.0", - "terser-webpack-plugin": "^5.3.10", - "ts-loader": "^9.5.1", - "tsconfig-paths-webpack-plugin": "^4.1.0", + "@icp-sdk/auth": "^7.1.0", + "@icp-sdk/canisters": "^3.6.0", + "@icp-sdk/core": "^5.4.0", "typescript": "^5.6.2", - "webpack": "5.95.0", - "webpack-cli": "5.1.4" + "vite": "^6.0.0", + "vite-plugin-css-injected-by-js": "^3.5.2" } }, - "../agent-js/packages/agent": { - "extraneous": true + "node_modules/@dfinity/cbor": { + "version": "0.2.3", + "dev": true, + "license": "Apache-2.0" }, - "node_modules/@dfinity/agent": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@dfinity/agent/-/agent-2.1.3.tgz", - "integrity": "sha512-4XmqhFR3GQSUrmx7lMFx7DyHEhFkM6nz4O9FeYJ/WpkmPe8tulKaAfgWbWdTSCjbd8meCgKVHo+QYj+JHXagcw==", + "node_modules/@dfinity/utils": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@dfinity/utils/-/utils-4.2.1.tgz", + "integrity": "sha512-5Rd0fflQzBf3h/doVvllJC0X+mrZPwbe6s9qfJm5jyJz07SEkN+cGmqY0HHMPDlCOohZgjDoAG6uwv+6cOMA+w==", "dev": true, - "dependencies": { - "@noble/curves": "^1.4.0", - "@noble/hashes": "^1.3.1", - "base64-arraybuffer": "^0.2.0", - "borc": "^2.1.1", - "buffer": "^6.0.3", - "simple-cbor": "^0.4.1" - }, + "license": "Apache-2.0", + "peer": true, "peerDependencies": { - "@dfinity/candid": "^2.1.3", - "@dfinity/principal": "^2.1.3" + "@icp-sdk/core": "^5" } }, - "node_modules/@dfinity/auth-client": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@dfinity/auth-client/-/auth-client-2.1.3.tgz", - "integrity": "sha512-6lxv7w8FWSnu5FxMa75O1lddUQJWDDMgJCG1DeYM4+08dWy0TxV/CeEo2uInAdqwlZECgQYB3yQEZKUNkbA6OQ==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], "dev": true, - "dependencies": { - "idb": "^7.0.2" - }, - "peerDependencies": { - "@dfinity/agent": "^2.1.3", - "@dfinity/identity": "^2.1.3", - "@dfinity/principal": "^2.1.3" + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@dfinity/candid": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@dfinity/candid/-/candid-2.1.3.tgz", - "integrity": "sha512-Asn7AfydLhhk7E5z9oW+5UL6ne11gxFlYTxHuhrIc7FdqYlM5Flcq1Wfg9EzRa6Btdol3w58Bcph7Brwh1bcIQ==", + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], "dev": true, - "peerDependencies": { - "@dfinity/principal": "^2.1.3" + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@dfinity/identity": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@dfinity/identity/-/identity-2.1.3.tgz", - "integrity": "sha512-qII0V91S1YeIz5/XRHomwrUhTME+C3oqdTnb99tBitXA2Gq6LU2JaCLbKbN7ehhSyW6EjO4tySJxANz6hYENcQ==", + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@noble/curves": "^1.2.0", - "@noble/hashes": "^1.3.1", - "borc": "^2.1.1" - }, - "peerDependencies": { - "@dfinity/agent": "^2.1.3", - "@dfinity/principal": "^2.1.3", - "@peculiar/webcrypto": "^1.4.0" + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@dfinity/principal": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@dfinity/principal/-/principal-2.1.3.tgz", - "integrity": "sha512-HtiAfZcs+ToPYFepVJdFlorIfPA56KzC6J97ZuH2lGNMTAfJA+NEBzLe476B4wVCAwZ0TiGJ27J4ks9O79DFEg==", + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@noble/hashes": "^1.3.1" + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz", - "integrity": "sha512-Fxt+AfXgjMoin2maPIYzFZnQjAXjAL0PHscM5pRTtatFqB+vZxAM9tLp2Optnuw3QOQC40jTNeGYFOMvyf7v9g==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "cpu": [ + "arm64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=10.0.0" + "node": ">=18" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=6.0.0" + "node": ">=18" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=6.0.0" + "node": ">=18" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=6.0.0" + "node": ">=18" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@noble/curves": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.6.0.tgz", - "integrity": "sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], "dev": true, - "dependencies": { - "@noble/hashes": "1.5.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" + "node": ">=18" } }, - "node_modules/@noble/hashes": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz", - "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" + "node": ">=18" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 8" + "node": ">=18" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 8" + "node": ">=18" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 8" + "node": ">=18" } }, - "node_modules/@peculiar/asn1-schema": { - "version": "2.3.13", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.13.tgz", - "integrity": "sha512-3Xq3a01WkHRZL8X04Zsfg//mGaA21xlL4tlVn4v2xGT0JStiztATRkMwa5b+f/HXmY2smsiLXYK46Gwgzvfg3g==", + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], "dev": true, - "peer": true, - "dependencies": { - "asn1js": "^3.0.5", - "pvtsutils": "^1.3.5", - "tslib": "^2.6.2" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@peculiar/json-schema": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", - "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], "dev": true, - "peer": true, - "dependencies": { - "tslib": "^2.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8.0.0" + "node": ">=18" } }, - "node_modules/@peculiar/webcrypto": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.5.0.tgz", - "integrity": "sha512-BRs5XUAwiyCDQMsVA9IDvDa7UBR9gAvPHgugOeGng3YN6vJ9JYonyDc0lNczErgtCWtucjR5N7VtaonboD/ezg==", + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], "dev": true, - "peer": true, - "dependencies": { - "@peculiar/asn1-schema": "^2.3.8", - "@peculiar/json-schema": "^1.1.12", - "pvtsutils": "^1.3.5", - "tslib": "^2.6.2", - "webcrypto-core": "^1.8.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=10.12.0" + "node": ">=18" } }, - "node_modules/@sindresorhus/merge-streams": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", - "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true - }, - "node_modules/@types/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", - "dev": true - }, - "node_modules/@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", - "dev": true - }, - "node_modules/@types/node": { - "version": "20.3.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.2.tgz", - "integrity": "sha512-vOBLVQeCQfIcF/2Y7eKFTqrMnizK5lRNQ7ykML/5RuwVXVWxYkgwS7xbt4B6fKCUPgbSL5FSsjHQpaGQP/dQmw==", - "dev": true - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", - "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", - "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@xtuc/long": "4.2.2" + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", - "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@xtuc/long": "4.2.2" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", - "dev": true - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", - "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-opt": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1", - "@webassemblyjs/wast-printer": "1.12.1" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", - "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", - "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "node_modules/@icp-sdk/auth": { + "version": "7.1.0", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1" + "@icp-sdk/signer": "^5.4.0", + "idb": "^7.1.1" + }, + "peerDependencies": { + "@icp-sdk/core": "^5" } }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", - "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "node_modules/@icp-sdk/canisters": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@icp-sdk/canisters/-/canisters-3.6.0.tgz", + "integrity": "sha512-GALqQiywObRabz98etHccTq1DpLy5Sbx3adrYO27ffbDgZGLY5sZcAQfR0CkP0Da6KUEauzZk3gBi8UDzbydHg==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@noble/hashes": "^1.8.0", + "base58-js": "^3.0.3", + "bech32": "^2.0.0", + "mime": "^3.0.0" + }, + "peerDependencies": { + "@dfinity/utils": "^4.1", + "@icp-sdk/core": "^5" } }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", - "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "node_modules/@icp-sdk/core": { + "version": "5.4.0", "dev": true, + "license": "Apache-2.0", + "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@xtuc/long": "4.2.2" + "@dfinity/cbor": "^0.2.3", + "@noble/curves": "^1.9.7", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "asn1js": "^3.0.7" } }, - "node_modules/@webpack-cli/configtest": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", - "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "node_modules/@icp-sdk/signer": { + "version": "5.4.0", "dev": true, - "engines": { - "node": ">=14.15.0" - }, + "license": "Apache-2.0", "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" + "@icp-sdk/core": "^5" } }, - "node_modules/@webpack-cli/info": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", - "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "node_modules/@noble/curves": { + "version": "1.9.7", "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, "engines": { - "node": ">=14.15.0" + "node": "^14.21.3 || >=16" }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@webpack-cli/serve": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", - "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", + "node_modules/@noble/hashes": { + "version": "1.8.0", "dev": true, + "license": "MIT", "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" + "node": "^14.21.3 || >=16" }, - "peerDependenciesMeta": { - "webpack-dev-server": { - "optional": true - } + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "node_modules/acorn": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", - "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==", + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.0.tgz", + "integrity": "sha512-IPIQ55ythEHkfEd9jMEi32OQ7SxURsGA43JI22lj01OLZNt2NUbJX8YUHxkVWyQ6daHPNn0truF5nSj3DQp6YQ==", + "cpu": [ + "arm" + ], "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.0.tgz", + "integrity": "sha512-M6s9cr10MibETyo8JsOkq+Lo1+lU6hcvb1MApnUql5qte/5hMEgzlN8/ReIKNfRV8rrqX50W1BX9zoUhC192RA==", + "cpu": [ + "arm64" + ], "dev": true, - "peerDependencies": { - "acorn": "^8" - } + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.0", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.0.tgz", + "integrity": "sha512-SIMzST3VFNXDAbeIWDWiFCNM5qncUBDWaEV7NfE7oZbDt2mgfW4MvbKdbYiGOLoM32gbTv608UMd0XktEYSD7w==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.0.tgz", + "integrity": "sha512-ezjfSQMP7ArdUsbBwbQIfwAlhE84I2iVnzQNCFSveqV42q+BmKlzVpf7mxv5EchLcoWU4y6/heFzVg1F+hodUQ==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.0.tgz", + "integrity": "sha512-9+qTWGW9AZRhnUgwtTwzNwcPlL87ngkeN0LA+q1bADvmY9aNvWaF2TFW8BZgnQPYxpDI7+rMVLivcd4V737TAQ==", + "cpu": [ + "x64" + ], "dev": true, - "peerDependencies": { - "ajv": "^6.9.1" - } + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.0.tgz", + "integrity": "sha512-T1dMEQhXA/jkJ/jyMIw9IovK8bSUq7A8kLIlvZTb/6YIVsp2zLavr4F3oyllHWo7eIVJRyE5n3tUjQJEbE1IuQ==", + "cpu": [ + "arm" + ], "dev": true, - "engines": { - "node": ">=8" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.0.tgz", + "integrity": "sha512-2as0LgT7qQpyceQq6VUJYnumUMUrgGQCWIiDIN9DE0/tglsk6o66uCB4f3djRawAltvfCNLyZZrsqbPA6inCsA==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/asn1js": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz", - "integrity": "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==", + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.0.tgz", + "integrity": "sha512-bVURMg+6eNN9C/yc0aVjooZcwTTtYF4YW3xta5pP0//r3o1V8gXEHXWCndj47w/HhwsFroZrFhR+6uQP5T0n0g==", + "cpu": [ + "arm64" + ], "dev": true, - "peer": true, - "dependencies": { - "pvtsutils": "^1.3.2", - "pvutils": "^1.1.3", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=12.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/base64-arraybuffer": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz", - "integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==", + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.0.tgz", + "integrity": "sha512-Ful8pM/2yYI83PViWdFdpZhdI8HJ5qsXANe5atypbHDf+KIBBDsZsbyy8hbXnULVvW9NsTh5DHwbcBftyLTfiw==", + "cpu": [ + "arm64" + ], "dev": true, - "engines": { - "node": ">= 0.6.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.0.tgz", + "integrity": "sha512-9Gp/DgrkzfUBmNPVTyPTvay+4xEP7M/clXpj3efXBcm6uTIVIgDg4rqUpqKXvLEuFRVuEpSAOkhgNeecvaZ4Cg==", + "cpu": [ + "loong64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" ] }, - "node_modules/bignumber.js": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", - "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==", + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.0.tgz", + "integrity": "sha512-m9tsJz54LUXkSYM8+8PG81B9IKK5r+2T0clMq4QrS16xFosufU7firBDAZEsDheDs7wTlP7h3++S7lMsU955HA==", + "cpu": [ + "loong64" + ], "dev": true, - "engines": { - "node": "*" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true - }, - "node_modules/borc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/borc/-/borc-2.1.2.tgz", - "integrity": "sha512-Sy9eoUi4OiKzq7VovMn246iTo17kzuyHJKomCfpWMlI6RpfN1gk95w7d7gH264nApVLg0HZfcpz62/g4VH1Y4w==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.0.tgz", + "integrity": "sha512-3UvJ5PNVU16aJf6M3tFI24pWzAl2/ynfbyRN3ICyQajK1lSkrnVYNnLz3v04J32qKa0FczJc22zeToc0lr2A3w==", + "cpu": [ + "ppc64" + ], "dev": true, - "dependencies": { - "bignumber.js": "^9.0.0", - "buffer": "^5.5.0", - "commander": "^2.15.0", - "ieee754": "^1.1.13", - "iso-url": "~0.4.7", - "json-text-sequence": "~0.1.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=4" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/borc/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.0.tgz", + "integrity": "sha512-vRWUAbYLGHBZS6Q8Msb2sfnf1fvJf+47t8l/TwOerM2qArzy+IeNMTHrYLHXh95h8MoatPHI5hhSZNs+mGXKPg==", + "cpu": [ + "ppc64" ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/browserslist": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", - "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.0.tgz", + "integrity": "sha512-c00T5SYENHAt86cfW47URaP3Us5vLC/4QO7GYud1G5VNRffCwwCuBspwqYrriuJB+5m0WFzClCn9wed0FBjKvg==", + "cpu": [ + "riscv64" ], - "dependencies": { - "caniuse-lite": "^1.0.30001663", - "electron-to-chromium": "^1.5.28", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.0.tgz", + "integrity": "sha512-krrCDilhXOwFkSkO3Wm9I/f9H0L92XHHwy2fwxjukxIbh0dem8gZqOW5Y8BsHrpJv5qwlRBV+Wl4ZFyRWhUpwg==", + "cpu": [ + "riscv64" + ], "dev": true, - "dependencies": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/caniuse-lite": { - "version": "1.0.30001664", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001664.tgz", - "integrity": "sha512-AmE7k4dXiNKQipgn7a2xg558IRqPN3jMQY/rOsbxDhrd0tyChwbITBfiwtnqz8bi2M5mIWbxAYBvk7W7QBUS2g==", + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.0.tgz", + "integrity": "sha512-7pfYFSTc4/rUC/FtAI0Qp6QthDBCIi6/AuP1xYqFk5vanI6KnL5dWKP60OM/05LOsbwTmIcvr6eXC4CJuJ75IA==", + "cpu": [ + "s390x" + ], "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" ] }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.0.tgz", + "integrity": "sha512-7SDIalKeIpG0Ifogbbdn58HmSotYMlf23K3dCJEmiVd9Fg36Vmni82iPQec27N3wY4Bvbxftkxz6vSx9OcouTg==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.0.tgz", + "integrity": "sha512-eRZevouTH2i1HeAVLqJuLnt256krQkGY0TN6WsTmsIhuzbh457HuWDMakKwmi0Cjadux983CoSr8Lim2QhUIFw==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.0.tgz", + "integrity": "sha512-3oVS7FLGa4U1qcvao9ylGxrjXZyUQqR8UwxEcnUEyPX53O/C/mKDZegNXTdHCP+h3e6ta/f1EN38Yif1mmZHYg==", + "cpu": [ + "x64" + ], "dev": true, - "engines": { - "node": ">=6.0" - } + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] }, - "node_modules/clean-css": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", - "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.0.tgz", + "integrity": "sha512-yTB9TgfWj5wHe5QgktAgXTLLot1gvEjl1NiPPAUiCs4oPrIWFl5V4nC3GrkNdj9LaAU4s94nVrGbGOCqUpyWsg==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "source-map": "~0.6.0" - }, - "engines": { - "node": ">= 10.0" - } + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.0.tgz", + "integrity": "sha512-5LOhoaesY3doG1c+ac/2JtgREpKoJr5bUHH8tKY0V8di7+uSV6BwLs2PlR0/yzefGOkR+wE7ZolZphHCsyG5Rw==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.0.tgz", + "integrity": "sha512-yYkWHhmbhRTWTnWos5HC4GcPQfjlzzCNbM9e/+GXrLuaBXYA3qSDR9f0Vgufd5S8yX81U8jPKp7ZnAjZFMtRnw==", + "cpu": [ + "ia32" + ], "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.0.tgz", + "integrity": "sha512-SoTb6lPg25xZlA2ibwQ++ahCCnH+FP0qmEuafMJ4gznZKOlXioKEAeJLgCrqjM98ACziXM9V1amFjICVL4IFoA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.0.tgz", + "integrity": "sha512-5L+T1fMX4RIEBoZzT0+sQ0PhTS36NULFmMXtl1TZo44TMAROIMHbZufSOjVWt/Y622BtxgxtaNOokbTDvfsrZA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/copy-webpack-plugin": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", - "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==", + "node_modules/@scure/base": { + "version": "1.2.6", "dev": true, - "dependencies": { - "fast-glob": "^3.3.2", - "glob-parent": "^6.0.1", - "globby": "^14.0.0", - "normalize-path": "^3.0.0", - "schema-utils": "^4.2.0", - "serialize-javascript": "^6.0.2" - }, - "engines": { - "node": ">= 18.12.0" - }, + "license": "MIT", "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" + "url": "https://paulmillr.com/funding/" } }, - "node_modules/copy-webpack-plugin/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "node_modules/@scure/bip32": { + "version": "1.7.0", "dev": true, + "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "url": "https://paulmillr.com/funding/" } }, - "node_modules/copy-webpack-plugin/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "node_modules/@scure/bip39": { + "version": "1.6.0", "dev": true, + "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3" + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" }, - "peerDependencies": { - "ajv": "^8.8.2" + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/copy-webpack-plugin/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/copy-webpack-plugin/node_modules/schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "node_modules/@types/estree": { + "version": "1.0.9", "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } + "license": "MIT" }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "node_modules/asn1js": { + "version": "3.0.10", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.5", + "tslib": "^2.8.1" }, "engines": { - "node": ">= 8" + "node": ">=12.0.0" } }, - "node_modules/css-loader": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", - "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", + "node_modules/base58-js": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/base58-js/-/base58-js-3.0.3.tgz", + "integrity": "sha512-3hf42BysHnUqmZO7mK6e5X/hs1AvyEJIhdVLbG/Mxn/fhFnhGxOO37mWbMHg1RT4TxqcPKXgqj9/bp1YG0GBXA==", "dev": true, - "dependencies": { - "icss-utils": "^5.1.0", - "postcss": "^8.4.33", - "postcss-modules-extract-imports": "^3.1.0", - "postcss-modules-local-by-default": "^4.0.5", - "postcss-modules-scope": "^3.2.0", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.2.0", - "semver": "^7.5.4" - }, + "license": "MIT", "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "webpack": "^5.27.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } + "node": ">=16.0.0", + "npm": ">=7.0.0" } }, - "node_modules/css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "node_modules/bech32": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", + "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==", "dev": true, - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } + "license": "MIT" }, - "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "node_modules/esbuild": { + "version": "0.25.12", "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, "engines": { - "node": ">= 6" + "node": ">=18" }, - "funding": { - "url": "https://github.com/sponsors/fb55" + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/cssesc": { + "node_modules/idb": { + "version": "7.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/mime": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", "dev": true, + "license": "MIT", "bin": { - "cssesc": "bin/cssesc" + "mime": "cli.js" }, "engines": { - "node": ">=4" + "node": ">=10.0.0" } }, - "node_modules/delimit-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/delimit-stream/-/delimit-stream-0.1.0.tgz", - "integrity": "sha1-m4MZR3wOX4rrPONXrjBfwl6hzSs=", - "dev": true - }, - "node_modules/dom-converter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", - "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "node_modules/nanoid": { + "version": "3.3.12", "dev": true, - "dependencies": { - "utila": "~0.4" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "node_modules/picocolors": { + "version": "1.1.1", "dev": true, - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } + "license": "ISC" }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "node_modules/postcss": { + "version": "8.5.15", "dev": true, "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, { "type": "github", - "url": "https://github.com/sponsors/fb55" + "url": "https://github.com/sponsors/ai" } - ] - }, - "node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "dev": true, + ], + "license": "MIT", "dependencies": { - "domelementtype": "^2.2.0" + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" + "node": "^10 || ^12 || >=14" } }, - "node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "node_modules/pvtsutils": { + "version": "1.3.6", "dev": true, + "license": "MIT", "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" + "tslib": "^2.8.1" } }, - "node_modules/dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "node_modules/pvutils": { + "version": "1.1.5", "dev": true, - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" + "license": "MIT", + "engines": { + "node": ">=16.0.0" } }, - "node_modules/electron-to-chromium": { - "version": "1.5.29", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.29.tgz", - "integrity": "sha512-PF8n2AlIhCKXQ+gTpiJi0VhcHDb69kYX4MtCiivctc2QD3XuNZ/XIOlbGzt7WAjjEev0TtaH6Cu3arZExm5DOw==", - "dev": true - }, - "node_modules/enhanced-resolve": { - "version": "5.17.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", - "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "node_modules/rollup": { + "version": "4.62.0", "dev": true, + "license": "MIT", "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "@types/estree": "1.0.9" }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/envinfo": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", - "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", - "dev": true, "bin": { - "envinfo": "dist/cli.js" - }, - "engines": { - "node": ">=4" + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.0", + "@rollup/rollup-android-arm64": "4.62.0", + "@rollup/rollup-darwin-arm64": "4.62.0", + "@rollup/rollup-darwin-x64": "4.62.0", + "@rollup/rollup-freebsd-arm64": "4.62.0", + "@rollup/rollup-freebsd-x64": "4.62.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.0", + "@rollup/rollup-linux-arm-musleabihf": "4.62.0", + "@rollup/rollup-linux-arm64-gnu": "4.62.0", + "@rollup/rollup-linux-arm64-musl": "4.62.0", + "@rollup/rollup-linux-loong64-gnu": "4.62.0", + "@rollup/rollup-linux-loong64-musl": "4.62.0", + "@rollup/rollup-linux-ppc64-gnu": "4.62.0", + "@rollup/rollup-linux-ppc64-musl": "4.62.0", + "@rollup/rollup-linux-riscv64-gnu": "4.62.0", + "@rollup/rollup-linux-riscv64-musl": "4.62.0", + "@rollup/rollup-linux-s390x-gnu": "4.62.0", + "@rollup/rollup-linux-x64-gnu": "4.62.0", + "@rollup/rollup-linux-x64-musl": "4.62.0", + "@rollup/rollup-openbsd-x64": "4.62.0", + "@rollup/rollup-openharmony-arm64": "4.62.0", + "@rollup/rollup-win32-arm64-msvc": "4.62.0", + "@rollup/rollup-win32-ia32-msvc": "4.62.0", + "@rollup/rollup-win32-x64-gnu": "4.62.0", + "@rollup/rollup-win32-x64-msvc": "4.62.0", + "fsevents": "~2.3.2" } }, - "node_modules/es-module-lexer": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", - "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", - "dev": true - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "node_modules/source-map-js": { + "version": "1.2.1", "dev": true, + "license": "BSD-3-Clause", "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "node_modules/tinyglobby": { + "version": "0.2.17", "dev": true, + "license": "MIT", "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" }, "engines": { - "node": ">=8.0.0" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, + "license": "MIT", "engines": { - "node": ">=4.0" + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", "dev": true, + "license": "MIT", + "peer": true, "engines": { - "node": ">=4.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "node_modules/tslib": { + "version": "2.8.1", "dev": true, - "engines": { - "node": ">=4.0" - } + "license": "0BSD" }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "node_modules/typescript": { + "version": "5.6.2", "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, "engines": { - "node": ">=0.8.x" + "node": ">=14.17" } }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "node_modules/vite": { + "version": "6.4.3", "dev": true, + "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-uri": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.2.tgz", - "integrity": "sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row==", - "dev": true - }, - "node_modules/fastest-levenshtein": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", - "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", - "dev": true - }, - "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=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==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob-parent": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.1.tgz", - "integrity": "sha512-kEVjS71mQazDBHKcsq4E9u/vUzaLcw1A8EtUeydawvIWQCJM0qQ08G1H7/XTjFUulla6XQiDOG6MXSaG0HDKog==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "node_modules/globby": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", - "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", - "dev": true, - "dependencies": { - "@sindresorhus/merge-streams": "^2.1.0", - "fast-glob": "^3.3.2", - "ignore": "^5.2.4", - "path-type": "^5.0.0", - "slash": "^5.1.0", - "unicorn-magic": "^0.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", - "dev": true, - "dependencies": { - "camel-case": "^4.1.2", - "clean-css": "^5.2.2", - "commander": "^8.3.0", - "he": "^1.2.0", - "param-case": "^3.0.4", - "relateurl": "^0.2.7", - "terser": "^5.10.0" + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" }, "bin": { - "html-minifier-terser": "cli.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/html-minifier-terser/node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "dev": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/html-webpack-plugin": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz", - "integrity": "sha512-iwaY4wzbe48AfKLZ/Cc8k0L+FKG6oSNRaZ8x5A/T/IVDGyXcbHncM9TdDa93wn0FsSm82FhTKW7f3vS61thXAw==", - "dev": true, - "dependencies": { - "@types/html-minifier-terser": "^6.0.0", - "html-minifier-terser": "^6.0.2", - "lodash": "^4.17.21", - "pretty-error": "^4.0.0", - "tapable": "^2.0.0" + "vite": "bin/vite.js" }, "engines": { - "node": ">=10.13.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/html-webpack-plugin" + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" }, "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "webpack": "^5.20.0" + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { - "@rspack/core": { + "@types/node": { "optional": true }, - "webpack": { + "jiti": { "optional": true - } - } - }, - "node_modules/htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "node_modules/icss-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/idb": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", - "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", - "dev": true - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "less": { + "optional": true }, - { - "type": "consulting", - "url": "https://feross.org/support" + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } - ] - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-local": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", - "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", - "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/interpret": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", - "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", - "dev": true, - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/iso-url": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/iso-url/-/iso-url-0.4.7.tgz", - "integrity": "sha512-27fFRDnPAMnHGLq36bWTpKET+eiXct3ENlCcdcMdk+mjXrb2kw3mhBUg1B7ewAC0kVzlOPhADzQgz1SE6Tglog==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-text-sequence": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/json-text-sequence/-/json-text-sequence-0.1.1.tgz", - "integrity": "sha1-py8hfcSvxGKf/1/rME3BvVGi89I=", - "dev": true, - "dependencies": { - "delimit-stream": "0.1.0" - } - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" } }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "node_modules/vite-plugin-css-injected-by-js": { + "version": "3.5.2", "dev": true, - "engines": { - "node": ">=0.10.0" + "license": "MIT", + "peerDependencies": { + "vite": ">2.0.0-0" } }, - "node_modules/loader-runner": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", - "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", "dev": true, + "license": "MIT", "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" + "node": ">=12.0.0" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dev": true, - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" + "peerDependencies": { + "picomatch": "^3 || ^4" }, - "engines": { - "node": ">=8.6" + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/mime-db": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", - "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==", + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.4", "dev": true, + "license": "MIT", + "peer": true, "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.32", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", - "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", - "dev": true, - "dependencies": { - "mime-db": "1.49.0" + "node": ">=12" }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "url": "https://github.com/sponsors/jonschlinkert" } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node_modules/no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dev": true, - "dependencies": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", - "dev": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/param-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "dev": true, - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "dev": true, - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-type": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", - "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/postcss": { - "version": "8.4.47", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.1.0", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-modules-extract-imports": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", - "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", - "dev": true, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-local-by-default": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", - "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", - "dev": true, - "dependencies": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-scope": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", - "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", - "dev": true, - "dependencies": { - "postcss-selector-parser": "^6.0.4" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", - "dev": true, - "dependencies": { - "icss-utils": "^5.0.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "dev": true, - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true - }, - "node_modules/pretty-error": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", - "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", - "dev": true, - "dependencies": { - "lodash": "^4.17.20", - "renderkid": "^3.0.0" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pvtsutils": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz", - "integrity": "sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA==", - "dev": true, - "peer": true, - "dependencies": { - "tslib": "^2.6.1" - } - }, - "node_modules/pvutils": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", - "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/rechoir": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", - "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", - "dev": true, - "dependencies": { - "resolve": "^1.20.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/renderkid": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", - "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", - "dev": true, - "dependencies": { - "css-select": "^4.1.3", - "dom-converter": "^0.2.0", - "htmlparser2": "^6.1.0", - "lodash": "^4.17.21", - "strip-ansi": "^6.0.1" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/simple-cbor": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/simple-cbor/-/simple-cbor-0.4.1.tgz", - "integrity": "sha512-rijcxtwx2b4Bje3sqeIqw5EeW7UlOIC4YfOdwqIKacpvRQ/D78bWg/4/0m5e0U91oKvlGh7LlJuZCu07ISCC7w==", - "dev": true - }, - "node_modules/slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", - "dev": true, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/style-loader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz", - "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", - "dev": true, - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.27.0" - } - }, - "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tapable": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.0.tgz", - "integrity": "sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/terser": { - "version": "5.34.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.34.1.tgz", - "integrity": "sha512-FsJZ7iZLd/BXkz+4xrRTGJ26o/6VTjQytUk8b8OxkwcD2I+79VPJlz7qss1+zE7h8GNIScFqXcDyJ/KqBYZFVA==", - "dev": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.10", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", - "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.20", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.26.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-loader": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", - "integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4", - "source-map": "^0.7.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "typescript": "*", - "webpack": "^5.0.0" - } - }, - "node_modules/ts-loader/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/tsconfig-paths": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", - "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", - "dev": true, - "dependencies": { - "json5": "^2.2.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tsconfig-paths-webpack-plugin": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.1.0.tgz", - "integrity": "sha512-xWFISjviPydmtmgeUAuXp4N1fky+VCtfhOkDUFIv5ea7p4wuTomI4QTrXvFBX2S4jZsmyTSrStQl+E+4w+RzxA==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.7.0", - "tsconfig-paths": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/tslib": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", - "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", - "dev": true - }, - "node_modules/typescript": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", - "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/unicorn-magic": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", - "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", - "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.0" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "node_modules/utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", - "dev": true - }, - "node_modules/watchpack": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", - "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", - "dev": true, - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webcrypto-core": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.8.1.tgz", - "integrity": "sha512-P+x1MvlNCXlKbLSOY4cYrdreqPG5hbzkmawbcXLKN/mf6DZW0SdNNkZ+sjwsqVkI4A4Ko2sPZmkZtCKY58w83A==", - "dev": true, - "peer": true, - "dependencies": { - "@peculiar/asn1-schema": "^2.3.13", - "@peculiar/json-schema": "^1.1.12", - "asn1js": "^3.0.5", - "pvtsutils": "^1.3.5", - "tslib": "^2.7.0" - } - }, - "node_modules/webpack": { - "version": "5.95.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", - "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", - "dev": true, - "dependencies": { - "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.7.1", - "acorn-import-attributes": "^1.9.5", - "browserslist": "^4.21.10", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.1", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.10", - "watchpack": "^2.4.1", - "webpack-sources": "^3.2.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-cli": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", - "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", - "dev": true, - "dependencies": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^2.1.1", - "@webpack-cli/info": "^2.0.2", - "@webpack-cli/serve": "^2.0.5", - "colorette": "^2.0.14", - "commander": "^10.0.1", - "cross-spawn": "^7.0.3", - "envinfo": "^7.7.3", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^3.1.1", - "rechoir": "^0.8.0", - "webpack-merge": "^5.7.3" - }, - "bin": { - "webpack-cli": "bin/cli.js" - }, - "engines": { - "node": ">=14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "5.x.x" - }, - "peerDependenciesMeta": { - "@webpack-cli/generators": { - "optional": true - }, - "webpack-bundle-analyzer": { - "optional": true - }, - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/webpack-cli/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "dev": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/webpack-merge": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", - "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", - "dev": true, - "dependencies": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/webpack/node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", - "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", - "dev": true - } - }, - "dependencies": { - "@dfinity/agent": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@dfinity/agent/-/agent-2.1.3.tgz", - "integrity": "sha512-4XmqhFR3GQSUrmx7lMFx7DyHEhFkM6nz4O9FeYJ/WpkmPe8tulKaAfgWbWdTSCjbd8meCgKVHo+QYj+JHXagcw==", - "dev": true, - "requires": { - "@noble/curves": "^1.4.0", - "@noble/hashes": "^1.3.1", - "base64-arraybuffer": "^0.2.0", - "borc": "^2.1.1", - "buffer": "^6.0.3", - "simple-cbor": "^0.4.1" - } - }, - "@dfinity/auth-client": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@dfinity/auth-client/-/auth-client-2.1.3.tgz", - "integrity": "sha512-6lxv7w8FWSnu5FxMa75O1lddUQJWDDMgJCG1DeYM4+08dWy0TxV/CeEo2uInAdqwlZECgQYB3yQEZKUNkbA6OQ==", - "dev": true, - "requires": { - "idb": "^7.0.2" - } - }, - "@dfinity/candid": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@dfinity/candid/-/candid-2.1.3.tgz", - "integrity": "sha512-Asn7AfydLhhk7E5z9oW+5UL6ne11gxFlYTxHuhrIc7FdqYlM5Flcq1Wfg9EzRa6Btdol3w58Bcph7Brwh1bcIQ==", - "dev": true, - "requires": {} - }, - "@dfinity/identity": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@dfinity/identity/-/identity-2.1.3.tgz", - "integrity": "sha512-qII0V91S1YeIz5/XRHomwrUhTME+C3oqdTnb99tBitXA2Gq6LU2JaCLbKbN7ehhSyW6EjO4tySJxANz6hYENcQ==", - "dev": true, - "requires": { - "@noble/curves": "^1.2.0", - "@noble/hashes": "^1.3.1", - "borc": "^2.1.1" - } - }, - "@dfinity/principal": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@dfinity/principal/-/principal-2.1.3.tgz", - "integrity": "sha512-HtiAfZcs+ToPYFepVJdFlorIfPA56KzC6J97ZuH2lGNMTAfJA+NEBzLe476B4wVCAwZ0TiGJ27J4ks9O79DFEg==", - "dev": true, - "requires": { - "@noble/hashes": "^1.3.1" - } - }, - "@discoveryjs/json-ext": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz", - "integrity": "sha512-Fxt+AfXgjMoin2maPIYzFZnQjAXjAL0PHscM5pRTtatFqB+vZxAM9tLp2Optnuw3QOQC40jTNeGYFOMvyf7v9g==", - "dev": true - }, - "@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true - }, - "@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true - }, - "@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "@noble/curves": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.6.0.tgz", - "integrity": "sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==", - "dev": true, - "requires": { - "@noble/hashes": "1.5.0" - } - }, - "@noble/hashes": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz", - "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==", - "dev": true - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@peculiar/asn1-schema": { - "version": "2.3.13", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.13.tgz", - "integrity": "sha512-3Xq3a01WkHRZL8X04Zsfg//mGaA21xlL4tlVn4v2xGT0JStiztATRkMwa5b+f/HXmY2smsiLXYK46Gwgzvfg3g==", - "dev": true, - "peer": true, - "requires": { - "asn1js": "^3.0.5", - "pvtsutils": "^1.3.5", - "tslib": "^2.6.2" - } - }, - "@peculiar/json-schema": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", - "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", - "dev": true, - "peer": true, - "requires": { - "tslib": "^2.0.0" - } - }, - "@peculiar/webcrypto": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.5.0.tgz", - "integrity": "sha512-BRs5XUAwiyCDQMsVA9IDvDa7UBR9gAvPHgugOeGng3YN6vJ9JYonyDc0lNczErgtCWtucjR5N7VtaonboD/ezg==", - "dev": true, - "peer": true, - "requires": { - "@peculiar/asn1-schema": "^2.3.8", - "@peculiar/json-schema": "^1.1.12", - "pvtsutils": "^1.3.5", - "tslib": "^2.6.2", - "webcrypto-core": "^1.8.0" - } - }, - "@sindresorhus/merge-streams": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", - "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", - "dev": true - }, - "@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true - }, - "@types/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", - "dev": true - }, - "@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", - "dev": true - }, - "@types/node": { - "version": "20.3.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.2.tgz", - "integrity": "sha512-vOBLVQeCQfIcF/2Y7eKFTqrMnizK5lRNQ7ykML/5RuwVXVWxYkgwS7xbt4B6fKCUPgbSL5FSsjHQpaGQP/dQmw==", - "dev": true - }, - "@webassemblyjs/ast": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", - "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", - "dev": true, - "requires": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" - } - }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", - "dev": true - }, - "@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", - "dev": true - }, - "@webassemblyjs/helper-buffer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", - "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", - "dev": true - }, - "@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", - "dev": true, - "requires": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", - "dev": true - }, - "@webassemblyjs/helper-wasm-section": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", - "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" - } - }, - "@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", - "dev": true, - "requires": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", - "dev": true, - "requires": { - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", - "dev": true - }, - "@webassemblyjs/wasm-edit": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", - "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-opt": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1", - "@webassemblyjs/wast-printer": "1.12.1" - } - }, - "@webassemblyjs/wasm-gen": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", - "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } - }, - "@webassemblyjs/wasm-opt": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", - "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1" - } - }, - "@webassemblyjs/wasm-parser": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", - "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } - }, - "@webassemblyjs/wast-printer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", - "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.12.1", - "@xtuc/long": "4.2.2" - } - }, - "@webpack-cli/configtest": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", - "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", - "dev": true, - "requires": {} - }, - "@webpack-cli/info": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", - "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", - "dev": true, - "requires": {} - }, - "@webpack-cli/serve": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", - "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", - "dev": true, - "requires": {} - }, - "@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "acorn": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", - "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==", - "dev": true - }, - "acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", - "dev": true, - "requires": {} - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, - "requires": { - "ajv": "^8.0.0" - }, - "dependencies": { - "ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } - } - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "requires": {} - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "asn1js": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz", - "integrity": "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==", - "dev": true, - "peer": true, - "requires": { - "pvtsutils": "^1.3.2", - "pvutils": "^1.1.3", - "tslib": "^2.4.0" - } - }, - "base64-arraybuffer": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz", - "integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==", - "dev": true - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true - }, - "bignumber.js": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", - "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==", - "dev": true - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true - }, - "borc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/borc/-/borc-2.1.2.tgz", - "integrity": "sha512-Sy9eoUi4OiKzq7VovMn246iTo17kzuyHJKomCfpWMlI6RpfN1gk95w7d7gH264nApVLg0HZfcpz62/g4VH1Y4w==", - "dev": true, - "requires": { - "bignumber.js": "^9.0.0", - "buffer": "^5.5.0", - "commander": "^2.15.0", - "ieee754": "^1.1.13", - "iso-url": "~0.4.7", - "json-text-sequence": "~0.1.0", - "readable-stream": "^3.6.0" - }, - "dependencies": { - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - } - } - }, - "braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "requires": { - "fill-range": "^7.1.1" - } - }, - "browserslist": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", - "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001663", - "electron-to-chromium": "^1.5.28", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.0" - } - }, - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "dev": true, - "requires": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" - } - }, - "caniuse-lite": { - "version": "1.0.30001664", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001664.tgz", - "integrity": "sha512-AmE7k4dXiNKQipgn7a2xg558IRqPN3jMQY/rOsbxDhrd0tyChwbITBfiwtnqz8bi2M5mIWbxAYBvk7W7QBUS2g==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true - }, - "clean-css": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", - "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", - "dev": true, - "requires": { - "source-map": "~0.6.0" - } - }, - "clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "copy-webpack-plugin": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", - "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==", - "dev": true, - "requires": { - "fast-glob": "^3.3.2", - "glob-parent": "^6.0.1", - "globby": "^14.0.0", - "normalize-path": "^3.0.0", - "schema-utils": "^4.2.0", - "serialize-javascript": "^6.0.2" - }, - "dependencies": { - "ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - } - }, - "ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.3" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - } - } - } - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "css-loader": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", - "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", - "dev": true, - "requires": { - "icss-utils": "^5.1.0", - "postcss": "^8.4.33", - "postcss-modules-extract-imports": "^3.1.0", - "postcss-modules-local-by-default": "^4.0.5", - "postcss-modules-scope": "^3.2.0", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.2.0", - "semver": "^7.5.4" - } - }, - "css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "dev": true, - "requires": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - } - }, - "css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "dev": true - }, - "cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true - }, - "delimit-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/delimit-stream/-/delimit-stream-0.1.0.tgz", - "integrity": "sha1-m4MZR3wOX4rrPONXrjBfwl6hzSs=", - "dev": true - }, - "dom-converter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", - "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", - "dev": true, - "requires": { - "utila": "~0.4" - } - }, - "dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "dev": true, - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - } - }, - "domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true - }, - "domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "dev": true, - "requires": { - "domelementtype": "^2.2.0" - } - }, - "domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "dev": true, - "requires": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - } - }, - "dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "dev": true, - "requires": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "electron-to-chromium": { - "version": "1.5.29", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.29.tgz", - "integrity": "sha512-PF8n2AlIhCKXQ+gTpiJi0VhcHDb69kYX4MtCiivctc2QD3XuNZ/XIOlbGzt7WAjjEev0TtaH6Cu3arZExm5DOw==", - "dev": true - }, - "enhanced-resolve": { - "version": "5.17.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", - "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - } - }, - "entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true - }, - "envinfo": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", - "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", - "dev": true - }, - "es-module-lexer": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", - "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", - "dev": true - }, - "escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "dependencies": { - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - } - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-uri": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.2.tgz", - "integrity": "sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row==", - "dev": true - }, - "fastest-levenshtein": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", - "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", - "dev": true - }, - "fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true - }, - "glob-parent": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.1.tgz", - "integrity": "sha512-kEVjS71mQazDBHKcsq4E9u/vUzaLcw1A8EtUeydawvIWQCJM0qQ08G1H7/XTjFUulla6XQiDOG6MXSaG0HDKog==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "globby": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", - "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", - "dev": true, - "requires": { - "@sindresorhus/merge-streams": "^2.1.0", - "fast-glob": "^3.3.2", - "ignore": "^5.2.4", - "path-type": "^5.0.0", - "slash": "^5.1.0", - "unicorn-magic": "^0.1.0" - } - }, - "graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "requires": { - "function-bind": "^1.1.2" - } - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true - }, - "html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", - "dev": true, - "requires": { - "camel-case": "^4.1.2", - "clean-css": "^5.2.2", - "commander": "^8.3.0", - "he": "^1.2.0", - "param-case": "^3.0.4", - "relateurl": "^0.2.7", - "terser": "^5.10.0" - }, - "dependencies": { - "commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "dev": true - } - } - }, - "html-webpack-plugin": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz", - "integrity": "sha512-iwaY4wzbe48AfKLZ/Cc8k0L+FKG6oSNRaZ8x5A/T/IVDGyXcbHncM9TdDa93wn0FsSm82FhTKW7f3vS61thXAw==", - "dev": true, - "requires": { - "@types/html-minifier-terser": "^6.0.0", - "html-minifier-terser": "^6.0.2", - "lodash": "^4.17.21", - "pretty-error": "^4.0.0", - "tapable": "^2.0.0" - } - }, - "htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "dev": true, - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "icss-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true, - "requires": {} - }, - "idb": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", - "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", - "dev": true - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true - }, - "ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true - }, - "import-local": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", - "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "interpret": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", - "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", - "dev": true - }, - "is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", - "dev": true, - "requires": { - "hasown": "^2.0.2" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "iso-url": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/iso-url/-/iso-url-0.4.7.tgz", - "integrity": "sha512-27fFRDnPAMnHGLq36bWTpKET+eiXct3ENlCcdcMdk+mjXrb2kw3mhBUg1B7ewAC0kVzlOPhADzQgz1SE6Tglog==", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - } - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-text-sequence": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/json-text-sequence/-/json-text-sequence-0.1.1.tgz", - "integrity": "sha1-py8hfcSvxGKf/1/rME3BvVGi89I=", - "dev": true, - "requires": { - "delimit-stream": "0.1.0" - } - }, - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "loader-runner": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", - "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", - "dev": true - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dev": true, - "requires": { - "tslib": "^2.0.3" - } - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "requires": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - } - }, - "mime-db": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", - "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==", - "dev": true - }, - "mime-types": { - "version": "2.1.32", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", - "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", - "dev": true, - "requires": { - "mime-db": "1.49.0" - } - }, - "minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true - }, - "nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dev": true, - "requires": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "requires": { - "boolbase": "^1.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - }, - "dependencies": { - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - } - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "param-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "dev": true, - "requires": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "dev": true, - "requires": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-type": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", - "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", - "dev": true - }, - "picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - }, - "postcss": { - "version": "8.4.47", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", - "dev": true, - "requires": { - "nanoid": "^3.3.7", - "picocolors": "^1.1.0", - "source-map-js": "^1.2.1" - } - }, - "postcss-modules-extract-imports": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", - "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", - "dev": true, - "requires": {} - }, - "postcss-modules-local-by-default": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", - "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", - "dev": true, - "requires": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" - } - }, - "postcss-modules-scope": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", - "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.4" - } - }, - "postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", - "dev": true, - "requires": { - "icss-utils": "^5.0.0" - } - }, - "postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "dev": true, - "requires": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - } - }, - "postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true - }, - "pretty-error": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", - "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", - "dev": true, - "requires": { - "lodash": "^4.17.20", - "renderkid": "^3.0.0" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "pvtsutils": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz", - "integrity": "sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA==", - "dev": true, - "peer": true, - "requires": { - "tslib": "^2.6.1" - } - }, - "pvutils": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", - "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==", - "dev": true, - "peer": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "rechoir": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", - "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", - "dev": true, - "requires": { - "resolve": "^1.20.0" - } - }, - "relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", - "dev": true - }, - "renderkid": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", - "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", - "dev": true, - "requires": { - "css-select": "^4.1.3", - "dom-converter": "^0.2.0", - "htmlparser2": "^6.1.0", - "lodash": "^4.17.21", - "strip-ansi": "^6.0.1" - } - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true - }, - "resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, - "requires": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - }, - "schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - }, - "semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true - }, - "serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "requires": { - "kind-of": "^6.0.2" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "simple-cbor": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/simple-cbor/-/simple-cbor-0.4.1.tgz", - "integrity": "sha512-rijcxtwx2b4Bje3sqeIqw5EeW7UlOIC4YfOdwqIKacpvRQ/D78bWg/4/0m5e0U91oKvlGh7LlJuZCu07ISCC7w==", - "dev": true - }, - "slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true - }, - "style-loader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz", - "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", - "dev": true, - "requires": {} - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true - }, - "tapable": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.0.tgz", - "integrity": "sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw==", - "dev": true - }, - "terser": { - "version": "5.34.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.34.1.tgz", - "integrity": "sha512-FsJZ7iZLd/BXkz+4xrRTGJ26o/6VTjQytUk8b8OxkwcD2I+79VPJlz7qss1+zE7h8GNIScFqXcDyJ/KqBYZFVA==", - "dev": true, - "requires": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - } - }, - "terser-webpack-plugin": { - "version": "5.3.10", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", - "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.20", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.26.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "ts-loader": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", - "integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4", - "source-map": "^0.7.4" - }, - "dependencies": { - "source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true - } - } - }, - "tsconfig-paths": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", - "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", - "dev": true, - "requires": { - "json5": "^2.2.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "tsconfig-paths-webpack-plugin": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.1.0.tgz", - "integrity": "sha512-xWFISjviPydmtmgeUAuXp4N1fky+VCtfhOkDUFIv5ea7p4wuTomI4QTrXvFBX2S4jZsmyTSrStQl+E+4w+RzxA==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.7.0", - "tsconfig-paths": "^4.1.2" - } - }, - "tslib": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", - "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", - "dev": true - }, - "typescript": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", - "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", - "dev": true - }, - "unicorn-magic": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", - "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", - "dev": true - }, - "update-browserslist-db": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", - "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", - "dev": true, - "requires": { - "escalade": "^3.2.0", - "picocolors": "^1.1.0" - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", - "dev": true - }, - "watchpack": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", - "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", - "dev": true, - "requires": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - } - }, - "webcrypto-core": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.8.1.tgz", - "integrity": "sha512-P+x1MvlNCXlKbLSOY4cYrdreqPG5hbzkmawbcXLKN/mf6DZW0SdNNkZ+sjwsqVkI4A4Ko2sPZmkZtCKY58w83A==", - "dev": true, - "peer": true, - "requires": { - "@peculiar/asn1-schema": "^2.3.13", - "@peculiar/json-schema": "^1.1.12", - "asn1js": "^3.0.5", - "pvtsutils": "^1.3.5", - "tslib": "^2.7.0" - } - }, - "webpack": { - "version": "5.95.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", - "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", - "dev": true, - "requires": { - "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.7.1", - "acorn-import-attributes": "^1.9.5", - "browserslist": "^4.21.10", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.1", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.10", - "watchpack": "^2.4.1", - "webpack-sources": "^3.2.3" - }, - "dependencies": { - "webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true - } - } - }, - "webpack-cli": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", - "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", - "dev": true, - "requires": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^2.1.1", - "@webpack-cli/info": "^2.0.2", - "@webpack-cli/serve": "^2.0.5", - "colorette": "^2.0.14", - "commander": "^10.0.1", - "cross-spawn": "^7.0.3", - "envinfo": "^7.7.3", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^3.1.1", - "rechoir": "^0.8.0", - "webpack-merge": "^5.7.3" - }, - "dependencies": { - "commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "dev": true - } - } - }, - "webpack-merge": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", - "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", - "dev": true, - "requires": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", - "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", - "dev": true } } } diff --git a/tools/ui/package.json b/tools/ui/package.json index d496abe41..fd5de9cb8 100644 --- a/tools/ui/package.json +++ b/tools/ui/package.json @@ -3,25 +3,17 @@ "version": "0.1.0", "description": "", "keywords": [], + "type": "module", "scripts": { - "build": "webpack" + "build": "vite build", + "dev": "vite" }, "devDependencies": { - "@dfinity/agent": "2.1.3", - "@dfinity/candid": "2.1.3", - "@dfinity/auth-client": "2.1.3", - "@dfinity/identity": "2.1.3", - "@dfinity/principal": "2.1.3", - "buffer": "6.0.3", - "copy-webpack-plugin": "^12.0.2", - "css-loader": "^7.1.2", - "html-webpack-plugin": "^5.6.0", - "style-loader": "^4.0.0", - "terser-webpack-plugin": "^5.3.10", - "ts-loader": "^9.5.1", - "tsconfig-paths-webpack-plugin": "^4.1.0", + "@icp-sdk/auth": "^7.1.0", + "@icp-sdk/canisters": "^3.6.0", + "@icp-sdk/core": "^5.4.0", "typescript": "^5.6.2", - "webpack": "5.95.0", - "webpack-cli": "5.1.4" + "vite": "^6.0.0", + "vite-plugin-css-injected-by-js": "^3.5.2" } } diff --git a/tools/ui/src/favicon.ico b/tools/ui/public/favicon.ico similarity index 100% rename from tools/ui/src/favicon.ico rename to tools/ui/public/favicon.ico diff --git a/tools/ui/src/auth/auth.ts b/tools/ui/src/auth/auth.ts index be2958460..96ea337c8 100644 --- a/tools/ui/src/auth/auth.ts +++ b/tools/ui/src/auth/auth.ts @@ -1,10 +1,10 @@ -import { Principal } from "@dfinity/principal" -import { authClient } from "../candid" +import { Principal } from "@icp-sdk/core/principal" +import { authClient, createAuthClient } from "../candid" import { refresh_actor } from "../index" import { dfinityLogo, copyIcon } from "./icons" export async function renderAuth() { - const is_logged = await authClient?.isAuthenticated(); + const is_logged = authClient?.isAuthenticated() ?? false; is_logged ? await insertLogout() : insertLoginForm(); } @@ -66,18 +66,17 @@ async function insertLoginForm() { } buttonLogin.addEventListener("click", async () => { - let config: any = { - identityProvider: provider, - onSuccess: async () => { - refresh_actor(cid); - insertLogout(); - }, - onError: (err: any) => console.error(err), - }; - if (origin) { - config = {...config, derivationOrigin: origin}; + try { + const client = createAuthClient({ + identityProvider: provider!, + ...(origin ? { derivationOrigin: origin } : {}), + }); + await client.signIn(); + refresh_actor(cid); + await insertLogout(); + } catch (err) { + console.error(err); } - await authClient?.login(config); }); } catch (err) { console.error(err); @@ -87,11 +86,11 @@ async function insertLoginForm() { } } -function insertLogout() { +async function insertLogout() { const auth = document.getElementById("authentication"); auth!.innerHTML = ""; - CopyId(); + await CopyId(); LogoutButton(); } @@ -110,7 +109,7 @@ function LogoutButton() { auth!.appendChild(buttonLogout); } -function CopyId() { +async function CopyId() { if (!authClient) { return; } @@ -119,7 +118,8 @@ function CopyId() { const copyText = document.createElement("span"); - const id = authClient.getIdentity().getPrincipal().toString(); + const identity = await authClient.getIdentity(); + const id = identity.getPrincipal().toString(); const idShort = id.slice(0, 5) + "..." + id.slice(-5); copyText.innerText = idShort; @@ -143,6 +143,6 @@ function CopyId() { } async function logout() { - await authClient?.logout(); + await authClient?.signOut(); window.location.reload(); } diff --git a/tools/ui/src/candid.ts b/tools/ui/src/candid.ts index 8cfaa473b..f85bc262b 100644 --- a/tools/ui/src/candid.ts +++ b/tools/ui/src/candid.ts @@ -1,52 +1,40 @@ import { EXTERNAL_CONFIG_PROMISE } from './external'; -import { Actor, HttpAgent, ActorSubclass, CanisterStatus, getManagementCanister } from '@dfinity/agent'; +import { Actor, HttpAgent, ActorSubclass, CanisterStatus } from '@icp-sdk/core/agent'; import { IDL, InputBox, renderInput, renderValue -} from '@dfinity/candid'; -import {Principal} from '@dfinity/principal' +} from '@icp-sdk/core/candid'; +import {Principal} from '@icp-sdk/core/principal' +import { IcManagementCanister } from '@icp-sdk/canisters/ic-management'; +import { getCanisterEnv } from '@icp-sdk/core/agent/canister-env'; import './candid.css'; -import { AuthClient } from "@dfinity/auth-client"; +import { AuthClient, type AuthClientCreateOptions } from "@icp-sdk/auth/client"; declare var flamegraph: any; declare var d3: any; const names: Record = {}; -function isKnownMainnet(agent: HttpAgent) { - // @ts-ignore - const hostname = agent.host.hostname; - return hostname.endsWith('.icp0.io') || hostname.endsWith('.ic0.app'); -} export let authClient: AuthClient | undefined; -const agent = HttpAgent.createSync(); -if (!isKnownMainnet(agent)) { - agent.fetchRootKey(); +// Construct (or reconstruct) the shared AuthClient. The @icp-sdk/auth client +// takes the identity provider and derivation origin at construction time +// (unlike the old callback-based login()), so callers that know the provider +// — e.g. the login button derives it from the `ii` query param — build it here. +export function createAuthClient(options?: AuthClientCreateOptions): AuthClient { + authClient = new AuthClient({ + idleOptions: { + disableIdle: true, + disableDefaultIdleCallback: true, + }, + ...options, + }); + return authClient; } -function getCanisterId(): Principal { - // Check the query params. - const maybeCanisterId = new URLSearchParams(window.location.search).get( - "canisterId" - ); - if (maybeCanisterId) { - return Principal.fromText(maybeCanisterId); - } - - // Return the first canister ID when resolving from the right hand side. - const domain = window.location.hostname.split(".").reverse(); - for (const subdomain of domain) { - try { - if (subdomain.length >= 25) { - // The following throws if it can't decode or the checksum is invalid. - return Principal.fromText(subdomain); - } - } catch (_) {} - } - - throw new Error("Could not find the canister ID."); -} +const canisterEnv = getCanisterEnv<{readonly "CANISTER_ID": string}>(); +const agent = HttpAgent.createSync(); +agent.rootKey = canisterEnv.IC_ROOT_KEY; export async function fetchActor(canisterId: Principal): Promise { let js; @@ -61,19 +49,9 @@ export async function fetchActor(canisterId: Principal): Promise } } if (!js) { + // Read the interface from the canister's `candid:service` metadata — the + // standard, certified way for a canister to expose its Candid interface. js = await getDidJsFromMetadata(canisterId); - if (!js) { - try { - js = await getDidJsFromTmpHack(canisterId); - } catch(err) { - if (/no query method/.test(err as any)) { - console.warn(err); - js = undefined; - } else { - throw(err); - } - } - } } if (!js) { throw new Error('Cannot fetch candid file'); @@ -81,16 +59,12 @@ export async function fetchActor(canisterId: Principal): Promise const dataUri = 'data:text/javascript;charset=utf-8,' + encodeURIComponent(js); const candid: any = await eval('import("' + dataUri + '")'); - authClient = authClient ?? (await AuthClient.create({ - idleOptions: { - disableIdle: true, - disableDefaultIdleCallback: true, - }, - })) - if (await authClient.isAuthenticated()) { - agent.replaceIdentity(authClient.getIdentity()); + const client = authClient ?? createAuthClient(); + if (client.isAuthenticated()) { + const identity = await client.getIdentity(); + agent.replaceIdentity(identity); console.log("Authenticated with Internet Identity Principal") - console.log(authClient.getIdentity().getPrincipal().toString()) + console.log(identity.getPrincipal().toString()) } return Actor.createActor(candid.idlFactory, { agent, canisterId }); @@ -129,8 +103,10 @@ function timestampToString(nanoseconds: bigint) { let last_log_idx: bigint = -1n; export async function getCanisterLogs(canisterId: Principal, logger: any) { try { - const actor = getManagementCanister({ agent }); - const logs = await actor.fetch_canister_logs({ canister_id: canisterId }); + // fetch_canister_logs is a management-canister query; IcManagementCanister + // routes it to the target canister's subnet via the effective canister id. + const management = IcManagementCanister.create({ agent }); + const logs = await management.fetchCanisterLogs(canisterId); let array = logs.canister_log_records; const idx = array.findIndex((e) => e.idx > last_log_idx); if (idx > 0) { @@ -170,14 +146,11 @@ export async function getCycles(canisterId: Principal): Promise; decoded.forEach(([id, name]) => names[id] = name); } catch(err) { @@ -189,14 +162,20 @@ async function getDidJsFromPostMessage(canisterId: Principal): Promise{}) } + async function getDidJsFromMetadata(canisterId: Principal): Promise { - const status = await CanisterStatus.request({ agent, canisterId, paths: ['candid'] }); - const did = status.get('candid') as string | null; - if (did) { - return didToJs(did); - } else { - return undefined; + // The 'candid' path resolves to the `candid:service` metadata section and is + // read (and certified) via the canister's read_state endpoint. + try { + const status = await CanisterStatus.request({ agent, canisterId, paths: ['candid'] }); + const did = status.get('candid') as string | null; + if (did) { + return didToJs(did); + } + } catch (err) { + console.warn('Failed to read candid:service metadata:', err); } + return undefined; } export async function getProfiling(canisterId: Principal): Promise|undefined> { @@ -281,18 +260,9 @@ async function renderFlameGraph(profiler: any) { } } -async function getDidJsFromTmpHack(canisterId: Principal): Promise { - const common_interface: IDL.InterfaceFactory = ({ IDL }) => IDL.Service({ - __get_candid_interface_tmp_hack: IDL.Func([], [IDL.Text], ['query']), - }); - const actor: ActorSubclass = Actor.createActor(common_interface, { agent, canisterId }); - const candid_source = await actor.__get_candid_interface_tmp_hack() as string; - return didToJs(candid_source); -} - async function didToJs(candid_source: string): Promise { // call didjs canister - const didjs_id = getCanisterId(); + const didjs_id = canisterEnv["CANISTER_ID"]; const didjs_interface: IDL.InterfaceFactory = ({ IDL }) => IDL.Service({ did_to_js: IDL.Func([IDL.Text], [IDL.Opt(IDL.Text)], ['query']), }); diff --git a/tools/ui/src/didjs/Cargo.toml b/tools/ui/src/didjs/Cargo.toml index 47bc75b2a..54e194b61 100644 --- a/tools/ui/src/didjs/Cargo.toml +++ b/tools/ui/src/didjs/Cargo.toml @@ -10,7 +10,8 @@ crate-type = ["cdylib"] [dependencies] candid = "0.10" candid_parser = "0.1" -ic-cdk = "0.16" +ic-cdk = "0.20" ic-http-certification = { git = "https://github.com/dfinity/response-verification", rev = "35dd2bd316682cbd3d33720af7e1dd14ffc47b50" } +hex = "0.4.3" +percent-encoding = "2.3" serde = "1.0" -serde_bytes = "0.11" diff --git a/tools/ui/src/didjs/lib.rs b/tools/ui/src/didjs/lib.rs index a3a932708..60fca3e31 100644 --- a/tools/ui/src/didjs/lib.rs +++ b/tools/ui/src/didjs/lib.rs @@ -1,23 +1,17 @@ use candid::types::{subtype, Type, TypeInner}; -use candid_parser::{check_prog, CandidType, Deserialize, IDLProg, TypeEnv}; -use ic_cdk::api::{data_certificate, set_certified_data}; +use candid_parser::{check_prog, IDLProg, TypeEnv}; +use ic_cdk::api::{ + certified_data_set, data_certificate, root_key, +}; use ic_cdk::{init, post_upgrade}; use ic_http_certification::{ - utils::{add_skip_certification_header, skip_certification_certified_data}, - HttpResponse, + utils::add_v2_certificate_header, DefaultCelBuilder, DefaultResponseCertification, + HttpCertification, HttpCertificationPath, HttpCertificationTree, HttpCertificationTreeEntry, + HttpRequest, HttpResponse, CERTIFICATE_EXPRESSION_HEADER_NAME, }; - -#[derive(CandidType, Deserialize)] -pub struct HeaderField(pub String, pub String); - -#[derive(CandidType, Deserialize)] -pub struct HttpRequest { - pub method: String, - pub url: String, - pub headers: Vec, - #[serde(with = "serde_bytes")] - pub body: Vec, -} +use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; +use std::cell::RefCell; +use std::collections::HashMap; #[ic_cdk::query] fn did_to_js(prog: String) -> Option { @@ -53,6 +47,16 @@ fn binding(prog: String, lang: String) -> Option { Some(res) } +#[init] +fn init() { + certify_all_assets(); +} + +#[post_upgrade] +fn post_upgrade() { + init(); +} + #[ic_cdk::query] fn merge_init_args(prog: String, init_args: String) -> Option { let (env, actor) = candid_parser::utils::merge_init_args(&prog, &init_args).ok()?; @@ -69,69 +73,199 @@ fn subtype(new: String, old: String) -> Result<(), String> { let old_actor = check_prog(&mut old_env, &old).unwrap().unwrap(); let mut gamma = std::collections::HashSet::new(); let old_actor = new_env.merge_type(old_env, old_actor); - subtype::subtype(&mut gamma, &new_env, &new_actor, &old_actor).map_err(|e| e.to_string()) -} - -fn retrieve(path: &str) -> Option<(&str, &'static [u8])> { - match path { - "/index.html" | "/" => Some(("text/html", include_bytes!("../../dist/didjs/index.html"))), - "/favicon.ico" => Some(( - "image/x-icon", - include_bytes!("../../dist/didjs/favicon.ico"), - )), - "/index.js" => Some(( - "application/javascript", - include_bytes!("../../dist/didjs/index.js"), - )), - _ => None, + let errors = subtype::subtype_check_all(&mut gamma, &new_env, &new_actor, &old_actor); + if errors.is_empty() { + Ok(()) + } else { + let report = subtype::format_report(&errors); + Err(format!( + "{} incompatible change{} found:\n\n{report}", + errors.len(), + if errors.len() == 1 { "" } else { "s" } + )) } } -fn get_path(url: &str) -> Option<&str> { - url.split('?').next() -} - #[ic_cdk::query] fn http_request(request: HttpRequest) -> HttpResponse<'static> { //TODO add /canister_id/ as endpoint when ICQC is available. - let path = get_path(request.url.as_str()).unwrap_or("/"); - let mut response = if let Some((content_type, bytes)) = retrieve(path) { - HttpResponse::builder() - .with_status_code(200) - .with_headers(vec![ - ("Content-Type".to_string(), content_type.to_string()), - ("Content-Length".to_string(), format!("{}", bytes.len())), - ("Cache-Control".to_string(), format!("max-age={}", 600)), - ( - "Cross-Origin-Embedder-Policy".to_string(), - "require-corp".to_string(), - ), - ( - "Cross-Origin-Resource-Policy".to_string(), - "cross-origin".to_string(), - ), - ]) - .with_body(bytes) - .build() - } else { - HttpResponse::builder() - .with_status_code(404) - .with_headers(Vec::new()) - .with_body(path.as_bytes().to_vec()) - .build() - }; - add_skip_certification_header(data_certificate().unwrap(), &mut response); + let req_path = request.get_path().unwrap_or_else(|_| "/".to_string()); + + // Look up the exact-path response, falling back to the certified 404 wildcard. + let (tree_path, certified) = RESPONSES.with_borrow(|responses| { + if let Some(certified) = responses.get(&req_path) { + (HttpCertificationPath::exact(&req_path), certified.clone()) + } else { + ( + not_found_tree_path(), + NOT_FOUND.with_borrow(|n| n.clone().unwrap()), + ) + } + }); + + let CertifiedHttpResponse { + mut response, + certification, + } = certified; + + HTTP_TREE.with_borrow(|tree| { + let witness = tree + .witness( + &HttpCertificationTreeEntry::new(&tree_path, certification), + &req_path, + ) + .unwrap(); + add_v2_certificate_header( + &data_certificate().expect("No data certificate available"), + &mut response, + &witness, + &tree_path.to_expr_path(), + ); + }); + response } -#[init] -fn init() { - set_certified_data(&skip_certification_certified_data()); +// Storage + +// The static assets served by this canister, baked into the wasm at build time. +const INDEX_HTML: &[u8] = include_bytes!("../../dist/didjs/index.html"); +const FAVICON: &[u8] = include_bytes!("../../dist/didjs/favicon.ico"); +const INDEX_JS: &[u8] = include_bytes!("../../dist/didjs/index.js"); + +// `/` and `/index.html` both serve the SPA entry point. +const ASSETS: &[(&str, &str, &[u8])] = &[ + ("/", "text/html", INDEX_HTML), + ("/index.html", "text/html", INDEX_HTML), + ("/favicon.ico", "image/x-icon", FAVICON), + ("/index.js", "application/javascript", INDEX_JS), +]; + +// Body returned (and certified) for any request that doesn't match a known asset. +const NOT_FOUND_BODY: &[u8] = b"Not found"; + +// A pre-certified response, ready to be served alongside its tree witness. +#[derive(Clone)] +struct CertifiedHttpResponse { + response: HttpResponse<'static>, + certification: HttpCertification, } -#[post_upgrade] -fn post_upgrade() { - init(); +thread_local! { + static HTTP_TREE: RefCell = RefCell::new(HttpCertificationTree::default()); + // Exact-path responses, keyed by request path. + static RESPONSES: RefCell> = RefCell::new(HashMap::new()); + // Wildcard fallback served for any unmatched path. + static NOT_FOUND: RefCell> = const { RefCell::new(None) }; +} + +// The `ic_env` cookie carries environment data compatible with the canister environment +// variables cookie. It is only attached to `text/html` responses. +const SET_COOKIE_HEADER_NAME: &str = "Set-Cookie"; +const IC_ENV_COOKIE_NAME: &str = "ic_env"; + +// Builds the (URL-encoded) `ic_env` cookie value +// `ic_root_key=` and CANISTER_ID, joined by `&`. +fn encoded_canister_env() -> String { + let values = [ + format!("ic_root_key={}", hex::encode(root_key())), + format!("CANISTER_ID={}", ic_cdk::api::canister_self()), + ]; + + utf8_percent_encode(&values.join("&"), NON_ALPHANUMERIC).to_string() +} + +// Certification + +fn build_response( + status_code: u16, + content_type: &str, + body: &'static [u8], + cel_expr_str: &str, + encoded_canister_env: &str, +) -> HttpResponse<'static> { + let mut headers = vec![ + ( + CERTIFICATE_EXPRESSION_HEADER_NAME.to_string(), + cel_expr_str.to_string(), + ), + ("Content-Type".to_string(), content_type.to_string()), + ("Content-Length".to_string(), format!("{}", body.len())), + ("Cache-Control".to_string(), format!("max-age={}", 600)), + ( + "Cross-Origin-Embedder-Policy".to_string(), + "require-corp".to_string(), + ), + ( + "Cross-Origin-Resource-Policy".to_string(), + "cross-origin".to_string(), + ), + ]; + + // Attach the canister environment cookie to HTML responses only. + if content_type == "text/html" { + headers.push(( + SET_COOKIE_HEADER_NAME.to_string(), + format!("{IC_ENV_COOKIE_NAME}={encoded_canister_env}; SameSite=Lax"), + )); + } + + HttpResponse::builder() + .with_status_code(status_code) + .with_headers(headers) + .with_body(body) + .build() +} + +fn certify_all_assets() { + let canister_env = encoded_canister_env(); + + // We certify the full response (no excluded headers) and exclude the request. + let cel = DefaultCelBuilder::response_only_certification() + .with_response_certification(DefaultResponseCertification::response_header_exclusions( + vec![], + )) + .build(); + let cel_str = cel.to_string(); + + HTTP_TREE.with_borrow_mut(|tree| { + // Certify each static asset at its exact path. + for (path, content_type, body) in ASSETS { + let response = build_response(200, content_type, body, &cel_str, &canister_env); + let certification = HttpCertification::response_only(&cel, &response, None).unwrap(); + let tree_path = HttpCertificationPath::exact(*path); + tree.insert(&HttpCertificationTreeEntry::new(&tree_path, certification)); + RESPONSES.with_borrow_mut(|responses| { + responses.insert( + path.to_string(), + CertifiedHttpResponse { + response, + certification, + }, + ); + }); + } + + // Certify a 404 response under a wildcard path, covering every other request. + let response = build_response(404, "text/plain", NOT_FOUND_BODY, &cel_str, &canister_env); + let certification = HttpCertification::response_only(&cel, &response, None).unwrap(); + tree.insert(&HttpCertificationTreeEntry::new( + not_found_tree_path(), + certification, + )); + NOT_FOUND.with_borrow_mut(|not_found| { + *not_found = Some(CertifiedHttpResponse { + response, + certification, + }); + }); + + certified_data_set(tree.root_hash()); + }); +} + +fn not_found_tree_path() -> HttpCertificationPath<'static> { + HttpCertificationPath::wildcard("") } ic_cdk::export_candid!(); diff --git a/tools/ui/src/index.ts b/tools/ui/src/index.ts index 083ee4e9d..78f3a18f9 100644 --- a/tools/ui/src/index.ts +++ b/tools/ui/src/index.ts @@ -1,7 +1,7 @@ import { fetchActor, render, getCycles, getNames } from "./candid"; import { renderAuth } from "./auth/auth"; -import { Principal } from "@dfinity/principal"; -import { ActorSubclass } from "@dfinity/agent"; +import { Principal } from "@icp-sdk/core/principal"; +import { ActorSubclass } from "@icp-sdk/core/agent"; let actor: ActorSubclass | undefined; diff --git a/tools/ui/tsconfig.json b/tools/ui/tsconfig.json index 536c31cce..3a27e8d7e 100644 --- a/tools/ui/tsconfig.json +++ b/tools/ui/tsconfig.json @@ -2,17 +2,18 @@ "compilerOptions": { "allowJs": false, "baseUrl": "./", - "composite": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, - "incremental": true, - "lib": ["dom", "es2020"], - "module": "commonjs", - "outDir": "ts-out/", + "lib": ["dom", "dom.iterable", "es2020"], + "module": "ESNext", + "moduleResolution": "bundler", + "types": ["vite/client"], + "noEmit": true, + "skipLibCheck": true, "sourceMap": true, "strict": true, "target": "ES2020" }, - "include": ["src/**/*.ts", "src/**/*.css"], - "exclude": ["**/*.test.ts"], + "include": ["src"], + "exclude": ["**/*.test.ts"] } diff --git a/tools/ui/vite.config.ts b/tools/ui/vite.config.ts new file mode 100644 index 000000000..7d869f314 --- /dev/null +++ b/tools/ui/vite.config.ts @@ -0,0 +1,34 @@ +import { defineConfig } from "vite"; +import cssInjectedByJsPlugin from "vite-plugin-css-injected-by-js"; + +// The `didjs` canister embeds the built frontend directly via +// `include_bytes!("../../dist/didjs/{index.html,index.js,favicon.ico}")` +// (see src/didjs/lib.rs). So the build must emit exactly those three files, +// with stable (un-hashed) names, into `dist/didjs/`: +// - index.html (this entry, processed by Vite) +// - index.js (single entry chunk; CSS is inlined into it) +// - favicon.ico (copied verbatim from the `public/` directory) +export default defineConfig({ + // Inline imported CSS into the JS bundle (the canister only serves index.js, + // not a separate stylesheet) — mirrors the previous webpack `style-loader`. + plugins: [cssInjectedByJsPlugin()], + // Some @dfinity packages reference `global`; map it to `globalThis` for the browser. + define: { + global: "globalThis", + }, + build: { + outDir: "dist/didjs", + emptyOutDir: true, + sourcemap: true, + target: "es2020", + cssCodeSplit: false, + rollupOptions: { + output: { + entryFileNames: "index.js", + chunkFileNames: "index.js", + assetFileNames: "[name][extname]", + inlineDynamicImports: true, + }, + }, + }, +}); diff --git a/tools/ui/webpack.config.js b/tools/ui/webpack.config.js deleted file mode 100644 index 00b183ac7..000000000 --- a/tools/ui/webpack.config.js +++ /dev/null @@ -1,116 +0,0 @@ -const path = require("path"); -const TerserPlugin = require("terser-webpack-plugin"); -const dfxJson = require("./dfx.json"); -const HtmlWebpackPlugin = require('html-webpack-plugin'); -const CopyWebpackPlugin = require('copy-webpack-plugin'); -const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); -const webpack = require("webpack"); - -// List of all aliases for canisters. This creates the module alias for -// the `import ... from "@dfinity/ic/canisters/xyz"` where xyz is the name of a -// canister. -const aliases = Object.entries(dfxJson.canisters).reduce( - (acc, [name, _value]) => { - // Get the network name, or `local` by default. - const networkName = process.env["DFX_NETWORK"] || "local"; - const outputRoot = path.join( - __dirname, - ".dfx", - networkName, - "canisters", - name - ); - - return { - ...acc, - ["dfx-generated/" + name]: path.join(outputRoot), - }; - }, - {} -); - -/** - * Generate a webpack configuration for a canister. - */ -function generateWebpackConfigForCanister(name, info) { - //if (typeof info.frontend !== "object") { - // return; - //} - - return { - mode: "production", - entry: { - index: path.join(__dirname, "src/index.ts"), - }, - target: 'web', - devtool: "source-map", - optimization: { - minimize: true, - minimizer: [new TerserPlugin()], - }, - resolve: { - plugins: [new TsconfigPathsPlugin({ configFile: './tsconfig.json' })], - extensions: [".js", ".ts", ".jsx", ".tsx"], - fallback: { - //"assert": require.resolve("assert/"), - "buffer": require.resolve("buffer/"), - //"events": require.resolve("events/"), - //"stream": require.resolve("stream-browserify/"), - //"util": require.resolve("util/"), - }, - alias: aliases, - }, - output: { - filename: "[name].js", - path: path.join(__dirname, "dist", name), - }, - - // Depending in the language or framework you are using for - // front-end development, add module loaders to the default - // webpack configuration. For example, if you are using React - // modules and CSS as described in the "Adding a stylesheet" - // tutorial, uncomment the following lines: - module: { - rules: [ - { test: /\.css$/, use: ['style-loader','css-loader'] }, - { test: /\.(jsx|ts|tsx)$/, - use: { - loader: "ts-loader", - options: { - // eslint-disable-next-line no-undef - configFile: path.join(__dirname, 'tsconfig.json'), - projectReferences: true, - } - } - } - ] - }, - plugins: [ - new HtmlWebpackPlugin({ - template: 'src/candid.html', - filename: 'index.html', - }), - new CopyWebpackPlugin({ - patterns: [ - { - from: 'src/favicon.ico', - to: 'favicon.ico', - }, - ]}), - new webpack.ProvidePlugin({ - Buffer: [require.resolve('buffer/'), 'Buffer'], - //process: require.resolve('process/browser'), - }), - ], - }; -} - -// If you have additional webpack configurations you want to build -// as part of this configuration, add them to the section below. -module.exports = [ - ...Object.entries(dfxJson.canisters) - .map(([name, info]) => { - return generateWebpackConfigForCanister(name, info); - }) - .filter((x) => !!x), -];