diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 4a6af5c..8a0246f 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -1,189 +1,53 @@ name: CI -env: - DEBUG: napi:* - APP_NAME: cinnamon - MACOSX_DEPLOYMENT_TARGET: '10.13' - -permissions: - contents: write - id-token: write - on: push: - tags: - - '[0-9]+.*' + branches: [ "main", "master" ] + tags: [ "*.*.*" ] + pull_request: + branches: [ "main", "master" ] workflow_dispatch: -jobs: - build: - name: Build - ${{ matrix.settings.target }} - runs-on: ${{ matrix.settings.host }} - strategy: - fail-fast: false - matrix: - settings: - # MacOS (Apple Silicon) - - host: macos-latest - target: aarch64-apple-darwin - build: | - npm run build -- --target aarch64-apple-darwin - strip -x *.node - - # Windows (x64 MSVC) - - host: windows-latest - target: x86_64-pc-windows-msvc - build: npm run build -- --target x86_64-pc-windows-msvc +env: + CARGO_TERM_COLOR: always - # Linux (x64 GNU) - Uses Docker - - host: ubuntu-latest - target: x86_64-unknown-linux-gnu - docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian - build: |- - rustup update stable && - npm run build -- --target x86_64-unknown-linux-gnu && - strip *.node +jobs: + check: + name: Test & Lint + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Setup node - uses: actions/setup-node@v4 - if: ${{ !matrix.settings.docker }} - with: - node-version: 20 - cache: npm - - - name: Install Rust + - name: Install Rust Toolchain uses: dtolnay/rust-toolchain@stable - if: ${{ !matrix.settings.docker }} with: - toolchain: stable - targets: ${{ matrix.settings.target }} + components: rustfmt, clippy - - name: Cache cargo - uses: actions/cache@v4 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ matrix.settings.target }}-cargo-${{ matrix.settings.host }} + - name: Cache Cargo dependencies + uses: Swatinem/rust-cache@v2 - - name: Install dependencies - run: npm install + - name: Check Formatting + run: cargo fmt --all -- --check - - name: Build (Docker) - if: ${{ matrix.settings.docker }} - uses: addnab/docker-run-action@v3 - with: - image: ${{ matrix.settings.docker }} - options: --user 0:0 -v ${{ github.workspace }}/.cargo-cache/git:/root/.cargo/git -v ${{ github.workspace }}/.cargo-cache/registry:/root/.cargo/registry -v ${{ github.workspace }}:/build -w /build - run: ${{ matrix.settings.build }} + - name: Run Clippy + run: cargo clippy --all-targets --all-features -- -D warnings - - name: Build (Host) - if: ${{ !matrix.settings.docker }} - run: ${{ matrix.settings.build }} - shell: bash - - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: bindings-${{ matrix.settings.target }} - path: ${{ env.APP_NAME }}.*.node - if-no-files-found: error + - name: Run Tests + run: cargo test --verbose publish: - name: Publish to NPM & Crates.io - runs-on: ubuntu-latest - needs: build + name: Publish to Crates.io + needs: check if: startsWith(github.ref, 'refs/tags/') + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Setup node - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: npm - - - name: Install Rust + - name: Install Rust Toolchain uses: dtolnay/rust-toolchain@stable - with: - toolchain: stable - - - name: Install dependencies - run: npm install - - - name: Download all artifacts - uses: actions/download-artifact@v4 - with: - path: artifacts - - - name: Create npm dirs - run: npx napi create-npm-dirs - - - name: Move artifacts to npm folders - run: npm run artifacts - - - name: Publish to NPM - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - run: | - npm config set //registry.npmjs.org/:_authToken=$NPM_TOKEN - - # Helper function to check if package exists - # Returns "true" if version exists, "false" if not - function version_exists() { - local name=$1 - local version=$2 - # npm view returns the version string if found, or empty/error if not - if [ "$(npm view "${name}@${version}" version 2>/dev/null)" == "$version" ]; then - echo "true" - else - echo "false" - fi - } - - # 1. Publish sub-packages - for package_dir in npm/*; do - if [ -d "$package_dir" ]; then - cd $package_dir - - # Get name and version from package.json in the subdir - PKG_NAME=$(node -p "require('./package.json').name") - PKG_VERSION=$(node -p "require('./package.json').version") - - if [ "$(version_exists $PKG_NAME $PKG_VERSION)" == "true" ]; then - echo "$PKG_NAME@$PKG_VERSION already exists on NPM. Skipping." - else - echo "Publishing $PKG_NAME@$PKG_VERSION..." - npm publish --access public --provenance - fi - - cd ../.. - fi - done - - # 2. Publish root package - ROOT_NAME=$(node -p "require('./package.json').name") - ROOT_VERSION=$(node -p "require('./package.json').version") - - if [ "$(version_exists $ROOT_NAME $ROOT_VERSION)" == "true" ]; then - echo "$ROOT_NAME@$ROOT_VERSION already exists on NPM. Skipping." - else - echo "Publishing root package $ROOT_NAME@$ROOT_VERSION..." - npm publish --access public --provenance - fi - name: Publish to Crates.io env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - run: | - if [ -n "$CARGO_REGISTRY_TOKEN" ]; then - echo "Publishing to Crates.io..." - cargo publish --allow-dirty - else - echo "Skipping Crates.io (Token not found)" - fi \ No newline at end of file + run: cargo publish \ No newline at end of file diff --git a/.gitignore b/.gitignore index 81a046b..0104787 100644 --- a/.gitignore +++ b/.gitignore @@ -9,12 +9,6 @@ target/ # MSVC Windows builds of rustc generate these, which store debugging information *.pdb -# NAPI-RS Build Artifacts -node_modules -*.node -index.js -index.d.ts - # RustRover # JetBrains specific template is maintained in a separate JetBrains.gitignore that can # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore diff --git a/Cargo.lock b/Cargo.lock index adcdb0f..bd69cbf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -12,10 +21,14 @@ dependencies = [ ] [[package]] -name = "anyhow" -version = "1.0.100" +name = "assert-json-diff" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" +dependencies = [ + "serde", + "serde_json", +] [[package]] name = "atomic-waker" @@ -86,9 +99,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" dependencies = [ "iana-time-zone", "js-sys", @@ -99,14 +112,10 @@ dependencies = [ [[package]] name = "cinnamon" -version = "0.1.3" +version = "1.0.0" dependencies = [ - "anyhow", "chrono", "futures", - "napi", - "napi-build", - "napi-derive", "reqwest", "serde", "serde_json", @@ -114,15 +123,7 @@ dependencies = [ "thiserror", "tokio", "url", -] - -[[package]] -name = "convert_case" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" -dependencies = [ - "unicode-segmentation", + "wiremock", ] [[package]] @@ -151,20 +152,22 @@ dependencies = [ ] [[package]] -name = "ctor" -version = "0.6.3" +name = "deadpool" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "424e0138278faeb2b401f174ad17e715c829512d74f3d1e81eb43365c2e0590e" +checksum = "0be2b1d1d6ec8d846f05e137292d0b89133caf95ef33695424c09568bdd39b1b" dependencies = [ - "ctor-proc-macro", - "dtor", + "deadpool-runtime", + "lazy_static", + "num_cpus", + "tokio", ] [[package]] -name = "ctor-proc-macro" -version = "0.0.7" +name = "deadpool-runtime" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" +checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" [[package]] name = "digest" @@ -188,19 +191,10 @@ dependencies = [ ] [[package]] -name = "dtor" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "404d02eeb088a82cfd873006cb713fe411306c7d182c344905e101fb1167d301" -dependencies = [ - "dtor-proc-macro", -] - -[[package]] -name = "dtor-proc-macro" -version = "0.0.6" +name = "equivalent" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" @@ -218,6 +212,12 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -353,6 +353,37 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "http" version = "1.4.0" @@ -392,6 +423,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "hyper" version = "1.8.1" @@ -402,9 +439,11 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2", "http", "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "pin-utils", @@ -580,6 +619,16 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -613,20 +662,16 @@ dependencies = [ ] [[package]] -name = "libc" -version = "0.2.180" +name = "lazy_static" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] -name = "libloading" -version = "0.9.0" +name = "libc" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" -dependencies = [ - "cfg-if", - "windows-link", -] +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "litemap" @@ -673,79 +718,22 @@ dependencies = [ ] [[package]] -name = "napi" -version = "3.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909805cbad4d569e69b80e101290fe72e92b9742ba9e333b0c1e83b22fb7447b" -dependencies = [ - "bitflags", - "chrono", - "ctor", - "futures", - "napi-build", - "napi-sys", - "nohash-hasher", - "rustc-hash", - "serde", - "serde_json", - "tokio", -] - -[[package]] -name = "napi-build" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d376940fd5b723c6893cd1ee3f33abbfd86acb1cd1ec079f3ab04a2a3bc4d3b1" - -[[package]] -name = "napi-derive" -version = "3.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04ba21bbdf40b33496b4ee6eadfc64d17a6a6cde57cd31549117b0882d1fef86" -dependencies = [ - "convert_case", - "ctor", - "napi-derive-backend", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "napi-derive-backend" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9a63791e230572c3218a7acd86ca0a0529fc64294bcbea567cf906d7b04e077" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "semver", - "syn", -] - -[[package]] -name = "napi-sys" -version = "3.2.1" +name = "num-traits" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eb602b84d7c1edae45e50bbf1374696548f36ae179dfa667f577e384bb90c2b" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "libloading", + "autocfg", ] [[package]] -name = "nohash-hasher" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" - -[[package]] -name = "num-traits" -version = "0.2.19" +name = "num_cpus" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ - "autocfg", + "hermit-abi", + "libc", ] [[package]] @@ -874,7 +862,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -930,6 +918,35 @@ dependencies = [ "bitflags", ] +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" + [[package]] name = "reqwest" version = "0.12.28" @@ -1041,12 +1058,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" - [[package]] name = "serde" version = "1.0.228" @@ -1196,18 +1207,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -1277,6 +1288,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "tower" version = "0.5.3" @@ -1359,12 +1383,6 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - [[package]] name = "untrusted" version = "0.9.0" @@ -1721,6 +1739,29 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +[[package]] +name = "wiremock" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08db1edfb05d9b3c1542e521aea074442088292f00b5f28e435c714a98f85031" +dependencies = [ + "assert-json-diff", + "base64", + "deadpool", + "futures", + "http", + "http-body-util", + "hyper", + "hyper-util", + "log", + "once_cell", + "regex", + "serde", + "serde_json", + "tokio", + "url", +] + [[package]] name = "wit-bindgen" version = "0.46.0" diff --git a/Cargo.toml b/Cargo.toml index 28dc882..84e2202 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cinnamon" -version = "0.1.3" +version = "1.0.0" edition = "2021" repository = "https://github.com/ItsLimeNade/cinnamon" authors = ["Limenade"] @@ -10,24 +10,23 @@ readme = "./README.md" license = "MIT" keywords = ["diabetes", "nightscout", "api"] categories = ["api-bindings", "asynchronous", "data-structures", "science::bioinformatics", "web-programming::http-client"] -build = "build.rs" [lib] -crate-type = ["cdylib", "rlib"] +crate-type = ["rlib"] +doctest = false [dependencies] -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.149" reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } -tokio = { version = "1", features = ["full"] } -anyhow = "1.0.100" -url = "2.5.7" -chrono = "0.4.42" +tokio = { version = "1.49", features = ["full"] } +url = "2.5.8" +chrono = "0.4.43" sha1 = "0.10.6" -thiserror = "2.0.17" +thiserror = "2.0.18" futures = "0.3.31" -napi = {version = "3.8.2", features = ["async", "serde-json", "chrono_date"]} -napi-derive = "3.5.1" -[build-dependencies] -napi-build = "2.3.1" \ No newline at end of file +[dev-dependencies] +wiremock = "0.6.5" +tokio = { version = "1.49", features = ["full", "test-util"] } +serde_json = "1.0.149" \ No newline at end of file diff --git a/build.rs b/build.rs deleted file mode 100644 index 9fc2367..0000000 --- a/build.rs +++ /dev/null @@ -1,5 +0,0 @@ -extern crate napi_build; - -fn main() { - napi_build::setup(); -} diff --git a/examples/check_iob.rs b/examples/check_iob.rs index 70b5ab5..2274cfa 100644 --- a/examples/check_iob.rs +++ b/examples/check_iob.rs @@ -6,7 +6,7 @@ use std::error::Error; #[tokio::main] async fn main() -> Result<(), Box> { let url = env::var("NS_URL").expect("NS_URL not set"); - let client = NightscoutClient::new(&url, None)?; + let client = NightscoutClient::new(&url)?; // Request specific properties (faster than fetching everything) let stats = client diff --git a/examples/javascript_demo.js b/examples/javascript_demo.js deleted file mode 100644 index a10521e..0000000 --- a/examples/javascript_demo.js +++ /dev/null @@ -1,31 +0,0 @@ -const { Cinnamon, DeviceType } = require('cinnamon-rs'); - -const NS_URL = process.env.NS_URL || "https://www.your-ns-url.com/"; -const NS_SECRET = process.env.NS_SECRET || "your-ns-pass"; - -async function main() { - console.log(`Connecting to: ${NS_URL}`); - - try { - const client = new Cinnamon(NS_URL, NS_SECRET); - - console.log("Fetching Treatments."); - const treatments = await client.treatments() - .limit(3) - .filterDevice(DeviceType.All) - .fetch(); - - console.log(`Found ${treatments.length} treatments.`); - - treatments.forEach((t, i) => { - console.log(`[${i + 1}] ${t.eventType} at ${t.createdAt}`); - if (t.insulin) console.log(`Insulin: ${t.insulin} U`); - if (t.carbs) console.log(`Carbs: ${t.carbs} g`); - }); - - } catch (error) { - console.error("Error:", error); - } -} - -main(); \ No newline at end of file diff --git a/examples/log_treatment.rs b/examples/log_treatment.rs index 9112afe..393b7d7 100644 --- a/examples/log_treatment.rs +++ b/examples/log_treatment.rs @@ -7,8 +7,8 @@ use std::error::Error; #[tokio::main] async fn main() -> Result<(), Box> { let url = env::var("NS_URL").expect("NS_URL not set"); - let token = env::var("NS_TOKEN").ok(); // Token IS required for writing - let client = NightscoutClient::new(&url, token)?; + let token = env::var("NS_TOKEN").expect("NS_TOKEN not set"); // Token IS required for writing + let client = NightscoutClient::new(&url)?.with_secret(token); let snack = Treatment { id: None, diff --git a/examples/read_bg.rs b/examples/read_bg.rs index cb4bf41..a9e9e97 100644 --- a/examples/read_bg.rs +++ b/examples/read_bg.rs @@ -6,13 +6,13 @@ use std::error::Error; async fn main() -> Result<(), Box> { // Initialize (Read-only doesn't strictly need a token, but good practice) let url = env::var("NS_URL").expect("NS_URL not set"); - let token = env::var("NS_TOKEN").ok(); - let client = NightscoutClient::new(&url, token)?; + let token = env::var("NS_TOKEN").expect("NS_TOKEN not set"); + let client = NightscoutClient::new(&url)?.with_secret(token); println!("Fetching latest glucose data."); // Fetch last 5 SGV entries - let entries = client.entries().sgv().list().limit(5).await?; + let entries = client.sgv().get().limit(5).send().await?; for entry in entries { println!( diff --git a/examples/typescript_demo.ts b/examples/typescript_demo.ts deleted file mode 100644 index ff98485..0000000 --- a/examples/typescript_demo.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Cinnamon, DeviceType, Trend } from 'cinnamon-rs'; - -const NS_URL = process.env.NS_URL || "https://www.your-ns-url.com/"; -const NS_SECRET = process.env.NS_SECRET || "your-ns-pass"; - -async function main() { - console.log(`Connecting to: ${NS_URL}`); - - try { - const client = new Cinnamon(NS_URL, NS_SECRET); - - console.log("Fetching SGV entries"); - const entries = await client.sgv() - .limit(5) - .filterDevice(DeviceType.Auto) - .fetch(); - - console.log(`Found ${entries.length} entries.`); - - if (entries.length > 0) { - const latest = entries[0]; - console.log(`Latest SGV: ${latest.sgv} mg/dL`); - console.log(`Direction: ${latest.direction}`); - - if (latest.direction === Trend.DoubleUp) { - console.log("Rising fast!"); - } - } - - console.log("Fetching System Properties"); - const props = await client.properties() - .enable(["iob", "cob", "pump"]) - .fetch(); - - if (props.iob) { - console.log(`IOB: ${props.iob.iob.toFixed(2)} U`); - } - if (props.cob) { - console.log(`COB: ${props.cob.cob.toFixed(1)} g`); - } - - } catch (error) { - console.error("Error:", error); - } -} - -main(); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 3a63930..0000000 --- a/package-lock.json +++ /dev/null @@ -1,2110 +0,0 @@ -{ - "name": "cinnamon-nightscout", - "version": "0.1.2", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "cinnamon-nightscout", - "version": "0.1.0", - "devDependencies": { - "@napi-rs/cli": "^3.5.1", - "ts-node": "^10.9.2", - "typescript": "^5.9.3" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@emnapi/core": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", - "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.1.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", - "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", - "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@inquirer/ansi": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.3.tgz", - "integrity": "sha512-g44zhR3NIKVs0zUesa4iMzExmZpLUdTLRMCStqX3GE5NT6VkPcxQGJ+uC8tDgBUC/vB1rUhUd55cOf++4NZcmw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" - } - }, - "node_modules/@inquirer/checkbox": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-5.0.4.tgz", - "integrity": "sha512-DrAMU3YBGMUAp6ArwTIp/25CNDtDbxk7UjIrrtM25JVVrlVYlVzHh5HR1BDFu9JMyUoZ4ZanzeaHqNDttf3gVg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^2.0.3", - "@inquirer/core": "^11.1.1", - "@inquirer/figures": "^2.0.3", - "@inquirer/type": "^4.0.3" - }, - "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/confirm": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.0.4.tgz", - "integrity": "sha512-WdaPe7foUnoGYvXzH4jp4wH/3l+dBhZ3uwhKjXjwdrq5tEIFaANxj6zrGHxLdsIA0yKM0kFPVcEalOZXBB5ISA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^11.1.1", - "@inquirer/type": "^4.0.3" - }, - "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/core": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-11.1.1.tgz", - "integrity": "sha512-hV9o15UxX46OyQAtaoMqAOxGR8RVl1aZtDx1jHbCtSJy1tBdTfKxLPKf7utsE4cRy4tcmCQ4+vdV+ca+oNxqNA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^2.0.3", - "@inquirer/figures": "^2.0.3", - "@inquirer/type": "^4.0.3", - "cli-width": "^4.1.0", - "mute-stream": "^3.0.0", - "signal-exit": "^4.1.0", - "wrap-ansi": "^9.0.2" - }, - "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/editor": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-5.0.4.tgz", - "integrity": "sha512-QI3Jfqcv6UO2/VJaEFONH8Im1ll++Xn/AJTBn9Xf+qx2M+H8KZAdQ5sAe2vtYlo+mLW+d7JaMJB4qWtK4BG3pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^11.1.1", - "@inquirer/external-editor": "^2.0.3", - "@inquirer/type": "^4.0.3" - }, - "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/expand": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-5.0.4.tgz", - "integrity": "sha512-0I/16YwPPP0Co7a5MsomlZLpch48NzYfToyqYAOWtBmaXSB80RiNQ1J+0xx2eG+Wfxt0nHtpEWSRr6CzNVnOGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^11.1.1", - "@inquirer/type": "^4.0.3" - }, - "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/external-editor": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-2.0.3.tgz", - "integrity": "sha512-LgyI7Agbda74/cL5MvA88iDpvdXI2KuMBCGRkbCl2Dg1vzHeOgs+s0SDcXV7b+WZJrv2+ERpWSM65Fpi9VfY3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "chardet": "^2.1.1", - "iconv-lite": "^0.7.2" - }, - "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/figures": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.3.tgz", - "integrity": "sha512-y09iGt3JKoOCBQ3w4YrSJdokcD8ciSlMIWsD+auPu+OZpfxLuyz+gICAQ6GCBOmJJt4KEQGHuZSVff2jiNOy7g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" - } - }, - "node_modules/@inquirer/input": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-5.0.4.tgz", - "integrity": "sha512-4B3s3jvTREDFvXWit92Yc6jF1RJMDy2VpSqKtm4We2oVU65YOh2szY5/G14h4fHlyQdpUmazU5MPCFZPRJ0AOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^11.1.1", - "@inquirer/type": "^4.0.3" - }, - "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/number": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-4.0.4.tgz", - "integrity": "sha512-CmMp9LF5HwE+G/xWsC333TlCzYYbXMkcADkKzcawh49fg2a1ryLc7JL1NJYYt1lJ+8f4slikNjJM9TEL/AljYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^11.1.1", - "@inquirer/type": "^4.0.3" - }, - "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/password": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-5.0.4.tgz", - "integrity": "sha512-ZCEPyVYvHK4W4p2Gy6sTp9nqsdHQCfiPXIP9LbJVW4yCinnxL/dDDmPaEZVysGrj8vxVReRnpfS2fOeODe9zjg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^2.0.3", - "@inquirer/core": "^11.1.1", - "@inquirer/type": "^4.0.3" - }, - "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/prompts": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-8.2.0.tgz", - "integrity": "sha512-rqTzOprAj55a27jctS3vhvDDJzYXsr33WXTjODgVOru21NvBo9yIgLIAf7SBdSV0WERVly3dR6TWyp7ZHkvKFA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/checkbox": "^5.0.4", - "@inquirer/confirm": "^6.0.4", - "@inquirer/editor": "^5.0.4", - "@inquirer/expand": "^5.0.4", - "@inquirer/input": "^5.0.4", - "@inquirer/number": "^4.0.4", - "@inquirer/password": "^5.0.4", - "@inquirer/rawlist": "^5.2.0", - "@inquirer/search": "^4.1.0", - "@inquirer/select": "^5.0.4" - }, - "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/rawlist": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-5.2.0.tgz", - "integrity": "sha512-CciqGoOUMrFo6HxvOtU5uL8fkjCmzyeB6fG7O1vdVAZVSopUBYECOwevDBlqNLyyYmzpm2Gsn/7nLrpruy9RFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^11.1.1", - "@inquirer/type": "^4.0.3" - }, - "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/search": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-4.1.0.tgz", - "integrity": "sha512-EAzemfiP4IFvIuWnrHpgZs9lAhWDA0GM3l9F4t4mTQ22IFtzfrk8xbkMLcAN7gmVML9O/i+Hzu8yOUyAaL6BKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^11.1.1", - "@inquirer/figures": "^2.0.3", - "@inquirer/type": "^4.0.3" - }, - "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/select": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-5.0.4.tgz", - "integrity": "sha512-s8KoGpPYMEQ6WXc0dT9blX2NtIulMdLOO3LA1UKOiv7KFWzlJ6eLkEYTDBIi+JkyKXyn8t/CD6TinxGjyLt57g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^2.0.3", - "@inquirer/core": "^11.1.1", - "@inquirer/figures": "^2.0.3", - "@inquirer/type": "^4.0.3" - }, - "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/type": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.0.3.tgz", - "integrity": "sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@napi-rs/cli": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-3.5.1.tgz", - "integrity": "sha512-XBfLQRDcB3qhu6bazdMJsecWW55kR85l5/k0af9BIBELXQSsCFU0fzug7PX8eQp6vVdm7W/U3z6uP5WmITB2Gw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/prompts": "^8.0.0", - "@napi-rs/cross-toolchain": "^1.0.3", - "@napi-rs/wasm-tools": "^1.0.1", - "@octokit/rest": "^22.0.1", - "clipanion": "^4.0.0-rc.4", - "colorette": "^2.0.20", - "emnapi": "^1.7.1", - "es-toolkit": "^1.41.0", - "js-yaml": "^4.1.0", - "obug": "^2.0.0", - "semver": "^7.7.3", - "typanion": "^3.14.0" - }, - "bin": { - "napi": "dist/cli.js", - "napi-raw": "cli.mjs" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/runtime": "^1.7.1" - }, - "peerDependenciesMeta": { - "@emnapi/runtime": { - "optional": true - } - } - }, - "node_modules/@napi-rs/cross-toolchain": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@napi-rs/cross-toolchain/-/cross-toolchain-1.0.3.tgz", - "integrity": "sha512-ENPfLe4937bsKVTDA6zdABx4pq9w0tHqRrJHyaGxgaPq03a2Bd1unD5XSKjXJjebsABJ+MjAv1A2OvCgK9yehg==", - "dev": true, - "license": "MIT", - "workspaces": [ - ".", - "arm64/*", - "x64/*" - ], - "dependencies": { - "@napi-rs/lzma": "^1.4.5", - "@napi-rs/tar": "^1.1.0", - "debug": "^4.4.1" - }, - "peerDependencies": { - "@napi-rs/cross-toolchain-arm64-target-aarch64": "^1.0.3", - "@napi-rs/cross-toolchain-arm64-target-armv7": "^1.0.3", - "@napi-rs/cross-toolchain-arm64-target-ppc64le": "^1.0.3", - "@napi-rs/cross-toolchain-arm64-target-s390x": "^1.0.3", - "@napi-rs/cross-toolchain-arm64-target-x86_64": "^1.0.3", - "@napi-rs/cross-toolchain-x64-target-aarch64": "^1.0.3", - "@napi-rs/cross-toolchain-x64-target-armv7": "^1.0.3", - "@napi-rs/cross-toolchain-x64-target-ppc64le": "^1.0.3", - "@napi-rs/cross-toolchain-x64-target-s390x": "^1.0.3", - "@napi-rs/cross-toolchain-x64-target-x86_64": "^1.0.3" - }, - "peerDependenciesMeta": { - "@napi-rs/cross-toolchain-arm64-target-aarch64": { - "optional": true - }, - "@napi-rs/cross-toolchain-arm64-target-armv7": { - "optional": true - }, - "@napi-rs/cross-toolchain-arm64-target-ppc64le": { - "optional": true - }, - "@napi-rs/cross-toolchain-arm64-target-s390x": { - "optional": true - }, - "@napi-rs/cross-toolchain-arm64-target-x86_64": { - "optional": true - }, - "@napi-rs/cross-toolchain-x64-target-aarch64": { - "optional": true - }, - "@napi-rs/cross-toolchain-x64-target-armv7": { - "optional": true - }, - "@napi-rs/cross-toolchain-x64-target-ppc64le": { - "optional": true - }, - "@napi-rs/cross-toolchain-x64-target-s390x": { - "optional": true - }, - "@napi-rs/cross-toolchain-x64-target-x86_64": { - "optional": true - } - } - }, - "node_modules/@napi-rs/lzma": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma/-/lzma-1.4.5.tgz", - "integrity": "sha512-zS5LuN1OBPAyZpda2ZZgYOEDC+xecUdAGnrvbYzjnLXkrq/OBC3B9qcRvlxbDR3k5H/gVfvef1/jyUqPknqjbg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "optionalDependencies": { - "@napi-rs/lzma-android-arm-eabi": "1.4.5", - "@napi-rs/lzma-android-arm64": "1.4.5", - "@napi-rs/lzma-darwin-arm64": "1.4.5", - "@napi-rs/lzma-darwin-x64": "1.4.5", - "@napi-rs/lzma-freebsd-x64": "1.4.5", - "@napi-rs/lzma-linux-arm-gnueabihf": "1.4.5", - "@napi-rs/lzma-linux-arm64-gnu": "1.4.5", - "@napi-rs/lzma-linux-arm64-musl": "1.4.5", - "@napi-rs/lzma-linux-ppc64-gnu": "1.4.5", - "@napi-rs/lzma-linux-riscv64-gnu": "1.4.5", - "@napi-rs/lzma-linux-s390x-gnu": "1.4.5", - "@napi-rs/lzma-linux-x64-gnu": "1.4.5", - "@napi-rs/lzma-linux-x64-musl": "1.4.5", - "@napi-rs/lzma-wasm32-wasi": "1.4.5", - "@napi-rs/lzma-win32-arm64-msvc": "1.4.5", - "@napi-rs/lzma-win32-ia32-msvc": "1.4.5", - "@napi-rs/lzma-win32-x64-msvc": "1.4.5" - } - }, - "node_modules/@napi-rs/lzma-android-arm-eabi": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-android-arm-eabi/-/lzma-android-arm-eabi-1.4.5.tgz", - "integrity": "sha512-Up4gpyw2SacmyKWWEib06GhiDdF+H+CCU0LAV8pnM4aJIDqKKd5LHSlBht83Jut6frkB0vwEPmAkv4NjQ5u//Q==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-android-arm64": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-android-arm64/-/lzma-android-arm64-1.4.5.tgz", - "integrity": "sha512-uwa8sLlWEzkAM0MWyoZJg0JTD3BkPknvejAFG2acUA1raXM8jLrqujWCdOStisXhqQjZ2nDMp3FV6cs//zjfuQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-darwin-arm64": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-darwin-arm64/-/lzma-darwin-arm64-1.4.5.tgz", - "integrity": "sha512-0Y0TQLQ2xAjVabrMDem1NhIssOZzF/y/dqetc6OT8mD3xMTDtF8u5BqZoX3MyPc9FzpsZw4ksol+w7DsxHrpMA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-darwin-x64": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-darwin-x64/-/lzma-darwin-x64-1.4.5.tgz", - "integrity": "sha512-vR2IUyJY3En+V1wJkwmbGWcYiT8pHloTAWdW4pG24+51GIq+intst6Uf6D/r46citObGZrlX0QvMarOkQeHWpw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-freebsd-x64": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-freebsd-x64/-/lzma-freebsd-x64-1.4.5.tgz", - "integrity": "sha512-XpnYQC5SVovO35tF0xGkbHYjsS6kqyNCjuaLQ2dbEblFRr5cAZVvsJ/9h7zj/5FluJPJRDojVNxGyRhTp4z2lw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-linux-arm-gnueabihf": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-arm-gnueabihf/-/lzma-linux-arm-gnueabihf-1.4.5.tgz", - "integrity": "sha512-ic1ZZMoRfRMwtSwxkyw4zIlbDZGC6davC9r+2oX6x9QiF247BRqqT94qGeL5ZP4Vtz0Hyy7TEViWhx5j6Bpzvw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-linux-arm64-gnu": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-arm64-gnu/-/lzma-linux-arm64-gnu-1.4.5.tgz", - "integrity": "sha512-asEp7FPd7C1Yi6DQb45a3KPHKOFBSfGuJWXcAd4/bL2Fjetb2n/KK2z14yfW8YC/Fv6x3rBM0VAZKmJuz4tysg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-linux-arm64-musl": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-arm64-musl/-/lzma-linux-arm64-musl-1.4.5.tgz", - "integrity": "sha512-yWjcPDgJ2nIL3KNvi4536dlT/CcCWO0DUyEOlBs/SacG7BeD6IjGh6yYzd3/X1Y3JItCbZoDoLUH8iB1lTXo3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-linux-ppc64-gnu": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-ppc64-gnu/-/lzma-linux-ppc64-gnu-1.4.5.tgz", - "integrity": "sha512-0XRhKuIU/9ZjT4WDIG/qnX7Xz7mSQHYZo9Gb3MP2gcvBgr6BA4zywQ9k3gmQaPn9ECE+CZg2V7DV7kT+x2pUMQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-linux-riscv64-gnu": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-riscv64-gnu/-/lzma-linux-riscv64-gnu-1.4.5.tgz", - "integrity": "sha512-QrqDIPEUUB23GCpyQj/QFyMlr8SGxxyExeZz9OWFnHfb70kXdTLWrHS/hEI1Ru+lSbQ/6xRqeoGyQ4Aqdg+/RA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-linux-s390x-gnu": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-s390x-gnu/-/lzma-linux-s390x-gnu-1.4.5.tgz", - "integrity": "sha512-k8RVM5aMhW86E9H0QXdquwojew4H3SwPxbRVbl49/COJQWCUjGi79X6mYruMnMPEznZinUiT1jgKbFo2A00NdA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-linux-x64-gnu": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.4.5.tgz", - "integrity": "sha512-6rMtBgnIq2Wcl1rQdZsnM+rtCcVCbws1nF8S2NzaUsVaZv8bjrPiAa0lwg4Eqnn1d9lgwqT+cZgm5m+//K08Kw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-linux-x64-musl": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-musl/-/lzma-linux-x64-musl-1.4.5.tgz", - "integrity": "sha512-eiadGBKi7Vd0bCArBUOO/qqRYPHt/VQVvGyYvDFt6C2ZSIjlD+HuOl+2oS1sjf4CFjK4eDIog6EdXnL0NE6iyQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-wasm32-wasi": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-wasm32-wasi/-/lzma-wasm32-wasi-1.4.5.tgz", - "integrity": "sha512-+VyHHlr68dvey6fXc2hehw9gHVFIW3TtGF1XkcbAu65qVXsA9D/T+uuoRVqhE+JCyFHFrO0ixRbZDRK1XJt1sA==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^1.0.3" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@napi-rs/lzma-win32-arm64-msvc": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-win32-arm64-msvc/-/lzma-win32-arm64-msvc-1.4.5.tgz", - "integrity": "sha512-eewnqvIyyhHi3KaZtBOJXohLvwwN27gfS2G/YDWdfHlbz1jrmfeHAmzMsP5qv8vGB+T80TMHNkro4kYjeh6Deg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-win32-ia32-msvc": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-win32-ia32-msvc/-/lzma-win32-ia32-msvc-1.4.5.tgz", - "integrity": "sha512-OeacFVRCJOKNU/a0ephUfYZ2Yt+NvaHze/4TgOwJ0J0P4P7X1mHzN+ig9Iyd74aQDXYqc7kaCXA2dpAOcH87Cg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-win32-x64-msvc": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-win32-x64-msvc/-/lzma-win32-x64-msvc-1.4.5.tgz", - "integrity": "sha512-T4I1SamdSmtyZgDXGAGP+y5LEK5vxHUFwe8mz6D4R7Sa5/WCxTcCIgPJ9BD7RkpO17lzhlaM2vmVvMy96Lvk9Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/tar": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@napi-rs/tar/-/tar-1.1.0.tgz", - "integrity": "sha512-7cmzIu+Vbupriudo7UudoMRH2OA3cTw67vva8MxeoAe5S7vPFI7z0vp0pMXiA25S8IUJefImQ90FeJjl8fjEaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@napi-rs/tar-android-arm-eabi": "1.1.0", - "@napi-rs/tar-android-arm64": "1.1.0", - "@napi-rs/tar-darwin-arm64": "1.1.0", - "@napi-rs/tar-darwin-x64": "1.1.0", - "@napi-rs/tar-freebsd-x64": "1.1.0", - "@napi-rs/tar-linux-arm-gnueabihf": "1.1.0", - "@napi-rs/tar-linux-arm64-gnu": "1.1.0", - "@napi-rs/tar-linux-arm64-musl": "1.1.0", - "@napi-rs/tar-linux-ppc64-gnu": "1.1.0", - "@napi-rs/tar-linux-s390x-gnu": "1.1.0", - "@napi-rs/tar-linux-x64-gnu": "1.1.0", - "@napi-rs/tar-linux-x64-musl": "1.1.0", - "@napi-rs/tar-wasm32-wasi": "1.1.0", - "@napi-rs/tar-win32-arm64-msvc": "1.1.0", - "@napi-rs/tar-win32-ia32-msvc": "1.1.0", - "@napi-rs/tar-win32-x64-msvc": "1.1.0" - } - }, - "node_modules/@napi-rs/tar-android-arm-eabi": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@napi-rs/tar-android-arm-eabi/-/tar-android-arm-eabi-1.1.0.tgz", - "integrity": "sha512-h2Ryndraj/YiKgMV/r5by1cDusluYIRT0CaE0/PekQ4u+Wpy2iUVqvzVU98ZPnhXaNeYxEvVJHNGafpOfaD0TA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/tar-android-arm64": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@napi-rs/tar-android-arm64/-/tar-android-arm64-1.1.0.tgz", - "integrity": "sha512-DJFyQHr1ZxNZorm/gzc1qBNLF/FcKzcH0V0Vwan5P+o0aE2keQIGEjJ09FudkF9v6uOuJjHCVDdK6S6uHtShAw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/tar-darwin-arm64": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@napi-rs/tar-darwin-arm64/-/tar-darwin-arm64-1.1.0.tgz", - "integrity": "sha512-Zz2sXRzjIX4e532zD6xm2SjXEym6MkvfCvL2RMpG2+UwNVDVscHNcz3d47Pf3sysP2e2af7fBB3TIoK2f6trPw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/tar-darwin-x64": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@napi-rs/tar-darwin-x64/-/tar-darwin-x64-1.1.0.tgz", - "integrity": "sha512-EI+CptIMNweT0ms9S3mkP/q+J6FNZ1Q6pvpJOEcWglRfyfQpLqjlC0O+dptruTPE8VamKYuqdjxfqD8hifZDOA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/tar-freebsd-x64": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@napi-rs/tar-freebsd-x64/-/tar-freebsd-x64-1.1.0.tgz", - "integrity": "sha512-J0PIqX+pl6lBIAckL/c87gpodLbjZB1OtIK+RDscKC9NLdpVv6VGOxzUV/fYev/hctcE8EfkLbgFOfpmVQPg2g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/tar-linux-arm-gnueabihf": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@napi-rs/tar-linux-arm-gnueabihf/-/tar-linux-arm-gnueabihf-1.1.0.tgz", - "integrity": "sha512-SLgIQo3f3EjkZ82ZwvrEgFvMdDAhsxCYjyoSuWfHCz0U16qx3SuGCp8+FYOPYCECHN3ZlGjXnoAIt9ERd0dEUg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/tar-linux-arm64-gnu": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@napi-rs/tar-linux-arm64-gnu/-/tar-linux-arm64-gnu-1.1.0.tgz", - "integrity": "sha512-d014cdle52EGaH6GpYTQOP9Py7glMO1zz/+ynJPjjzYFSxvdYx0byrjumZk2UQdIyGZiJO2MEFpCkEEKFSgPYA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/tar-linux-arm64-musl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@napi-rs/tar-linux-arm64-musl/-/tar-linux-arm64-musl-1.1.0.tgz", - "integrity": "sha512-L/y1/26q9L/uBqiW/JdOb/Dc94egFvNALUZV2WCGKQXc6UByPBMgdiEyW2dtoYxYYYYc+AKD+jr+wQPcvX2vrQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/tar-linux-ppc64-gnu": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@napi-rs/tar-linux-ppc64-gnu/-/tar-linux-ppc64-gnu-1.1.0.tgz", - "integrity": "sha512-EPE1K/80RQvPbLRJDJs1QmCIcH+7WRi0F73+oTe1582y9RtfGRuzAkzeBuAGRXAQEjRQw/RjtNqr6UTJ+8UuWQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/tar-linux-s390x-gnu": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@napi-rs/tar-linux-s390x-gnu/-/tar-linux-s390x-gnu-1.1.0.tgz", - "integrity": "sha512-B2jhWiB1ffw1nQBqLUP1h4+J1ovAxBOoe5N2IqDMOc63fsPZKNqF1PvO/dIem8z7LL4U4bsfmhy3gBfu547oNQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/tar-linux-x64-gnu": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@napi-rs/tar-linux-x64-gnu/-/tar-linux-x64-gnu-1.1.0.tgz", - "integrity": "sha512-tbZDHnb9617lTnsDMGo/eAMZxnsQFnaRe+MszRqHguKfMwkisc9CCJnks/r1o84u5fECI+J/HOrKXgczq/3Oww==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/tar-linux-x64-musl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@napi-rs/tar-linux-x64-musl/-/tar-linux-x64-musl-1.1.0.tgz", - "integrity": "sha512-dV6cODlzbO8u6Anmv2N/ilQHq/AWz0xyltuXoLU3yUyXbZcnWYZuB2rL8OBGPmqNcD+x9NdScBNXh7vWN0naSQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/tar-wasm32-wasi": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@napi-rs/tar-wasm32-wasi/-/tar-wasm32-wasi-1.1.0.tgz", - "integrity": "sha512-jIa9nb2HzOrfH0F8QQ9g3WE4aMH5vSI5/1NYVNm9ysCmNjCCtMXCAhlI3WKCdm/DwHf0zLqdrrtDFXODcNaqMw==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^1.0.3" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@napi-rs/tar-win32-arm64-msvc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@napi-rs/tar-win32-arm64-msvc/-/tar-win32-arm64-msvc-1.1.0.tgz", - "integrity": "sha512-vfpG71OB0ijtjemp3WTdmBKJm9R70KM8vsSExMsIQtV0lVzP07oM1CW6JbNRPXNLhRoue9ofYLiUDk8bE0Hckg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/tar-win32-ia32-msvc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@napi-rs/tar-win32-ia32-msvc/-/tar-win32-ia32-msvc-1.1.0.tgz", - "integrity": "sha512-hGPyPW60YSpOSgzfy68DLBHgi6HxkAM+L59ZZZPMQ0TOXjQg+p2EW87+TjZfJOkSpbYiEkULwa/f4a2hcVjsqQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/tar-win32-x64-msvc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@napi-rs/tar-win32-x64-msvc/-/tar-win32-x64-msvc-1.1.0.tgz", - "integrity": "sha512-L6Ed1DxXK9YSCMyvpR8MiNAyKNkQLjsHsHK9E0qnHa8NzLFqzDKhvs5LfnWxM2kJ+F7m/e5n9zPm24kHb3LsVw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", - "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1", - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, - "node_modules/@napi-rs/wasm-tools": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools/-/wasm-tools-1.0.1.tgz", - "integrity": "sha512-enkZYyuCdo+9jneCPE/0fjIta4wWnvVN9hBo2HuiMpRF0q3lzv1J6b/cl7i0mxZUKhBrV3aCKDBQnCOhwKbPmQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@napi-rs/wasm-tools-android-arm-eabi": "1.0.1", - "@napi-rs/wasm-tools-android-arm64": "1.0.1", - "@napi-rs/wasm-tools-darwin-arm64": "1.0.1", - "@napi-rs/wasm-tools-darwin-x64": "1.0.1", - "@napi-rs/wasm-tools-freebsd-x64": "1.0.1", - "@napi-rs/wasm-tools-linux-arm64-gnu": "1.0.1", - "@napi-rs/wasm-tools-linux-arm64-musl": "1.0.1", - "@napi-rs/wasm-tools-linux-x64-gnu": "1.0.1", - "@napi-rs/wasm-tools-linux-x64-musl": "1.0.1", - "@napi-rs/wasm-tools-wasm32-wasi": "1.0.1", - "@napi-rs/wasm-tools-win32-arm64-msvc": "1.0.1", - "@napi-rs/wasm-tools-win32-ia32-msvc": "1.0.1", - "@napi-rs/wasm-tools-win32-x64-msvc": "1.0.1" - } - }, - "node_modules/@napi-rs/wasm-tools-android-arm-eabi": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-android-arm-eabi/-/wasm-tools-android-arm-eabi-1.0.1.tgz", - "integrity": "sha512-lr07E/l571Gft5v4aA1dI8koJEmF1F0UigBbsqg9OWNzg80H3lDPO+auv85y3T/NHE3GirDk7x/D3sLO57vayw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/wasm-tools-android-arm64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-android-arm64/-/wasm-tools-android-arm64-1.0.1.tgz", - "integrity": "sha512-WDR7S+aRLV6LtBJAg5fmjKkTZIdrEnnQxgdsb7Cf8pYiMWBHLU+LC49OUVppQ2YSPY0+GeYm9yuZWW3kLjJ7Bg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/wasm-tools-darwin-arm64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-darwin-arm64/-/wasm-tools-darwin-arm64-1.0.1.tgz", - "integrity": "sha512-qWTI+EEkiN0oIn/N2gQo7+TVYil+AJ20jjuzD2vATS6uIjVz+Updeqmszi7zq7rdFTLp6Ea3/z4kDKIfZwmR9g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/wasm-tools-darwin-x64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-darwin-x64/-/wasm-tools-darwin-x64-1.0.1.tgz", - "integrity": "sha512-bA6hubqtHROR5UI3tToAF/c6TDmaAgF0SWgo4rADHtQ4wdn0JeogvOk50gs2TYVhKPE2ZD2+qqt7oBKB+sxW3A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/wasm-tools-freebsd-x64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-freebsd-x64/-/wasm-tools-freebsd-x64-1.0.1.tgz", - "integrity": "sha512-90+KLBkD9hZEjPQW1MDfwSt5J1L46EUKacpCZWyRuL6iIEO5CgWU0V/JnEgFsDOGyyYtiTvHc5bUdUTWd4I9Vg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/wasm-tools-linux-arm64-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-linux-arm64-gnu/-/wasm-tools-linux-arm64-gnu-1.0.1.tgz", - "integrity": "sha512-rG0QlS65x9K/u3HrKafDf8cFKj5wV2JHGfl8abWgKew0GVPyp6vfsDweOwHbWAjcHtp2LHi6JHoW80/MTHm52Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/wasm-tools-linux-arm64-musl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-linux-arm64-musl/-/wasm-tools-linux-arm64-musl-1.0.1.tgz", - "integrity": "sha512-jAasbIvjZXCgX0TCuEFQr+4D6Lla/3AAVx2LmDuMjgG4xoIXzjKWl7c4chuaD+TI+prWT0X6LJcdzFT+ROKGHQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/wasm-tools-linux-x64-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-linux-x64-gnu/-/wasm-tools-linux-x64-gnu-1.0.1.tgz", - "integrity": "sha512-Plgk5rPqqK2nocBGajkMVbGm010Z7dnUgq0wtnYRZbzWWxwWcXfZMPa8EYxrK4eE8SzpI7VlZP1tdVsdjgGwMw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/wasm-tools-linux-x64-musl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-linux-x64-musl/-/wasm-tools-linux-x64-musl-1.0.1.tgz", - "integrity": "sha512-GW7AzGuWxtQkyHknHWYFdR0CHmW6is8rG2Rf4V6GNmMpmwtXt/ItWYWtBe4zqJWycMNazpfZKSw/BpT7/MVCXQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/wasm-tools-wasm32-wasi": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-wasm32-wasi/-/wasm-tools-wasm32-wasi-1.0.1.tgz", - "integrity": "sha512-/nQVSTrqSsn7YdAc2R7Ips/tnw5SPUcl3D7QrXCNGPqjbatIspnaexvaOYNyKMU6xPu+pc0BTnKVmqhlJJCPLA==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^1.0.3" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@napi-rs/wasm-tools-win32-arm64-msvc": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-win32-arm64-msvc/-/wasm-tools-win32-arm64-msvc-1.0.1.tgz", - "integrity": "sha512-PFi7oJIBu5w7Qzh3dwFea3sHRO3pojMsaEnUIy22QvsW+UJfNQwJCryVrpoUt8m4QyZXI+saEq/0r4GwdoHYFQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/wasm-tools-win32-ia32-msvc": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-win32-ia32-msvc/-/wasm-tools-win32-ia32-msvc-1.0.1.tgz", - "integrity": "sha512-gXkuYzxQsgkj05Zaq+KQTkHIN83dFAwMcTKa2aQcpYPRImFm2AQzEyLtpXmyCWzJ0F9ZYAOmbSyrNew8/us6bw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/wasm-tools-win32-x64-msvc": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-win32-x64-msvc/-/wasm-tools-win32-x64-msvc-1.0.1.tgz", - "integrity": "sha512-rEAf05nol3e3eei2sRButmgXP+6ATgm0/38MKhz9Isne82T4rPIMYsCIFj0kOisaGeVwoi2fnm7O9oWp5YVnYQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@octokit/auth-token": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", - "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/core": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz", - "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/auth-token": "^6.0.0", - "@octokit/graphql": "^9.0.3", - "@octokit/request": "^10.0.6", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "before-after-hook": "^4.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/endpoint": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.2.tgz", - "integrity": "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/graphql": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.3.tgz", - "integrity": "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/request": "^10.0.6", - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/openapi-types": { - "version": "27.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", - "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@octokit/plugin-paginate-rest": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-14.0.0.tgz", - "integrity": "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-request-log": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-6.0.0.tgz", - "integrity": "sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-17.0.0.tgz", - "integrity": "sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/request": { - "version": "10.0.7", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.7.tgz", - "integrity": "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/endpoint": "^11.0.2", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "fast-content-type-parse": "^3.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/request-error": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", - "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/rest": { - "version": "22.0.1", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-22.0.1.tgz", - "integrity": "sha512-Jzbhzl3CEexhnivb1iQ0KJ7s5vvjMWcmRtq5aUsKmKDrRW6z3r84ngmiFKFvpZjpiU/9/S6ITPFRpn5s/3uQJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/core": "^7.0.6", - "@octokit/plugin-paginate-rest": "^14.0.0", - "@octokit/plugin-request-log": "^6.0.0", - "@octokit/plugin-rest-endpoint-methods": "^17.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/types": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", - "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^27.0.0" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", - "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/node": { - "version": "25.0.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.8.tgz", - "integrity": "sha512-powIePYMmC3ibL0UJ2i2s0WIbq6cg6UyVFQxSCpaPxxzAaziRfimGivjdF943sSGV6RADVbk0Nvlm5P/FB44Zg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "license": "MIT" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/before-after-hook": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", - "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/chardet": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", - "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 12" - } - }, - "node_modules/clipanion": { - "version": "4.0.0-rc.4", - "resolved": "https://registry.npmjs.org/clipanion/-/clipanion-4.0.0-rc.4.tgz", - "integrity": "sha512-CXkMQxU6s9GklO/1f714dkKBMu1lopS1WFF0B8o4AxPykR1hpozxSiUZ5ZUeBjfPgCWqbcNOtZVFhB8Lkfp1+Q==", - "dev": true, - "license": "MIT", - "workspaces": [ - "website" - ], - "dependencies": { - "typanion": "^3.8.0" - }, - "peerDependencies": { - "typanion": "*" - } - }, - "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, - "license": "MIT" - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/emnapi": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/emnapi/-/emnapi-1.8.1.tgz", - "integrity": "sha512-34i2BbgHx1LnEO4JCGQYo6h6s4e4KrdWtdTHfllBNLbXSHPmdIHplxKejfabsRK+ukNciqVdalB+fxMibqHdaQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "node-addon-api": ">= 6.1.0" - }, - "peerDependenciesMeta": { - "node-addon-api": { - "optional": true - } - } - }, - "node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "dev": true, - "license": "MIT" - }, - "node_modules/es-toolkit": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.43.0.tgz", - "integrity": "sha512-SKCT8AsWvYzBBuUqMk4NPwFlSdqLpJwmy6AP322ERn8W2YLIB6JBXnwMI2Qsh2gfphT3q7EKAxKb23cvFHFwKA==", - "dev": true, - "license": "MIT", - "workspaces": [ - "docs", - "benchmarks" - ] - }, - "node_modules/fast-content-type-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", - "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/get-east-asian-width": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", - "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "license": "ISC" - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/mute-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-3.0.0.tgz", - "integrity": "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/obug": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", - "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "license": "MIT" - }, - "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, - "node_modules/typanion": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/typanion/-/typanion-3.14.0.tgz", - "integrity": "sha512-ZW/lVMRabETuYCd9O9ZvMhAh8GslSqaUjxmK/JLPCh6l73CvLBiuXswj/+7LdnWOgYsQ130FqLzFz5aGT4I3Ug==", - "dev": true, - "license": "MIT", - "workspaces": [ - "website" - ] - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/universal-user-agent": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", - "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", - "dev": true, - "license": "ISC" - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index 3126203..0000000 --- a/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "cinnamon-rs", - "version": "0.1.3", - "main": "index.js", - "types": "index.d.ts", - "napi": { - "binaryName": "cinnamon", - "targets": [ - "aarch64-apple-darwin", - "x86_64-pc-windows-msvc", - "x86_64-unknown-linux-gnu" - ] - }, - "repository": { - "type": "git", - "url": "git+https://github.com/ItsLimeNade/cinnamon.git" - }, - "license": "MIT", - "scripts": { - "build": "napi build --platform --release", - "artifacts": "napi artifacts -d ./artifacts" - }, - "devDependencies": { - "@napi-rs/cli": "^3.5.1", - "ts-node": "^10.9.2", - "typescript": "^5.9.3" - }, - "engines": { - "node": ">= 10" - } -} diff --git a/src/client.rs b/src/client.rs index db47348..1e107fa 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,42 +1,97 @@ use super::error::NightscoutError; -use anyhow::Result; use reqwest::{Client as HttpClient, Response}; use sha1::{Digest, Sha1}; use url::Url; use crate::models::devicestatus::DeviceStatusService; -use crate::models::entries::EntriesService; +use crate::models::entries::{MbgService, SgvService}; use crate::models::profile::ProfileService; use crate::models::properties::PropertiesService; use crate::models::status::StatusService; use crate::models::treatments::TreatmentsService; +use std::ops::Deref; +use std::sync::Arc; + #[derive(Clone)] pub struct NightscoutClient { + pub inner: Arc, +} + +#[derive(Clone)] +pub struct NightscoutClientInner { + /// The base URL of the Nightscout instance. pub base_url: Url, - pub api_secret: Option, + /// The internal HTTP client used for requests. pub http: HttpClient, + /// The SHA1 hash of the API secret, used for authentication headers. pub api_secret_hash: Option, } +impl Deref for NightscoutClient { + type Target = NightscoutClientInner; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + impl NightscoutClient { - pub fn new(base_url: &str, api_secret: Option) -> Result { - let hash = api_secret.clone().map(|secret| { - let mut hasher = Sha1::new(); - hasher.update(secret.as_bytes()); - let result = hasher.finalize(); - format!("{:x}", result) - }); - - Ok(Self { + /// Creates a new `NightscoutClient` without an API secret. + /// + /// This client will only be able to access public endpoints. To perform write operations + /// or access protected data, chain `with_secret`. + /// + /// ## Arguments + /// + /// * `base_url` - The full URL to the Nightscout instance (e.g., `https://my-site.herokuapp.com`). + /// + /// ## Errors + /// + /// Returns a `NightscoutError` if the URL is invalid. + pub fn new(base_url: &str) -> Result { + let inner = NightscoutClientInner { base_url: Url::parse(base_url)?, http: HttpClient::new(), - api_secret, - api_secret_hash: hash, - }) + api_secret_hash: None, + }; + let client = Self { + inner: Arc::new(inner), + }; + Ok(client) + } + + /// Appends an API secret to the client for authentication. + /// + /// The secret is automatically hashed using SHA1 as required by Nightscout headers. + /// + /// # Example + /// + /// ```rust + /// # use cinnamon::client::NightscoutClient; + /// let client = NightscoutClient::new("https://example.com").unwrap() + /// .with_secret("my-password-123"); + /// ``` + pub fn with_secret(self, api_secret: impl Into) -> Self { + let secret = api_secret.into(); + + let mut hasher = Sha1::new(); + hasher.update(secret.as_bytes()); + let hash = format!("{:x}", hasher.finalize()); + + let inner = NightscoutClientInner { + base_url: self.base_url.clone(), + http: self.http.clone(), + api_secret_hash: Some(hash), + }; + + Self { + inner: Arc::new(inner), + } } + /// Adds authentication headers to a request if a secret is present. pub fn auth(&self, request: reqwest::RequestBuilder) -> reqwest::RequestBuilder { if let Some(hash) = &self.api_secret_hash { request.header("api-secret", hash) @@ -45,43 +100,60 @@ impl NightscoutClient { } } + /// Access the Treatments service for managing care events (boluses, carbs, etc.). pub fn treatments(&self) -> TreatmentsService { TreatmentsService { client: self.clone(), } } - pub fn entries(&self) -> EntriesService { - EntriesService { + /// Access the Sensor Glucose Value (SGV) service. + pub fn sgv(&self) -> SgvService { + SgvService { client: self.clone(), } } + /// Access the Meter Blood Glucose (MBG) service. + pub fn mbg(&self) -> MbgService { + MbgService { + client: self.clone(), + } + } + + /// Access the Properties service for system status (IOB, COB, Pump). pub fn properties(&self) -> PropertiesService { PropertiesService { client: self.clone(), } } + /// Access the Device Status service for uploader and pump status updates. pub fn devicestatus(&self) -> DeviceStatusService { DeviceStatusService { client: self.clone(), } } + /// Access the Profile service for basal rates, ISF, and carb ratios. pub fn profiles(&self) -> ProfileService { ProfileService { client: self.clone(), } } + /// Access the server status service (version, settings, capabilities). pub fn status(&self) -> StatusService { StatusService { client: self.clone(), } } - pub async fn send_checked( + /// Sends a request and checks the response status. + /// + /// Returns `NightscoutError::AuthError` if the server returns 401 Unauthorized, + /// or `NightscoutError::ApiError` for other non-success codes. + pub(crate) async fn send_checked( &self, request: reqwest::RequestBuilder, ) -> Result { @@ -104,19 +176,14 @@ impl NightscoutClient { } } - // pub async fn iob(&self) -> Result { - // let url = self - // .base_url - // .join(Endpoint::Iob.as_path()) - // .expect("Error building the URL"); - - // let mut request = self.http.get(url); - - // request = self.auth(request); - - // let res = self.send_checked(request).await?; - // let wrapper = res.json::().await?; - - // Ok(wrapper.iob) - // } + /// Helper to fetch and deserialize a JSON response from a URL. + pub(crate) async fn fetch( + &self, + url: Url, + ) -> Result { + let req = self.auth(self.http.get(url)); + let res = self.send_checked(req).await?; + let data = res.json::().await?; + Ok(data) + } } diff --git a/src/lib.rs b/src/lib.rs index 400ed0c..4743140 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,45 @@ +//! # Cinnamon +//! +//! `cinnamon` is a type-safe, asynchronous Rust client for the Nightscout API (v1 & v2). +//! +//! It provides strongly-typed interfaces for interacting with Nightscout entries, treatments, +//! profiles, and device status updates. The library handles authentication, URL construction, +//! and error propagation, allowing you to focus on the data. +//! +//! ## Usage Pattern: Builder vs. Direct Fetch +//! +//! This library uses two distinct patterns depending on the complexity of the endpoint: +//! +//! 1. **Query Builder Pattern**: For endpoints that support filtering, pagination, or large datasets (e.g., Entries, Treatments), +//! the `.get()` method returns a `QueryBuilder`. You must chain methods to configure the query and finally call `.send().await` to execute it. +//! 2. **Direct Fetch Pattern**: For simpler endpoints that return a single system state (e.g., Status, Profile), +//! the `.get()` method is asynchronous and returns the data immediately. No `.send()` is required. +//! +//! ## Example +//! +//! ```rust,no_run +//! use cinnamon::client::NightscoutClient; +//! use cinnamon::models::properties::PropertyType; +//! +//! #[tokio::main] +//! async fn main() -> Result<(), Box> { +//! let client = NightscoutClient::new("https://my-cgm.herokuapp.com")? +//! .with_secret("my_secret"); +//! +//! // Pattern 1: Query Builder (needs .send()) +//! let entries = client.sgv().get() +//! .limit(5) +//! .send() +//! .await?; +//! +//! // Pattern 2: Direct Fetch (returns data immediately) +//! let status = client.status().get().await?; +//! +//! Ok(()) +//! } +//! ``` pub mod client; pub mod endpoints; pub mod error; pub mod models; -pub mod node_bindings; pub mod query_builder; diff --git a/src/models/devicestatus.rs b/src/models/devicestatus.rs index 6840981..d305019 100644 --- a/src/models/devicestatus.rs +++ b/src/models/devicestatus.rs @@ -3,7 +3,6 @@ use crate::endpoints::Endpoint; use crate::error::NightscoutError; use crate::query_builder::{HasDevice, QueryBuilder}; -use napi_derive::napi; use reqwest::Method; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -13,10 +12,32 @@ pub struct DeviceStatusService { } impl DeviceStatusService { - pub fn list(&self) -> QueryBuilder { + /// Initiates a query for Device Status entries. + /// + /// This returns a `QueryBuilder`. You can chain methods like `.limit()`, `.from()`, and `.to()` + /// before calling `.send()` to execute the request. + /// + /// # Example + /// + /// ```rust,no_run + /// # use cinnamon::client::NightscoutClient; + /// # async fn run() -> Result<(), Box> { + /// let client = NightscoutClient::new("https://ns.example.com")?; + /// let entries = client.devicestatus() + /// .get() + /// .limit(10) + /// .send() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn get(&self) -> QueryBuilder { QueryBuilder::::new(self.client.clone(), Endpoint::DeviceStatus, Method::GET) } + /// Initiates a delete request for Device Status entries. + /// + /// Use the builder to specify which entries to delete (e.g. by ID or date range). pub fn delete(&self) -> QueryBuilder { QueryBuilder::::new( self.client.clone(), @@ -25,6 +46,7 @@ impl DeviceStatusService { ) } + /// Uploads new Device Status entries to Nightscout. pub async fn create( &self, entries: Vec, @@ -40,7 +62,6 @@ impl DeviceStatusService { } } -#[napi(object)] #[derive(Debug, Serialize, Deserialize, Clone)] pub struct DeviceStatus { #[serde(rename = "_id", skip_serializing_if = "Option::is_none")] @@ -49,7 +70,6 @@ pub struct DeviceStatus { #[serde(skip_serializing_if = "Option::is_none")] pub device: Option, - #[napi(js_name = "createdAt")] #[serde(rename = "created_at")] pub created_at: String, diff --git a/src/models/entries.rs b/src/models/entries.rs index 799799c..a794d16 100644 --- a/src/models/entries.rs +++ b/src/models/entries.rs @@ -5,14 +5,9 @@ use crate::models::trends::Trend; use crate::query_builder::{HasDevice, QueryBuilder}; use chrono::{DateTime, Utc}; -use napi_derive::napi; use reqwest::Method; use serde::{Deserialize, Serialize}; -pub struct EntriesService { - pub client: NightscoutClient, -} - pub struct SgvService { pub client: NightscoutClient, } @@ -21,66 +16,50 @@ pub struct MbgService { pub client: NightscoutClient, } -impl EntriesService { - pub fn sgv(&self) -> SgvService { - SgvService { - client: self.client.clone(), - } - } - - pub fn mbg(&self) -> MbgService { - MbgService { - client: self.client.clone(), - } - } -} - impl SgvService { - /// Returns a query builder used to create your request + /// Initiates a query for SGV entries. /// - /// # Examples + /// This returns a `QueryBuilder`. You can chain methods like `.limit()`, `.from()`, and `.to()` + /// before calling `.send()` to execute the request. /// - /// ``` - /// use cinnamon::client::NightscoutClient; + /// # Example /// - /// let URL = "https://www.example_url.com/"; - /// let SECRET = "SecretPasss"; - /// - /// let client = NightscoutClient::new(URL, SECRET); - /// let entries: Vec = client.entries().sgv() - /// .list() - /// .from(Utc::now() - Duration::hours(24)) - /// .to(Utc::now() - Duration::hours(20)) - /// .limit(10) - /// .await?; - pub fn list(&self) -> QueryBuilder { + /// ```rust,no_run + /// # use cinnamon::client::NightscoutClient; + /// # async fn run() -> Result<(), Box> { + /// let client = NightscoutClient::new("https://ns.example.com")?; + /// let entries = client.sgv() + /// .get() + /// .limit(10) + /// .send() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn get(&self) -> QueryBuilder { QueryBuilder::::new(self.client.clone(), Endpoint::Sgv, Method::GET) } + /// Initiates a delete request for SGV entries. + /// + /// Use the builder to specify which entries to delete (e.g. by ID or date range). pub fn delete(&self) -> QueryBuilder { QueryBuilder::::new(self.client.clone(), Endpoint::Sgv, Method::DELETE) } - /// Fetches the latest available SGV entry. + /// Fetches the single latest available SGV entry. + /// + /// This is a convenience wrapper around `.get().limit(1)`. pub async fn latest(&self) -> Result { - let url = self.client.base_url.join(Endpoint::Current.as_path())?; - - let mut request = self.client.http.get(url); - - request = self.client.auth(request); + let builder = self.get().limit(1); + let result = builder.send().await?; - let res = self.client.send_checked(request).await?; - - let resp = res.json::>().await?; - resp.first().cloned().ok_or(NightscoutError::NotFound) + result.first().cloned().ok_or(NightscoutError::NotFound) } + /// Uploads new SGV entries to Nightscout. pub async fn create(&self, entries: Vec) -> Result, NightscoutError> { - let url = self - .client - .base_url - .join(Endpoint::Entries.as_path()) - .expect("URL Error"); + let url = self.client.base_url.join(Endpoint::Entries.as_path())?; let mut request = self.client.http.post(url); @@ -93,21 +72,47 @@ impl SgvService { } impl MbgService { - pub fn list(&self) -> QueryBuilder { + /// Initiates a query for MBG entries. + /// + /// This returns a `QueryBuilder`. You can chain methods like `.limit()`, `.from()`, and `.to()` + /// before calling `.send()` to execute the request. + /// + /// # Example + /// + /// ```rust,no_run + /// # use cinnamon::client::NightscoutClient; + /// # async fn run() -> Result<(), Box> { + /// let client = NightscoutClient::new("https://ns.example.com")?; + /// let entries = client.mbg() + /// .get() + /// .limit(10) + /// .send() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn get(&self) -> QueryBuilder { QueryBuilder::::new(self.client.clone(), Endpoint::Mbg, Method::GET) } + /// Initiates a delete request for MBG entries. + /// + /// Use the builder to specify which entries to delete (e.g. by ID or date range). pub fn delete(&self) -> QueryBuilder { QueryBuilder::::new(self.client.clone(), Endpoint::Mbg, Method::DELETE) } + /// Fetches the single latest available MBG entry. + /// + /// This is a convenience wrapper around `.get().limit(1)`. pub async fn latest(&self) -> Result { - let builder = self.list().limit(1); - let result = builder.await?; + let builder = self.get().limit(1); + let result = builder.send().await?; result.first().cloned().ok_or(NightscoutError::NotFound) } + /// Uploads new MBG entries to Nightscout. pub async fn create(&self, entries: Vec) -> Result, NightscoutError> { let url = self.client.base_url.join(Endpoint::Entries.as_path())?; @@ -124,14 +129,12 @@ impl MbgService { /// /// This struct represents blood glucose values automatically entered by a CGM (continuous glucose monitor) #[derive(Debug, Serialize, Deserialize, Clone)] -#[napi(object)] pub struct SgvEntry { #[serde(rename = "_id", skip_serializing_if = "Option::is_none")] pub id: Option, pub sgv: i32, pub date: i64, #[serde(rename = "dateString")] - #[napi(js_name = "dateString")] pub date_string: String, pub direction: Trend, #[serde(rename = "type")] @@ -171,14 +174,12 @@ impl HasDevice for SgvEntry { /// /// https://en.wikipedia.org/wiki/Fingerstick #[derive(Debug, Serialize, Deserialize, Clone)] -#[napi(object)] pub struct MbgEntry { #[serde(rename = "_id", skip_serializing_if = "Option::is_none")] pub id: Option, pub mbg: i32, pub date: i64, #[serde(rename = "dateString")] - #[napi(js_name = "dateString")] pub date_string: String, #[serde(rename = "type")] pub type_: String, diff --git a/src/models/profile.rs b/src/models/profile.rs index f61a2de..d097258 100644 --- a/src/models/profile.rs +++ b/src/models/profile.rs @@ -1,7 +1,6 @@ use crate::client::NightscoutClient; use crate::endpoints::Endpoint; use crate::error::NightscoutError; -use napi_derive::napi; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -10,30 +9,37 @@ pub struct ProfileService { } impl ProfileService { + /// Retrieves the Nightscout profile data. + /// + /// This is a "Direct Fetch" method. It does not use a query builder; calling this + /// method immediately initiates the HTTP request. + /// + /// # Example + /// + /// ```no_run + /// # use cinnamon::client::NightscoutClient; + /// # async fn run() -> Result<(), Box> { + /// let client = NightscoutClient::new("https://ns.example.com")?; + /// let status = client.profiles().get().await?; + /// println!("Nightscout Name: {}", status.default_profile_name); + /// # Ok(()) + /// # } + /// ``` pub async fn get(&self) -> Result, NightscoutError> { let url = self.client.base_url.join(Endpoint::Profile.as_path())?; - - let mut request = self.client.http.get(url); - request = self.client.auth(request); - - let response = self.client.send_checked(request).await?; - - Ok(response.json::>().await?) + self.client.fetch::>(url).await } } #[derive(Debug, Serialize, Deserialize, Clone)] -#[napi(object)] pub struct ProfileSet { #[serde(rename = "_id")] pub id: String, - #[napi(js_name = "defaultProfile")] #[serde(rename = "defaultProfile")] pub default_profile_name: String, - #[napi(js_name = "startDate")] #[serde(rename = "startDate")] pub start_date: String, @@ -46,12 +52,10 @@ pub struct ProfileSet { #[serde(skip_serializing_if = "Option::is_none")] pub units: Option, - #[napi(js_name = "createdAt")] pub created_at: String, } #[derive(Debug, Serialize, Deserialize, Clone)] -#[napi(object)] pub struct ProfileConfig { pub dia: f64, #[serde(skip_serializing_if = "Option::is_none")] @@ -68,7 +72,6 @@ pub struct ProfileConfig { } #[derive(Debug, Serialize, Deserialize, Clone)] -#[napi(object)] pub struct TimeSchedule { pub time: String, pub value: f64, diff --git a/src/models/properties.rs b/src/models/properties.rs index 90d6a8c..e3ce984 100644 --- a/src/models/properties.rs +++ b/src/models/properties.rs @@ -3,7 +3,6 @@ use crate::endpoints::Endpoint; use crate::error::NightscoutError; use crate::models::treatments::Treatment; use chrono::{DateTime, Utc}; -use napi_derive::napi; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; @@ -67,7 +66,6 @@ impl fmt::Display for PropertyType { /// The main response object for /api/v2/properties #[derive(Debug, Serialize, Deserialize, Clone)] -#[napi(object)] pub struct Properties { #[serde(skip_serializing_if = "Option::is_none")] pub bgnow: Option, @@ -105,7 +103,6 @@ pub struct Properties { } #[derive(Debug, Serialize, Deserialize, Clone)] -#[napi(object)] pub struct BgNow { pub mean: f64, pub last: f64, @@ -114,14 +111,11 @@ pub struct BgNow { } #[derive(Debug, Serialize, Deserialize, Clone)] -#[napi(object)] pub struct Delta { pub absolute: f64, - #[napi(js_name = "elapsedMins")] #[serde(rename = "elapsedMins")] pub elapsed_mins: f64, pub interpolated: bool, - #[napi(js_name = "mean5MinsAgo")] #[serde(rename = "mean5MinsAgo")] pub mean_5_mins_ago: f64, pub mgdl: f64, @@ -130,16 +124,13 @@ pub struct Delta { } #[derive(Debug, Serialize, Deserialize, Clone)] -#[napi(object)] pub struct Bucket { pub mean: f64, pub last: f64, pub mills: i64, pub index: i32, #[serde(rename = "fromMills")] - #[napi(js_name = "fromMills")] pub from_mills: i64, - #[napi(js_name = "toMills")] #[serde(rename = "toMills")] pub to_mills: i64, pub sgvs: Vec, @@ -147,7 +138,6 @@ pub struct Bucket { /// A simplified SGV used inside properties (slightly different from main Entries) #[derive(Debug, Serialize, Deserialize, Clone)] -#[napi(object)] pub struct PropertySgv { #[serde(rename = "_id")] pub id: String, @@ -161,7 +151,6 @@ pub struct PropertySgv { } #[derive(Debug, Serialize, Deserialize, Clone)] -#[napi(object)] pub struct Direction { pub display: Option, pub value: String, @@ -170,7 +159,6 @@ pub struct Direction { } #[derive(Debug, Serialize, Deserialize, Clone)] -#[napi(object)] pub struct Upbat { pub display: String, // devices is sometimes a Map, sometimes empty. Value is safest. @@ -178,65 +166,52 @@ pub struct Upbat { } #[derive(Debug, Serialize, Deserialize, Clone)] -#[napi(object)] pub struct IobProperty { pub iob: f64, pub activity: f64, pub source: String, pub display: String, - #[napi(js_name = "displayLine")] #[serde(rename = "displayLine")] pub display_line: String, - #[napi(js_name = "lastBolus")] #[serde(rename = "lastBolus", skip_serializing_if = "Option::is_none")] pub last_bolus: Option, } #[derive(Debug, Serialize, Deserialize, Clone)] -#[napi(object)] pub struct Cob { pub cob: f64, - #[napi(js_name = "isDecaying")] #[serde(rename = "isDecaying")] pub is_decaying: i32, - #[napi(js_name = "decayedBy")] #[serde(rename = "decayedBy")] pub decayed_by: String, pub source: String, pub display: Value, - #[napi(js_name = "displayLine")] #[serde(rename = "displayLine")] pub display_line: String, } #[derive(Debug, Serialize, Deserialize, Clone)] -#[napi(object)] pub struct Basal { pub display: String, pub current: Option, } #[derive(Debug, Serialize, Deserialize, Clone)] -#[napi(object)] pub struct BasalCurrent { pub basal: f64, - #[napi(js_name = "tempbasal")] #[serde(rename = "tempbasal")] pub temp_basal: Option, } #[derive(Debug, Serialize, Deserialize, Clone)] -#[napi(object)] pub struct DbSize { pub display: String, pub status: String, - #[napi(js_name = "totalDataSize")] #[serde(rename = "totalDataSize")] pub total_data_size: f64, } #[derive(Debug, Serialize, Deserialize, Clone)] -#[napi(object)] pub struct RuntimeState { pub state: String, } @@ -246,37 +221,33 @@ pub struct PropertiesService { } impl PropertiesService { + /// Begins building a request for Nightscout system properties. + /// + /// System properties include Insulin on Board (IOB), Carbs on Board (COB), Pump Status, etc. + /// + /// # Example + /// + /// ```rust,no_run + /// # use cinnamon::client::NightscoutClient; + /// # use cinnamon::models::properties::PropertyType; + /// # async fn run() -> Result<(), Box> { + /// let client = NightscoutClient::new("https://ns.example.com")?; + /// + /// // Fetch only IOB and COB + /// let props = client.properties() + /// .get() + /// .only(&[PropertyType::Iob, PropertyType::Cob]) + /// .send() + /// .await?; + /// # Ok(()) + /// # } + /// ``` pub fn get(&self) -> PropertiesRequest { PropertiesRequest::new(self.client.clone()) } } -/// Represents a snapshot of the Nightscout system state and properties. -/// -/// This struct acts as a strongly-typed container for the JSON response returned by the -/// `/api/v2/properties` endpoint. It aggregates data from various Nightscout plugins -/// (like IOB, COB, Pump status, etc.) into a single object. -/// -/// # Usage -/// You typically obtain this struct by calling `.send()` on a `PropertiesRequest`: -/// -/// ```rust -/// let properties = client.properties() -/// .get() -/// .only(&[PropertyType::Iob, PropertyType::Cob]) -/// .send() -/// .await?; -/// -/// if let Some(iob) = properties.iob { -/// println!("Current IOB: {} U", iob.iob); -/// } -/// ``` -/// -/// -/// # Forward Compatibility -/// Any properties returned by Nightscout that are not explicitly defined in this struct -/// (e.g., custom or future plugins) are captured in the `unknown` field, ensuring -/// no data is lost during deserialization. +/// A builder for constructing a properties request. pub struct PropertiesRequest { client: NightscoutClient, requested_properties: Vec, @@ -292,16 +263,47 @@ impl PropertiesRequest { } } + /// Specifies which properties to retrieve. + /// + /// By default, Nightscout returns all available system properties. Using this method + /// allows you to filter the response to specific fields (e.g., IOB, COB), which reduces + /// payload size and processing time. + /// + /// # Example + /// + /// ```rust,no_run + /// # use cinnamon::client::NightscoutClient; + /// # use cinnamon::models::properties::PropertyType; + /// # async fn run() -> Result<(), Box> { + /// let client = NightscoutClient::new("https://ns.example.com")?; + /// + /// let properties = client.properties() + /// .get() + /// // Request only Insulin on Board and Carbs on Board + /// .only(&[PropertyType::Iob, PropertyType::Cob]) + /// .send() + /// .await?; + /// + /// if let Some(iob) = properties.iob { + /// println!("IOB: {}", iob.iob); + /// } + /// # Ok(()) + /// # } + /// ``` pub fn only(mut self, properties: &[PropertyType]) -> Self { self.requested_properties.extend_from_slice(properties); self } + /// Requests the system state as it was at a specific time. + /// + /// If omitted, the current system state is returned. pub fn at(mut self, time: DateTime) -> Self { self.at_time = Some(time); self } + /// Executes the request. pub async fn send(self) -> Result { let base_path = Endpoint::Properties.as_path(); @@ -324,13 +326,7 @@ impl PropertiesRequest { .append_pair("time", &time.to_rfc3339()); } - let mut request = self.client.http.get(url); - request = self.client.auth(request); - - let response = self.client.send_checked(request).await?; - - let data = response.json::().await?; - + let data = self.client.fetch::(url).await?; Ok(data) } } diff --git a/src/models/status.rs b/src/models/status.rs index b0f09fd..2412d5e 100644 --- a/src/models/status.rs +++ b/src/models/status.rs @@ -1,7 +1,6 @@ use crate::client::NightscoutClient; use crate::endpoints::Endpoint; use crate::error::NightscoutError; -use napi_derive::napi; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -10,50 +9,53 @@ pub struct StatusService { } impl StatusService { + /// Retrieves the Nightscout server status, version, and settings. + /// + /// This is a "Direct Fetch" method. It does not use a query builder; calling this + /// method immediately initiates the HTTP request. + /// + /// # Example + /// + /// ```rust,no_run + /// # use cinnamon::client::NightscoutClient; + /// # async fn run() -> Result<(), Box> { + /// let client = NightscoutClient::new("[https://ns.example.com](https://ns.example.com)")?; + /// let status = client.status().get().await?; + /// println!("Nightscout Version: {}", status.version); + /// # Ok(()) + /// # } + /// ``` pub async fn get(&self) -> Result { let url = self.client.base_url.join(Endpoint::Status.as_path())?; - - let mut request = self.client.http.get(url); - request = self.client.auth(request); - - let response = self.client.send_checked(request).await?; - - Ok(response.json::().await?) + self.client.fetch::(url).await } } #[derive(Debug, Serialize, Deserialize, Clone)] -#[napi(object)] pub struct Status { pub status: String, #[allow(dead_code)] pub name: String, pub version: String, - #[napi(js_name = "serverTime")] #[serde(rename = "serverTime")] pub server_time: String, - #[napi(js_name = "serverTimeEpoch")] #[serde(rename = "serverTimeEpoch")] pub server_time_epoch: i64, - #[napi(js_name = "apiEnabled")] #[serde(rename = "apiEnabled")] pub api_enabled: bool, - #[napi(js_name = "carePortalEnabled")] #[serde(rename = "careportalEnabled")] pub care_portal_enabled: bool, - #[napi(js_name = "bolusCalcEnabled")] #[serde(rename = "boluscalcEnabled")] pub bolus_calc_enabled: bool, #[serde(default, skip_serializing_if = "Option::is_none")] pub settings: Option, - #[napi(js_name = "extendedSettings")] #[serde( default, rename = "extendedSettings", @@ -64,7 +66,6 @@ pub struct Status { #[serde(default, skip_serializing_if = "Option::is_none")] pub authorized: Option, - #[napi(js_name = "runtimeState")] #[serde( default, rename = "runtimeState", @@ -77,12 +78,10 @@ pub struct Status { } #[derive(Debug, Serialize, Deserialize, Clone)] -#[napi(object)] pub struct StatusSettings { #[serde(default, skip_serializing_if = "Option::is_none")] pub units: Option, - #[napi(js_name = "timeFormat")] #[serde( default, rename = "timeFormat", @@ -90,27 +89,21 @@ pub struct StatusSettings { )] pub time_format: Option, - #[napi(js_name = "dayStart")] #[serde(default, rename = "dayStart", skip_serializing_if = "Option::is_none")] pub day_start: Option, - #[napi(js_name = "dayEnd")] #[serde(default, rename = "dayEnd", skip_serializing_if = "Option::is_none")] pub day_end: Option, - #[napi(js_name = "nightMode")] #[serde(default, rename = "nightMode", skip_serializing_if = "Option::is_none")] pub night_mode: Option, - #[napi(js_name = "editMode")] #[serde(default, rename = "editMode", skip_serializing_if = "Option::is_none")] pub edit_mode: Option, - #[napi(js_name = "showRawbg")] #[serde(default, rename = "showRawbg", skip_serializing_if = "Option::is_none")] pub show_rawbg: Option, - #[napi(js_name = "customTitle")] #[serde( default, rename = "customTitle", @@ -121,7 +114,6 @@ pub struct StatusSettings { #[serde(default, skip_serializing_if = "Option::is_none")] pub theme: Option, - #[napi(js_name = "alarmUrgentHigh")] #[serde( default, rename = "alarmUrgentHigh", @@ -129,7 +121,6 @@ pub struct StatusSettings { )] pub alarm_urgent_high: Option, - #[napi(js_name = "alarmUrgentHighMins")] #[serde( default, rename = "alarmUrgentHighMins", @@ -137,11 +128,9 @@ pub struct StatusSettings { )] pub alarm_urgent_high_mins: Option>, - #[napi(js_name = "alarmHigh")] #[serde(default, rename = "alarmHigh", skip_serializing_if = "Option::is_none")] pub alarm_high: Option, - #[napi(js_name = "alarmHighMins")] #[serde( default, rename = "alarmHighMins", @@ -149,11 +138,9 @@ pub struct StatusSettings { )] pub alarm_high_mins: Option>, - #[napi(js_name = "alarmLow")] #[serde(default, rename = "alarmLow", skip_serializing_if = "Option::is_none")] pub alarm_low: Option, - #[napi(js_name = "alarmLowMins")] #[serde( default, rename = "alarmLowMins", @@ -161,7 +148,6 @@ pub struct StatusSettings { )] pub alarm_low_mins: Option>, - #[napi(js_name = "alarmUrgentLow")] #[serde( default, rename = "alarmUrgentLow", @@ -169,7 +155,6 @@ pub struct StatusSettings { )] pub alarm_urgent_low: Option, - #[napi(js_name = "alarmUrgentLowMins")] #[serde( default, rename = "alarmUrgentLowMins", @@ -177,7 +162,6 @@ pub struct StatusSettings { )] pub alarm_urgent_low_mins: Option>, - #[napi(js_name = "alarmUrgentMins")] #[serde( default, rename = "alarmUrgentMins", @@ -185,7 +169,6 @@ pub struct StatusSettings { )] pub alarm_urgent_mins: Option>, - #[napi(js_name = "alarmWarnMins")] #[serde( default, rename = "alarmWarnMins", @@ -193,7 +176,6 @@ pub struct StatusSettings { )] pub alarm_warn_mins: Option>, - #[napi(js_name = "alarmTimeagoWarn")] #[serde( default, rename = "alarmTimeagoWarn", @@ -201,7 +183,6 @@ pub struct StatusSettings { )] pub alarm_timeago_warn: Option, - #[napi(js_name = "alarmTimeagoWarnMins")] #[serde( default, rename = "alarmTimeagoWarnMins", @@ -209,7 +190,6 @@ pub struct StatusSettings { )] pub alarm_timeago_warn_mins: Option, - #[napi(js_name = "alarmTimeagoUrgent")] #[serde( default, rename = "alarmTimeagoUrgent", @@ -217,7 +197,6 @@ pub struct StatusSettings { )] pub alarm_timeago_urgent: Option, - #[napi(js_name = "alarmTimeagoUrgentMins")] #[serde( default, rename = "alarmTimeagoUrgentMins", @@ -225,7 +204,6 @@ pub struct StatusSettings { )] pub alarm_timeago_urgent_mins: Option, - #[napi(js_name = "alarmPumpBatteryLow")] #[serde( default, rename = "alarmPumpBatteryLow", @@ -233,11 +211,9 @@ pub struct StatusSettings { )] pub alarm_pump_battery_low: Option, - #[napi(js_name = "baseURL")] #[serde(default, rename = "baseURL", skip_serializing_if = "Option::is_none")] pub base_url: Option, - #[napi(js_name = "authDefaultRoles")] #[serde( default, rename = "authDefaultRoles", @@ -248,11 +224,9 @@ pub struct StatusSettings { #[serde(default, skip_serializing_if = "Option::is_none")] pub language: Option, - #[napi(js_name = "scaleY")] #[serde(default, rename = "scaleY", skip_serializing_if = "Option::is_none")] pub scale_y: Option, - #[napi(js_name = "showPlugins")] #[serde( default, rename = "showPlugins", @@ -260,7 +234,6 @@ pub struct StatusSettings { )] pub show_plugins: Option, - #[napi(js_name = "showForecast")] #[serde( default, rename = "showForecast", @@ -268,7 +241,6 @@ pub struct StatusSettings { )] pub show_forecast: Option, - #[napi(js_name = "focusHours")] #[serde( default, rename = "focusHours", @@ -279,7 +251,6 @@ pub struct StatusSettings { #[serde(default, skip_serializing_if = "Option::is_none")] pub heartbeat: Option, - #[napi(js_name = "DEFAULT_FEATURES")] #[serde( default, rename = "DEFAULT_FEATURES", @@ -293,7 +264,6 @@ pub struct StatusSettings { #[serde(default, skip_serializing_if = "Option::is_none")] pub thresholds: Option, - #[napi(js_name = "alarmTypes")] #[serde( default, rename = "alarmTypes", @@ -301,7 +271,6 @@ pub struct StatusSettings { )] pub alarm_types: Option>, - #[napi(js_name = "insecureUseHttp")] #[serde( default, rename = "insecureUseHttp", @@ -309,7 +278,6 @@ pub struct StatusSettings { )] pub insecure_use_http: Option, - #[napi(js_name = "secureHstsHeader")] #[serde( default, rename = "secureHstsHeader", @@ -317,7 +285,6 @@ pub struct StatusSettings { )] pub secure_hsts_header: Option, - #[napi(js_name = "secureHstsHeaderIncludeSubdomains")] #[serde( default, rename = "secureHstsHeaderIncludeSubdomains", @@ -325,7 +292,6 @@ pub struct StatusSettings { )] pub secure_hsts_header_include_subdomains: Option, - #[napi(js_name = "secureHstsHeaderPreload")] #[serde( default, rename = "secureHstsHeaderPreload", @@ -333,11 +299,9 @@ pub struct StatusSettings { )] pub secure_hsts_header_preload: Option, - #[napi(js_name = "secureCsp")] #[serde(default, rename = "secureCsp", skip_serializing_if = "Option::is_none")] pub secure_csp: Option, - #[napi(js_name = "deNormalizeDates")] #[serde( default, rename = "deNormalizeDates", @@ -345,7 +309,6 @@ pub struct StatusSettings { )] pub de_normalize_dates: Option, - #[napi(js_name = "showClockDelta")] #[serde( default, rename = "showClockDelta", @@ -353,7 +316,6 @@ pub struct StatusSettings { )] pub show_clock_delta: Option, - #[napi(js_name = "showClockLastTime")] #[serde( default, rename = "showClockLastTime", @@ -361,39 +323,30 @@ pub struct StatusSettings { )] pub show_clock_last_time: Option, - #[napi(js_name = "frameUrl1")] #[serde(default, rename = "frameUrl1", skip_serializing_if = "Option::is_none")] pub frame_url_1: Option, - #[napi(js_name = "frameUrl2")] #[serde(default, rename = "frameUrl2", skip_serializing_if = "Option::is_none")] pub frame_url_2: Option, - #[napi(js_name = "frameUrl3")] #[serde(default, rename = "frameUrl3", skip_serializing_if = "Option::is_none")] pub frame_url_3: Option, - #[napi(js_name = "frameUrl4")] #[serde(default, rename = "frameUrl4", skip_serializing_if = "Option::is_none")] pub frame_url_4: Option, - #[napi(js_name = "frameUrl5")] #[serde(default, rename = "frameUrl5", skip_serializing_if = "Option::is_none")] pub frame_url_5: Option, - #[napi(js_name = "frameUrl6")] #[serde(default, rename = "frameUrl6", skip_serializing_if = "Option::is_none")] pub frame_url_6: Option, - #[napi(js_name = "frameUrl7")] #[serde(default, rename = "frameUrl7", skip_serializing_if = "Option::is_none")] pub frame_url_7: Option, - #[napi(js_name = "frameUrl8")] #[serde(default, rename = "frameUrl8", skip_serializing_if = "Option::is_none")] pub frame_url_8: Option, - #[napi(js_name = "frameName1")] #[serde( default, rename = "frameName1", @@ -401,7 +354,6 @@ pub struct StatusSettings { )] pub frame_name_1: Option, - #[napi(js_name = "frameName2")] #[serde( default, rename = "frameName2", @@ -409,7 +361,6 @@ pub struct StatusSettings { )] pub frame_name_2: Option, - #[napi(js_name = "frameName3")] #[serde( default, rename = "frameName3", @@ -417,7 +368,6 @@ pub struct StatusSettings { )] pub frame_name_3: Option, - #[napi(js_name = "frameName4")] #[serde( default, rename = "frameName4", @@ -425,7 +375,6 @@ pub struct StatusSettings { )] pub frame_name_4: Option, - #[napi(js_name = "frameName5")] #[serde( default, rename = "frameName5", @@ -433,7 +382,6 @@ pub struct StatusSettings { )] pub frame_name_5: Option, - #[napi(js_name = "frameName6")] #[serde( default, rename = "frameName6", @@ -441,7 +389,6 @@ pub struct StatusSettings { )] pub frame_name_6: Option, - #[napi(js_name = "frameName7")] #[serde( default, rename = "frameName7", @@ -449,7 +396,6 @@ pub struct StatusSettings { )] pub frame_name_7: Option, - #[napi(js_name = "frameName8")] #[serde( default, rename = "frameName8", @@ -457,7 +403,6 @@ pub struct StatusSettings { )] pub frame_name_8: Option, - #[napi(js_name = "authFailDelay")] #[serde( default, rename = "authFailDelay", @@ -465,7 +410,6 @@ pub struct StatusSettings { )] pub auth_fail_delay: Option, - #[napi(js_name = "adminNotifiesEnabled")] #[serde( default, rename = "adminNotifiesEnabled", @@ -473,7 +417,6 @@ pub struct StatusSettings { )] pub admin_notifies_enabled: Option, - #[napi(js_name = "authenticationPromptOnLoad")] #[serde( default, rename = "authenticationPromptOnLoad", @@ -486,13 +429,10 @@ pub struct StatusSettings { } #[derive(Debug, Serialize, Deserialize, Clone)] -#[napi(object)] pub struct StatusThresholds { - #[napi(js_name = "bgHigh")] #[serde(default, rename = "bgHigh", skip_serializing_if = "Option::is_none")] pub bg_high: Option, - #[napi(js_name = "bgTargetTop")] #[serde( default, rename = "bgTargetTop", @@ -500,7 +440,6 @@ pub struct StatusThresholds { )] pub bg_target_top: Option, - #[napi(js_name = "bgTargetBottom")] #[serde( default, rename = "bgTargetBottom", @@ -508,7 +447,6 @@ pub struct StatusThresholds { )] pub bg_target_bottom: Option, - #[napi(js_name = "bgLow")] #[serde(default, rename = "bgLow", skip_serializing_if = "Option::is_none")] pub bg_low: Option, @@ -517,7 +455,6 @@ pub struct StatusThresholds { } #[derive(Debug, Serialize, Deserialize, Clone)] -#[napi(object)] pub struct ExtendedSettings { #[serde(default, skip_serializing_if = "Option::is_none")] pub devicestatus: Option, @@ -527,7 +464,6 @@ pub struct ExtendedSettings { } #[derive(Debug, Serialize, Deserialize, Clone)] -#[napi(object)] pub struct ExtendedDeviceStatusSettings { #[serde(default, skip_serializing_if = "Option::is_none")] pub advanced: Option, diff --git a/src/models/treatments.rs b/src/models/treatments.rs index 274a5c0..8a92bbb 100644 --- a/src/models/treatments.rs +++ b/src/models/treatments.rs @@ -5,7 +5,6 @@ use crate::client::NightscoutClient; use crate::endpoints::Endpoint; use crate::error::NightscoutError; use crate::query_builder::{HasDevice, QueryBuilder}; -use napi_derive::napi; #[derive(Debug, Deserialize)] pub struct IobWrapper { @@ -23,24 +22,20 @@ pub struct IobData { /// Treatment /// Represents a care event (bolus, carb correction, temp basal, etc.) -#[napi(object)] #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Treatment { #[serde(rename = "_id", skip_serializing_if = "Option::is_none")] pub id: Option, - #[napi(js_name = "eventType")] #[serde(rename = "eventType")] pub event_type: String, - #[napi(js_name = "createdAt")] #[serde(rename = "created_at")] pub created_at: String, #[serde(skip_serializing_if = "Option::is_none")] pub glucose: Option, - #[napi(js_name = "glucoseType")] #[serde(rename = "glucoseType", skip_serializing_if = "Option::is_none")] pub glucose_type: Option, @@ -56,7 +51,6 @@ pub struct Treatment { #[serde(skip_serializing_if = "Option::is_none")] pub notes: Option, - #[napi(js_name = "enteredBy")] #[serde(rename = "enteredBy", skip_serializing_if = "Option::is_none")] pub entered_by: Option, } @@ -72,17 +66,37 @@ pub struct TreatmentsService { } impl TreatmentsService { - /// Returns a query builder to list treatments - pub fn list(&self) -> QueryBuilder { + /// Initiates a query for Treatments entries. + /// + /// This returns a `QueryBuilder`. You can chain methods like `.limit()`, `.from()`, and `.to()` + /// before calling `.send()` to execute the request. + /// + /// # Example + /// + /// ```rust,no_run + /// # use cinnamon::client::NightscoutClient; + /// # async fn run() -> Result<(), Box> { + /// let client = NightscoutClient::new("https://ns.example.com")?; + /// let entries = client.treatments() + /// .get() + /// .limit(10) + /// .send() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub fn get(&self) -> QueryBuilder { QueryBuilder::::new(self.client.clone(), Endpoint::Treatments, Method::GET) } - /// Returns a query builder to delete treatments + /// Initiates a delete request for Treatments entries. + /// + /// Use the builder to specify which entries to delete (e.g. by ID or date range). pub fn delete(&self) -> QueryBuilder { QueryBuilder::::new(self.client.clone(), Endpoint::Treatments, Method::DELETE) } - /// Creates new treatments + /// Uploads new Treatments entries to Nightscout. pub async fn create( &self, treatments: Vec, diff --git a/src/models/trends.rs b/src/models/trends.rs index 29497dd..65291b9 100644 --- a/src/models/trends.rs +++ b/src/models/trends.rs @@ -1,8 +1,6 @@ -use napi_derive::napi; use serde::{Deserialize, Serialize}; use std::fmt; -#[napi(string_enum)] #[derive(Serialize, Deserialize, Debug, Clone, Copy)] pub enum Trend { DoubleUp, diff --git a/src/node_bindings.rs b/src/node_bindings.rs deleted file mode 100644 index 695b559..0000000 --- a/src/node_bindings.rs +++ /dev/null @@ -1,453 +0,0 @@ -use crate::client::NightscoutClient; -use crate::models::devicestatus::DeviceStatus; -use crate::models::entries::{MbgEntry, SgvEntry}; -use crate::models::profile::{ProfileService, ProfileSet}; -use crate::models::properties::{Properties, PropertiesService, PropertyType}; -use crate::models::status::Status; -use crate::models::treatments::Treatment; -use crate::query_builder::Device; - -use chrono::{DateTime, Utc}; -use napi::bindgen_prelude::*; -use napi_derive::napi; - -// ----------------------------- -// Client - -#[napi] -pub struct Cinnamon { - client: NightscoutClient, -} - -#[napi(string_enum)] -pub enum DeviceType { - Auto, - All, - Custom, -} - -#[napi] -impl Cinnamon { - #[napi(constructor)] - pub fn new(url: String, api_secret: Option) -> Result { - let client = NightscoutClient::new(&url, api_secret) - .map_err(|e| Error::from_reason(e.to_string()))?; - Ok(Cinnamon { client }) - } - - #[napi] - pub fn sgv(&self) -> JsSgvQuery { - JsSgvQuery { - client: self.client.clone(), - limit: 10, - from: None, - to: None, - device: Device::All, - } - } - - #[napi] - pub fn mbg(&self) -> JsMbgQuery { - JsMbgQuery { - client: self.client.clone(), - limit: 10, - from: None, - to: None, - device: Device::All, - } - } - - #[napi] - pub fn treatments(&self) -> JsTreatmentQuery { - JsTreatmentQuery { - client: self.client.clone(), - limit: 10, - from: None, - to: None, - device: Device::All, - } - } - - #[napi] - pub fn device_status(&self) -> JsDeviceStatusQuery { - JsDeviceStatusQuery { - client: self.client.clone(), - limit: 10, - from: None, - to: None, - device: Device::All, - } - } - - #[napi] - pub fn profile(&self) -> JsProfileQuery { - JsProfileQuery { - client: self.client.clone(), - } - } - - #[napi] - pub fn properties(&self) -> JsPropertiesQuery { - JsPropertiesQuery { - client: self.client.clone(), - enabled: vec![], - at: None, - } - } - - #[napi] - pub fn status(&self) -> JsStatusQuery { - JsStatusQuery { - client: self.client.clone(), - } - } -} - -// ----------------------------- -// Status - -#[napi] -pub struct JsStatusQuery { - client: NightscoutClient, -} - -#[napi] -impl JsStatusQuery { - #[napi] - pub async fn fetch(&self) -> Result { - self.client - .status() - .get() - .await - .map_err(|e| Error::from_reason(e.to_string())) - } -} - -// ----------------------------- -// SGV - -#[napi] -pub struct JsSgvQuery { - client: NightscoutClient, - limit: usize, - from: Option>, - to: Option>, - device: Device, -} - -#[napi] -impl JsSgvQuery { - #[napi] - pub fn limit(&mut self, count: u32) -> &Self { - self.limit = count as usize; - self - } - - #[napi] - pub fn from_date(&mut self, date: DateTime) -> &Self { - self.from = Some(date); - self - } - - #[napi] - pub fn to_date(&mut self, date: DateTime) -> &Self { - self.to = Some(date); - self - } - - #[napi] - pub fn filter_device(&mut self, mode: DeviceType, name: Option) -> &Self { - self.device = match mode { - DeviceType::Auto => Device::Auto, - DeviceType::Custom => Device::Custom(name.unwrap_or_default()), - DeviceType::All => Device::All, - }; - self - } - - #[napi] - pub async fn fetch(&self) -> Result> { - let mut builder = self.client.entries().sgv().list(); - builder = builder.limit(self.limit); - builder = builder.device(self.device.clone()); - if let Some(f) = self.from { - builder = builder.from(f); - } - if let Some(t) = self.to { - builder = builder.to(t); - } - - builder.await.map_err(|e| Error::from_reason(e.to_string())) - } -} - -// ----------------------------- -// MBG - -#[napi] -pub struct JsMbgQuery { - client: NightscoutClient, - limit: usize, - from: Option>, - to: Option>, - device: Device, -} - -#[napi] -impl JsMbgQuery { - #[napi] - pub fn limit(&mut self, count: u32) -> &Self { - self.limit = count as usize; - self - } - - #[napi] - pub fn from_date(&mut self, date: DateTime) -> &Self { - self.from = Some(date); - self - } - - #[napi] - pub fn to_date(&mut self, date: DateTime) -> &Self { - self.to = Some(date); - self - } - - #[napi] - pub fn filter_device(&mut self, mode: DeviceType, name: Option) -> &Self { - self.device = match mode { - DeviceType::Auto => Device::Auto, - DeviceType::Custom => Device::Custom(name.unwrap_or_default()), - DeviceType::All => Device::All, - }; - self - } - - #[napi] - pub async fn fetch(&self) -> Result> { - let mut builder = self.client.entries().mbg().list(); - builder = builder.limit(self.limit); - builder = builder.device(self.device.clone()); - if let Some(f) = self.from { - builder = builder.from(f); - } - if let Some(t) = self.to { - builder = builder.to(t); - } - - builder.await.map_err(|e| Error::from_reason(e.to_string())) - } -} - -// ----------------------------- -// Treatments - -#[napi] -pub struct JsTreatmentQuery { - client: NightscoutClient, - limit: usize, - from: Option>, - to: Option>, - device: Device, -} - -#[napi] -impl JsTreatmentQuery { - #[napi] - pub fn limit(&mut self, count: u32) -> &Self { - self.limit = count as usize; - self - } - - #[napi] - pub fn from_date(&mut self, date: DateTime) -> &Self { - self.from = Some(date); - self - } - - #[napi] - pub fn to_date(&mut self, date: DateTime) -> &Self { - self.to = Some(date); - self - } - - #[napi] - pub fn filter_device(&mut self, mode: DeviceType, name: Option) -> &Self { - self.device = match mode { - DeviceType::Auto => Device::Auto, - DeviceType::Custom => Device::Custom(name.unwrap_or_default()), - DeviceType::All => Device::All, - }; - self - } - - #[napi] - pub async fn fetch(&self) -> Result> { - let mut builder = self.client.treatments().list(); - builder = builder.limit(self.limit); - builder = builder.device(self.device.clone()); - if let Some(f) = self.from { - builder = builder.from(f); - } - if let Some(t) = self.to { - builder = builder.to(t); - } - - builder.await.map_err(|e| Error::from_reason(e.to_string())) - } -} - -// ----------------------------- -// DeviceStatus - -#[napi] -pub struct JsDeviceStatusQuery { - client: NightscoutClient, - limit: usize, - from: Option>, - to: Option>, - device: Device, -} - -#[napi] -impl JsDeviceStatusQuery { - #[napi] - pub fn limit(&mut self, count: u32) -> &Self { - self.limit = count as usize; - self - } - - #[napi] - pub fn from_date(&mut self, date: DateTime) -> &Self { - self.from = Some(date); - self - } - - #[napi] - pub fn to_date(&mut self, date: DateTime) -> &Self { - self.to = Some(date); - self - } - - #[napi] - pub fn filter_device(&mut self, mode: DeviceType, name: Option) -> &Self { - self.device = match mode { - DeviceType::Auto => Device::Auto, - DeviceType::Custom => Device::Custom(name.unwrap_or_default()), - DeviceType::All => Device::All, - }; - self - } - - #[napi] - pub async fn fetch(&self) -> Result> { - let mut builder = self.client.devicestatus().list(); - builder = builder.limit(self.limit); - builder = builder.device(self.device.clone()); - if let Some(f) = self.from { - builder = builder.from(f); - } - if let Some(t) = self.to { - builder = builder.to(t); - } - - builder.await.map_err(|e| Error::from_reason(e.to_string())) - } -} - -// ----------------------------- -// Profile - -#[napi] -pub struct JsProfileQuery { - client: NightscoutClient, -} - -#[napi] -impl JsProfileQuery { - #[napi] - pub async fn fetch(&self) -> Result> { - let service = ProfileService { - client: self.client.clone(), - }; - - service - .get() - .await - .map_err(|e| Error::from_reason(e.to_string())) - } -} - -// ----------------------------- -// Properties - -#[napi] -pub struct JsPropertiesQuery { - client: NightscoutClient, - enabled: Vec, - at: Option>, -} - -#[napi] -impl JsPropertiesQuery { - #[napi] - pub fn enable(&mut self, properties: Vec) -> &Self { - self.enabled = properties; - self - } - - #[napi] - pub fn at_date(&mut self, date: DateTime) -> &Self { - self.at = Some(date); - self - } - - #[napi] - pub async fn fetch(&self) -> Result { - let service = PropertiesService { - client: self.client.clone(), - }; - let mut request = service.get(); - - if let Some(date) = self.at { - request = request.at(date); - } - - if !self.enabled.is_empty() { - let props: Vec = self - .enabled - .iter() - .map(|s| match s.to_lowercase().as_str() { - "iob" => PropertyType::Iob, - "cob" => PropertyType::Cob, - "pump" => PropertyType::Pump, - "basal" => PropertyType::Basal, - "profile" => PropertyType::Profile, - "bage" => PropertyType::Bage, - "cage" => PropertyType::Cage, - "iage" => PropertyType::Iage, - "sage" => PropertyType::Sage, - "upbat" => PropertyType::Upbat, - "rawbg" => PropertyType::Rawbg, - "delta" => PropertyType::Delta, - "direction" => PropertyType::Direction, - "ar2" => PropertyType::Ar2, - "devicestatus" => PropertyType::Devicestatus, - "openaps" => PropertyType::Openaps, - "loop" => PropertyType::Loop, - "bgnow" => PropertyType::BgNow, - "buckets" => PropertyType::Buckets, - "dbsize" => PropertyType::DbSize, - "runtimestate" => PropertyType::RuntimeState, - _ => PropertyType::Custom(s.to_string()), - }) - .collect(); - request = request.only(&props); - } - - request - .send() - .await - .map_err(|e| Error::from_reason(e.to_string())) - } -} diff --git a/src/query_builder.rs b/src/query_builder.rs index b250390..978057c 100644 --- a/src/query_builder.rs +++ b/src/query_builder.rs @@ -1,25 +1,27 @@ -use crate::error::NightscoutError; - use super::client::NightscoutClient; use crate::endpoints::Endpoint; +use crate::error::NightscoutError; -use std::future::{Future, IntoFuture}; use std::marker::PhantomData; -use std::pin::Pin; -use anyhow::Result; use chrono::{DateTime, Utc}; use futures::stream::{self, StreamExt}; use reqwest::Method; use serde::de::DeserializeOwned; #[derive(Clone, Debug, PartialEq)] +/// Specifies target device filtering behavior. pub enum Device { + /// Automatically attempts to determine the primary device name from recent data. + /// Performs an extra HTTP request (pre-flight) to find the device name. Auto, + /// Fetches data from all devices. All, + /// Fetches data only from a specific device name (e.g., "bubble"). Custom(String), } +/// Trait for models that contain a device name field. pub trait HasDevice { fn device(&self) -> Option<&str>; } @@ -51,154 +53,154 @@ impl QueryBuilder { } } + /// Filters results to entries occurring on or after this date. pub fn from(mut self, date: DateTime) -> Self { self.from_date = Some(date); self } + /// Filters results to entries occurring on or before this date. pub fn to(mut self, date: DateTime) -> Self { self.to_date = Some(date); self } + /// Limits the number of results returned. Default is 10. pub fn limit(mut self, count: usize) -> Self { self.count = count; self } + /// targets a specific resource ID. + /// + /// When used with `GET`, this fetches a single item. + /// When used with `DELETE`, this deletes a single item. pub fn id(mut self, id: impl Into) -> Self { self.id = Some(id.into()); self } + /// Filters results by device name. pub fn device(mut self, device: Device) -> Self { self.device = device; self } } -impl IntoFuture for QueryBuilder +impl QueryBuilder where T: DeserializeOwned + Send + Sync + 'static + HasDevice, { - type Output = Result, NightscoutError>; - type IntoFuture = Pin + Send>>; - - fn into_future(self) -> Self::IntoFuture { - Box::pin(async move { - let path = if let Some(id) = &self.id { - format!("{}/{}", self.endpoint.as_path(), id) - } else { - self.endpoint.as_path().to_string() - }; - - let mut url = self.client.base_url.join(&path)?; - - { - let mut query = url.query_pairs_mut(); - - if self.id.is_none() { - query.append_pair("count", &self.count.to_string()); - + /// Executes the built query. + /// + /// This method sends the HTTP request to Nightscout constructed by the builder methods. + pub async fn send(self) -> Result, NightscoutError> { + // For Device::Auto, it is needed to do a pre-flight to determine which device to use. + // While it has performance impact, it's a good tradeoff if you do not know the device + // names on the server and only want data from one device. + let resolved_device_name: Option = match &self.device { + Device::Custom(name) => Some(name.clone()), + Device::Auto => { + let mut probe_url = self.client.base_url.join(self.endpoint.as_path())?; + { + let mut query = probe_url.query_pairs_mut(); + query.append_pair("count", "1"); + + // We still need to access the data at the interval which the user wants us to get data + // if we didn't the device name could be (and probably will be) total wrong. if let Some(from) = self.from_date { query.append_pair("find[dateString][$gte]", &from.to_rfc3339()); } - if let Some(to) = self.to_date { query.append_pair("find[dateString][$lte]", &to.to_rfc3339()); } } + let probe_result: Result, _> = self.client.fetch(probe_url).await; + + match probe_result { + Ok(items) => items + .first() + .and_then(|item| item.device()) + .map(|s| s.to_string()), + Err(_) => None, + } } + Device::All => None, + }; - match self.method { - Method::GET => { - let mut request = self.client.http.get(url); - request = self.client.auth(request); - - let response = self.client.send_checked(request).await?; - let items = response.json::>().await?; - - match &self.device { - Device::All => Ok(items), - Device::Custom(name) => { - let filtered: Vec = items - .into_iter() - .filter(|item| item.device() == Some(name.as_str())) - .collect(); - Ok(filtered) - } - Device::Auto => { - let target_device = items - .iter() - .find_map(|item| item.device()) - .map(|s| s.to_string()); - - if let Some(device_name) = target_device { - let filtered: Vec = items - .into_iter() - .filter(|item| item.device() == Some(device_name.as_str())) - .collect(); - Ok(filtered) - } else { - Ok(items) - } - } - } + let path = if let Some(id) = &self.id { + format!("{}/{}", self.endpoint.as_path(), id) + } else { + self.endpoint.as_path().to_string() + }; + + let mut url = self.client.base_url.join(&path)?; + + { + let mut query = url.query_pairs_mut(); + + if self.id.is_none() { + query.append_pair("count", &self.count.to_string()); + + if let Some(from) = self.from_date { + query.append_pair("find[dateString][$gte]", &from.to_rfc3339()); } - Method::DELETE => { - if self.id.is_some() { - // Single ID delete logic - let mut get_req = self.client.http.get(url.clone()); - get_req = self.client.auth(get_req); - let item = self - .client - .send_checked(get_req) - .await? - .json::>() - .await?; - - let mut del_req = self.client.http.delete(url); - del_req = self.client.auth(del_req); - self.client.send_checked(del_req).await?; - - Ok(item) - } else { - let mut get_request = self.client.http.get(url.clone()); - get_request = self.client.auth(get_request); - - let get_response = self.client.send_checked(get_request).await?; - let items: Vec = get_response.json().await?; - - let delete_urls: Vec = items - .iter() - .filter_map(|item| { - let id = item.get("_id")?.as_str()?; - let delete_path = format!("{}/{}", self.endpoint.as_path(), id); - self.client.base_url.join(&delete_path).ok() - }) - .collect(); - - let delete_tasks = delete_urls.into_iter().map(|url| { - let client = self.client.clone(); - async move { - let mut req = client.http.delete(url); - req = client.auth(req); - client.send_checked(req).await - } - }); - - stream::iter(delete_tasks) - .buffer_unordered(10) - .collect::>() - .await; - - let t_items: Vec = - serde_json::from_value(serde_json::Value::Array(items))?; - Ok(t_items) - } + + if let Some(to) = self.to_date { + query.append_pair("find[dateString][$lte]", &to.to_rfc3339()); + } + + if let Some(name) = &resolved_device_name { + query.append_pair("find[device]", name); } - _ => Err(NightscoutError::Unknown), } - }) + } + + match self.method { + Method::GET => { + let items: Vec = self.client.fetch(url).await?; + Ok(items) + } + Method::DELETE => { + if self.id.is_some() { + let item: Vec = self.client.fetch(url.clone()).await?; + + let mut del_req = self.client.http.delete(url); + del_req = self.client.auth(del_req); + self.client.send_checked(del_req).await?; + + Ok(item) + } else { + let items: Vec = self.client.fetch(url.clone()).await?; + + let delete_urls: Vec = items + .iter() + .filter_map(|item| { + let id = item.get("_id")?.as_str()?; + let delete_path = format!("{}/{}", self.endpoint.as_path(), id); + self.client.base_url.join(&delete_path).ok() + }) + .collect(); + + let delete_tasks = delete_urls.into_iter().map(|url| { + let client = self.client.clone(); + async move { + let mut req = client.http.delete(url); + req = client.auth(req); + client.send_checked(req).await + } + }); + + stream::iter(delete_tasks) + .buffer_unordered(10) + .collect::>() + .await; + + let t_items: Vec = serde_json::from_value(serde_json::Value::Array(items))?; + Ok(t_items) + } + } + _ => Err(NightscoutError::Unknown), + } } } diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs new file mode 100644 index 0000000..bb2f8ec --- /dev/null +++ b/tests/integration_tests.rs @@ -0,0 +1,344 @@ +use chrono::Utc; +use cinnamon::client::NightscoutClient; +use cinnamon::models::entries::SgvEntry; +use cinnamon::models::properties::PropertyType; +use cinnamon::models::treatments::Treatment; +use cinnamon::models::trends::Trend; +use cinnamon::query_builder::Device; +use serde_json::json; +use wiremock::matchers::{method, path, query_param}; +use wiremock::{Mock, MockServer, ResponseTemplate}; + +async fn get_client(mock_server: &MockServer) -> NightscoutClient { + NightscoutClient::new(&mock_server.uri()) + .expect("Failed to create client") + .with_secret("test-secret-123") +} + +#[tokio::test] +async fn test_profile_get() { + let mock_server = MockServer::start().await; + let client = get_client(&mock_server).await; + + let mock_profile = json!([{ + "_id": "653b6...", + "defaultProfile": "Default", + "startDate": "2023-01-01T00:00:00.000Z", + "created_at": "2023-01-01T00:00:00.000Z", + "store": { + "Default": { + "dia": 3.0, + "timezone": "UTC", + "units": "mg/dl", + "carbratio": [{"time": "00:00", "value": 10.0}], + "sens": [{"time": "00:00", "value": 30.0}], + "basal": [{"time": "00:00", "value": 1.5}], + "target_low": [{"time": "00:00", "value": 80.0}], + "target_high": [{"time": "00:00", "value": 120.0}] + } + } + }]); + + Mock::given(method("GET")) + .and(path("/api/v2/profile.json")) + .respond_with(ResponseTemplate::new(200).set_body_json(mock_profile)) + .mount(&mock_server) + .await; + + let profiles = client + .profiles() + .get() + .await + .expect("Failed to fetch profiles"); + assert!(!profiles.is_empty()); + assert_eq!(profiles[0].default_profile_name, "Default"); +} + +#[tokio::test] +async fn test_sgv_get_limit() { + let mock_server = MockServer::start().await; + let client = get_client(&mock_server).await; + + let mock_sgvs = json!([ + { + "_id": "1", + "sgv": 120, + "date": 1698393600000i64, + "dateString": "2023-10-27T10:00:00Z", + "direction": "Flat", + "type": "sgv", + "device": "xDrip" + } + ]); + + Mock::given(method("GET")) + .and(path("/api/v2/entries/sgv.json")) + .and(query_param("count", "5")) + .respond_with(ResponseTemplate::new(200).set_body_json(mock_sgvs)) + .mount(&mock_server) + .await; + + let result = client + .sgv() + .get() + .limit(5) + .send() + .await + .expect("Failed to get SGV"); + assert_eq!(result.len(), 1); + assert_eq!(result[0].sgv, 120); +} + +#[tokio::test] +async fn test_sgv_create() { + let mock_server = MockServer::start().await; + let client = get_client(&mock_server).await; + + let new_entry = SgvEntry::new(150, Trend::SingleUp, Utc::now()); + let entries_vec = vec![new_entry.clone()]; + + Mock::given(method("POST")) + .and(path("/api/v2/entries.json")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!([new_entry]))) + .mount(&mock_server) + .await; + + let created = client + .sgv() + .create(entries_vec) + .await + .expect("Failed to create SGV"); + assert_eq!(created[0].sgv, 150); +} + +#[tokio::test] +async fn test_sgv_delete_by_id() { + let mock_server = MockServer::start().await; + let client = get_client(&mock_server).await; + + let entry_id = "test-id-123"; + + Mock::given(method("GET")) + .and(path(format!("/api/v2/entries/sgv.json/{}", entry_id))) + .respond_with(ResponseTemplate::new(200).set_body_json(json!([{ "sgv": 100, "date": 0, "dateString": "", "direction": "Flat", "type": "sgv" }]))) + .mount(&mock_server) + .await; + + Mock::given(method("DELETE")) + .and(path(format!("/api/v2/entries/sgv.json/{}", entry_id))) + .respond_with(ResponseTemplate::new(200)) + .mount(&mock_server) + .await; + + let result = client.sgv().delete().id(entry_id).send().await; + assert!(result.is_ok()); +} + +#[tokio::test] +async fn test_treatments_create_and_read() { + let mock_server = MockServer::start().await; + let client = get_client(&mock_server).await; + + let mock_treatment = json!({ + "eventType": "Correction Bolus", + "created_at": "2023-10-27T10:00:00Z", + "insulin": 2.5, + "enteredBy": "TestRunner" + }); + + Mock::given(method("POST")) + .and(path("/api/v2/treatments.json")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!([mock_treatment]))) + .mount(&mock_server) + .await; + + let treatment_obj: Treatment = serde_json::from_value(mock_treatment.clone()).unwrap(); + let created = client + .treatments() + .create(vec![treatment_obj]) + .await + .expect("Failed to create treatment"); + assert_eq!(created[0].insulin, Some(2.5)); + + Mock::given(method("GET")) + .and(path("/api/v2/treatments.json")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!([mock_treatment]))) + .mount(&mock_server) + .await; + + let fetched = client + .treatments() + .get() + .send() + .await + .expect("Failed to get treatments"); + assert_eq!(fetched[0].event_type, "Correction Bolus"); +} + +#[tokio::test] +async fn test_properties_filter() { + let mock_server = MockServer::start().await; + let client = get_client(&mock_server).await; + + let mock_props = json!({ + "iob": { + "iob": 1.25, + "activity": 0.1, + "source": "openaps", + "display": "IOB: 1.25 U", + "displayLine": "1.25 U" + }, + "cob": { + "cob": 30.0, + "isDecaying": 1, + "decayedBy": "test", + "source": "openaps", + "display": {}, + "displayLine": "30g" + } + }); + + Mock::given(method("GET")) + .and(path("/api/v2/properties/iob,cob")) + .respond_with(ResponseTemplate::new(200).set_body_json(mock_props)) + .mount(&mock_server) + .await; + + let result = client + .properties() + .get() + .only(&[PropertyType::Iob, PropertyType::Cob]) + .send() + .await + .expect("Failed to fetch properties"); + + assert!(result.iob.is_some()); + assert!(result.cob.is_some()); + assert!(result.basal.is_none()); + assert_eq!(result.iob.unwrap().iob, 1.25); +} + +#[tokio::test] +async fn test_devicestatus_custom_device() { + let mock_server = MockServer::start().await; + let client = get_client(&mock_server).await; + + let mock_ds = json!([{ + "device": "MyPump", + "created_at": "2023-10-27T10:00:00Z", + "pump": { "battery": { "percent": 50 } } + }]); + + Mock::given(method("GET")) + .and(path("/api/v2/devicestatus.json")) + .and(query_param("find[device]", "MyPump")) + .respond_with(ResponseTemplate::new(200).set_body_json(mock_ds)) + .mount(&mock_server) + .await; + + let result = client + .devicestatus() + .get() + .device(Device::Custom("MyPump".to_string())) + .send() + .await + .expect("Failed to fetch devicestatus"); + + assert_eq!(result[0].device, Some("MyPump".to_string())); +} + +#[tokio::test] +async fn test_query_builder_auto_device() { + let mock_server = MockServer::start().await; + let client = get_client(&mock_server).await; + + let probe_response = json!([{ + "_id": "probe1", + "sgv": 100, + "date": 1000, + "dateString": "now", + "direction": "Flat", + "type": "sgv", + "device": "FoundDeviceName" + }]); + + Mock::given(method("GET")) + .and(path("/api/v2/entries/sgv.json")) + .and(query_param("count", "1")) + .respond_with(ResponseTemplate::new(200).set_body_json(probe_response)) + .up_to_n_times(1) + .mount(&mock_server) + .await; + + let actual_response = json!([ + { + "_id": "real1", + "sgv": 110, + "date": 2000, + "dateString": "later", + "direction": "Flat", + "type": "sgv", + "device": "FoundDeviceName" + }, + { + "_id": "real2", + "sgv": 115, + "date": 3000, + "dateString": "later2", + "direction": "Flat", + "type": "sgv", + "device": "FoundDeviceName" + } + ]); + + Mock::given(method("GET")) + .and(path("/api/v2/entries/sgv.json")) + .and(query_param("count", "10")) + .and(query_param("find[device]", "FoundDeviceName")) + .respond_with(ResponseTemplate::new(200).set_body_json(actual_response)) + .mount(&mock_server) + .await; + + let result = client + .sgv() + .get() + .device(Device::Auto) + .limit(10) + .send() + .await + .expect("Auto device fetch failed"); + + assert_eq!(result.len(), 2); + assert_eq!(result[0].device.as_deref(), Some("FoundDeviceName")); +} + +#[tokio::test] +async fn test_mbg_latest() { + let mock_server = MockServer::start().await; + let client = get_client(&mock_server).await; + + let mock_mbg = json!([ + { + "_id": "m1", + "mbg": 105, + "date": 1000, + "dateString": "now", + "type": "mbg", + "device": "Contour" + } + ]); + + Mock::given(method("GET")) + .and(path("/api/v2/entries/mbg.json")) + .and(query_param("count", "1")) + .respond_with(ResponseTemplate::new(200).set_body_json(mock_mbg)) + .mount(&mock_server) + .await; + + let entry = client + .mbg() + .latest() + .await + .expect("Failed to fetch latest MBG"); + assert_eq!(entry.mbg, 105); +}