diff --git a/.github/workflows/ci-absurd-sqlite-extension.yml b/.github/workflows/ci-absurd-sqlite-extension.yml new file mode 100644 index 0000000..973c6a2 --- /dev/null +++ b/.github/workflows/ci-absurd-sqlite-extension.yml @@ -0,0 +1,31 @@ +name: Absurd SQLite Extension CI + +on: + pull_request: + paths: + - absurd-sqlite-extension/** + - Cargo.toml + - Cargo.lock + - .github/workflows/ci-absurd-sqlite-extension.yml + +jobs: + lint-format-test: + name: Lint, Format, Test + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + + - name: Format check + run: cargo fmt -p absurd_sqlite_extension -- --check + + - name: Lint + run: cargo clippy -p absurd_sqlite_extension -- -D warnings + + - name: Unit tests + run: cargo test -p absurd_sqlite_extension diff --git a/.github/workflows/ci-standalone.yml b/.github/workflows/ci-standalone.yml new file mode 100644 index 0000000..fa68b00 --- /dev/null +++ b/.github/workflows/ci-standalone.yml @@ -0,0 +1,57 @@ +name: Standalone App CI + +on: + pull_request: + paths: + - standalone/** + - Cargo.toml + - Cargo.lock + - .github/workflows/ci-standalone.yml + +jobs: + lint-format-test: + name: Lint, Format, Test + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Bun + uses: oven-sh/setup-bun@v1 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + + - name: Install Linux dependencies + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y \ + libgtk-3-dev \ + libayatana-appindicator3-dev \ + librsvg2-dev \ + libwebkit2gtk-4.1-dev \ + libglib2.0-dev \ + pkg-config + + - name: Build SQLite extension + run: cargo build -p absurd_sqlite_extension + + - name: Frontend install + working-directory: standalone + run: bun install --frozen-lockfile + + - name: Frontend check + working-directory: standalone + run: bun check + + - name: Backend format check + run: cargo fmt -p AbsurdSQLite -- --check + + - name: Backend lint + run: cargo clippy -p AbsurdSQLite -- -D warnings + + - name: Backend tests + run: cargo test -p AbsurdSQLite diff --git a/.github/workflows/release-extension.yml b/.github/workflows/release-absurd-sqlite-extension.yml similarity index 100% rename from .github/workflows/release-extension.yml rename to .github/workflows/release-absurd-sqlite-extension.yml diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..22f46de --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,33 @@ +# Absurd-SQLite + +Absurd-SQLite is a SQLite based durable execution engine designed for personal / homelab / edge computing environments. + +This project is based on the PostgreSQL implementation from [absurd](https://github.com/earendil-works/absurd). + +## Key Concepts + +A task is subdivided into steps that act as checkpoints. Tasks can suspend (for sleep or events) and resume without data loss. +State data is stored in the SQLite tables below: + +- Queues: `absurd_queues` scopes all durable execution by `queue_name`. +- Tasks: `absurd_tasks` stores task definitions, parameters, headers, retry/cancel info, idempotency keys, and task state. +- Runs: `absurd_runs` represents per-attempt execution for a task with claim/lease timing, availability, and results/failures. +- Checkpoints: `absurd_checkpoints` tracks named task checkpoints with optional state and run ownership. +- Events: `absurd_events` stores emitted events with JSON payloads scoped by queue and event name. +- Waits: `absurd_waits` records task steps waiting on events or timeouts. + +To interact with the system, a SQLite extension is provided and exposed various SQL functions. + +## Repository Structure + +- `absurd-sqlite-extension`: Core Rust crate providing the SQLite extension, engine logic, and schema migrations. +- `standalone`: Tauri + SvelteKit desktop app. +- `sdks`: Client SDKs for different languages. +- `samples`: Example applications demonstrating usage. + +## Development + +General rule: please make sure to lint, format, and test code after making changes. No need to ask for human confirmation +before running linters / formatters / tests. + +Please follow the sub-project README.md / AGENTS.md for detailed instructions. diff --git a/Cargo.lock b/Cargo.lock index b6da688..5b7c639 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,33 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "AbsurdSQLite" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum", + "chrono", + "log", + "rusqlite", + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-cli", + "tauri-plugin-fs", + "tauri-plugin-log", + "tauri-plugin-opener", + "tauri-plugin-shell", + "tauri-plugin-store", + "tokio", + "tower-http 0.5.2", +] + +[[package]] +name = "absurd-sqlite-deno-runtime" +version = "0.1.0" + [[package]] name = "absurd_sqlite_extension" version = "0.1.0" @@ -14,6 +41,23 @@ dependencies = [ "uuid", ] +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "version_check", +] + [[package]] name = "aho-corasick" version = "1.1.4" @@ -24,874 +68,1131 @@ dependencies = [ ] [[package]] -name = "atty" -version = "0.2.14" +name = "alloc-no-stdlib" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" dependencies = [ - "hermit-abi", - "libc", - "winapi", + "alloc-no-stdlib", ] [[package]] -name = "autocfg" -version = "1.5.0" +name = "android_log-sys" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d" [[package]] -name = "bindgen" -version = "0.60.1" +name = "android_logger" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "062dddbc1ba4aca46de6338e2bf87771414c335f7b2f2036e8f3e9befebf88e6" +checksum = "dbb4e440d04be07da1f1bf44fb4495ebd58669372fe0cffa6e48595ac5bd88a3" dependencies = [ - "bitflags 1.3.2", - "cexpr", - "clang-sys", - "clap", - "env_logger", - "lazy_static", - "lazycell", + "android_log-sys", + "env_filter", "log", - "peeking_take_while", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "which", ] [[package]] -name = "bitflags" -version = "1.3.2" +name = "android_system_properties" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] [[package]] -name = "bitflags" -version = "2.10.0" +name = "anstream" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] [[package]] -name = "bumpalo" -version = "3.19.1" +name = "anstyle" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] -name = "cc" -version = "1.2.50" +name = "anstyle-parse" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ - "find-msvc-tools", - "shlex", + "utf8parse", ] [[package]] -name = "cexpr" -version = "0.6.0" +name = "anstyle-query" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "nom", + "windows-sys 0.61.2", ] [[package]] -name = "cfg-if" -version = "1.0.4" +name = "anstyle-wincon" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] [[package]] -name = "chrono" -version = "0.4.42" +name = "anyhow" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" -dependencies = [ - "num-traits", -] +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] -name = "clang-sys" -version = "1.8.1" +name = "arrayvec" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" dependencies = [ - "glob", - "libc", - "libloading", + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", ] [[package]] -name = "clap" -version = "3.2.25" +name = "async-channel" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" dependencies = [ - "atty", - "bitflags 1.3.2", - "clap_lex", - "indexmap", - "strsim", - "termcolor", - "textwrap", + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", ] [[package]] -name = "clap_lex" -version = "0.2.4" +name = "async-executor" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" dependencies = [ - "os_str_bytes", + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", ] [[package]] -name = "either" -version = "1.15.0" +name = "async-io" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix 1.1.3", + "slab", + "windows-sys 0.61.2", +] [[package]] -name = "env_logger" -version = "0.9.3" +name = "async-lock" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ - "atty", - "humantime", - "log", - "regex", - "termcolor", + "event-listener", + "event-listener-strategy", + "pin-project-lite", ] [[package]] -name = "errno" -version = "0.3.14" +name = "async-process" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" dependencies = [ - "libc", - "windows-sys 0.61.2", + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix 1.1.3", ] [[package]] -name = "fallible-iterator" -version = "0.3.0" +name = "async-recursion" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] [[package]] -name = "fallible-streaming-iterator" -version = "0.1.9" +name = "async-signal" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" +checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 1.1.3", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] [[package]] -name = "find-msvc-tools" -version = "0.1.5" +name = "async-task" +version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] -name = "foldhash" -version = "0.1.5" +name = "async-trait" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] [[package]] -name = "getrandom" -version = "0.3.4" +name = "atk" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" dependencies = [ - "cfg-if", + "atk-sys", + "glib", "libc", - "r-efi", - "wasip2", ] [[package]] -name = "glob" -version = "0.3.3" +name = "atk-sys" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] [[package]] -name = "hashbrown" -version = "0.12.3" +name = "atomic-waker" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] -name = "hashbrown" -version = "0.15.5" +name = "atty" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "foldhash", + "hermit-abi 0.1.19", + "libc", + "winapi", ] [[package]] -name = "hashlink" -version = "0.10.0" +name = "autocfg" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" -dependencies = [ - "hashbrown 0.15.5", -] +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] -name = "hermit-abi" -version = "0.1.19" +name = "axum" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" dependencies = [ - "libc", + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", ] [[package]] -name = "home" -version = "0.5.12" +name = "axum-core" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" dependencies = [ - "windows-sys 0.61.2", + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", ] [[package]] -name = "humantime" -version = "2.3.0" +name = "base64" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] -name = "indexmap" -version = "1.9.3" +name = "base64" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bindgen" +version = "0.60.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "062dddbc1ba4aca46de6338e2bf87771414c335f7b2f2036e8f3e9befebf88e6" dependencies = [ - "autocfg", - "hashbrown 0.12.3", + "bitflags 1.3.2", + "cexpr", + "clang-sys", + "clap 3.2.25", + "env_logger", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "which", ] [[package]] -name = "itoa" -version = "1.0.16" +name = "bitflags" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] -name = "js-sys" -version = "0.3.83" +name = "bitflags" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" dependencies = [ - "once_cell", - "wasm-bindgen", + "serde_core", ] [[package]] -name = "lazy_static" -version = "1.5.0" +name = "bitvec" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] [[package]] -name = "lazycell" -version = "1.3.0" +name = "block-buffer" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] [[package]] -name = "libc" -version = "0.2.178" +name = "block2" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] [[package]] -name = "libloading" -version = "0.8.9" +name = "blocking" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" dependencies = [ - "cfg-if", - "windows-link", + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", ] [[package]] -name = "libsqlite3-sys" -version = "0.34.0" +name = "borsh" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91632f3b4fb6bd1d72aa3d78f41ffecfcf2b1a6648d8c241dbe7dbfaf4875e15" +checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" dependencies = [ - "cc", - "pkg-config", - "vcpkg", + "borsh-derive", + "cfg_aliases", ] [[package]] -name = "linux-raw-sys" -version = "0.4.15" +name = "borsh-derive" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" +dependencies = [ + "once_cell", + "proc-macro-crate 3.4.0", + "proc-macro2", + "quote", + "syn 2.0.111", +] [[package]] -name = "log" -version = "0.4.29" +name = "brotli" +version = "8.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] [[package]] -name = "memchr" -version = "2.7.6" +name = "brotli-decompressor" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] [[package]] -name = "minimal-lexical" -version = "0.2.1" +name = "bumpalo" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] -name = "nom" -version = "7.1.3" +name = "byte-unit" +version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +checksum = "8c6d47a4e2961fb8721bcfc54feae6455f2f64e7054f9bc67e875f0e77f4c58d" dependencies = [ - "memchr", - "minimal-lexical", + "rust_decimal", + "schemars 1.2.0", + "serde", + "utf8-width", ] [[package]] -name = "num-traits" -version = "0.2.19" +name = "bytecheck" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" dependencies = [ - "autocfg", + "bytecheck_derive", + "ptr_meta", + "simdutf8", ] [[package]] -name = "once_cell" -version = "1.21.3" +name = "bytecheck_derive" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] [[package]] -name = "os_str_bytes" -version = "6.6.1" +name = "bytemuck" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" [[package]] -name = "peeking_take_while" -version = "0.1.2" +name = "byteorder" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] -name = "pkg-config" -version = "0.3.32" +name = "bytes" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +dependencies = [ + "serde", +] [[package]] -name = "ppv-lite86" -version = "0.2.21" +name = "cairo-rs" +version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" dependencies = [ - "zerocopy", + "bitflags 2.10.0", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", ] [[package]] -name = "proc-macro2" -version = "1.0.103" +name = "cairo-sys-rs" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" dependencies = [ - "unicode-ident", + "glib-sys", + "libc", + "system-deps", ] [[package]] -name = "quote" -version = "1.0.42" +name = "camino" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" dependencies = [ - "proc-macro2", + "serde_core", ] [[package]] -name = "r-efi" -version = "5.3.0" +name = "cargo-platform" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] [[package]] -name = "rand" -version = "0.9.2" +name = "cargo_metadata" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" dependencies = [ - "rand_chacha", - "rand_core", + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.17", ] [[package]] -name = "rand_chacha" -version = "0.9.0" +name = "cargo_toml" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" dependencies = [ - "ppv-lite86", - "rand_core", + "serde", + "toml 0.9.10+spec-1.1.0", ] [[package]] -name = "rand_core" -version = "0.9.3" +name = "cc" +version = "1.2.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" dependencies = [ - "getrandom", + "find-msvc-tools", + "shlex", ] [[package]] -name = "regex" -version = "1.12.2" +name = "cesu8" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] -name = "regex-automata" -version = "0.4.13" +name = "cexpr" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", + "nom", ] [[package]] -name = "regex-syntax" -version = "0.8.8" +name = "cfb" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] [[package]] -name = "rusqlite" -version = "0.36.0" +name = "cfg-expr" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3de23c3319433716cf134eed225fe9986bc24f63bed9be9f20c329029e672dc7" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" dependencies = [ - "bitflags 2.10.0", - "fallible-iterator", - "fallible-streaming-iterator", - "hashlink", - "libsqlite3-sys", "smallvec", + "target-lexicon", ] [[package]] -name = "rustc-hash" -version = "1.1.0" +name = "cfg-if" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] -name = "rustix" -version = "0.38.44" +name = "cfg_aliases" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.10.0", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.59.0", -] +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] -name = "rustversion" -version = "1.0.22" +name = "chrono" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link 0.2.1", +] [[package]] -name = "serde" -version = "1.0.228" +name = "clang-sys" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ - "serde_core", - "serde_derive", + "glob", + "libc", + "libloading 0.8.9", ] [[package]] -name = "serde_core" -version = "1.0.228" +name = "clap" +version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ - "serde_derive", + "atty", + "bitflags 1.3.2", + "clap_lex 0.2.4", + "indexmap 1.9.3", + "strsim 0.10.0", + "termcolor", + "textwrap", ] [[package]] -name = "serde_derive" -version = "1.0.228" +name = "clap" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", + "clap_builder", ] [[package]] -name = "serde_json" -version = "1.0.147" +name = "clap_builder" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6af14725505314343e673e9ecb7cd7e8a36aa9791eb936235a3567cc31447ae4" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", + "anstream", + "anstyle", + "clap_lex 0.7.6", + "strsim 0.11.1", ] [[package]] -name = "shlex" -version = "1.3.0" +name = "clap_lex" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] [[package]] -name = "smallvec" -version = "1.15.1" +name = "clap_lex" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] -name = "sqlite-loadable" -version = "0.0.6-alpha.6" +name = "colorchoice" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daaaad0ad506b154a72bf01fde23235377c01256abd4bd25e17419dbfd4e28a0" -dependencies = [ - "bitflags 1.3.2", - "serde", - "serde_json", - "sqlite-loadable-macros", - "sqlite3ext-sys", -] +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] -name = "sqlite-loadable-macros" -version = "0.0.3" +name = "combine" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96037a396115a2675db783f700faad878b44c8ff56c8a29c3404649a517a5e8f" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "bytes", + "memchr", ] [[package]] -name = "sqlite3ext-sys" -version = "0.0.1" +name = "concurrent-queue" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3afdc2b3dc08f16d6eecf8aa07d19975a268603ab1cca67d3f9b4172c507cf16" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ - "bindgen", - "cc", + "crossbeam-utils", ] [[package]] -name = "strsim" -version = "0.10.0" +name = "convert_case" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] -name = "syn" -version = "1.0.109" +name = "cookie" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "time", + "version_check", ] [[package]] -name = "syn" -version = "2.0.111" +name = "core-foundation" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "core-foundation-sys", + "libc", ] [[package]] -name = "termcolor" -version = "1.4.1" +name = "core-foundation-sys" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" dependencies = [ - "winapi-util", + "bitflags 2.10.0", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", ] [[package]] -name = "textwrap" -version = "0.16.2" +name = "core-graphics-types" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.10.0", + "core-foundation", + "libc", +] [[package]] -name = "unicode-ident" -version = "1.0.22" +name = "cpufeatures" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] [[package]] -name = "uuid" -version = "1.19.0" +name = "crc32fast" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ - "getrandom", - "js-sys", - "rand", - "wasm-bindgen", + "cfg-if", ] [[package]] -name = "vcpkg" -version = "0.2.15" +name = "crossbeam-channel" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] [[package]] -name = "wasip2" -version = "1.0.1+wasi-0.2.4" +name = "crossbeam-utils" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ - "wit-bindgen", + "generic-array", + "typenum", ] [[package]] -name = "wasm-bindgen" -version = "0.2.106" +name = "cssparser" +version = "0.29.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", + "cssparser-macros", + "dtoa-short", + "itoa", + "matches", + "phf 0.10.1", + "proc-macro2", + "quote", + "smallvec", + "syn 1.0.109", ] [[package]] -name = "wasm-bindgen-macro" -version = "0.2.106" +name = "cssparser-macros" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "wasm-bindgen-macro-support", + "syn 2.0.111", ] [[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.106" +name = "ctor" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ - "bumpalo", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", "proc-macro2", "quote", + "strsim 0.11.1", "syn 2.0.111", - "wasm-bindgen-shared", ] [[package]] -name = "wasm-bindgen-shared" -version = "0.2.106" +name = "darling_macro" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ - "unicode-ident", + "darling_core", + "quote", + "syn 2.0.111", ] [[package]] -name = "which" -version = "4.4.2" +name = "deranged" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ - "either", - "home", - "once_cell", - "rustix", + "powerfmt", + "serde_core", ] [[package]] -name = "winapi" -version = "0.3.9" +name = "derive_more" +version = "0.99.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.111", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "digest" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] [[package]] -name = "winapi-util" -version = "0.1.11" +name = "dirs" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ + "libc", + "option-ext", + "redox_users", "windows-sys 0.61.2", ] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "dispatch" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" [[package]] -name = "windows-link" -version = "0.2.1" +name = "dispatch2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.10.0", + "objc2", +] [[package]] -name = "windows-sys" -version = "0.59.0" +name = "displaydoc" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ - "windows-targets", + "proc-macro2", + "quote", + "syn 2.0.111", ] [[package]] -name = "windows-sys" -version = "0.61.2" +name = "dlopen2" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" dependencies = [ - "windows-link", + "proc-macro2", + "quote", + "syn 2.0.111", ] [[package]] -name = "windows-targets" -version = "0.52.6" +name = "dpi" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "serde", ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" +name = "dtoa" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" [[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" +name = "dtoa-short" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] [[package]] -name = "windows_i686_gnu" -version = "0.52.6" +name = "dunce" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" +name = "dyn-clone" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] -name = "windows_i686_msvc" -version = "0.52.6" +name = "either" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" +name = "embed-resource" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +checksum = "55a075fc573c64510038d7ee9abc7990635863992f83ebc52c8b433b8411a02e" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 0.9.10+spec-1.1.0", + "vswhom", + "winreg", +] [[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" +name = "embed_plist" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" [[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" +name = "encoding_rs" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] [[package]] -name = "wit-bindgen" -version = "0.46.0" +name = "endi" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" [[package]] -name = "zerocopy" -version = "0.8.31" +name = "enumflags2" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" dependencies = [ - "zerocopy-derive", + "enumflags2_derive", + "serde", ] [[package]] -name = "zerocopy-derive" -version = "0.8.31" +name = "enumflags2_derive" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", @@ -899,7 +1200,4998 @@ dependencies = [ ] [[package]] -name = "zmij" +name = "env_filter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0095ecd462946aa3927d9297b63ef82fb9a5316d7a37d134eeb36e58228615a" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "fern" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4316185f709b23713e41e3195f90edef7fb00c3ed4adc79769cf09cc762a3b29" +dependencies = [ + "log", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + +[[package]] +name = "flate2" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" +dependencies = [ + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdkx11-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.10.0", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.2", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashlink" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0b22561a9c04a7cb1a302c013e0259cd3b4bb619f145b32f72b8b4bcbed230" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "html5ever" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" +dependencies = [ + "log", + "mac", + "markup5ever", + "match_token", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +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 = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-util" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ico" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98" +dependencies = [ + "byteorder", + "png", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +dependencies = [ + "cfb", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" + +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "js-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "jsonptr" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.10.0", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "kuchikiki" +version = "0.8.8-speedreader" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" +dependencies = [ + "cssparser", + "html5ever", + "indexmap 2.12.1", + "selectors", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading 0.7.4", + "once_cell", +] + +[[package]] +name = "libc" +version = "0.2.178" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] + +[[package]] +name = "libredox" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" +dependencies = [ + "bitflags 2.10.0", + "libc", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b4103cffefa72eb8428cb6b47d6627161e51c2739fc5e3b734584157bc642a" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +dependencies = [ + "value-bag", +] + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "markup5ever" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" +dependencies = [ + "log", + "phf 0.11.3", + "phf_codegen 0.11.3", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "match_token" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.61.2", +] + +[[package]] +name = "muda" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "once_cell", + "png", + "serde", + "thiserror 2.0.17", + "windows-sys 0.60.2", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.10.0", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +dependencies = [ + "proc-macro-crate 3.4.0", + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "objc2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +dependencies = [ + "objc2-encode", + "objc2-exception-helper", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.10.0", + "block2", + "libc", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-text", + "objc2-core-video", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags 2.10.0", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "bitflags 2.10.0", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.10.0", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.10.0", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.10.0", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-core-video" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" +dependencies = [ + "bitflags 2.10.0", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-io-surface", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-exception-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.10.0", + "block2", + "libc", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.10.0", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-javascript-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a1e6550c4caed348956ce3370c9ffeca70bb1dbed4fa96112e7c6170e074586" +dependencies = [ + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.10.0", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-security" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a" +dependencies = [ + "bitflags 2.10.0", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.10.0", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-web-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" +dependencies = [ + "bitflags 2.10.0", + "block2", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "objc2-javascript-core", + "objc2-security", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "open" +version = "5.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43bb73a7fa3799b198970490a51174027ba0d4ec504b03cd08caf513d40024bc" +dependencies = [ + "dunce", + "is-wsl", + "libc", + "pathdiff", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "os_pipe" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "os_str_bytes" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_shared 0.8.0", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_macros 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher 1.0.1", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plist" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" +dependencies = [ + "base64 0.22.1", + "indexmap 2.12.1", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.5.2", + "pin-project-lite", + "rustix 1.1.3", + "windows-sys 0.61.2", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +dependencies = [ + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit 0.23.10+spec-1.0.0", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quick-xml" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.17", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-util", + "tower", + "tower-http 0.6.8", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "rkyv" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rusqlite" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1c93dd1c9683b438c392c492109cb702b8090b2bfc8fed6f6e4eb4523f17af3" +dependencies = [ + "bitflags 2.10.0", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", + "sqlite-wasm-rs", +] + +[[package]] +name = "rust_decimal" +version = "1.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35affe401787a9bd846712274d97654355d21b2a2c092a3139aabe31e9022282" +dependencies = [ + "arrayvec", + "borsh", + "bytes", + "num-traits", + "rand 0.8.5", + "rkyv", + "serde", + "serde_json", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", + "url", + "uuid", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.111", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "selectors" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" +dependencies = [ + "bitflags 1.3.2", + "cssparser", + "derive_more", + "fxhash", + "log", + "phf 0.8.0", + "phf_codegen 0.8.0", + "precomputed-hash", + "servo_arc", + "smallvec", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "serde_json" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af14725505314343e673e9ecb7cd7e8a36aa9791eb936235a3567cc31447ae4" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.12.1", + "schemars 0.9.0", + "schemars 1.2.0", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "servo_arc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" +dependencies = [ + "nodrop", + "stable_deref_trait", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shared_child" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7" +dependencies = [ + "libc", + "sigchld", + "windows-sys 0.60.2", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "sigchld" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1" +dependencies = [ + "libc", + "os_pipe", + "signal-hook", +] + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "softbuffer" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" +dependencies = [ + "bytemuck", + "js-sys", + "ndk", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "objc2-quartz-core", + "raw-window-handle", + "redox_syscall", + "tracing", + "wasm-bindgen", + "web-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "sqlite-loadable" +version = "0.0.6-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daaaad0ad506b154a72bf01fde23235377c01256abd4bd25e17419dbfd4e28a0" +dependencies = [ + "bitflags 1.3.2", + "serde", + "serde_json", + "sqlite-loadable-macros", + "sqlite3ext-sys", +] + +[[package]] +name = "sqlite-loadable-macros" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96037a396115a2675db783f700faad878b44c8ff56c8a29c3404649a517a5e8f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "sqlite-wasm-rs" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e98301bf8b0540c7de45ecd760539b9c62f5772aed172f08efba597c11cd5d" +dependencies = [ + "cc", + "hashbrown 0.16.1", + "js-sys", + "thiserror 2.0.17", + "wasm-bindgen", +] + +[[package]] +name = "sqlite3ext-sys" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3afdc2b3dc08f16d6eecf8aa07d19975a268603ab1cca67d3f9b4172c507cf16" +dependencies = [ + "bindgen", + "cc", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.11.3", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "swift-rs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 0.8.2", + "version-compare", +] + +[[package]] +name = "tao" +version = "0.34.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a753bdc39c07b192151523a3f77cd0394aa75413802c883a0f6f6a0e5ee2e7" +dependencies = [ + "bitflags 2.10.0", + "block2", + "core-foundation", + "core-graphics", + "crossbeam-channel", + "dispatch", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "jni", + "lazy_static", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "once_cell", + "parking_lot", + "raw-window-handle", + "scopeguard", + "tao-macros", + "unicode-segmentation", + "url", + "windows", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tauri" +version = "2.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a3868da5508446a7cd08956d523ac3edf0a8bc20bf7e4038f9a95c2800d2033" +dependencies = [ + "anyhow", + "bytes", + "cookie", + "dirs", + "dunce", + "embed_plist", + "getrandom 0.3.4", + "glob", + "gtk", + "heck 0.5.0", + "http", + "jni", + "libc", + "log", + "mime", + "muda", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "percent-encoding", + "plist", + "raw-window-handle", + "reqwest", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "swift-rs", + "tauri-build", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "thiserror 2.0.17", + "tokio", + "tray-icon", + "url", + "webkit2gtk", + "webview2-com", + "window-vibrancy", + "windows", +] + +[[package]] +name = "tauri-build" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17fcb8819fd16463512a12f531d44826ce566f486d7ccd211c9c8cebdaec4e08" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs", + "glob", + "heck 0.5.0", + "json-patch", + "schemars 0.8.22", + "semver", + "serde", + "serde_json", + "tauri-utils", + "tauri-winres", + "toml 0.9.10+spec-1.1.0", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa9844cefcf99554a16e0a278156ae73b0d8680bbc0e2ad1e4287aadd8489cf" +dependencies = [ + "base64 0.22.1", + "brotli", + "ico", + "json-patch", + "plist", + "png", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "sha2", + "syn 2.0.111", + "tauri-utils", + "thiserror 2.0.17", + "time", + "url", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3764a12f886d8245e66b7ee9b43ccc47883399be2019a61d80cf0f4117446fde" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.111", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-plugin" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1d0a4860b7ff570c891e1d2a586bf1ede205ff858fbc305e0b5ae5d14c1377" +dependencies = [ + "anyhow", + "glob", + "plist", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri-utils", + "toml 0.9.10+spec-1.1.0", + "walkdir", +] + +[[package]] +name = "tauri-plugin-cli" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28e78fb2c09a81546bcd376d34db4bda5769270d00990daa9f0d6e7ac1107e25" +dependencies = [ + "clap 4.5.53", + "log", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.17", +] + +[[package]] +name = "tauri-plugin-fs" +version = "2.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47df422695255ecbe7bac7012440eddaeefd026656171eac9559f5243d3230d9" +dependencies = [ + "anyhow", + "dunce", + "glob", + "percent-encoding", + "schemars 0.8.22", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "tauri-utils", + "thiserror 2.0.17", + "toml 0.9.10+spec-1.1.0", + "url", +] + +[[package]] +name = "tauri-plugin-log" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5709c792b8630290b5d9811a1f8fe983dd925fc87c7fc7f4923616458cd00b6" +dependencies = [ + "android_logger", + "byte-unit", + "fern", + "log", + "objc2", + "objc2-foundation", + "serde", + "serde_json", + "serde_repr", + "swift-rs", + "tauri", + "tauri-plugin", + "thiserror 2.0.17", + "time", +] + +[[package]] +name = "tauri-plugin-opener" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c26b72571d25dee25667940027114e60f569fc3974f8cefbe50c2cbc5fd65e3b" +dependencies = [ + "dunce", + "glob", + "objc2-app-kit", + "objc2-foundation", + "open", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.17", + "url", + "windows", + "zbus", +] + +[[package]] +name = "tauri-plugin-shell" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c374b6db45f2a8a304f0273a15080d98c70cde86178855fc24653ba657a1144c" +dependencies = [ + "encoding_rs", + "log", + "open", + "os_pipe", + "regex", + "schemars 0.8.22", + "serde", + "serde_json", + "shared_child", + "tauri", + "tauri-plugin", + "thiserror 2.0.17", + "tokio", +] + +[[package]] +name = "tauri-plugin-store" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a77036340a97eb5bbe1b3209c31e5f27f75e6f92a52fd9dd4b211ef08bf310" +dependencies = [ + "dunce", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.17", + "tokio", + "tracing", +] + +[[package]] +name = "tauri-runtime" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f766fe9f3d1efc4b59b17e7a891ad5ed195fa8d23582abb02e6c9a01137892" +dependencies = [ + "cookie", + "dpi", + "gtk", + "http", + "jni", + "objc2", + "objc2-ui-kit", + "objc2-web-kit", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror 2.0.17", + "url", + "webkit2gtk", + "webview2-com", + "windows", +] + +[[package]] +name = "tauri-runtime-wry" +version = "2.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "187a3f26f681bdf028f796ccf57cf478c1ee422c50128e5a0a6ebeb3f5910065" +dependencies = [ + "gtk", + "http", + "jni", + "log", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "once_cell", + "percent-encoding", + "raw-window-handle", + "softbuffer", + "tao", + "tauri-runtime", + "tauri-utils", + "url", + "webkit2gtk", + "webview2-com", + "windows", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a423c51176eb3616ee9b516a9fa67fed5f0e78baaba680e44eb5dd2cc37490" +dependencies = [ + "anyhow", + "brotli", + "cargo_metadata", + "ctor", + "dunce", + "glob", + "html5ever", + "http", + "infer", + "json-patch", + "kuchikiki", + "log", + "memchr", + "phf 0.11.3", + "proc-macro2", + "quote", + "regex", + "schemars 0.8.22", + "semver", + "serde", + "serde-untagged", + "serde_json", + "serde_with", + "swift-rs", + "thiserror 2.0.17", + "toml 0.9.10+spec-1.1.0", + "url", + "urlpattern", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-winres" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1087b111fe2b005e42dbdc1990fc18593234238d47453b0c99b7de1c9ab2c1e0" +dependencies = [ + "dunce", + "embed-resource", + "toml 0.9.10+spec-1.1.0", +] + +[[package]] +name = "tempfile" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix 1.1.3", + "windows-sys 0.61.2", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "time" +version = "0.3.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +dependencies = [ + "deranged", + "itoa", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" + +[[package]] +name = "time-macros" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "tokio-util" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "toml" +version = "0.9.10+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" +dependencies = [ + "indexmap 2.12.1", + "serde_core", + "serde_spanned 1.0.4", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.14", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.12.1", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap 2.12.1", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.23.10+spec-1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +dependencies = [ + "indexmap 2.12.1", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "winnow 0.7.14", +] + +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +dependencies = [ + "winnow 0.7.14", +] + +[[package]] +name = "toml_writer" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" +dependencies = [ + "bitflags 2.10.0", + "bytes", + "http", + "http-body", + "http-body-util", + "pin-project-lite", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.10.0", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tray-icon" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d5572781bee8e3f994d7467084e1b1fd7a93ce66bd480f8156ba89dee55a2b" +dependencies = [ + "crossbeam-channel", + "dirs", + "libappindicator", + "muda", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "once_cell", + "png", + "serde", + "thiserror 2.0.17", + "windows-sys 0.60.2", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-ucd-ident" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + +[[package]] +name = "unicode-ident" +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 = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "urlpattern" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +dependencies = [ + "regex", + "serde", + "unic-ucd-ident", + "url", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1292c0d970b54115d14f2492fe0170adf21d68a1de108eebc51c1df4f346a091" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "rand 0.9.2", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "value-bag" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.111", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webkit2gtk" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup3", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pkg-config", + "soup3-sys", + "system-deps", +] + +[[package]] +name = "webview2-com" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ba622a989277ef3886dd5afb3e280e3dd6d974b766118950a08f8f678ad6a4" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows", + "windows-core 0.61.2", + "windows-implement", + "windows-interface", +] + +[[package]] +name = "webview2-com-macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "webview2-com-sys" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" +dependencies = [ + "thiserror 2.0.17", + "windows", + "windows-core 0.61.2", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.44", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "window-vibrancy" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" +dependencies = [ + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "raw-window-handle", + "windows-sys 0.59.0", + "windows-version", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "wry" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728b7d4c8ec8d81cab295e0b5b8a4c263c0d41a785fb8f8c4df284e5411140a2" +dependencies = [ + "base64 0.22.1", + "block2", + "cookie", + "crossbeam-channel", + "dirs", + "dpi", + "dunce", + "gdkx11", + "gtk", + "html5ever", + "http", + "javascriptcore-rs", + "jni", + "kuchikiki", + "libc", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "sha2", + "soup3", + "tao-macros", + "thiserror 2.0.17", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", + "synstructure", +] + +[[package]] +name = "zbus" +version = "5.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b622b18155f7a93d1cd2dc8c01d2d6a44e08fb9ebb7b3f9e6ed101488bad6c91" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "nix", + "ordered-stream", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow 0.7.14", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cdb94821ca8a87ca9c298b5d1cbd80e2a8b67115d99f6e4551ac49e42b6a314" +dependencies = [ + "proc-macro-crate 3.4.0", + "proc-macro2", + "quote", + "syn 2.0.111", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" +dependencies = [ + "serde", + "static_assertions", + "winnow 0.7.14", + "zvariant", +] + +[[package]] +name = "zerocopy" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "zmij" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0095ecd462946aa3927d9297b63ef82fb9a5316d7a37d134eeb36e58228615a" + +[[package]] +name = "zvariant" +version = "5.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2be61892e4f2b1772727be11630a62664a1826b62efa43a6fe7449521cb8744c" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow 0.7.14", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58575a1b2b20766513b1ec59d8e2e68db2745379f961f86650655e862d2006" +dependencies = [ + "proc-macro-crate 3.4.0", + "proc-macro2", + "quote", + "syn 2.0.111", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6949d142f89f6916deca2232cf26a8afacf2b9fdc35ce766105e104478be599" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 2.0.111", + "winnow 0.7.14", +] diff --git a/Cargo.toml b/Cargo.toml index ec12f15..346c423 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,9 @@ [workspace] -members = ["absurd-sqlite-extension"] -resolver = "3" \ No newline at end of file +members = ["absurd-sqlite-deno-runtime", "absurd-sqlite-extension", "standalone/src-tauri"] +resolver = "3" + +[profile.release] +codegen-units = 1 +lto = true +opt-level = "z" +panic = "abort" diff --git a/absurd-sqlite-deno-runtime/AGENTS.md b/absurd-sqlite-deno-runtime/AGENTS.md new file mode 100644 index 0000000..49027cd --- /dev/null +++ b/absurd-sqlite-deno-runtime/AGENTS.md @@ -0,0 +1,4 @@ +# absurd-deno-runtime + +This folder contains the Deno runtime for running Absurd SQLite client worker. +The runtime is backed by Deno's Rust API with restricted permissions and API access. diff --git a/absurd-sqlite-deno-runtime/Cargo.toml b/absurd-sqlite-deno-runtime/Cargo.toml new file mode 100644 index 0000000..be8eef3 --- /dev/null +++ b/absurd-sqlite-deno-runtime/Cargo.toml @@ -0,0 +1,6 @@ +[package] +edition = "2024" +name = "absurd-sqlite-deno-runtime" +version = "0.1.0" + +[dependencies] diff --git a/absurd-sqlite-deno-runtime/src/main.rs b/absurd-sqlite-deno-runtime/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/absurd-sqlite-deno-runtime/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/absurd-sqlite-extension/AGENTS.md b/absurd-sqlite-extension/AGENTS.md new file mode 100644 index 0000000..d952e1e --- /dev/null +++ b/absurd-sqlite-extension/AGENTS.md @@ -0,0 +1,30 @@ +# absurd-sqlite-extension + +This folder contains the core SQLite extension logic for Absurd-SQLite, implemented in Rust. + +## Development + +This is a Rust based project. To build the extension, ensure you have Rust and Cargo installed, then run: + +```bash +$ cargo build +``` + +### Code Linting and Formatting + +Please make sure to lint / format code. Use the following commands to validate: + +``` +$ cargo fmt -p absurd-sqlite-extension -- --check +$ cargo clippy -p absurd-sqlite-extension -- -D warnings +``` + +### Code Testing + +To run the tests for the SQLite extension, use: + +```bash +$ cargo test -p absurd-sqlite-extension +``` + +Please make sure all tests pass after making changes. diff --git a/absurd-sqlite-extension/Cargo.toml b/absurd-sqlite-extension/Cargo.toml index 47068ac..966f60e 100644 --- a/absurd-sqlite-extension/Cargo.toml +++ b/absurd-sqlite-extension/Cargo.toml @@ -1,25 +1,19 @@ [package] +build = "build.rs" edition = "2021" name = "absurd_sqlite_extension" version = "0.1.0" -build = "build.rs" [lib] crate-type = ["cdylib"] name = "absurd" [dependencies] -sqlite-loadable = { version = "0.0.6-alpha.6", features = ["exec"] } +chrono = {version = "0.4", default-features = false, features = ["std"]} serde_json = "1.0" -uuid = { version = "1.7", features = ["v4", "v7", "fast-rng"] } +sqlite-loadable = {version = "0.0.6-alpha.6", features = ["exec"]} sqlite3ext-sys = "0.0.1" -chrono = { version = "0.4", default-features = false, features = ["std"] } +uuid = {version = "1.7", features = ["v4", "v7", "fast-rng"]} [dev-dependencies] -rusqlite = { version = "0.36", features = ["load_extension", "bundled"] } - -[profile.release] -codegen-units = 1 -lto = true -opt-level = "z" -panic = "abort" +rusqlite = {version = "0.38", features = ["load_extension", "bundled"]} diff --git a/absurd-sqlite-extension/build.rs b/absurd-sqlite-extension/build.rs index 4427bc1..19ff353 100644 --- a/absurd-sqlite-extension/build.rs +++ b/absurd-sqlite-extension/build.rs @@ -28,8 +28,7 @@ fn main() { let migrations_dir = manifest_dir.join("migrations"); println!("cargo:rerun-if-changed={}", migrations_dir.display()); - let default_version = - env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "0.0.0".to_string()); + let default_version = env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "0.0.0".to_string()); let mut migrations: Vec = Vec::new(); let entries = fs::read_dir(&migrations_dir) @@ -53,8 +52,7 @@ fn main() { continue; } let id: i64 = stem.parse().expect("migration id"); - let contents = - fs::read_to_string(&path).expect("failed to read migration file"); + let contents = fs::read_to_string(&path).expect("failed to read migration file"); let introduced_version = parse_introduced_version(&contents, &default_version); migrations.push(MigrationFile { id, diff --git a/absurd-sqlite-extension/src/checkpoint.rs b/absurd-sqlite-extension/src/checkpoint.rs index ba976e2..cd0dffc 100644 --- a/absurd-sqlite-extension/src/checkpoint.rs +++ b/absurd-sqlite-extension/src/checkpoint.rs @@ -1,13 +1,13 @@ use crate::sql; use crate::validate; use serde_json::Value as JsonValue; +use sqlite3ext_sys::sqlite3; use sqlite_loadable::prelude::*; use sqlite_loadable::{ api, table::{BestIndexError, ConstraintOperator, IndexInfo, VTab, VTabArguments, VTabCursor}, Error, Result, }; -use sqlite3ext_sys::sqlite3; use std::os::raw::c_int; struct CheckpointRow { @@ -18,8 +18,6 @@ struct CheckpointRow { updated_at: i64, } - - fn parse_optional_int(value: &*mut sqlite3_value) -> Option { if api::value_is_null(value) { return None; @@ -35,15 +33,12 @@ pub fn absurd_set_task_checkpoint_state( context: *mut sqlite3_context, values: &[*mut sqlite3_value], ) -> Result<()> { - let queue_name = api::value_text_notnull(values.get(0).expect("queue_name"))?; + let queue_name = api::value_text_notnull(values.first().expect("queue_name"))?; let task_id = api::value_text_notnull(values.get(1).expect("task_id"))?; let step_name = api::value_text_notnull(values.get(2).expect("step_name"))?; let state_value = values.get(3).expect("state"); let owner_run = api::value_text_notnull(values.get(4).expect("owner_run"))?; - let extend_claim_by = values - .get(5) - .and_then(|value| parse_optional_int(value)) - .unwrap_or(0); + let extend_claim_by = values.get(5).and_then(parse_optional_int).unwrap_or(0); validate::queue_name(queue_name)?; validate::step_name(step_name)?; @@ -52,14 +47,14 @@ pub fn absurd_set_task_checkpoint_state( "".to_string() } else { let raw = api::value_text(state_value) - .map_err(|err| Error::new_message(&format!("state must be valid JSON: {:?}", err)))? + .map_err(|err| Error::new_message(format!("state must be valid JSON: {:?}", err)))? .trim() .to_string(); if raw.is_empty() { return Err(Error::new_message("state must be valid JSON")); } - let _: JsonValue = - serde_json::from_str(&raw).map_err(|err| Error::new_message(&format!("state must be valid JSON: {:?}", err)))?; + let _: JsonValue = serde_json::from_str(&raw) + .map_err(|err| Error::new_message(format!("state must be valid JSON: {:?}", err)))?; raw }; @@ -79,29 +74,30 @@ pub fn absurd_set_task_checkpoint_state( where r.queue_name = ?1 and r.run_id = ?2", ) - .map_err(|err| Error::new_message(&format!("failed to prepare run lookup: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to prepare run lookup: {:?}", err)))?; stmt.bind_text(1, queue_name) - .map_err(|err| Error::new_message(&format!("failed to bind queue_name: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind queue_name: {:?}", err)))?; stmt.bind_text(2, owner_run) - .map_err(|err| Error::new_message(&format!("failed to bind owner_run: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind owner_run: {:?}", err)))?; let mut rows = stmt.execute(); let row = rows .next() .ok_or_else(|| Error::new_message("run not found for checkpoint"))? - .map_err(|err| Error::new_message(&format!("failed to read run row: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read run row: {:?}", err)))?; let new_attempt = row .get::(0) - .map_err(|err| Error::new_message(&format!("failed to read attempt: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read attempt: {:?}", err)))?; let task_state = row .get::(1) - .map_err(|err| Error::new_message(&format!("failed to read task state: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read task state: {:?}", err)))?; if task_state == "cancelled" { return Err(Error::new_message("Task has been cancelled")); } if extend_claim_by > 0 { - let extend_value = (sql::now_ms_from_db(db) + extend_claim_by.saturating_mul(1000)).to_string(); + let extend_value = + (sql::now_ms_from_db(db) + extend_claim_by.saturating_mul(1000)).to_string(); sql::exec_with_bind_text( db, "update absurd_runs @@ -126,26 +122,29 @@ pub fn absurd_set_task_checkpoint_state( and c.task_id = ?2 and c.checkpoint_name = ?3", ) - .map_err(|err| Error::new_message(&format!("failed to prepare checkpoint lookup: {:?}", err)))?; + .map_err(|err| { + Error::new_message(format!("failed to prepare checkpoint lookup: {:?}", err)) + })?; existing_stmt .bind_text(1, queue_name) - .map_err(|err| Error::new_message(&format!("failed to bind queue_name: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind queue_name: {:?}", err)))?; existing_stmt .bind_text(2, task_id) - .map_err(|err| Error::new_message(&format!("failed to bind task_id: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind task_id: {:?}", err)))?; existing_stmt .bind_text(3, step_name) - .map_err(|err| Error::new_message(&format!("failed to bind step_name: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind step_name: {:?}", err)))?; let mut existing_rows = existing_stmt.execute(); let mut allow_update = true; if let Some(Ok(existing_row)) = existing_rows.next() { - let existing_owner = existing_row - .get::(0) - .map_err(|err| Error::new_message(&format!("failed to read owner_run_id: {:?}", err)))?; + let existing_owner = existing_row.get::(0).map_err(|err| { + Error::new_message(format!("failed to read owner_run_id: {:?}", err)) + })?; let existing_attempt = existing_row .get::(1) - .map_err(|err| Error::new_message(&format!("failed to read attempt: {:?}", err)))?; - if !existing_owner.is_empty() && existing_attempt >= 0 && new_attempt < existing_attempt { + .map_err(|err| Error::new_message(format!("failed to read attempt: {:?}", err)))?; + if !existing_owner.is_empty() && existing_attempt >= 0 && new_attempt < existing_attempt + { allow_update = false; } } @@ -390,7 +389,7 @@ impl VTabCursor for CheckpointCursor { idx_str: Option<&str>, values: &[*mut sqlite3_value], ) -> Result<()> { - let queue_name = api::value_text_notnull(values.get(0).expect("queue_name"))?; + let queue_name = api::value_text_notnull(values.first().expect("queue_name"))?; let task_id = api::value_text_notnull(values.get(1).expect("task_id"))?; validate::queue_name(queue_name)?; @@ -424,37 +423,49 @@ impl VTabCursor for CheckpointCursor { ) }; - let mut stmt = sqlite_loadable::exec::Statement::prepare(self.db, sql_query) - .map_err(|err| Error::new_message(&format!("failed to prepare checkpoint query: {:?}", err)))?; + let mut stmt = + sqlite_loadable::exec::Statement::prepare(self.db, sql_query).map_err(|err| { + Error::new_message(format!("failed to prepare checkpoint query: {:?}", err)) + })?; for (idx, value) in params.iter().enumerate() { - stmt.bind_text((idx + 1) as i32, value) - .map_err(|err| Error::new_message(&format!("failed to bind parameter: {:?}", err)))?; + stmt.bind_text((idx + 1) as i32, value).map_err(|err| { + Error::new_message(format!("failed to bind parameter: {:?}", err)) + })?; } let mut results = Vec::new(); for row in stmt.execute() { - let row = row.map_err(|err| Error::new_message(&format!("failed to read row: {:?}", err)))?; - let checkpoint_name = row - .get::(0) - .map_err(|err| Error::new_message(&format!("failed to read checkpoint_name: {:?}", err)))?; + let row = + row.map_err(|err| Error::new_message(format!("failed to read row: {:?}", err)))?; + let checkpoint_name = row.get::(0).map_err(|err| { + Error::new_message(format!("failed to read checkpoint_name: {:?}", err)) + })?; let state_raw = row .get::(1) - .map_err(|err| Error::new_message(&format!("failed to read state: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read state: {:?}", err)))?; let status = row .get::(2) - .map_err(|err| Error::new_message(&format!("failed to read status: {:?}", err)))?; - let owner_raw = row - .get::(3) - .map_err(|err| Error::new_message(&format!("failed to read owner_run_id: {:?}", err)))?; - let updated_at = row - .get::(4) - .map_err(|err| Error::new_message(&format!("failed to read updated_at: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read status: {:?}", err)))?; + let owner_raw = row.get::(3).map_err(|err| { + Error::new_message(format!("failed to read owner_run_id: {:?}", err)) + })?; + let updated_at = row.get::(4).map_err(|err| { + Error::new_message(format!("failed to read updated_at: {:?}", err)) + })?; results.push(CheckpointRow { checkpoint_name, - state: if state_raw.is_empty() { None } else { Some(state_raw) }, + state: if state_raw.is_empty() { + None + } else { + Some(state_raw) + }, status, - owner_run_id: if owner_raw.is_empty() { None } else { Some(owner_raw) }, + owner_run_id: if owner_raw.is_empty() { + None + } else { + Some(owner_raw) + }, updated_at, }); } @@ -480,7 +491,9 @@ impl VTabCursor for CheckpointCursor { } let result = &self.results[self.rowid as usize]; match checkpoint_output_column(i) { - Some(CheckpointColumns::CheckpointName) => api::result_text(context, &result.checkpoint_name)?, + Some(CheckpointColumns::CheckpointName) => { + api::result_text(context, &result.checkpoint_name)? + } Some(CheckpointColumns::State) => match &result.state { Some(value) => sql::result_json_value(self.db, context, value)?, None => api::result_null(context), diff --git a/absurd-sqlite-extension/src/claim.rs b/absurd-sqlite-extension/src/claim.rs index 57c6ce3..50c1332 100644 --- a/absurd-sqlite-extension/src/claim.rs +++ b/absurd-sqlite-extension/src/claim.rs @@ -35,7 +35,7 @@ struct ClaimResult { fn parse_claim_args(values: &[*mut sqlite3_value]) -> Result { let queue_name = - api::value_text_notnull(values.get(0).expect("queue_name is required"))?.to_string(); + api::value_text_notnull(values.first().expect("queue_name is required"))?.to_string(); validate::queue_name(&queue_name)?; let worker_id = values @@ -164,44 +164,44 @@ fn expire_claims(db: *mut sqlite3, queue_name: &str, now: i64) -> Result<()> { and r.claim_expires_at <= cast(?2 as integer)", ) .map_err(|err| { - Error::new_message(&format!("failed to prepare expired claim query: {:?}", err)) + Error::new_message(format!("failed to prepare expired claim query: {:?}", err)) })?; stmt.bind_text(1, queue_name) - .map_err(|err| Error::new_message(&format!("failed to bind queue_name: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind queue_name: {:?}", err)))?; stmt.bind_text(2, &now_value) - .map_err(|err| Error::new_message(&format!("failed to bind now: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind now: {:?}", err)))?; for row in stmt.execute() { let row = row.map_err(|err| { - Error::new_message(&format!("failed to read expired claim row: {:?}", err)) + Error::new_message(format!("failed to read expired claim row: {:?}", err)) })?; let run_id = row .get::(0) - .map_err(|err| Error::new_message(&format!("failed to read run_id: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read run_id: {:?}", err)))?; let task_id = row .get::(1) - .map_err(|err| Error::new_message(&format!("failed to read task_id: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read task_id: {:?}", err)))?; let attempt = row .get::(2) - .map_err(|err| Error::new_message(&format!("failed to read attempt: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read attempt: {:?}", err)))?; let claimed_by = row .get::(3) - .map_err(|err| Error::new_message(&format!("failed to read claimed_by: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read claimed_by: {:?}", err)))?; let claim_expires_at = row.get::(4).map_err(|err| { - Error::new_message(&format!("failed to read claim_expires_at: {:?}", err)) + Error::new_message(format!("failed to read claim_expires_at: {:?}", err)) })?; let retry_strategy = row.get::(5).map_err(|err| { - Error::new_message(&format!("failed to read retry_strategy: {:?}", err)) - })?; - let max_attempts = row.get::(6).map_err(|err| { - Error::new_message(&format!("failed to read max_attempts: {:?}", err)) + Error::new_message(format!("failed to read retry_strategy: {:?}", err)) })?; + let max_attempts = row + .get::(6) + .map_err(|err| Error::new_message(format!("failed to read max_attempts: {:?}", err)))?; let first_started_at = row.get::(7).map_err(|err| { - Error::new_message(&format!("failed to read first_started_at: {:?}", err)) - })?; - let cancellation = row.get::(8).map_err(|err| { - Error::new_message(&format!("failed to read cancellation: {:?}", err)) + Error::new_message(format!("failed to read first_started_at: {:?}", err)) })?; + let cancellation = row + .get::(8) + .map_err(|err| Error::new_message(format!("failed to read cancellation: {:?}", err)))?; let failure_reason = serde_json::json!({ "name": "$ClaimTimeout", @@ -240,7 +240,7 @@ fn expire_claims(db: *mut sqlite3, queue_name: &str, now: i64) -> Result<()> { } else { Some(max_attempts) }; - let allow_retry = max_attempts_opt.map_or(true, |max| next_attempt <= max); + let allow_retry = max_attempts_opt.is_none_or(|max| next_attempt <= max); let mut task_state = "failed"; let mut last_attempt_run = run_id.clone(); let mut cancelled_at = ""; @@ -372,51 +372,51 @@ fn select_candidates( order by r.available_at, r.run_id limit cast(?3 as integer)", ) - .map_err(|err| Error::new_message(&format!("failed to prepare claim query: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to prepare claim query: {:?}", err)))?; stmt.bind_text(1, queue_name) - .map_err(|err| Error::new_message(&format!("failed to bind queue_name: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind queue_name: {:?}", err)))?; stmt.bind_text(2, &now_value) - .map_err(|err| Error::new_message(&format!("failed to bind now: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind now: {:?}", err)))?; stmt.bind_text(3, &qty_value) - .map_err(|err| Error::new_message(&format!("failed to bind qty: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind qty: {:?}", err)))?; let mut results = Vec::new(); for row in stmt.execute() { let row = - row.map_err(|err| Error::new_message(&format!("failed to read row: {:?}", err)))?; + row.map_err(|err| Error::new_message(format!("failed to read row: {:?}", err)))?; let run_id = row .get::(0) - .map_err(|err| Error::new_message(&format!("failed to read run_id: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read run_id: {:?}", err)))?; let task_id = row .get::(1) - .map_err(|err| Error::new_message(&format!("failed to read task_id: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read task_id: {:?}", err)))?; let attempt = row .get::(2) - .map_err(|err| Error::new_message(&format!("failed to read attempt: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read attempt: {:?}", err)))?; let run_state = row .get::(3) - .map_err(|err| Error::new_message(&format!("failed to read run_state: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read run_state: {:?}", err)))?; let task_name = row .get::(4) - .map_err(|err| Error::new_message(&format!("failed to read task_name: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read task_name: {:?}", err)))?; let params = row .get::(5) - .map_err(|err| Error::new_message(&format!("failed to read params: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read params: {:?}", err)))?; let retry_strategy_raw = row.get::(6).map_err(|err| { - Error::new_message(&format!("failed to read retry_strategy: {:?}", err)) - })?; - let max_attempts_raw = row.get::(7).map_err(|err| { - Error::new_message(&format!("failed to read max_attempts: {:?}", err)) + Error::new_message(format!("failed to read retry_strategy: {:?}", err)) })?; + let max_attempts_raw = row + .get::(7) + .map_err(|err| Error::new_message(format!("failed to read max_attempts: {:?}", err)))?; let headers_raw = row .get::(8) - .map_err(|err| Error::new_message(&format!("failed to read headers: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read headers: {:?}", err)))?; let wake_event_raw = row .get::(9) - .map_err(|err| Error::new_message(&format!("failed to read wake_event: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read wake_event: {:?}", err)))?; let event_payload_raw = row.get::(10).map_err(|err| { - Error::new_message(&format!("failed to read event_payload: {:?}", err)) + Error::new_message(format!("failed to read event_payload: {:?}", err)) })?; results.push(ClaimResult { diff --git a/absurd-sqlite-extension/src/event.rs b/absurd-sqlite-extension/src/event.rs index bf1e7f1..ff90164 100644 --- a/absurd-sqlite-extension/src/event.rs +++ b/absurd-sqlite-extension/src/event.rs @@ -1,13 +1,13 @@ use crate::sql; use crate::validate; use serde_json::Value as JsonValue; +use sqlite3ext_sys::sqlite3; use sqlite_loadable::prelude::*; use sqlite_loadable::{ api, table::{BestIndexError, ConstraintOperator, IndexInfo, VTab, VTabArguments, VTabCursor}, Error, Result, }; -use sqlite3ext_sys::sqlite3; use std::os::raw::c_int; struct AwaitResult { @@ -17,8 +17,6 @@ struct AwaitResult { const FAR_FUTURE_MS: i64 = 9_223_372_036_854_775_000; - - fn parse_optional_timeout_secs(value: Option<*mut sqlite3_value>) -> Result> { let value = match value { Some(value) => value, @@ -31,10 +29,13 @@ fn parse_optional_timeout_secs(value: Option<*mut sqlite3_value>) -> Result api::value_int64(&value), api::ValueType::Text => { let raw = api::value_text(&value) - .map_err(|err| Error::new_message(&format!("timeout must be non-negative integer: {:?}", err)))? + .map_err(|err| { + Error::new_message(format!("timeout must be non-negative integer: {:?}", err)) + })? .trim(); - raw.parse::() - .map_err(|err| Error::new_message(&format!("timeout must be non-negative integer: {:?}", err)))? + raw.parse::().map_err(|err| { + Error::new_message(format!("timeout must be non-negative integer: {:?}", err)) + })? } _ => return Err(Error::new_message("timeout must be non-negative integer")), }; @@ -75,21 +76,23 @@ fn await_event_impl( and task_id = ?2 and checkpoint_name = ?3", ) - .map_err(|err| Error::new_message(&format!("failed to prepare checkpoint lookup: {:?}", err)))?; + .map_err(|err| { + Error::new_message(format!("failed to prepare checkpoint lookup: {:?}", err)) + })?; checkpoint_stmt .bind_text(1, queue_name) - .map_err(|err| Error::new_message(&format!("failed to bind queue_name: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind queue_name: {:?}", err)))?; checkpoint_stmt .bind_text(2, task_id) - .map_err(|err| Error::new_message(&format!("failed to bind task_id: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind task_id: {:?}", err)))?; checkpoint_stmt .bind_text(3, step_name) - .map_err(|err| Error::new_message(&format!("failed to bind step_name: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind step_name: {:?}", err)))?; let mut checkpoint_rows = checkpoint_stmt.execute(); if let Some(Ok(row)) = checkpoint_rows.next() { - let checkpoint_payload = row - .get::(0) - .map_err(|err| Error::new_message(&format!("failed to read checkpoint payload: {:?}", err)))?; + let checkpoint_payload = row.get::(0).map_err(|err| { + Error::new_message(format!("failed to read checkpoint payload: {:?}", err)) + })?; if !checkpoint_payload.is_empty() { return Ok(AwaitResult { should_suspend: 0, @@ -119,30 +122,30 @@ fn await_event_impl( where r.queue_name = ?1 and r.run_id = ?2", ) - .map_err(|err| Error::new_message(&format!("failed to prepare run lookup: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to prepare run lookup: {:?}", err)))?; run_stmt .bind_text(1, queue_name) - .map_err(|err| Error::new_message(&format!("failed to bind queue_name: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind queue_name: {:?}", err)))?; run_stmt .bind_text(2, run_id) - .map_err(|err| Error::new_message(&format!("failed to bind run_id: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind run_id: {:?}", err)))?; let mut run_rows = run_stmt.execute(); let run_row = run_rows .next() .ok_or_else(|| Error::new_message("Run not found while awaiting event"))? - .map_err(|err| Error::new_message(&format!("failed to read run row: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read run row: {:?}", err)))?; let run_state = run_row .get::(0) - .map_err(|err| Error::new_message(&format!("failed to read run state: {:?}", err)))?; - let existing_payload = run_row - .get::(1) - .map_err(|err| Error::new_message(&format!("failed to read event payload: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read run state: {:?}", err)))?; + let existing_payload = run_row.get::(1).map_err(|err| { + Error::new_message(format!("failed to read event payload: {:?}", err)) + })?; let wake_event = run_row .get::(2) - .map_err(|err| Error::new_message(&format!("failed to read wake_event: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read wake_event: {:?}", err)))?; let task_state = run_row .get::(3) - .map_err(|err| Error::new_message(&format!("failed to read task state: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read task state: {:?}", err)))?; if task_state == "cancelled" { return Err(Error::new_message("Task has been cancelled")); @@ -155,21 +158,21 @@ fn await_event_impl( where queue_name = ?1 and event_name = ?2", ) - .map_err(|err| Error::new_message(&format!("failed to prepare event lookup: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to prepare event lookup: {:?}", err)))?; event_stmt .bind_text(1, queue_name) - .map_err(|err| Error::new_message(&format!("failed to bind queue_name: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind queue_name: {:?}", err)))?; event_stmt .bind_text(2, event_name) - .map_err(|err| Error::new_message(&format!("failed to bind event_name: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind event_name: {:?}", err)))?; let mut event_rows = event_stmt.execute(); let event_row = event_rows .next() .ok_or_else(|| Error::new_message("event not found"))? - .map_err(|err| Error::new_message(&format!("failed to read event row: {:?}", err)))?; - let event_payload = event_row - .get::(0) - .map_err(|err| Error::new_message(&format!("failed to read event payload: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read event row: {:?}", err)))?; + let event_payload = event_row.get::(0).map_err(|err| { + Error::new_message(format!("failed to read event payload: {:?}", err)) + })?; let mut resolved_payload: Option = None; @@ -291,8 +294,11 @@ fn await_event_impl( } } -pub fn absurd_emit_event(context: *mut sqlite3_context, values: &[*mut sqlite3_value]) -> Result<()> { - let queue_name = api::value_text_notnull(values.get(0).expect("queue_name"))?; +pub fn absurd_emit_event( + context: *mut sqlite3_context, + values: &[*mut sqlite3_value], +) -> Result<()> { + let queue_name = api::value_text_notnull(values.first().expect("queue_name"))?; let event_name = api::value_text_notnull(values.get(1).expect("event_name"))?; let payload_value = values.get(2); @@ -304,14 +310,17 @@ pub fn absurd_emit_event(context: *mut sqlite3_context, values: &[*mut sqlite3_v "null".to_string() } else { let raw = api::value_text(value) - .map_err(|err| Error::new_message(&format!("payload must be valid JSON: {:?}", err)))? + .map_err(|err| { + Error::new_message(format!("payload must be valid JSON: {:?}", err)) + })? .trim() .to_string(); if raw.is_empty() { return Err(Error::new_message("payload must be valid JSON")); } - let _: JsonValue = serde_json::from_str(&raw) - .map_err(|err| Error::new_message(&format!("payload must be valid JSON: {:?}", err)))?; + let _: JsonValue = serde_json::from_str(&raw).map_err(|err| { + Error::new_message(format!("payload must be valid JSON: {:?}", err)) + })?; raw } } else { @@ -352,28 +361,31 @@ pub fn absurd_emit_event(context: *mut sqlite3_context, values: &[*mut sqlite3_v and event_name = ?2 and (timeout_at is null or timeout_at > cast(?3 as integer))", ) - .map_err(|err| Error::new_message(&format!("failed to prepare absurd_waits lookup: {:?}", err)))?; + .map_err(|err| { + Error::new_message(format!("failed to prepare absurd_waits lookup: {:?}", err)) + })?; waits_stmt .bind_text(1, queue_name) - .map_err(|err| Error::new_message(&format!("failed to bind queue_name: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind queue_name: {:?}", err)))?; waits_stmt .bind_text(2, event_name) - .map_err(|err| Error::new_message(&format!("failed to bind event_name: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind event_name: {:?}", err)))?; waits_stmt .bind_text(3, &now_value) - .map_err(|err| Error::new_message(&format!("failed to bind now: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind now: {:?}", err)))?; for row in waits_stmt.execute() { - let row = row.map_err(|err| Error::new_message(&format!("failed to read wait row: {:?}", err)))?; + let row = row + .map_err(|err| Error::new_message(format!("failed to read wait row: {:?}", err)))?; let run_id = row .get::(0) - .map_err(|err| Error::new_message(&format!("failed to read run_id: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read run_id: {:?}", err)))?; let task_id = row .get::(1) - .map_err(|err| Error::new_message(&format!("failed to read task_id: {:?}", err)))?; - let step_name = row - .get::(2) - .map_err(|err| Error::new_message(&format!("failed to read step_name: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read task_id: {:?}", err)))?; + let step_name = row.get::(2).map_err(|err| { + Error::new_message(format!("failed to read step_name: {:?}", err)) + })?; sql::exec_with_bind_text( db, @@ -600,7 +612,7 @@ impl VTabCursor for AwaitCursor { _idx_str: Option<&str>, values: &[*mut sqlite3_value], ) -> Result<()> { - let queue_name = api::value_text_notnull(values.get(0).expect("queue_name"))?; + let queue_name = api::value_text_notnull(values.first().expect("queue_name"))?; let task_id = api::value_text_notnull(values.get(1).expect("task_id"))?; let run_id = api::value_text_notnull(values.get(2).expect("run_id"))?; let step_name = api::value_text_notnull(values.get(3).expect("step_name"))?; @@ -608,13 +620,7 @@ impl VTabCursor for AwaitCursor { let timeout = parse_optional_timeout_secs(values.get(5).copied())?; let result = await_event_impl( - self.db, - queue_name, - task_id, - run_id, - step_name, - event_name, - timeout, + self.db, queue_name, task_id, run_id, step_name, event_name, timeout, )?; self.result = Some(result); self.rowid = 0; diff --git a/absurd-sqlite-extension/src/lib.rs b/absurd-sqlite-extension/src/lib.rs index 33b17b9..ed83771 100644 --- a/absurd-sqlite-extension/src/lib.rs +++ b/absurd-sqlite-extension/src/lib.rs @@ -1,30 +1,32 @@ -use sqlite_loadable::prelude::*; use sqlite_loadable::exec::Statement; -use sqlite_loadable::{define_scalar_function, define_table_function, Error, FunctionFlags, Result}; +use sqlite_loadable::prelude::*; +use sqlite_loadable::{ + define_scalar_function, define_table_function, Error, FunctionFlags, Result, +}; -mod spawn; -mod claim; -mod retry; -mod run; mod checkpoint; +mod claim; mod event; -mod sql; mod migrate; mod migrations; mod queue; +mod retry; +mod run; mod settings; +mod spawn; +mod sql; mod validate; fn absurd_version(context: *mut sqlite3_context, _values: &[*mut sqlite3_value]) -> Result<()> { let version = env!("CARGO_PKG_VERSION"); - sqlite_loadable::api::result_text(context, &format!("absurd-sqlite/{}", version))?; + sqlite_loadable::api::result_text(context, format!("absurd-sqlite/{}", version))?; Ok(()) } // NOTE: jsonb() is provided by SQLite 3.45+. No local shim needed. fn absurd_create_queue(context: *mut sqlite3_context, values: &[*mut sqlite3_value]) -> Result<()> { let queue_name = - sqlite_loadable::api::value_text_notnull(values.get(0).expect("queue_name is required"))?; + sqlite_loadable::api::value_text_notnull(values.first().expect("queue_name is required"))?; validate::queue_name(queue_name)?; let db = sqlite_loadable::api::context_db_handle(context); @@ -39,7 +41,7 @@ fn absurd_create_queue(context: *mut sqlite3_context, values: &[*mut sqlite3_val fn absurd_drop_queue(context: *mut sqlite3_context, values: &[*mut sqlite3_value]) -> Result<()> { let queue_name = - sqlite_loadable::api::value_text_notnull(values.get(0).expect("queue_name is required"))?; + sqlite_loadable::api::value_text_notnull(values.first().expect("queue_name is required"))?; validate::queue_name(queue_name)?; let db = sqlite_loadable::api::context_db_handle(context); @@ -66,15 +68,63 @@ fn absurd_init(db: *mut sqlite3) -> Result<()> { define_scalar_function(db, "absurd_version", 0, absurd_version, flags)?; define_scalar_function(db, "absurd_create_queue", 1, absurd_create_queue, flags)?; define_scalar_function(db, "absurd_drop_queue", 1, absurd_drop_queue, flags)?; - define_scalar_function(db, "absurd_complete_run", 3, run::absurd_complete_run, flags)?; - define_scalar_function(db, "absurd_schedule_run", 3, run::absurd_schedule_run, flags)?; - define_scalar_function(db, "absurd_fail_run", 3, run::absurd_fail_run_no_retry, flags)?; + define_scalar_function( + db, + "absurd_complete_run", + 3, + run::absurd_complete_run, + flags, + )?; + define_scalar_function( + db, + "absurd_schedule_run", + 3, + run::absurd_schedule_run, + flags, + )?; + define_scalar_function( + db, + "absurd_fail_run", + 3, + run::absurd_fail_run_no_retry, + flags, + )?; define_scalar_function(db, "absurd_fail_run", 4, run::absurd_fail_run, flags)?; - define_scalar_function(db, "absurd_extend_claim", 3, run::absurd_extend_claim, flags)?; - define_scalar_function(db, "absurd_cleanup_tasks", 2, run::absurd_cleanup_tasks, flags)?; - define_scalar_function(db, "absurd_cleanup_tasks", 3, run::absurd_cleanup_tasks, flags)?; - define_scalar_function(db, "absurd_cleanup_events", 2, run::absurd_cleanup_events, flags)?; - define_scalar_function(db, "absurd_cleanup_events", 3, run::absurd_cleanup_events, flags)?; + define_scalar_function( + db, + "absurd_extend_claim", + 3, + run::absurd_extend_claim, + flags, + )?; + define_scalar_function( + db, + "absurd_cleanup_tasks", + 2, + run::absurd_cleanup_tasks, + flags, + )?; + define_scalar_function( + db, + "absurd_cleanup_tasks", + 3, + run::absurd_cleanup_tasks, + flags, + )?; + define_scalar_function( + db, + "absurd_cleanup_events", + 2, + run::absurd_cleanup_events, + flags, + )?; + define_scalar_function( + db, + "absurd_cleanup_events", + 3, + run::absurd_cleanup_events, + flags, + )?; define_scalar_function(db, "absurd_cancel_task", 2, run::absurd_cancel_task, flags)?; define_scalar_function( db, @@ -179,9 +229,7 @@ mod tests { #[test] fn test_absurd_version() { unsafe { - sqlite3_auto_extension(Some(std::mem::transmute( - sqlite3_absurd_init as *const (), - ))); + sqlite3_auto_extension(Some(std::mem::transmute(sqlite3_absurd_init as *const ()))); } let conn = Connection::open_in_memory().unwrap(); @@ -193,15 +241,16 @@ mod tests { .query_row("select absurd_version()", [], |row| row.get(0)) .unwrap(); - assert_eq!(result, format!("absurd-sqlite/{}", env!("CARGO_PKG_VERSION"))); + assert_eq!( + result, + format!("absurd-sqlite/{}", env!("CARGO_PKG_VERSION")) + ); } #[test] fn test_apply_migrations_and_records() { unsafe { - sqlite3_auto_extension(Some(std::mem::transmute( - sqlite3_absurd_init as *const (), - ))); + sqlite3_auto_extension(Some(std::mem::transmute(sqlite3_absurd_init as *const ()))); } let conn = Connection::open_in_memory().unwrap(); @@ -211,9 +260,11 @@ mod tests { assert!(applied >= 1); let count: i64 = conn - .query_row("select count(*) from absurd_migration_records()", [], |row| { - row.get(0) - }) + .query_row( + "select count(*) from absurd_migration_records()", + [], + |row| row.get(0), + ) .unwrap(); assert!(count >= 1); @@ -241,9 +292,7 @@ mod tests { #[test] fn test_set_fake_now() { unsafe { - sqlite3_auto_extension(Some(std::mem::transmute( - sqlite3_absurd_init as *const (), - ))); + sqlite3_auto_extension(Some(std::mem::transmute(sqlite3_absurd_init as *const ()))); } let conn = Connection::open_in_memory().unwrap(); @@ -270,9 +319,7 @@ mod tests { #[test] fn test_apply_migrations_to_target() { unsafe { - sqlite3_auto_extension(Some(std::mem::transmute( - sqlite3_absurd_init as *const (), - ))); + sqlite3_auto_extension(Some(std::mem::transmute(sqlite3_absurd_init as *const ()))); } let conn = Connection::open_in_memory().unwrap(); @@ -285,9 +332,7 @@ mod tests { #[test] fn test_list_queues() { unsafe { - sqlite3_auto_extension(Some(std::mem::transmute( - sqlite3_absurd_init as *const (), - ))); + sqlite3_auto_extension(Some(std::mem::transmute(sqlite3_absurd_init as *const ()))); } let conn = Connection::open_in_memory().unwrap(); @@ -305,9 +350,7 @@ mod tests { let mut stmt = conn .prepare("select queue_name from absurd_list_queues() order by queue_name") .unwrap(); - let rows = stmt - .query_map([], |row| row.get::<_, String>(0)) - .unwrap(); + let rows = stmt.query_map([], |row| row.get::<_, String>(0)).unwrap(); let queues: Vec = rows.map(|row| row.unwrap()).collect(); assert_eq!(queues, vec!["alpha".to_string(), "beta".to_string()]); } @@ -315,9 +358,7 @@ mod tests { #[test] fn test_drop_queue() { unsafe { - sqlite3_auto_extension(Some(std::mem::transmute( - sqlite3_absurd_init as *const (), - ))); + sqlite3_auto_extension(Some(std::mem::transmute(sqlite3_absurd_init as *const ()))); } let conn = Connection::open_in_memory().unwrap(); @@ -343,9 +384,7 @@ mod tests { #[test] fn test_spawn_task_basic() { unsafe { - sqlite3_auto_extension(Some(std::mem::transmute( - sqlite3_absurd_init as *const (), - ))); + sqlite3_auto_extension(Some(std::mem::transmute(sqlite3_absurd_init as *const ()))); } let conn = Connection::open_in_memory().unwrap(); @@ -372,9 +411,7 @@ mod tests { #[test] fn test_claim_task_basic() { unsafe { - sqlite3_auto_extension(Some(std::mem::transmute( - sqlite3_absurd_init as *const (), - ))); + sqlite3_auto_extension(Some(std::mem::transmute(sqlite3_absurd_init as *const ()))); } let conn = Connection::open_in_memory().unwrap(); @@ -421,9 +458,7 @@ mod tests { #[test] fn test_claim_task_json_outputs() { unsafe { - sqlite3_auto_extension(Some(std::mem::transmute( - sqlite3_absurd_init as *const (), - ))); + sqlite3_auto_extension(Some(std::mem::transmute(sqlite3_absurd_init as *const ()))); } let conn = Connection::open_in_memory().unwrap(); @@ -469,9 +504,7 @@ mod tests { #[test] fn test_claim_task_params_decode_client_side() { unsafe { - sqlite3_auto_extension(Some(std::mem::transmute( - sqlite3_absurd_init as *const (), - ))); + sqlite3_auto_extension(Some(std::mem::transmute(sqlite3_absurd_init as *const ()))); } let conn = Connection::open_in_memory().unwrap(); @@ -506,9 +539,7 @@ mod tests { fn table_column_types(conn: &Connection, table: &str) -> HashMap { let pragma = format!("pragma table_xinfo({})", table); - let mut stmt = conn - .prepare(&pragma) - .expect("prepare table_xinfo"); + let mut stmt = conn.prepare(&pragma).expect("prepare table_xinfo"); let rows = stmt .query_map([], |row| { let name: String = row.get(1)?; @@ -527,9 +558,7 @@ mod tests { #[test] fn test_table_function_declared_types() { unsafe { - sqlite3_auto_extension(Some(std::mem::transmute( - sqlite3_absurd_init as *const (), - ))); + sqlite3_auto_extension(Some(std::mem::transmute(sqlite3_absurd_init as *const ()))); } let conn = Connection::open_in_memory().unwrap(); @@ -599,9 +628,7 @@ mod tests { #[test] fn test_schedule_run_basic() { unsafe { - sqlite3_auto_extension(Some(std::mem::transmute( - sqlite3_absurd_init as *const (), - ))); + sqlite3_auto_extension(Some(std::mem::transmute(sqlite3_absurd_init as *const ()))); } let conn = Connection::open_in_memory().unwrap(); @@ -661,9 +688,7 @@ mod tests { #[test] fn test_fail_run_basic() { unsafe { - sqlite3_auto_extension(Some(std::mem::transmute( - sqlite3_absurd_init as *const (), - ))); + sqlite3_auto_extension(Some(std::mem::transmute(sqlite3_absurd_init as *const ()))); } let conn = Connection::open_in_memory().unwrap(); @@ -724,9 +749,7 @@ mod tests { #[test] fn test_complete_run_basic() { unsafe { - sqlite3_auto_extension(Some(std::mem::transmute( - sqlite3_absurd_init as *const (), - ))); + sqlite3_auto_extension(Some(std::mem::transmute(sqlite3_absurd_init as *const ()))); } let conn = Connection::open_in_memory().unwrap(); @@ -787,9 +810,7 @@ mod tests { #[test] fn test_extend_claim_basic() { unsafe { - sqlite3_auto_extension(Some(std::mem::transmute( - sqlite3_absurd_init as *const (), - ))); + sqlite3_auto_extension(Some(std::mem::transmute(sqlite3_absurd_init as *const ()))); } let conn = Connection::open_in_memory().unwrap(); @@ -847,9 +868,7 @@ mod tests { #[test] fn test_checkpoint_roundtrip() { unsafe { - sqlite3_auto_extension(Some(std::mem::transmute( - sqlite3_absurd_init as *const (), - ))); + sqlite3_auto_extension(Some(std::mem::transmute(sqlite3_absurd_init as *const ()))); } let conn = Connection::open_in_memory().unwrap(); @@ -910,9 +929,7 @@ mod tests { #[test] fn test_await_and_emit_event() { unsafe { - sqlite3_auto_extension(Some(std::mem::transmute( - sqlite3_absurd_init as *const (), - ))); + sqlite3_auto_extension(Some(std::mem::transmute(sqlite3_absurd_init as *const ()))); } let conn = Connection::open_in_memory().unwrap(); @@ -981,9 +998,7 @@ mod tests { #[test] fn test_cleanup_tasks_and_events() { unsafe { - sqlite3_auto_extension(Some(std::mem::transmute( - sqlite3_absurd_init as *const (), - ))); + sqlite3_auto_extension(Some(std::mem::transmute(sqlite3_absurd_init as *const ()))); } let conn = Connection::open_in_memory().unwrap(); @@ -1027,11 +1042,9 @@ mod tests { .unwrap(); let _: Option = conn - .query_row( - "select absurd_emit_event('alpha', 'eventA')", - [], - |r| r.get(0), - ) + .query_row("select absurd_emit_event('alpha', 'eventA')", [], |r| { + r.get(0) + }) .unwrap(); conn.execute( "update absurd_events set emitted_at = 1 where queue_name = 'alpha' and event_name = 'eventA'", @@ -1040,20 +1053,16 @@ mod tests { .unwrap(); let deleted_tasks: i64 = conn - .query_row( - "select absurd_cleanup_tasks('alpha', 1, 100)", - [], - |r| r.get(0), - ) + .query_row("select absurd_cleanup_tasks('alpha', 1, 100)", [], |r| { + r.get(0) + }) .unwrap(); assert_eq!(deleted_tasks, 1); let deleted_events: i64 = conn - .query_row( - "select absurd_cleanup_events('alpha', 1, 100)", - [], - |r| r.get(0), - ) + .query_row("select absurd_cleanup_events('alpha', 1, 100)", [], |r| { + r.get(0) + }) .unwrap(); assert_eq!(deleted_events, 1); } @@ -1061,9 +1070,7 @@ mod tests { #[test] fn test_cancel_task_basic() { unsafe { - sqlite3_auto_extension(Some(std::mem::transmute( - sqlite3_absurd_init as *const (), - ))); + sqlite3_auto_extension(Some(std::mem::transmute(sqlite3_absurd_init as *const ()))); } let conn = Connection::open_in_memory().unwrap(); @@ -1084,11 +1091,9 @@ mod tests { .unwrap(); let _: Option = conn - .query_row( - "select absurd_cancel_task('alpha', ?1)", - [&row.0], - |r| r.get(0), - ) + .query_row("select absurd_cancel_task('alpha', ?1)", [&row.0], |r| { + r.get(0) + }) .unwrap(); let task_state: String = conn diff --git a/absurd-sqlite-extension/src/migrate.rs b/absurd-sqlite-extension/src/migrate.rs index 393a6c7..6729685 100644 --- a/absurd-sqlite-extension/src/migrate.rs +++ b/absurd-sqlite-extension/src/migrate.rs @@ -26,14 +26,14 @@ fn parse_optional_int(value: Option<*mut sqlite3_value>) -> Result> api::ValueType::Integer => Ok(Some(api::value_int64(&value))), api::ValueType::Text => { let raw = api::value_text(&value) - .map_err(|err| Error::new_message(&format!("to must be integer: {:?}", err)))? + .map_err(|err| Error::new_message(format!("to must be integer: {:?}", err)))? .trim(); if raw.is_empty() { return Ok(None); } raw.parse::() .map(Some) - .map_err(|err| Error::new_message(&format!("to must be integer: {:?}", err))) + .map_err(|err| Error::new_message(format!("to must be integer: {:?}", err))) } _ => Err(Error::new_message("to must be integer")), } @@ -47,7 +47,7 @@ pub fn absurd_apply_migrations( context: *mut sqlite3_context, values: &[*mut sqlite3_value], ) -> Result<()> { - let target = parse_optional_int(values.get(0).copied())?; + let target = parse_optional_int(values.first().copied())?; let db = api::context_db_handle(context); sql::exec_with_bind_text(db, "begin immediate", &[])?; @@ -60,12 +60,16 @@ pub fn absurd_apply_migrations( db, "select id from absurd_migrations order by id", ) - .map_err(|err| Error::new_message(&format!("failed to prepare migrations lookup: {:?}", err)))?; + .map_err(|err| { + Error::new_message(format!("failed to prepare migrations lookup: {:?}", err)) + })?; for row in stmt.execute() { - let row = row.map_err(|err| Error::new_message(&format!("failed to read migration row: {:?}", err)))?; - let id = row - .get::(0) - .map_err(|err| Error::new_message(&format!("failed to read migration id: {:?}", err)))?; + let row = row.map_err(|err| { + Error::new_message(format!("failed to read migration row: {:?}", err)) + })?; + let id = row.get::(0).map_err(|err| { + Error::new_message(format!("failed to read migration id: {:?}", err)) + })?; applied_ids.push(id); } let max_applied = applied_ids.iter().max().cloned().unwrap_or(0); @@ -192,19 +196,23 @@ impl VTabCursor for MigrationRecordsCursor { self.db, "select id, introduced_version, applied_time from absurd_migrations order by id", ) - .map_err(|err| Error::new_message(&format!("failed to prepare migration records: {:?}", err)))?; + .map_err(|err| { + Error::new_message(format!("failed to prepare migration records: {:?}", err)) + })?; let mut rows = Vec::new(); for row in stmt.execute() { - let row = row.map_err(|err| Error::new_message(&format!("failed to read migration record: {:?}", err)))?; - let id = row - .get::(0) - .map_err(|err| Error::new_message(&format!("failed to read migration id: {:?}", err)))?; - let introduced_version = row - .get::(1) - .map_err(|err| Error::new_message(&format!("failed to read introduced_version: {:?}", err)))?; - let applied_time = row - .get::(2) - .map_err(|err| Error::new_message(&format!("failed to read applied_time: {:?}", err)))?; + let row = row.map_err(|err| { + Error::new_message(format!("failed to read migration record: {:?}", err)) + })?; + let id = row.get::(0).map_err(|err| { + Error::new_message(format!("failed to read migration id: {:?}", err)) + })?; + let introduced_version = row.get::(1).map_err(|err| { + Error::new_message(format!("failed to read introduced_version: {:?}", err)) + })?; + let applied_time = row.get::(2).map_err(|err| { + Error::new_message(format!("failed to read applied_time: {:?}", err)) + })?; rows.push(MigrationRecord { id, introduced_version, diff --git a/absurd-sqlite-extension/src/queue.rs b/absurd-sqlite-extension/src/queue.rs index fed440a..f948312 100644 --- a/absurd-sqlite-extension/src/queue.rs +++ b/absurd-sqlite-extension/src/queue.rs @@ -81,16 +81,18 @@ impl VTabCursor for ListQueuesCursor { self.db, "select queue_name, created_at from absurd_queues order by queue_name", ) - .map_err(|err| Error::new_message(&format!("failed to prepare queue list: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to prepare queue list: {:?}", err)))?; let mut rows = Vec::new(); for row in stmt.execute() { - let row = row.map_err(|err| Error::new_message(&format!("failed to read queue row: {:?}", err)))?; - let queue_name = row - .get::(0) - .map_err(|err| Error::new_message(&format!("failed to read queue_name: {:?}", err)))?; - let created_at = row - .get::(1) - .map_err(|err| Error::new_message(&format!("failed to read created_at: {:?}", err)))?; + let row = row.map_err(|err| { + Error::new_message(format!("failed to read queue row: {:?}", err)) + })?; + let queue_name = row.get::(0).map_err(|err| { + Error::new_message(format!("failed to read queue_name: {:?}", err)) + })?; + let created_at = row.get::(1).map_err(|err| { + Error::new_message(format!("failed to read created_at: {:?}", err)) + })?; rows.push(QueueRow { queue_name, created_at, diff --git a/absurd-sqlite-extension/src/retry.rs b/absurd-sqlite-extension/src/retry.rs index 0e9ff7b..3b5de15 100644 --- a/absurd-sqlite-extension/src/retry.rs +++ b/absurd-sqlite-extension/src/retry.rs @@ -5,16 +5,13 @@ pub fn parse_retry_strategy(raw: &str, attempt: i64) -> Result { if raw.trim().is_empty() { return Ok(0); } - let parsed: JsonValue = - serde_json::from_str(raw).map_err(|err| Error::new_message(&format!("invalid retry_strategy JSON: {:?}", err)))?; + let parsed: JsonValue = serde_json::from_str(raw) + .map_err(|err| Error::new_message(format!("invalid retry_strategy JSON: {:?}", err)))?; let obj = match parsed.as_object() { Some(obj) => obj, None => return Ok(0), }; - let kind = obj - .get("kind") - .and_then(|v| v.as_str()) - .unwrap_or("none"); + let kind = obj.get("kind").and_then(|v| v.as_str()).unwrap_or("none"); let delay_seconds = match kind { "fixed" => obj .get("base_seconds") @@ -25,10 +22,7 @@ pub fn parse_retry_strategy(raw: &str, attempt: i64) -> Result { .get("base_seconds") .and_then(|v| v.as_f64()) .unwrap_or(30.0); - let factor = obj - .get("factor") - .and_then(|v| v.as_f64()) - .unwrap_or(2.0); + let factor = obj.get("factor").and_then(|v| v.as_f64()).unwrap_or(2.0); let mut delay = base * factor.powf((attempt.saturating_sub(1)) as f64); if let Some(max) = obj.get("max_seconds").and_then(|v| v.as_f64()) { if delay > max { @@ -47,8 +41,8 @@ pub fn parse_cancellation_max_duration(raw: &str) -> Result> { if raw.trim().is_empty() { return Ok(None); } - let parsed: JsonValue = - serde_json::from_str(raw).map_err(|err| Error::new_message(&format!("invalid cancellation JSON: {:?}", err)))?; + let parsed: JsonValue = serde_json::from_str(raw) + .map_err(|err| Error::new_message(format!("invalid cancellation JSON: {:?}", err)))?; let obj = match parsed.as_object() { Some(obj) => obj, None => return Ok(None), diff --git a/absurd-sqlite-extension/src/run.rs b/absurd-sqlite-extension/src/run.rs index e65cc28..ed5cbd3 100644 --- a/absurd-sqlite-extension/src/run.rs +++ b/absurd-sqlite-extension/src/run.rs @@ -18,19 +18,17 @@ fn parse_optional_time_ms(value: &*mut sqlite3_value, label: &str) -> Result() { + Ok(Some(parsed)) + } else if let Ok(parsed) = DateTime::parse_from_rfc3339(raw) { + Ok(Some(parsed.timestamp_millis())) } else { - if let Ok(parsed) = raw.parse::() { - Ok(Some(parsed)) - } else if let Ok(parsed) = DateTime::parse_from_rfc3339(raw) { - Ok(Some(parsed.timestamp_millis())) - } else { - Err(Error::new_message(&format!( - "{label} must be integer ms or RFC3339" - ))) - } + Err(Error::new_message(format!( + "{label} must be integer ms or RFC3339" + ))) } } - _ => Err(Error::new_message(&format!( + _ => Err(Error::new_message(format!( "{label} must be integer ms or RFC3339" ))), } @@ -38,7 +36,7 @@ fn parse_optional_time_ms(value: &*mut sqlite3_value, label: &str) -> Result Result { if api::value_is_null(value) { - return Err(Error::new_message(&format!("{label} must be provided"))); + return Err(Error::new_message(format!("{label} must be provided"))); } match api::value_type(value) { api::ValueType::Integer => Ok(api::value_int64(value)), @@ -46,14 +44,14 @@ fn parse_required_int(value: &*mut sqlite3_value, label: &str) -> Result { api::ValueType::Text => { let raw = api::value_text(value).unwrap_or("").trim(); if raw.is_empty() { - Err(Error::new_message(&format!("{label} must be provided"))) + Err(Error::new_message(format!("{label} must be provided"))) } else { raw.parse::().map_err(|err| { - Error::new_message(&format!("{label} must be integer: {:?}", err)) + Error::new_message(format!("{label} must be integer: {:?}", err)) }) } } - _ => Err(Error::new_message(&format!("{label} must be integer",))), + _ => Err(Error::new_message(format!("{label} must be integer",))), } } @@ -91,7 +89,7 @@ fn fail_run_impl( ) -> Result<()> { validate::queue_name(queue_name)?; let _: JsonValue = serde_json::from_str(reason) - .map_err(|err| Error::new_message(&format!("reason must be valid JSON: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("reason must be valid JSON: {:?}", err)))?; let now = sql::now_ms_from_db(db); let now_value = now.to_string(); @@ -108,22 +106,22 @@ fn fail_run_impl( and run_id = ?2 and state in ('running','sleeping')", ) - .map_err(|err| Error::new_message(&format!("failed to prepare run lookup: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to prepare run lookup: {:?}", err)))?; stmt.bind_text(1, queue_name) - .map_err(|err| Error::new_message(&format!("failed to bind queue_name: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind queue_name: {:?}", err)))?; stmt.bind_text(2, run_id) - .map_err(|err| Error::new_message(&format!("failed to bind run_id: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind run_id: {:?}", err)))?; let mut rows = stmt.execute(); let row = rows .next() .ok_or_else(|| Error::new_message("run not found or not active"))? - .map_err(|err| Error::new_message(&format!("failed to read run row: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read run row: {:?}", err)))?; let task_id = row .get::(0) - .map_err(|err| Error::new_message(&format!("failed to read task_id: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read task_id: {:?}", err)))?; let attempt = row .get::(1) - .map_err(|err| Error::new_message(&format!("failed to read attempt: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read attempt: {:?}", err)))?; let mut task_stmt = sqlite_loadable::exec::Statement::prepare( db, @@ -135,30 +133,30 @@ fn fail_run_impl( where queue_name = ?1 and task_id = ?2", ) - .map_err(|err| Error::new_message(&format!("failed to prepare task lookup: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to prepare task lookup: {:?}", err)))?; task_stmt .bind_text(1, queue_name) - .map_err(|err| Error::new_message(&format!("failed to bind queue_name: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind queue_name: {:?}", err)))?; task_stmt .bind_text(2, &task_id) - .map_err(|err| Error::new_message(&format!("failed to bind task_id: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind task_id: {:?}", err)))?; let mut task_rows = task_stmt.execute(); let task_row = task_rows .next() .ok_or_else(|| Error::new_message("task not found"))? - .map_err(|err| Error::new_message(&format!("failed to read task row: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read task row: {:?}", err)))?; let retry_strategy = task_row.get::(0).map_err(|err| { - Error::new_message(&format!("failed to read retry_strategy: {:?}", err)) - })?; - let max_attempts = task_row.get::(1).map_err(|err| { - Error::new_message(&format!("failed to read max_attempts: {:?}", err)) + Error::new_message(format!("failed to read retry_strategy: {:?}", err)) })?; + let max_attempts = task_row + .get::(1) + .map_err(|err| Error::new_message(format!("failed to read max_attempts: {:?}", err)))?; let first_started_at = task_row.get::(2).map_err(|err| { - Error::new_message(&format!("failed to read first_started_at: {:?}", err)) - })?; - let cancellation = task_row.get::(3).map_err(|err| { - Error::new_message(&format!("failed to read cancellation: {:?}", err)) + Error::new_message(format!("failed to read first_started_at: {:?}", err)) })?; + let cancellation = task_row + .get::(3) + .map_err(|err| Error::new_message(format!("failed to read cancellation: {:?}", err)))?; sql::exec_with_bind_text( db, @@ -180,7 +178,7 @@ fn fail_run_impl( } else { Some(max_attempts) }; - let allow_retry = max_attempts_opt.map_or(true, |max| next_attempt <= max); + let allow_retry = max_attempts_opt.is_none_or(|max| next_attempt <= max); let mut task_state = "failed"; let mut last_attempt_run = run_id.to_string(); let mut cancelled_at = ""; @@ -306,7 +304,7 @@ pub fn absurd_complete_run( context: *mut sqlite3_context, values: &[*mut sqlite3_value], ) -> Result<()> { - let queue_name = api::value_text_notnull(values.get(0).expect("queue_name"))?; + let queue_name = api::value_text_notnull(values.first().expect("queue_name"))?; let run_id = api::value_text_notnull(values.get(1).expect("run_id"))?; let state_value = values.get(2); let state_text = state_value @@ -326,7 +324,7 @@ pub fn absurd_complete_run( if !state_text.is_empty() { let _: JsonValue = serde_json::from_str(&state_text) - .map_err(|err| Error::new_message(&format!("state must be valid JSON: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("state must be valid JSON: {:?}", err)))?; } validate::queue_name(queue_name)?; @@ -343,22 +341,22 @@ pub fn absurd_complete_run( where queue_name = ?1 and run_id = ?2", ) - .map_err(|err| Error::new_message(&format!("failed to prepare run lookup: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to prepare run lookup: {:?}", err)))?; stmt.bind_text(1, queue_name) - .map_err(|err| Error::new_message(&format!("failed to bind queue_name: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind queue_name: {:?}", err)))?; stmt.bind_text(2, run_id) - .map_err(|err| Error::new_message(&format!("failed to bind run_id: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind run_id: {:?}", err)))?; let mut rows = stmt.execute(); let row = rows .next() .ok_or_else(|| Error::new_message("run not found"))? - .map_err(|err| Error::new_message(&format!("failed to read run row: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read run row: {:?}", err)))?; let task_id = row .get::(0) - .map_err(|err| Error::new_message(&format!("failed to read task_id: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read task_id: {:?}", err)))?; let run_state = row .get::(1) - .map_err(|err| Error::new_message(&format!("failed to read state: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read state: {:?}", err)))?; if run_state != "running" { return Err(Error::new_message("run is not currently running")); @@ -410,7 +408,7 @@ pub fn absurd_complete_run( } pub fn absurd_fail_run(context: *mut sqlite3_context, values: &[*mut sqlite3_value]) -> Result<()> { - let queue_name = api::value_text_notnull(values.get(0).expect("queue_name"))?; + let queue_name = api::value_text_notnull(values.first().expect("queue_name"))?; let run_id = api::value_text_notnull(values.get(1).expect("run_id"))?; let reason = api::value_text_notnull(values.get(2).expect("reason"))?; let retry_at = values @@ -428,7 +426,7 @@ pub fn absurd_fail_run_no_retry( context: *mut sqlite3_context, values: &[*mut sqlite3_value], ) -> Result<()> { - let queue_name = api::value_text_notnull(values.get(0).expect("queue_name"))?; + let queue_name = api::value_text_notnull(values.first().expect("queue_name"))?; let run_id = api::value_text_notnull(values.get(1).expect("run_id"))?; let reason = api::value_text_notnull(values.get(2).expect("reason"))?; let db = api::context_db_handle(context); @@ -440,7 +438,7 @@ pub fn absurd_extend_claim( context: *mut sqlite3_context, values: &[*mut sqlite3_value], ) -> Result<()> { - let queue_name = api::value_text_notnull(values.get(0).expect("queue_name"))?; + let queue_name = api::value_text_notnull(values.first().expect("queue_name"))?; let run_id = api::value_text_notnull(values.get(1).expect("run_id"))?; let extend_by = parse_required_int(values.get(2).expect("extend_by"), "extend_by")?; @@ -463,19 +461,19 @@ pub fn absurd_extend_claim( where r.queue_name = ?1 and r.run_id = ?2", ) - .map_err(|err| Error::new_message(&format!("failed to prepare task lookup: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to prepare task lookup: {:?}", err)))?; stmt.bind_text(1, queue_name) - .map_err(|err| Error::new_message(&format!("failed to bind queue_name: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind queue_name: {:?}", err)))?; stmt.bind_text(2, run_id) - .map_err(|err| Error::new_message(&format!("failed to bind run_id: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind run_id: {:?}", err)))?; let mut rows = stmt.execute(); let row = rows .next() .ok_or_else(|| Error::new_message("run not found"))? - .map_err(|err| Error::new_message(&format!("failed to read task row: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read task row: {:?}", err)))?; let task_state = row .get::(0) - .map_err(|err| Error::new_message(&format!("failed to read state: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read state: {:?}", err)))?; if task_state == "cancelled" { return Err(Error::new_message("Task has been cancelled")); @@ -516,7 +514,7 @@ pub fn absurd_schedule_run( context: *mut sqlite3_context, values: &[*mut sqlite3_value], ) -> Result<()> { - let queue_name = api::value_text_notnull(values.get(0).expect("queue_name"))?; + let queue_name = api::value_text_notnull(values.first().expect("queue_name"))?; let run_id = api::value_text_notnull(values.get(1).expect("run_id"))?; let wake_at = parse_optional_time_ms(values.get(2).expect("wake_at"), "wake_at")? .ok_or_else(|| Error::new_message("wake_at must be provided"))?; @@ -534,19 +532,19 @@ pub fn absurd_schedule_run( and run_id = ?2 and state = 'running'", ) - .map_err(|err| Error::new_message(&format!("failed to prepare run lookup: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to prepare run lookup: {:?}", err)))?; stmt.bind_text(1, queue_name) - .map_err(|err| Error::new_message(&format!("failed to bind queue_name: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind queue_name: {:?}", err)))?; stmt.bind_text(2, run_id) - .map_err(|err| Error::new_message(&format!("failed to bind run_id: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind run_id: {:?}", err)))?; let mut rows = stmt.execute(); let row = rows .next() .ok_or_else(|| Error::new_message("run not running"))? - .map_err(|err| Error::new_message(&format!("failed to read run row: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read run row: {:?}", err)))?; let task_id = row .get::(0) - .map_err(|err| Error::new_message(&format!("failed to read task_id: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read task_id: {:?}", err)))?; let wake_at_value = wake_at.to_string(); sql::exec_with_bind_text( @@ -590,7 +588,7 @@ pub fn absurd_cleanup_tasks( context: *mut sqlite3_context, values: &[*mut sqlite3_value], ) -> Result<()> { - let queue_name = api::value_text_notnull(values.get(0).expect("queue_name"))?; + let queue_name = api::value_text_notnull(values.first().expect("queue_name"))?; let ttl_seconds = parse_required_ttl_secs(values.get(1).expect("ttl_seconds"), "ttl_seconds")?; let limit = parse_optional_limit(values.get(2).copied())?; @@ -628,29 +626,25 @@ pub fn absurd_cleanup_tasks( limit cast(?3 as integer)", ) .map_err(|err| { - Error::new_message(&format!( - "failed to prepare cleanup_tasks select: {:?}", - err - )) + Error::new_message(format!("failed to prepare cleanup_tasks select: {:?}", err)) })?; select_stmt .bind_text(1, queue_name) - .map_err(|err| Error::new_message(&format!("failed to bind queue_name: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind queue_name: {:?}", err)))?; select_stmt .bind_text(2, &cutoff_value) - .map_err(|err| Error::new_message(&format!("failed to bind cutoff: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind cutoff: {:?}", err)))?; select_stmt .bind_text(3, &limit_value) - .map_err(|err| Error::new_message(&format!("failed to bind limit: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind limit: {:?}", err)))?; let mut task_ids = Vec::new(); for row in select_stmt.execute() { - let row = row.map_err(|err| { - Error::new_message(&format!("failed to read task row: {:?}", err)) - })?; + let row = row + .map_err(|err| Error::new_message(format!("failed to read task row: {:?}", err)))?; let task_id = row .get::(0) - .map_err(|err| Error::new_message(&format!("failed to read task_id: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read task_id: {:?}", err)))?; task_ids.push(task_id); } drop(select_stmt); @@ -694,7 +688,7 @@ pub fn absurd_cleanup_events( context: *mut sqlite3_context, values: &[*mut sqlite3_value], ) -> Result<()> { - let queue_name = api::value_text_notnull(values.get(0).expect("queue_name"))?; + let queue_name = api::value_text_notnull(values.first().expect("queue_name"))?; let ttl_seconds = parse_required_ttl_secs(values.get(1).expect("ttl_seconds"), "ttl_seconds")?; let limit = parse_optional_limit(values.get(2).copied())?; @@ -718,28 +712,28 @@ pub fn absurd_cleanup_events( limit cast(?3 as integer)", ) .map_err(|err| { - Error::new_message(&format!( + Error::new_message(format!( "failed to prepare cleanup_events select: {:?}", err )) })?; select_stmt .bind_text(1, queue_name) - .map_err(|err| Error::new_message(&format!("failed to bind queue_name: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind queue_name: {:?}", err)))?; select_stmt .bind_text(2, &cutoff_value) - .map_err(|err| Error::new_message(&format!("failed to bind cutoff: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind cutoff: {:?}", err)))?; select_stmt .bind_text(3, &limit_value) - .map_err(|err| Error::new_message(&format!("failed to bind limit: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind limit: {:?}", err)))?; let mut deleted_count = 0; for row in select_stmt.execute() { let row = row.map_err(|err| { - Error::new_message(&format!("failed to read event row: {:?}", err)) + Error::new_message(format!("failed to read event row: {:?}", err)) })?; let event_name = row.get::(0).map_err(|err| { - Error::new_message(&format!("failed to read event_name: {:?}", err)) + Error::new_message(format!("failed to read event_name: {:?}", err)) })?; sql::exec_with_bind_text( db, @@ -775,7 +769,7 @@ pub fn absurd_cancel_task( context: *mut sqlite3_context, values: &[*mut sqlite3_value], ) -> Result<()> { - let queue_name = api::value_text_notnull(values.get(0).expect("queue_name"))?; + let queue_name = api::value_text_notnull(values.first().expect("queue_name"))?; let task_id = api::value_text_notnull(values.get(1).expect("task_id"))?; validate::queue_name(queue_name)?; @@ -792,19 +786,19 @@ pub fn absurd_cancel_task( where queue_name = ?1 and task_id = ?2", ) - .map_err(|err| Error::new_message(&format!("failed to prepare task lookup: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to prepare task lookup: {:?}", err)))?; stmt.bind_text(1, queue_name) - .map_err(|err| Error::new_message(&format!("failed to bind queue_name: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind queue_name: {:?}", err)))?; stmt.bind_text(2, task_id) - .map_err(|err| Error::new_message(&format!("failed to bind task_id: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind task_id: {:?}", err)))?; let mut rows = stmt.execute(); let row = rows .next() .ok_or_else(|| Error::new_message("Task not found"))? - .map_err(|err| Error::new_message(&format!("failed to read task row: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read task row: {:?}", err)))?; let task_state = row .get::(0) - .map_err(|err| Error::new_message(&format!("failed to read task state: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read task state: {:?}", err)))?; if task_state == "completed" || task_state == "failed" || task_state == "cancelled" { return Ok(()); diff --git a/absurd-sqlite-extension/src/settings.rs b/absurd-sqlite-extension/src/settings.rs index ceae783..927fde4 100644 --- a/absurd-sqlite-extension/src/settings.rs +++ b/absurd-sqlite-extension/src/settings.rs @@ -17,14 +17,14 @@ fn parse_optional_int(value: Option<*mut sqlite3_value>) -> Result> api::ValueType::Integer => Ok(Some(api::value_int64(&value))), api::ValueType::Text => { let raw = api::value_text(&value) - .map_err(|err| Error::new_message(&format!("fake_now must be integer: {:?}", err)))? + .map_err(|err| Error::new_message(format!("fake_now must be integer: {:?}", err)))? .trim(); if raw.is_empty() { return Ok(None); } raw.parse::() .map(Some) - .map_err(|err| Error::new_message(&format!("fake_now must be integer: {:?}", err))) + .map_err(|err| Error::new_message(format!("fake_now must be integer: {:?}", err))) } _ => Err(Error::new_message("fake_now must be integer")), } @@ -35,7 +35,7 @@ pub fn absurd_set_fake_now( values: &[*mut sqlite3_value], ) -> Result<()> { let db = api::context_db_handle(context); - let fake_now = parse_optional_int(values.get(0).copied())?; + let fake_now = parse_optional_int(values.first().copied())?; sql::exec_with_bind_text(db, SETTINGS_TABLE_SQL, &[])?; diff --git a/absurd-sqlite-extension/src/spawn.rs b/absurd-sqlite-extension/src/spawn.rs index 3e400fd..c8793ce 100644 --- a/absurd-sqlite-extension/src/spawn.rs +++ b/absurd-sqlite-extension/src/spawn.rs @@ -31,8 +31,8 @@ fn parse_spawn_options(raw: &str) -> Result { if raw.trim().is_empty() { return Ok(SpawnOptions::default()); } - let parsed: JsonValue = - serde_json::from_str(raw).map_err(|err| Error::new_message(&format!("options must be valid JSON: {:?}", err)))?; + let parsed: JsonValue = serde_json::from_str(raw) + .map_err(|err| Error::new_message(format!("options must be valid JSON: {:?}", err)))?; if parsed.is_null() { return Ok(SpawnOptions::default()); } @@ -82,8 +82,6 @@ fn parse_spawn_options(raw: &str) -> Result { }) } - - fn spawn_task_impl( db: *mut sqlite3, queue_name: &str, @@ -200,13 +198,7 @@ fn spawn_task_impl( null, null )", - &[ - queue_name, - &run_id, - &task_id, - &attempt_value, - &now_value, - ], + &[queue_name, &run_id, &task_id, &attempt_value, &now_value], )?; sql::exec_with_bind_text( @@ -214,7 +206,7 @@ fn spawn_task_impl( "update absurd_tasks set last_attempt_run = ?1 where queue_name = ?2 and task_id = ?3", &[&run_id, queue_name, &task_id], ) - .map_err(|err| Error::new_message(&format!("task update failed: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("task update failed: {:?}", err)))?; Ok(SpawnResult { task_id, @@ -363,7 +355,7 @@ impl VTabCursor for SpawnTaskCursor { _idx_str: Option<&str>, values: &[*mut sqlite3_value], ) -> Result<()> { - let queue_name = api::value_text_notnull(values.get(0).expect("queue_name"))?; + let queue_name = api::value_text_notnull(values.first().expect("queue_name"))?; let task_name = api::value_text_notnull(values.get(1).expect("task_name"))?; let params = api::value_text(values.get(2).expect("params"))?; let options_raw = if values.len() > 3 { diff --git a/absurd-sqlite-extension/src/sql.rs b/absurd-sqlite-extension/src/sql.rs index dd2b6b1..a4cf879 100644 --- a/absurd-sqlite-extension/src/sql.rs +++ b/absurd-sqlite-extension/src/sql.rs @@ -1,16 +1,18 @@ +use sqlite3ext_sys::{sqlite3, sqlite3_context}; use sqlite_loadable::ext::{ sqlite3ext_bind_text, sqlite3ext_finalize, sqlite3ext_prepare_v2, sqlite3ext_step, }; use sqlite_loadable::{Error, Result, SQLITE_DONE, SQLITE_OKAY, SQLITE_ROW}; -use sqlite3ext_sys::{sqlite3, sqlite3_context}; use std::ffi::CString; use std::os::raw::{c_char, c_void}; use std::time::{SystemTime, UNIX_EPOCH}; pub fn exec_with_bind_text(db: *mut sqlite3, sql: &str, params: &[&str]) -> Result<()> { let mut stmt = std::ptr::null_mut(); - let sql_c = CString::new(sql).map_err(|err| Error::new_message(&format!("invalid sql: {:?}", err)))?; - let rc = unsafe { sqlite3ext_prepare_v2(db, sql_c.as_ptr(), -1, &mut stmt, std::ptr::null_mut()) }; + let sql_c = + CString::new(sql).map_err(|err| Error::new_message(format!("invalid sql: {:?}", err)))?; + let rc = + unsafe { sqlite3ext_prepare_v2(db, sql_c.as_ptr(), -1, &mut stmt, std::ptr::null_mut()) }; if rc != SQLITE_OKAY { return Err(Error::new_message("failed to prepare statement")); } @@ -22,7 +24,7 @@ pub fn exec_with_bind_text(db: *mut sqlite3, sql: &str, params: &[&str]) -> Resu let step_rc = unsafe { sqlite3ext_step(stmt) }; unsafe { sqlite3ext_finalize(stmt) }; if step_rc != SQLITE_DONE && step_rc != SQLITE_ROW { - return Err(Error::new_message(&format!( + return Err(Error::new_message(format!( "statement execution failed (code {})", step_rc ))); @@ -31,7 +33,8 @@ pub fn exec_with_bind_text(db: *mut sqlite3, sql: &str, params: &[&str]) -> Resu } pub fn exec_batch(db: *mut sqlite3, sql: &str) -> Result<()> { - let sql_c = CString::new(sql).map_err(|err| Error::new_message(&format!("invalid sql: {:?}", err)))?; + let sql_c = + CString::new(sql).map_err(|err| Error::new_message(format!("invalid sql: {:?}", err)))?; let mut tail = std::ptr::null(); let mut current = sql_c.as_ptr(); @@ -47,7 +50,7 @@ pub fn exec_batch(db: *mut sqlite3, sql: &str) -> Result<()> { let step_rc = unsafe { sqlite3ext_step(stmt) }; unsafe { sqlite3ext_finalize(stmt) }; if step_rc != SQLITE_DONE && step_rc != SQLITE_ROW { - return Err(Error::new_message(&format!( + return Err(Error::new_message(format!( "statement execution failed (code {})", step_rc ))); @@ -63,15 +66,15 @@ pub fn exec_batch(db: *mut sqlite3, sql: &str) -> Result<()> { pub fn query_row_i64(db: *mut sqlite3, sql: &str, params: &[&str]) -> Result { let mut stmt = sqlite_loadable::exec::Statement::prepare(db, sql) - .map_err(|err| Error::new_message(&format!("failed to prepare statement: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to prepare statement: {:?}", err)))?; for (idx, value) in params.iter().enumerate() { stmt.bind_text((idx + 1) as i32, value) - .map_err(|err| Error::new_message(&format!("failed to bind text: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind text: {:?}", err)))?; } let mut rows = stmt.execute(); if let Some(Ok(row)) = rows.next() { row.get::(0) - .map_err(|err| Error::new_message(&format!("failed to read row: {:?}", err))) + .map_err(|err| Error::new_message(format!("failed to read row: {:?}", err))) } else { Err(Error::new_message("no rows returned")) } @@ -83,22 +86,22 @@ pub fn query_row_strings( params: &[&str], ) -> Result<(String, String, i64)> { let mut stmt = sqlite_loadable::exec::Statement::prepare(db, sql) - .map_err(|err| Error::new_message(&format!("failed to prepare statement: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to prepare statement: {:?}", err)))?; for (idx, value) in params.iter().enumerate() { stmt.bind_text((idx + 1) as i32, value) - .map_err(|err| Error::new_message(&format!("failed to bind text: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to bind text: {:?}", err)))?; } let mut rows = stmt.execute(); if let Some(Ok(row)) = rows.next() { let task_id = row .get::(0) - .map_err(|err| Error::new_message(&format!("failed to read task_id: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read task_id: {:?}", err)))?; let run_id = row .get::(1) - .map_err(|err| Error::new_message(&format!("failed to read run_id: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read run_id: {:?}", err)))?; let attempt = row .get::(2) - .map_err(|err| Error::new_message(&format!("failed to read attempt: {:?}", err)))?; + .map_err(|err| Error::new_message(format!("failed to read attempt: {:?}", err)))?; Ok((task_id, run_id, attempt)) } else { Err(Error::new_message("no rows returned")) @@ -107,7 +110,8 @@ pub fn query_row_strings( fn bind_text(stmt: *mut sqlite3ext_sys::sqlite3_stmt, idx: i32, value: &str) -> Result<()> { let bytes = value.as_bytes(); - let cstr = CString::new(bytes).map_err(|err| Error::new_message(&format!("invalid bind text: {:?}", err)))?; + let cstr = CString::new(bytes) + .map_err(|err| Error::new_message(format!("invalid bind text: {:?}", err)))?; unsafe { sqlite3ext_bind_text( stmt, diff --git a/absurd-sqlite-extension/tests/integration.rs b/absurd-sqlite-extension/tests/integration.rs index f1e6002..a875af3 100644 --- a/absurd-sqlite-extension/tests/integration.rs +++ b/absurd-sqlite-extension/tests/integration.rs @@ -5,9 +5,7 @@ use uuid::Uuid; fn extension_path() -> PathBuf { let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); - let workspace_root = manifest_dir - .parent() - .expect("workspace root not found"); + let workspace_root = manifest_dir.parent().expect("workspace root not found"); let lib_name = format!("libabsurd{}", std::env::consts::DLL_SUFFIX); workspace_root.join("target").join("debug").join(lib_name) } @@ -19,7 +17,10 @@ fn build_extension(_path: &Path) { .args([ "build", "--manifest-path", - manifest_dir.join("Cargo.toml").to_str().expect("manifest path"), + manifest_dir + .join("Cargo.toml") + .to_str() + .expect("manifest path"), ]) .status() .expect("run cargo build"); @@ -46,7 +47,8 @@ fn test_extension_with_real_db() { conn.load_extension_enable().expect("enable load_extension"); conn.load_extension(&ext_path, None::<&str>) .expect("load extension"); - conn.load_extension_disable().expect("disable load_extension"); + conn.load_extension_disable() + .expect("disable load_extension"); } apply_migrations(&conn); @@ -113,7 +115,8 @@ fn test_claim_timeout_creates_retry_run() { conn.load_extension_enable().expect("enable load_extension"); conn.load_extension(&ext_path, None::<&str>) .expect("load extension"); - conn.load_extension_disable().expect("disable load_extension"); + conn.load_extension_disable() + .expect("disable load_extension"); } apply_migrations(&conn); @@ -194,7 +197,8 @@ fn test_schedule_and_fail_run_integration() { conn.load_extension_enable().expect("enable load_extension"); conn.load_extension(&ext_path, None::<&str>) .expect("load extension"); - conn.load_extension_disable().expect("disable load_extension"); + conn.load_extension_disable() + .expect("disable load_extension"); } apply_migrations(&conn); @@ -291,7 +295,8 @@ fn test_complete_and_extend_claim_integration() { conn.load_extension_enable().expect("enable load_extension"); conn.load_extension(&ext_path, None::<&str>) .expect("load extension"); - conn.load_extension_disable().expect("disable load_extension"); + conn.load_extension_disable() + .expect("disable load_extension"); } apply_migrations(&conn); @@ -385,7 +390,8 @@ fn test_checkpoint_and_event_integration() { conn.load_extension_enable().expect("enable load_extension"); conn.load_extension(&ext_path, None::<&str>) .expect("load extension"); - conn.load_extension_disable().expect("disable load_extension"); + conn.load_extension_disable() + .expect("disable load_extension"); } apply_migrations(&conn); @@ -481,7 +487,8 @@ fn test_cleanup_integration() { conn.load_extension_enable().expect("enable load_extension"); conn.load_extension(&ext_path, None::<&str>) .expect("load extension"); - conn.load_extension_disable().expect("disable load_extension"); + conn.load_extension_disable() + .expect("disable load_extension"); } apply_migrations(&conn); @@ -522,11 +529,9 @@ fn test_cleanup_integration() { .expect("set completed_at"); let _: Option = conn - .query_row( - "select absurd_emit_event('alpha', 'eventA')", - [], - |r| r.get(0), - ) + .query_row("select absurd_emit_event('alpha', 'eventA')", [], |r| { + r.get(0) + }) .expect("emit event"); conn.execute( "update absurd_events set emitted_at = 1 where queue_name = 'alpha' and event_name = 'eventA'", @@ -535,20 +540,16 @@ fn test_cleanup_integration() { .expect("set emitted_at"); let deleted_tasks: i64 = conn - .query_row( - "select absurd_cleanup_tasks('alpha', 1, 100)", - [], - |r| r.get(0), - ) + .query_row("select absurd_cleanup_tasks('alpha', 1, 100)", [], |r| { + r.get(0) + }) .expect("cleanup absurd_tasks"); assert_eq!(deleted_tasks, 1); let deleted_events: i64 = conn - .query_row( - "select absurd_cleanup_events('alpha', 1, 100)", - [], - |r| r.get(0), - ) + .query_row("select absurd_cleanup_events('alpha', 1, 100)", [], |r| { + r.get(0) + }) .expect("cleanup absurd_events"); assert_eq!(deleted_events, 1); @@ -567,7 +568,8 @@ fn test_cancel_task_integration() { conn.load_extension_enable().expect("enable load_extension"); conn.load_extension(&ext_path, None::<&str>) .expect("load extension"); - conn.load_extension_disable().expect("disable load_extension"); + conn.load_extension_disable() + .expect("disable load_extension"); } apply_migrations(&conn); @@ -585,11 +587,9 @@ fn test_cancel_task_integration() { .expect("spawn task"); let _: Option = conn - .query_row( - "select absurd_cancel_task('alpha', ?1)", - [&row.0], - |r| r.get(0), - ) + .query_row("select absurd_cancel_task('alpha', ?1)", [&row.0], |r| { + r.get(0) + }) .expect("cancel task"); let task_state: String = conn diff --git a/assets/generate-icon.sh b/assets/generate-icon.sh new file mode 100755 index 0000000..a2e0e3c --- /dev/null +++ b/assets/generate-icon.sh @@ -0,0 +1,20 @@ +input="$1" +if [ -z "$input" ]; then + echo "Usage: $0 " + exit 1 +fi + +file_name_noext=$(basename "$input" | sed 's/\.[^.]*$//') + +mkdir -p "${file_name_noext}" + +for s in 16 24 32 48 64 96 128 256 512 1024; do + magick "$input" -resize ${s}x${s} ${file_name_noext}/logo-${s}x${s}.png +done + +magick "$input" -resize 256x256 \ + -define icon:auto-resize=16,24,32,48,64,128,256 \ + ${file_name_noext}/icon.ico + +magick "$input" -resize 1024x1024 \ + ${file_name_noext}/icon.icns diff --git a/assets/logo-standalone.png b/assets/logo-standalone.png new file mode 100644 index 0000000..29c1b65 Binary files /dev/null and b/assets/logo-standalone.png differ diff --git a/assets/logo.png b/assets/logo.png new file mode 100644 index 0000000..b8c2f18 Binary files /dev/null and b/assets/logo.png differ diff --git a/samples/typescript-client/.gitignore b/samples/typescript-client/.gitignore new file mode 100644 index 0000000..b947077 --- /dev/null +++ b/samples/typescript-client/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +dist/ diff --git a/samples/typescript-client/package-lock.json b/samples/typescript-client/package-lock.json new file mode 100644 index 0000000..7174b31 --- /dev/null +++ b/samples/typescript-client/package-lock.json @@ -0,0 +1,1022 @@ +{ + "name": "@absurd-sqlite-samples/typescript-client", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@absurd-sqlite-samples/typescript-client", + "version": "0.1.0", + "license": "Apache-2.0", + "dependencies": { + "@absurd-sqlite/sdk": "file:../../sdks/typescript", + "better-sqlite3": "^12.5.0" + }, + "devDependencies": { + "@types/better-sqlite3": "^7.6.13", + "esbuild": "0.27.2", + "typescript": "^5.9.3" + } + }, + "../../sdks/typescript": { + "name": "@absurd-sqlite/sdk", + "version": "0.1.0", + "license": "Apache-2.0", + "dependencies": { + "absurd-sdk": "~0.0.7" + }, + "devDependencies": { + "@types/better-sqlite3": "^7.6.13", + "typedoc": "^0.28.14", + "typescript": "^5.9.3", + "vitest": "^4.0.7" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "better-sqlite3": "^12.5.0" + } + }, + "node_modules/@absurd-sqlite/sdk": { + "resolved": "../../sdks/typescript", + "link": true + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/better-sqlite3": { + "version": "7.6.13", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", + "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz", + "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/better-sqlite3": { + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.5.0.tgz", + "integrity": "sha512-WwCZ/5Diz7rsF29o27o0Gcc1Du+l7Zsv7SYtVPG0X3G/uUI1LqdxrQI7c9Hs2FWpqXXERjW9hp6g3/tH7DlVKg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + }, + "engines": { + "node": "20.x || 22.x || 23.x || 24.x || 25.x" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, + "node_modules/node-abi": { + "version": "3.85.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz", + "integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "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" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + } + } +} diff --git a/samples/typescript-client/package.json b/samples/typescript-client/package.json new file mode 100644 index 0000000..249bc23 --- /dev/null +++ b/samples/typescript-client/package.json @@ -0,0 +1,22 @@ +{ + "name": "@absurd-sqlite-samples/typescript-client", + "version": "0.1.0", + "main": "index.js", + "scripts": { + "build": "esbuild src/index.ts --bundle --outfile=dist/bundle.js --platform=node --external:better-sqlite3", + "start": "node dist/bundle.js", + "type-check": "tsc --noEmit" + }, + "author": "", + "license": "Apache-2.0", + "description": "", + "devDependencies": { + "@types/better-sqlite3": "^7.6.13", + "esbuild": "0.27.2", + "typescript": "^5.9.3" + }, + "dependencies": { + "@absurd-sqlite/sdk": "file:../../sdks/typescript", + "better-sqlite3": "^12.5.0" + } +} \ No newline at end of file diff --git a/samples/typescript-client/src/index.ts b/samples/typescript-client/src/index.ts new file mode 100644 index 0000000..fc7d4d3 --- /dev/null +++ b/samples/typescript-client/src/index.ts @@ -0,0 +1,70 @@ +import { homedir } from "node:os"; +import { join } from "node:path"; +import { Absurd, SQLiteDatabase } from "@absurd-sqlite/sdk"; +import sqlite from "better-sqlite3"; + +async function main() { + const extensionPath = "../../target/release/libabsurd.dylib"; + const bundleId = "ing.isbuild.absurd-sqlite-standalone"; + const appLocalDir = + process.env.APP_LOCAL_DIR ?? + (process.platform === "darwin" + ? join(homedir(), "Library", "Application Support", bundleId) + : process.platform === "win32" + ? join( + process.env.LOCALAPPDATA ?? join(homedir(), "AppData", "Local"), + bundleId + ) + : join(homedir(), ".local", "share", bundleId)); + const dbPath = join(appLocalDir, "absurd-sqlite.db"); + const db = sqlite(dbPath) as unknown as SQLiteDatabase; + + const absurd = new Absurd(db, extensionPath); + + absurd.registerTask( + { + name: "hello", + }, + async (params, ctx) => { + await ctx.step("init", async () => { + console.log("init step"); + ctx.emitEvent("progress", { message: "Initialization complete" }); + }); + + await ctx.sleepFor("back off 15s", 15); + + await ctx.step("process", async () => { + console.log("process step"); + ctx.emitEvent("progress", { message: "Processing complete" }); + }); + + const name = params.name || "world"; + + console.log(`Saying hello to ${name}`); + return { greeting: `Hello, ${name}!` }; + } + ); + + setInterval(async () => { + console.log("creating queue"); + await absurd.createQueue("default"); + + console.log("spawning tasks"); + + const spawnedTask = await absurd.spawn( + "hello", + { name: "Alice" }, + { queue: "default" } + ); + console.log("Spawned task ID:", spawnedTask.taskID); + }, 3000); + + await absurd.startWorker({ + concurrency: 2, + }); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/samples/typescript-client/tsconfig.json b/samples/typescript-client/tsconfig.json new file mode 100644 index 0000000..42509b3 --- /dev/null +++ b/samples/typescript-client/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "outDir": "./dist", + "moduleResolution": "node" + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/sdks/typescript/package.json b/sdks/typescript/package.json index 8728cdc..04e75fd 100644 --- a/sdks/typescript/package.json +++ b/sdks/typescript/package.json @@ -7,7 +7,7 @@ "build": "tsc --project tsconfig.build.json && tsc --project tsconfig.cjs.json && echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json", "type-check": "tsc --noEmit", "prepare": "npm run build", - "test:prepare": "cd ../../ && cargo build --release", + "test:prepare": "cd ../../ && cargo build -p absurd_sqlite_extension --release", "test": "npm run test:prepare && vitest --silent --run", "test:watch": "npm run test:prepare && vitest", "test:ui": "npm run test:prepare && vitest --ui", diff --git a/sdks/typescript/src/absurd.ts b/sdks/typescript/src/absurd-types.ts similarity index 96% rename from sdks/typescript/src/absurd.ts rename to sdks/typescript/src/absurd-types.ts index 1b515fb..c6187d6 100644 --- a/sdks/typescript/src/absurd.ts +++ b/sdks/typescript/src/absurd-types.ts @@ -8,6 +8,8 @@ import type { WorkerOptions, } from "absurd-sdk"; +import type { SQLiteRestBindParams } from "./sqlite-types"; + /** * Minimal query interface compatible with Absurd's database operations. */ @@ -17,9 +19,9 @@ export interface Queryable { * @param sql SQL text with parameter placeholders. * @param params Optional positional parameters. */ - query( + query>( sql: string, - params?: I[] + params?: SQLiteRestBindParams ): Promise<{ rows: R[] }>; } diff --git a/sdks/typescript/src/index.ts b/sdks/typescript/src/index.ts index e370b27..95b4538 100644 --- a/sdks/typescript/src/index.ts +++ b/sdks/typescript/src/index.ts @@ -1,10 +1,10 @@ import { Absurd as AbsurdBase } from "absurd-sdk"; -import sqlite from "better-sqlite3"; -import { AbsurdClient } from "./absurd"; +import type { AbsurdClient } from "./absurd-types"; +import type { SQLiteDatabase } from "./sqlite-types"; import { SqliteConnection } from "./sqlite"; -export type { AbsurdClient, Queryable, Worker } from "./absurd"; +export type { AbsurdClient, Queryable, Worker } from "./absurd-types"; export type { AbsurdHooks, AbsurdOptions, @@ -20,16 +20,20 @@ export type { TaskRegistrationOptions, WorkerOptions, } from "absurd-sdk"; +export type { + SQLiteBindParams, + SQLiteBindValue, + SQLiteColumnDefinition, + SQLiteDatabase, + SQLiteRestBindParams, + SQLiteStatement, + SQLiteVerboseLog, +} from "./sqlite-types"; export class Absurd extends AbsurdBase implements AbsurdClient { - private db: sqlite.Database; + private db: SQLiteDatabase; - constructor( - extensionPath: string, - filename?: string, - options?: sqlite.Options - ) { - const db = new sqlite(filename, options); + constructor(db: SQLiteDatabase, extensionPath: string) { db.loadExtension(extensionPath); const queryable = new SqliteConnection(db); super(queryable); diff --git a/sdks/typescript/src/sqlite-types.ts b/sdks/typescript/src/sqlite-types.ts new file mode 100644 index 0000000..8882a10 --- /dev/null +++ b/sdks/typescript/src/sqlite-types.ts @@ -0,0 +1,35 @@ +export type SQLiteBindValue = number | string | Buffer | bigint | Date | null; + +export type SQLiteBindParams = + | SQLiteBindValue[] + | Record; + +export type SQLiteRestBindParams = SQLiteBindValue[] | [SQLiteBindParams]; + +export interface SQLiteColumnDefinition { + name: string; + column: string | null; + table: string | null; + database: string | null; + type: string | null; +} + +export interface SQLiteStatement> { + readonly: boolean; + columns(): SQLiteColumnDefinition[]; + all(...args: SQLiteRestBindParams): Result[]; + run(...args: SQLiteRestBindParams): number; +} + +export interface SQLiteDatabase { + prepare>( + sql: string + ): SQLiteStatement; + close(): void; + loadExtension(path: string): void; +} + +export type SQLiteVerboseLog = ( + message?: any, + ...optionalParams: any[] +) => void; diff --git a/sdks/typescript/src/sqlite.ts b/sdks/typescript/src/sqlite.ts index 425429b..818a4c0 100644 --- a/sdks/typescript/src/sqlite.ts +++ b/sdks/typescript/src/sqlite.ts @@ -1,44 +1,41 @@ -import sqlite from "better-sqlite3"; - -import { Queryable } from "./absurd"; - -type EnforceIdenticalKeys = keyof T extends keyof U - ? keyof U extends keyof T - ? T - : never - : never; +import type { Queryable } from "./absurd-types"; +import type { + SQLiteRestBindParams, + SQLiteDatabase, + SQLiteStatement, + SQLiteVerboseLog, +} from "./sqlite-types"; export class SqliteConnection implements Queryable { - private readonly db: sqlite.Database; + private readonly db: SQLiteDatabase; - constructor(db: sqlite.Database) { + constructor(db: SQLiteDatabase) { this.db = db; + // TODO: verbose logging } - async query( + async query>( sql: string, - params?: I[] + params?: SQLiteRestBindParams ): Promise<{ rows: R[] }> { const sqliteQuery = rewritePostgresQuery(sql); const sqliteParams = rewritePostgresParams(params); const statement = this.db.prepare(sqliteQuery); - if (!statement.reader) { + if (!statement.readonly) { // this indicates `return_data` is false // https://github.com/WiseLibs/better-sqlite3/blob/6209be238d6a1b181f516e4e636986604b0f62e1/src/objects/statement.cpp#L134C83-L134C95 throw new Error("The query() method is only statements that return data"); } - const rows = statement.all(sqliteParams) as EnforceIdenticalKeys< - sqlite.RunResult, - R - >[]; - const rowsDecoded = rows.map((row) => decodeRowValues(statement, row)); + const rowsDecoded = statement + .all(sqliteParams) + .map((row) => decodeRowValues(statement, row)); return { rows: rowsDecoded }; } - async exec(sql: string, params?: I[]): Promise { + async exec(sql: string, params?: SQLiteRestBindParams): Promise { const sqliteQuery = rewritePostgresQuery(sql); const sqliteParams = rewritePostgresParams(params); @@ -54,7 +51,9 @@ function rewritePostgresQuery(text: string): string { .replace(/absurd\.(\w+)/g, "absurd_$1"); } -function rewritePostgresParams(params?: I[]): Record { +function rewritePostgresParams( + params?: SQLiteRestBindParams +): Record { if (!params) { return {}; } @@ -70,9 +69,9 @@ function rewritePostgresParams(params?: I[]): Record { } function decodeRowValues( - statement: sqlite.Statement, - row: EnforceIdenticalKeys, - verbose?: sqlite.Options["verbose"] + statement: SQLiteStatement, + row: U, + verbose?: SQLiteVerboseLog ): R { const columns = statement.columns(); @@ -97,7 +96,7 @@ function decodeColumnValue( value: unknown | V, columnName: string, columnType: string | null, - verbose?: sqlite.Options["verbose"] + verbose?: SQLiteVerboseLog ): V | null { if (value === null || value === undefined) { return null; diff --git a/sdks/typescript/test/index.test.ts b/sdks/typescript/test/index.test.ts index f652164..51a7ee8 100644 --- a/sdks/typescript/test/index.test.ts +++ b/sdks/typescript/test/index.test.ts @@ -5,7 +5,7 @@ import { join } from "node:path"; import { fileURLToPath } from "node:url"; import { afterEach, describe, expect, it } from "vitest"; -import { Absurd } from "../src/index"; +import { Absurd, SQLiteDatabase } from "../src/index"; const testDir = fileURLToPath(new URL(".", import.meta.url)); const repoRoot = join(testDir, "../../.."); @@ -53,7 +53,8 @@ afterEach(() => { describe("Absurd", () => { it("creates and lists queues using the sqlite extension", async () => { const dbPath = createDatabaseWithMigrations(); - const absurd = new Absurd(extensionPath, dbPath); + const db = new sqlite(dbPath) as unknown as SQLiteDatabase; + const absurd = new Absurd(db, extensionPath); await absurd.createQueue("alpha"); await absurd.createQueue("beta"); @@ -66,8 +67,8 @@ describe("Absurd", () => { const remaining = await absurd.listQueues(); expect(remaining).not.toContain("alpha"); - const db = new sqlite(dbPath); - const { count } = db + const db2 = new sqlite(dbPath); + const { count } = db2 .prepare("select count(*) as count from absurd_queues") .get() as { count: number }; expect(count).toBe(1); @@ -78,12 +79,13 @@ describe("Absurd", () => { it("closes the sqlite database on close()", async () => { const dbPath = createDatabaseWithMigrations(); - const absurd = new Absurd(extensionPath, dbPath); + const db = new sqlite(dbPath) as unknown as SQLiteDatabase; + const absurd = new Absurd(db, extensionPath); await absurd.close(); - const db = new sqlite(dbPath); - const { ok } = db.prepare("select 1 as ok").get() as { ok: number }; + const db2 = new sqlite(dbPath); + const { ok } = db2.prepare("select 1 as ok").get() as { ok: number }; expect(ok).toBe(1); db.close(); }); diff --git a/sdks/typescript/test/setup.ts b/sdks/typescript/test/setup.ts index 4fb3e42..56fe45f 100644 --- a/sdks/typescript/test/setup.ts +++ b/sdks/typescript/test/setup.ts @@ -11,7 +11,7 @@ import { } from "absurd-sdk"; import { SqliteConnection } from "../src/sqlite"; -import type { Absurd } from "../src/index"; +import type { Absurd, SQLiteDatabase } from "../src/index"; // Database row types matching the SQLite schema export interface TaskRow { @@ -123,10 +123,7 @@ export interface TestContext { ): Promise; extendClaim(runID: string, extendBySeconds: number): Promise; expectCancelledError(promise: Promise): Promise; - createClient(options?: { - queueName?: string; - hooks?: AbsurdHooks; - }): Absurd; + createClient(options?: { queueName?: string; hooks?: AbsurdHooks }): Absurd; } export function randomName(prefix = "test"): string { @@ -163,7 +160,7 @@ function createFixture(): SqliteFixture { const db = new sqlite(dbPath); db.loadExtension(extensionPath); db.prepare("select absurd_apply_migrations()").get(); - const conn = new SqliteConnection(db); + const conn = new SqliteConnection(db as unknown as SQLiteDatabase); const cleanup = () => { rmSync(tempDir, { recursive: true, force: true }); @@ -199,7 +196,8 @@ export async function createTestAbsurd( getRuns: (taskID: string) => getRuns(fixture.conn, taskID, queueName), setFakeNow: (ts: Date | null) => setFakeNow(fixture.conn, ts), sleep: (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)), - getRemainingTasksCount: () => getRemainingTasksCount(fixture.conn, queueName), + getRemainingTasksCount: () => + getRemainingTasksCount(fixture.conn, queueName), getRemainingEventsCount: () => getRemainingEventsCount(fixture.conn, queueName), getWaitsCount: () => getWaitsCount(fixture.conn, queueName), @@ -551,9 +549,7 @@ async function extendClaim( ]); } -async function expectCancelledError( - promise: Promise -): Promise { +async function expectCancelledError(promise: Promise): Promise { try { await promise; } catch (err: any) { diff --git a/sdks/typescript/test/sqlite.test.ts b/sdks/typescript/test/sqlite.test.ts index ee599cc..0ac4f51 100644 --- a/sdks/typescript/test/sqlite.test.ts +++ b/sdks/typescript/test/sqlite.test.ts @@ -2,17 +2,18 @@ import sqlite from "better-sqlite3"; import { describe, expect, it } from "vitest"; import { SqliteConnection } from "../src/sqlite"; +import type { SQLiteDatabase } from "../src/sqlite-types"; describe("SqliteConnection", () => { it("rewrites postgres-style params and absurd schema names", async () => { - const db = new sqlite(":memory:"); + const db = new sqlite(":memory:") as unknown as SQLiteDatabase; const conn = new SqliteConnection(db); await conn.exec("CREATE TABLE absurd_tasks (id, name)"); - await conn.exec( - "INSERT INTO absurd.tasks (id, name) VALUES ($1, $2)", - [1, "alpha"] - ); + await conn.exec("INSERT INTO absurd.tasks (id, name) VALUES ($1, $2)", [ + 1, + "alpha", + ]); const { rows } = await conn.query<{ id: number; name: string }>( "SELECT id, name FROM absurd.tasks WHERE id = $1", @@ -24,7 +25,7 @@ describe("SqliteConnection", () => { }); it("throws when query is used for non-reader statements", async () => { - const db = new sqlite(":memory:"); + const db = new sqlite(":memory:") as unknown as SQLiteDatabase; const conn = new SqliteConnection(db); await expect(conn.query("CREATE TABLE t (id)")).rejects.toThrow( @@ -34,7 +35,7 @@ describe("SqliteConnection", () => { }); it("decodes JSON from typeless columns", async () => { - const db = new sqlite(":memory:"); + const db = new sqlite(":memory:") as unknown as SQLiteDatabase; const conn = new SqliteConnection(db); await conn.exec("CREATE TABLE t (payload)"); @@ -49,7 +50,7 @@ describe("SqliteConnection", () => { }); it("decodes JSON from blob columns", async () => { - const db = new sqlite(":memory:"); + const db = new sqlite(":memory:") as unknown as SQLiteDatabase; const conn = new SqliteConnection(db); await conn.exec("CREATE TABLE t_blob (payload BLOB)"); @@ -66,7 +67,7 @@ describe("SqliteConnection", () => { }); it("decodes datetime columns into Date objects", async () => { - const db = new sqlite(":memory:"); + const db = new sqlite(":memory:") as unknown as SQLiteDatabase; const conn = new SqliteConnection(db); const now = Date.now(); diff --git a/standalone/.gitignore b/standalone/.gitignore new file mode 100644 index 0000000..6635cf5 --- /dev/null +++ b/standalone/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +node_modules +/build +/.svelte-kit +/package +.env +.env.* +!.env.example +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/standalone/.vscode/extensions.json b/standalone/.vscode/extensions.json new file mode 100644 index 0000000..61343e9 --- /dev/null +++ b/standalone/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "svelte.svelte-vscode", + "tauri-apps.tauri-vscode", + "rust-lang.rust-analyzer" + ] +} diff --git a/standalone/.vscode/settings.json b/standalone/.vscode/settings.json new file mode 100644 index 0000000..2f86c50 --- /dev/null +++ b/standalone/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "svelte.enable-ts-plugin": true +} diff --git a/standalone/AGENTS.md b/standalone/AGENTS.md new file mode 100644 index 0000000..cdca3d8 --- /dev/null +++ b/standalone/AGENTS.md @@ -0,0 +1,51 @@ +# Standalone + +Standalone folder hosts the Tauri v2 based desktop application, pairing a Rust backend (`src-tauri`) with a SvelteKit + Typescript frontend (`src`) to provide a local UI around the Absurd-SQLite engine. + +## Development + +A development environment can be started with: + +```bash +$ bun tauri dev +``` + +This will launch the Tauri application in development mode. The SvelteKit frontend will be served with hot-reloading, and the Rust backend will be compiled and run by Tauri. + +The frontend runs on `http://localhost:1420` by default. In development mode, the Tauri application +exposes a dev API server (tRPC based) on `http://localhost:11223` for debugging. + +### Debugging via Playwright MCP + +In development mode, please use playwright MCP to connect to the frontend app for debugging. + +### Code Linting and Formatting + +Please make sure to lint / format code. Use the following commands to validate: + +**Frontend** + +``` +$ bun check +``` + +**Backend** + +``` +$ cargo fmt -p AbsurdSQLite -- --check +$ cargo clippy -p AbsurdSQLite -- -D warnings +``` + +### Code Testing + +To run the tests for the SQLite extension, use: + +```bash +$ cargo test -p AbsurdSQLite +``` + +## SQLite Extension + +This project depends on the [`absurd-sqlite-extension`](../absurd-sqlite-extension/) for the core SQLite extension logic. +Please refer to that sub-project for development instructions around the extension itself. We can copy the built extension +from repo's Rust build target folder. diff --git a/standalone/README.md b/standalone/README.md new file mode 100644 index 0000000..a7514a2 --- /dev/null +++ b/standalone/README.md @@ -0,0 +1,3 @@ +# Standalone + +Standalone folder hosts the Tauri v2 based desktop application, pairing a Rust backend (`src-tauri`) with a SvelteKit + Typescript frontend (`src`) to provide a local UI around the Absurd-SQLite engine. diff --git a/standalone/bun.lock b/standalone/bun.lock new file mode 100644 index 0000000..08e8af4 --- /dev/null +++ b/standalone/bun.lock @@ -0,0 +1,351 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "standalone", + "dependencies": { + "@tailwindcss/vite": "^4.1.18", + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-cli": "~2", + "@tauri-apps/plugin-fs": "~2", + "@tauri-apps/plugin-log": "~2", + "@tauri-apps/plugin-opener": "^2", + "@tauri-apps/plugin-shell": "~2", + "@trpc/client": "^10.45.2", + "tailwindcss": "^4.1.18", + }, + "devDependencies": { + "@sveltejs/adapter-static": "^3.0.6", + "@sveltejs/kit": "^2.9.0", + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "@tauri-apps/cli": "^2", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "typescript": "~5.6.2", + "vite": "^6.0.3", + }, + }, + }, + "packages": { + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.54.0", "", { "os": "android", "cpu": "arm" }, "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.54.0", "", { "os": "android", "cpu": "arm64" }, "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.54.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.54.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.54.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.54.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.54.0", "", { "os": "linux", "cpu": "arm" }, "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.54.0", "", { "os": "linux", "cpu": "arm" }, "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.54.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.54.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.54.0", "", { "os": "linux", "cpu": "none" }, "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.54.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.54.0", "", { "os": "linux", "cpu": "none" }, "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.54.0", "", { "os": "linux", "cpu": "none" }, "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.54.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.54.0", "", { "os": "linux", "cpu": "x64" }, "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.54.0", "", { "os": "linux", "cpu": "x64" }, "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.54.0", "", { "os": "none", "cpu": "arm64" }, "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.54.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.54.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.54.0", "", { "os": "win32", "cpu": "x64" }, "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.54.0", "", { "os": "win32", "cpu": "x64" }, "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg=="], + + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.8", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-esgN+54+q0NjB0Y/4BomT9samII7jGwNy/2a3wNZbT2A2RpmXsXwUt24LvLhx6jUq2gVk4cWEvcRO6MFQbOfNA=="], + + "@sveltejs/adapter-static": ["@sveltejs/adapter-static@3.0.10", "", { "peerDependencies": { "@sveltejs/kit": "^2.0.0" } }, "sha512-7D9lYFWJmB7zxZyTE/qxjksvMqzMuYrrsyh1f4AlZqeZeACPRySjbC3aFiY55wb1tWUaKOQG9PVbm74JcN2Iew=="], + + "@sveltejs/kit": ["@sveltejs/kit@2.49.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.3.2", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", "sirv": "^3.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["@opentelemetry/api"], "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-Vp3zX/qlwerQmHMP6x0Ry1oY7eKKRcOWGc2P59srOp4zcqyn+etJyQpELgOi4+ZSUgteX8Y387NuwruLgGXLUQ=="], + + "@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@5.1.1", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", "debug": "^4.4.1", "deepmerge": "^4.3.1", "kleur": "^4.1.5", "magic-string": "^0.30.17", "vitefu": "^1.0.6" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.0.0" } }, "sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ=="], + + "@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@4.0.1", "", { "dependencies": { "debug": "^4.3.7" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^5.0.0", "svelte": "^5.0.0", "vite": "^6.0.0" } }, "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw=="], + + "@tailwindcss/node": ["@tailwindcss/node@4.1.18", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.18" } }, "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ=="], + + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.18", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.18", "@tailwindcss/oxide-darwin-arm64": "4.1.18", "@tailwindcss/oxide-darwin-x64": "4.1.18", "@tailwindcss/oxide-freebsd-x64": "4.1.18", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", "@tailwindcss/oxide-linux-x64-musl": "4.1.18", "@tailwindcss/oxide-wasm32-wasi": "4.1.18", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" } }, "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A=="], + + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.18", "", { "os": "android", "cpu": "arm64" }, "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q=="], + + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.18", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A=="], + + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.18", "", { "os": "darwin", "cpu": "x64" }, "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw=="], + + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.18", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA=="], + + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18", "", { "os": "linux", "cpu": "arm" }, "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA=="], + + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw=="], + + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg=="], + + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g=="], + + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ=="], + + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.18", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.0", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA=="], + + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.18", "", { "os": "win32", "cpu": "arm64" }, "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA=="], + + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.18", "", { "os": "win32", "cpu": "x64" }, "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q=="], + + "@tailwindcss/vite": ["@tailwindcss/vite@4.1.18", "", { "dependencies": { "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "tailwindcss": "4.1.18" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA=="], + + "@tauri-apps/api": ["@tauri-apps/api@2.9.1", "", {}, "sha512-IGlhP6EivjXHepbBic618GOmiWe4URJiIeZFlB7x3czM0yDHHYviH1Xvoiv4FefdkQtn6v7TuwWCRfOGdnVUGw=="], + + "@tauri-apps/cli": ["@tauri-apps/cli@2.9.6", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.9.6", "@tauri-apps/cli-darwin-x64": "2.9.6", "@tauri-apps/cli-linux-arm-gnueabihf": "2.9.6", "@tauri-apps/cli-linux-arm64-gnu": "2.9.6", "@tauri-apps/cli-linux-arm64-musl": "2.9.6", "@tauri-apps/cli-linux-riscv64-gnu": "2.9.6", "@tauri-apps/cli-linux-x64-gnu": "2.9.6", "@tauri-apps/cli-linux-x64-musl": "2.9.6", "@tauri-apps/cli-win32-arm64-msvc": "2.9.6", "@tauri-apps/cli-win32-ia32-msvc": "2.9.6", "@tauri-apps/cli-win32-x64-msvc": "2.9.6" }, "bin": { "tauri": "tauri.js" } }, "sha512-3xDdXL5omQ3sPfBfdC8fCtDKcnyV7OqyzQgfyT5P3+zY6lcPqIYKQBvUasNvppi21RSdfhy44ttvJmftb0PCDw=="], + + "@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.9.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gf5no6N9FCk1qMrti4lfwP77JHP5haASZgVbBgpZG7BUepB3fhiLCXGUK8LvuOjP36HivXewjg72LTnPDScnQQ=="], + + "@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.9.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-oWh74WmqbERwwrwcueJyY6HYhgCksUc6NT7WKeXyrlY/FPmNgdyQAgcLuTSkhRFuQ6zh4Np1HZpOqCTpeZBDcw=="], + + "@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.9.6", "", { "os": "linux", "cpu": "arm" }, "sha512-/zde3bFroFsNXOHN204DC2qUxAcAanUjVXXSdEGmhwMUZeAQalNj5cz2Qli2elsRjKN/hVbZOJj0gQ5zaYUjSg=="], + + "@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.9.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-pvbljdhp9VOo4RnID5ywSxgBs7qiylTPlK56cTk7InR3kYSTJKYMqv/4Q/4rGo/mG8cVppesKIeBMH42fw6wjg=="], + + "@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.9.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-02TKUndpodXBCR0oP//6dZWGYcc22Upf2eP27NvC6z0DIqvkBBFziQUcvi2n6SrwTRL0yGgQjkm9K5NIn8s6jw=="], + + "@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.9.6", "", { "os": "linux", "cpu": "none" }, "sha512-fmp1hnulbqzl1GkXl4aTX9fV+ubHw2LqlLH1PE3BxZ11EQk+l/TmiEongjnxF0ie4kV8DQfDNJ1KGiIdWe1GvQ=="], + + "@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.9.6", "", { "os": "linux", "cpu": "x64" }, "sha512-vY0le8ad2KaV1PJr+jCd8fUF9VOjwwQP/uBuTJvhvKTloEwxYA/kAjKK9OpIslGA9m/zcnSo74czI6bBrm2sYA=="], + + "@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.9.6", "", { "os": "linux", "cpu": "x64" }, "sha512-TOEuB8YCFZTWVDzsO2yW0+zGcoMiPPwcUgdnW1ODnmgfwccpnihDRoks+ABT1e3fHb1ol8QQWsHSCovb3o2ENQ=="], + + "@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.9.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-ujmDGMRc4qRLAnj8nNG26Rlz9klJ0I0jmZs2BPpmNNf0gM/rcVHhqbEkAaHPTBVIrtUdf7bGvQAD2pyIiUrBHQ=="], + + "@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.9.6", "", { "os": "win32", "cpu": "ia32" }, "sha512-S4pT0yAJgFX8QRCyKA1iKjZ9Q/oPjCZf66A/VlG5Yw54Nnr88J1uBpmenINbXxzyhduWrIXBaUbEY1K80ZbpMg=="], + + "@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.9.6", "", { "os": "win32", "cpu": "x64" }, "sha512-ldWuWSSkWbKOPjQMJoYVj9wLHcOniv7diyI5UAJ4XsBdtaFB0pKHQsqw/ItUma0VXGC7vB4E9fZjivmxur60aw=="], + + "@tauri-apps/plugin-cli": ["@tauri-apps/plugin-cli@2.4.1", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-8JXofQFI5cmiGolh1PlU4hzE2YJgrgB1lyaztyBYiiMCy13luVxBXaXChYPeqMkUo46J1UadxvYdjRjj0E8zaw=="], + + "@tauri-apps/plugin-fs": ["@tauri-apps/plugin-fs@2.4.4", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-MTorXxIRmOnOPT1jZ3w96vjSuScER38ryXY88vl5F0uiKdnvTKKTtaEjTEo8uPbl4e3gnUtfsDVwC7h77GQLvQ=="], + + "@tauri-apps/plugin-log": ["@tauri-apps/plugin-log@2.7.1", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-jdb+o0wxQc8PjnLktgGpOs9Dh1YupaOGDXzO+Y8peA1UZ1ep3eXv4E1oiJ7nIQVN0XUFDDhnn3aBszl8ijhR+A=="], + + "@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.5.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-ei/yRRoCklWHImwpCcDK3VhNXx+QXM9793aQ64YxpqVF0BDuuIlXhZgiAkc15wnPVav+IbkYhmDJIv5R326Mew=="], + + "@tauri-apps/plugin-shell": ["@tauri-apps/plugin-shell@2.3.3", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-Xod+pRcFxmOWFWEnqH5yZcA7qwAMuaaDkMR1Sply+F8VfBj++CGnj2xf5UoialmjZ2Cvd8qrvSCbU+7GgNVsKQ=="], + + "@trpc/client": ["@trpc/client@10.45.3", "", { "peerDependencies": { "@trpc/server": "10.45.3" } }, "sha512-Fkr2pUckEyse4HeAj79FL9OIKGrITRzK4Z1KDB0qxlCwA7sgcAeafY23qzOOKHeJxAi+hTJmJx3k9p2iabY90g=="], + + "@trpc/server": ["@trpc/server@10.45.3", "", {}, "sha512-CVZM42FdGwqYQxV7vSRwb//AF8REfV60S/fUnwx7xqCnnSDus0FW/UuAOW4eQJ30RQlOPy4MYU8B3dbTAx85Ew=="], + + "@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + + "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], + + "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], + + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], + + "cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "devalue": ["devalue@5.6.1", "", {}, "sha512-jDwizj+IlEZBunHcOuuFVBnIMPAEHvTsJj0BcIp94xYguLRVBcXO853px/MyIJvbVzWdsGvrRweIUWJw8hBP7A=="], + + "enhanced-resolve": ["enhanced-resolve@5.18.4", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q=="], + + "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], + + "esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="], + + "esrap": ["esrap@2.2.1", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-GiYWG34AN/4CUyaWAgunGt0Rxvr1PTMlGC0vvEov/uOQYWne2bpN03Um+k8jT+q3op33mKouP2zeJ6OlM+qeUg=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="], + + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + + "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], + + "lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="], + + "locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="], + + "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + + "rollup": ["rollup@4.54.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.54.0", "@rollup/rollup-android-arm64": "4.54.0", "@rollup/rollup-darwin-arm64": "4.54.0", "@rollup/rollup-darwin-x64": "4.54.0", "@rollup/rollup-freebsd-arm64": "4.54.0", "@rollup/rollup-freebsd-x64": "4.54.0", "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", "@rollup/rollup-linux-arm-musleabihf": "4.54.0", "@rollup/rollup-linux-arm64-gnu": "4.54.0", "@rollup/rollup-linux-arm64-musl": "4.54.0", "@rollup/rollup-linux-loong64-gnu": "4.54.0", "@rollup/rollup-linux-ppc64-gnu": "4.54.0", "@rollup/rollup-linux-riscv64-gnu": "4.54.0", "@rollup/rollup-linux-riscv64-musl": "4.54.0", "@rollup/rollup-linux-s390x-gnu": "4.54.0", "@rollup/rollup-linux-x64-gnu": "4.54.0", "@rollup/rollup-linux-x64-musl": "4.54.0", "@rollup/rollup-openharmony-arm64": "4.54.0", "@rollup/rollup-win32-arm64-msvc": "4.54.0", "@rollup/rollup-win32-ia32-msvc": "4.54.0", "@rollup/rollup-win32-x64-gnu": "4.54.0", "@rollup/rollup-win32-x64-msvc": "4.54.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw=="], + + "sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="], + + "set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="], + + "sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "svelte": ["svelte@5.46.1", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.5.0", "esm-env": "^1.2.1", "esrap": "^2.2.1", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-ynjfCHD3nP2el70kN5Pmg37sSi0EjOm9FgHYQdC4giWG/hzO3AatzXXJJgP305uIhGQxSufJLuYWtkY8uK/8RA=="], + + "svelte-check": ["svelte-check@4.3.5", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-e4VWZETyXaKGhpkxOXP+B/d0Fp/zKViZoJmneZWe/05Y2aqSKj3YN2nLfYPJBQ87WEiY4BQCQ9hWGu9mPT1a1Q=="], + + "tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="], + + "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], + + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="], + + "typescript": ["typescript@5.6.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw=="], + + "vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="], + + "vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="], + + "zimmerframe": ["zimmerframe@1.1.4", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.0", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA=="], + + "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + + "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + } +} diff --git a/standalone/package.json b/standalone/package.json new file mode 100644 index 0000000..4fc1b60 --- /dev/null +++ b/standalone/package.json @@ -0,0 +1,37 @@ +{ + "name": "standalone", + "version": "0.1.0", + "description": "", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "build:tauri": "vite build && cargo build -p absurd_sqlite_extension --release", + "preview": "vite preview", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "tauri": "tauri" + }, + "license": "MIT", + "dependencies": { + "@tailwindcss/vite": "^4.1.18", + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-cli": "~2", + "@tauri-apps/plugin-fs": "~2", + "@tauri-apps/plugin-log": "~2", + "@tauri-apps/plugin-opener": "^2", + "@tauri-apps/plugin-shell": "~2", + "@trpc/client": "^10.45.2", + "tailwindcss": "^4.1.18" + }, + "devDependencies": { + "@sveltejs/adapter-static": "^3.0.6", + "@sveltejs/kit": "^2.9.0", + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "typescript": "~5.6.2", + "vite": "^6.0.3", + "@tauri-apps/cli": "^2" + } +} diff --git a/standalone/src-tauri/.gitignore b/standalone/src-tauri/.gitignore new file mode 100644 index 0000000..734925c --- /dev/null +++ b/standalone/src-tauri/.gitignore @@ -0,0 +1,10 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Generated by Tauri +# will have schema files for capabilities auto-completion +/gen/schemas + +bin/* +!bin/.gitignore diff --git a/standalone/src-tauri/Cargo.toml b/standalone/src-tauri/Cargo.toml new file mode 100644 index 0000000..b074959 --- /dev/null +++ b/standalone/src-tauri/Cargo.toml @@ -0,0 +1,38 @@ +[package] +authors = ["me@hbc.rocks"] +description = "Standalone Tauri application for Absurd SQLite" +edition = "2021" +name = "AbsurdSQLite" +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +# The `_lib` suffix may seem redundant but it is necessary +# to make the lib name unique and wouldn't conflict with the bin name. +# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519 +crate-type = ["staticlib", "cdylib", "rlib"] +name = "standalone_lib" + +[build-dependencies] +tauri-build = {version = "2", features = [] } + +[dependencies] +anyhow = "1.0.100" +axum = "0.7" +chrono = {version = "0.4", features = ["clock"] } +log = "0.4.29" +rusqlite = {version = "0.38.0", features = ["bundled", "load_extension"] } +serde = {version = "1", features = ["derive"] } +serde_json = "1" +tauri = {version = "2", features = [] } +tauri-plugin-fs = "2" +tauri-plugin-log = "2" +tauri-plugin-opener = "2" +tauri-plugin-shell = "2" +tauri-plugin-store = "2" +tokio = "1.48.0" +tower-http = {version = "0.5", features = ["cors"] } + +[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] +tauri-plugin-cli = "2" diff --git a/standalone/src-tauri/bin/.gitignore b/standalone/src-tauri/bin/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/standalone/src-tauri/bin/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/standalone/src-tauri/build.rs b/standalone/src-tauri/build.rs new file mode 100644 index 0000000..6013ad3 --- /dev/null +++ b/standalone/src-tauri/build.rs @@ -0,0 +1,65 @@ +use std::{env, fs, path::PathBuf}; + +fn main() { + prepare_sqlite_extension(); + tauri_build::build() +} + +fn prepare_sqlite_extension() { + let manifest_dir = + PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR must be set")); + let workspace_root = manifest_dir + .parent() + .and_then(|path| path.parent()) + .expect("workspace root"); + + let target_triple = env::var("TAURI_ENV_TARGET_TRIPLE") + .or_else(|_| env::var("TARGET")) + .unwrap_or_else(|_| "x86_64-unknown-linux-gnu".to_string()); + let profile = env::var("PROFILE").unwrap_or_else(|_| "debug".to_string()); + + let target_dir = env::var("CARGO_TARGET_DIR") + .map(PathBuf::from) + .unwrap_or_else(|_| workspace_root.join("target")); + let dylib_name = match target_triple.as_str() { + triple if triple.contains("windows") => "absurd.dll", + triple if triple.contains("apple") => "libabsurd.dylib", + _ => "libabsurd.so", + }; + + let candidates = [ + target_dir + .join(&target_triple) + .join(&profile) + .join(dylib_name), + target_dir.join(&profile).join(dylib_name), + ]; + + let extension_path = candidates.into_iter().find(|path| path.exists()).unwrap_or_else(|| { + panic!( + "absurd_sqlite_extension artifact not found. Build it with `cargo build -p absurd_sqlite_extension --target {target_triple} {}` first.", + if profile == "debug" { + "".to_string() + } else { + format!("--profile {profile}") + } + ); + }); + + let bin_dir = manifest_dir.join("bin"); + fs::create_dir_all(&bin_dir).expect("create bin directory"); + let dest = bin_dir.join(format!( + "absurd-extension-{}{}", + target_triple, + if target_triple.contains("windows") { + ".exe" + } else { + "" + } + )); + fs::copy(&extension_path, &dest).expect("copy SQLite extension into resources"); + println!( + "cargo:warning=Bundling absurd_sqlite_extension from {}", + extension_path.display() + ); +} diff --git a/standalone/src-tauri/capabilities/default.json b/standalone/src-tauri/capabilities/default.json new file mode 100644 index 0000000..bec1395 --- /dev/null +++ b/standalone/src-tauri/capabilities/default.json @@ -0,0 +1,15 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Capability for the main window", + "windows": [ + "main" + ], + "permissions": [ + "core:default", + "opener:default", + "shell:default", + "log:default", + "fs:default" + ] +} \ No newline at end of file diff --git a/standalone/src-tauri/capabilities/desktop.json b/standalone/src-tauri/capabilities/desktop.json new file mode 100644 index 0000000..742be1a --- /dev/null +++ b/standalone/src-tauri/capabilities/desktop.json @@ -0,0 +1,15 @@ +{ + "identifier": "desktop-capability", + "platforms": [ + "macOS", + "windows", + "linux" + ], + "windows": [ + "main" + ], + "permissions": [ + "cli:default", + "fs:default" + ] +} \ No newline at end of file diff --git a/standalone/src-tauri/icons/icon.icns b/standalone/src-tauri/icons/icon.icns new file mode 100644 index 0000000..23bc3a5 Binary files /dev/null and b/standalone/src-tauri/icons/icon.icns differ diff --git a/standalone/src-tauri/icons/icon.ico b/standalone/src-tauri/icons/icon.ico new file mode 100644 index 0000000..04cfabf Binary files /dev/null and b/standalone/src-tauri/icons/icon.ico differ diff --git a/standalone/src-tauri/icons/logo-1024x1024.png b/standalone/src-tauri/icons/logo-1024x1024.png new file mode 100644 index 0000000..4066767 Binary files /dev/null and b/standalone/src-tauri/icons/logo-1024x1024.png differ diff --git a/standalone/src-tauri/icons/logo-128x128.png b/standalone/src-tauri/icons/logo-128x128.png new file mode 100644 index 0000000..b74bf5f Binary files /dev/null and b/standalone/src-tauri/icons/logo-128x128.png differ diff --git a/standalone/src-tauri/icons/logo-32x32.png b/standalone/src-tauri/icons/logo-32x32.png new file mode 100644 index 0000000..6d94616 Binary files /dev/null and b/standalone/src-tauri/icons/logo-32x32.png differ diff --git a/standalone/src-tauri/icons/logo-512x512.png b/standalone/src-tauri/icons/logo-512x512.png new file mode 100644 index 0000000..441b5ce Binary files /dev/null and b/standalone/src-tauri/icons/logo-512x512.png differ diff --git a/standalone/src-tauri/src/db.rs b/standalone/src-tauri/src/db.rs new file mode 100644 index 0000000..627f1df --- /dev/null +++ b/standalone/src-tauri/src/db.rs @@ -0,0 +1,141 @@ +use anyhow::{Context, Result}; +use rusqlite::Connection; +use serde_json::Value; +use std::path::{Path, PathBuf}; +use tauri::{path::BaseDirectory, AppHandle, Manager}; +use tauri_plugin_cli::ArgData; + +enum Source { + Path(String), + AppData, +} + +pub struct DatabaseHandle { + source: Source, +} + +impl DatabaseHandle { + fn new(source: Source) -> Self { + Self { source } + } + + pub fn from_path(path: &str) -> Result { + Ok(Self::new(Source::Path(path.to_string()))) + } + + pub fn use_app_data(app_handle: &AppHandle) -> Result { + let app_data_dir = app_handle.path().app_local_data_dir()?; + if !app_data_dir.exists() { + std::fs::create_dir_all(&app_data_dir)?; + } + + let db_path = app_data_dir.join("absurd-sqlite.db"); + log::info!("Using database path: {:?}", db_path); + + Ok(Self::new(Source::AppData)) + } + + pub fn from_cli_arg(app_handle: &AppHandle, path: Option<&ArgData>) -> Result { + if let Some(arg_data) = path { + if let Value::String(db_path) = &arg_data.value { + log::info!("Opening database from CLI arg: {}", db_path); + return Self::from_path(db_path); + } + } + + Self::use_app_data(app_handle) + } + + pub fn db_path(&self, app_handle: &AppHandle) -> Result { + match &self.source { + Source::Path(path) => Ok(path.clone()), + Source::AppData => { + let app_data_dir = app_handle.path().app_local_data_dir()?; + if !app_data_dir.exists() { + std::fs::create_dir_all(&app_data_dir)?; + } + + let db_path = app_data_dir.join("absurd-sqlite.db"); + Ok(db_path.to_string_lossy().to_string()) + } + } + } + + pub fn connect(&self, app_handle: &AppHandle) -> Result { + let db_path = self.db_path(app_handle)?; + let conn = Connection::open(db_path)?; + + let extension_path = resolve_extension_path(app_handle); + if extension_path.is_none() { + // fail early if no extension found + return Err(anyhow::anyhow!("SQLite extension not found")); + } + + log::debug!("Loading SQLite extension from {:?}", extension_path); + // Safety: extension from own build + unsafe { + conn.load_extension_enable() + .context("enable extension loading")?; + conn.load_extension( + extension_path.unwrap().to_string_lossy().as_ref(), + None::<&str>, + ) + .context("load SQLite extension")?; + conn.load_extension_disable() + .context("disable extension loading")?; + } + log::debug!("SQLite extension loaded successfully"); + + Ok(conn) + } +} + +fn resolve_extension_path(app_handle: &AppHandle) -> Option { + let triple = std::env::var("TAURI_TARGET_TRIPLE").unwrap_or_else(|_| "unknown".to_string()); + let bin_name = format!( + "absurd-extension-{}{}", + triple, + if cfg!(target_os = "windows") { + ".exe" + } else { + "" + } + ); + + if let Ok(path) = app_handle + .path() + .resolve(Path::new("bin").join(&bin_name), BaseDirectory::Resource) + { + if path.exists() { + return Some(path); + } + } + + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let workspace_root = manifest_dir + .parent() + .and_then(Path::parent) + .unwrap_or(&manifest_dir); + let target_dir = workspace_root.join("target"); + let lib_name = extension_lib_name(); + let debug_path = target_dir.join("debug").join(&lib_name); + if debug_path.exists() { + return Some(debug_path); + } + let release_path = target_dir.join("release").join(&lib_name); + if release_path.exists() { + return Some(release_path); + } + + None +} + +fn extension_lib_name() -> String { + if cfg!(target_os = "windows") { + "absurd.dll".to_string() + } else if cfg!(target_os = "macos") { + "libabsurd.dylib".to_string() + } else { + "libabsurd.so".to_string() + } +} diff --git a/standalone/src-tauri/src/db_commands.rs b/standalone/src-tauri/src/db_commands.rs new file mode 100644 index 0000000..32ccff4 --- /dev/null +++ b/standalone/src-tauri/src/db_commands.rs @@ -0,0 +1,1525 @@ +use anyhow::{Context, Result}; +use chrono::{TimeZone, Utc}; +use rusqlite::{params_from_iter, types::Value as SqlValue, Connection, OptionalExtension}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::collections::HashMap; +use std::fs; +use std::path::{Path, PathBuf}; +use tauri::{AppHandle, State}; + +use crate::db::DatabaseHandle; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct OverviewMetrics { + pub active_queues: i64, + pub messages_processed: i64, + pub messages_in_queue: i64, + pub visible_now: i64, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct QueueMetric { + pub name: String, + pub in_queue: i64, + pub visible: i64, + pub newest_age: String, + pub oldest_age: String, + pub total_seen: i64, + pub scraped_at: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum TaskStatus { + Running, + Failed, + Completed, + Sleeping, + Pending, + Cancelled, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TaskRun { + pub id: String, + pub name: String, + pub queue: String, + pub status: TaskStatus, + pub attempt: String, + pub attempt_number: i64, + pub run_id: String, + pub age: String, + pub started_at: String, + pub updated_at: String, + pub created_ago: String, + pub updated_ago: String, + pub params_summary: String, + pub params_json: String, + pub final_state_json: Option, + pub worker: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TaskInfo { + pub id: String, + pub name: String, + pub queue: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TaskRunFilters { + pub queue_name: Option, + pub status: Option, + pub task_name: Option, + pub search: Option, + pub limit: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TaskRunPage { + pub runs: Vec, + pub total_count: i64, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct QueueSummaryStat { + pub label: String, + pub value: i64, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct QueueSummary { + pub name: String, + pub created_at: String, + pub age: String, + pub stats: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct EventFilterDefaults { + pub event_name_placeholder: String, + pub queue_label: String, + pub queue_options: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct EventEntry { + pub id: String, + pub name: String, + pub queue: String, + pub created_at: String, + pub payload_preview: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MigrationStatus { + pub status: String, + pub applied_count: i64, + pub latest_version: Option, + pub latest_applied_at: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SettingsInfo { + pub absurd_version: String, + pub sqlite_version: String, + pub db_path: String, + pub migration: MigrationStatus, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MigrationEntry { + pub id: i64, + pub introduced_version: String, + pub applied_at: Option, + pub status: String, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct EventFilters { + pub queue_name: Option, + pub event_name: Option, +} + +pub struct TauriDataProvider<'a> { + conn: &'a Connection, + now_ms: i64, +} + +impl<'a> TauriDataProvider<'a> { + pub fn new(conn: &'a Connection) -> Self { + Self { + conn, + now_ms: current_time_ms(), + } + } + + #[allow(unused)] + pub fn with_now(conn: &'a Connection, now_ms: i64) -> Self { + Self { conn, now_ms } + } + + pub fn get_overview_metrics(&self) -> Result { + let active_queues: i64 = self + .conn + .query_row( + "select count(distinct queue_name) from absurd_tasks where state in ('pending', 'running', 'sleeping')", + [], + |row| row.get(0), + ) + .unwrap_or(0); + + let messages_processed: i64 = self + .conn + .query_row( + "select count(*) from absurd_tasks where state in ('completed', 'failed', 'cancelled')", + [], + |row| row.get(0), + ) + .unwrap_or(0); + + let messages_in_queue: i64 = self + .conn + .query_row( + "select count(*) from absurd_tasks where state = 'pending'", + [], + |row| row.get(0), + ) + .unwrap_or(0); + + let visible_now: i64 = self + .conn + .query_row( + "select count(*) from absurd_runs where state = 'pending' and available_at <= ?", + [self.now_ms], + |row| row.get(0), + ) + .unwrap_or(0); + + Ok(OverviewMetrics { + active_queues, + messages_processed, + messages_in_queue, + visible_now, + }) + } + + pub fn get_queue_names(&self) -> Result> { + let mut stmt = self + .conn + .prepare("select queue_name from absurd_queues order by queue_name")?; + let names = stmt + .query_map([], |row| row.get(0))? + .collect::, _>>()?; + Ok(names) + } + + pub fn create_queue(&self, queue_name: &str) -> Result<()> { + let trimmed = queue_name.trim(); + if trimmed.is_empty() { + return Err(anyhow::anyhow!("Queue name is required")); + } + + self.conn.execute( + "insert or ignore into absurd_queues (queue_name, created_at) values (?1, ?2)", + rusqlite::params![trimmed, current_time_ms()], + )?; + Ok(()) + } + + pub fn get_queue_metrics(&self) -> Result> { + let queue_names = self.get_queue_names()?; + let mut metrics = Vec::with_capacity(queue_names.len()); + + for queue in queue_names { + let in_queue: i64 = self + .conn + .query_row( + "select count(*) from absurd_tasks where queue_name = ? and state = 'pending'", + [&queue], + |row| row.get(0), + ) + .unwrap_or(0); + + let visible: i64 = self + .conn + .query_row( + "select count(*) from absurd_runs where queue_name = ? and state = 'pending' and available_at <= ?", + rusqlite::params![queue, self.now_ms], + |row| row.get(0), + ) + .unwrap_or(0); + + let total_seen: i64 = self + .conn + .query_row( + "select count(*) from absurd_runs where queue_name = ?", + [&queue], + |row| row.get(0), + ) + .unwrap_or(0); + + let mut stmt = self.conn.prepare( + "select min(enqueue_at), max(enqueue_at) from absurd_tasks where queue_name = ?", + )?; + let (oldest, newest): (Option, Option) = + stmt.query_row([&queue], |row| Ok((row.get(0)?, row.get(1)?)))?; + + let (oldest_age, newest_age) = match (oldest, newest) { + (Some(oldest), Some(newest)) => ( + format_age_short(self.now_ms - oldest), + format_age_short(self.now_ms - newest), + ), + _ => ("--".to_string(), "--".to_string()), + }; + + metrics.push(QueueMetric { + name: queue, + in_queue, + visible, + newest_age, + oldest_age, + total_seen, + scraped_at: format_datetime_with_seconds(self.now_ms), + }); + } + + Ok(metrics) + } + + pub fn get_task_runs(&self) -> Result> { + self.fetch_task_runs(None) + } + + pub fn get_task_runs_for_queue(&self, queue_name: &str) -> Result> { + self.fetch_task_runs(Some(queue_name)) + } + + pub fn get_task_runs_page(&self, filters: TaskRunFilters) -> Result { + let (filter_sql, mut params) = build_task_run_filters(&filters); + let count_sql = format!( + "select count(*) from absurd_runs r + join absurd_tasks t + on t.queue_name = r.queue_name and t.task_id = r.task_id{}", + filter_sql + ); + + let total_count: i64 = self + .conn + .query_row(&count_sql, params_from_iter(params.iter()), |row| { + row.get(0) + }) + .unwrap_or(0); + + let limit = filters.limit.unwrap_or(500); + let mut sql = format!( + "select + r.queue_name, + t.task_id, + t.task_name, + r.state, + r.attempt, + r.run_id, + r.started_at, + r.created_at, + r.completed_at, + r.failed_at, + json(t.params), + t.max_attempts, + r.claimed_by, + json(r.result), + json(r.failure_reason) + from absurd_runs r + join absurd_tasks t + on t.queue_name = r.queue_name and t.task_id = r.task_id{} + order by r.created_at desc", + filter_sql + ); + + if limit > 0 { + sql.push_str(" limit ?"); + params.push(SqlValue::Integer(limit)); + } + + let mut stmt = self.conn.prepare(&sql)?; + let rows = stmt.query_map(params_from_iter(params.iter()), |row| { + map_task_run_row(row, self.now_ms) + })?; + + Ok(TaskRunPage { + runs: rows.collect::, _>>()?, + total_count, + }) + } + + pub fn get_task_name_options(&self, queue_name: Option<&str>) -> Result> { + let queue_filter = + queue_name.filter(|value| !value.is_empty() && value.to_lowercase() != "all queues"); + let (sql, params) = if let Some(queue) = queue_filter { + ( + "select distinct task_name from absurd_tasks where queue_name = ? order by task_name", + vec![SqlValue::Text(queue.to_string())], + ) + } else { + ( + "select distinct task_name from absurd_tasks order by task_name", + Vec::new(), + ) + }; + + let mut stmt = self.conn.prepare(sql)?; + let names = stmt + .query_map(params_from_iter(params.iter()), |row| row.get(0))? + .collect::, _>>()?; + Ok(names) + } + + pub fn get_task_history(&self, task_id: &str) -> Result> { + let mut stmt = self.conn.prepare( + "select + r.queue_name, + t.task_id, + t.task_name, + r.state, + r.attempt, + r.run_id, + r.started_at, + r.created_at, + r.completed_at, + r.failed_at, + json(t.params), + t.max_attempts, + r.claimed_by, + json(r.result), + json(r.failure_reason) + from absurd_runs r + join absurd_tasks t + on t.queue_name = r.queue_name and t.task_id = r.task_id + where t.task_id = ? + order by r.created_at desc", + )?; + + let rows = stmt.query_map([task_id], |row| map_task_run_row(row, self.now_ms))?; + Ok(rows.collect::, _>>()?) + } + + pub fn get_task_info(&self, task_id: &str) -> Result> { + self.conn + .query_row( + "select task_id, task_name, queue_name from absurd_tasks where task_id = ?", + [task_id], + |row| { + Ok(TaskInfo { + id: row.get(0)?, + name: row.get(1)?, + queue: row.get(2)?, + }) + }, + ) + .optional() + .map_err(Into::into) + } + + pub fn get_queue_summaries(&self) -> Result> { + let mut stmt = self + .conn + .prepare("select queue_name, created_at from absurd_queues order by queue_name")?; + let queue_rows = stmt.query_map([], |row| { + Ok((row.get::<_, String>(0)?, row.get::<_, i64>(1)?)) + })?; + + let mut summaries = Vec::new(); + + for row in queue_rows { + let (queue_name, created_at) = row?; + let stats = self.get_queue_stats(&queue_name)?; + + summaries.push(QueueSummary { + name: queue_name, + created_at: format!("Created {}", format_datetime(created_at)), + age: format_age_ago(self.now_ms - created_at), + stats, + }); + } + + Ok(summaries) + } + + pub fn get_event_filter_defaults( + &self, + queue_name: Option<&str>, + ) -> Result { + let placeholder: Option = self + .conn + .query_row( + "select event_name from absurd_events order by emitted_at desc limit 1", + [], + |row| row.get(0), + ) + .optional()?; + + let queue_options = { + let mut options = Vec::new(); + options.push("All queues".to_string()); + options.extend(self.get_queue_names()?); + options + }; + + Ok(EventFilterDefaults { + event_name_placeholder: placeholder.unwrap_or_else(|| "payment.completed".to_string()), + queue_label: queue_name.unwrap_or("All queues").to_string(), + queue_options, + }) + } + + pub fn get_events(&self) -> Result> { + self.get_filtered_events(None) + } + + pub fn get_filtered_events(&self, filters: Option) -> Result> { + let queue_filter = filters + .as_ref() + .and_then(|filters| filters.queue_name.clone()) + .filter(|value| !value.is_empty() && value.to_lowercase() != "all queues"); + let event_filter = filters + .as_ref() + .and_then(|filters| filters.event_name.clone()) + .map(|value| value.trim().to_lowercase()) + .filter(|value| !value.is_empty()); + + let mut stmt = self.conn.prepare( + "select queue_name, event_name, emitted_at, json(payload) + from absurd_events + order by emitted_at desc", + )?; + + let rows = stmt.query_map([], |row| { + let queue: String = row.get(0)?; + let name: String = row.get(1)?; + let emitted_at: i64 = row.get(2)?; + let payload: SqlValue = row.get(3)?; + + Ok((queue, name, emitted_at, payload)) + })?; + + let mut entries = Vec::new(); + + for row in rows { + let (queue, name, emitted_at, payload) = row?; + + if let Some(filter_queue) = &queue_filter { + if queue.to_lowercase() != filter_queue.to_lowercase() { + continue; + } + } + + if let Some(filter_event) = &event_filter { + if !name.to_lowercase().contains(filter_event) { + continue; + } + } + + let payload_preview = payload_preview_from_value(json_from_sql_value(payload)); + + entries.push(EventEntry { + id: format!("{}_{}", queue, name), + name, + queue, + created_at: format_datetime(emitted_at), + payload_preview, + }); + } + + Ok(entries) + } + + pub fn get_settings_info(&self, db_path: String) -> Result { + let absurd_version: String = self + .conn + .query_row("select absurd_version()", [], |row| row.get(0))?; + let sqlite_version: String = self + .conn + .query_row("select sqlite_version()", [], |row| row.get(0))?; + + let applied_count: i64 = self + .conn + .query_row( + "select count(*) from absurd_migration_records()", + [], + |row| row.get(0), + ) + .unwrap_or(0); + let latest: Option<(String, i64)> = self + .conn + .query_row( + "select introduced_version, applied_time from absurd_migration_records() order by id desc limit 1", + [], + |row| Ok((row.get(0)?, row.get(1)?)), + ) + .optional()?; + + let (latest_version, latest_applied_at) = match latest { + Some((version, applied_time)) => (Some(version), Some(format_datetime(applied_time))), + None => (None, None), + }; + + let status = if applied_count > 0 { + "applied".to_string() + } else { + "missing".to_string() + }; + + Ok(SettingsInfo { + absurd_version, + sqlite_version, + db_path, + migration: MigrationStatus { + status, + applied_count, + latest_version, + latest_applied_at, + }, + }) + } + + pub fn get_migrations(&self) -> Result> { + let mut stmt = self.conn.prepare( + "select id, introduced_version, applied_time from absurd_migration_records() order by id", + )?; + let applied_rows = stmt + .query_map([], |row| { + Ok((row.get::<_, i64>(0)?, row.get(1)?, row.get(2)?)) + })? + .collect::, _>>()?; + + let mut applied_map: HashMap = HashMap::new(); + for (id, introduced_version, applied_time) in applied_rows { + applied_map.insert(id, (introduced_version, applied_time)); + } + + let mut entries = Vec::new(); + let mut known_ids = HashMap::new(); + for migration in read_known_migrations() { + let applied = applied_map.get(&migration.id); + entries.push(MigrationEntry { + id: migration.id, + introduced_version: applied + .map(|(version, _)| version.clone()) + .unwrap_or(migration.introduced_version), + applied_at: applied.map(|(_, time)| format_datetime(*time)), + status: if applied.is_some() { + "applied".to_string() + } else { + "pending".to_string() + }, + }); + known_ids.insert(migration.id, ()); + } + + for (id, (introduced_version, applied_time)) in applied_map { + if known_ids.contains_key(&id) { + continue; + } + entries.push(MigrationEntry { + id, + introduced_version, + applied_at: Some(format_datetime(applied_time)), + status: "applied".to_string(), + }); + } + + entries.sort_by_key(|entry| entry.id); + Ok(entries) + } + + pub fn apply_migrations_all(&self) -> Result { + let applied: i64 = self + .conn + .query_row("select absurd_apply_migrations()", [], |row| row.get(0))?; + Ok(applied) + } + + pub fn apply_migration(&self, migration_id: i64) -> Result { + let applied: i64 = self.conn.query_row( + "select absurd_apply_migrations(?1)", + [migration_id], + |row| row.get(0), + )?; + Ok(applied) + } + + fn get_queue_stats(&self, queue_name: &str) -> Result> { + let mut stmt = self.conn.prepare( + "select state, count(*) from absurd_tasks where queue_name = ? group by state", + )?; + let rows = stmt.query_map([queue_name], |row| { + Ok((row.get::<_, String>(0)?, row.get::<_, i64>(1)?)) + })?; + + let mut counts = std::collections::HashMap::new(); + for row in rows { + let (state, count) = row?; + counts.insert(state, count); + } + + let labels = [ + ("pending", "Pending"), + ("running", "Running"), + ("sleeping", "Sleeping"), + ("completed", "Completed"), + ("failed", "Failed"), + ("cancelled", "Cancelled"), + ]; + + Ok(labels + .iter() + .map(|(state, label)| QueueSummaryStat { + label: (*label).to_string(), + value: *counts.get(*state).unwrap_or(&0), + }) + .collect()) + } + + fn fetch_task_runs(&self, queue_name: Option<&str>) -> Result> { + if let Some(queue) = queue_name { + let mut stmt = self.conn.prepare( + r#"select + r.queue_name, + t.task_id, + t.task_name, + r.state, + r.attempt, + r.run_id, + r.started_at, + r.created_at, + r.completed_at, + r.failed_at, + json(t.params), + t.max_attempts, + r.claimed_by, + json(r.result), + json(r.failure_reason) + from absurd_runs r + join absurd_tasks t + on t.queue_name = r.queue_name and t.task_id = r.task_id + where r.queue_name = ? + order by r.created_at desc"#, + )?; + let rows = stmt.query_map(rusqlite::params![queue], |row| { + map_task_run_row(row, self.now_ms) + })?; + return Ok(rows.collect::, _>>()?); + } + + let sql = r#"select + r.queue_name, + t.task_id, + t.task_name, + r.state, + r.attempt, + r.run_id, + r.started_at, + r.created_at, + r.completed_at, + r.failed_at, + json(t.params), + t.max_attempts, + r.claimed_by, + json(r.result), + json(r.failure_reason) + from absurd_runs r + join absurd_tasks t + on t.queue_name = r.queue_name and t.task_id = r.task_id + order by r.created_at desc"#; + + let mut stmt = self.conn.prepare(sql)?; + let rows = stmt.query_map([], |row| map_task_run_row(row, self.now_ms))?; + Ok(rows.collect::, _>>()?) + } +} + +fn build_task_run_filters(filters: &TaskRunFilters) -> (String, Vec) { + let mut clauses: Vec = Vec::new(); + let mut params: Vec = Vec::new(); + + if let Some(queue) = filters + .queue_name + .as_ref() + .map(|value| value.trim().to_string()) + .filter(|value| !value.is_empty() && value.to_lowercase() != "all queues") + { + clauses.push("r.queue_name = ?".to_string()); + params.push(SqlValue::Text(queue)); + } + + if let Some(status) = filters + .status + .as_ref() + .map(|value| value.trim().to_lowercase()) + .filter(|value| !value.is_empty()) + { + clauses.push("lower(r.state) = ?".to_string()); + params.push(SqlValue::Text(status)); + } + + if let Some(task_name) = filters + .task_name + .as_ref() + .map(|value| value.trim().to_string()) + .filter(|value| !value.is_empty()) + { + clauses.push("t.task_name = ?".to_string()); + params.push(SqlValue::Text(task_name)); + } + + if let Some(search) = filters + .search + .as_ref() + .map(|value| value.trim().to_lowercase()) + .filter(|value| !value.is_empty()) + { + let like = format!("%{}%", search); + clauses.push( + "(lower(t.task_id) like ? + or lower(r.run_id) like ? + or lower(t.task_name) like ? + or lower(r.queue_name) like ? + or lower(r.state) like ? + or lower(coalesce(r.claimed_by, '')) like ? + or lower(coalesce(json(t.params), '')) like ? + or lower(coalesce(json(r.result), '')) like ? + or lower(coalesce(json(r.failure_reason), '')) like ?)" + .to_string(), + ); + for _ in 0..9 { + params.push(SqlValue::Text(like.clone())); + } + } + + let filter_sql = if clauses.is_empty() { + "".to_string() + } else { + format!(" where {}", clauses.join(" and ")) + }; + + (filter_sql, params) +} + +fn map_task_run_row(row: &rusqlite::Row<'_>, now_ms: i64) -> rusqlite::Result { + let queue: String = row.get(0)?; + let task_id: String = row.get(1)?; + let task_name: String = row.get(2)?; + let state: String = row.get(3)?; + let attempt_number: i64 = row.get(4)?; + let run_id: String = row.get(5)?; + let started_at: Option = row.get(6)?; + let created_at: i64 = row.get(7)?; + let completed_at: Option = row.get(8)?; + let failed_at: Option = row.get(9)?; + let params_value = json_from_sql_value(row.get::<_, SqlValue>(10)?).unwrap_or(Value::Null); + let max_attempts: Option = row.get(11)?; + let claimed_by: Option = row.get(12)?; + let result_value = json_from_sql_value(row.get::<_, SqlValue>(13)?); + let failure_value = json_from_sql_value(row.get::<_, SqlValue>(14)?); + let params_summary = build_params_summary(¶ms_value); + let params_json = format_json_pretty(¶ms_value); + + let final_state_json = match state.as_str() { + "failed" => failure_value.as_ref().map(format_json_pretty), + "completed" => result_value.as_ref().map(format_json_pretty), + _ => None, + }; + + let updated_at = completed_at + .or(failed_at) + .or(started_at) + .unwrap_or(created_at); + let started_at = started_at.unwrap_or(created_at); + + Ok(TaskRun { + id: task_id, + name: task_name, + queue, + status: map_task_status(&state), + attempt: format_attempt(attempt_number, max_attempts), + attempt_number, + run_id, + age: format_age_short(now_ms - created_at), + started_at: format_datetime(started_at), + updated_at: format_datetime(updated_at), + created_ago: format_age_ago(now_ms - created_at), + updated_ago: format_age_ago(now_ms - updated_at), + params_summary, + params_json, + final_state_json, + worker: claimed_by.unwrap_or_else(|| "--".to_string()), + }) +} + +fn map_task_status(state: &str) -> TaskStatus { + match state { + "running" => TaskStatus::Running, + "failed" => TaskStatus::Failed, + "completed" => TaskStatus::Completed, + "sleeping" => TaskStatus::Sleeping, + "pending" => TaskStatus::Pending, + "cancelled" => TaskStatus::Cancelled, + _ => TaskStatus::Pending, + } +} + +fn format_attempt(attempt: i64, max_attempts: Option) -> String { + match max_attempts { + Some(max) => format!("{} / {}", attempt, max), + None => format!("{} / inf", attempt), + } +} + +fn format_json_pretty(value: &Value) -> String { + serde_json::to_string_pretty(value).unwrap_or_else(|_| value.to_string()) +} + +fn build_params_summary(value: &Value) -> String { + if let Some(obj) = value.as_object() { + let mut parts = Vec::new(); + for (key, val) in obj.iter().take(2) { + let formatted = serde_json::to_string(val).unwrap_or_else(|_| "null".to_string()); + parts.push(format!("\"{}\": {}", key, formatted)); + } + return format!("{{ {} }}", parts.join(", ")); + } + + value.to_string() +} + +fn payload_preview_from_value(value: Option) -> String { + let Some(value) = value else { + return String::new(); + }; + let compact = serde_json::to_string(&value).unwrap_or_else(|_| value.to_string()); + truncate_string(&compact, 120) +} + +fn json_from_sql_value(value: SqlValue) -> Option { + let text = match value { + SqlValue::Null => return None, + SqlValue::Text(text) => text, + SqlValue::Blob(blob) => String::from_utf8(blob).ok()?, + SqlValue::Integer(value) => value.to_string(), + SqlValue::Real(value) => value.to_string(), + }; + + serde_json::from_str(&text).ok() +} + +fn truncate_string(value: &str, max_len: usize) -> String { + if value.len() <= max_len { + return value.to_string(); + } + + let truncated = &value[..max_len.saturating_sub(3)]; + format!("{}...", truncated) +} + +struct MigrationFile { + id: i64, + introduced_version: String, +} + +fn parse_introduced_version(contents: &str, default_version: &str) -> String { + for line in contents.lines() { + let trimmed = line.trim(); + if let Some(rest) = trimmed.strip_prefix("-- introduced_version:") { + let value = rest.trim(); + if !value.is_empty() { + return value.to_string(); + } + } + } + default_version.to_string() +} + +fn read_known_migrations() -> Vec { + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let workspace_root = manifest_dir + .parent() + .and_then(Path::parent) + .unwrap_or(&manifest_dir); + let migrations_dir = workspace_root + .join("absurd-sqlite-extension") + .join("migrations"); + let entries = match fs::read_dir(&migrations_dir) { + Ok(entries) => entries, + Err(_) => return Vec::new(), + }; + + let default_version = "0.0.0"; + let mut migrations = Vec::new(); + + for entry in entries.flatten() { + let path = entry.path(); + if path.extension().and_then(|ext| ext.to_str()) != Some("sql") { + continue; + } + let filename = match path.file_name().and_then(|name| name.to_str()) { + Some(name) => name, + None => continue, + }; + let stem = match Path::new(filename) + .file_stem() + .and_then(|name| name.to_str()) + { + Some(stem) => stem, + None => continue, + }; + if !stem.chars().all(|c| c.is_ascii_digit()) { + continue; + } + let id: i64 = match stem.parse() { + Ok(id) => id, + Err(_) => continue, + }; + let contents = match fs::read_to_string(&path) { + Ok(contents) => contents, + Err(_) => continue, + }; + let introduced_version = parse_introduced_version(&contents, default_version); + migrations.push(MigrationFile { + id, + introduced_version, + }); + } + + migrations.sort_by_key(|migration| migration.id); + migrations +} + +fn format_datetime(ms: i64) -> String { + let fallback = Utc.timestamp_millis_opt(0).single().unwrap(); + let dt = Utc.timestamp_millis_opt(ms).single().unwrap_or(fallback); + dt.format("%b %-d, %Y, %-I:%M %p").to_string() +} + +fn format_datetime_with_seconds(ms: i64) -> String { + let fallback = Utc.timestamp_millis_opt(0).single().unwrap(); + let dt = Utc.timestamp_millis_opt(ms).single().unwrap_or(fallback); + dt.format("%b %-d, %Y, %-I:%M:%S %p").to_string() +} + +fn format_age_short(delta_ms: i64) -> String { + let mut delta = delta_ms; + if delta < 0 { + delta = 0; + } + + let seconds = delta / 1000; + let days = seconds / 86_400; + if days > 0 { + return format!("{}d", days); + } + + let hours = seconds / 3_600; + if hours > 0 { + return format!("{}h", hours); + } + + let minutes = seconds / 60; + if minutes > 0 { + return format!("{}m", minutes); + } + + format!("{}s", seconds) +} + +fn format_age_ago(delta_ms: i64) -> String { + format!("{} ago", format_age_short(delta_ms)) +} + +fn current_time_ms() -> i64 { + use std::time::{SystemTime, UNIX_EPOCH}; + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default(); + now.as_millis() as i64 +} + +#[tauri::command] +pub fn get_overview_metrics( + app_handle: AppHandle, + db_handle: State, +) -> Result { + with_provider(&app_handle, &db_handle, |provider| { + provider.get_overview_metrics() + }) +} + +#[tauri::command] +pub fn get_queue_metrics( + app_handle: AppHandle, + db_handle: State, +) -> Result, String> { + with_provider(&app_handle, &db_handle, |provider| { + provider.get_queue_metrics() + }) +} + +#[tauri::command] +pub fn get_task_runs( + app_handle: AppHandle, + db_handle: State, +) -> Result, String> { + with_provider(&app_handle, &db_handle, |provider| provider.get_task_runs()) +} + +#[tauri::command] +pub fn get_task_runs_for_queue( + queue_name: String, + app_handle: AppHandle, + db_handle: State, +) -> Result, String> { + with_provider(&app_handle, &db_handle, |provider| { + provider.get_task_runs_for_queue(&queue_name) + }) +} + +#[tauri::command] +pub fn get_task_runs_page( + filters: TaskRunFilters, + app_handle: AppHandle, + db_handle: State, +) -> Result { + with_provider(&app_handle, &db_handle, |provider| { + provider.get_task_runs_page(filters) + }) +} + +#[tauri::command] +pub fn get_task_name_options( + queue_name: Option, + app_handle: AppHandle, + db_handle: State, +) -> Result, String> { + with_provider(&app_handle, &db_handle, |provider| { + provider.get_task_name_options(queue_name.as_deref()) + }) +} + +#[tauri::command] +pub fn get_task_history( + task_id: String, + app_handle: AppHandle, + db_handle: State, +) -> Result, String> { + with_provider(&app_handle, &db_handle, |provider| { + provider.get_task_history(&task_id) + }) +} + +#[tauri::command] +pub fn get_task_info( + task_id: String, + app_handle: AppHandle, + db_handle: State, +) -> Result, String> { + with_provider(&app_handle, &db_handle, |provider| { + provider.get_task_info(&task_id) + }) +} + +#[tauri::command] +pub fn get_queue_names( + app_handle: AppHandle, + db_handle: State, +) -> Result, String> { + with_provider(&app_handle, &db_handle, |provider| { + provider.get_queue_names() + }) +} + +#[tauri::command] +pub fn get_queue_summaries( + app_handle: AppHandle, + db_handle: State, +) -> Result, String> { + with_provider(&app_handle, &db_handle, |provider| { + provider.get_queue_summaries() + }) +} + +#[tauri::command] +pub fn create_queue( + queue_name: String, + app_handle: AppHandle, + db_handle: State, +) -> Result<(), String> { + with_provider(&app_handle, &db_handle, |provider| { + provider.create_queue(&queue_name) + }) +} + +#[tauri::command] +pub fn get_event_filter_defaults( + queue_name: Option, + app_handle: AppHandle, + db_handle: State, +) -> Result { + with_provider(&app_handle, &db_handle, |provider| { + provider.get_event_filter_defaults(queue_name.as_deref()) + }) +} + +#[tauri::command] +pub fn get_events( + app_handle: AppHandle, + db_handle: State, +) -> Result, String> { + with_provider(&app_handle, &db_handle, |provider| provider.get_events()) +} + +#[tauri::command] +pub fn get_filtered_events( + filters: EventFilters, + app_handle: AppHandle, + db_handle: State, +) -> Result, String> { + with_provider(&app_handle, &db_handle, |provider| { + provider.get_filtered_events(Some(filters)) + }) +} + +#[tauri::command] +pub fn get_settings_info( + app_handle: AppHandle, + db_handle: State, +) -> Result { + let db_path = db_handle + .db_path(&app_handle) + .context("failed to resolve database path") + .map_err(|err| err.to_string())?; + with_provider(&app_handle, &db_handle, |provider| { + provider.get_settings_info(db_path.clone()) + }) +} + +#[tauri::command] +pub fn get_migrations( + app_handle: AppHandle, + db_handle: State, +) -> Result, String> { + with_provider(&app_handle, &db_handle, |provider| { + provider.get_migrations() + }) +} + +#[tauri::command] +pub fn apply_migrations_all( + app_handle: AppHandle, + db_handle: State, +) -> Result { + with_provider(&app_handle, &db_handle, |provider| { + provider.apply_migrations_all() + }) +} + +#[tauri::command] +pub fn apply_migration( + migration_id: i64, + app_handle: AppHandle, + db_handle: State, +) -> Result { + with_provider(&app_handle, &db_handle, |provider| { + provider.apply_migration(migration_id) + }) +} + +fn with_provider( + app_handle: &AppHandle, + db_handle: &DatabaseHandle, + f: impl FnOnce(&TauriDataProvider<'_>) -> Result, +) -> Result { + let conn = db_handle + .connect(app_handle) + .context("failed to connect to database") + .map_err(|err| err.to_string())?; + let provider = TauriDataProvider::new(&conn); + f(&provider).map_err(|err| err.to_string()) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn setup_connection() -> Connection { + let conn = Connection::open_in_memory().expect("open in-memory"); + conn.execute_batch(include_str!( + "../../../absurd-sqlite-extension/migrations/1.sql" + )) + .expect("apply schema"); + conn + } + + fn seed_data(conn: &Connection, now_ms: i64) { + conn.execute( + "insert into absurd_queues (queue_name, created_at) values (?, ?)", + rusqlite::params!["default", now_ms - 1000], + ) + .expect("insert queue"); + + let params = serde_json::json!({"tenant": "absurd", "retries": 2}).to_string(); + + conn.execute( + "insert into absurd_tasks (queue_name, task_id, task_name, params, enqueue_at, state, attempts) + values (?, ?, ?, ?, ?, ?, ?)", + rusqlite::params![ + "default", + "task_pending", + "pending", + params, + now_ms - 10_000, + "pending", + 0 + ], + ) + .expect("insert task pending"); + + conn.execute( + "insert into absurd_tasks (queue_name, task_id, task_name, params, enqueue_at, state, attempts) + values (?, ?, ?, ?, ?, ?, ?)", + rusqlite::params![ + "default", + "task_running", + "running", + params, + now_ms - 20_000, + "running", + 1 + ], + ) + .expect("insert task running"); + + conn.execute( + "insert into absurd_tasks (queue_name, task_id, task_name, params, enqueue_at, state, attempts) + values (?, ?, ?, ?, ?, ?, ?)", + rusqlite::params![ + "default", + "task_completed", + "completed", + params, + now_ms - 30_000, + "completed", + 1 + ], + ) + .expect("insert task completed"); + + conn.execute( + "insert into absurd_tasks (queue_name, task_id, task_name, params, enqueue_at, state, attempts) + values (?, ?, ?, ?, ?, ?, ?)", + rusqlite::params![ + "default", + "task_cancelled", + "cancelled", + params, + now_ms - 15_000, + "cancelled", + 1 + ], + ) + .expect("insert task cancelled"); + + conn.execute( + "insert into absurd_runs (queue_name, run_id, task_id, attempt, state, available_at, created_at, started_at, claimed_by) + values (?, ?, ?, ?, ?, ?, ?, ?, ?)", + rusqlite::params![ + "default", + "run_running", + "task_running", + 1, + "running", + now_ms - 5_000, + now_ms - 20_000, + now_ms - 15_000, + "worker-1" + ], + ) + .expect("insert run running"); + + conn.execute( + "insert into absurd_runs (queue_name, run_id, task_id, attempt, state, available_at, created_at, completed_at, result) + values (?, ?, ?, ?, ?, ?, ?, ?, ?)", + rusqlite::params![ + "default", + "run_completed", + "task_completed", + 1, + "completed", + now_ms - 25_000, + now_ms - 25_000, + now_ms - 5_000, + serde_json::json!({"ok": true}).to_string() + ], + ) + .expect("insert run completed"); + + conn.execute( + "insert into absurd_runs (queue_name, run_id, task_id, attempt, state, available_at, created_at) + values (?, ?, ?, ?, ?, ?, ?)", + rusqlite::params![ + "default", + "run_pending", + "task_pending", + 1, + "pending", + now_ms - 1_000, + now_ms - 9_000 + ], + ) + .expect("insert run pending"); + + conn.execute( + "insert into absurd_runs (queue_name, run_id, task_id, attempt, state, available_at, created_at) + values (?, ?, ?, ?, ?, ?, ?)", + rusqlite::params![ + "default", + "run_cancelled", + "task_cancelled", + 1, + "cancelled", + now_ms - 1_000, + now_ms - 8_000 + ], + ) + .expect("insert run cancelled"); + + conn.execute( + "insert into absurd_events (queue_name, event_name, payload, emitted_at) + values (?, ?, ?, ?)", + rusqlite::params![ + "default", + "task.completed", + serde_json::json!({"taskId": "task_completed"}).to_string(), + now_ms - 2_000 + ], + ) + .expect("insert event"); + } + + #[test] + fn overview_metrics_counts() { + let now_ms = 1_700_000_000_000; + let conn = setup_connection(); + seed_data(&conn, now_ms); + + let provider = TauriDataProvider::with_now(&conn, now_ms); + let metrics = provider.get_overview_metrics().expect("metrics"); + + assert_eq!(metrics.active_queues, 1); + assert_eq!(metrics.messages_processed, 2); + assert_eq!(metrics.messages_in_queue, 1); + assert_eq!(metrics.visible_now, 1); + } + + #[test] + fn queue_metrics_show_ages_and_counts() { + let now_ms = 1_700_000_000_000; + let conn = setup_connection(); + seed_data(&conn, now_ms); + + let provider = TauriDataProvider::with_now(&conn, now_ms); + let metrics = provider.get_queue_metrics().expect("metrics"); + + assert_eq!(metrics.len(), 1); + let metric = &metrics[0]; + assert_eq!(metric.name, "default"); + assert_eq!(metric.in_queue, 1); + assert_eq!(metric.visible, 1); + assert_eq!(metric.total_seen, 4); + assert_eq!(metric.newest_age, format_age_short(10_000)); + assert_eq!(metric.oldest_age, format_age_short(30_000)); + assert_eq!(metric.scraped_at, format_datetime_with_seconds(now_ms)); + } + + #[test] + fn task_runs_include_status_and_attempts() { + let now_ms = 1_700_000_000_000; + let conn = setup_connection(); + seed_data(&conn, now_ms); + + let provider = TauriDataProvider::with_now(&conn, now_ms); + let runs = provider.get_task_runs().expect("runs"); + + assert_eq!(runs.len(), 4); + let running = runs + .iter() + .find(|run| run.run_id == "run_running") + .expect("running run"); + + assert_eq!(running.status, TaskStatus::Running); + assert_eq!(running.attempt, "1 / inf"); + assert_eq!(running.worker, "worker-1"); + + let cancelled = runs + .iter() + .find(|run| run.run_id == "run_cancelled") + .expect("cancelled run"); + assert_eq!(cancelled.status, TaskStatus::Cancelled); + } + + #[test] + fn queue_summaries_include_state_counts() { + let now_ms = 1_700_000_000_000; + let conn = setup_connection(); + seed_data(&conn, now_ms); + + let provider = TauriDataProvider::with_now(&conn, now_ms); + let summaries = provider.get_queue_summaries().expect("summaries"); + + assert_eq!(summaries.len(), 1); + let summary = &summaries[0]; + assert_eq!(summary.name, "default"); + assert_eq!(summary.age, format_age_ago(1000)); + assert_eq!(summary.stats.len(), 6); + assert_eq!(summary.stats[0].value, 1); // Pending + assert_eq!(summary.stats[5].value, 1); // Cancelled + } + + #[test] + fn events_and_filters_work() { + let now_ms = 1_700_000_000_000; + let conn = setup_connection(); + seed_data(&conn, now_ms); + + let provider = TauriDataProvider::with_now(&conn, now_ms); + let defaults = provider.get_event_filter_defaults(None).expect("defaults"); + assert_eq!(defaults.event_name_placeholder, "task.completed"); + assert_eq!(defaults.queue_label, "All queues"); + assert_eq!(defaults.queue_options, vec!["All queues", "default"]); + + let events = provider.get_events().expect("events"); + assert_eq!(events.len(), 1); + assert_eq!(events[0].queue, "default"); + + let filtered = provider + .get_filtered_events(Some(EventFilters { + queue_name: Some("default".to_string()), + event_name: Some("completed".to_string()), + })) + .expect("filtered"); + assert_eq!(filtered.len(), 1); + } +} diff --git a/standalone/src-tauri/src/dev_api.rs b/standalone/src-tauri/src/dev_api.rs new file mode 100644 index 0000000..46aa832 --- /dev/null +++ b/standalone/src-tauri/src/dev_api.rs @@ -0,0 +1,467 @@ +use axum::{ + extract::{Path, Query, State}, + routing::post, + Json, Router, +}; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; +use std::sync::Mutex; +use tauri::{AppHandle, Manager}; +use tauri_plugin_store::StoreExt; +use tokio::sync::oneshot; +use tokio::task::JoinHandle; +use tower_http::cors::CorsLayer; + +use crate::db::DatabaseHandle; +use crate::db_commands::{EventFilters, TaskRunFilters, TauriDataProvider}; + +const DEV_API_PORT_DEFAULT: u16 = 11223; +const DEV_API_PORT_ATTEMPTS: u16 = 10; +const DEV_API_STORE_PATH: &str = "dev-api.json"; +const DEV_API_ENABLED_KEY: &str = "dev_api_enabled"; + +#[derive(Clone)] +struct DevApiContext { + app_handle: AppHandle, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct TrpcRequest { + id: Option, + json: Option, + input: Option, +} + +#[derive(Deserialize)] +struct TrpcQuery { + input: Option, + json: Option, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DevApiStatus { + pub enabled: bool, + pub running: bool, + pub port: Option, + pub desired_port: u16, +} + +struct RunningDevApi { + port: u16, + shutdown: oneshot::Sender<()>, + handle: JoinHandle<()>, +} + +pub struct DevApiState { + enabled: Mutex, + desired_port: Mutex, + running: Mutex>, +} + +impl DevApiState { + pub fn new(enabled: bool, desired_port: Option) -> Self { + Self { + enabled: Mutex::new(enabled), + desired_port: Mutex::new(desired_port.unwrap_or(DEV_API_PORT_DEFAULT)), + running: Mutex::new(None), + } + } + + pub fn status(&self) -> DevApiStatus { + let enabled = *self.enabled.lock().unwrap(); + let desired_port = *self.desired_port.lock().unwrap(); + let running_guard = self.running.lock().unwrap(); + let running = running_guard.is_some(); + let port = running_guard.as_ref().map(|server| server.port); + DevApiStatus { + enabled, + running, + port, + desired_port, + } + } +} + +#[tauri::command] +pub async fn get_dev_api_status(app_handle: AppHandle) -> Result { + let state = app_handle.state::(); + Ok(state.status()) +} + +#[tauri::command] +pub async fn set_dev_api_enabled( + app_handle: AppHandle, + enabled: bool, +) -> Result { + set_enabled(&app_handle, enabled).await +} + +pub fn load_dev_api_enabled(app_handle: &AppHandle) -> Option { + let store = app_handle.store(DEV_API_STORE_PATH).ok()?; + if store.reload().is_err() { + return None; + } + let stored = store.get(DEV_API_ENABLED_KEY); + stored + .as_ref() + .map(|value| parse_dev_api_enabled(Some(value))) +} + +fn persist_dev_api_enabled(app_handle: &AppHandle, enabled: bool) -> Result<(), String> { + let store = app_handle + .store(DEV_API_STORE_PATH) + .map_err(|err| err.to_string())?; + store.set(DEV_API_ENABLED_KEY, Value::Bool(enabled)); + store.save().map_err(|err| err.to_string()) +} + +pub fn parse_dev_api_enabled(value: Option<&serde_json::Value>) -> bool { + match value { + Some(Value::Bool(flag)) => *flag, + Some(Value::String(text)) => text.parse::().unwrap_or(false), + Some(Value::Null) => true, + Some(_) => true, + None => false, + } +} + +#[cfg(test)] +mod tests { + use super::parse_dev_api_enabled; + use serde_json::json; + + #[test] + fn parses_dev_api_enabled_true() { + let value = json!(true); + assert!(parse_dev_api_enabled(Some(&value))); + } + + #[test] + fn parses_dev_api_enabled_string() { + let value = json!("true"); + assert!(parse_dev_api_enabled(Some(&value))); + } + + #[test] + fn parses_dev_api_enabled_null() { + let value = json!(null); + assert!(parse_dev_api_enabled(Some(&value))); + } + + #[test] + fn parses_dev_api_enabled_missing() { + assert!(!parse_dev_api_enabled(None)); + } +} + +pub async fn set_enabled(app_handle: &AppHandle, enabled: bool) -> Result { + let state = app_handle.state::(); + { + let mut flag = state.enabled.lock().unwrap(); + *flag = enabled; + } + + if enabled { + if let Err(err) = ensure_running(app_handle).await { + let mut flag = state.enabled.lock().unwrap(); + *flag = false; + let _ = persist_dev_api_enabled(app_handle, false); + return Err(err); + } + } else { + stop_running(app_handle).await?; + } + + persist_dev_api_enabled(app_handle, enabled)?; + + Ok(state.status()) +} + +pub async fn ensure_running(app_handle: &AppHandle) -> Result<(), String> { + let state = app_handle.state::(); + { + if state.running.lock().unwrap().is_some() { + return Ok(()); + } + } + + let desired_port = *state.desired_port.lock().unwrap(); + let running = start_server(app_handle.clone(), desired_port).await?; + let mut guard = state.running.lock().unwrap(); + *guard = Some(running); + Ok(()) +} + +pub async fn stop_running(app_handle: &AppHandle) -> Result<(), String> { + let state = app_handle.state::(); + let running = { + let mut guard = state.running.lock().unwrap(); + guard.take() + }; + + if let Some(server) = running { + let _ = server.shutdown.send(()); + let _ = server.handle.await; + } + + Ok(()) +} + +async fn start_server(app_handle: AppHandle, desired_port: u16) -> Result { + let (listener, port) = bind_with_fallback(desired_port).await?; + let (shutdown_tx, shutdown_rx) = oneshot::channel(); + + let context = DevApiContext { app_handle }; + let router = Router::new() + .route( + "/absurd-data/:procedure", + post(trpc_post_handler).get(trpc_get_handler), + ) + .with_state(context) + .layer(CorsLayer::permissive()); + + let handle = tokio::spawn(async move { + let server = axum::serve(listener, router).with_graceful_shutdown(async move { + let _ = shutdown_rx.await; + }); + if let Err(err) = server.await { + log::error!("dev api server failed: {}", err); + } + }); + + Ok(RunningDevApi { + port, + shutdown: shutdown_tx, + handle, + }) +} + +async fn bind_with_fallback(start_port: u16) -> Result<(tokio::net::TcpListener, u16), String> { + for offset in 0..DEV_API_PORT_ATTEMPTS { + let port = start_port.saturating_add(offset); + match tokio::net::TcpListener::bind(("127.0.0.1", port)).await { + Ok(listener) => return Ok((listener, port)), + Err(_) => continue, + } + } + + Err("Failed to bind dev API server port after 10 attempts".to_string()) +} + +async fn trpc_post_handler( + Path(procedure): Path, + State(context): State, + Json(payload): Json, +) -> Json { + let id = payload.id.unwrap_or(Value::Null); + let input = payload.json.or(payload.input).unwrap_or(Value::Null); + execute_trpc(&context, &procedure, id, input).await +} + +async fn trpc_get_handler( + Path(procedure): Path, + State(context): State, + Query(query): Query, +) -> Json { + let input = query + .input + .or(query.json) + .and_then(|value| serde_json::from_str::(&value).ok()) + .unwrap_or(Value::Null); + execute_trpc(&context, &procedure, Value::Null, input).await +} + +async fn execute_trpc( + context: &DevApiContext, + procedure: &str, + id: Value, + input: Value, +) -> Json { + let app_handle = context.app_handle.clone(); + let procedure_name = procedure.to_string(); + + let result = + tokio::task::spawn_blocking(move || handle_procedure(&app_handle, &procedure_name, input)) + .await + .map_err(|err| err.to_string()) + .and_then(|result| result); + + let body = match result { + Ok(data) => json!({ "id": id, "result": { "data": data } }), + Err(message) => json!({ + "id": id, + "error": { + "message": message, + "code": -32603, + "data": { "code": "INTERNAL_SERVER_ERROR", "httpStatus": 500 } + } + }), + }; + + Json(body) +} + +fn handle_procedure( + app_handle: &AppHandle, + procedure: &str, + input: Value, +) -> Result { + match procedure { + "health" => Ok(json!({ "ok": true })), + "getOverviewMetrics" => with_provider(app_handle, |provider| { + let metrics = provider.get_overview_metrics()?; + Ok(serde_json::to_value(metrics)?) + }), + "getQueueMetrics" => with_provider(app_handle, |provider| { + let metrics = provider.get_queue_metrics()?; + Ok(serde_json::to_value(metrics)?) + }), + "getTaskRuns" => with_provider(app_handle, |provider| { + let runs = provider.get_task_runs()?; + Ok(serde_json::to_value(runs)?) + }), + "getTaskRunsForQueue" => { + let payload: QueueNameInput = parse_input(input)?; + with_provider(app_handle, |provider| { + let runs = provider.get_task_runs_for_queue(&payload.queue_name)?; + Ok(serde_json::to_value(runs)?) + }) + } + "getTaskRunsPage" => { + let payload: TaskRunFilters = parse_input(input)?; + with_provider(app_handle, |provider| { + let page = provider.get_task_runs_page(payload)?; + Ok(serde_json::to_value(page)?) + }) + } + "getTaskHistory" => { + let payload: TaskIdInput = parse_input(input)?; + with_provider(app_handle, |provider| { + let runs = provider.get_task_history(&payload.task_id)?; + Ok(serde_json::to_value(runs)?) + }) + } + "getTaskInfo" => { + let payload: TaskIdInput = parse_input(input)?; + with_provider(app_handle, |provider| { + let info = provider.get_task_info(&payload.task_id)?; + Ok(serde_json::to_value(info)?) + }) + } + "getQueueNames" => with_provider(app_handle, |provider| { + let names = provider.get_queue_names()?; + Ok(serde_json::to_value(names)?) + }), + "getQueueSummaries" => with_provider(app_handle, |provider| { + let summaries = provider.get_queue_summaries()?; + Ok(serde_json::to_value(summaries)?) + }), + "getTaskNameOptions" => { + let payload: OptionalQueueNameInput = parse_optional_input(input)?; + with_provider(app_handle, |provider| { + let names = provider.get_task_name_options(payload.queue_name.as_deref())?; + Ok(serde_json::to_value(names)?) + }) + } + "createQueue" => { + let payload: QueueNameInput = parse_input(input)?; + with_provider(app_handle, |provider| { + provider.create_queue(&payload.queue_name)?; + Ok(Value::Null) + }) + } + "getEventFilterDefaults" => { + let payload: OptionalQueueNameInput = parse_optional_input(input)?; + with_provider(app_handle, |provider| { + let defaults = provider.get_event_filter_defaults(payload.queue_name.as_deref())?; + Ok(serde_json::to_value(defaults)?) + }) + } + "getEvents" => with_provider(app_handle, |provider| { + let events = provider.get_events()?; + Ok(serde_json::to_value(events)?) + }), + "getFilteredEvents" => { + let payload: EventFilters = parse_input(input)?; + with_provider(app_handle, |provider| { + let events = provider.get_filtered_events(Some(payload))?; + Ok(serde_json::to_value(events)?) + }) + } + "getSettingsInfo" => { + let db_handle = app_handle.state::(); + let db_path = db_handle + .db_path(app_handle) + .map_err(|err| err.to_string())?; + with_provider(app_handle, |provider| { + let info = provider.get_settings_info(db_path)?; + Ok(serde_json::to_value(info)?) + }) + } + "getMigrations" => with_provider(app_handle, |provider| { + let migrations = provider.get_migrations()?; + Ok(serde_json::to_value(migrations)?) + }), + "applyMigrationsAll" => with_provider(app_handle, |provider| { + let applied = provider.apply_migrations_all()?; + Ok(serde_json::to_value(applied)?) + }), + "applyMigration" => { + let payload: MigrationInput = parse_input(input)?; + with_provider(app_handle, |provider| { + let applied = provider.apply_migration(payload.migration_id)?; + Ok(serde_json::to_value(applied)?) + }) + } + _ => Err(format!("Unknown procedure: {}", procedure)), + } +} + +fn with_provider( + app_handle: &AppHandle, + f: impl FnOnce(&TauriDataProvider) -> anyhow::Result, +) -> Result { + let db_handle = app_handle.state::(); + let conn = db_handle + .connect(app_handle) + .map_err(|err| err.to_string())?; + let provider = TauriDataProvider::new(&conn); + f(&provider).map_err(|err| err.to_string()) +} + +fn parse_input Deserialize<'de>>(value: Value) -> Result { + serde_json::from_value(value).map_err(|err| err.to_string()) +} + +fn parse_optional_input Deserialize<'de> + Default>(value: Value) -> Result { + if value.is_null() { + return Ok(T::default()); + } + serde_json::from_value(value).map_err(|err| err.to_string()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct QueueNameInput { + queue_name: String, +} + +#[derive(Default, Deserialize)] +#[serde(rename_all = "camelCase")] +struct OptionalQueueNameInput { + queue_name: Option, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct TaskIdInput { + task_id: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct MigrationInput { + migration_id: i64, +} diff --git a/standalone/src-tauri/src/lib.rs b/standalone/src-tauri/src/lib.rs new file mode 100644 index 0000000..b6d936c --- /dev/null +++ b/standalone/src-tauri/src/lib.rs @@ -0,0 +1,120 @@ +use tauri::menu::{MenuBuilder, MenuItemBuilder, PredefinedMenuItem, SubmenuBuilder}; +use tauri::{async_runtime, Manager}; +use tauri_plugin_cli::CliExt; + +use crate::dev_api::{load_dev_api_enabled, DevApiState}; +use crate::{db::DatabaseHandle, worker::spawn_worker}; + +mod db; +mod db_commands; +mod dev_api; +mod worker; + +const DEVTOOLS_MENU_ID: &str = "open_devtools"; + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + tauri::Builder::default() + .plugin(tauri_plugin_fs::init()) + .plugin(tauri_plugin_cli::init()) + .plugin( + tauri_plugin_log::Builder::new() + .level(tauri_plugin_log::log::LevelFilter::Info) + .target(tauri_plugin_log::Target::new( + tauri_plugin_log::TargetKind::Stderr, + )) + .build(), + ) + .plugin(tauri_plugin_shell::init()) + .plugin(tauri_plugin_opener::init()) + .plugin(tauri_plugin_store::Builder::default().build()) + .invoke_handler(tauri::generate_handler![ + db_commands::get_overview_metrics, + db_commands::get_queue_metrics, + db_commands::get_task_runs, + db_commands::get_task_runs_for_queue, + db_commands::get_task_runs_page, + db_commands::get_task_history, + db_commands::get_task_info, + db_commands::get_queue_names, + db_commands::get_queue_summaries, + db_commands::create_queue, + db_commands::get_task_name_options, + db_commands::get_event_filter_defaults, + db_commands::get_events, + db_commands::get_filtered_events, + db_commands::get_settings_info, + db_commands::get_migrations, + db_commands::apply_migrations_all, + db_commands::apply_migration, + dev_api::get_dev_api_status, + dev_api::set_dev_api_enabled + ]) + .on_menu_event(|app, event| { + // DevTools is only available in debug builds + #[cfg(debug_assertions)] + if event.id() == DEVTOOLS_MENU_ID { + if let Some(window) = app.get_webview_window("main") { + window.open_devtools(); + } + } + }) + .setup(move |app| { + let app_handle = app.handle().clone(); + + let enable_dev_api = load_dev_api_enabled(&app_handle).unwrap_or(false); + let mut db_handle = None; + + if let Ok(matches) = app.cli().matches() { + db_handle = Some(DatabaseHandle::from_cli_arg( + &app_handle, + matches.args.get("db"), + )?); + } + + let db_handle = match db_handle { + Some(handle) => handle, + None => DatabaseHandle::use_app_data(&app_handle)?, + }; + + app_handle.manage(db_handle); + app_handle.manage(DevApiState::new(enable_dev_api, None)); + if enable_dev_api { + let app_handle = app_handle.clone(); + async_runtime::spawn(async move { + if let Err(err) = dev_api::ensure_running(&app_handle).await { + log::error!("Failed to start dev api server: {}", err); + } + }); + } + + #[cfg(not(any(target_os = "android", target_os = "ios")))] + { + let devtools = MenuItemBuilder::with_id(DEVTOOLS_MENU_ID, "Open DevTools") + .accelerator("CmdOrCtrl+Alt+I") + .build(&app_handle)?; + let edit_menu = SubmenuBuilder::new(&app_handle, "Edit") + .item(&PredefinedMenuItem::cut(&app_handle, None)?) + .item(&PredefinedMenuItem::copy(&app_handle, None)?) + .item(&PredefinedMenuItem::paste(&app_handle, None)?) + .item(&PredefinedMenuItem::select_all(&app_handle, None)?) + .build()?; + let developer_menu = SubmenuBuilder::new(&app_handle, "Developer") + .item(&devtools) + .build()?; + let menu = MenuBuilder::new(&app_handle) + .item(&edit_menu) + .item(&developer_menu) + .build()?; + app.set_menu(menu)?; + } + + async_runtime::spawn(async move { spawn_worker(&app_handle).await }); + + log::info!("setup"); + + Ok(()) + }) + .run(tauri::generate_context!()) + .expect("create tauri app failed"); +} diff --git a/standalone/src-tauri/src/main.rs b/standalone/src-tauri/src/main.rs new file mode 100644 index 0000000..d9e1e8c --- /dev/null +++ b/standalone/src-tauri/src/main.rs @@ -0,0 +1,6 @@ +// Prevents additional console window on Windows in release, DO NOT REMOVE!! +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + standalone_lib::run() +} diff --git a/standalone/src-tauri/src/worker.rs b/standalone/src-tauri/src/worker.rs new file mode 100644 index 0000000..4e35564 --- /dev/null +++ b/standalone/src-tauri/src/worker.rs @@ -0,0 +1,33 @@ +use anyhow::Result; +use tauri::AppHandle; +use tauri_plugin_shell::{process::CommandEvent, ShellExt}; + +pub async fn spawn_worker(handle: &AppHandle) -> Result<()> { + let shell = handle.shell(); + + log::info!("Spawning worker....."); + + let (mut rx, _child) = shell + .command("node") + .args(["./worker.js"]) + .spawn() + .expect("failed to spawn worker"); + + while let Some(event) = rx.recv().await { + match event { + CommandEvent::Terminated(code) => { + log::info!("Worker terminated with code: {:?}", code); + break; + } + CommandEvent::Stderr(line) => { + log::info!("Worker stderr: {}", String::from_utf8(line).unwrap()); + } + CommandEvent::Stdout(line) => { + log::info!("Worker stdout: {}", String::from_utf8(line).unwrap()); + } + _ => {} + } + } + + Ok(()) +} diff --git a/standalone/src-tauri/tauri.conf.json b/standalone/src-tauri/tauri.conf.json new file mode 100644 index 0000000..a6fea9a --- /dev/null +++ b/standalone/src-tauri/tauri.conf.json @@ -0,0 +1,51 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "Absurd SQLite", + "version": "0.1.0", + "identifier": "ing.isbuild.absurd-sqlite-standalone", + "build": { + "beforeDevCommand": "bun run dev", + "devUrl": "http://localhost:1420", + "beforeBuildCommand": "bun run build:tauri", + "frontendDist": "../build" + }, + "app": { + "windows": [ + { + "title": "Absurd SQLite", + "width": 1075, + "height": 800 + } + ], + "security": { + "csp": null + }, + "withGlobalTauri": true + }, + "bundle": { + "active": true, + "targets": "all", + "externalBin": [ + "bin/absurd-extension" + ], + "icon": [ + "icons/logo-32x32.png", + "icons/logo-128x128.png", + "icons/logo-512x512.png", + "icons/logo-1024x1024.png", + "icons/icon.icns", + "icons/icon.ico" + ] + }, + "plugins": { + "cli": { + "description": "Absurd SQLite", + "args": [ + { + "name": "db", + "description": "Path to the SQLite database file" + } + ] + } + } +} diff --git a/standalone/src/app.css b/standalone/src/app.css new file mode 100644 index 0000000..cfeece5 --- /dev/null +++ b/standalone/src/app.css @@ -0,0 +1,21 @@ +@import "tailwindcss"; + +:root { + color-scheme: light; + font-family: "Segoe UI", system-ui, sans-serif; + background-color: #f6f6f6; +} + +body { + min-height: 100vh; + color: #111827; + background-color: #f6f6f6; +} + +html { + overscroll-behavior: none; +} + +* { + box-sizing: border-box; +} diff --git a/standalone/src/app.html b/standalone/src/app.html new file mode 100644 index 0000000..00167ac --- /dev/null +++ b/standalone/src/app.html @@ -0,0 +1,13 @@ + + + + + + + Absurd Sqlite + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/standalone/src/lib/components/Button.svelte b/standalone/src/lib/components/Button.svelte new file mode 100644 index 0000000..e3c0c63 --- /dev/null +++ b/standalone/src/lib/components/Button.svelte @@ -0,0 +1,22 @@ + + + diff --git a/standalone/src/lib/components/JsonBlock.svelte b/standalone/src/lib/components/JsonBlock.svelte new file mode 100644 index 0000000..b63a44b --- /dev/null +++ b/standalone/src/lib/components/JsonBlock.svelte @@ -0,0 +1,58 @@ + + +
+
+ {title} + +
+ {#if formattedValue} +
+{formattedValue}
+    
+ {:else} +
{emptyText}
+ {/if} +
diff --git a/standalone/src/lib/components/SelectField.svelte b/standalone/src/lib/components/SelectField.svelte new file mode 100644 index 0000000..c9267fa --- /dev/null +++ b/standalone/src/lib/components/SelectField.svelte @@ -0,0 +1,25 @@ + + + diff --git a/standalone/src/lib/providers/absurdData.ts b/standalone/src/lib/providers/absurdData.ts new file mode 100644 index 0000000..fd55ef0 --- /dev/null +++ b/standalone/src/lib/providers/absurdData.ts @@ -0,0 +1,630 @@ +import { createTRPCProxyClient, httpLink } from "@trpc/client"; + +export type OverviewMetrics = { + activeQueues: number; + messagesProcessed: number; + messagesInQueue: number; + visibleNow: number; +}; + +export type QueueMetric = { + name: string; + inQueue: number; + visible: number; + newestAge: string; + oldestAge: string; + totalSeen: number; + scrapedAt: string; +}; + +export type TaskRun = { + id: string; + name: string; + queue: string; + status: "running" | "failed" | "completed" | "sleeping" | "pending" | "cancelled"; + attempt: string; + attemptNumber: number; + runId: string; + age: string; + startedAt: string; + updatedAt: string; + createdAgo: string; + updatedAgo: string; + paramsSummary: string; + paramsJson: string; + finalStateJson?: string; + worker: string; +}; + +export type TaskInfo = { + id: string; + name: string; + queue: string; +}; + +export type TaskRunFilters = { + queueName?: string; + status?: TaskRun["status"]; + taskName?: string; + search?: string; + limit?: number; +}; + +export type TaskRunPage = { + runs: TaskRun[]; + totalCount: number; +}; + +export type QueueSummary = { + name: string; + createdAt: string; + age: string; + stats: { + label: string; + value: number; + }[]; +}; + +export type EventFilterDefaults = { + eventNamePlaceholder: string; + queueLabel: string; + queueOptions: string[]; +}; + +export type EventEntry = { + id: string; + name: string; + queue: string; + createdAt: string; + payloadPreview: string; +}; + +export type MigrationStatus = { + status: "applied" | "missing"; + appliedCount: number; + latestVersion: string | null; + latestAppliedAt: string | null; +}; + +export type SettingsInfo = { + absurdVersion: string; + sqliteVersion: string; + dbPath: string; + migration: MigrationStatus; +}; + +export type MigrationEntry = { + id: number; + introducedVersion: string; + appliedAt: string | null; + status: "applied" | "pending"; +}; + +export type AbsurdDataProvider = { + getOverviewMetrics: () => Promise; + getQueueMetrics: () => Promise; + getTaskRuns: () => Promise; + getTaskRunsForQueue: (queueName: string) => Promise; + getTaskRunsPage: (filters: TaskRunFilters) => Promise; + getTaskHistory: (taskId: string) => Promise; + getTaskInfo: (taskId: string) => Promise; + getQueueNames: () => Promise; + getTaskNameOptions: (queueName?: string) => Promise; + getQueueSummaries: () => Promise; + createQueue: (queueName: string) => Promise; + getEventFilterDefaults: (queueName?: string) => Promise; + getEvents: () => Promise; + getFilteredEvents: (filters: { queueName?: string; eventName?: string }) => Promise; + getSettingsInfo: () => Promise; + getMigrations: () => Promise; + applyMigrationsAll: () => Promise; + applyMigration: (migrationId: number) => Promise; +}; + +export const isTauriRuntime = () => + typeof window !== "undefined" && Boolean((window as { __TAURI__?: unknown }).__TAURI__); + +const tauriInvoke = async (command: string, args?: Record): Promise => { + const { invoke } = await import("@tauri-apps/api/core"); + return invoke(command, args); +}; + +export const tauriAbsurdProvider: AbsurdDataProvider = { + getOverviewMetrics: () => tauriInvoke("get_overview_metrics"), + getQueueMetrics: () => tauriInvoke("get_queue_metrics"), + getTaskRuns: () => tauriInvoke("get_task_runs"), + getTaskRunsForQueue: (queueName) => + tauriInvoke("get_task_runs_for_queue", { queue_name: queueName }), + getTaskRunsPage: (filters) => tauriInvoke("get_task_runs_page", { filters }), + getTaskHistory: (taskId) => tauriInvoke("get_task_history", { taskId }), + getTaskInfo: (taskId) => tauriInvoke("get_task_info", { taskId }), + getQueueNames: () => tauriInvoke("get_queue_names"), + getTaskNameOptions: (queueName) => + tauriInvoke("get_task_name_options", { queue_name: queueName ?? null }), + getQueueSummaries: () => tauriInvoke("get_queue_summaries"), + createQueue: (queueName) => tauriInvoke("create_queue", { queueName }), + getEventFilterDefaults: (queueName) => + tauriInvoke("get_event_filter_defaults", queueName ? { queue_name: queueName } : undefined), + getEvents: () => tauriInvoke("get_events"), + getFilteredEvents: (filters) => tauriInvoke("get_filtered_events", { filters }), + getSettingsInfo: () => tauriInvoke("get_settings_info"), + getMigrations: () => tauriInvoke("get_migrations"), + applyMigrationsAll: () => tauriInvoke("apply_migrations_all"), + applyMigration: (migrationId) => + tauriInvoke("apply_migration", { migration_id: migrationId }), +}; + +const mockMigrations: MigrationEntry[] = [ + { + id: 1, + introducedVersion: "0.1.0", + appliedAt: "Dec 27, 2025, 2:48 PM", + status: "applied", + }, + { + id: 2, + introducedVersion: "0.2.0", + appliedAt: null, + status: "pending", + }, +]; + +const DEV_API_PORT_BASE = 11223; +const DEV_API_PORT_ATTEMPTS = 10; +const DEV_API_REQUEST_TIMEOUT_MS = 400; +const DEV_API_RETRY_DELAY_MS = 2000; + +let devApiBaseUrl: string | null | undefined = undefined; +let devApiLastFailureMs: number | null = null; +let devApiResolveInFlight: Promise | null = null; +let trpcClient: ReturnType> | null = null; +let trpcClientBaseUrl: string | null = null; + +const resolveDevApiBaseUrl = async (): Promise => { + if (devApiBaseUrl === null && devApiLastFailureMs !== null) { + if (Date.now() - devApiLastFailureMs < DEV_API_RETRY_DELAY_MS) { + return null; + } + devApiBaseUrl = undefined; + } + + if (devApiBaseUrl !== undefined) { + return devApiBaseUrl; + } + + if (devApiResolveInFlight) { + return devApiResolveInFlight; + } + + devApiResolveInFlight = (async () => { + for (let attempt = 0; attempt < DEV_API_PORT_ATTEMPTS; attempt += 1) { + const port = DEV_API_PORT_BASE + attempt; + const baseUrl = `http://localhost:${port}`; + const ok = await probeDevApi(baseUrl); + if (ok) { + devApiBaseUrl = baseUrl; + devApiLastFailureMs = null; + return baseUrl; + } + } + devApiBaseUrl = null; + devApiLastFailureMs = Date.now(); + return null; + })(); + + const resolved = await devApiResolveInFlight; + devApiResolveInFlight = null; + return resolved; +}; + +const probeDevApi = async (baseUrl: string): Promise => { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), DEV_API_REQUEST_TIMEOUT_MS); + try { + const response = await fetch(`${baseUrl}/absurd-data/health`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ id: 1, json: null }), + signal: controller.signal, + }); + if (!response.ok) { + return false; + } + const payload = (await response.json()) as { + result?: { data?: { ok?: boolean } }; + }; + return Boolean(payload.result?.data?.ok); + } catch { + return false; + } finally { + clearTimeout(timeout); + } +}; + +const getTrpcClient = async () => { + const baseUrl = await resolveDevApiBaseUrl(); + if (!baseUrl) { + return null; + } + + if (!trpcClient || trpcClientBaseUrl !== baseUrl) { + trpcClientBaseUrl = baseUrl; + trpcClient = createTRPCProxyClient({ + links: [ + httpLink({ + url: `${baseUrl}/absurd-data`, + }), + ], + }); + } + + return trpcClient; +}; + +const trpcQuery = async (procedure: string, input?: unknown): Promise => { + const client = await getTrpcClient(); + if (!client) { + throw new Error("Dev API server not available"); + } + const caller = ( + client as unknown as Record Promise }> + )[procedure]; + if (!caller?.query) { + throw new Error(`Unknown tRPC query ${procedure}`); + } + return caller.query(input); +}; + +const trpcMutation = async (procedure: string, input?: unknown): Promise => { + const client = await getTrpcClient(); + if (!client) { + throw new Error("Dev API server not available"); + } + const caller = ( + client as unknown as Record Promise }> + )[procedure]; + if (!caller?.mutate) { + throw new Error(`Unknown tRPC mutation ${procedure}`); + } + return caller.mutate(input); +}; + +const trpcAbsurdProvider: AbsurdDataProvider = { + getOverviewMetrics: () => trpcQuery("getOverviewMetrics"), + getQueueMetrics: () => trpcQuery("getQueueMetrics"), + getTaskRuns: () => trpcQuery("getTaskRuns"), + getTaskRunsForQueue: (queueName) => + trpcQuery("getTaskRunsForQueue", { queueName }), + getTaskRunsPage: (filters) => trpcQuery("getTaskRunsPage", filters), + getTaskHistory: (taskId) => trpcQuery("getTaskHistory", { taskId }), + getTaskInfo: (taskId) => trpcQuery("getTaskInfo", { taskId }), + getQueueNames: () => trpcQuery("getQueueNames"), + getTaskNameOptions: (queueName) => + trpcQuery("getTaskNameOptions", queueName ? { queueName } : null), + getQueueSummaries: () => trpcQuery("getQueueSummaries"), + createQueue: (queueName) => trpcMutation("createQueue", { queueName }), + getEventFilterDefaults: (queueName) => + trpcQuery("getEventFilterDefaults", queueName ? { queueName } : null), + getEvents: () => trpcQuery("getEvents"), + getFilteredEvents: (filters) => trpcQuery("getFilteredEvents", filters), + getSettingsInfo: () => trpcQuery("getSettingsInfo"), + getMigrations: () => trpcQuery("getMigrations"), + applyMigrationsAll: () => trpcMutation("applyMigrationsAll"), + applyMigration: (migrationId) => + trpcMutation("applyMigration", { migrationId }), +}; + +export const getAbsurdProvider = (): AbsurdDataProvider => + isTauriRuntime() ? tauriAbsurdProvider : trpcAbsurdProvider; + +export const mockAbsurdProvider: AbsurdDataProvider = { + getQueueNames: async () => + (await mockAbsurdProvider.getQueueSummaries()).map((queue) => queue.name), + getTaskNameOptions: async (queueName?: string) => { + const runs = await mockAbsurdProvider.getTaskRuns(); + const filtered = queueName && queueName !== "All queues" + ? runs.filter((run) => run.queue === queueName) + : runs; + return [...new Set(filtered.map((run) => run.name))].sort(); + }, + createQueue: async () => {}, + getOverviewMetrics: async () => ({ + activeQueues: 1, + messagesProcessed: 0, + messagesInQueue: 0, + visibleNow: 0, + }), + getQueueMetrics: async () => [ + { + name: "test", + inQueue: 0, + visible: 0, + newestAge: "—", + oldestAge: "—", + totalSeen: 0, + scrapedAt: "Dec 27, 2025, 2:48:45 PM", + }, + ], + getTaskRuns: async () => [ + { + id: "019b470c-a9e6-70c7-aba8-d57f79368ba2", + name: "test", + queue: "default", + status: "running", + attempt: "5 / ∞", + attemptNumber: 5, + runId: "019b4714-040d-76bd-aa15-e3d372e9f53f", + age: "5d", + startedAt: "Dec 22, 2025, 9:13 AM", + updatedAt: "Dec 27, 2025, 3:03 PM", + createdAgo: "5d ago", + updatedAgo: "5d ago", + paramsSummary: "{ \"tenant\": \"absurd\", \"retries\": 4 }", + paramsJson: "{\n \"tenant\": \"absurd\",\n \"retries\": 4\n}", + worker: "mordor.local:89695", + }, + { + id: "019b470c-a9e6-70c7-aba8-d57f79368ba2", + name: "test", + queue: "default", + status: "failed", + attempt: "4 / ∞", + attemptNumber: 4, + runId: "019b4712-2d47-77c6-b3a0-bd49d125c35b", + age: "5d", + startedAt: "Dec 22, 2025, 9:03 AM", + updatedAt: "Dec 22, 2025, 9:05 AM", + createdAgo: "5d ago", + updatedAgo: "5d ago", + paramsSummary: "{ \"tenant\": \"absurd\", \"retries\": 3 }", + paramsJson: "{\n \"tenant\": \"absurd\",\n \"retries\": 3\n}", + finalStateJson: + "{\n \"name\": \"SqsClaimTimeout\",\n \"message\": \"Worker did not finish task within claim interval\",\n \"worker\": \"mordor.local:18695\",\n \"claimExpiredAt\": \"2025-12-22T09:05:01.006Z\"\n}", + worker: "mordor.local:89695", + }, + { + id: "019b470c-a9e6-70c7-aba8-d57f79368ba2", + name: "test", + queue: "default", + status: "failed", + attempt: "3 / ∞", + attemptNumber: 3, + runId: "019b4710-568e-73a7-aaee-e2bed95c4936", + age: "5d", + startedAt: "Dec 22, 2025, 9:01 AM", + updatedAt: "Dec 22, 2025, 9:03 AM", + createdAgo: "5d ago", + updatedAgo: "5d ago", + paramsSummary: "{ \"tenant\": \"absurd\", \"retries\": 2 }", + paramsJson: "{\n \"tenant\": \"absurd\",\n \"retries\": 2\n}", + finalStateJson: + "{\n \"name\": \"SqsClaimTimeout\",\n \"message\": \"Worker did not finish task within claim interval\",\n \"worker\": \"mordor.local:18695\",\n \"claimExpiredAt\": \"2025-12-22T09:03:01.006Z\"\n}", + worker: "mordor.local:89695", + }, + { + id: "019b470c-a9e6-70c7-aba8-d57f79368ba2", + name: "test", + queue: "default", + status: "failed", + attempt: "2 / ∞", + attemptNumber: 2, + runId: "019b470e-8014-7aa3-83de-9c3cf715a103", + age: "5d", + startedAt: "Dec 22, 2025, 8:59 AM", + updatedAt: "Dec 22, 2025, 9:01 AM", + createdAgo: "5d ago", + updatedAgo: "5d ago", + paramsSummary: "{ \"tenant\": \"absurd\", \"retries\": 1 }", + paramsJson: "{\n \"tenant\": \"absurd\",\n \"retries\": 1\n}", + finalStateJson: + "{\n \"name\": \"SqsClaimTimeout\",\n \"message\": \"Worker did not finish task within claim interval\",\n \"worker\": \"mordor.local:18695\",\n \"claimExpiredAt\": \"2025-12-22T09:01:01.006Z\"\n}", + worker: "mordor.local:89695", + }, + { + id: "019b470c-a9e6-70c7-aba8-d57f79368ba2", + name: "test", + queue: "default", + status: "failed", + attempt: "1 / ∞", + attemptNumber: 1, + runId: "019b470c-a9e9-7cf1-aea5-73182e8773eb", + age: "5d", + startedAt: "Dec 22, 2025, 8:57 AM", + updatedAt: "Dec 22, 2025, 8:59 AM", + createdAgo: "5d ago", + updatedAgo: "5d ago", + paramsSummary: "{ \"tenant\": \"absurd\", \"retries\": 0 }", + paramsJson: "{\n \"tenant\": \"absurd\",\n \"retries\": 0\n}", + finalStateJson: + "{\n \"name\": \"SqsClaimTimeout\",\n \"message\": \"Worker did not finish task within claim interval\",\n \"worker\": \"mordor.local:18695\",\n \"claimExpiredAt\": \"2025-12-22T08:59:01.006Z\"\n}", + worker: "mordor.local:89695", + }, + { + id: "019b470c-ad55-7763-b622-6505a1b74c9b", + name: "test2", + queue: "default", + status: "completed", + attempt: "1 / ∞", + attemptNumber: 1, + runId: "019b470c-ad58-72e6-935d-6855f1c0e568", + age: "5d", + startedAt: "Dec 22, 2025, 8:20 AM", + updatedAt: "Dec 22, 2025, 8:21 AM", + createdAgo: "5d ago", + updatedAgo: "5d ago", + paramsSummary: "{ \"source\": \"nightly\" }", + paramsJson: "{\n \"source\": \"nightly\"\n}", + worker: "mordor.local:77102", + }, + { + id: "019b470c-246b-73f0-876d-f76a9818ac5a", + name: "test", + queue: "default", + status: "sleeping", + attempt: "1 / ∞", + attemptNumber: 1, + runId: "019b470c-246e-7f6e-a245-f6e38bad94f1", + age: "5d", + startedAt: "Dec 22, 2025, 7:10 AM", + updatedAt: "Dec 22, 2025, 7:12 AM", + createdAgo: "5d ago", + updatedAgo: "5d ago", + paramsSummary: "{ \"stage\": \"backoff\" }", + paramsJson: "{\n \"stage\": \"backoff\"\n}", + worker: "mordor.local:99210", + }, + ], + getTaskRunsForQueue: async (queueName: string) => + queueName === "All queues" + ? mockAbsurdProvider.getTaskRuns() + : (await mockAbsurdProvider.getTaskRuns()).filter((run) => run.queue === queueName), + getTaskRunsPage: async (filters) => { + const runs = await mockAbsurdProvider.getTaskRuns(); + const normalizedQueue = (filters.queueName ?? "All queues").toLowerCase(); + const status = (filters.status ?? "").toLowerCase(); + const taskName = (filters.taskName ?? "").trim(); + const search = (filters.search ?? "").trim().toLowerCase(); + const limit = filters.limit ?? 500; + const filtered = runs.filter((run) => { + if (normalizedQueue !== "all queues" && run.queue.toLowerCase() !== normalizedQueue) { + return false; + } + if (status && run.status.toLowerCase() !== status) { + return false; + } + if (taskName && run.name !== taskName) { + return false; + } + if (search) { + const haystack = [ + run.id, + run.runId, + run.name, + run.queue, + run.status, + run.paramsSummary, + run.paramsJson, + run.finalStateJson ?? "", + run.worker, + ] + .join(" ") + .toLowerCase(); + if (!haystack.includes(search)) { + return false; + } + } + return true; + }); + const limited = limit > 0 ? filtered.slice(0, limit) : filtered; + return { runs: limited, totalCount: filtered.length }; + }, + getTaskHistory: async (taskId: string) => + (await mockAbsurdProvider.getTaskRuns()).filter((run) => run.id === taskId), + getTaskInfo: async (taskId: string) => { + const run = (await mockAbsurdProvider.getTaskRuns()).find((item) => item.id === taskId); + if (!run) { + return null; + } + return { id: run.id, name: run.name, queue: run.queue }; + }, + getQueueSummaries: async () => [ + { + name: "default", + createdAt: "Created Dec 19, 2025, 5:25 PM", + age: "8d ago", + stats: [ + { label: "Pending", value: 0 }, + { label: "Running", value: 1 }, + { label: "Sleeping", value: 1 }, + { label: "Completed", value: 3 }, + { label: "Failed", value: 0 }, + { label: "Cancelled", value: 0 }, + ], + }, + { + name: "test", + createdAt: "Created Dec 19, 2025, 5:20 PM", + age: "8d ago", + stats: [ + { label: "Pending", value: 0 }, + { label: "Running", value: 0 }, + { label: "Sleeping", value: 0 }, + { label: "Completed", value: 0 }, + { label: "Failed", value: 0 }, + { label: "Cancelled", value: 0 }, + ], + }, + ], + getEventFilterDefaults: async (queueName?: string) => ({ + eventNamePlaceholder: "payment.completed", + queueLabel: queueName ?? "All queues", + queueOptions: ["All queues", ...(await mockAbsurdProvider.getQueueNames())], + }), + getEvents: async () => [ + { + id: "evt_019b4714", + name: "task.started", + queue: "default", + createdAt: "Dec 27, 2025, 2:48:45 PM", + payloadPreview: "{ \"taskId\": \"019b470c-a9e6\", \"runId\": \"019b4714\" }", + }, + { + id: "evt_019b4712", + name: "task.failed", + queue: "default", + createdAt: "Dec 27, 2025, 2:46:12 PM", + payloadPreview: "{ \"taskId\": \"019b470c-a9e6\", \"attempt\": 4 }", + }, + { + id: "evt_019b470e", + name: "task.failed", + queue: "test", + createdAt: "Dec 27, 2025, 2:42:05 PM", + payloadPreview: "{ \"taskId\": \"019b470c-246b\", \"attempt\": 1 }", + }, + ], + getFilteredEvents: async ({ queueName, eventName }) => { + const normalizedQueue = (queueName ?? "All queues").toLowerCase(); + const normalizedEventName = (eventName ?? "").trim().toLowerCase(); + const events = await mockAbsurdProvider.getEvents(); + return events.filter((event) => { + const queueMatch = + normalizedQueue === "all queues" || event.queue.toLowerCase() === normalizedQueue; + const nameMatch = + normalizedEventName.length === 0 || event.name.toLowerCase().includes(normalizedEventName); + return queueMatch && nameMatch; + }); + }, + getSettingsInfo: async () => ({ + absurdVersion: "absurd-sqlite/0.0.0", + sqliteVersion: "3.45.0", + dbPath: "/Users/demo/Library/Application Support/absurd-sqlite.db", + migration: { + status: "applied", + appliedCount: 1, + latestVersion: "0.1.0", + latestAppliedAt: "Dec 27, 2025, 2:48 PM", + }, + }), + getMigrations: async () => mockMigrations.map((entry) => ({ ...entry })), + applyMigrationsAll: async () => { + let applied = 0; + for (const entry of mockMigrations) { + if (entry.status === "pending") { + entry.status = "applied"; + entry.appliedAt = "Dec 27, 2025, 3:15 PM"; + applied += 1; + } + } + return applied; + }, + applyMigration: async (migrationId: number) => { + const entry = mockMigrations.find((row) => row.id === migrationId); + if (!entry || entry.status === "applied") { + return 0; + } + entry.status = "applied"; + entry.appliedAt = "Dec 27, 2025, 3:15 PM"; + return 1; + }, +}; diff --git a/standalone/src/routes/+layout.svelte b/standalone/src/routes/+layout.svelte new file mode 100644 index 0000000..ee9fa75 --- /dev/null +++ b/standalone/src/routes/+layout.svelte @@ -0,0 +1,83 @@ + + +
+
+
+ + +
+
+ +
+ {@render children?.()} +
+
+ + diff --git a/standalone/src/routes/+layout.ts b/standalone/src/routes/+layout.ts new file mode 100644 index 0000000..a3d1578 --- /dev/null +++ b/standalone/src/routes/+layout.ts @@ -0,0 +1 @@ +export const ssr = false; diff --git a/standalone/src/routes/+page.svelte b/standalone/src/routes/+page.svelte new file mode 100644 index 0000000..fa39d9c --- /dev/null +++ b/standalone/src/routes/+page.svelte @@ -0,0 +1,132 @@ + + +
+
+

Overview

+

+ Current queue health across your Absurd installation. +

+
+ +
+ +
+ {#each statCards as card} +
+

{card.label}

+

{card.value}

+

{card.description}

+
+ {/each} +
+ +
+
+

Queue metrics

+

+ Visibility into backlog depth and message freshness. +

+
+ +
+ + + + + + + + + + + + + + + {#each queueMetrics as metric} + + + + + + + + + + + {/each} + +
QueueIn QueueVisibleNewest ageOldest ageTotal seenScrapedActions
{metric.name}{metric.inQueue}{metric.visible}{metric.newestAge}{metric.oldestAge}{metric.totalSeen}{metric.scrapedAt} + +
+
+
diff --git a/standalone/src/routes/events/+page.svelte b/standalone/src/routes/events/+page.svelte new file mode 100644 index 0000000..a3219d2 --- /dev/null +++ b/standalone/src/routes/events/+page.svelte @@ -0,0 +1,180 @@ + + +
+
+

Event log

+

+ Inspect emitted events across queues with filterable payloads. +

+
+ +
+ +
+
+
+

Filters

+

Narrow the event list by queue and event name.

+
+ +
+ + + + {#each filters.queueOptions as option} + + {/each} + +
+
+ +
+
+

Event timeline

+

+ Showing the most recent events matching your filters. +

+
+ + {#if isLoading} +
+ + Loading events... +
+ {:else if filteredEvents.length === 0} +
+

No events matched the selected filters.

+
+ {:else} +
+ {#each filteredEvents as event} +
+
+
+ {event.name} + + {event.queue} +
+ {event.createdAt} +
+
+{event.payloadPreview}
+            
+
+ {/each} +
+ {/if} +
+
diff --git a/standalone/src/routes/queues/+page.svelte b/standalone/src/routes/queues/+page.svelte new file mode 100644 index 0000000..2f8aaae --- /dev/null +++ b/standalone/src/routes/queues/+page.svelte @@ -0,0 +1,168 @@ + + +
+
+

Queues

+

+ Inspect queue health, backlog contents, and recent events. +

+
+
+ + +
+
+ +{#if isCreateOpen} + +{/if} + +
+ {#each queueSummaries as queue} +
+
+
+
+

{queue.name}

+ {queue.age} +
+

{queue.createdAt}

+
+ +
+ +
+ {#each queue.stats as stat} +
+

{stat.label}

+

{stat.value}

+
+ {/each} +
+
+ {/each} +
diff --git a/standalone/src/routes/settings/+page.svelte b/standalone/src/routes/settings/+page.svelte new file mode 100644 index 0000000..bed6e27 --- /dev/null +++ b/standalone/src/routes/settings/+page.svelte @@ -0,0 +1,278 @@ + + +
+
+

Settings

+

+ Version details, storage location, and migration health. +

+
+ +
+ +
+
+

Version

+

Runtime and extension versions.

+
+
+
Absurd
+
{data.absurdVersion}
+
+
+
SQLite
+
{data.sqliteVersion}
+
+
+
+ +
+

Database

+

Primary storage location for task data.

+
+
+
+
File path
+ +
+
+ {data.dbPath} +
+
+
+
+ +
+
+
+

Migrations

+

+ Schema upgrade status and migration history. +

+
+
+ +
+
+
+ + + + + + + + + + + + {#if migrations.length === 0} + + + + {:else} + {#each migrations as migration} + + + + + + + + {/each} + {/if} + +
IDIntroducedStatusApplied atAction
+ No migration metadata available yet. +
{migration.id} + {migration.introducedVersion} + + + {migration.status === "applied" ? "Applied" : "Pending"} + + + {migration.appliedAt ?? "—"} + + {#if migration.status === "pending"} + + {/if} +
+
+
+ + {#if showDevApi} +
+
+
+

Developer

+
+
+
+
+ + + Dev API server: + {#if devApiStatus?.running && devApiStatus?.port} + + {`http://localhost:${devApiStatus.port}`} + + {:else} + Disabled + {/if} + +
+ +
+
+ {/if} +
diff --git a/standalone/src/routes/tasks/+page.svelte b/standalone/src/routes/tasks/+page.svelte new file mode 100644 index 0000000..b110401 --- /dev/null +++ b/standalone/src/routes/tasks/+page.svelte @@ -0,0 +1,395 @@ + + +
+
+

Tasks

+

+ Monitor and manage durable tasks across all queues. +

+
+
+ + +
+
+ +
+
+

Task Runs

+

+ Each row represents a single run. Click a run to view details or open the full task history. +

+
+ +
+ + + + {#each queueOptions as queue} + + {/each} + + + {#each statusOptions as option} + + {/each} + + + {#each taskNameOptions as option} + + {/each} + +
+ +
+ + {#if totalCount === 0} + Showing 0 tasks + {:else} + Showing 1–{taskRuns.length} of {totalCount} tasks + {/if} + + {#if isLoading} + + + Loading task runs... + + {/if} +
+ +
+ + + + + + + + + + + + + + + {#each taskRuns as run} + toggleExpanded(run.runId)} + > + + + + + + + + + + {#if expandedId === run.runId} + + + + {/if} + {/each} + +
Task IDTask NameQueueStatusAttemptRun IDAge
{run.id}{run.name}{run.queue} + + {run.status} + + {run.attempt}{run.runId}{run.age}{expandedId === run.runId ? "▲" : "▼"}
+
+
+

Basic Information

+ + View task history + +
+
+
+
Current status:
+
+ + {currentStatusByTaskId[run.id]?.status ?? run.status} + +
+
+
+
Task Name:
+
+ + {run.name} + +
+
+
+
Queue:
+
{run.queue}
+
+
+
Task ID:
+
+ + {run.id} + +
+
+
+
Run ID:
+
+ {run.runId} +
+
+
+
Worker:
+
{run.worker}
+
+
+
+ +
+ +
+ +
+ +
+
+
+
+ {#if hasMore} + + {/if} +
+
diff --git a/standalone/src/routes/tasks/[taskId]/+page.svelte b/standalone/src/routes/tasks/[taskId]/+page.svelte new file mode 100644 index 0000000..e562d77 --- /dev/null +++ b/standalone/src/routes/tasks/[taskId]/+page.svelte @@ -0,0 +1,159 @@ + + +
+
+

Task "{taskName}"

+

+ Task ID: + + {taskId} + +

+

+ Queues: {queueName} +

+
+
+ ← Back to runs + +
+
+ +
+
+
+ Runs + {runs.length} +
+
+ Statuses + {#each sortedRuns as run} + + {run.status} + + {/each} +
+
+ Duration + {durationLabel} +
+
+ Completion + {completionLabel} +
+
+ Queue + {queueName} +
+
+ Checkpoints + 0 +
+
+ Updated + {latestUpdatedAgo} +
+
+
+ +
+ {#if sortedRuns.length === 0} +
+ No history found for this task. +
+ {:else} + {#each sortedRuns as run} +
+
+ Run ID: + + {run.runId} + + + {run.status} + +
+
+
Queue: {run.queue}
+
Attempt: {run.attemptNumber}
+
Created: {run.createdAgo}
+
Updated: {run.updatedAgo}
+
+ +
+ +
+ +
+ +
+
+ {/each} + {/if} +
diff --git a/standalone/static/favicon.png b/standalone/static/favicon.png new file mode 100644 index 0000000..b74bf5f Binary files /dev/null and b/standalone/static/favicon.png differ diff --git a/standalone/svelte.config.js b/standalone/svelte.config.js new file mode 100644 index 0000000..a7830ea --- /dev/null +++ b/standalone/svelte.config.js @@ -0,0 +1,18 @@ +// Tauri doesn't have a Node.js server to do proper SSR +// so we use adapter-static with a fallback to index.html to put the site in SPA mode +// See: https://svelte.dev/docs/kit/single-page-apps +// See: https://v2.tauri.app/start/frontend/sveltekit/ for more info +import adapter from "@sveltejs/adapter-static"; +import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + preprocess: vitePreprocess(), + kit: { + adapter: adapter({ + fallback: "index.html", + }), + }, +}; + +export default config; diff --git a/standalone/tsconfig.json b/standalone/tsconfig.json new file mode 100644 index 0000000..f4d0a0e --- /dev/null +++ b/standalone/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + } + // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias + // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files + // + // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes + // from the referenced tsconfig.json - TypeScript does not merge them in +} diff --git a/standalone/vite.config.js b/standalone/vite.config.js new file mode 100644 index 0000000..ba6f28e --- /dev/null +++ b/standalone/vite.config.js @@ -0,0 +1,33 @@ +import { defineConfig } from "vite"; +import { sveltekit } from "@sveltejs/kit/vite"; +import tailwindcss from "@tailwindcss/vite"; + +// @ts-expect-error process is a nodejs global +const host = process.env.TAURI_DEV_HOST; + +// https://vite.dev/config/ +export default defineConfig(async () => ({ + plugins: [tailwindcss(), sveltekit()], + + // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` + // + // 1. prevent Vite from obscuring rust errors + clearScreen: false, + // 2. tauri expects a fixed port, fail if that port is not available + server: { + port: 1420, + strictPort: true, + host: host || false, + hmr: host + ? { + protocol: "ws", + host, + port: 1421, + } + : undefined, + watch: { + // 3. tell Vite to ignore watching `src-tauri` + ignored: ["**/src-tauri/**"], + }, + }, +}));