From 06b1611723fec92c8b02beb4b6680875c72089e2 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 16 Aug 2026 11:09:00 +0000 Subject: [PATCH 01/16] Add a local-first Python SDK wrapping atomic_lib via PyO3. Python talks to the same Rust store as the WASM and Flutter bindings: redb on disk, signed Loro commits, no HTTP server required. v1 covers setup, create/read/update/query/delete, and reopen-after-flush. Co-authored-by: joepmeindertsma --- .gitignore | 8 + AGENTS.md | 2 + Cargo.toml | 2 +- README.md | 3 +- TESTING_COVERAGE.md | 7 + docs/src/SUMMARY.md | 1 + docs/src/atomic-server.md | 2 +- docs/src/python.md | 62 + docs/src/tooling.md | 1 + planning/README.md | 1 + planning/SDK-API-design.md | 1 + planning/python-sdk.md | 50 + python/Cargo.lock | 3214 ++++++++++++++++++++++++++++++ python/Cargo.toml | 17 + python/README.md | 94 + python/pyproject.toml | 42 + python/src/convert.rs | 150 ++ python/src/lib.rs | 64 + python/src/resource.rs | 123 ++ python/src/store.rs | 337 ++++ python/src/urls.rs | 51 + python/tests/test_persistence.py | 55 + python/tests/test_sdk.py | 150 ++ 23 files changed, 4434 insertions(+), 3 deletions(-) create mode 100644 docs/src/python.md create mode 100644 planning/python-sdk.md create mode 100644 python/Cargo.lock create mode 100644 python/Cargo.toml create mode 100644 python/README.md create mode 100644 python/pyproject.toml create mode 100644 python/src/convert.rs create mode 100644 python/src/lib.rs create mode 100644 python/src/resource.rs create mode 100644 python/src/store.rs create mode 100644 python/src/urls.rs create mode 100644 python/tests/test_persistence.py create mode 100644 python/tests/test_sdk.py diff --git a/.gitignore b/.gitignore index 51f6e7e153..123b1c172d 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,11 @@ scratchpad node_modules/ # Generated integration certification evidence /artifacts/integration-certification/ + +# Python SDK (excluded from the Cargo workspace, so it has its own target/) +python/target +python/.venv +python/*.egg-info +**/__pycache__ +**/.pytest_cache +*.so diff --git a/AGENTS.md b/AGENTS.md index 9b331c520e..6365de7c84 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -107,6 +107,7 @@ Atomic Server is a graph database with real-time sync, built on **Loro CRDT** fo - **`@tomic/react`** (`browser/react/`) — React hooks. - **`data-browser`** (`browser/data-browser/`) — The web app (React + TipTap + Loro), feels similar to notion. See the related AGENTS.md - **`flutter/`** — Cross-platform canvas app (Android/iOS/Web). Uses `flutter_rust_bridge` to call `atomic_lib`. See `flutter/README.md` and `flutter/AGENTS.md`. +- **`python/`** — Python SDK (`atomic_data`). PyO3 bindings over `atomic_lib` (local redb). Excluded from the Cargo workspace; build with `maturin`. See `python/README.md` and `planning/python-sdk.md`. ### Data model @@ -281,6 +282,7 @@ cd browser/lib && pnpm test # JS unit tests cd browser && pnpm run -r build # Full workspace build cd browser && pnpm run test-e2e:light # Playwright @smoke (feature-branch CI) cd browser && pnpm run test-e2e # Full Playwright suite (develop / tags) +cd python && maturin develop && pytest # Python SDK (excluded from workspace) ``` `atomic_lib`'s unit tests need the `db` feature — `hierarchy.rs`'s test module diff --git a/Cargo.toml b/Cargo.toml index f6bdca3f7a..f0e24107b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ members = [ "plugin-runtime", "tools/cargo-bin", ] -exclude = ["flutter/rust", "integrations/localthought/syncables"] +exclude = ["flutter/rust", "integrations/localthought/syncables", "python"] # Debuginfo dominates target/ size: with ~1460 deps (tauri, actix, iroh) the # default `debug = true` produces a multi-GB tree per build flavor, and this diff --git a/README.md b/README.md index de86ffaa5e..2f9b9f039c 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ This repo also includes: - [`atomic_lib`](lib/README.md) Rust library. - [`atomic-cli`](cli/README.md) terminal client. - [`flutter`](/flutter) a Dart / Flutter client for Atomic Data, plus AtomicCanvas, a collaborative infinite drawing canvas that syncs peer-to-peer between devices. +- [`python`](/python) local-first Python SDK (`atomic_data`) wrapping `atomic_lib` via PyO3. - [`docs`](docs/README.md) documentation / specification for Atomic Data ([docs.atomicdata.dev](https://docs.atomicdata.dev)). _Status: alpha. [Breaking changes](CHANGELOG.md) are expected until 1.0._ @@ -48,7 +49,7 @@ _Status: alpha. [Breaking changes](CHANGELOG.md) are expected until 1.0._ - 📲 **Invite and sharing system** with [Atomic Invites](https://docs.atomicdata.dev/invitations.html) - 🌐 **Embedded server** with support for HTTP / HTTPS / HTTP2.0 (TLS) and Built-in LetsEncrypt handshake. - 📱 **Runs on mobile**: `atomic_lib` compiles into Flutter apps through [flutter_rust_bridge](https://github.com/fzyzcjy/flutter_rust_bridge), so phones get the same local-first store, signing and peer sync as the browser, not a thin REST wrapper. See [`/flutter`](/flutter). -- 📚 **Libraries**: [Javascript / Typescript](https://www.npmjs.com/package/@tomic/lib), [React](https://www.npmjs.com/package/@tomic/react), [Svelte](https://www.npmjs.com/package/@tomic/svelte), [Rust](https://crates.io/crates/atomic-lib), and a [Dart / Flutter client](/flutter/lib/atomic) +- 📚 **Libraries**: [Javascript / Typescript](https://www.npmjs.com/package/@tomic/lib), [React](https://www.npmjs.com/package/@tomic/react), [Svelte](https://www.npmjs.com/package/@tomic/svelte), [Rust](https://crates.io/crates/atomic-lib), a [Dart / Flutter client](/flutter/lib/atomic), and a [Python SDK](/python) (`atomic_data`) https://user-images.githubusercontent.com/2183313/139728539-d69b899f-6f9b-44cb-a1b7-bbab68beac0c.mp4 diff --git a/TESTING_COVERAGE.md b/TESTING_COVERAGE.md index 6cda0d8c70..94fdbc6573 100644 --- a/TESTING_COVERAGE.md +++ b/TESTING_COVERAGE.md @@ -449,6 +449,7 @@ templates, and offline variants stay in the full suite. Policy: | Browser e2e full | `cd browser && pnpm run test-e2e` | `endToEnd` on `develop` and `v*` tags | | Flutter Dart | `cd flutter && flutter test` | `flutterTest` | | Flutter Rust bridge | `cargo test --manifest-path flutter/rust/Cargo.toml` | `flutterTest` | +| Python SDK | `cd python && maturin develop && pytest` | **not in CI** | CI runs `cargo nextest run --workspace --exclude atomic-server-tauri --no-default-features --features light`. Feature unification pulls in @@ -459,6 +460,8 @@ Two things worth knowing about the runners: - **`flutter/rust` is excluded from the workspace** (root `Cargo.toml`), so `--workspace` never compiles it. It is covered only by the explicit `--manifest-path` step in `flutterTest`. +- **`python/` is excluded from the workspace** for the same reason (PyO3). + Tests are `pytest` after `maturin develop`; they are not in Dagger CI yet. - **`.config/nextest.toml` sets `retries = 2`.** A flaky test passes CI silently. Check for `FLAKY` in nextest output, not just the summary line. @@ -644,6 +647,10 @@ nothing to test yet. Listed so it is not mistaken for covered. One 13-line smoke test, never run in CI — the pipeline has no emulator. +### 7b. Python SDK (`python/`) + +Glue only: `pytest` after `maturin develop` covers in-memory create/read/update/query/delete and file-backed reopen. Not in Dagger CI. No sync, blobs, or HTTP. + ### 8. Known residual races None outstanding. The concurrent-writer bug that lived here — a local edit diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index b7d1ab86ea..a3777e11dd 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -47,6 +47,7 @@ - [Rust](rust.md) - [CLI](rust-cli.md) - [Lib](rust-lib.md) + - [Python](python.md) # Guides diff --git a/docs/src/atomic-server.md b/docs/src/atomic-server.md index 954f399767..930fc1cfc6 100644 --- a/docs/src/atomic-server.md +++ b/docs/src/atomic-server.md @@ -30,7 +30,7 @@ It's free, open source (MIT license), and has a ton of features: - 📲 **Invite and sharing system** with [Atomic Invites](https://docs.atomicdata.dev/invitations.html) - 🌐 **Embedded server** with support for HTTP / HTTPS / HTTP2.0 (TLS) and Built-in LetsEncrypt handshake. - 📱 **Runs on mobile**: `atomic_lib` compiles into Flutter apps through [flutter_rust_bridge](https://github.com/fzyzcjy/flutter_rust_bridge), so phones get the same local-first store, signing and peer sync as the browser — not a thin REST wrapper. -- 📚 **Libraries**: [Javascript / Typescript](https://www.npmjs.com/package/@tomic/lib), [React](https://www.npmjs.com/package/@tomic/react), [Svelte](https://www.npmjs.com/package/@tomic/svelte), [Rust](https://crates.io/crates/atomic-lib), and a Dart / Flutter client +- 📚 **Libraries**: [Javascript / Typescript](https://www.npmjs.com/package/@tomic/lib), [React](https://www.npmjs.com/package/@tomic/react), [Svelte](https://www.npmjs.com/package/@tomic/svelte), [Rust](https://crates.io/crates/atomic-lib), a Dart / Flutter client, and a [Python SDK](python.md) ## Document undo and redo diff --git a/docs/src/python.md b/docs/src/python.md new file mode 100644 index 0000000000..f4bb699d1e --- /dev/null +++ b/docs/src/python.md @@ -0,0 +1,62 @@ +{{#title Python SDK for Atomic Data}} + +# Python SDK + +Local-first [Atomic Data](atomic-data-overview.md) for Python. The package +wraps [`atomic_lib`](rust-lib.md) through +[PyO3](https://pyo3.rs) — the same Rust store the browser (WASM) and Flutter +app use. Reads and writes go to a local [redb](https://github.com/cberner/redb) +file. Edits are signed Loro commits. A server is optional. + +Source: [`python/`](https://github.com/atomicdata-dev/atomic-server/tree/develop/python). + +## Install + +From a checkout of this repo (needs a Rust toolchain and Python 3.9+): + +```bash +pip install maturin +cd python +maturin develop +``` + +## Quick start + +```python +from atomic_data import Store, urls + +store = Store.open("./my-atomic-data") +setup = store.setup("Ada") + +note = store.create( + urls.PLAIN_TEXT, + name="Hello", + description="A locally stored note", +) +note["description"] = "Edited offline" +note.save() + +got = store.get(note.subject) +print(got["name"], got["description"]) + +for child in store.query(parent=setup.drive_subject): + print(child.subject, child.name) + +store.flush() +``` + +`setup.agent_secret` is the only way to sign writes after you reopen the +store. Keep it. + +```python +store = Store.open("./my-atomic-data") +store.load_agent(secret) +``` + +`Store.in_memory()` is the same API without a directory. + +## What this is not (yet) + +v1 is local read, write, query, and persist. It does not open a WebSocket or +an Iroh session. Those can use the same store later, as they already do in +Flutter. diff --git a/docs/src/tooling.md b/docs/src/tooling.md index a628cde39e..74aceeb323 100644 --- a/docs/src/tooling.md +++ b/docs/src/tooling.md @@ -9,6 +9,7 @@ Libraries and clients (all MIT licenced) that work great with [atomic-server](at - Svelte library: [@tomic/svelte](svelte.md) - Client CLI (rust): [atomic-cli](rust-cli.md) for fetching & editing data - Rust library: [atomic-lib](rust-lib.md) powers `atomic-server` and `atomic-cli`, and can be used in other Rust projects ([docs.rs](https://docs.rs/atomic_lib/latest/atomic_lib/)) +- Python SDK: [atomic-data](python.md) wraps `atomic_lib` via PyO3 (local redb store, no server required) - [Raycast Extension](https://www.raycast.com/atomicdata-dev/atomic-data-browser): full-text search ## Want to add to this list? Some ideas for tooling diff --git a/planning/README.md b/planning/README.md index efc0ad27c4..a6b196b4fa 100644 --- a/planning/README.md +++ b/planning/README.md @@ -171,6 +171,7 @@ the document itself. | [`demo-experience.md`](./demo-experience.md) | v1 and v2 demo workspace. | | [`local-search.md`](./local-search.md) | KV inverted index in `atomic_lib` (redb/OPFS/sled): BM25 + prefix + 1-edit prefix-fuzzy + PropValSub filters. Hosted `/search` is the same engine; Tantivy is gone. | | [`cloud-sync-managed-node.md`](./cloud-sync-managed-node.md) | Onboarding, managed-node detection, enrollment, heartbeat and replication pull. Verified against the SaaS `LocalProcessNodeProvider` only; the sync-path admission gate and reaper are not built. | +| [`python-sdk.md`](./python-sdk.md) | Python bindings over `atomic_lib` via PyO3 (local redb, read/write/query). | | [`completed/commit-fanout-drive-isolation.md`](./completed/commit-fanout-drive-isolation.md) | Drive-scoped WS commit fan-out + server-side drive safety net. | | [`completed/cleanup-update-encoding.md`](./completed/cleanup-update-encoding.md) | Unified `decode_update`; TS client exports compact Loro deltas. | | [`completed/sign-at-drain.md`](./completed/sign-at-drain.md) | One signed commit per dirty subject per drain pass. | diff --git a/planning/SDK-API-design.md b/planning/SDK-API-design.md index 968a134e1b..bab3ea8f0a 100644 --- a/planning/SDK-API-design.md +++ b/planning/SDK-API-design.md @@ -22,6 +22,7 @@ In the "old" HTTP based Atomic(Server) UX, an app developer had to: - Server & connection to it is optional, only required for back-up. - Flutter library for native iOS and android apps +- Python SDK (`python/`, import `atomic_data`) wrapping `atomic_lib` via PyO3 — local read/write/query/persist. See [`python-sdk.md`](./python-sdk.md). - Still no full, end-to-end atomic app building tutorial available ## Future situation diff --git a/planning/python-sdk.md b/planning/python-sdk.md new file mode 100644 index 0000000000..8a21420cd0 --- /dev/null +++ b/planning/python-sdk.md @@ -0,0 +1,50 @@ +# Python SDK + +## Status + +v1 implemented: local read / write / query / persist. Package lives in +`python/`, import `atomic_data`. + +## Decision + +Wrap `atomic_lib` with **PyO3 + maturin**. Do not reimplement commits, Loro, +or Ed25519 in Python. Do not ship an HTTP-only client as the SDK. + +This matches the existing bindings: + +- Browser: `wasm-bindgen` → `ClientDb` over redb/OPFS +- Flutter: `flutter_rust_bridge` → `Db` on disk +- Python: PyO3 → `Db` on disk + +UniFFI would help if one IDL had to generate Swift + Kotlin + Python. The +Python API can be idiomatic on its own (dict-like `Resource`, sync methods, +context manager). Flutter already has its own bridge. + +The crate is **excluded from the Cargo workspace** (same reason as +`flutter/rust`): workspace `clippy` / `nextest` should not compile PyO3 on +every Rust CI run. + +## v1 surface + +- `Store.open(path)` / `Store.in_memory()` +- `setup`, `load_agent`, `create_agent`, `create_drive` +- `create`, `get`, `query`, `delete`, `flush` +- `Resource` dict access, `save`, `destroy`, `to_dict`, `to_json` +- `atomic_data.urls` constants + +Python calls are synchronous. Each one `block_on`s a process-wide tokio +runtime and drops the GIL for the Rust work. + +## Not in v1 + +- WebSocket / Iroh sync (Flutter already has the pattern) +- Blobs +- History / time-travel +- Code-first schema (see `json-schema-code-first.md`) +- PyPI publish / Dagger CI job + +## Tests + +```bash +cd python && maturin develop && pytest +``` diff --git a/python/Cargo.lock b/python/Cargo.lock new file mode 100644 index 0000000000..d914da8bd6 --- /dev/null +++ b/python/Cargo.lock @@ -0,0 +1,3214 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "append-only-bytes" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac436601d6bdde674a0d7fb593e829ffe7b3387c351b356dd20e2d40f5bf3ee5" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures 0.2.17", + "password-hash", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "arref" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ccd462b64c3c72f1be8305905a85d85403d768e8690c9b8bd3b9009a5761679" + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + +[[package]] +name = "atomic-python" +version = "0.1.0" +dependencies = [ + "atomic_lib", + "pyo3", + "serde_json", + "tokio", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atomic_lib" +version = "0.41.0-beta.2" +dependencies = [ + "argon2", + "async-trait", + "base64", + "blake3", + "chacha20poly1305", + "ed25519-dalek", + "futures", + "hex", + "loro", + "rand 0.8.7", + "redb", + "regex", + "reqwest", + "rmp-serde", + "serde", + "serde_jcs", + "serde_json", + "sha2", + "tokio", + "tracing", + "ulid", + "url", + "urlencoding", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "aws-lc-rs" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2b2dcc879c3bae0d371e77c99f2238400ef24ec001394befa67b6e543add9e" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09fae7be8bb3174e05c6afdb34199e6dc0c7c04ba9fa237b1967adfbde27483" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "blake3" +version = "1.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ae7bad254120e9e4c63bafc385310756f90c484eac0e36b8317cf09cb92a77" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures 0.3.0", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cc" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20 0.9.1", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.20", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.6.4", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "either" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "ensure-cov" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33753185802e107b8fa907192af1f0eca13b1fb33327a59266d650fef29b2b4e" + +[[package]] +name = "enum-as-inner" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-msvc-tools" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generator" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3b854b0e584ead1a33f18b2fcad7cf7be18b3875c78816b753639aa501513ae" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link", + "windows-result", +] + +[[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 = "generic-btree" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c1bce85c110ab718fd139e0cc89c51b63bd647b14a767e24bdfc77c83df79b" +dependencies = [ + "arref", + "heapless 0.9.3", + "itertools 0.11.0", + "loro-thunderdome", + "proc-macro2", + "rustc-hash", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[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 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasm-bindgen", +] + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32 0.2.1", + "rustc_version", + "serde", + "spin", + "stable_deref_trait", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32 0.3.1", + "stable_deref_trait", +] + +[[package]] +name = "heapless" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ba4bd83f9415b58b4ed8dc5714c76e626a105be4646c02630ad730ad3b5aa4" +dependencies = [ + "hash32 0.3.1", + "stable_deref_trait", +] + +[[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 = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +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 = "hyper" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92a7ed671a6aad807a8651a2e1782a6598fda9ce5185dd8158549e95a91c6428" +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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "im" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" +dependencies = [ + "bitmaps", + "rand_core 0.6.4", + "rand_xoshiro", + "serde", + "sized-chunks", + "typenum", + "version_check", +] + +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipnet" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror 2.0.20", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83bff1d572d6b9aeef67ddfc8448e4a3737909cb28e81f97c791b9018703e52" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[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.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "loro" +version = "1.13.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ea00f70c1acad4b7a05aaf72c1d9a671426021a1999ee821c77b56f0a15900" +dependencies = [ + "enum-as-inner 0.6.1", + "generic-btree", + "loro-common", + "loro-delta", + "loro-internal", + "loro-kv-store", + "rustc-hash", + "tracing", +] + +[[package]] +name = "loro-common" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b51042936156a1d537df8a38938ec8ed24b45de14530af933c1d02de39c9e056" +dependencies = [ + "arbitrary", + "enum-as-inner 0.6.1", + "leb128", + "loro-rle", + "nonmax", + "rustc-hash", + "serde", + "serde_columnar", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "loro-delta" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96f381e7fb5d4bf5605b4f4a9241f3adf61403ded978d757552004c8302cccbb" +dependencies = [ + "arrayvec", + "enum-as-inner 0.5.1", + "generic-btree", + "heapless 0.8.0", +] + +[[package]] +name = "loro-internal" +version = "1.13.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fec47acf8db115b7146da5e1d5b18d0cf58032e6012c0c1ee3ec3442d1e936f" +dependencies = [ + "append-only-bytes", + "arref", + "bytes", + "either", + "ensure-cov", + "enum-as-inner 0.6.1", + "enum_dispatch", + "generic-btree", + "getrandom 0.2.17", + "im", + "itertools 0.12.1", + "leb128", + "loom", + "loro-common", + "loro-delta", + "loro-kv-store", + "loro-rle", + "loro_fractional_index", + "md5", + "nonmax", + "num", + "num-traits", + "once_cell", + "parking_lot", + "pest", + "pest_derive", + "postcard", + "pretty_assertions", + "rand 0.8.7", + "rustc-hash", + "serde", + "serde_columnar", + "serde_json", + "smallvec", + "thiserror 1.0.69", + "thread_local", + "tracing", + "wasm-bindgen", + "xxhash-rust", +] + +[[package]] +name = "loro-kv-store" +version = "1.13.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "937f88e4a3d80d5853f5119753f5e5f68d4375726db7e9b3d6dac9db8167e8bf" +dependencies = [ + "bytes", + "ensure-cov", + "loro-common", + "lz4_flex", + "once_cell", + "quick_cache", + "rustc-hash", + "tracing", + "xxhash-rust", +] + +[[package]] +name = "loro-rle" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76400c3eea6bb39b013406acce964a8db39311534e308286c8d8721baba8ee20" +dependencies = [ + "append-only-bytes", + "num", + "smallvec", +] + +[[package]] +name = "loro-thunderdome" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3d053a135388e6b1df14e8af1212af5064746e9b87a06a345a7a779ee9695a" + +[[package]] +name = "loro_fractional_index" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aca7180674d0273ddf37049a5efcde4547fd5330d24abb7519bb9d9eb6780d5b" +dependencies = [ + "once_cell", + "rand 0.8.7", + "serde", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lz4_flex" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373f5eceeeab7925e0c1098212f2fbc4d416adec9d35051a6ab251e824c1854a" +dependencies = [ + "twox-hash", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mio" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "nonmax" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[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", +] + +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pest" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a07a60cc7a4d00c91f95c685609d1d2f79050e6804b70ebedd7650f0b839bcf" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a83744a5c8455b8b3e0dc5031362780a347c878bdd11584d1a8984228cc88d" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0cd3451aa3de60d4b9a1e736885e4dea6b31617598026f12256ad566d63304a" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "pest_meta" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e04d3a0849e241d7dfce834c83b1c5edc8622009e8dd51a12ba1927c32f05496" +dependencies = [ + "pest", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "heapless 0.7.17", + "serde", +] + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pyo3" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8970a78afe0628a3e3430376fc5fd76b6b45c4d43360ffd6cdd40bdde72b682a" +dependencies = [ + "indoc", + "libc", + "memoffset", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "458eb0c55e7ece017adeba38f2248ff3ac615e53660d7c71a238d7d2a01c7598" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7114fe5457c61b276ab77c5055f206295b812608083644a5c5b2640c3102565c" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8725c0a622b374d6cb051d11a0983786448f7785336139c3c94f5aa6bef7e50" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4109984c22491085343c05b0dbc54ddc405c3cf7b4374fc533f5c3313a572ccc" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "quick_cache" +version = "0.6.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9c6658afe513a3b484e3abfdaa0d03ef3c0bbf017542c178dd55f94eb3051f9" +dependencies = [ + "ahash", + "equivalent", + "hashbrown", + "parking_lot", +] + +[[package]] +name = "quinn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.20", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand 0.10.2", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.20", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20 0.10.1", + "getrandom 0.4.3", + "rand_core 0.10.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.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "redb" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e925444704b5f17d32bf42f5b6e2df050bceebc3dcd6e71cc73dafe8092e839" +dependencies = [ + "libc", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "reqwest" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +dependencies = [ + "base64", + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rmp" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "rmp-serde" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" +dependencies = [ + "rmp", + "serde", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustls" +version = "0.23.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +dependencies = [ + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0527518605e68109d875e248ea259b6758801cf165e4b2c2733ae3b51f12535a" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ryu-js" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6518fc26bced4d53678a22d6e423e9d8716377def84545fe328236e3af070e7f" + +[[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 = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_columnar" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a16e404f17b16d0273460350e29b02d76ba0d70f34afdc9a4fa034c97d6c6eb" +dependencies = [ + "itertools 0.11.0", + "postcard", + "serde", + "serde_columnar_derive", + "thiserror 1.0.69", +] + +[[package]] +name = "serde_columnar_derive" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45958fce4903f67e871fbf15ac78e289269b21ebd357d6fecacdba233629112e" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_jcs" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3a60f3fda61525e439ef6d67422118f11e986566997d9021c56867ad814a0aa" +dependencies = [ + "ryu-js", + "serde", + "serde_json", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "sized-chunks" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +dependencies = [ + "bitmaps", + "typenum", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[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.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +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.119", +] + +[[package]] +name = "target-lexicon" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" + +[[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.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl 2.0.20", +] + +[[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.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +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.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[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 = [ + "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.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "twox-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8464ec13c3691491391d9fce00f6416c9a48e46972f72d7865688be2080192c9" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "ulid" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "470dbf6591da1b39d43c14523b2b469c86879a53e8b758c8e090a470fe7b1fbe" +dependencies = [ + "rand 0.9.5", + "web-time", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unindent" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[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.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" +dependencies = [ + "rustls-pki-types", +] + +[[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 = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +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", +] + +[[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_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[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_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[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_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "xxhash-rust" +version = "0.8.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aee1b19627c7c60102ab80d3a9cbe18de90bfe03bfa6c3715447681f0e8c8af6" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b5c6b5976d66c1d703c4fd17d3f5e43c8cedaacf604961b171adc7130896d8" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47402523226a02bfe5230160dc3ccc089aa6f6f19e7fcbb4e6f824bbb1b4aa62" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/python/Cargo.toml b/python/Cargo.toml new file mode 100644 index 0000000000..48fd9b7b4d --- /dev/null +++ b/python/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "atomic-python" +version = "0.1.0" +edition = "2021" +description = "Python bindings for atomic_lib — local-first Atomic Data SDK" +license = "MIT" +publish = false + +[lib] +name = "atomic_data" +crate-type = ["cdylib"] + +[dependencies] +atomic_lib = { path = "../lib", default-features = false, features = ["db-redb"] } +pyo3 = { version = "0.25", features = ["abi3-py39"] } +serde_json = "1" +tokio = { version = "1", features = ["rt-multi-thread", "macros"] } diff --git a/python/README.md b/python/README.md new file mode 100644 index 0000000000..03cb3f53da --- /dev/null +++ b/python/README.md @@ -0,0 +1,94 @@ +# atomic-data (Python) + +Local-first [Atomic Data](https://atomicdata.dev) SDK for Python. + +This is **not** a thin HTTP client. It wraps [`atomic_lib`](../lib) — the same +Rust crate that powers AtomicServer, the browser WASM store, and the Flutter +app — through [PyO3](https://pyo3.rs). Reads and writes go to a local [redb] +database. Edits are signed Loro CRDT commits. A server is optional. + +[redb]: https://github.com/cberner/redb + +## Why PyO3 + +The other language bindings already follow this shape: + +| Language | Binding | Store | +| --- | --- | --- | +| TypeScript (browser) | `wasm-bindgen` | redb in OPFS | +| Dart / Flutter | `flutter_rust_bridge` | redb on disk | +| Python | **PyO3 + maturin** | redb on disk | + +Reimplementing commits, Ed25519, and Loro in Python would drift. UniFFI is a +better fit when one IDL must generate Swift + Kotlin + Python; here the Python +API can be idiomatic on its own, the way the Flutter bridge is. + +v1 is local read / write / query / persist. WebSocket and Iroh sync can sit on +the same store later, as they already do in Flutter. + +## Install (from this repo) + +Needs a Rust toolchain and Python 3.9+. + +```bash +pip install maturin +cd python +maturin develop +``` + +Then `import atomic_data`. + +## Quick start + +```python +from atomic_data import Store, urls + +store = Store.open("./my-atomic-data") +setup = store.setup("Ada") # agent + personal drive + +note = store.create( + urls.PLAIN_TEXT, + name="Hello", + description="A locally stored note", +) +note["description"] = "Edited offline" +note.save() + +got = store.get(note.subject) +print(got["name"], got["description"]) + +for child in store.query(parent=setup.drive_subject): + print(child.subject, child.name) + +store.flush() +``` + +Reopen later with the same path and the agent secret from `setup.agent_secret`: + +```python +store = Store.open("./my-atomic-data") +store.load_agent(secret) +page = store.get(subject) +``` + +`Store.in_memory()` is the same API without a directory — useful in tests. + +## API + +- **Store** — `open(path)`, `in_memory()`, `setup(name)`, `load_agent(secret)`, + `create(class_url, name, ...)`, `get(subject)`, `query(...)`, `delete(subject)`, + `flush()`, context manager +- **Resource** — dict-like access, `.save()`, `.destroy()`, `.to_dict()`, `.to_json()` +- **atomic_data.urls** — well-known class and property URLs + +Property keys accept full URLs or shortnames (`name`, `description`, `parent`, +`isA`, …). + +## Develop + +```bash +cd python +maturin develop +pip install pytest +pytest +``` diff --git a/python/pyproject.toml b/python/pyproject.toml new file mode 100644 index 0000000000..e80b729b3c --- /dev/null +++ b/python/pyproject.toml @@ -0,0 +1,42 @@ +[build-system] +requires = ["maturin>=1.8,<2"] +build-backend = "maturin" + +[project] +name = "atomic-data" +version = "0.1.0" +description = "Local-first Atomic Data SDK for Python. Wraps atomic_lib (Rust) via PyO3." +readme = "README.md" +requires-python = ">=3.9" +license = { text = "MIT" } +authors = [{ name = "Joep Meindertsma", email = "joep@argu.co" }] +keywords = ["atomic-data", "crdt", "local-first", "graph"] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: Database", +] + +[project.urls] +Homepage = "https://atomicdata.dev" +Documentation = "https://docs.atomicdata.dev/python.html" +Repository = "https://github.com/atomicdata-dev/atomic-server" + +[project.optional-dependencies] +dev = ["pytest>=8"] + +[tool.maturin] +features = ["pyo3/extension-module"] +module-name = "atomic_data" + +[tool.pytest.ini_options] +testpaths = ["tests"] diff --git a/python/src/convert.rs b/python/src/convert.rs new file mode 100644 index 0000000000..942f71d134 --- /dev/null +++ b/python/src/convert.rs @@ -0,0 +1,150 @@ +use atomic_lib::{urls, values::SubResource, Resource, Value}; +use pyo3::exceptions::{PyTypeError, PyValueError}; +use pyo3::prelude::*; +use pyo3::types::{PyDict, PyList}; +use pyo3::IntoPyObjectExt; + +pub(crate) fn py_err(err: impl ToString) -> PyErr { + pyo3::exceptions::PyRuntimeError::new_err(err.to_string()) +} + +/// Map a shortname or URL to a property URL. +/// +/// Full `https://` / `http://` / `did:` strings pass through. Well-known +/// core shortnames resolve to `https://atomicdata.dev/properties/{name}`. +/// Anything else is treated as that same core-property convention so +/// `resource["description"] = "..."` works without importing `urls`. +pub(crate) fn resolve_property(name: &str) -> String { + if name.starts_with("https://") || name.starts_with("http://") || name.starts_with("did:") { + return name.to_string(); + } + match name { + "name" => urls::NAME.to_string(), + "description" => urls::DESCRIPTION.to_string(), + "parent" => urls::PARENT.to_string(), + "isA" | "is_a" | "isa" => urls::IS_A.to_string(), + "shortname" => urls::SHORTNAME.to_string(), + "datatype" => urls::DATATYPE_PROP.to_string(), + "read" => urls::READ.to_string(), + "write" => urls::WRITE.to_string(), + "children" => urls::CHILDREN.to_string(), + "drives" => urls::DRIVES.to_string(), + "classType" | "classtype" => urls::CLASSTYPE_PROP.to_string(), + "requires" => urls::REQUIRES.to_string(), + "recommends" => urls::RECOMMENDS.to_string(), + other => format!("https://atomicdata.dev/properties/{other}"), + } +} + +pub(crate) fn py_to_value(obj: &Bound<'_, PyAny>) -> PyResult { + if obj.is_none() { + return Err(PyValueError::new_err( + "cannot store None as an Atomic value", + )); + } + // `bool` is a subclass of `int` in Python — check it first. + if let Ok(b) = obj.extract::() { + return Ok(Value::Boolean(b)); + } + if let Ok(i) = obj.extract::() { + return Ok(Value::Integer(i)); + } + if let Ok(f) = obj.extract::() { + return Ok(Value::Float(f)); + } + if let Ok(s) = obj.extract::() { + if s.starts_with("https://") || s.starts_with("http://") || s.starts_with("did:") { + return Ok(Value::AtomicUrl(s.into())); + } + return Ok(Value::String(s)); + } + if let Ok(list) = obj.downcast::() { + let mut subjects = Vec::with_capacity(list.len()); + let mut all_strings = true; + for item in list.iter() { + if let Ok(s) = item.extract::() { + subjects.push(SubResource::Subject(s.into())); + } else { + all_strings = false; + break; + } + } + if all_strings { + return Ok(Value::ResourceArray(subjects)); + } + return json_value(obj); + } + if obj.downcast::().is_ok() { + return json_value(obj); + } + Err(PyTypeError::new_err(format!( + "cannot convert Python {} to an Atomic value", + obj.get_type().name()? + ))) +} + +fn json_value(obj: &Bound<'_, PyAny>) -> PyResult { + let dumps = obj.py().import("json")?.getattr("dumps")?; + let encoded: String = dumps.call1((obj,))?.extract()?; + let parsed: serde_json::Value = serde_json::from_str(&encoded).map_err(py_err)?; + Ok(Value::Json(parsed)) +} + +pub(crate) fn value_to_py<'py>(py: Python<'py>, value: &Value) -> PyResult> { + match value { + Value::Boolean(b) => (*b).into_bound_py_any(py), + Value::Integer(i) => (*i).into_bound_py_any(py), + Value::Timestamp(i) => (*i).into_bound_py_any(py), + Value::Float(f) => (*f).into_bound_py_any(py), + Value::String(s) | Value::Markdown(s) | Value::Slug(s) | Value::Date(s) | Value::Uri(s) => { + s.as_str().into_bound_py_any(py) + } + Value::AtomicUrl(s) => s.to_string().into_bound_py_any(py), + Value::ResourceArray(items) => { + let list = PyList::empty(py); + for item in items { + list.append(subresource_to_py(py, item)?)?; + } + Ok(list.into_any()) + } + Value::NestedResource(item) => subresource_to_py(py, item), + Value::Json(v) => loads_json(py, &v.to_string()), + Value::LocalizedText(map) => { + let json = serde_json::to_string(map).map_err(py_err)?; + loads_json(py, &json) + } + Value::LoroDoc(bytes) => bytes.as_slice().into_bound_py_any(py), + Value::Unsupported(u) => u.value.as_str().into_bound_py_any(py), + } +} + +fn subresource_to_py<'py>(py: Python<'py>, item: &SubResource) -> PyResult> { + match item { + SubResource::Subject(s) => s.to_string().into_bound_py_any(py), + SubResource::Nested(propvals) => { + let dict = PyDict::new(py); + for (key, val) in propvals { + dict.set_item(key, value_to_py(py, val)?)?; + } + Ok(dict.into_any()) + } + } +} + +fn loads_json<'py>(py: Python<'py>, json: &str) -> PyResult> { + py.import("json")?.call_method1("loads", (json,)) +} + +/// Used when we already have a `Resource` and want a Python dict of its +/// current propvals (URL keys, native Python values). +pub(crate) fn resource_to_dict<'py>( + py: Python<'py>, + resource: &Resource, +) -> PyResult> { + let dict = PyDict::new(py); + dict.set_item("@id", resource.get_subject().to_string())?; + for (key, val) in resource.get_propvals() { + dict.set_item(key, value_to_py(py, val)?)?; + } + Ok(dict) +} diff --git a/python/src/lib.rs b/python/src/lib.rs new file mode 100644 index 0000000000..039cff09c7 --- /dev/null +++ b/python/src/lib.rs @@ -0,0 +1,64 @@ +//! Python bindings for `atomic_lib`. +//! +//! Same idea as the WASM (`wasm-bindgen`) and Flutter (`flutter_rust_bridge`) +//! bindings: Python talks to the Rust store. Loro, Ed25519 signing, and redb +//! stay in Rust. The Python API is synchronous; each call blocks on a process +//! tokio runtime. + +use std::sync::OnceLock; + +use pyo3::prelude::*; + +mod convert; +mod resource; +mod store; +mod urls; + +pub(crate) use convert::{py_err, py_to_value, resolve_property, resource_to_dict, value_to_py}; +pub(crate) use resource::Resource; +pub(crate) use store::{AgentInfo, DriveInfo, SetupInfo, Store}; + +pub(crate) fn runtime() -> &'static tokio::runtime::Runtime { + static RT: OnceLock = OnceLock::new(); + RT.get_or_init(|| { + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .thread_name("atomic-python") + .build() + .expect("failed to start tokio runtime") + }) +} + +/// Run an async `atomic_lib` call on the process runtime. +/// +/// Holds the GIL. Local redb work is short, and some return types (`Resource`) +/// are not `Send`, so `allow_threads` is not worth the bound. +pub(crate) fn block_on(fut: F) -> F::Output { + runtime().block_on(fut) +} + +/// Local-first Atomic Data SDK. +/// +/// Opens a redb-backed store (or an in-memory one), creates an agent + drive, +/// and reads / writes resources as signed Loro commits. No server required. +#[pymodule] +fn atomic_data(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + + let urls_mod = urls::urls_module(m.py())?; + m.add_submodule(&urls_mod)?; + m.py() + .import("sys")? + .getattr("modules")? + .set_item("atomic_data.urls", &urls_mod)?; + + m.add( + "__doc__", + "Local-first Atomic Data SDK. Wraps atomic_lib (Rust) via PyO3.", + )?; + Ok(()) +} diff --git a/python/src/resource.rs b/python/src/resource.rs new file mode 100644 index 0000000000..56893c53ef --- /dev/null +++ b/python/src/resource.rs @@ -0,0 +1,123 @@ +use atomic_lib::{urls, Db}; +use pyo3::exceptions::PyKeyError; +use pyo3::prelude::*; +use pyo3::types::PyDict; + +use crate::{block_on, py_err, py_to_value, resolve_property, resource_to_dict, value_to_py}; + +/// A single Atomic resource: property → value, backed by a Loro document. +/// +/// Dict-like access uses property URLs or well-known shortnames +/// (`resource["name"]`). Writes stay in memory until [`Resource.save`]. +#[pyclass(unsendable)] +pub struct Resource { + pub(crate) inner: atomic_lib::Resource, + pub(crate) db: Db, +} + +#[pymethods] +impl Resource { + #[getter] + fn subject(&self) -> String { + self.inner.get_subject().to_string() + } + + #[getter] + fn name(&self) -> Option { + self.inner.get(urls::NAME).ok().map(|v| v.to_string()) + } + + #[setter] + fn set_name(&mut self, name: &str) -> PyResult<()> { + self.inner + .set_unsafe(urls::NAME.into(), atomic_lib::Value::String(name.into())) + .map_err(py_err)?; + Ok(()) + } + + /// Set a property. `property` may be a URL or a shortname. + fn set(&mut self, property: &str, value: &Bound<'_, PyAny>) -> PyResult<()> { + let prop = resolve_property(property); + self.inner + .set_unsafe(prop, py_to_value(value)?) + .map_err(py_err)?; + Ok(()) + } + + /// Read a property, or `default` if it is missing. + #[pyo3(signature = (property, default=None))] + fn get<'py>( + &self, + py: Python<'py>, + property: &str, + default: Option<&Bound<'py, PyAny>>, + ) -> PyResult> { + let prop = resolve_property(property); + match self.inner.get(&prop) { + Ok(value) => value_to_py(py, value), + Err(_) => match default { + Some(d) => Ok(d.clone()), + None => Ok(py.None().into_bound(py)), + }, + } + } + + fn __getitem__<'py>(&self, py: Python<'py>, property: &str) -> PyResult> { + let prop = resolve_property(property); + match self.inner.get(&prop) { + Ok(value) => value_to_py(py, value), + Err(_) => Err(PyKeyError::new_err(property.to_string())), + } + } + + fn __setitem__(&mut self, property: &str, value: &Bound<'_, PyAny>) -> PyResult<()> { + self.set(property, value) + } + + fn __delitem__(&mut self, property: &str) -> PyResult<()> { + let prop = resolve_property(property); + if self.inner.get(&prop).is_err() { + return Err(PyKeyError::new_err(property.to_string())); + } + self.inner.remove_propval(&prop).map_err(py_err) + } + + fn __contains__(&self, property: &str) -> bool { + self.inner.get(&resolve_property(property)).is_ok() + } + + fn keys(&self) -> Vec { + self.inner.get_propvals().keys().cloned().collect() + } + + /// Property URL → native Python value, plus `@id`. + fn to_dict<'py>(&self, py: Python<'py>) -> PyResult> { + resource_to_dict(py, &self.inner) + } + + /// JSON-AD serialization of this resource. + fn to_json(&self) -> PyResult { + self.inner.to_json_ad(None).map_err(py_err) + } + + /// Sign and apply the pending edits to the local store. + fn save(&mut self) -> PyResult<()> { + block_on(self.inner.save_locally(&self.db)).map_err(py_err)?; + Ok(()) + } + + /// Sign a destroy commit and remove the resource from the store. + pub fn destroy(&mut self) -> PyResult<()> { + block_on(self.inner.destroy(&self.db)).map_err(py_err)?; + Ok(()) + } + + fn __repr__(&self) -> String { + let name = self + .inner + .get(urls::NAME) + .map(|v| v.to_string()) + .unwrap_or_default(); + format!("Resource(subject={:?}, name={:?})", self.subject(), name) + } +} diff --git a/python/src/store.rs b/python/src/store.rs new file mode 100644 index 0000000000..2303e390c0 --- /dev/null +++ b/python/src/store.rs @@ -0,0 +1,337 @@ +use atomic_lib::storelike::{Query, Storelike}; +use atomic_lib::{urls, Db, Subject, Value}; +use pyo3::exceptions::{PyKeyError, PyValueError}; +use pyo3::prelude::*; +use pyo3::types::PyDict; + +use crate::{block_on, py_err, py_to_value, resolve_property, Resource}; + +/// Agent + personal drive created by [`Store.setup`]. +#[pyclass] +#[derive(Clone)] +pub struct SetupInfo { + #[pyo3(get)] + pub agent_subject: String, + #[pyo3(get)] + pub agent_secret: String, + #[pyo3(get)] + pub drive_subject: String, +} + +#[pymethods] +impl SetupInfo { + fn __repr__(&self) -> String { + format!( + "SetupInfo(agent_subject={:?}, drive_subject={:?})", + self.agent_subject, self.drive_subject + ) + } +} + +/// An Atomic agent (keypair) loaded into a store. +#[pyclass] +#[derive(Clone)] +pub struct AgentInfo { + #[pyo3(get)] + pub subject: String, + #[pyo3(get)] + pub secret: String, + #[pyo3(get)] + pub public_key: String, + #[pyo3(get)] + pub name: Option, + /// True when the secret names a drive that is not in this store yet. + #[pyo3(get)] + pub drive_needs_sync: bool, +} + +#[pymethods] +impl AgentInfo { + fn __repr__(&self) -> String { + format!("AgentInfo(subject={:?})", self.subject) + } +} + +/// A drive subject and its display name. +#[pyclass] +#[derive(Clone)] +pub struct DriveInfo { + #[pyo3(get)] + pub subject: String, + #[pyo3(get)] + pub name: String, +} + +#[pymethods] +impl DriveInfo { + fn __repr__(&self) -> String { + format!( + "DriveInfo(name={:?}, subject={:?})", + self.name, self.subject + ) + } +} + +/// Local Atomic Data store. Backed by redb on disk, or in memory. +/// +/// Cheap to clone: every [`Resource`] keeps a handle so `.save()` works +/// without passing the store back in. +#[pyclass] +#[derive(Clone)] +pub struct Store { + pub(crate) db: Db, +} + +impl Store { + pub(crate) fn wrap_resource(&self, resource: atomic_lib::Resource) -> Resource { + Resource { + inner: resource, + db: self.db.clone(), + } + } + + fn subject(&self, raw: &str) -> Subject { + Subject::from_raw(raw, self.db.get_base_domain().as_deref()) + } +} + +#[pymethods] +impl Store { + /// Open a persistent store at `path`. + /// + /// Creates the directory if needed. Data lives in `path/atomic.redb`. + /// Call [`Store.flush`] (or use the store as a context manager) so recent + /// writes survive a crash — redb commits are not fsynced on every write. + #[staticmethod] + fn open(path: &str) -> PyResult { + let base = std::path::Path::new(path); + let uploads = base.join("uploads"); + let db = block_on(Db::init_redb_file(base, None, &uploads)).map_err(py_err)?; + Ok(Store { db }) + } + + /// In-memory store. Lost when the process exits. Useful in tests. + #[staticmethod] + fn in_memory() -> PyResult { + let db = block_on(Db::init_redb(None)).map_err(py_err)?; + Ok(Store { db }) + } + + /// Create an agent and a personal drive. Pure local — no network. + /// + /// Keep `agent_secret`. It is the only way to sign writes after a reopen. + fn setup(&self, name: &str) -> PyResult { + let (agent, drive_subject) = block_on(self.db.setup(name)).map_err(py_err)?; + let secret = agent.build_secret().map_err(py_err)?; + Ok(SetupInfo { + agent_subject: agent.subject.to_string(), + agent_secret: secret, + drive_subject, + }) + } + + /// Create an agent and set it as the default signer. No drive. + fn create_agent(&self, name: &str) -> PyResult { + let agent = block_on(self.db.create_agent(Some(name))).map_err(py_err)?; + self.db.set_default_agent(agent.clone()); + agent_info(agent, false) + } + + /// Restore an agent from a secret and make it the default signer. + /// + /// If the secret names a drive that is not in this store, + /// `drive_needs_sync` is true — you can still read local data, but you + /// cannot create resources under that drive until it is synced in. + fn load_agent(&self, secret: &str) -> PyResult { + let result = block_on(self.db.load_agent_from_secret(secret)).map_err(py_err)?; + agent_info(result.agent, result.drive_needs_sync) + } + + /// The currently active agent, if one has been set up or loaded. + fn agent(&self) -> PyResult> { + match self.db.get_default_agent() { + Ok(agent) => Ok(Some(agent_info(agent, false)?)), + Err(_) => Ok(None), + } + } + + /// Create a drive and make it active. Requires a default agent. + fn create_drive(&self, name: &str) -> PyResult { + block_on(self.db.create_drive(name)).map_err(py_err) + } + + /// Drives belonging to the current agent. + fn drives(&self) -> PyResult> { + let drives = block_on(self.db.list_drives()).map_err(py_err)?; + Ok(drives + .into_iter() + .map(|d| DriveInfo { + subject: d.subject, + name: d.name, + }) + .collect()) + } + + #[getter] + fn active_drive(&self) -> Option { + self.db.get_active_drive() + } + + #[setter] + fn set_active_drive(&self, subject: &str) -> PyResult<()> { + self.db.set_active_drive(subject).map_err(py_err) + } + + /// Create a resource, sign a genesis commit, persist it, and return it. + /// + /// Extra keyword arguments become properties (`description="..."`, or a + /// full property URL). `parent` defaults to the active drive. + #[pyo3(signature = (class_url, name, parent=None, **properties))] + fn create( + &self, + class_url: &str, + name: &str, + parent: Option<&str>, + properties: Option<&Bound<'_, PyDict>>, + ) -> PyResult { + let parent = match parent { + Some(p) => p.to_string(), + None => self.db.get_active_drive().ok_or_else(|| { + PyValueError::new_err("create() needs a parent or an active drive") + })?, + }; + + let mut extra_owned: Vec<(String, Value)> = Vec::new(); + if let Some(props) = properties { + for (key, val) in props.iter() { + let key: String = key.extract()?; + extra_owned.push((resolve_property(&key), py_to_value(&val)?)); + } + } + let extra_refs: Vec<(&str, Value)> = extra_owned + .iter() + .map(|(k, v)| (k.as_str(), v.clone())) + .collect(); + let extra = if extra_refs.is_empty() { + None + } else { + Some(extra_refs) + }; + + let subject = + block_on(self.db.create_resource(class_url, &parent, name, extra)).map_err(py_err)?; + self.get(&subject)? + .ok_or_else(|| py_err(format!("created {subject} but could not read it back"))) + } + + /// Fetch a resource by subject URL / DID. `None` if it is not stored. + fn get(&self, subject: &str) -> PyResult> { + let subject = self.subject(subject); + match block_on(self.db.get_resource(&subject)) { + Ok(resource) => Ok(Some(self.wrap_resource(resource))), + Err(_) => Ok(None), + } + } + + /// True when the subject is already in this store (no network fetch). + fn has(&self, subject: &str) -> bool { + self.db.has_resource_locally(subject) + } + + /// Query local resources. + /// + /// Filters AND together. With no filter, lists children of the active + /// drive. Pass `parent`, `class_url`, and/or `property` + `value`. + #[pyo3(signature = (parent=None, class_url=None, property=None, value=None, limit=None, offset=0))] + fn query( + &self, + parent: Option<&str>, + class_url: Option<&str>, + property: Option<&str>, + value: Option<&Bound<'_, PyAny>>, + limit: Option, + offset: usize, + ) -> PyResult> { + let mut parent = parent.map(|s| s.to_string()); + if parent.is_none() && class_url.is_none() && property.is_none() { + parent = self.db.get_active_drive(); + if parent.is_none() { + return Err(PyValueError::new_err( + "query() needs a filter or an active drive", + )); + } + } + + let mut q = Query::new(); + q.include_nested = true; + q.limit = limit; + q.offset = offset; + + if let Some(class) = class_url { + q = q.class_filter(class); + } + if let Some(parent) = &parent { + q = q.filter(urls::PARENT, Value::String(parent.clone())); + } + if let Some(prop) = property { + let val = value + .ok_or_else(|| PyValueError::new_err("query(property=...) also needs value="))?; + q.property = Some(resolve_property(prop)); + q.value = Some(py_to_value(val)?); + } + + let result = block_on(self.db.query(&q)).map_err(py_err)?; + Ok(result + .resources + .into_iter() + .map(|r| self.wrap_resource(r)) + .collect()) + } + + /// Destroy a resource (signed commit). Raises if it is not stored. + fn delete(&self, subject: &str) -> PyResult<()> { + let mut resource = self + .get(subject)? + .ok_or_else(|| PyKeyError::new_err(subject.to_string()))?; + resource.destroy()?; + Ok(()) + } + + /// Persist buffered writes. Call this before exit if you care about + /// the last few seconds of data. + fn flush(&self) -> PyResult<()> { + self.db.flush().map_err(py_err) + } + + fn __enter__(slf: Py) -> Py { + slf + } + + fn __exit__( + &self, + _exc_type: &Bound<'_, PyAny>, + _exc: &Bound<'_, PyAny>, + _tb: &Bound<'_, PyAny>, + ) -> PyResult { + self.flush()?; + Ok(false) + } + + fn __repr__(&self) -> String { + match self.db.get_active_drive() { + Some(drive) => format!("Store(active_drive={drive:?})"), + None => "Store()".to_string(), + } + } +} + +fn agent_info(agent: atomic_lib::agents::Agent, drive_needs_sync: bool) -> PyResult { + let secret = agent.build_secret().map_err(py_err)?; + Ok(AgentInfo { + subject: agent.subject.to_string(), + secret, + public_key: agent.public_key, + name: agent.name, + drive_needs_sync, + }) +} diff --git a/python/src/urls.rs b/python/src/urls.rs new file mode 100644 index 0000000000..b9dcc1f758 --- /dev/null +++ b/python/src/urls.rs @@ -0,0 +1,51 @@ +use atomic_lib::urls; +use pyo3::prelude::*; +use pyo3::types::PyModule; + +/// Well-known Atomic Data class and property URLs. +pub(crate) fn urls_module(py: Python<'_>) -> PyResult> { + let m = PyModule::new(py, "urls")?; + + // Classes + m.add("CLASS", urls::CLASS)?; + m.add("PROPERTY", urls::PROPERTY)?; + m.add("AGENT", urls::AGENT)?; + m.add("DRIVE", urls::DRIVE)?; + m.add("FOLDER", urls::FOLDER)?; + m.add("FILE", urls::FILE)?; + m.add("COMMIT", urls::COMMIT)?; + m.add("CHATROOM", urls::CHATROOM)?; + m.add("MESSAGE", urls::MESSAGE)?; + m.add("DOCUMENT_V2", urls::DOCUMENT_V2)?; + m.add("TABLE", urls::TABLE)?; + m.add("TAG", urls::TAG)?; + m.add("PLAIN_TEXT", urls::PLAIN_TEXT)?; + m.add("FORK", urls::FORK)?; + m.add("ONTOLOGY", urls::ONTOLOGY)?; + m.add("BOOKMARK", urls::BOOKMARK)?; + + // Properties + m.add("NAME", urls::NAME)?; + m.add("DESCRIPTION", urls::DESCRIPTION)?; + m.add("SHORTNAME", urls::SHORTNAME)?; + m.add("IS_A", urls::IS_A)?; + m.add("PARENT", urls::PARENT)?; + m.add("READ", urls::READ)?; + m.add("WRITE", urls::WRITE)?; + m.add("CHILDREN", urls::CHILDREN)?; + m.add("DRIVES", urls::DRIVES)?; + m.add("DATATYPE", urls::DATATYPE_PROP)?; + m.add("CLASSTYPE", urls::CLASSTYPE_PROP)?; + m.add("REQUIRES", urls::REQUIRES)?; + m.add("RECOMMENDS", urls::RECOMMENDS)?; + m.add("CREATED_AT", urls::CREATED_AT)?; + m.add("CREATED_BY", urls::CREATED_BY)?; + m.add("PUBLIC_KEY", urls::PUBLIC_KEY)?; + m.add("SUBJECT", urls::SUBJECT)?; + m.add("SIGNER", urls::SIGNER)?; + m.add("DRIVE_PROP", urls::DRIVE_PROP)?; + m.add("PERSONAL_DRIVE", urls::PERSONAL_DRIVE)?; + m.add("LANGUAGE", urls::LANGUAGE)?; + + Ok(m) +} diff --git a/python/tests/test_persistence.py b/python/tests/test_persistence.py new file mode 100644 index 0000000000..2b65980502 --- /dev/null +++ b/python/tests/test_persistence.py @@ -0,0 +1,55 @@ +"""File-backed store survives close + reopen.""" + +from atomic_data import Store, urls + + +def test_reopen_reads_and_writes(tmp_path): + path = tmp_path / "atomic-store" + + store = Store.open(str(path)) + setup = store.setup("Ada") + note = store.create( + urls.PLAIN_TEXT, + name="Persisted", + description="on disk", + ) + subject = note.subject + store.flush() + del store + + reopened = Store.open(str(path)) + # Agent is in-memory only; the graph is on disk. + assert reopened.agent() is None + loaded = reopened.get(subject) + assert loaded is not None + assert loaded["name"] == "Persisted" + assert loaded["description"] == "on disk" + assert reopened.has(setup.drive_subject) + + agent = reopened.load_agent(setup.agent_secret) + assert agent.subject == setup.agent_subject + assert agent.drive_needs_sync is False + reopened.active_drive = setup.drive_subject + + loaded["description"] = "edited after reopen" + loaded.save() + reopened.flush() + del reopened + + again = Store.open(str(path)) + got = again.get(subject) + assert got is not None + assert got["description"] == "edited after reopen" + + +def test_context_manager_flushes(tmp_path): + path = tmp_path / "ctx-store" + subject = None + with Store.open(str(path)) as store: + store.setup("Ada") + note = store.create(urls.FOLDER, name="Ctx") + subject = note.subject + + reopened = Store.open(str(path)) + assert reopened.get(subject) is not None + assert reopened.get(subject)["name"] == "Ctx" diff --git a/python/tests/test_sdk.py b/python/tests/test_sdk.py new file mode 100644 index 0000000000..ee13280291 --- /dev/null +++ b/python/tests/test_sdk.py @@ -0,0 +1,150 @@ +"""Local read / write / query against an in-memory atomic_lib store.""" + +import json + +import pytest + +from atomic_data import Store, urls + + +@pytest.fixture +def store(): + s = Store.in_memory() + s.setup("Test agent") + return s + + +def test_setup_creates_agent_and_drive(): + store = Store.in_memory() + assert store.agent() is None + assert store.active_drive is None + + info = store.setup("Ada") + + assert info.agent_subject.startswith("did:ad:agent:") + assert info.drive_subject.startswith("did:ad:") + assert info.agent_secret + assert store.active_drive == info.drive_subject + agent = store.agent() + assert agent is not None + assert agent.subject == info.agent_subject + assert store.has(info.drive_subject) + + +def test_create_read_update(store): + note = store.create( + urls.PLAIN_TEXT, + name="Hello", + description="first draft", + ) + assert note.subject.startswith("did:ad:") + assert note["name"] == "Hello" + assert note["description"] == "first draft" + assert urls.PARENT in note + assert note[urls.IS_A] == [urls.PLAIN_TEXT] + + note["description"] = "second draft" + note["name"] = "Hello again" + note.save() + + got = store.get(note.subject) + assert got is not None + assert got["name"] == "Hello again" + assert got["description"] == "second draft" + + +def test_shortname_and_url_keys(store): + note = store.create(urls.FOLDER, name="Docs") + note.set(urls.DESCRIPTION, "folder") + note.save() + + got = store.get(note.subject) + assert got["description"] == "folder" + assert got[urls.DESCRIPTION] == "folder" + assert got.get("missing") is None + assert got.get("missing", "fallback") == "fallback" + + +def test_value_types_roundtrip(store): + note = store.create(urls.PLAIN_TEXT, name="types") + note["description"] = "text" + note.set("https://example.com/properties/count", 7) + note.set("https://example.com/properties/flag", True) + note.set("https://example.com/properties/ratio", 1.5) + note.set("https://example.com/properties/tags", ["did:ad:a", "did:ad:b"]) + note.set("https://example.com/properties/payload", {"k": 1}) + note.save() + + got = store.get(note.subject) + assert got["https://example.com/properties/count"] == 7 + assert got["https://example.com/properties/flag"] is True + assert got["https://example.com/properties/ratio"] == 1.5 + assert got["https://example.com/properties/tags"] == ["did:ad:a", "did:ad:b"] + assert got["https://example.com/properties/payload"] == {"k": 1} + + +def test_query_by_parent_and_class(store): + drive = store.active_drive + a = store.create(urls.FOLDER, name="Alpha") + b = store.create(urls.PLAIN_TEXT, name="Beta") + store.create(urls.PLAIN_TEXT, name="Gamma") + + children = store.query(parent=drive) + names = {c.name for c in children} + assert {"Alpha", "Beta", "Gamma"} <= names + + folders = store.query(parent=drive, class_url=urls.FOLDER) + assert {f.subject for f in folders} == {a.subject} + + texts = store.query(class_url=urls.PLAIN_TEXT) + subjects = {t.subject for t in texts} + assert b.subject in subjects + assert a.subject not in subjects + + +def test_delete_removes_resource(store): + note = store.create(urls.PLAIN_TEXT, name="ephemeral") + subject = note.subject + assert store.get(subject) is not None + + store.delete(subject) + assert store.get(subject) is None + + +def test_resource_destroy(store): + note = store.create(urls.PLAIN_TEXT, name="bye") + subject = note.subject + note.destroy() + assert store.get(subject) is None + + +def test_to_dict_and_json(store): + note = store.create(urls.PLAIN_TEXT, name="serial") + as_dict = note.to_dict() + assert as_dict["@id"] == note.subject + assert as_dict[urls.NAME] == "serial" + + parsed = json.loads(note.to_json()) + assert parsed["@id"] == note.subject + assert parsed[urls.NAME] == "serial" + + +def test_missing_get_returns_none(store): + assert store.get("did:ad:does-not-exist") is None + + +def test_create_requires_parent_or_drive(): + store = Store.in_memory() + store.create_agent("No drive") + with pytest.raises(ValueError, match="parent or an active drive"): + store.create(urls.FOLDER, name="orphan") + + +def test_independent_in_memory_stores(): + a = Store.in_memory() + b = Store.in_memory() + setup_a = a.setup("A") + b.setup("B") + note = a.create(urls.PLAIN_TEXT, name="only-in-a") + assert b.get(note.subject) is None + assert a.get(setup_a.drive_subject) is not None From 73ff4c060c7e79ea1a6f6e61a756d1987c63b006 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 16 Aug 2026 11:13:38 +0000 Subject: [PATCH 02/16] Fix Python SDK query drive scope, persistence reopen, and changelog. Indexed queries need Query::drive. Persistence tests drop every Store and Resource handle before reopen so redb can release its file lock. destroy tests use Folder, which has no extra required properties. Co-authored-by: joepmeindertsma --- CHANGELOG.md | 1 + python/README.md | 6 ++ python/src/store.rs | 6 ++ python/tests/test_persistence.py | 101 ++++++++++++++++++------------- python/tests/test_sdk.py | 5 +- 5 files changed, 74 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29a26cf564..4d3d5b70fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -363,6 +363,7 @@ See [STATUS.md](server/STATUS.md) to learn more about which features will remain `ingest_commit_json` serializes; `sync::ws_apply::apply_commit_json` now returns the `CommitResponse` instead of `()`. See `planning/runtime-boundary-decision.md`. +- **Python SDK** (`python/`, import `atomic_data`): local-first bindings over `atomic_lib` via PyO3. `Store.open` / `in_memory`, setup, create/read/update/query/delete, file-backed reopen. No server required. Sync and PyPI are not in this first cut. ## [v0.41.0-beta.2] - 2026-08-01 diff --git a/python/README.md b/python/README.md index 03cb3f53da..4888922f5e 100644 --- a/python/README.md +++ b/python/README.md @@ -73,6 +73,12 @@ page = store.get(subject) `Store.in_memory()` is the same API without a directory — useful in tests. +A `Resource` keeps a handle to the store. Drop every `Store` and `Resource` +before opening the same directory again — redb takes an exclusive file lock. + +`save()` and `destroy()` validate schema. `PlainText` requires `description`; +`Folder` does not. + ## API - **Store** — `open(path)`, `in_memory()`, `setup(name)`, `load_agent(secret)`, diff --git a/python/src/store.rs b/python/src/store.rs index 2303e390c0..617679d2d9 100644 --- a/python/src/store.rs +++ b/python/src/store.rs @@ -266,6 +266,12 @@ impl Store { q.include_nested = true; q.limit = limit; q.offset = offset; + // Indexed queries refuse to run without a drive. Prefer the active + // drive; fall back to `parent` when that *is* the drive (setup just + // created it and set it active, or the caller passed it explicitly). + if let Some(drive) = self.db.get_active_drive().or_else(|| parent.clone()) { + q.drive = Some(self.subject(&drive)); + } if let Some(class) = class_url { q = q.class_filter(class); diff --git a/python/tests/test_persistence.py b/python/tests/test_persistence.py index 2b65980502..4a4995b79f 100644 --- a/python/tests/test_persistence.py +++ b/python/tests/test_persistence.py @@ -1,55 +1,70 @@ -"""File-backed store survives close + reopen.""" +"""File-backed store survives close + reopen. + +redb holds an exclusive file lock for as long as any `Store` or `Resource` +handle is alive (both clone the same `Db`). Tests that reopen must drop +every handle first — a nested function is the reliable way to do that. +""" from atomic_data import Store, urls def test_reopen_reads_and_writes(tmp_path): - path = tmp_path / "atomic-store" - - store = Store.open(str(path)) - setup = store.setup("Ada") - note = store.create( - urls.PLAIN_TEXT, - name="Persisted", - description="on disk", - ) - subject = note.subject - store.flush() - del store - - reopened = Store.open(str(path)) - # Agent is in-memory only; the graph is on disk. - assert reopened.agent() is None - loaded = reopened.get(subject) - assert loaded is not None - assert loaded["name"] == "Persisted" - assert loaded["description"] == "on disk" - assert reopened.has(setup.drive_subject) - - agent = reopened.load_agent(setup.agent_secret) - assert agent.subject == setup.agent_subject - assert agent.drive_needs_sync is False - reopened.active_drive = setup.drive_subject - - loaded["description"] = "edited after reopen" - loaded.save() - reopened.flush() - del reopened - - again = Store.open(str(path)) + path = str(tmp_path / "atomic-store") + + def write(): + store = Store.open(path) + setup = store.setup("Ada") + note = store.create( + urls.PLAIN_TEXT, + name="Persisted", + description="on disk", + ) + subject = note.subject + secret = setup.agent_secret + drive = setup.drive_subject + store.flush() + return subject, secret, drive + + subject, secret, drive = write() + + def edit(): + reopened = Store.open(path) + assert reopened.agent() is None + loaded = reopened.get(subject) + assert loaded is not None + assert loaded["name"] == "Persisted" + assert loaded["description"] == "on disk" + assert reopened.has(drive) + + agent = reopened.load_agent(secret) + assert agent.subject.startswith("did:ad:agent:") + assert agent.drive_needs_sync is False + reopened.active_drive = drive + + loaded["description"] = "edited after reopen" + loaded.save() + reopened.flush() + + edit() + + again = Store.open(path) got = again.get(subject) assert got is not None assert got["description"] == "edited after reopen" def test_context_manager_flushes(tmp_path): - path = tmp_path / "ctx-store" - subject = None - with Store.open(str(path)) as store: - store.setup("Ada") - note = store.create(urls.FOLDER, name="Ctx") - subject = note.subject + path = str(tmp_path / "ctx-store") - reopened = Store.open(str(path)) - assert reopened.get(subject) is not None - assert reopened.get(subject)["name"] == "Ctx" + def write(): + with Store.open(path) as store: + store.setup("Ada") + note = store.create(urls.FOLDER, name="Ctx") + return note.subject + + subject = write() + + reopened = Store.open(path) + got = reopened.get(subject) + assert got is not None + assert got["name"] == "Ctx" diff --git a/python/tests/test_sdk.py b/python/tests/test_sdk.py index ee13280291..53022be60e 100644 --- a/python/tests/test_sdk.py +++ b/python/tests/test_sdk.py @@ -103,7 +103,8 @@ def test_query_by_parent_and_class(store): def test_delete_removes_resource(store): - note = store.create(urls.PLAIN_TEXT, name="ephemeral") + # Folder has no extra required props; destroy/save validate schema. + note = store.create(urls.FOLDER, name="ephemeral") subject = note.subject assert store.get(subject) is not None @@ -112,7 +113,7 @@ def test_delete_removes_resource(store): def test_resource_destroy(store): - note = store.create(urls.PLAIN_TEXT, name="bye") + note = store.create(urls.FOLDER, name="bye") subject = note.subject note.destroy() assert store.get(subject) is None From 5921c0a75ace16541bc294bb1e82c7221e5d19b5 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 16 Aug 2026 11:18:27 +0000 Subject: [PATCH 03/16] Plan a Kotlin SDK: same v1 scope as Python, UniFFI over a shared Rust surface. Do not clone python/src. Extract atomic_lib::sdk first so Kotlin, Swift, and the Android Binder host share one API. PyO3 and FRB stay for their languages. Co-authored-by: joepmeindertsma --- planning/SDK-API-design.md | 1 + planning/kotlin-sdk.md | 121 +++++++++++++++++++++++++++++++++++++ planning/python-sdk.md | 4 ++ 3 files changed, 126 insertions(+) create mode 100644 planning/kotlin-sdk.md diff --git a/planning/SDK-API-design.md b/planning/SDK-API-design.md index bab3ea8f0a..e2dd35e70f 100644 --- a/planning/SDK-API-design.md +++ b/planning/SDK-API-design.md @@ -23,6 +23,7 @@ In the "old" HTTP based Atomic(Server) UX, an app developer had to: - Server & connection to it is optional, only required for back-up. - Flutter library for native iOS and android apps - Python SDK (`python/`, import `atomic_data`) wrapping `atomic_lib` via PyO3 — local read/write/query/persist. See [`python-sdk.md`](./python-sdk.md). +- Kotlin (not built): same v1 scope, UniFFI over a shared Rust `sdk` surface — not another hand-rolled FFI. See [`kotlin-sdk.md`](./kotlin-sdk.md). Unlocks Swift and the Android Binder host. - Still no full, end-to-end atomic app building tutorial available ## Future situation diff --git a/planning/kotlin-sdk.md b/planning/kotlin-sdk.md new file mode 100644 index 0000000000..f9bb59d1f2 --- /dev/null +++ b/planning/kotlin-sdk.md @@ -0,0 +1,121 @@ +# Kotlin SDK + +## Status + +Decision, not built. Same *product* scope as the Python v1 SDK +([`python-sdk.md`](./python-sdk.md)): local read / write / query / persist +over `atomic_lib`. Different *binding*. + +## Decision + +Use **UniFFI**, not PyO3-style hand bindings, not `flutter_rust_bridge`, not +an HTTP client. + +Kotlin is the first language where UniFFI is the obvious tool: + +- UniFFI's Kotlin (and Swift) backends are first-class. Python's is not, + which is why `python/` uses PyO3. +- [`android-data-reuse.md`](./android-data-reuse.md) already needs UniFFI: + the on-device host must serve other apps over Binder **without a Flutter + engine**. FRB cannot be that entry point. +- [`virtual-drive.md`](./virtual-drive.md) needs the same Swift + Kotlin + pair for File Provider / DocumentsProvider. +- One UDL gives Swift for free when iOS wants the same v1 surface. + +Do **not** copy `python/src/*.rs` into a `kotlin/` crate and re-wrap `Db`. +That is how the binding zoo grows. The Python v1 API is the *shape* to +expose, not the code to duplicate. + +## Shared Rust surface first + +Before (or as the first commit of) the Kotlin work, extract the generic +store API into one Rust module that every binding calls: + +```text +atomic_lib::sdk (or a small atomic-ffi crate) + + open / in_memory + setup / load_agent / create_agent + create_drive / list_drives + create / get / query / delete / flush + Resource: get/set/save/destroy / to_json +``` + +Callers: + +| Binding | Today | Target | +| --- | --- | --- | +| Python | PyO3 talks to `Db` directly | keep PyO3; point it at `sdk` when convenient | +| Flutter | FRB `simple.rs` mixes store + canvas | store group calls `sdk`; canvas stays FRB | +| Kotlin / Swift | none | UniFFI over `sdk` | +| Android host | none | same UniFFI, then Binder in Kotlin | +| WASM | `ClientDb` | later; OPFS constraints differ | + +This is a thin slice of [`atomic-lib-runtime.md`](./atomic-lib-runtime.md) +`AtomicNode` — enough for local CRUD, not events / outbox / transport yet. + +`flutter/rust/src/api/simple.rs` already listed the groups. Steal those, drop +canvas/history/peer from v1. + +## v1 Kotlin product scope (match Python) + +```kotlin +val store = Store.open(path) // or Store.inMemory() +val setup = store.setup("Ada") +val note = store.create(Urls.PLAIN_TEXT, name = "Hello", parent = setup.driveSubject) +note.set(Urls.DESCRIPTION, "offline") +note.save() +val got = store.get(note.subject) +store.query(parent = setup.driveSubject) +store.flush() +``` + +Ship as an AAR (`dev.atomicdata:sdk`) for JVM + Android. JVM-only is enough +to test the binding; Android needs `cargo-ndk` and rustls-platform-verifier +JNI init (known pitfall from the Android build work). + +Same v1 exclusions as Python: no WS/Iroh, no blobs, no history. Sync waits +until the shared `sdk` grows it, rather than each language inventing a +session API. + +## What Kotlin adds that Python does not + +- **Process lifetime.** An Android host can be killed the moment the caller + unbinds. `flush()` after every user-visible write is load-bearing, not + polite. See android-data-reuse. +- **One store per device.** The in-process UniFFI SDK is the *host* path. + Other apps should not each open a redb file. The Binder client SDK is a + later layer on top of this, not a second store. +- **redb exclusive lock.** Same as Python: one process owns the file. On + Android that is the elected host, not "whoever imported the AAR". + +## Layout (when built) + +```text +ffi/ # or kotlin/ — excluded from the Cargo workspace + Cargo.toml # uniffi, atomic_lib (db-redb) + src/lib.rs # thin UniFFI scaffolding + src/atomic.udl # the sdk surface + android/ # Gradle AAR + tests/ # JVM unit tests against in-memory store +``` + +Exclude from the workspace like `python/` and `flutter/rust`. + +## Order + +1. Extract `atomic_lib::sdk` (or `atomic-ffi`) with in-process Rust tests. + No new language yet. This is the useful prerequisite. +2. UniFFI + JVM tests for the same cases as `python/tests/`. +3. Android AAR + `cargo-ndk`. +4. Only then Binder host/client (`android-data-reuse.md`). + +Skipping step 1 and writing Kotlin-only wrappers around `Db` will have to +be rewritten when Swift, the Android host, and `AtomicNode` show up. + +## Not this work + +- Rewriting the Python SDK onto UniFFI. PyO3 stays. +- Replacing Flutter's FRB with UniFFI. FRB stays for Dart; UniFFI is the + non-Flutter native entry. +- Wear OS / watch HTTP clients (`habits-app.md`). Different product. diff --git a/planning/python-sdk.md b/planning/python-sdk.md index 8a21420cd0..2e47708292 100644 --- a/planning/python-sdk.md +++ b/planning/python-sdk.md @@ -20,6 +20,10 @@ UniFFI would help if one IDL had to generate Swift + Kotlin + Python. The Python API can be idiomatic on its own (dict-like `Resource`, sync methods, context manager). Flutter already has its own bridge. +Kotlin is next and **does** use UniFFI — see [`kotlin-sdk.md`](./kotlin-sdk.md). +Do not clone `python/src` for that. Extract a shared `atomic_lib::sdk` +surface first; PyO3 can keep its skin. + The crate is **excluded from the Cargo workspace** (same reason as `flutter/rust`): workspace `clippy` / `nextest` should not compile PyO3 on every Rust CI run. From 9a86206348ab1c26ea8ab1188d93449f14de2a89 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 16 Aug 2026 11:27:11 +0000 Subject: [PATCH 04/16] Clarify why the Python/Kotlin v1 SDKs omit Iroh: scope and SyncSession, not a ban. Iroh already works in atomic_lib and Flutter. New language bindings should not ship peer_sync as the primary API. Co-authored-by: joepmeindertsma --- planning/kotlin-sdk.md | 6 +++--- planning/python-sdk.md | 8 +++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/planning/kotlin-sdk.md b/planning/kotlin-sdk.md index f9bb59d1f2..ab1dcb0443 100644 --- a/planning/kotlin-sdk.md +++ b/planning/kotlin-sdk.md @@ -74,9 +74,9 @@ Ship as an AAR (`dev.atomicdata:sdk`) for JVM + Android. JVM-only is enough to test the binding; Android needs `cargo-ndk` and rustls-platform-verifier JNI init (known pitfall from the Android build work). -Same v1 exclusions as Python: no WS/Iroh, no blobs, no history. Sync waits -until the shared `sdk` grows it, rather than each language inventing a -session API. +Same v1 exclusions as Python: no blobs, no history. Sync is not forbidden — +it waits for the shared `sdk` to grow a `SyncSession` (WS or Iroh) rather +than each language wrapping `peer_sync()` again. See `python-sdk.md`. ## What Kotlin adds that Python does not diff --git a/planning/python-sdk.md b/planning/python-sdk.md index 2e47708292..aac2677646 100644 --- a/planning/python-sdk.md +++ b/planning/python-sdk.md @@ -41,7 +41,13 @@ runtime and drops the GIL for the Rust work. ## Not in v1 -- WebSocket / Iroh sync (Flutter already has the pattern) +- **Iroh / WS sync.** Not blocked — `atomic_lib` already has it, Flutter + already wraps it (`start_peer`, `peer_announce`, `peer_sync`). Left out + because v1 was the binding + local redb loop, Iroh is a heavy feature + (`iroh` + `discovery`, live `Router`, one node per process), and + [`unified-sync.md`](./unified-sync.md) does not want a new language to + ship `peer_sync()` as the primary API. Next network work should be a + `SyncSession` (WS or Iroh), not a Python-only QR/peer helper. - Blobs - History / time-travel - Code-first schema (see `json-schema-code-first.md`) From a9716fff804c5f11e7a39c0216e15b03e665f992 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 16 Aug 2026 12:12:06 +0000 Subject: [PATCH 05/16] Add Iroh P2P to the Python SDK: start_peer, sync_with, live save. Local CRUD without realtime peer sync is not the product. Same surface Flutter already wraps, plus a two-process pytest. Co-authored-by: joepmeindertsma --- CHANGELOG.md | 2 +- TESTING_COVERAGE.md | 2 +- docs/src/python.md | 20 +++- planning/README.md | 2 + planning/kotlin-sdk.md | 7 +- planning/python-sdk.md | 22 +++-- python/Cargo.toml | 6 +- python/README.md | 14 ++- python/src/lib.rs | 4 + python/src/peer.rs | 156 ++++++++++++++++++++++++++++++++ python/src/resource.rs | 6 +- python/src/store.rs | 75 +++++++++++++++ python/tests/iroh_peer_child.py | 34 +++++++ python/tests/test_iroh.py | 100 ++++++++++++++++++++ 14 files changed, 430 insertions(+), 20 deletions(-) create mode 100644 python/src/peer.rs create mode 100644 python/tests/iroh_peer_child.py create mode 100644 python/tests/test_iroh.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d3d5b70fc..1fcb33104b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -363,7 +363,7 @@ See [STATUS.md](server/STATUS.md) to learn more about which features will remain `ingest_commit_json` serializes; `sync::ws_apply::apply_commit_json` now returns the `CommitResponse` instead of `()`. See `planning/runtime-boundary-decision.md`. -- **Python SDK** (`python/`, import `atomic_data`): local-first bindings over `atomic_lib` via PyO3. `Store.open` / `in_memory`, setup, create/read/update/query/delete, file-backed reopen. No server required. Sync and PyPI are not in this first cut. +- **Python SDK** (`python/`, import `atomic_data`): bindings over `atomic_lib` via PyO3. Local redb plus Iroh P2P (`start_peer`, `sync_with`, live push on save). No server required. ## [v0.41.0-beta.2] - 2026-08-01 diff --git a/TESTING_COVERAGE.md b/TESTING_COVERAGE.md index 94fdbc6573..827c8ac7d2 100644 --- a/TESTING_COVERAGE.md +++ b/TESTING_COVERAGE.md @@ -649,7 +649,7 @@ One 13-line smoke test, never run in CI — the pipeline has no emulator. ### 7b. Python SDK (`python/`) -Glue only: `pytest` after `maturin develop` covers in-memory create/read/update/query/delete and file-backed reopen. Not in Dagger CI. No sync, blobs, or HTTP. +Glue: `pytest` after `maturin build` covers in-memory CRUD, file-backed reopen, and a two-process Iroh sync (`tests/test_iroh.py`). Not in Dagger CI. No WS session or blobs. ### 8. Known residual races diff --git a/docs/src/python.md b/docs/src/python.md index f4bb699d1e..21463738d1 100644 --- a/docs/src/python.md +++ b/docs/src/python.md @@ -55,8 +55,22 @@ store.load_agent(secret) `Store.in_memory()` is the same API without a directory. +## P2P sync (Iroh) + +```python +node = store.start_peer() # did:ad:node:… +store.announce() # optional pkarr publish +# On the other device, same agent secret (or a grant), then: +other.sync_with(node) +``` + +After the first sync, connected peers get live Loro updates on `.save()`. +`store.wait_for(subject)` blocks until a local or peer change lands. + +Two Iroh nodes cannot share one Python process — `start_peer` is +process-global, same as Flutter. Use two processes (or two devices). + ## What this is not (yet) -v1 is local read, write, query, and persist. It does not open a WebSocket or -an Iroh session. Those can use the same store later, as they already do in -Flutter. +WebSocket-to-server `SyncSession` is not wrapped. Blobs and history are not +wrapped. Iroh P2P is. diff --git a/planning/README.md b/planning/README.md index a6b196b4fa..9f936a39b9 100644 --- a/planning/README.md +++ b/planning/README.md @@ -126,6 +126,8 @@ Design intent, kept for direction. None has a line of code behind it. | --- | --- | | [`reticulum-sync.md`](./reticulum-sync.md) | Atomic sync protocol over Reticulum. | | [`nextgraph-interop.md`](./nextgraph-interop.md) | `did:ng:` via a scheme-routed Store backend. PR #1360 (optional NextGraph mirror) is open. | +| [`kotlin-sdk.md`](./kotlin-sdk.md) | **Decision.** Kotlin v1 matches Python's local CRUD scope via UniFFI over a shared Rust surface. | +| [`llm-wasm-gui-plugins.md`](./llm-wasm-gui-plugins.md) | **Proposal.** Browser-built JS/TS apps with scoped Loro docs. | | [`personal-information-suite.md`](./personal-information-suite.md) | Contacts, calendar, email. | | [`social-apps.md`](./social-apps.md) | Requirements for social-network-shaped apps. Companion to `zones.md`. | | [`atomic-assistant-browser-extension.md`](./atomic-assistant-browser-extension.md) | Local-first Chromium extension. | diff --git a/planning/kotlin-sdk.md b/planning/kotlin-sdk.md index ab1dcb0443..800c512b57 100644 --- a/planning/kotlin-sdk.md +++ b/planning/kotlin-sdk.md @@ -74,9 +74,10 @@ Ship as an AAR (`dev.atomicdata:sdk`) for JVM + Android. JVM-only is enough to test the binding; Android needs `cargo-ndk` and rustls-platform-verifier JNI init (known pitfall from the Android build work). -Same v1 exclusions as Python: no blobs, no history. Sync is not forbidden — -it waits for the shared `sdk` to grow a `SyncSession` (WS or Iroh) rather -than each language wrapping `peer_sync()` again. See `python-sdk.md`. +Iroh belongs in Kotlin v1 the same way it does in Python — local CRUD +without P2P is not the product. Wrap the same `start_peer` / `sync_with` / +live-save surface. A later `SyncSession` can sit on top; do not ship Kotlin +without a way for two devices to converge. ## What Kotlin adds that Python does not diff --git a/planning/python-sdk.md b/planning/python-sdk.md index aac2677646..1fba2784f7 100644 --- a/planning/python-sdk.md +++ b/planning/python-sdk.md @@ -33,21 +33,29 @@ every Rust CI run. - `Store.open(path)` / `Store.in_memory()` - `setup`, `load_agent`, `create_agent`, `create_drive` - `create`, `get`, `query`, `delete`, `flush` +- Iroh: `start_peer`, `announce`, `sync_with`, `wait_for`, live push on `save` - `Resource` dict access, `save`, `destroy`, `to_dict`, `to_json` - `atomic_data.urls` constants Python calls are synchronous. Each one `block_on`s a process-wide tokio runtime and drops the GIL for the Rust work. +## Sync + +Iroh is in. Local CRUD without P2P is not the product. + +- `start_peer()` / `peer_id` / `announce()` / `sync_with(node_id)` +- `.save()` broadcasts a live Loro update when peers are connected +- `wait_for(subject)` observes local or peer changes +- Two-process pytest (`tests/test_iroh.py`) — one node per OS process + +WS `SyncSession` (unified-sync) is still the next *shared* API. Python +ships the same Iroh calls Flutter already has, because that is what +makes two devices converge today. + ## Not in v1 -- **Iroh / WS sync.** Not blocked — `atomic_lib` already has it, Flutter - already wraps it (`start_peer`, `peer_announce`, `peer_sync`). Left out - because v1 was the binding + local redb loop, Iroh is a heavy feature - (`iroh` + `discovery`, live `Router`, one node per process), and - [`unified-sync.md`](./unified-sync.md) does not want a new language to - ship `peer_sync()` as the primary API. Next network work should be a - `SyncSession` (WS or Iroh), not a Python-only QR/peer helper. +- WebSocket-to-server session - Blobs - History / time-travel - Code-first schema (see `json-schema-code-first.md`) diff --git a/python/Cargo.toml b/python/Cargo.toml index 48fd9b7b4d..960f4e8489 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -11,7 +11,11 @@ name = "atomic_data" crate-type = ["cdylib"] [dependencies] -atomic_lib = { path = "../lib", default-features = false, features = ["db-redb"] } +atomic_lib = { path = "../lib", default-features = false, features = [ + "db-redb", + "iroh", + "discovery", +] } pyo3 = { version = "0.25", features = ["abi3-py39"] } serde_json = "1" tokio = { version = "1", features = ["rt-multi-thread", "macros"] } diff --git a/python/README.md b/python/README.md index 4888922f5e..1b8a74a23e 100644 --- a/python/README.md +++ b/python/README.md @@ -23,8 +23,8 @@ Reimplementing commits, Ed25519, and Loro in Python would drift. UniFFI is a better fit when one IDL must generate Swift + Kotlin + Python; here the Python API can be idiomatic on its own, the way the Flutter bridge is. -v1 is local read / write / query / persist. WebSocket and Iroh sync can sit on -the same store later, as they already do in Flutter. +Reads and writes are local. Sync is Iroh: `start_peer()`, hand the node URI +to another device, `sync_with()`. After that, `.save()` pushes live. ## Install (from this repo) @@ -61,6 +61,11 @@ for child in store.query(parent=setup.drive_subject): print(child.subject, child.name) store.flush() + +# P2P: start Iroh, give this URI to another device, then sync. +node = store.start_peer() +print(node) # did:ad:node:… +# other.sync_with(node) ``` Reopen later with the same path and the agent secret from `setup.agent_secret`: @@ -84,7 +89,10 @@ before opening the same directory again — redb takes an exclusive file lock. - **Store** — `open(path)`, `in_memory()`, `setup(name)`, `load_agent(secret)`, `create(class_url, name, ...)`, `get(subject)`, `query(...)`, `delete(subject)`, `flush()`, context manager -- **Resource** — dict-like access, `.save()`, `.destroy()`, `.to_dict()`, `.to_json()` +- **Iroh** — `start_peer()`, `peer_id`, `announce()`, `sync_with(node_id)`, + `add_peer()`, `peers()`, `live_peers()`, `wait_for(subject)` +- **Resource** — dict-like access, `.save()` (live-pushes if peers are up), + `.destroy()`, `.to_dict()`, `.to_json()` - **atomic_data.urls** — well-known class and property URLs Property keys accept full URLs or shortnames (`name`, `description`, `parent`, diff --git a/python/src/lib.rs b/python/src/lib.rs index 039cff09c7..f03829f13f 100644 --- a/python/src/lib.rs +++ b/python/src/lib.rs @@ -10,11 +10,13 @@ use std::sync::OnceLock; use pyo3::prelude::*; mod convert; +mod peer; mod resource; mod store; mod urls; pub(crate) use convert::{py_err, py_to_value, resolve_property, resource_to_dict, value_to_py}; +pub(crate) use peer::{PeerInfo, SyncReport}; pub(crate) use resource::Resource; pub(crate) use store::{AgentInfo, DriveInfo, SetupInfo, Store}; @@ -48,6 +50,8 @@ fn atomic_data(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; + m.add_class::()?; let urls_mod = urls::urls_module(m.py())?; m.add_submodule(&urls_mod)?; diff --git a/python/src/peer.rs b/python/src/peer.rs new file mode 100644 index 0000000000..307dfec440 --- /dev/null +++ b/python/src/peer.rs @@ -0,0 +1,156 @@ +//! Iroh P2P surface for the Python SDK. +//! +//! Same calls Flutter exposes (`start_peer`, announce, `sync_drive_with_peer`), +//! plus a blocking `wait_for` so a script can observe live updates. + +use std::time::Duration; + +use atomic_lib::storelike::Storelike; +use atomic_lib::{Db, Subject}; +use pyo3::exceptions::{PyTimeoutError, PyValueError}; +use pyo3::prelude::*; + +use crate::{block_on, py_err}; + +/// Result of a bulk Iroh sync round-trip. +#[pyclass] +#[derive(Clone)] +pub struct SyncReport { + #[pyo3(get)] + pub imported: usize, + #[pyo3(get)] + pub pushed: usize, + #[pyo3(get)] + pub in_sync: bool, + #[pyo3(get)] + pub peer_name: Option, +} + +#[pymethods] +impl SyncReport { + fn __repr__(&self) -> String { + format!( + "SyncReport(imported={}, pushed={}, in_sync={})", + self.imported, self.pushed, self.in_sync + ) + } +} + +/// A remembered Iroh peer. +#[pyclass] +#[derive(Clone)] +pub struct PeerInfo { + #[pyo3(get)] + pub node_id: String, + #[pyo3(get)] + pub name: String, +} + +#[pymethods] +impl PeerInfo { + fn __repr__(&self) -> String { + format!("PeerInfo(name={:?}, node_id={:?})", self.name, self.node_id) + } +} + +pub(crate) fn node_uri(id: &str) -> String { + let hex = atomic_lib::sync::peer::normalize_node_id(id); + if hex.starts_with("did:ad:node:") { + hex + } else { + format!("did:ad:node:{hex}") + } +} + +pub(crate) fn publish_live(response: &atomic_lib::commit::CommitResponse) { + if let Some(bytes) = &response.commit.loro_update { + if !bytes.is_empty() { + let key = response.commit.subject.pure_id(); + atomic_lib::sync::peer::broadcast_live_update(&key, bytes); + } + } +} + +pub(crate) fn start_peer(db: &Db) -> PyResult { + if let Some(existing) = atomic_lib::sync::peer::get_node_id() { + return Ok(node_uri(existing)); + } + let (node_id, _router) = block_on(atomic_lib::sync::peer::start(db.clone())).map_err(py_err)?; + Ok(node_uri(&node_id.to_string())) +} + +pub(crate) fn peer_id() -> Option { + atomic_lib::sync::peer::get_node_id().map(node_uri) +} + +pub(crate) fn announce(db: &Db, drive: Option<&str>) -> PyResult<()> { + let drive = match drive { + Some(d) => d.to_string(), + None => db + .get_active_drive() + .ok_or_else(|| PyValueError::new_err("announce() needs a drive or an active drive"))?, + }; + let Some(node_id) = atomic_lib::sync::peer::get_node_id() else { + return Err(PyValueError::new_err( + "Peer not started. Call start_peer() first.", + )); + }; + block_on(atomic_lib::discovery::publish_node_id(&drive, node_id)).map_err(py_err) +} + +pub(crate) fn sync_with(db: &Db, node_id: &str, drive: Option<&str>) -> PyResult { + if atomic_lib::sync::peer::get_node_id().is_none() { + return Err(PyValueError::new_err( + "Peer not started. Call start_peer() first.", + )); + } + let drive = match drive { + Some(d) => d.to_string(), + None => db + .get_active_drive() + .ok_or_else(|| PyValueError::new_err("sync_with() needs a drive or an active drive"))?, + }; + let outcome = block_on(atomic_lib::sync::peer::sync_drive_with_peer_outcome( + node_id, &drive, db, + )) + .map_err(py_err)?; + Ok(SyncReport { + imported: outcome.count, + pushed: outcome.pushed, + in_sync: outcome.in_sync, + peer_name: outcome.peer_name, + }) +} + +pub(crate) fn wait_for(db: &Db, subject: &str, timeout: f64) -> PyResult { + let target = Subject::from_raw(subject, db.get_base_domain().as_deref()).without_params(); + let mut rx = db.subscribe_events(); + let dur = Duration::from_secs_f64(timeout.max(0.0)); + let result = block_on(async { + tokio::time::timeout(dur, async { + loop { + match rx.recv().await { + Ok(atomic_lib::DbEvent::Changed { subject, .. }) if subject == target => { + return subject.to_string(); + } + Ok(atomic_lib::DbEvent::Destroyed { subject, .. }) if subject == target => { + return format!("!{subject}"); + } + Ok(_) => continue, + Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => continue, + Err(_) => { + tokio::time::sleep(Duration::from_millis(50)).await; + rx = db.subscribe_events(); + } + } + } + }) + .await + }); + match result { + Ok(s) => Ok(s), + Err(_) => Err(PyTimeoutError::new_err(format!( + "timed out waiting for {subject}" + ))), + } +} diff --git a/python/src/resource.rs b/python/src/resource.rs index 56893c53ef..eda764639a 100644 --- a/python/src/resource.rs +++ b/python/src/resource.rs @@ -101,8 +101,12 @@ impl Resource { } /// Sign and apply the pending edits to the local store. + /// + /// If this process has `start_peer()` running and live peers, the Loro + /// delta is pushed over Iroh immediately. fn save(&mut self) -> PyResult<()> { - block_on(self.inner.save_locally(&self.db)).map_err(py_err)?; + let response = block_on(self.inner.save_locally(&self.db)).map_err(py_err)?; + crate::peer::publish_live(&response); Ok(()) } diff --git a/python/src/store.rs b/python/src/store.rs index 617679d2d9..be18e78bbd 100644 --- a/python/src/store.rs +++ b/python/src/store.rs @@ -309,6 +309,81 @@ impl Store { self.db.flush().map_err(py_err) } + /// Start the Iroh node. Returns `did:ad:node:{hex}`. + /// + /// Idempotent. The NodeID is persisted in the store so it survives reopen. + /// Incoming connections stay up for the process lifetime. + fn start_peer(&self) -> PyResult { + crate::peer::start_peer(&self.db) + } + + /// This process's Iroh node URI, if [`Store.start_peer`] has run. + #[getter] + fn peer_id(&self) -> Option { + let _ = self; + crate::peer::peer_id() + } + + /// Publish this node on pkarr for `drive` (default: active drive). + #[pyo3(signature = (drive=None))] + fn announce(&self, drive: Option<&str>) -> PyResult<()> { + crate::peer::announce(&self.db, drive) + } + + /// Bulk-sync the drive with a peer (`did:ad:node:…` or raw hex). + /// + /// Call [`Store.start_peer`] first. After this, live Iroh updates flow + /// both ways for connected peers. + #[pyo3(signature = (node_id, drive=None))] + fn sync_with(&self, node_id: &str, drive: Option<&str>) -> PyResult { + crate::peer::sync_with(&self.db, node_id, drive) + } + + /// Remember a peer so auto-connect can find it later. + #[pyo3(signature = (node_id, name=""))] + fn add_peer(&self, node_id: &str, name: &str) -> PyResult<()> { + atomic_lib::sync::peer::add_known_peer(&self.db, node_id, name); + Ok(()) + } + + /// Peers persisted in this store. + fn peers(&self) -> Vec { + atomic_lib::sync::peer::get_known_peers(&self.db) + .into_iter() + .map(|p| crate::PeerInfo { + node_id: crate::peer::node_uri(&p.node_id), + name: p.name, + }) + .collect() + } + + /// Node URIs currently holding a live Iroh stream. + fn live_peers(&self) -> Vec { + let _ = self; + atomic_lib::sync::peer::live_peer_ids() + .into_iter() + .map(|id| crate::peer::node_uri(&id)) + .collect() + } + + /// Block until `subject` changes in this store (local or peer). + /// + /// Returns the subject, or `!{subject}` if it was destroyed. + #[pyo3(signature = (subject, timeout=30.0))] + fn wait_for(&self, subject: &str, timeout: f64) -> PyResult { + crate::peer::wait_for(&self.db, subject, timeout) + } + + #[getter] + fn device_name(&self) -> String { + atomic_lib::sync::peer::get_device_name(&self.db) + } + + #[setter] + fn set_device_name(&self, name: &str) { + atomic_lib::sync::peer::set_device_name(&self.db, name); + } + fn __enter__(slf: Py) -> Py { slf } diff --git a/python/tests/iroh_peer_child.py b/python/tests/iroh_peer_child.py new file mode 100644 index 0000000000..e8e7541c73 --- /dev/null +++ b/python/tests/iroh_peer_child.py @@ -0,0 +1,34 @@ +"""Second OS process for the Iroh P2P test. Invoked by test_iroh.py.""" + +import json +import sys + +from atomic_data import Store + + +def main() -> None: + handshake_path, store_path, done_path = sys.argv[1:4] + hs = json.loads(open(handshake_path, encoding="utf-8").read()) + store = Store.open(store_path) + loaded = store.load_agent(hs["secret"]) + if loaded.drive_needs_sync: + # Expected: this process has the agent but not the drive genesis yet. + pass + store.start_peer() + report = store.sync_with(hs["node_id"], drive=hs["drive"]) + got = store.get(hs["subject"]) + open(done_path, "w", encoding="utf-8").write( + json.dumps( + { + "ok": bool(got) and got["name"] == hs["name"], + "imported": report.imported, + "pushed": report.pushed, + "name": got["name"] if got else None, + "has_drive": store.has(hs["drive"]), + } + ) + ) + + +if __name__ == "__main__": + main() diff --git a/python/tests/test_iroh.py b/python/tests/test_iroh.py new file mode 100644 index 0000000000..cdcb74da86 --- /dev/null +++ b/python/tests/test_iroh.py @@ -0,0 +1,100 @@ +"""Iroh P2P — the product surface, not an optional extra. + +`peer::start` is process-global, so two nodes must be two OS processes. +""" + +import json +import subprocess +import sys +import time +from pathlib import Path + +import pytest + +from atomic_data import Store, urls + +CHILD = Path(__file__).parent / "iroh_peer_child.py" + + +def test_sync_with_requires_start_peer(): + store = Store.in_memory() + store.setup("Ada") + with pytest.raises(ValueError, match="start_peer"): + store.sync_with("did:ad:node:" + "ab" * 32) + + +def test_known_peers_persist_locally(): + store = Store.in_memory() + store.setup("Ada") + store.add_peer("did:ad:node:" + "cd" * 32, name="Phone") + peers = store.peers() + assert len(peers) == 1 + assert peers[0].name == "Phone" + assert peers[0].node_id.startswith("did:ad:node:") + + +def test_wait_for_times_out(): + store = Store.in_memory() + store.setup("Ada") + with pytest.raises(TimeoutError): + store.wait_for("did:ad:does-not-change", timeout=0.2) + + +def test_start_peer_returns_node_uri(): + store = Store.in_memory() + store.setup("Ada") + store.device_name = "pytest-a" + node = store.start_peer() + assert node.startswith("did:ad:node:") + assert store.peer_id == node + # Idempotent. + assert store.start_peer() == node + + +def test_two_process_iroh_sync(tmp_path): + path_a = tmp_path / "a" + path_b = tmp_path / "b" + handshake = tmp_path / "handshake.json" + done = tmp_path / "done.json" + + store = Store.open(str(path_a)) + setup = store.setup("Ada") + store.device_name = "pytest-parent" + node = store.start_peer() + note = store.create(urls.FOLDER, name="From A") + store.flush() + handshake.write_text( + json.dumps( + { + "node_id": node, + "secret": setup.agent_secret, + "drive": setup.drive_subject, + "subject": note.subject, + "name": "From A", + } + ), + encoding="utf-8", + ) + + child = subprocess.Popen( + [sys.executable, str(CHILD), str(handshake), str(path_b), str(done)], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + try: + deadline = time.time() + 90 + while time.time() < deadline and child.poll() is None and not done.exists(): + time.sleep(0.25) + if not done.exists(): + child.kill() + out, _ = child.communicate(timeout=10) + pytest.fail(f"child did not finish Iroh sync:\n{out}") + report = json.loads(done.read_text(encoding="utf-8")) + assert report["ok"], report + assert report["has_drive"] + assert report["imported"] >= 1 + finally: + if child.poll() is None: + child.kill() + child.wait(timeout=10) From c65f76a3a4387b5aa988de5fcd4863e98a49dcf4 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 16 Aug 2026 12:13:10 +0000 Subject: [PATCH 06/16] Fix wait_for async shape and lock Iroh deps for the Python crate. Co-authored-by: joepmeindertsma --- python/Cargo.lock | 2376 ++++++++++++++++++++++++++++++++++++++++++-- python/src/peer.rs | 44 +- 2 files changed, 2299 insertions(+), 121 deletions(-) diff --git a/python/Cargo.lock b/python/Cargo.lock index d914da8bd6..5516ff8869 100644 --- a/python/Cargo.lock +++ b/python/Cargo.lock @@ -2,12 +2,27 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "acto" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a026259da4f1a13b4af60cda453c392de64c58c12d239c560923e0382f42f2b9" +dependencies = [ + "parking_lot", + "pin-project-lite", + "rustc_version", + "smol_str", + "tokio", + "tracing", +] + [[package]] name = "aead" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ + "bytes", "crypto-common", "generic-array", ] @@ -34,6 +49,27 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + [[package]] name = "append-only-bytes" version = "0.1.12" @@ -79,6 +115,58 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ccd462b64c3c72f1be8305905a85d85403d768e8690c9b8bd3b9009a5761679" +[[package]] +name = "asn1-rs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "async-compat" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ba85bc55464dcbf728b56d97e119d673f4cf9062be330a9a26f3acf504a590" +dependencies = [ + "futures-core", + "futures-io", + "once_cell", + "pin-project-lite", + "tokio", +] + [[package]] name = "async-trait" version = "0.1.92" @@ -90,6 +178,17 @@ dependencies = [ "syn 3.0.3", ] +[[package]] +name = "async_io_stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" +dependencies = [ + "futures", + "pharos", + "rustc_version", +] + [[package]] name = "atomic-polyfill" version = "1.0.3" @@ -119,6 +218,7 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" name = "atomic_lib" version = "0.41.0-beta.2" dependencies = [ + "anyhow", "argon2", "async-trait", "base64", @@ -127,11 +227,14 @@ dependencies = [ "ed25519-dalek", "futures", "hex", + "hostname", + "iroh", "loro", + "pkarr", "rand 0.8.7", "redb", "regex", - "reqwest", + "reqwest 0.13.4", "rmp-serde", "serde", "serde_jcs", @@ -144,6 +247,18 @@ dependencies = [ "urlencoding", ] +[[package]] +name = "attohttpc" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9" +dependencies = [ + "base64", + "http", + "log", + "url", +] + [[package]] name = "autocfg" version = "1.5.1" @@ -173,6 +288,29 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "backon" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" +dependencies = [ + "fastrand", + "gloo-timers", + "tokio", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base32" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "022dfe9eb35f19ebbcb51e0b40a5ab759f46ad60cadf7297e0bd085afb50e076" + [[package]] name = "base64" version = "0.22.1" @@ -185,6 +323,12 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.13.1" @@ -232,6 +376,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bounded-integer" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "102dbef1187b1893e6dfe05a774e79fd52265f49f214f6879c8ff49f52c8188b" + [[package]] name = "bumpalo" version = "3.20.3" @@ -309,6 +459,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link 0.2.1", +] + [[package]] name = "cipher" version = "0.4.4" @@ -348,6 +510,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -360,6 +531,26 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" +[[package]] +name = "cordyceps" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9ab7e0ca1d179628fa0172b2b97203c7fa0cd81be2448bd446fb9559ca9261" +dependencies = [ + "loom", + "tracing", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -394,12 +585,51 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" + [[package]] name = "critical-section" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" +[[package]] +name = "crossbeam-channel" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" + [[package]] name = "crypto-common" version = "0.1.7" @@ -407,9 +637,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] +[[package]] +name = "crypto_box" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16182b4f39a82ec8a6851155cc4c0cda3065bb1db33651726a29e1951de0f009" +dependencies = [ + "aead", + "chacha20 0.9.1", + "crypto_secretbox", + "curve25519-dalek", + "salsa20", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto_secretbox" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d6cf87adf719ddf43a805e92c6870a531aedda35ff640442cbaf8674e141e1" +dependencies = [ + "aead", + "chacha20 0.9.1", + "cipher", + "generic-array", + "poly1305", + "salsa20", + "subtle", + "zeroize", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -421,7 +684,9 @@ dependencies = [ "curve25519-dalek-derive", "digest", "fiat-crypto", + "rand_core 0.6.4", "rustc_version", + "serde", "subtle", "zeroize", ] @@ -472,6 +737,12 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "data-encoding" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" + [[package]] name = "der" version = "0.7.10" @@ -479,9 +750,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", + "der_derive", + "pem-rfc7468", "zeroize", ] +[[package]] +name = "der-parser" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "der_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + [[package]] name = "derive_arbitrary" version = "1.4.2" @@ -493,6 +797,33 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "unicode-xid", +] + +[[package]] +name = "diatomic-waker" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab03c107fafeb3ee9f5925686dbb7a73bc76e3932abb0d2b365cb64b169cf04c" + [[package]] name = "diff" version = "0.1.13" @@ -521,12 +852,38 @@ dependencies = [ "syn 3.0.3", ] +[[package]] +name = "dlopen2" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b4f5f101177ff01b8ec4ecc81eead416a8aa42819a2869311b3420fa114ffa" +dependencies = [ + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + [[package]] name = "dunce" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "ed25519" version = "2.2.3" @@ -534,6 +891,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ "pkcs8", + "serde", "signature", ] @@ -613,29 +971,77 @@ dependencies = [ ] [[package]] -name = "equivalent" -version = "1.0.2" +name = "enumflags2" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", +] [[package]] -name = "fiat-crypto" -version = "0.2.9" +name = "enumflags2_derive" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] [[package]] -name = "find-msvc-tools" -version = "0.1.11" +name = "equivalent" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "fnv" +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-msvc-tools" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" + +[[package]] +name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -666,6 +1072,19 @@ dependencies = [ "futures-util", ] +[[package]] +name = "futures-buffered" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4421cb78ee172b6b06080093479d3c50f058e7c81b7d577bbb8d118d551d4cd5" +dependencies = [ + "cordyceps", + "diatomic-waker", + "futures-core", + "pin-project-lite", + "spin 0.10.1", +] + [[package]] name = "futures-channel" version = "0.3.34" @@ -699,6 +1118,19 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" +[[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.34" @@ -750,8 +1182,8 @@ dependencies = [ "libc", "log", "rustversion", - "windows-link", - "windows-result", + "windows-link 0.2.1", + "windows-result 0.4.1", ] [[package]] @@ -762,6 +1194,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -798,9 +1231,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 5.3.0", "wasip2", + "wasm-bindgen", ] [[package]] @@ -817,6 +1252,43 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "h2" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hash32" version = "0.2.1" @@ -835,12 +1307,29 @@ dependencies = [ "byteorder", ] +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + [[package]] name = "heapless" version = "0.7.17" @@ -851,7 +1340,7 @@ dependencies = [ "hash32 0.2.1", "rustc_version", "serde", - "spin", + "spin 0.9.9", "stable_deref_trait", ] @@ -893,6 +1382,94 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hickory-proto" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner 0.6.1", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "once_cell", + "rand 0.9.5", + "ring", + "thiserror 2.0.20", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "moka", + "once_cell", + "parking_lot", + "rand 0.9.5", + "resolv-conf", + "smallvec", + "thiserror 2.0.20", + "tokio", + "tracing", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "hmac-sha1" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b05da5b9e5d4720bfb691eebb2b9d42da3570745da71eac8a1f5bb7e59aab88" +dependencies = [ + "hmac", + "sha1", +] + +[[package]] +name = "hmac-sha256" +version = "1.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec9d92d097f4749b64e8cc33d924d9f40a2d4eb91402b458014b781f5733d60f" + +[[package]] +name = "hostname" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd" +dependencies = [ + "cfg-if", + "libc", + "windows-link 0.2.1", +] + +[[package]] +name = "hostname-validator" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f558a64ac9af88b5ba400d99b579451af0d39c6d360980045b91aac966d705e2" + [[package]] name = "http" version = "1.5.0" @@ -932,6 +1509,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "hyper" version = "1.11.0" @@ -942,9 +1525,11 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2", "http", "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -965,6 +1550,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", + "webpki-roots 1.0.9", ] [[package]] @@ -984,12 +1570,36 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.6.5", "tokio", "tower-service", "tracing", ] +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +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 = "icu_collections" version = "2.3.0" @@ -1100,6 +1710,27 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "igd-next" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516893339c97f6011282d5825ac94fc1c7aad5cad26bdc2d0cee068c0bf97f97" +dependencies = [ + "async-trait", + "attohttpc", + "bytes", + "futures", + "http", + "http-body-util", + "hyper", + "hyper-util", + "log", + "rand 0.9.5", + "tokio", + "url", + "xmltree", +] + [[package]] name = "im" version = "15.1.0" @@ -1115,6 +1746,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", +] + [[package]] name = "indoc" version = "2.0.7" @@ -1134,60 +1775,290 @@ dependencies = [ ] [[package]] -name = "ipnet" -version = "2.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" - -[[package]] -name = "itertools" -version = "0.11.0" +name = "instant" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ - "either", + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", ] [[package]] -name = "itertools" -version = "0.12.1" +name = "ipconfig" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" dependencies = [ - "either", + "socket2 0.6.5", + "widestring", + "windows-registry", + "windows-result 0.4.1", + "windows-sys 0.61.2", ] [[package]] -name = "itoa" -version = "1.0.18" +name = "ipnet" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" [[package]] -name = "jni" -version = "0.22.4" +name = "iroh" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +checksum = "6ca758f4ce39ae3f07de922be6c73de6a48a07f39554e78b5745585652ce38f5" dependencies = [ - "cfg-if", - "combine", - "jni-macros", - "jni-sys", - "log", - "simd_cesu8", + "aead", + "anyhow", + "atomic-waker", + "backon", + "bytes", + "cfg_aliases", + "concurrent-queue", + "crypto_box", + "data-encoding", + "der", + "derive_more", + "ed25519-dalek", + "futures-buffered", + "futures-util", + "getrandom 0.3.4", + "hickory-resolver", + "http", + "igd-next", + "instant", + "iroh-base", + "iroh-metrics", + "iroh-quinn", + "iroh-quinn-proto", + "iroh-quinn-udp", + "iroh-relay", + "n0-future", + "netdev", + "netwatch", + "pin-project", + "pkarr", + "portmapper", + "rand 0.8.7", + "rcgen", + "reqwest 0.12.28", + "ring", + "rustls", + "rustls-webpki 0.102.8", + "serde", + "smallvec", + "spki", + "strum", + "stun-rs", + "surge-ping", + "swarm-discovery", "thiserror 2.0.20", - "walkdir", - "windows-link", + "time", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", + "url", + "wasm-bindgen-futures", + "webpki-roots 0.26.11", + "x509-parser", + "z32", ] [[package]] -name = "jni-macros" -version = "0.22.4" +name = "iroh-base" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +checksum = "f91ac4aaab68153d726c4e6b39c30f9f9253743f0e25664e52f4caeb46f48d11" dependencies = [ - "proc-macro2", - "quote", + "curve25519-dalek", + "data-encoding", + "derive_more", + "ed25519-dalek", + "rand_core 0.6.4", + "serde", + "thiserror 2.0.20", + "url", +] + +[[package]] +name = "iroh-metrics" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f70466f14caff7420a14373676947e25e2917af6a5b1bec45825beb2bf1eb6a7" +dependencies = [ + "iroh-metrics-derive", + "itoa", + "serde", + "snafu", + "tracing", +] + +[[package]] +name = "iroh-metrics-derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d12f5c45c4ed2436302a4e03cad9a0ad34b2962ad0c5791e1019c0ee30eeb09" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "iroh-quinn" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76c6245c9ed906506ab9185e8d7f64857129aee4f935e899f398a3bd3b70338d" +dependencies = [ + "bytes", + "cfg_aliases", + "iroh-quinn-proto", + "iroh-quinn-udp", + "pin-project-lite", + "rustc-hash", + "rustls", + "socket2 0.5.10", + "thiserror 2.0.20", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "iroh-quinn-proto" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "929d5d8fa77d5c304d3ee7cae9aede31f13908bd049f9de8c7c0094ad6f7c535" +dependencies = [ + "bytes", + "getrandom 0.2.17", + "rand 0.8.7", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.20", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "iroh-quinn-udp" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c53afaa1049f7c83ea1331f5ebb9e6ebc5fdd69c468b7a22dd598b02c9bcc973" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.5.10", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "iroh-relay" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c63f122cdfaa4b4e0e7d6d3921d2b878f42a0c6d3ee5a29456dc3f5ab5ec931f" +dependencies = [ + "anyhow", + "bytes", + "cfg_aliases", + "data-encoding", + "derive_more", + "getrandom 0.3.4", + "hickory-resolver", + "http", + "http-body-util", + "hyper", + "hyper-util", + "iroh-base", + "iroh-metrics", + "iroh-quinn", + "iroh-quinn-proto", + "lru 0.12.5", + "n0-future", + "num_enum", + "pin-project", + "pkarr", + "postcard", + "rand 0.8.7", + "reqwest 0.12.28", + "rustls", + "rustls-webpki 0.102.8", + "serde", + "sha1", + "strum", + "stun-rs", + "thiserror 2.0.20", + "tokio", + "tokio-rustls", + "tokio-util", + "tokio-websockets", + "tracing", + "url", + "webpki-roots 0.26.11", + "ws_stream_wasm", + "z32", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror 2.0.20", + "walkdir", + "windows-link 0.2.1", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", "rustc_version", "simd_cesu8", "syn 2.0.119", @@ -1257,6 +2128,12 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + [[package]] name = "lock_api" version = "0.4.14" @@ -1425,6 +2302,21 @@ dependencies = [ "serde", ] +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "lru" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465" + [[package]] name = "lru-slab" version = "0.1.2" @@ -1470,6 +2362,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "mio" version = "1.2.2" @@ -1481,12 +2379,222 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "moka" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4293f18e7567a1caf3c584855554377025c65e0aa445344d04171f5ad63d19b9" +dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "parking_lot", + "portable-atomic", + "smallvec", + "tagptr", + "uuid", +] + +[[package]] +name = "n0-future" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb0e5d99e681ab3c938842b96fcb41bf8a7bb4bfdb11ccbd653a7e83e06c794" +dependencies = [ + "cfg_aliases", + "derive_more", + "futures-buffered", + "futures-lite", + "futures-util", + "js-sys", + "pin-project", + "send_wrapper", + "tokio", + "tokio-util", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-time", +] + +[[package]] +name = "nested_enum_utils" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d5475271bdd36a4a2769eac1ef88df0f99428ea43e52dfd8b0ee5cb674695f" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "netdev" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f901362e84cd407be6f8cd9d3a46bccf09136b095792785401ea7d283c79b91d" +dependencies = [ + "dlopen2", + "ipnet", + "libc", + "netlink-packet-core", + "netlink-packet-route 0.17.1", + "netlink-sys", + "once_cell", + "system-configuration", + "windows-sys 0.52.0", +] + +[[package]] +name = "netlink-packet-core" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" +dependencies = [ + "anyhow", + "byteorder", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-route" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66" +dependencies = [ + "anyhow", + "bitflags 1.3.2", + "byteorder", + "libc", + "netlink-packet-core", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-route" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0800eae8638a299eaa67476e1c6b6692922273e0f7939fd188fc861c837b9cd2" +dependencies = [ + "anyhow", + "bitflags 2.13.1", + "byteorder", + "libc", + "log", + "netlink-packet-core", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-utils" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" +dependencies = [ + "anyhow", + "byteorder", + "paste", + "thiserror 1.0.69", +] + +[[package]] +name = "netlink-proto" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72452e012c2f8d612410d89eea01e2d9b56205274abb35d53f60200b2ec41d60" +dependencies = [ + "bytes", + "futures", + "log", + "netlink-packet-core", + "netlink-sys", + "thiserror 2.0.20", +] + +[[package]] +name = "netlink-sys" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6c30ed10fa69cc491d491b85cc971f6bdeb8e7367b7cde2ee6cc878d583fae" +dependencies = [ + "bytes", + "futures-util", + "libc", + "log", + "tokio", +] + +[[package]] +name = "netwatch" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eeaa5f7505c93c5a9b35ba84fd21fb8aa3f24678c76acfe8716af7862fb07a" +dependencies = [ + "atomic-waker", + "bytes", + "cfg_aliases", + "derive_more", + "iroh-quinn-udp", + "js-sys", + "libc", + "n0-future", + "nested_enum_utils", + "netdev", + "netlink-packet-core", + "netlink-packet-route 0.23.0", + "netlink-proto", + "netlink-sys", + "serde", + "snafu", + "socket2 0.5.10", + "time", + "tokio", + "tokio-util", + "tracing", + "web-sys", + "windows", + "windows-result 0.3.4", + "wmi", +] + +[[package]] +name = "no-std-net" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" + +[[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 = "nonmax" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" +[[package]] +name = "ntimestamp" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50f94c405726d3e0095e89e72f75ce7f6587b94a8bd8dc8054b73f65c0fd68c" +dependencies = [ + "base32", + "document-features", + "getrandom 0.2.17", + "httpdate", + "js-sys", + "once_cell", + "serde", +] + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -1529,6 +2637,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + [[package]] name = "num-integer" version = "0.1.47" @@ -1568,11 +2682,46 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "oid-registry" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" +dependencies = [ + "asn1-rs", +] + [[package]] name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +dependencies = [ + "critical-section", + "portable-atomic", +] [[package]] name = "opaque-debug" @@ -1586,6 +2735,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" +[[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" @@ -1606,7 +2761,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -1620,6 +2775,31 @@ dependencies = [ "subtle", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -1668,12 +2848,73 @@ dependencies = [ "pest", ] +[[package]] +name = "pharos" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" +dependencies = [ + "futures", + "rustc_version", +] + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "pin-project-lite" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pkarr" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb1f2f4311bae1da11f930c804c724c9914cf55ae51a9ee0440fc98826984f7" +dependencies = [ + "async-compat", + "base32", + "bytes", + "cfg_aliases", + "document-features", + "dyn-clone", + "ed25519-dalek", + "futures-buffered", + "futures-lite", + "getrandom 0.2.17", + "log", + "lru 0.13.0", + "ntimestamp", + "reqwest 0.12.28", + "self_cell", + "serde", + "sha1_smol", + "simple-dns", + "thiserror 2.0.20", + "tokio", + "tracing", + "url", + "wasm-bindgen-futures", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -1690,6 +2931,48 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" +[[package]] +name = "pnet_base" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cf6fb3ab38b68d01ab2aea03ed3d1132b4868fa4e06285f29f16da01c5f4c" +dependencies = [ + "no-std-net", +] + +[[package]] +name = "pnet_macros" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688b17499eee04a0408aca0aa5cba5fc86401d7216de8a63fdf7a4c227871804" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.119", +] + +[[package]] +name = "pnet_macros_support" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eea925b72f4bd37f8eab0f221bbe4c78b63498350c983ffa9dd4bcde7e030f56" +dependencies = [ + "pnet_base", +] + +[[package]] +name = "pnet_packet" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a005825396b7fe7a38a8e288dbc342d5034dac80c15212436424fef8ea90ba" +dependencies = [ + "glob", + "pnet_base", + "pnet_macros", + "pnet_macros_support", +] + [[package]] name = "poly1305" version = "0.8.0" @@ -1707,6 +2990,37 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" +[[package]] +name = "portmapper" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d6db66007eac4a0ec8331d0d20c734bd64f6445d64bbaf0d0a27fea7a054e36" +dependencies = [ + "base64", + "bytes", + "derive_more", + "futures-lite", + "futures-util", + "hyper-util", + "igd-next", + "iroh-metrics", + "libc", + "nested_enum_utils", + "netwatch", + "num_enum", + "rand 0.8.7", + "serde", + "smallvec", + "snafu", + "socket2 0.5.10", + "time", + "tokio", + "tokio-util", + "tower-layer", + "tracing", + "url", +] + [[package]] name = "postcard" version = "1.1.3" @@ -1717,9 +3031,21 @@ dependencies = [ "embedded-io 0.4.0", "embedded-io 0.6.1", "heapless 0.7.17", + "postcard-derive", "serde", ] +[[package]] +name = "postcard-derive" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0232bd009a197ceec9cc881ba46f727fcd8060a2d8d6a9dde7a69030a6fe2bb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "potential_utf" version = "0.1.6" @@ -1729,6 +3055,12 @@ 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" @@ -1738,6 +3070,40 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "precis-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c2e7b31f132e0c6f8682cfb7bf4a5340dbe925b7986618d0826a56dfe0c8e56" +dependencies = [ + "precis-tools", + "ucd-parse", + "unicode-normalization", +] + +[[package]] +name = "precis-profiles" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e2768890a47af73a032af9f0cedbddce3c9d06cf8de201d5b8f2436ded7674" +dependencies = [ + "lazy_static", + "precis-core", + "precis-tools", + "unicode-normalization", +] + +[[package]] +name = "precis-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cc1eb2d5887ac7bfd2c0b745764db89edb84b856e4214e204ef48ef96d10c4a" +dependencies = [ + "lazy_static", + "regex", + "ucd-parse", +] + [[package]] name = "pretty_assertions" version = "1.4.1" @@ -1748,6 +3114,15 @@ dependencies = [ "yansi", ] +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.107" @@ -1827,7 +3202,7 @@ checksum = "b9c6658afe513a3b484e3abfdaa0d03ef3c0bbf017542c178dd55f94eb3051f9" dependencies = [ "ahash", "equivalent", - "hashbrown", + "hashbrown 0.16.1", "parking_lot", ] @@ -1844,7 +3219,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2", + "socket2 0.6.5", "thiserror 2.0.20", "tokio", "tracing", @@ -1883,7 +3258,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2", + "socket2 0.6.5", "tracing", "windows-sys 0.61.2", ] @@ -1897,6 +3272,16 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "quoted-string-parser" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc75379cdb451d001f1cb667a9f74e8b355e9df84cc5193513cbe62b96fc5e9" +dependencies = [ + "pest", + "pest_derive", +] + [[package]] name = "r-efi" version = "5.3.0" @@ -2003,6 +3388,19 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rcgen" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "yasna", +] + [[package]] name = "redb" version = "4.1.0" @@ -2018,7 +3416,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.13.1", ] [[package]] @@ -2044,12 +3442,59 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + [[package]] name = "regex-syntax" version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots 1.0.9", +] + [[package]] name = "reqwest" version = "0.13.4" @@ -2087,6 +3532,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "resolv-conf" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" + [[package]] name = "ring" version = "0.17.14" @@ -2135,6 +3586,15 @@ dependencies = [ "semver", ] +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + [[package]] name = "rustls" version = "0.23.43" @@ -2142,9 +3602,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" dependencies = [ "aws-lc-rs", + "log", "once_cell", + "ring", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.103.14", "subtle", "zeroize", ] @@ -2177,7 +3639,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "jni", "log", @@ -2185,7 +3647,7 @@ dependencies = [ "rustls", "rustls-native-certs", "rustls-platform-verifier-android", - "rustls-webpki", + "rustls-webpki 0.103.14", "security-framework", "security-framework-sys", "webpki-root-certs", @@ -2196,7 +3658,18 @@ dependencies = [ name = "rustls-platform-verifier-android" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] [[package]] name = "rustls-webpki" @@ -2216,12 +3689,27 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + [[package]] name = "ryu-js" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6518fc26bced4d53678a22d6e423e9d8716377def84545fe328236e3af070e7f" +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + [[package]] name = "same-file" version = "1.0.6" @@ -2258,8 +3746,8 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags", - "core-foundation", + "bitflags 2.13.1", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -2275,12 +3763,24 @@ dependencies = [ "libc", ] +[[package]] +name = "self_cell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab42ca02749e120097e328d91d415325bdf43b1c72c4c8badf37375fe40a813" + [[package]] name = "semver" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + [[package]] name = "serde" version = "1.0.229" @@ -2360,6 +3860,45 @@ dependencies = [ "zmij", ] +[[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 = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + [[package]] name = "sha2" version = "0.10.9" @@ -2386,6 +3925,16 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +[[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 = "signature" version = "2.2.0" @@ -2411,6 +3960,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" +[[package]] +name = "simple-dns" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee851d0e5e7af3721faea1843e8015e820a234f81fda3dea9247e15bac9a86a" +dependencies = [ + "bitflags 2.13.1", +] + [[package]] name = "sized-chunks" version = "0.6.5" @@ -2436,6 +3994,43 @@ dependencies = [ "serde", ] +[[package]] +name = "smol_str" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad6c857cbab2627dcf01ec85a623ca4e7dcb5691cbaa3d7fb7653671f0d09c9" + +[[package]] +name = "snafu" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" +dependencies = [ + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "socket2" version = "0.6.5" @@ -2455,6 +4050,12 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spin" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3" + [[package]] name = "spki" version = "0.7.3" @@ -2477,12 +4078,89 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.119", +] + +[[package]] +name = "stun-rs" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb921f10397d5669e1af6455e9e2d367bf1f9cebcd6b1dd1dc50e19f6a9ac2ac" +dependencies = [ + "base64", + "bounded-integer", + "byteorder", + "crc", + "enumflags2", + "fallible-iterator", + "hmac-sha1", + "hmac-sha256", + "hostname-validator", + "lazy_static", + "md5", + "paste", + "precis-core", + "precis-profiles", + "quoted-string-parser", + "rand 0.9.5", +] + [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "surge-ping" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30498e9c9feba213c3df6ed675bdf75519ccbee493517e7225305898c86cac05" +dependencies = [ + "hex", + "parking_lot", + "pnet_packet", + "rand 0.9.5", + "socket2 0.6.5", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "swarm-discovery" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3a95032b94c1dc318f55e0b130e3d2176cda022310a65c3df0092764ea69562" +dependencies = [ + "acto", + "anyhow", + "hickory-proto", + "rand 0.8.7", + "socket2 0.5.10", + "tokio", + "tracing", +] + [[package]] name = "syn" version = "1.0.109" @@ -2536,6 +4214,33 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + [[package]] name = "target-lexicon" version = "0.13.5" @@ -2591,6 +4296,37 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "js-sys", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.8.4" @@ -2611,45 +4347,125 @@ dependencies = [ ] [[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.53.1" +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.5", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "futures-util", + "libc", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-websockets" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fcaf159b4e7a376b05b5bfd77bfd38f3324f5fce751b4213bfc7eaa47affb4e" +dependencies = [ + "base64", + "bytes", + "futures-core", + "futures-sink", + "getrandom 0.3.4", + "http", + "httparse", + "rand 0.9.5", + "ring", + "rustls-pki-types", + "simdutf8", + "tokio", + "tokio-rustls", + "tokio-util", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ - "bytes", - "libc", - "mio", - "pin-project-lite", - "socket2", - "tokio-macros", - "windows-sys 0.61.2", + "serde_core", ] [[package]] -name = "tokio-macros" -version = "2.7.2" +name = "toml_edit" +version = "0.25.13+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.3", + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", ] [[package]] -name = "tokio-rustls" -version = "0.26.4" +name = "toml_parser" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" dependencies = [ - "rustls", - "tokio", + "winnow", ] [[package]] @@ -2673,7 +4489,7 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags", + "bitflags 2.13.1", "bytes", "futures-util", "http", @@ -2703,6 +4519,7 @@ 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", @@ -2776,6 +4593,15 @@ version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" +[[package]] +name = "ucd-parse" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06ff81122fcbf4df4c1660b15f7e3336058e7aec14437c9f85c6b31a0f279b9" +dependencies = [ + "regex-lite", +] + [[package]] name = "ucd-trie" version = "0.1.7" @@ -2798,6 +4624,21 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "unindent" version = "0.2.4" @@ -2830,6 +4671,7 @@ dependencies = [ "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] @@ -2844,6 +4686,17 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "uuid" +version = "1.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cefc03fd367c0c6d4305de1b312cf00248c4114f4a0418ce6a6af769e3b0bd9" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" @@ -2945,6 +4798,19 @@ 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.104" @@ -2974,6 +4840,46 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.9", +] + +[[package]] +name = "webpki-roots" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[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" @@ -2983,19 +4889,138 @@ 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 = "windows" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f919aee0a93304be7f62e8e5027811bbba96bcb1de84d6618be56e43f8a32a1" +dependencies = [ + "windows-core 0.59.0", + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-core" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "810ce18ed2112484b0d4e15d022e5f598113e220c53e373fb31e67e21670c1ce" +dependencies = [ + "windows-implement 0.59.0", + "windows-interface", + "windows-result 0.3.4", + "windows-strings 0.3.1", + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-implement" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[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.119", +] + +[[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.119", +] + +[[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-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[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", + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +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]] @@ -3004,7 +5029,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -3013,7 +5038,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -3022,14 +5047,31 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 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", + "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]] @@ -3038,60 +5080,183 @@ 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.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.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.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.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.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.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 = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen" version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" +[[package]] +name = "wmi" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7787dacdd8e71cbc104658aade4009300777f9b5fda6a75f19145fedb8a18e71" +dependencies = [ + "chrono", + "futures", + "log", + "serde", + "thiserror 2.0.20", + "windows", + "windows-core 0.59.0", +] + [[package]] name = "writeable" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" +[[package]] +name = "ws_stream_wasm" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c173014acad22e83f16403ee360115b38846fe754e735c5d9d3803fe70c6abc" +dependencies = [ + "async_io_stream", + "futures", + "js-sys", + "log", + "pharos", + "rustc_version", + "send_wrapper", + "thiserror 2.0.20", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "x509-parser" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + +[[package]] +name = "xml-rs" +version = "0.8.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7" + +[[package]] +name = "xmltree" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb" +dependencies = [ + "xml-rs", +] + [[package]] name = "xxhash-rust" version = "0.8.18" @@ -3104,6 +5269,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + [[package]] name = "yoke" version = "0.8.3" @@ -3127,6 +5301,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "z32" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2164e798d9e3d84ee2c91139ace54638059a3b23e361f5c11781c2c6459bde0f" + [[package]] name = "zerocopy" version = "0.8.56" diff --git a/python/src/peer.rs b/python/src/peer.rs index 307dfec440..0c8f86f81f 100644 --- a/python/src/peer.rs +++ b/python/src/peer.rs @@ -122,32 +122,30 @@ pub(crate) fn sync_with(db: &Db, node_id: &str, drive: Option<&str>) -> PyResult }) } +async fn recv_change(db: &Db, target: Subject) -> String { + let mut rx = db.subscribe_events(); + loop { + match rx.recv().await { + Ok(atomic_lib::DbEvent::Changed { subject, .. }) if subject == target => { + return subject.to_string(); + } + Ok(atomic_lib::DbEvent::Destroyed { subject, .. }) if subject == target => { + return format!("!{subject}"); + } + Ok(_) => continue, + Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => continue, + Err(_) => { + tokio::time::sleep(Duration::from_millis(50)).await; + rx = db.subscribe_events(); + } + } + } +} + pub(crate) fn wait_for(db: &Db, subject: &str, timeout: f64) -> PyResult { let target = Subject::from_raw(subject, db.get_base_domain().as_deref()).without_params(); - let mut rx = db.subscribe_events(); let dur = Duration::from_secs_f64(timeout.max(0.0)); - let result = block_on(async { - tokio::time::timeout(dur, async { - loop { - match rx.recv().await { - Ok(atomic_lib::DbEvent::Changed { subject, .. }) if subject == target => { - return subject.to_string(); - } - Ok(atomic_lib::DbEvent::Destroyed { subject, .. }) if subject == target => { - return format!("!{subject}"); - } - Ok(_) => continue, - Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => continue, - Err(_) => { - tokio::time::sleep(Duration::from_millis(50)).await; - rx = db.subscribe_events(); - } - } - } - }) - .await - }); - match result { + match block_on(tokio::time::timeout(dur, recv_change(db, target))) { Ok(s) => Ok(s), Err(_) => Err(PyTimeoutError::new_err(format!( "timed out waiting for {subject}" From e3c78a467ca4ce64de1dd89cd5ffdf34a1096f7e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 16 Aug 2026 12:17:18 +0000 Subject: [PATCH 07/16] Fix Iroh wait_for runtime and isolate the two-process sync test. start_peer is process-global; a prior in-memory start stole the node from the P2P test. wait_for must construct timeout inside block_on. Co-authored-by: joepmeindertsma --- python/src/peer.rs | 2 +- python/tests/iroh_peer_child.py | 3 +++ python/tests/test_iroh.py | 15 ++++----------- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/python/src/peer.rs b/python/src/peer.rs index 0c8f86f81f..8c1fe0e0c4 100644 --- a/python/src/peer.rs +++ b/python/src/peer.rs @@ -145,7 +145,7 @@ async fn recv_change(db: &Db, target: Subject) -> String { pub(crate) fn wait_for(db: &Db, subject: &str, timeout: f64) -> PyResult { let target = Subject::from_raw(subject, db.get_base_domain().as_deref()).without_params(); let dur = Duration::from_secs_f64(timeout.max(0.0)); - match block_on(tokio::time::timeout(dur, recv_change(db, target))) { + match block_on(async { tokio::time::timeout(dur, recv_change(db, target)).await }) { Ok(s) => Ok(s), Err(_) => Err(PyTimeoutError::new_err(format!( "timed out waiting for {subject}" diff --git a/python/tests/iroh_peer_child.py b/python/tests/iroh_peer_child.py index e8e7541c73..bb1f1b57ae 100644 --- a/python/tests/iroh_peer_child.py +++ b/python/tests/iroh_peer_child.py @@ -23,8 +23,11 @@ def main() -> None: "ok": bool(got) and got["name"] == hs["name"], "imported": report.imported, "pushed": report.pushed, + "in_sync": report.in_sync, + "peer_name": report.peer_name, "name": got["name"] if got else None, "has_drive": store.has(hs["drive"]), + "child_peer": store.peer_id, } ) ) diff --git a/python/tests/test_iroh.py b/python/tests/test_iroh.py index cdcb74da86..9c4f460193 100644 --- a/python/tests/test_iroh.py +++ b/python/tests/test_iroh.py @@ -40,18 +40,9 @@ def test_wait_for_times_out(): store.wait_for("did:ad:does-not-change", timeout=0.2) -def test_start_peer_returns_node_uri(): - store = Store.in_memory() - store.setup("Ada") - store.device_name = "pytest-a" - node = store.start_peer() - assert node.startswith("did:ad:node:") - assert store.peer_id == node - # Idempotent. - assert store.start_peer() == node - - def test_two_process_iroh_sync(tmp_path): + # `start_peer` is process-global. This must be the only test in this + # process that starts Iroh, and it must own the store the node serves. path_a = tmp_path / "a" path_b = tmp_path / "b" handshake = tmp_path / "handshake.json" @@ -61,6 +52,8 @@ def test_two_process_iroh_sync(tmp_path): setup = store.setup("Ada") store.device_name = "pytest-parent" node = store.start_peer() + assert node.startswith("did:ad:node:") + assert store.peer_id == node note = store.create(urls.FOLDER, name="From A") store.flush() handshake.write_text( From 03e24224027261e575fd2a7d9648ac35d5cbae3e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 16 Aug 2026 13:50:27 +0000 Subject: [PATCH 08/16] Add a Kotlin / UniFFI SDK over atomic_lib with Iroh P2P. Same product surface as the Python SDK: local redb, signed Loro commits, and process-global Iroh (startPeer / syncWith / live save). The ffi crate is excluded from the workspace. Resource.destroyResource avoids UniFFI's Disposable.destroy collision. JVM JUnit covers CRUD plus a two-process Iroh sync; Android AAR is later. Co-authored-by: joepmeindertsma --- .gitignore | 5 +- AGENTS.md | 2 + CHANGELOG.md | 1 + Cargo.toml | 2 +- README.md | 3 +- TESTING_COVERAGE.md | 8 + docs/src/SUMMARY.md | 1 + docs/src/atomic-server.md | 2 +- docs/src/kotlin.md | 93 + docs/src/tooling.md | 1 + ffi/.gitignore | 4 + ffi/Cargo.lock | 5697 +++++++++++++++++ ffi/Cargo.toml | 28 + ffi/README.md | 87 + ffi/generate-kotlin.sh | 10 + ffi/kotlin/build.gradle.kts | 35 + ffi/kotlin/gradle.properties | 2 + ffi/kotlin/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43764 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 + ffi/kotlin/gradlew | 185 + ffi/kotlin/settings.gradle.kts | 1 + .../src/main/kotlin/dev/atomicdata/Urls.kt | 48 + .../main/kotlin/dev/atomicdata/atomic_ffi.kt | 3141 +++++++++ .../kotlin/dev/atomicdata/IrohPeerChild.kt | 55 + .../test/kotlin/dev/atomicdata/IrohTest.kt | 122 + .../test/kotlin/dev/atomicdata/StoreTest.kt | 190 + ffi/src/bin/uniffi-bindgen.rs | 3 + ffi/src/convert.rs | 28 + ffi/src/lib.rs | 351 + ffi/src/peer.rs | 128 + ffi/src/resource.rs | 110 + ffi/src/store.rs | 258 + ffi/uniffi.toml | 3 + planning/README.md | 4 +- planning/SDK-API-design.md | 4 +- planning/kotlin-sdk.md | 84 +- planning/python-sdk.md | 6 +- python/README.md | 1 + 38 files changed, 10654 insertions(+), 56 deletions(-) create mode 100644 docs/src/kotlin.md create mode 100644 ffi/.gitignore create mode 100644 ffi/Cargo.lock create mode 100644 ffi/Cargo.toml create mode 100644 ffi/README.md create mode 100755 ffi/generate-kotlin.sh create mode 100644 ffi/kotlin/build.gradle.kts create mode 100644 ffi/kotlin/gradle.properties create mode 100644 ffi/kotlin/gradle/wrapper/gradle-wrapper.jar create mode 100644 ffi/kotlin/gradle/wrapper/gradle-wrapper.properties create mode 100755 ffi/kotlin/gradlew create mode 100644 ffi/kotlin/settings.gradle.kts create mode 100644 ffi/kotlin/src/main/kotlin/dev/atomicdata/Urls.kt create mode 100644 ffi/kotlin/src/main/kotlin/dev/atomicdata/atomic_ffi.kt create mode 100644 ffi/kotlin/src/test/kotlin/dev/atomicdata/IrohPeerChild.kt create mode 100644 ffi/kotlin/src/test/kotlin/dev/atomicdata/IrohTest.kt create mode 100644 ffi/kotlin/src/test/kotlin/dev/atomicdata/StoreTest.kt create mode 100644 ffi/src/bin/uniffi-bindgen.rs create mode 100644 ffi/src/convert.rs create mode 100644 ffi/src/lib.rs create mode 100644 ffi/src/peer.rs create mode 100644 ffi/src/resource.rs create mode 100644 ffi/src/store.rs create mode 100644 ffi/uniffi.toml diff --git a/.gitignore b/.gitignore index 123b1c172d..ed232ad8e9 100644 --- a/.gitignore +++ b/.gitignore @@ -48,10 +48,13 @@ node_modules/ # Generated integration certification evidence /artifacts/integration-certification/ -# Python SDK (excluded from the Cargo workspace, so it has its own target/) +# Python / UniFFI SDKs (excluded from the Cargo workspace, own target/) python/target python/.venv python/*.egg-info +ffi/target +ffi/kotlin/.gradle +ffi/kotlin/build **/__pycache__ **/.pytest_cache *.so diff --git a/AGENTS.md b/AGENTS.md index 6365de7c84..0f6789da05 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -108,6 +108,7 @@ Atomic Server is a graph database with real-time sync, built on **Loro CRDT** fo - **`data-browser`** (`browser/data-browser/`) — The web app (React + TipTap + Loro), feels similar to notion. See the related AGENTS.md - **`flutter/`** — Cross-platform canvas app (Android/iOS/Web). Uses `flutter_rust_bridge` to call `atomic_lib`. See `flutter/README.md` and `flutter/AGENTS.md`. - **`python/`** — Python SDK (`atomic_data`). PyO3 bindings over `atomic_lib` (local redb). Excluded from the Cargo workspace; build with `maturin`. See `python/README.md` and `planning/python-sdk.md`. +- **`ffi/`** — UniFFI crate (`atomic-ffi`) and Kotlin SDK (`dev.atomicdata`). Same local redb + Iroh surface as Python. Excluded from the Cargo workspace. See `ffi/README.md` and `planning/kotlin-sdk.md`. ### Data model @@ -283,6 +284,7 @@ cd browser && pnpm run -r build # Full workspace build cd browser && pnpm run test-e2e:light # Playwright @smoke (feature-branch CI) cd browser && pnpm run test-e2e # Full Playwright suite (develop / tags) cd python && maturin develop && pytest # Python SDK (excluded from workspace) +cd ffi && cargo test && cd kotlin && ./gradlew test # Kotlin / UniFFI SDK (excluded from workspace) ``` `atomic_lib`'s unit tests need the `db` feature — `hierarchy.rs`'s test module diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fcb33104b..c433334c34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -364,6 +364,7 @@ See [STATUS.md](server/STATUS.md) to learn more about which features will remain returns the `CommitResponse` instead of `()`. See `planning/runtime-boundary-decision.md`. - **Python SDK** (`python/`, import `atomic_data`): bindings over `atomic_lib` via PyO3. Local redb plus Iroh P2P (`start_peer`, `sync_with`, live push on save). No server required. +- **Kotlin SDK** (`ffi/`, package `dev.atomicdata`): UniFFI bindings over `atomic_lib`. Same local redb + Iroh surface as Python (`startPeer`, `syncWith`, live push on save). JVM tests included; Android AAR is later. ## [v0.41.0-beta.2] - 2026-08-01 diff --git a/Cargo.toml b/Cargo.toml index f0e24107b2..ea17ee393e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ members = [ "plugin-runtime", "tools/cargo-bin", ] -exclude = ["flutter/rust", "integrations/localthought/syncables", "python"] +exclude = ["flutter/rust", "integrations/localthought/syncables", "python", "ffi"] # Debuginfo dominates target/ size: with ~1460 deps (tauri, actix, iroh) the # default `debug = true` produces a multi-GB tree per build flavor, and this diff --git a/README.md b/README.md index 2f9b9f039c..527750bdf3 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ This repo also includes: - [`atomic-cli`](cli/README.md) terminal client. - [`flutter`](/flutter) a Dart / Flutter client for Atomic Data, plus AtomicCanvas, a collaborative infinite drawing canvas that syncs peer-to-peer between devices. - [`python`](/python) local-first Python SDK (`atomic_data`) wrapping `atomic_lib` via PyO3. +- [`ffi`](/ffi) UniFFI crate and Kotlin SDK (`dev.atomicdata`) wrapping `atomic_lib`. - [`docs`](docs/README.md) documentation / specification for Atomic Data ([docs.atomicdata.dev](https://docs.atomicdata.dev)). _Status: alpha. [Breaking changes](CHANGELOG.md) are expected until 1.0._ @@ -49,7 +50,7 @@ _Status: alpha. [Breaking changes](CHANGELOG.md) are expected until 1.0._ - 📲 **Invite and sharing system** with [Atomic Invites](https://docs.atomicdata.dev/invitations.html) - 🌐 **Embedded server** with support for HTTP / HTTPS / HTTP2.0 (TLS) and Built-in LetsEncrypt handshake. - 📱 **Runs on mobile**: `atomic_lib` compiles into Flutter apps through [flutter_rust_bridge](https://github.com/fzyzcjy/flutter_rust_bridge), so phones get the same local-first store, signing and peer sync as the browser, not a thin REST wrapper. See [`/flutter`](/flutter). -- 📚 **Libraries**: [Javascript / Typescript](https://www.npmjs.com/package/@tomic/lib), [React](https://www.npmjs.com/package/@tomic/react), [Svelte](https://www.npmjs.com/package/@tomic/svelte), [Rust](https://crates.io/crates/atomic-lib), a [Dart / Flutter client](/flutter/lib/atomic), and a [Python SDK](/python) (`atomic_data`) +- 📚 **Libraries**: [Javascript / Typescript](https://www.npmjs.com/package/@tomic/lib), [React](https://www.npmjs.com/package/@tomic/react), [Svelte](https://www.npmjs.com/package/@tomic/svelte), [Rust](https://crates.io/crates/atomic-lib), a [Dart / Flutter client](/flutter/lib/atomic), a [Python SDK](/python) (`atomic_data`), and a [Kotlin SDK](/ffi) (`dev.atomicdata`) https://user-images.githubusercontent.com/2183313/139728539-d69b899f-6f9b-44cb-a1b7-bbab68beac0c.mp4 diff --git a/TESTING_COVERAGE.md b/TESTING_COVERAGE.md index 827c8ac7d2..2e9d05e3e0 100644 --- a/TESTING_COVERAGE.md +++ b/TESTING_COVERAGE.md @@ -450,6 +450,7 @@ templates, and offline variants stay in the full suite. Policy: | Flutter Dart | `cd flutter && flutter test` | `flutterTest` | | Flutter Rust bridge | `cargo test --manifest-path flutter/rust/Cargo.toml` | `flutterTest` | | Python SDK | `cd python && maturin develop && pytest` | **not in CI** | +| Kotlin / UniFFI SDK | `cd ffi && cargo test && cd kotlin && ./gradlew test` | **not in CI** | CI runs `cargo nextest run --workspace --exclude atomic-server-tauri --no-default-features --features light`. Feature unification pulls in @@ -462,6 +463,9 @@ Two things worth knowing about the runners: `--manifest-path` step in `flutterTest`. - **`python/` is excluded from the workspace** for the same reason (PyO3). Tests are `pytest` after `maturin develop`; they are not in Dagger CI yet. +- **`ffi/` is excluded from the workspace** (UniFFI + Iroh). Rust tests are + `cargo test` in `ffi/`; JVM tests are `./gradlew test` in `ffi/kotlin`. + Not in Dagger CI yet. - **`.config/nextest.toml` sets `retries = 2`.** A flaky test passes CI silently. Check for `FLAKY` in nextest output, not just the summary line. @@ -651,6 +655,10 @@ One 13-line smoke test, never run in CI — the pipeline has no emulator. Glue: `pytest` after `maturin build` covers in-memory CRUD, file-backed reopen, and a two-process Iroh sync (`tests/test_iroh.py`). Not in Dagger CI. No WS session or blobs. +### 7c. Kotlin / UniFFI SDK (`ffi/`) + +Glue: `cargo test` in `ffi/` covers the same local CRUD + Iroh-guard cases as Python, in-process. `ffi/kotlin` JUnit covers the generated bindings plus a two-process Iroh sync (`IrohTest.twoProcessIrohSync`). Not in Dagger CI. No Android AAR, WS session, or blobs. `startPeer` is process-global — only one JVM test may start Iroh. + ### 8. Known residual races None outstanding. The concurrent-writer bug that lived here — a local edit diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index a3777e11dd..a38af3472f 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -48,6 +48,7 @@ - [CLI](rust-cli.md) - [Lib](rust-lib.md) - [Python](python.md) + - [Kotlin](kotlin.md) # Guides diff --git a/docs/src/atomic-server.md b/docs/src/atomic-server.md index 930fc1cfc6..8049f918e4 100644 --- a/docs/src/atomic-server.md +++ b/docs/src/atomic-server.md @@ -30,7 +30,7 @@ It's free, open source (MIT license), and has a ton of features: - 📲 **Invite and sharing system** with [Atomic Invites](https://docs.atomicdata.dev/invitations.html) - 🌐 **Embedded server** with support for HTTP / HTTPS / HTTP2.0 (TLS) and Built-in LetsEncrypt handshake. - 📱 **Runs on mobile**: `atomic_lib` compiles into Flutter apps through [flutter_rust_bridge](https://github.com/fzyzcjy/flutter_rust_bridge), so phones get the same local-first store, signing and peer sync as the browser — not a thin REST wrapper. -- 📚 **Libraries**: [Javascript / Typescript](https://www.npmjs.com/package/@tomic/lib), [React](https://www.npmjs.com/package/@tomic/react), [Svelte](https://www.npmjs.com/package/@tomic/svelte), [Rust](https://crates.io/crates/atomic-lib), a Dart / Flutter client, and a [Python SDK](python.md) +- 📚 **Libraries**: [Javascript / Typescript](https://www.npmjs.com/package/@tomic/lib), [React](https://www.npmjs.com/package/@tomic/react), [Svelte](https://www.npmjs.com/package/@tomic/svelte), [Rust](https://crates.io/crates/atomic-lib), a Dart / Flutter client, a [Python SDK](python.md), and a [Kotlin SDK](kotlin.md) ## Document undo and redo diff --git a/docs/src/kotlin.md b/docs/src/kotlin.md new file mode 100644 index 0000000000..c63768989f --- /dev/null +++ b/docs/src/kotlin.md @@ -0,0 +1,93 @@ +{{#title Kotlin SDK for Atomic Data}} + +# Kotlin SDK + +Local-first [Atomic Data](atomic-data-overview.md) for the JVM. The package +wraps [`atomic_lib`](rust-lib.md) through +[UniFFI](https://mozilla.github.io/uniffi-rs/) — the same Rust store the +browser (WASM), Flutter app, and [Python SDK](python.md) use. Reads and +writes go to a local [redb](https://github.com/cberner/redb) file. Edits are +signed Loro commits. A server is optional. + +Source: [`ffi/`](https://github.com/atomicdata-dev/atomic-server/tree/develop/ffi) +(crate `atomic-ffi`, Kotlin package `dev.atomicdata`). + +Python stays on PyO3. This UniFFI surface is what Swift and the Android +Binder host will share later. + +## Install + +From a checkout of this repo (needs a Rust toolchain and JDK 21): + +```bash +cd ffi +cargo build +./generate-kotlin.sh +cd kotlin && ./gradlew test +``` + +Point `jna.library.path` at `ffi/target/debug` so the JVM can load +`libatomic_ffi`. + +## Quick start + +```kotlin +import dev.atomicdata.Store +import dev.atomicdata.Urls + +val store = Store.open("./my-atomic-data") +val setup = store.setup("Ada") + +val note = store.create( + Urls.PLAIN_TEXT, + "Hello", + null, + mapOf("description" to "A locally stored note"), +) +note.set(Urls.DESCRIPTION, "Edited offline") +note.save() + +val got = store.get(note.subject()) +println("${got?.get("name")} ${got?.get("description")}") + +for (child in store.query(setup.driveSubject, null, null, null, null, 0u)) { + println("${child.subject()} ${child.name()}") +} + +store.flush() +``` + +`setup.agentSecret` is the only way to sign writes after you reopen the +store. Keep it. + +```kotlin +val store = Store.open("./my-atomic-data") +store.loadAgent(secret) +``` + +`Store.inMemory()` is the same API without a directory. + +`Resource.destroyResource()` deletes the resource. It is not named +`destroy()` because UniFFI already uses that for FFI handle teardown. + +## P2P sync (Iroh) + +```kotlin +val node = store.startPeer() // did:ad:node:… +store.announce(null) // optional pkarr publish +// On the other device, same agent secret (or a grant), then: +other.syncWith(node, null) +``` + +After the first sync, connected peers get live Loro updates on `.save()`. +`store.waitFor(subject, timeoutSecs)` blocks until a local or peer change +lands. + +Two Iroh nodes cannot share one JVM — `startPeer` is process-global, same +as Flutter and Python. Use two processes (or two devices). + +## What this is not (yet) + +WebSocket-to-server `SyncSession` is not wrapped. Blobs and history are not +wrapped. An Android AAR (`cargo-ndk`) and Binder host are later layers. +Iroh P2P is. diff --git a/docs/src/tooling.md b/docs/src/tooling.md index 74aceeb323..67d90eb4b9 100644 --- a/docs/src/tooling.md +++ b/docs/src/tooling.md @@ -10,6 +10,7 @@ Libraries and clients (all MIT licenced) that work great with [atomic-server](at - Client CLI (rust): [atomic-cli](rust-cli.md) for fetching & editing data - Rust library: [atomic-lib](rust-lib.md) powers `atomic-server` and `atomic-cli`, and can be used in other Rust projects ([docs.rs](https://docs.rs/atomic_lib/latest/atomic_lib/)) - Python SDK: [atomic-data](python.md) wraps `atomic_lib` via PyO3 (local redb store, no server required) +- Kotlin SDK: [dev.atomicdata](kotlin.md) wraps `atomic_lib` via UniFFI (local redb store, Iroh P2P) - [Raycast Extension](https://www.raycast.com/atomicdata-dev/atomic-data-browser): full-text search ## Want to add to this list? Some ideas for tooling diff --git a/ffi/.gitignore b/ffi/.gitignore new file mode 100644 index 0000000000..a0c39c536a --- /dev/null +++ b/ffi/.gitignore @@ -0,0 +1,4 @@ +/target +/kotlin/.gradle +/kotlin/build +/kotlin/.kotlin diff --git a/ffi/Cargo.lock b/ffi/Cargo.lock new file mode 100644 index 0000000000..d495ecdc01 --- /dev/null +++ b/ffi/Cargo.lock @@ -0,0 +1,5697 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "acto" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a026259da4f1a13b4af60cda453c392de64c58c12d239c560923e0382f42f2b9" +dependencies = [ + "parking_lot", + "pin-project-lite", + "rustc_version", + "smol_str", + "tokio", + "tracing", +] + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "bytes", + "crypto-common", + "generic-array", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "append-only-bytes" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac436601d6bdde674a0d7fb593e829ffe7b3387c351b356dd20e2d40f5bf3ee5" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures 0.2.17", + "password-hash", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "arref" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ccd462b64c3c72f1be8305905a85d85403d768e8690c9b8bd3b9009a5761679" + +[[package]] +name = "askama" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4744ed2eef2645831b441d8f5459689ade2ab27c854488fbab1fbe94fce1a7" +dependencies = [ + "askama_derive", + "itoa", + "percent-encoding", + "serde", + "serde_json", +] + +[[package]] +name = "askama_derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d661e0f57be36a5c14c48f78d09011e67e0cb618f269cca9f2fd8d15b68c46ac" +dependencies = [ + "askama_parser", + "basic-toml", + "memchr", + "proc-macro2", + "quote", + "rustc-hash", + "serde", + "serde_derive", + "syn 2.0.119", +] + +[[package]] +name = "askama_parser" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf315ce6524c857bb129ff794935cf6d42c82a6cff60526fe2a63593de4d0d4f" +dependencies = [ + "memchr", + "serde", + "serde_derive", + "winnow 0.7.15", +] + +[[package]] +name = "asn1-rs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "async-compat" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ba85bc55464dcbf728b56d97e119d673f4cf9062be330a9a26f3acf504a590" +dependencies = [ + "futures-core", + "futures-io", + "once_cell", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "async_io_stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" +dependencies = [ + "futures", + "pharos", + "rustc_version", +] + +[[package]] +name = "atomic-ffi" +version = "0.1.0" +dependencies = [ + "atomic_lib", + "tempfile", + "thiserror 2.0.20", + "tokio", + "uniffi", +] + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atomic_lib" +version = "0.41.0-beta.2" +dependencies = [ + "anyhow", + "argon2", + "async-trait", + "base64", + "blake3", + "chacha20poly1305", + "ed25519-dalek", + "futures", + "hex", + "hostname", + "iroh", + "loro", + "pkarr", + "rand 0.8.7", + "redb", + "regex", + "reqwest 0.13.4", + "rmp-serde", + "serde", + "serde_jcs", + "serde_json", + "sha2", + "tokio", + "tracing", + "ulid", + "url", + "urlencoding", +] + +[[package]] +name = "attohttpc" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9" +dependencies = [ + "base64", + "http", + "log", + "url", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "aws-lc-rs" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2b2dcc879c3bae0d371e77c99f2238400ef24ec001394befa67b6e543add9e" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09fae7be8bb3174e05c6afdb34199e6dc0c7c04ba9fa237b1967adfbde27483" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + +[[package]] +name = "backon" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" +dependencies = [ + "fastrand", + "gloo-timers", + "tokio", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base32" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "022dfe9eb35f19ebbcb51e0b40a5ab759f46ad60cadf7297e0bd085afb50e076" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "basic-toml" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "blake3" +version = "1.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ae7bad254120e9e4c63bafc385310756f90c484eac0e36b8317cf09cb92a77" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures 0.3.0", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bounded-integer" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "102dbef1187b1893e6dfe05a774e79fd52265f49f214f6879c8ff49f52c8188b" + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "camino" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb1307f12aa967b5a58416e87b3653360e0fd614a016b6e970db08fecbb1b80d" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.20", +] + +[[package]] +name = "cc" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20 0.9.1", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link 0.2.1", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "clap" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" +dependencies = [ + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.20", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "cordyceps" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9ab7e0ca1d179628fa0172b2b97203c7fa0cd81be2448bd446fb9559ca9261" +dependencies = [ + "loom", + "tracing", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-channel" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "crypto_box" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16182b4f39a82ec8a6851155cc4c0cda3065bb1db33651726a29e1951de0f009" +dependencies = [ + "aead", + "chacha20 0.9.1", + "crypto_secretbox", + "curve25519-dalek", + "salsa20", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto_secretbox" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d6cf87adf719ddf43a805e92c6870a531aedda35ff640442cbaf8674e141e1" +dependencies = [ + "aead", + "chacha20 0.9.1", + "cipher", + "generic-array", + "poly1305", + "salsa20", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rand_core 0.6.4", + "rustc_version", + "serde", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "data-encoding" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "der_derive", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "der-parser" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "der_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "unicode-xid", +] + +[[package]] +name = "diatomic-waker" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab03c107fafeb3ee9f5925686dbb7a73bc76e3932abb0d2b365cb64b169cf04c" + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "dlopen2" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b4f5f101177ff01b8ec4ecc81eead416a8aa42819a2869311b3420fa114ffa" +dependencies = [ + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "serde", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.6.4", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "either" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "ensure-cov" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33753185802e107b8fa907192af1f0eca13b1fb33327a59266d650fef29b2b4e" + +[[package]] +name = "enum-as-inner" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[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 = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-msvc-tools" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[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 = "fs-err" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" +dependencies = [ + "autocfg", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-buffered" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4421cb78ee172b6b06080093479d3c50f058e7c81b7d577bbb8d118d551d4cd5" +dependencies = [ + "cordyceps", + "diatomic-waker", + "futures-core", + "pin-project-lite", + "spin 0.10.1", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[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.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generator" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3b854b0e584ead1a33f18b2fcad7cf7be18b3875c78816b753639aa501513ae" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link 0.2.1", + "windows-result 0.4.1", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "generic-btree" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c1bce85c110ab718fd139e0cc89c51b63bd647b14a767e24bdfc77c83df79b" +dependencies = [ + "arref", + "heapless 0.9.3", + "itertools 0.11.0", + "loro-thunderdome", + "proc-macro2", + "rustc-hash", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasm-bindgen", +] + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "goblin" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b363a30c165f666402fe6a3024d3bec7ebc898f96a4a23bd1c99f8dbf3f4f47" +dependencies = [ + "log", + "plain", + "scroll", +] + +[[package]] +name = "h2" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32 0.2.1", + "rustc_version", + "serde", + "spin 0.9.9", + "stable_deref_trait", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32 0.3.1", + "stable_deref_trait", +] + +[[package]] +name = "heapless" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ba4bd83f9415b58b4ed8dc5714c76e626a105be4646c02630ad730ad3b5aa4" +dependencies = [ + "hash32 0.3.1", + "stable_deref_trait", +] + +[[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 = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hickory-proto" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner 0.6.1", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "once_cell", + "rand 0.9.5", + "ring", + "thiserror 2.0.20", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "moka", + "once_cell", + "parking_lot", + "rand 0.9.5", + "resolv-conf", + "smallvec", + "thiserror 2.0.20", + "tokio", + "tracing", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "hmac-sha1" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b05da5b9e5d4720bfb691eebb2b9d42da3570745da71eac8a1f5bb7e59aab88" +dependencies = [ + "hmac", + "sha1", +] + +[[package]] +name = "hmac-sha256" +version = "1.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec9d92d097f4749b64e8cc33d924d9f40a2d4eb91402b458014b781f5733d60f" + +[[package]] +name = "hostname" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd" +dependencies = [ + "cfg-if", + "libc", + "windows-link 0.2.1", +] + +[[package]] +name = "hostname-validator" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f558a64ac9af88b5ba400d99b579451af0d39c6d360980045b91aac966d705e2" + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +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 = "hyper" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots 1.0.9", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.5", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +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 = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92a7ed671a6aad807a8651a2e1782a6598fda9ce5185dd8158549e95a91c6428" +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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "igd-next" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516893339c97f6011282d5825ac94fc1c7aad5cad26bdc2d0cee068c0bf97f97" +dependencies = [ + "async-trait", + "attohttpc", + "bytes", + "futures", + "http", + "http-body-util", + "hyper", + "hyper-util", + "log", + "rand 0.9.5", + "tokio", + "url", + "xmltree", +] + +[[package]] +name = "im" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" +dependencies = [ + "bitmaps", + "rand_core 0.6.4", + "rand_xoshiro", + "serde", + "sized-chunks", + "typenum", + "version_check", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "ipconfig" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" +dependencies = [ + "socket2 0.6.5", + "widestring", + "windows-registry", + "windows-result 0.4.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "ipnet" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" + +[[package]] +name = "iroh" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ca758f4ce39ae3f07de922be6c73de6a48a07f39554e78b5745585652ce38f5" +dependencies = [ + "aead", + "anyhow", + "atomic-waker", + "backon", + "bytes", + "cfg_aliases", + "concurrent-queue", + "crypto_box", + "data-encoding", + "der", + "derive_more", + "ed25519-dalek", + "futures-buffered", + "futures-util", + "getrandom 0.3.4", + "hickory-resolver", + "http", + "igd-next", + "instant", + "iroh-base", + "iroh-metrics", + "iroh-quinn", + "iroh-quinn-proto", + "iroh-quinn-udp", + "iroh-relay", + "n0-future", + "netdev", + "netwatch", + "pin-project", + "pkarr", + "portmapper", + "rand 0.8.7", + "rcgen", + "reqwest 0.12.28", + "ring", + "rustls", + "rustls-webpki 0.102.8", + "serde", + "smallvec", + "spki", + "strum", + "stun-rs", + "surge-ping", + "swarm-discovery", + "thiserror 2.0.20", + "time", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", + "url", + "wasm-bindgen-futures", + "webpki-roots 0.26.11", + "x509-parser", + "z32", +] + +[[package]] +name = "iroh-base" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91ac4aaab68153d726c4e6b39c30f9f9253743f0e25664e52f4caeb46f48d11" +dependencies = [ + "curve25519-dalek", + "data-encoding", + "derive_more", + "ed25519-dalek", + "rand_core 0.6.4", + "serde", + "thiserror 2.0.20", + "url", +] + +[[package]] +name = "iroh-metrics" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f70466f14caff7420a14373676947e25e2917af6a5b1bec45825beb2bf1eb6a7" +dependencies = [ + "iroh-metrics-derive", + "itoa", + "serde", + "snafu", + "tracing", +] + +[[package]] +name = "iroh-metrics-derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d12f5c45c4ed2436302a4e03cad9a0ad34b2962ad0c5791e1019c0ee30eeb09" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "iroh-quinn" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76c6245c9ed906506ab9185e8d7f64857129aee4f935e899f398a3bd3b70338d" +dependencies = [ + "bytes", + "cfg_aliases", + "iroh-quinn-proto", + "iroh-quinn-udp", + "pin-project-lite", + "rustc-hash", + "rustls", + "socket2 0.5.10", + "thiserror 2.0.20", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "iroh-quinn-proto" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "929d5d8fa77d5c304d3ee7cae9aede31f13908bd049f9de8c7c0094ad6f7c535" +dependencies = [ + "bytes", + "getrandom 0.2.17", + "rand 0.8.7", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.20", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "iroh-quinn-udp" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c53afaa1049f7c83ea1331f5ebb9e6ebc5fdd69c468b7a22dd598b02c9bcc973" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.5.10", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "iroh-relay" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c63f122cdfaa4b4e0e7d6d3921d2b878f42a0c6d3ee5a29456dc3f5ab5ec931f" +dependencies = [ + "anyhow", + "bytes", + "cfg_aliases", + "data-encoding", + "derive_more", + "getrandom 0.3.4", + "hickory-resolver", + "http", + "http-body-util", + "hyper", + "hyper-util", + "iroh-base", + "iroh-metrics", + "iroh-quinn", + "iroh-quinn-proto", + "lru 0.12.5", + "n0-future", + "num_enum", + "pin-project", + "pkarr", + "postcard", + "rand 0.8.7", + "reqwest 0.12.28", + "rustls", + "rustls-webpki 0.102.8", + "serde", + "sha1", + "strum", + "stun-rs", + "thiserror 2.0.20", + "tokio", + "tokio-rustls", + "tokio-util", + "tokio-websockets", + "tracing", + "url", + "webpki-roots 0.26.11", + "ws_stream_wasm", + "z32", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror 2.0.20", + "walkdir", + "windows-link 0.2.1", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83bff1d572d6b9aeef67ddfc8448e4a3737909cb28e81f97c791b9018703e52" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[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.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "loro" +version = "1.13.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ea00f70c1acad4b7a05aaf72c1d9a671426021a1999ee821c77b56f0a15900" +dependencies = [ + "enum-as-inner 0.6.1", + "generic-btree", + "loro-common", + "loro-delta", + "loro-internal", + "loro-kv-store", + "rustc-hash", + "tracing", +] + +[[package]] +name = "loro-common" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b51042936156a1d537df8a38938ec8ed24b45de14530af933c1d02de39c9e056" +dependencies = [ + "arbitrary", + "enum-as-inner 0.6.1", + "leb128", + "loro-rle", + "nonmax", + "rustc-hash", + "serde", + "serde_columnar", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "loro-delta" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96f381e7fb5d4bf5605b4f4a9241f3adf61403ded978d757552004c8302cccbb" +dependencies = [ + "arrayvec", + "enum-as-inner 0.5.1", + "generic-btree", + "heapless 0.8.0", +] + +[[package]] +name = "loro-internal" +version = "1.13.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fec47acf8db115b7146da5e1d5b18d0cf58032e6012c0c1ee3ec3442d1e936f" +dependencies = [ + "append-only-bytes", + "arref", + "bytes", + "either", + "ensure-cov", + "enum-as-inner 0.6.1", + "enum_dispatch", + "generic-btree", + "getrandom 0.2.17", + "im", + "itertools 0.12.1", + "leb128", + "loom", + "loro-common", + "loro-delta", + "loro-kv-store", + "loro-rle", + "loro_fractional_index", + "md5", + "nonmax", + "num", + "num-traits", + "once_cell", + "parking_lot", + "pest", + "pest_derive", + "postcard", + "pretty_assertions", + "rand 0.8.7", + "rustc-hash", + "serde", + "serde_columnar", + "serde_json", + "smallvec", + "thiserror 1.0.69", + "thread_local", + "tracing", + "wasm-bindgen", + "xxhash-rust", +] + +[[package]] +name = "loro-kv-store" +version = "1.13.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "937f88e4a3d80d5853f5119753f5e5f68d4375726db7e9b3d6dac9db8167e8bf" +dependencies = [ + "bytes", + "ensure-cov", + "loro-common", + "lz4_flex", + "once_cell", + "quick_cache", + "rustc-hash", + "tracing", + "xxhash-rust", +] + +[[package]] +name = "loro-rle" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76400c3eea6bb39b013406acce964a8db39311534e308286c8d8721baba8ee20" +dependencies = [ + "append-only-bytes", + "num", + "smallvec", +] + +[[package]] +name = "loro-thunderdome" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3d053a135388e6b1df14e8af1212af5064746e9b87a06a345a7a779ee9695a" + +[[package]] +name = "loro_fractional_index" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aca7180674d0273ddf37049a5efcde4547fd5330d24abb7519bb9d9eb6780d5b" +dependencies = [ + "once_cell", + "rand 0.8.7", + "serde", +] + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "lru" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lz4_flex" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373f5eceeeab7925e0c1098212f2fbc4d416adec9d35051a6ab251e824c1854a" +dependencies = [ + "twox-hash", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "moka" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4293f18e7567a1caf3c584855554377025c65e0aa445344d04171f5ad63d19b9" +dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "parking_lot", + "portable-atomic", + "smallvec", + "tagptr", + "uuid", +] + +[[package]] +name = "n0-future" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb0e5d99e681ab3c938842b96fcb41bf8a7bb4bfdb11ccbd653a7e83e06c794" +dependencies = [ + "cfg_aliases", + "derive_more", + "futures-buffered", + "futures-lite", + "futures-util", + "js-sys", + "pin-project", + "send_wrapper", + "tokio", + "tokio-util", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-time", +] + +[[package]] +name = "nested_enum_utils" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d5475271bdd36a4a2769eac1ef88df0f99428ea43e52dfd8b0ee5cb674695f" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "netdev" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f901362e84cd407be6f8cd9d3a46bccf09136b095792785401ea7d283c79b91d" +dependencies = [ + "dlopen2", + "ipnet", + "libc", + "netlink-packet-core", + "netlink-packet-route 0.17.1", + "netlink-sys", + "once_cell", + "system-configuration", + "windows-sys 0.52.0", +] + +[[package]] +name = "netlink-packet-core" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" +dependencies = [ + "anyhow", + "byteorder", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-route" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66" +dependencies = [ + "anyhow", + "bitflags 1.3.2", + "byteorder", + "libc", + "netlink-packet-core", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-route" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0800eae8638a299eaa67476e1c6b6692922273e0f7939fd188fc861c837b9cd2" +dependencies = [ + "anyhow", + "bitflags 2.13.1", + "byteorder", + "libc", + "log", + "netlink-packet-core", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-utils" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" +dependencies = [ + "anyhow", + "byteorder", + "paste", + "thiserror 1.0.69", +] + +[[package]] +name = "netlink-proto" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72452e012c2f8d612410d89eea01e2d9b56205274abb35d53f60200b2ec41d60" +dependencies = [ + "bytes", + "futures", + "log", + "netlink-packet-core", + "netlink-sys", + "thiserror 2.0.20", +] + +[[package]] +name = "netlink-sys" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6c30ed10fa69cc491d491b85cc971f6bdeb8e7367b7cde2ee6cc878d583fae" +dependencies = [ + "bytes", + "futures-util", + "libc", + "log", + "tokio", +] + +[[package]] +name = "netwatch" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eeaa5f7505c93c5a9b35ba84fd21fb8aa3f24678c76acfe8716af7862fb07a" +dependencies = [ + "atomic-waker", + "bytes", + "cfg_aliases", + "derive_more", + "iroh-quinn-udp", + "js-sys", + "libc", + "n0-future", + "nested_enum_utils", + "netdev", + "netlink-packet-core", + "netlink-packet-route 0.23.0", + "netlink-proto", + "netlink-sys", + "serde", + "snafu", + "socket2 0.5.10", + "time", + "tokio", + "tokio-util", + "tracing", + "web-sys", + "windows", + "windows-result 0.3.4", + "wmi", +] + +[[package]] +name = "no-std-net" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" + +[[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 = "nonmax" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" + +[[package]] +name = "ntimestamp" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50f94c405726d3e0095e89e72f75ce7f6587b94a8bd8dc8054b73f65c0fd68c" +dependencies = [ + "base32", + "document-features", + "getrandom 0.2.17", + "httpdate", + "js-sys", + "once_cell", + "serde", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "oid-registry" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" +dependencies = [ + "asn1-rs", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +dependencies = [ + "critical-section", + "portable-atomic", +] + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[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 = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pest" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a07a60cc7a4d00c91f95c685609d1d2f79050e6804b70ebedd7650f0b839bcf" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a83744a5c8455b8b3e0dc5031362780a347c878bdd11584d1a8984228cc88d" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0cd3451aa3de60d4b9a1e736885e4dea6b31617598026f12256ad566d63304a" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "pest_meta" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e04d3a0849e241d7dfce834c83b1c5edc8622009e8dd51a12ba1927c32f05496" +dependencies = [ + "pest", +] + +[[package]] +name = "pharos" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" +dependencies = [ + "futures", + "rustc_version", +] + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkarr" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb1f2f4311bae1da11f930c804c724c9914cf55ae51a9ee0440fc98826984f7" +dependencies = [ + "async-compat", + "base32", + "bytes", + "cfg_aliases", + "document-features", + "dyn-clone", + "ed25519-dalek", + "futures-buffered", + "futures-lite", + "getrandom 0.2.17", + "log", + "lru 0.13.0", + "ntimestamp", + "reqwest 0.12.28", + "self_cell", + "serde", + "sha1_smol", + "simple-dns", + "thiserror 2.0.20", + "tokio", + "tracing", + "url", + "wasm-bindgen-futures", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "pnet_base" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cf6fb3ab38b68d01ab2aea03ed3d1132b4868fa4e06285f29f16da01c5f4c" +dependencies = [ + "no-std-net", +] + +[[package]] +name = "pnet_macros" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688b17499eee04a0408aca0aa5cba5fc86401d7216de8a63fdf7a4c227871804" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.119", +] + +[[package]] +name = "pnet_macros_support" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eea925b72f4bd37f8eab0f221bbe4c78b63498350c983ffa9dd4bcde7e030f56" +dependencies = [ + "pnet_base", +] + +[[package]] +name = "pnet_packet" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a005825396b7fe7a38a8e288dbc342d5034dac80c15212436424fef8ea90ba" +dependencies = [ + "glob", + "pnet_base", + "pnet_macros", + "pnet_macros_support", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "portmapper" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d6db66007eac4a0ec8331d0d20c734bd64f6445d64bbaf0d0a27fea7a054e36" +dependencies = [ + "base64", + "bytes", + "derive_more", + "futures-lite", + "futures-util", + "hyper-util", + "igd-next", + "iroh-metrics", + "libc", + "nested_enum_utils", + "netwatch", + "num_enum", + "rand 0.8.7", + "serde", + "smallvec", + "snafu", + "socket2 0.5.10", + "time", + "tokio", + "tokio-util", + "tower-layer", + "tracing", + "url", +] + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "heapless 0.7.17", + "postcard-derive", + "serde", +] + +[[package]] +name = "postcard-derive" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0232bd009a197ceec9cc881ba46f727fcd8060a2d8d6a9dde7a69030a6fe2bb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +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 = "precis-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c2e7b31f132e0c6f8682cfb7bf4a5340dbe925b7986618d0826a56dfe0c8e56" +dependencies = [ + "precis-tools", + "ucd-parse", + "unicode-normalization", +] + +[[package]] +name = "precis-profiles" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e2768890a47af73a032af9f0cedbddce3c9d06cf8de201d5b8f2436ded7674" +dependencies = [ + "lazy_static", + "precis-core", + "precis-tools", + "unicode-normalization", +] + +[[package]] +name = "precis-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cc1eb2d5887ac7bfd2c0b745764db89edb84b856e4214e204ef48ef96d10c4a" +dependencies = [ + "lazy_static", + "regex", + "ucd-parse", +] + +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick_cache" +version = "0.6.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9c6658afe513a3b484e3abfdaa0d03ef3c0bbf017542c178dd55f94eb3051f9" +dependencies = [ + "ahash", + "equivalent", + "hashbrown 0.16.1", + "parking_lot", +] + +[[package]] +name = "quinn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2 0.6.5", + "thiserror 2.0.20", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand 0.10.2", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.20", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.6.5", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "quoted-string-parser" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc75379cdb451d001f1cb667a9f74e8b355e9df84cc5193513cbe62b96fc5e9" +dependencies = [ + "pest", + "pest_derive", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20 0.10.1", + "getrandom 0.4.3", + "rand_core 0.10.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.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "rcgen" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "yasna", +] + +[[package]] +name = "redb" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e925444704b5f17d32bf42f5b6e2df050bceebc3dcd6e71cc73dafe8092e839" +dependencies = [ + "libc", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots 1.0.9", +] + +[[package]] +name = "reqwest" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +dependencies = [ + "base64", + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "resolv-conf" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rmp" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "rmp-serde" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" +dependencies = [ + "rmp", + "serde", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.103.14", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki 0.103.14", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0527518605e68109d875e248ea259b6758801cf165e4b2c2733ae3b51f12535a" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "ryu-js" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6518fc26bced4d53678a22d6e423e9d8716377def84545fe328236e3af070e7f" + +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + +[[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 = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scroll" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "self_cell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab42ca02749e120097e328d91d415325bdf43b1c72c4c8badf37375fe40a813" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_columnar" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a16e404f17b16d0273460350e29b02d76ba0d70f34afdc9a4fa034c97d6c6eb" +dependencies = [ + "itertools 0.11.0", + "postcard", + "serde", + "serde_columnar_derive", + "thiserror 1.0.69", +] + +[[package]] +name = "serde_columnar_derive" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45958fce4903f67e871fbf15ac78e289269b21ebd357d6fecacdba233629112e" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_jcs" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3a60f3fda61525e439ef6d67422118f11e986566997d9021c56867ad814a0aa" +dependencies = [ + "ryu-js", + "serde", + "serde_json", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[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 = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[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 = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "simple-dns" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee851d0e5e7af3721faea1843e8015e820a234f81fda3dea9247e15bac9a86a" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "sized-chunks" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +dependencies = [ + "bitmaps", + "typenum", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +dependencies = [ + "serde", +] + +[[package]] +name = "smawk" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8e2fb0f499abb4d162f2bedad68f5ef91a1682b5a03596ddb67efd37768d100" + +[[package]] +name = "smol_str" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad6c857cbab2627dcf01ec85a623ca4e7dcb5691cbaa3d7fb7653671f0d09c9" + +[[package]] +name = "snafu" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" +dependencies = [ + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spin" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[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 = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.119", +] + +[[package]] +name = "stun-rs" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb921f10397d5669e1af6455e9e2d367bf1f9cebcd6b1dd1dc50e19f6a9ac2ac" +dependencies = [ + "base64", + "bounded-integer", + "byteorder", + "crc", + "enumflags2", + "fallible-iterator", + "hmac-sha1", + "hmac-sha256", + "hostname-validator", + "lazy_static", + "md5", + "paste", + "precis-core", + "precis-profiles", + "quoted-string-parser", + "rand 0.9.5", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "surge-ping" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30498e9c9feba213c3df6ed675bdf75519ccbee493517e7225305898c86cac05" +dependencies = [ + "hex", + "parking_lot", + "pnet_packet", + "rand 0.9.5", + "socket2 0.6.5", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "swarm-discovery" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3a95032b94c1dc318f55e0b130e3d2176cda022310a65c3df0092764ea69562" +dependencies = [ + "acto", + "anyhow", + "hickory-proto", + "rand 0.8.7", + "socket2 0.5.10", + "tokio", + "tracing", +] + +[[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.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +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.119", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "smawk", +] + +[[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.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl 2.0.20", +] + +[[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.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "js-sys", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +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.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.5", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "futures-util", + "libc", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-websockets" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fcaf159b4e7a376b05b5bfd77bfd38f3324f5fce751b4213bfc7eaa47affb4e" +dependencies = [ + "base64", + "bytes", + "futures-core", + "futures-sink", + "getrandom 0.3.4", + "http", + "httparse", + "rand 0.9.5", + "ring", + "rustls-pki-types", + "simdutf8", + "tokio", + "tokio-rustls", + "tokio-util", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.13+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow 1.0.4", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow 1.0.4", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags 2.13.1", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[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.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "twox-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8464ec13c3691491391d9fce00f6416c9a48e46972f72d7865688be2080192c9" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "ucd-parse" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06ff81122fcbf4df4c1660b15f7e3336058e7aec14437c9f85c6b31a0f279b9" +dependencies = [ + "regex-lite", +] + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "ulid" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "470dbf6591da1b39d43c14523b2b469c86879a53e8b758c8e090a470fe7b1fbe" +dependencies = [ + "rand 0.9.5", + "web-time", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "uniffi" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3291800a6b06569f7d3e15bdb6dc235e0f0c8bd3eb07177f430057feb076415f" +dependencies = [ + "anyhow", + "camino", + "cargo_metadata", + "clap", + "uniffi_bindgen", + "uniffi_core", + "uniffi_macros", + "uniffi_pipeline", +] + +[[package]] +name = "uniffi_bindgen" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a04b99fa7796eaaa7b87976a0dbdd1178dc1ee702ea00aca2642003aef9b669e" +dependencies = [ + "anyhow", + "askama", + "camino", + "cargo_metadata", + "fs-err", + "glob", + "goblin", + "heck 0.5.0", + "indexmap", + "once_cell", + "serde", + "tempfile", + "textwrap", + "toml", + "uniffi_internal_macros", + "uniffi_meta", + "uniffi_pipeline", + "uniffi_udl", +] + +[[package]] +name = "uniffi_core" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38a9a27529ccff732f8efddb831b65b1e07f7dea3fd4cacd4a35a8c4b253b98" +dependencies = [ + "anyhow", + "bytes", + "once_cell", + "static_assertions", +] + +[[package]] +name = "uniffi_internal_macros" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09acd2ce09c777dd65ee97c251d33c8a972afc04873f1e3b21eb3492ade16933" +dependencies = [ + "anyhow", + "indexmap", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "uniffi_macros" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5596f178c4f7aafa1a501c4e0b96236a96bc2ef92bdb453d83e609dad0040152" +dependencies = [ + "camino", + "fs-err", + "once_cell", + "proc-macro2", + "quote", + "serde", + "syn 2.0.119", + "toml", + "uniffi_meta", +] + +[[package]] +name = "uniffi_meta" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beadc1f460eb2e209263c49c4f5b19e9a02e00a3b2b393f78ad10d766346ecff" +dependencies = [ + "anyhow", + "siphasher", + "uniffi_internal_macros", + "uniffi_pipeline", +] + +[[package]] +name = "uniffi_pipeline" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd76b3ac8a2d964ca9fce7df21c755afb4c77b054a85ad7a029ad179cc5abb8a" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap", + "tempfile", + "uniffi_internal_macros", +] + +[[package]] +name = "uniffi_udl" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4319cf905911d70d5b97ce0f46f101619a22e9a189c8c46d797a9955e9233716" +dependencies = [ + "anyhow", + "textwrap", + "uniffi_meta", + "weedle2", +] + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cefc03fd367c0c6d4305de1b312cf00248c4114f4a0418ce6a6af769e3b0bd9" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[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.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +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.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.9", +] + +[[package]] +name = "webpki-roots" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "weedle2" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "998d2c24ec099a87daf9467808859f9d82b61f1d9c9701251aea037f514eae0e" +dependencies = [ + "nom", +] + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[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 = "windows" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f919aee0a93304be7f62e8e5027811bbba96bcb1de84d6618be56e43f8a32a1" +dependencies = [ + "windows-core 0.59.0", + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-core" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "810ce18ed2112484b0d4e15d022e5f598113e220c53e373fb31e67e21670c1ce" +dependencies = [ + "windows-implement 0.59.0", + "windows-interface", + "windows-result 0.3.4", + "windows-strings 0.3.1", + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-implement" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[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.119", +] + +[[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.119", +] + +[[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-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[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.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +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.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[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.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.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_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.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.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.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.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.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.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.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wmi" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7787dacdd8e71cbc104658aade4009300777f9b5fda6a75f19145fedb8a18e71" +dependencies = [ + "chrono", + "futures", + "log", + "serde", + "thiserror 2.0.20", + "windows", + "windows-core 0.59.0", +] + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "ws_stream_wasm" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c173014acad22e83f16403ee360115b38846fe754e735c5d9d3803fe70c6abc" +dependencies = [ + "async_io_stream", + "futures", + "js-sys", + "log", + "pharos", + "rustc_version", + "send_wrapper", + "thiserror 2.0.20", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "x509-parser" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + +[[package]] +name = "xml-rs" +version = "0.8.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7" + +[[package]] +name = "xmltree" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb" +dependencies = [ + "xml-rs", +] + +[[package]] +name = "xxhash-rust" +version = "0.8.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aee1b19627c7c60102ab80d3a9cbe18de90bfe03bfa6c3715447681f0e8c8af6" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "z32" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2164e798d9e3d84ee2c91139ace54638059a3b23e361f5c11781c2c6459bde0f" + +[[package]] +name = "zerocopy" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b5c6b5976d66c1d703c4fd17d3f5e43c8cedaacf604961b171adc7130896d8" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47402523226a02bfe5230160dc3ccc089aa6f6f19e7fcbb4e6f824bbb1b4aa62" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml new file mode 100644 index 0000000000..98d1f1f7e1 --- /dev/null +++ b/ffi/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "atomic-ffi" +version = "0.1.0" +edition = "2021" +description = "UniFFI bindings for atomic_lib — Kotlin (and later Swift)" +license = "MIT" +publish = false + +[lib] +name = "atomic_ffi" +crate-type = ["cdylib", "staticlib", "lib"] + +[[bin]] +name = "uniffi-bindgen" +path = "src/bin/uniffi-bindgen.rs" + +[dependencies] +atomic_lib = { path = "../lib", default-features = false, features = [ + "db-redb", + "iroh", + "discovery", +] } +thiserror = "2" +tokio = { version = "1", features = ["rt-multi-thread", "macros", "time"] } +uniffi = { version = "0.29", features = ["cli"] } + +[dev-dependencies] +tempfile = "3" diff --git a/ffi/README.md b/ffi/README.md new file mode 100644 index 0000000000..3656d581ae --- /dev/null +++ b/ffi/README.md @@ -0,0 +1,87 @@ +# atomic-ffi / Kotlin SDK + +Local-first [Atomic Data](https://atomicdata.dev) for the JVM. This crate +exposes `atomic_lib` through [UniFFI](https://mozilla.github.io/uniffi-rs/). +Kotlin is the first generated language; Swift can use the same surface later. + +This is **not** an HTTP client. Reads and writes go to a local [redb] +database. Edits are signed Loro CRDT commits. Sync is Iroh: +`startPeer()`, hand the node URI to another device, `syncWith()`. + +[redb]: https://github.com/cberner/redb + +Python stays on PyO3 (`../python`). Do not clone this crate to add another +hand-rolled binding. + +The crate is **excluded from the Cargo workspace** (same reason as +`python/` and `flutter/rust`): workspace clippy / nextest should not compile +UniFFI + Iroh on every Rust CI run. + +## Layout + +```text +ffi/ + src/ UniFFI objects: Store, Resource, Iroh helpers + uniffi.toml package_name = dev.atomicdata + kotlin/ Gradle JVM project + generated bindings + generate-kotlin.sh cargo build + uniffi-bindgen +``` + +Android AAR / `cargo-ndk` is not in this tree yet. + +## Build + +Needs a Rust toolchain and JDK 21. + +```bash +cd ffi +cargo test # in-process Rust tests (no Iroh start) +./generate-kotlin.sh # refresh kotlin/src/main/kotlin/dev/atomicdata/atomic_ffi.kt +cd kotlin && ./gradlew test # JVM CRUD + two-process Iroh +``` + +`./gradlew test` expects `ffi/target/debug/libatomic_ffi.so` (or `.dylib` / +`.dll`) on `jna.library.path`. + +## Quick start + +```kotlin +import dev.atomicdata.Store +import dev.atomicdata.Urls + +val store = Store.open("./my-atomic-data") +val setup = store.setup("Ada") + +val note = store.create( + Urls.PLAIN_TEXT, + "Hello", + null, + mapOf("description" to "A locally stored note"), +) +note.set(Urls.DESCRIPTION, "Edited offline") +note.save() + +val got = store.get(note.subject()) +println("${got?.get("name")} ${got?.get("description")}") + +for (child in store.query(setup.driveSubject, null, null, null, null, 0u)) { + println("${child.subject()} ${child.name()}") +} + +store.flush() + +val node = store.startPeer() // did:ad:node:… +// other.syncWith(node, null) +``` + +`Resource.destroyResource()` deletes the resource. It is not named +`destroy()` because UniFFI already uses that for FFI handle teardown. + +Two Iroh nodes cannot share one process — `startPeer` is process-global, +same as Flutter and Python. + +## What this is not (yet) + +WebSocket-to-server `SyncSession` is not wrapped. Blobs and history are not +wrapped. Android AAR / Binder host is a later layer +(`planning/android-data-reuse.md`). Iroh P2P is. diff --git a/ffi/generate-kotlin.sh b/ffi/generate-kotlin.sh new file mode 100755 index 0000000000..0ae420f9ef --- /dev/null +++ b/ffi/generate-kotlin.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# Rebuild the cdylib and regenerate committed Kotlin bindings. +set -euo pipefail +cd "$(dirname "$0")" +cargo build +cargo run --bin uniffi-bindgen -- generate \ + --library target/debug/libatomic_ffi.so \ + --language kotlin \ + --out-dir kotlin/src/main/kotlin \ + --no-format diff --git a/ffi/kotlin/build.gradle.kts b/ffi/kotlin/build.gradle.kts new file mode 100644 index 0000000000..e9347a7863 --- /dev/null +++ b/ffi/kotlin/build.gradle.kts @@ -0,0 +1,35 @@ +plugins { + kotlin("jvm") version "2.0.21" +} + +group = "dev.atomicdata" +version = "0.1.0" + +repositories { + mavenCentral() +} + +dependencies { + implementation("net.java.dev.jna:jna:5.17.0") + testImplementation(kotlin("test")) +} + +kotlin { + jvmToolchain(21) +} + +val nativeLibDir = file("${rootProject.projectDir}/../target/debug") + +tasks.test { + useJUnitPlatform() + maxParallelForks = 1 + workingDir = rootProject.projectDir + systemProperty("jna.library.path", nativeLibDir.absolutePath) + systemProperty("java.library.path", nativeLibDir.absolutePath) + environment("LD_LIBRARY_PATH", nativeLibDir.absolutePath) + // Iroh two-process test talks to the n0 relay. + testLogging { + events("passed", "skipped", "failed") + exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL + } +} diff --git a/ffi/kotlin/gradle.properties b/ffi/kotlin/gradle.properties new file mode 100644 index 0000000000..f1e88d6a43 --- /dev/null +++ b/ffi/kotlin/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx1g +kotlin.code.style=official diff --git a/ffi/kotlin/gradle/wrapper/gradle-wrapper.jar b/ffi/kotlin/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..1b33c55baabb587c669f562ae36f953de2481846 GIT binary patch literal 43764 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/ffi/kotlin/settings.gradle.kts b/ffi/kotlin/settings.gradle.kts new file mode 100644 index 0000000000..699bad332d --- /dev/null +++ b/ffi/kotlin/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "atomic-data" diff --git a/ffi/kotlin/src/main/kotlin/dev/atomicdata/Urls.kt b/ffi/kotlin/src/main/kotlin/dev/atomicdata/Urls.kt new file mode 100644 index 0000000000..1344d1bae5 --- /dev/null +++ b/ffi/kotlin/src/main/kotlin/dev/atomicdata/Urls.kt @@ -0,0 +1,48 @@ +package dev.atomicdata + +/** + * Well-known Atomic Data class and property URLs. + * + * Same constants as `atomic_data.urls` in Python. These are plain strings so + * they can be used without loading the native library. + */ +object Urls { + const val CLASS = "https://atomicdata.dev/classes/Class" + const val PROPERTY = "https://atomicdata.dev/classes/Property" + const val AGENT = "https://atomicdata.dev/classes/Agent" + const val DRIVE = "https://atomicdata.dev/classes/Drive" + const val FOLDER = "https://atomicdata.dev/classes/Folder" + const val FILE = "https://atomicdata.dev/classes/File" + const val COMMIT = "https://atomicdata.dev/classes/Commit" + const val CHATROOM = "https://atomicdata.dev/classes/ChatRoom" + const val MESSAGE = "https://atomicdata.dev/classes/Message" + const val DOCUMENT_V2 = "https://atomicdata.dev/classes/DocumentV2" + const val TABLE = "https://atomicdata.dev/classes/Table" + const val TAG = "https://atomicdata.dev/classes/Tag" + const val PLAIN_TEXT = "https://atomicdata.dev/classes/PlainText" + const val FORK = "https://atomicdata.dev/classes/Fork" + const val ONTOLOGY = "https://atomicdata.dev/classes/Ontology" + const val BOOKMARK = "https://atomicdata.dev/classes/Bookmark" + + const val NAME = "https://atomicdata.dev/properties/name" + const val DESCRIPTION = "https://atomicdata.dev/properties/description" + const val SHORTNAME = "https://atomicdata.dev/properties/shortname" + const val IS_A = "https://atomicdata.dev/properties/isA" + const val PARENT = "https://atomicdata.dev/properties/parent" + const val READ = "https://atomicdata.dev/properties/read" + const val WRITE = "https://atomicdata.dev/properties/write" + const val CHILDREN = "https://atomicdata.dev/properties/children" + const val DRIVES = "https://atomicdata.dev/properties/drives" + const val DATATYPE = "https://atomicdata.dev/properties/datatype" + const val CLASSTYPE = "https://atomicdata.dev/properties/classtype" + const val REQUIRES = "https://atomicdata.dev/properties/requires" + const val RECOMMENDS = "https://atomicdata.dev/properties/recommends" + const val CREATED_AT = "https://atomicdata.dev/properties/createdAt" + const val CREATED_BY = "https://atomicdata.dev/properties/createdBy" + const val PUBLIC_KEY = "https://atomicdata.dev/properties/publicKey" + const val SUBJECT = "https://atomicdata.dev/properties/subject" + const val SIGNER = "https://atomicdata.dev/properties/signer" + const val DRIVE_PROP = "https://atomicdata.dev/properties/drive" + const val PERSONAL_DRIVE = "https://atomicdata.dev/properties/personalDrive" + const val LANGUAGE = "https://atomicdata.dev/properties/language" +} diff --git a/ffi/kotlin/src/main/kotlin/dev/atomicdata/atomic_ffi.kt b/ffi/kotlin/src/main/kotlin/dev/atomicdata/atomic_ffi.kt new file mode 100644 index 0000000000..86811102ac --- /dev/null +++ b/ffi/kotlin/src/main/kotlin/dev/atomicdata/atomic_ffi.kt @@ -0,0 +1,3141 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +@file:Suppress("NAME_SHADOWING") + +package dev.atomicdata + +// Common helper code. +// +// Ideally this would live in a separate .kt file where it can be unittested etc +// in isolation, and perhaps even published as a re-useable package. +// +// However, it's important that the details of how this helper code works (e.g. the +// way that different builtin types are passed across the FFI) exactly match what's +// expected by the Rust code on the other side of the interface. In practice right +// now that means coming from the exact some version of `uniffi` that was used to +// compile the Rust component. The easiest way to ensure this is to bundle the Kotlin +// helpers directly inline like we're doing here. + +import com.sun.jna.Library +import com.sun.jna.IntegerType +import com.sun.jna.Native +import com.sun.jna.Pointer +import com.sun.jna.Structure +import com.sun.jna.Callback +import com.sun.jna.ptr.* +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.nio.CharBuffer +import java.nio.charset.CodingErrorAction +import java.util.concurrent.atomic.AtomicLong +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.atomic.AtomicBoolean + +// This is a helper for safely working with byte buffers returned from the Rust code. +// A rust-owned buffer is represented by its capacity, its current length, and a +// pointer to the underlying data. + +/** + * @suppress + */ +@Structure.FieldOrder("capacity", "len", "data") +open class RustBuffer : Structure() { + // Note: `capacity` and `len` are actually `ULong` values, but JVM only supports signed values. + // When dealing with these fields, make sure to call `toULong()`. + @JvmField var capacity: Long = 0 + @JvmField var len: Long = 0 + @JvmField var data: Pointer? = null + + class ByValue: RustBuffer(), Structure.ByValue + class ByReference: RustBuffer(), Structure.ByReference + + internal fun setValue(other: RustBuffer) { + capacity = other.capacity + len = other.len + data = other.data + } + + companion object { + internal fun alloc(size: ULong = 0UL) = uniffiRustCall() { status -> + // Note: need to convert the size to a `Long` value to make this work with JVM. + UniffiLib.INSTANCE.ffi_atomic_ffi_rustbuffer_alloc(size.toLong(), status) + }.also { + if(it.data == null) { + throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})") + } + } + + internal fun create(capacity: ULong, len: ULong, data: Pointer?): RustBuffer.ByValue { + var buf = RustBuffer.ByValue() + buf.capacity = capacity.toLong() + buf.len = len.toLong() + buf.data = data + return buf + } + + internal fun free(buf: RustBuffer.ByValue) = uniffiRustCall() { status -> + UniffiLib.INSTANCE.ffi_atomic_ffi_rustbuffer_free(buf, status) + } + } + + @Suppress("TooGenericExceptionThrown") + fun asByteBuffer() = + this.data?.getByteBuffer(0, this.len.toLong())?.also { + it.order(ByteOrder.BIG_ENDIAN) + } +} + +/** + * The equivalent of the `*mut RustBuffer` type. + * Required for callbacks taking in an out pointer. + * + * Size is the sum of all values in the struct. + * + * @suppress + */ +class RustBufferByReference : ByReference(16) { + /** + * Set the pointed-to `RustBuffer` to the given value. + */ + fun setValue(value: RustBuffer.ByValue) { + // NOTE: The offsets are as they are in the C-like struct. + val pointer = getPointer() + pointer.setLong(0, value.capacity) + pointer.setLong(8, value.len) + pointer.setPointer(16, value.data) + } + + /** + * Get a `RustBuffer.ByValue` from this reference. + */ + fun getValue(): RustBuffer.ByValue { + val pointer = getPointer() + val value = RustBuffer.ByValue() + value.writeField("capacity", pointer.getLong(0)) + value.writeField("len", pointer.getLong(8)) + value.writeField("data", pointer.getLong(16)) + + return value + } +} + +// This is a helper for safely passing byte references into the rust code. +// It's not actually used at the moment, because there aren't many things that you +// can take a direct pointer to in the JVM, and if we're going to copy something +// then we might as well copy it into a `RustBuffer`. But it's here for API +// completeness. + +@Structure.FieldOrder("len", "data") +internal open class ForeignBytes : Structure() { + @JvmField var len: Int = 0 + @JvmField var data: Pointer? = null + + class ByValue : ForeignBytes(), Structure.ByValue +} +/** + * The FfiConverter interface handles converter types to and from the FFI + * + * All implementing objects should be public to support external types. When a + * type is external we need to import it's FfiConverter. + * + * @suppress + */ +public interface FfiConverter { + // Convert an FFI type to a Kotlin type + fun lift(value: FfiType): KotlinType + + // Convert an Kotlin type to an FFI type + fun lower(value: KotlinType): FfiType + + // Read a Kotlin type from a `ByteBuffer` + fun read(buf: ByteBuffer): KotlinType + + // Calculate bytes to allocate when creating a `RustBuffer` + // + // This must return at least as many bytes as the write() function will + // write. It can return more bytes than needed, for example when writing + // Strings we can't know the exact bytes needed until we the UTF-8 + // encoding, so we pessimistically allocate the largest size possible (3 + // bytes per codepoint). Allocating extra bytes is not really a big deal + // because the `RustBuffer` is short-lived. + fun allocationSize(value: KotlinType): ULong + + // Write a Kotlin type to a `ByteBuffer` + fun write(value: KotlinType, buf: ByteBuffer) + + // Lower a value into a `RustBuffer` + // + // This method lowers a value into a `RustBuffer` rather than the normal + // FfiType. It's used by the callback interface code. Callback interface + // returns are always serialized into a `RustBuffer` regardless of their + // normal FFI type. + fun lowerIntoRustBuffer(value: KotlinType): RustBuffer.ByValue { + val rbuf = RustBuffer.alloc(allocationSize(value)) + try { + val bbuf = rbuf.data!!.getByteBuffer(0, rbuf.capacity).also { + it.order(ByteOrder.BIG_ENDIAN) + } + write(value, bbuf) + rbuf.writeField("len", bbuf.position().toLong()) + return rbuf + } catch (e: Throwable) { + RustBuffer.free(rbuf) + throw e + } + } + + // Lift a value from a `RustBuffer`. + // + // This here mostly because of the symmetry with `lowerIntoRustBuffer()`. + // It's currently only used by the `FfiConverterRustBuffer` class below. + fun liftFromRustBuffer(rbuf: RustBuffer.ByValue): KotlinType { + val byteBuf = rbuf.asByteBuffer()!! + try { + val item = read(byteBuf) + if (byteBuf.hasRemaining()) { + throw RuntimeException("junk remaining in buffer after lifting, something is very wrong!!") + } + return item + } finally { + RustBuffer.free(rbuf) + } + } +} + +/** + * FfiConverter that uses `RustBuffer` as the FfiType + * + * @suppress + */ +public interface FfiConverterRustBuffer: FfiConverter { + override fun lift(value: RustBuffer.ByValue) = liftFromRustBuffer(value) + override fun lower(value: KotlinType) = lowerIntoRustBuffer(value) +} +// A handful of classes and functions to support the generated data structures. +// This would be a good candidate for isolating in its own ffi-support lib. + +internal const val UNIFFI_CALL_SUCCESS = 0.toByte() +internal const val UNIFFI_CALL_ERROR = 1.toByte() +internal const val UNIFFI_CALL_UNEXPECTED_ERROR = 2.toByte() + +@Structure.FieldOrder("code", "error_buf") +internal open class UniffiRustCallStatus : Structure() { + @JvmField var code: Byte = 0 + @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue() + + class ByValue: UniffiRustCallStatus(), Structure.ByValue + + fun isSuccess(): Boolean { + return code == UNIFFI_CALL_SUCCESS + } + + fun isError(): Boolean { + return code == UNIFFI_CALL_ERROR + } + + fun isPanic(): Boolean { + return code == UNIFFI_CALL_UNEXPECTED_ERROR + } + + companion object { + fun create(code: Byte, errorBuf: RustBuffer.ByValue): UniffiRustCallStatus.ByValue { + val callStatus = UniffiRustCallStatus.ByValue() + callStatus.code = code + callStatus.error_buf = errorBuf + return callStatus + } + } +} + +class InternalException(message: String) : kotlin.Exception(message) + +/** + * Each top-level error class has a companion object that can lift the error from the call status's rust buffer + * + * @suppress + */ +interface UniffiRustCallStatusErrorHandler { + fun lift(error_buf: RustBuffer.ByValue): E; +} + +// Helpers for calling Rust +// In practice we usually need to be synchronized to call this safely, so it doesn't +// synchronize itself + +// Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err +private inline fun uniffiRustCallWithError(errorHandler: UniffiRustCallStatusErrorHandler, callback: (UniffiRustCallStatus) -> U): U { + var status = UniffiRustCallStatus() + val return_value = callback(status) + uniffiCheckCallStatus(errorHandler, status) + return return_value +} + +// Check UniffiRustCallStatus and throw an error if the call wasn't successful +private fun uniffiCheckCallStatus(errorHandler: UniffiRustCallStatusErrorHandler, status: UniffiRustCallStatus) { + if (status.isSuccess()) { + return + } else if (status.isError()) { + throw errorHandler.lift(status.error_buf) + } else if (status.isPanic()) { + // when the rust code sees a panic, it tries to construct a rustbuffer + // with the message. but if that code panics, then it just sends back + // an empty buffer. + if (status.error_buf.len > 0) { + throw InternalException(FfiConverterString.lift(status.error_buf)) + } else { + throw InternalException("Rust panic") + } + } else { + throw InternalException("Unknown rust call status: $status.code") + } +} + +/** + * UniffiRustCallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR + * + * @suppress + */ +object UniffiNullRustCallStatusErrorHandler: UniffiRustCallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): InternalException { + RustBuffer.free(error_buf) + return InternalException("Unexpected CALL_ERROR") + } +} + +// Call a rust function that returns a plain value +private inline fun uniffiRustCall(callback: (UniffiRustCallStatus) -> U): U { + return uniffiRustCallWithError(UniffiNullRustCallStatusErrorHandler, callback) +} + +internal inline fun uniffiTraitInterfaceCall( + callStatus: UniffiRustCallStatus, + makeCall: () -> T, + writeReturn: (T) -> Unit, +) { + try { + writeReturn(makeCall()) + } catch(e: kotlin.Exception) { + callStatus.code = UNIFFI_CALL_UNEXPECTED_ERROR + callStatus.error_buf = FfiConverterString.lower(e.toString()) + } +} + +internal inline fun uniffiTraitInterfaceCallWithError( + callStatus: UniffiRustCallStatus, + makeCall: () -> T, + writeReturn: (T) -> Unit, + lowerError: (E) -> RustBuffer.ByValue +) { + try { + writeReturn(makeCall()) + } catch(e: kotlin.Exception) { + if (e is E) { + callStatus.code = UNIFFI_CALL_ERROR + callStatus.error_buf = lowerError(e) + } else { + callStatus.code = UNIFFI_CALL_UNEXPECTED_ERROR + callStatus.error_buf = FfiConverterString.lower(e.toString()) + } + } +} +// Map handles to objects +// +// This is used pass an opaque 64-bit handle representing a foreign object to the Rust code. +internal class UniffiHandleMap { + private val map = ConcurrentHashMap() + private val counter = java.util.concurrent.atomic.AtomicLong(0) + + val size: Int + get() = map.size + + // Insert a new object into the handle map and get a handle for it + fun insert(obj: T): Long { + val handle = counter.getAndAdd(1) + map.put(handle, obj) + return handle + } + + // Get an object from the handle map + fun get(handle: Long): T { + return map.get(handle) ?: throw InternalException("UniffiHandleMap.get: Invalid handle") + } + + // Remove an entry from the handlemap and get the Kotlin object back + fun remove(handle: Long): T { + return map.remove(handle) ?: throw InternalException("UniffiHandleMap: Invalid handle") + } +} + +// Contains loading, initialization code, +// and the FFI Function declarations in a com.sun.jna.Library. +@Synchronized +private fun findLibraryName(componentName: String): String { + val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride") + if (libOverride != null) { + return libOverride + } + return "atomic_ffi" +} + +private inline fun loadIndirect( + componentName: String +): Lib { + return Native.load(findLibraryName(componentName), Lib::class.java) +} + +// Define FFI callback types +internal interface UniffiRustFutureContinuationCallback : com.sun.jna.Callback { + fun callback(`data`: Long,`pollResult`: Byte,) +} +internal interface UniffiForeignFutureFree : com.sun.jna.Callback { + fun callback(`handle`: Long,) +} +internal interface UniffiCallbackInterfaceFree : com.sun.jna.Callback { + fun callback(`handle`: Long,) +} +@Structure.FieldOrder("handle", "free") +internal open class UniffiForeignFuture( + @JvmField internal var `handle`: Long = 0.toLong(), + @JvmField internal var `free`: UniffiForeignFutureFree? = null, +) : Structure() { + class UniffiByValue( + `handle`: Long = 0.toLong(), + `free`: UniffiForeignFutureFree? = null, + ): UniffiForeignFuture(`handle`,`free`,), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFuture) { + `handle` = other.`handle` + `free` = other.`free` + } + +} +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructU8( + @JvmField internal var `returnValue`: Byte = 0.toByte(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Byte = 0.toByte(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ): UniffiForeignFutureStructU8(`returnValue`,`callStatus`,), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructU8) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } + +} +internal interface UniffiForeignFutureCompleteU8 : com.sun.jna.Callback { + fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructU8.UniffiByValue,) +} +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructI8( + @JvmField internal var `returnValue`: Byte = 0.toByte(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Byte = 0.toByte(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ): UniffiForeignFutureStructI8(`returnValue`,`callStatus`,), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructI8) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } + +} +internal interface UniffiForeignFutureCompleteI8 : com.sun.jna.Callback { + fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructI8.UniffiByValue,) +} +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructU16( + @JvmField internal var `returnValue`: Short = 0.toShort(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Short = 0.toShort(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ): UniffiForeignFutureStructU16(`returnValue`,`callStatus`,), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructU16) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } + +} +internal interface UniffiForeignFutureCompleteU16 : com.sun.jna.Callback { + fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructU16.UniffiByValue,) +} +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructI16( + @JvmField internal var `returnValue`: Short = 0.toShort(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Short = 0.toShort(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ): UniffiForeignFutureStructI16(`returnValue`,`callStatus`,), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructI16) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } + +} +internal interface UniffiForeignFutureCompleteI16 : com.sun.jna.Callback { + fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructI16.UniffiByValue,) +} +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructU32( + @JvmField internal var `returnValue`: Int = 0, + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Int = 0, + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ): UniffiForeignFutureStructU32(`returnValue`,`callStatus`,), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructU32) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } + +} +internal interface UniffiForeignFutureCompleteU32 : com.sun.jna.Callback { + fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructU32.UniffiByValue,) +} +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructI32( + @JvmField internal var `returnValue`: Int = 0, + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Int = 0, + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ): UniffiForeignFutureStructI32(`returnValue`,`callStatus`,), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructI32) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } + +} +internal interface UniffiForeignFutureCompleteI32 : com.sun.jna.Callback { + fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructI32.UniffiByValue,) +} +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructU64( + @JvmField internal var `returnValue`: Long = 0.toLong(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Long = 0.toLong(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ): UniffiForeignFutureStructU64(`returnValue`,`callStatus`,), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructU64) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } + +} +internal interface UniffiForeignFutureCompleteU64 : com.sun.jna.Callback { + fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructU64.UniffiByValue,) +} +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructI64( + @JvmField internal var `returnValue`: Long = 0.toLong(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Long = 0.toLong(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ): UniffiForeignFutureStructI64(`returnValue`,`callStatus`,), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructI64) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } + +} +internal interface UniffiForeignFutureCompleteI64 : com.sun.jna.Callback { + fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructI64.UniffiByValue,) +} +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructF32( + @JvmField internal var `returnValue`: Float = 0.0f, + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Float = 0.0f, + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ): UniffiForeignFutureStructF32(`returnValue`,`callStatus`,), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructF32) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } + +} +internal interface UniffiForeignFutureCompleteF32 : com.sun.jna.Callback { + fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructF32.UniffiByValue,) +} +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructF64( + @JvmField internal var `returnValue`: Double = 0.0, + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Double = 0.0, + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ): UniffiForeignFutureStructF64(`returnValue`,`callStatus`,), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructF64) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } + +} +internal interface UniffiForeignFutureCompleteF64 : com.sun.jna.Callback { + fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructF64.UniffiByValue,) +} +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructPointer( + @JvmField internal var `returnValue`: Pointer = Pointer.NULL, + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: Pointer = Pointer.NULL, + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ): UniffiForeignFutureStructPointer(`returnValue`,`callStatus`,), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructPointer) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } + +} +internal interface UniffiForeignFutureCompletePointer : com.sun.jna.Callback { + fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructPointer.UniffiByValue,) +} +@Structure.FieldOrder("returnValue", "callStatus") +internal open class UniffiForeignFutureStructRustBuffer( + @JvmField internal var `returnValue`: RustBuffer.ByValue = RustBuffer.ByValue(), + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `returnValue`: RustBuffer.ByValue = RustBuffer.ByValue(), + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ): UniffiForeignFutureStructRustBuffer(`returnValue`,`callStatus`,), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructRustBuffer) { + `returnValue` = other.`returnValue` + `callStatus` = other.`callStatus` + } + +} +internal interface UniffiForeignFutureCompleteRustBuffer : com.sun.jna.Callback { + fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructRustBuffer.UniffiByValue,) +} +@Structure.FieldOrder("callStatus") +internal open class UniffiForeignFutureStructVoid( + @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), +) : Structure() { + class UniffiByValue( + `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), + ): UniffiForeignFutureStructVoid(`callStatus`,), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiForeignFutureStructVoid) { + `callStatus` = other.`callStatus` + } + +} +internal interface UniffiForeignFutureCompleteVoid : com.sun.jna.Callback { + fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructVoid.UniffiByValue,) +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +// For large crates we prevent `MethodTooLargeException` (see #2340) +// N.B. the name of the extension is very misleading, since it is +// rather `InterfaceTooLargeException`, caused by too many methods +// in the interface for large crates. +// +// By splitting the otherwise huge interface into two parts +// * UniffiLib +// * IntegrityCheckingUniffiLib (this) +// we allow for ~2x as many methods in the UniffiLib interface. +// +// The `ffi_uniffi_contract_version` method and all checksum methods are put +// into `IntegrityCheckingUniffiLib` and these methods are called only once, +// when the library is loaded. +internal interface IntegrityCheckingUniffiLib : Library { + // Integrity check functions only + fun uniffi_atomic_ffi_checksum_func_url_description( +): Short +fun uniffi_atomic_ffi_checksum_func_url_drive( +): Short +fun uniffi_atomic_ffi_checksum_func_url_folder( +): Short +fun uniffi_atomic_ffi_checksum_func_url_is_a( +): Short +fun uniffi_atomic_ffi_checksum_func_url_name( +): Short +fun uniffi_atomic_ffi_checksum_func_url_parent( +): Short +fun uniffi_atomic_ffi_checksum_func_url_plain_text( +): Short +fun uniffi_atomic_ffi_checksum_method_resource_contains( +): Short +fun uniffi_atomic_ffi_checksum_method_resource_destroy_resource( +): Short +fun uniffi_atomic_ffi_checksum_method_resource_get( +): Short +fun uniffi_atomic_ffi_checksum_method_resource_keys( +): Short +fun uniffi_atomic_ffi_checksum_method_resource_name( +): Short +fun uniffi_atomic_ffi_checksum_method_resource_save( +): Short +fun uniffi_atomic_ffi_checksum_method_resource_set( +): Short +fun uniffi_atomic_ffi_checksum_method_resource_set_name( +): Short +fun uniffi_atomic_ffi_checksum_method_resource_subject( +): Short +fun uniffi_atomic_ffi_checksum_method_resource_to_json( +): Short +fun uniffi_atomic_ffi_checksum_method_store_active_drive( +): Short +fun uniffi_atomic_ffi_checksum_method_store_add_peer( +): Short +fun uniffi_atomic_ffi_checksum_method_store_agent( +): Short +fun uniffi_atomic_ffi_checksum_method_store_announce( +): Short +fun uniffi_atomic_ffi_checksum_method_store_create( +): Short +fun uniffi_atomic_ffi_checksum_method_store_create_agent( +): Short +fun uniffi_atomic_ffi_checksum_method_store_create_drive( +): Short +fun uniffi_atomic_ffi_checksum_method_store_delete( +): Short +fun uniffi_atomic_ffi_checksum_method_store_device_name( +): Short +fun uniffi_atomic_ffi_checksum_method_store_drives( +): Short +fun uniffi_atomic_ffi_checksum_method_store_flush( +): Short +fun uniffi_atomic_ffi_checksum_method_store_get( +): Short +fun uniffi_atomic_ffi_checksum_method_store_has( +): Short +fun uniffi_atomic_ffi_checksum_method_store_live_peers( +): Short +fun uniffi_atomic_ffi_checksum_method_store_load_agent( +): Short +fun uniffi_atomic_ffi_checksum_method_store_peer_id( +): Short +fun uniffi_atomic_ffi_checksum_method_store_peers( +): Short +fun uniffi_atomic_ffi_checksum_method_store_query( +): Short +fun uniffi_atomic_ffi_checksum_method_store_set_active_drive( +): Short +fun uniffi_atomic_ffi_checksum_method_store_set_device_name( +): Short +fun uniffi_atomic_ffi_checksum_method_store_setup( +): Short +fun uniffi_atomic_ffi_checksum_method_store_start_peer( +): Short +fun uniffi_atomic_ffi_checksum_method_store_sync_with( +): Short +fun uniffi_atomic_ffi_checksum_method_store_wait_for( +): Short +fun uniffi_atomic_ffi_checksum_constructor_store_in_memory( +): Short +fun uniffi_atomic_ffi_checksum_constructor_store_open( +): Short +fun ffi_atomic_ffi_uniffi_contract_version( +): Int + +} + +// A JNA Library to expose the extern-C FFI definitions. +// This is an implementation detail which will be called internally by the public API. +internal interface UniffiLib : Library { + companion object { + internal val INSTANCE: UniffiLib by lazy { + val componentName = "atomic_ffi" + // For large crates we prevent `MethodTooLargeException` (see #2340) + // N.B. the name of the extension is very misleading, since it is + // rather `InterfaceTooLargeException`, caused by too many methods + // in the interface for large crates. + // + // By splitting the otherwise huge interface into two parts + // * UniffiLib (this) + // * IntegrityCheckingUniffiLib + // And all checksum methods are put into `IntegrityCheckingUniffiLib` + // we allow for ~2x as many methods in the UniffiLib interface. + // + // Thus we first load the library with `loadIndirect` as `IntegrityCheckingUniffiLib` + // so that we can (optionally!) call `uniffiCheckApiChecksums`... + loadIndirect(componentName) + .also { lib: IntegrityCheckingUniffiLib -> + uniffiCheckContractApiVersion(lib) + uniffiCheckApiChecksums(lib) + } + // ... and then we load the library as `UniffiLib` + // N.B. we cannot use `loadIndirect` once and then try to cast it to `UniffiLib` + // => results in `java.lang.ClassCastException: com.sun.proxy.$Proxy cannot be cast to ...` + // error. So we must call `loadIndirect` twice. For crates large enough + // to trigger this issue, the performance impact is negligible, running on + // a macOS M1 machine the `loadIndirect` call takes ~50ms. + val lib = loadIndirect(componentName) + // No need to check the contract version and checksums, since + // we already did that with `IntegrityCheckingUniffiLib` above. + // Loading of library with integrity check done. + lib + } + + // The Cleaner for the whole library + internal val CLEANER: UniffiCleaner by lazy { + UniffiCleaner.create() + } + } + + // FFI functions + fun uniffi_atomic_ffi_fn_clone_resource(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, +): Pointer +fun uniffi_atomic_ffi_fn_free_resource(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, +): Unit +fun uniffi_atomic_ffi_fn_method_resource_contains(`ptr`: Pointer,`property`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, +): Byte +fun uniffi_atomic_ffi_fn_method_resource_destroy_resource(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, +): Unit +fun uniffi_atomic_ffi_fn_method_resource_get(`ptr`: Pointer,`property`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun uniffi_atomic_ffi_fn_method_resource_keys(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun uniffi_atomic_ffi_fn_method_resource_name(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun uniffi_atomic_ffi_fn_method_resource_save(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, +): Unit +fun uniffi_atomic_ffi_fn_method_resource_set(`ptr`: Pointer,`property`: RustBuffer.ByValue,`value`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, +): Unit +fun uniffi_atomic_ffi_fn_method_resource_set_name(`ptr`: Pointer,`name`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, +): Unit +fun uniffi_atomic_ffi_fn_method_resource_subject(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun uniffi_atomic_ffi_fn_method_resource_to_json(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun uniffi_atomic_ffi_fn_clone_store(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, +): Pointer +fun uniffi_atomic_ffi_fn_free_store(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, +): Unit +fun uniffi_atomic_ffi_fn_constructor_store_in_memory(uniffi_out_err: UniffiRustCallStatus, +): Pointer +fun uniffi_atomic_ffi_fn_constructor_store_open(`path`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, +): Pointer +fun uniffi_atomic_ffi_fn_method_store_active_drive(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun uniffi_atomic_ffi_fn_method_store_add_peer(`ptr`: Pointer,`nodeId`: RustBuffer.ByValue,`name`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, +): Unit +fun uniffi_atomic_ffi_fn_method_store_agent(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun uniffi_atomic_ffi_fn_method_store_announce(`ptr`: Pointer,`drive`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, +): Unit +fun uniffi_atomic_ffi_fn_method_store_create(`ptr`: Pointer,`classUrl`: RustBuffer.ByValue,`name`: RustBuffer.ByValue,`parent`: RustBuffer.ByValue,`properties`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, +): Pointer +fun uniffi_atomic_ffi_fn_method_store_create_agent(`ptr`: Pointer,`name`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun uniffi_atomic_ffi_fn_method_store_create_drive(`ptr`: Pointer,`name`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun uniffi_atomic_ffi_fn_method_store_delete(`ptr`: Pointer,`subject`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, +): Unit +fun uniffi_atomic_ffi_fn_method_store_device_name(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun uniffi_atomic_ffi_fn_method_store_drives(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun uniffi_atomic_ffi_fn_method_store_flush(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, +): Unit +fun uniffi_atomic_ffi_fn_method_store_get(`ptr`: Pointer,`subject`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun uniffi_atomic_ffi_fn_method_store_has(`ptr`: Pointer,`subject`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, +): Byte +fun uniffi_atomic_ffi_fn_method_store_live_peers(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun uniffi_atomic_ffi_fn_method_store_load_agent(`ptr`: Pointer,`secret`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun uniffi_atomic_ffi_fn_method_store_peer_id(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun uniffi_atomic_ffi_fn_method_store_peers(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun uniffi_atomic_ffi_fn_method_store_query(`ptr`: Pointer,`parent`: RustBuffer.ByValue,`classUrl`: RustBuffer.ByValue,`property`: RustBuffer.ByValue,`value`: RustBuffer.ByValue,`limit`: RustBuffer.ByValue,`offset`: Int,uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun uniffi_atomic_ffi_fn_method_store_set_active_drive(`ptr`: Pointer,`subject`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, +): Unit +fun uniffi_atomic_ffi_fn_method_store_set_device_name(`ptr`: Pointer,`name`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, +): Unit +fun uniffi_atomic_ffi_fn_method_store_setup(`ptr`: Pointer,`name`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun uniffi_atomic_ffi_fn_method_store_start_peer(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun uniffi_atomic_ffi_fn_method_store_sync_with(`ptr`: Pointer,`nodeId`: RustBuffer.ByValue,`drive`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun uniffi_atomic_ffi_fn_method_store_wait_for(`ptr`: Pointer,`subject`: RustBuffer.ByValue,`timeoutSecs`: Double,uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun uniffi_atomic_ffi_fn_func_url_description(uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun uniffi_atomic_ffi_fn_func_url_drive(uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun uniffi_atomic_ffi_fn_func_url_folder(uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun uniffi_atomic_ffi_fn_func_url_is_a(uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun uniffi_atomic_ffi_fn_func_url_name(uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun uniffi_atomic_ffi_fn_func_url_parent(uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun uniffi_atomic_ffi_fn_func_url_plain_text(uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun ffi_atomic_ffi_rustbuffer_alloc(`size`: Long,uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun ffi_atomic_ffi_rustbuffer_from_bytes(`bytes`: ForeignBytes.ByValue,uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun ffi_atomic_ffi_rustbuffer_free(`buf`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, +): Unit +fun ffi_atomic_ffi_rustbuffer_reserve(`buf`: RustBuffer.ByValue,`additional`: Long,uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun ffi_atomic_ffi_rust_future_poll_u8(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_cancel_u8(`handle`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_free_u8(`handle`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_complete_u8(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, +): Byte +fun ffi_atomic_ffi_rust_future_poll_i8(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_cancel_i8(`handle`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_free_i8(`handle`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_complete_i8(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, +): Byte +fun ffi_atomic_ffi_rust_future_poll_u16(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_cancel_u16(`handle`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_free_u16(`handle`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_complete_u16(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, +): Short +fun ffi_atomic_ffi_rust_future_poll_i16(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_cancel_i16(`handle`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_free_i16(`handle`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_complete_i16(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, +): Short +fun ffi_atomic_ffi_rust_future_poll_u32(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_cancel_u32(`handle`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_free_u32(`handle`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_complete_u32(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, +): Int +fun ffi_atomic_ffi_rust_future_poll_i32(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_cancel_i32(`handle`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_free_i32(`handle`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_complete_i32(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, +): Int +fun ffi_atomic_ffi_rust_future_poll_u64(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_cancel_u64(`handle`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_free_u64(`handle`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_complete_u64(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, +): Long +fun ffi_atomic_ffi_rust_future_poll_i64(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_cancel_i64(`handle`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_free_i64(`handle`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_complete_i64(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, +): Long +fun ffi_atomic_ffi_rust_future_poll_f32(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_cancel_f32(`handle`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_free_f32(`handle`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_complete_f32(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, +): Float +fun ffi_atomic_ffi_rust_future_poll_f64(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_cancel_f64(`handle`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_free_f64(`handle`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_complete_f64(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, +): Double +fun ffi_atomic_ffi_rust_future_poll_pointer(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_cancel_pointer(`handle`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_free_pointer(`handle`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_complete_pointer(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, +): Pointer +fun ffi_atomic_ffi_rust_future_poll_rust_buffer(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_cancel_rust_buffer(`handle`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_free_rust_buffer(`handle`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_complete_rust_buffer(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun ffi_atomic_ffi_rust_future_poll_void(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_cancel_void(`handle`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_free_void(`handle`: Long, +): Unit +fun ffi_atomic_ffi_rust_future_complete_void(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, +): Unit + +} + +private fun uniffiCheckContractApiVersion(lib: IntegrityCheckingUniffiLib) { + // Get the bindings contract version from our ComponentInterface + val bindings_contract_version = 29 + // Get the scaffolding contract version by calling the into the dylib + val scaffolding_contract_version = lib.ffi_atomic_ffi_uniffi_contract_version() + if (bindings_contract_version != scaffolding_contract_version) { + throw RuntimeException("UniFFI contract version mismatch: try cleaning and rebuilding your project") + } +} +@Suppress("UNUSED_PARAMETER") +private fun uniffiCheckApiChecksums(lib: IntegrityCheckingUniffiLib) { + if (lib.uniffi_atomic_ffi_checksum_func_url_description() != 39560.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_func_url_drive() != 11934.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_func_url_folder() != 64619.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_func_url_is_a() != 3511.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_func_url_name() != 33254.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_func_url_parent() != 36080.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_func_url_plain_text() != 1557.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_resource_contains() != 64737.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_resource_destroy_resource() != 8799.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_resource_get() != 28141.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_resource_keys() != 15609.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_resource_name() != 38905.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_resource_save() != 30956.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_resource_set() != 5476.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_resource_set_name() != 46341.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_resource_subject() != 585.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_resource_to_json() != 18397.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_store_active_drive() != 57458.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_store_add_peer() != 4438.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_store_agent() != 55703.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_store_announce() != 59636.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_store_create() != 25388.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_store_create_agent() != 36604.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_store_create_drive() != 1466.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_store_delete() != 32680.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_store_device_name() != 19615.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_store_drives() != 61213.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_store_flush() != 4851.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_store_get() != 21403.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_store_has() != 17296.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_store_live_peers() != 46534.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_store_load_agent() != 32154.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_store_peer_id() != 20547.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_store_peers() != 25826.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_store_query() != 41621.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_store_set_active_drive() != 2781.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_store_set_device_name() != 18642.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_store_setup() != 26554.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_store_start_peer() != 15960.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_store_sync_with() != 57075.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_store_wait_for() != 64329.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_constructor_store_in_memory() != 41056.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_constructor_store_open() != 32226.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } +} + +/** + * @suppress + */ +public fun uniffiEnsureInitialized() { + UniffiLib.INSTANCE +} + +// Async support + +// Public interface members begin here. + + +// Interface implemented by anything that can contain an object reference. +// +// Such types expose a `destroy()` method that must be called to cleanly +// dispose of the contained objects. Failure to call this method may result +// in memory leaks. +// +// The easiest way to ensure this method is called is to use the `.use` +// helper method to execute a block and destroy the object at the end. +interface Disposable { + fun destroy() + companion object { + fun destroy(vararg args: Any?) { + for (arg in args) { + when (arg) { + is Disposable -> arg.destroy() + is ArrayList<*> -> { + for (idx in arg.indices) { + val element = arg[idx] + if (element is Disposable) { + element.destroy() + } + } + } + is Map<*, *> -> { + for (element in arg.values) { + if (element is Disposable) { + element.destroy() + } + } + } + is Iterable<*> -> { + for (element in arg) { + if (element is Disposable) { + element.destroy() + } + } + } + } + } + } + } +} + +/** + * @suppress + */ +inline fun T.use(block: (T) -> R) = + try { + block(this) + } finally { + try { + // N.B. our implementation is on the nullable type `Disposable?`. + this?.destroy() + } catch (e: Throwable) { + // swallow + } + } + +/** + * Used to instantiate an interface without an actual pointer, for fakes in tests, mostly. + * + * @suppress + * */ +object NoPointer +/** + * The cleaner interface for Object finalization code to run. + * This is the entry point to any implementation that we're using. + * + * The cleaner registers objects and returns cleanables, so now we are + * defining a `UniffiCleaner` with a `UniffiClenaer.Cleanable` to abstract the + * different implmentations available at compile time. + * + * @suppress + */ +interface UniffiCleaner { + interface Cleanable { + fun clean() + } + + fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable + + companion object +} + +// The fallback Jna cleaner, which is available for both Android, and the JVM. +private class UniffiJnaCleaner : UniffiCleaner { + private val cleaner = com.sun.jna.internal.Cleaner.getCleaner() + + override fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable = + UniffiJnaCleanable(cleaner.register(value, cleanUpTask)) +} + +private class UniffiJnaCleanable( + private val cleanable: com.sun.jna.internal.Cleaner.Cleanable, +) : UniffiCleaner.Cleanable { + override fun clean() = cleanable.clean() +} + + +// We decide at uniffi binding generation time whether we were +// using Android or not. +// There are further runtime checks to chose the correct implementation +// of the cleaner. +private fun UniffiCleaner.Companion.create(): UniffiCleaner = + try { + // For safety's sake: if the library hasn't been run in android_cleaner = true + // mode, but is being run on Android, then we still need to think about + // Android API versions. + // So we check if java.lang.ref.Cleaner is there, and use that… + java.lang.Class.forName("java.lang.ref.Cleaner") + JavaLangRefCleaner() + } catch (e: ClassNotFoundException) { + // … otherwise, fallback to the JNA cleaner. + UniffiJnaCleaner() + } + +private class JavaLangRefCleaner : UniffiCleaner { + val cleaner = java.lang.ref.Cleaner.create() + + override fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable = + JavaLangRefCleanable(cleaner.register(value, cleanUpTask)) +} + +private class JavaLangRefCleanable( + val cleanable: java.lang.ref.Cleaner.Cleanable +) : UniffiCleaner.Cleanable { + override fun clean() = cleanable.clean() +} + +/** + * @suppress + */ +public object FfiConverterUInt: FfiConverter { + override fun lift(value: Int): UInt { + return value.toUInt() + } + + override fun read(buf: ByteBuffer): UInt { + return lift(buf.getInt()) + } + + override fun lower(value: UInt): Int { + return value.toInt() + } + + override fun allocationSize(value: UInt) = 4UL + + override fun write(value: UInt, buf: ByteBuffer) { + buf.putInt(value.toInt()) + } +} + +/** + * @suppress + */ +public object FfiConverterDouble: FfiConverter { + override fun lift(value: Double): Double { + return value + } + + override fun read(buf: ByteBuffer): Double { + return buf.getDouble() + } + + override fun lower(value: Double): Double { + return value + } + + override fun allocationSize(value: Double) = 8UL + + override fun write(value: Double, buf: ByteBuffer) { + buf.putDouble(value) + } +} + +/** + * @suppress + */ +public object FfiConverterBoolean: FfiConverter { + override fun lift(value: Byte): Boolean { + return value.toInt() != 0 + } + + override fun read(buf: ByteBuffer): Boolean { + return lift(buf.get()) + } + + override fun lower(value: Boolean): Byte { + return if (value) 1.toByte() else 0.toByte() + } + + override fun allocationSize(value: Boolean) = 1UL + + override fun write(value: Boolean, buf: ByteBuffer) { + buf.put(lower(value)) + } +} + +/** + * @suppress + */ +public object FfiConverterString: FfiConverter { + // Note: we don't inherit from FfiConverterRustBuffer, because we use a + // special encoding when lowering/lifting. We can use `RustBuffer.len` to + // store our length and avoid writing it out to the buffer. + override fun lift(value: RustBuffer.ByValue): String { + try { + val byteArr = ByteArray(value.len.toInt()) + value.asByteBuffer()!!.get(byteArr) + return byteArr.toString(Charsets.UTF_8) + } finally { + RustBuffer.free(value) + } + } + + override fun read(buf: ByteBuffer): String { + val len = buf.getInt() + val byteArr = ByteArray(len) + buf.get(byteArr) + return byteArr.toString(Charsets.UTF_8) + } + + fun toUtf8(value: String): ByteBuffer { + // Make sure we don't have invalid UTF-16, check for lone surrogates. + return Charsets.UTF_8.newEncoder().run { + onMalformedInput(CodingErrorAction.REPORT) + encode(CharBuffer.wrap(value)) + } + } + + override fun lower(value: String): RustBuffer.ByValue { + val byteBuf = toUtf8(value) + // Ideally we'd pass these bytes to `ffi_bytebuffer_from_bytes`, but doing so would require us + // to copy them into a JNA `Memory`. So we might as well directly copy them into a `RustBuffer`. + val rbuf = RustBuffer.alloc(byteBuf.limit().toULong()) + rbuf.asByteBuffer()!!.put(byteBuf) + return rbuf + } + + // We aren't sure exactly how many bytes our string will be once it's UTF-8 + // encoded. Allocate 3 bytes per UTF-16 code unit which will always be + // enough. + override fun allocationSize(value: String): ULong { + val sizeForLength = 4UL + val sizeForString = value.length.toULong() * 3UL + return sizeForLength + sizeForString + } + + override fun write(value: String, buf: ByteBuffer) { + val byteBuf = toUtf8(value) + buf.putInt(byteBuf.limit()) + buf.put(byteBuf) + } +} + + +// This template implements a class for working with a Rust struct via a Pointer/Arc +// to the live Rust struct on the other side of the FFI. +// +// Each instance implements core operations for working with the Rust `Arc` and the +// Kotlin Pointer to work with the live Rust struct on the other side of the FFI. +// +// There's some subtlety here, because we have to be careful not to operate on a Rust +// struct after it has been dropped, and because we must expose a public API for freeing +// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each instance holds an opaque pointer to the underlying Rust struct. +// Method calls need to read this pointer from the object's state and pass it in to +// the Rust FFI. +// +// * When an instance is no longer needed, its pointer should be passed to a +// special destructor function provided by the Rust FFI, which will drop the +// underlying Rust struct. +// +// * Given an instance, calling code is expected to call the special +// `destroy` method in order to free it after use, either by calling it explicitly +// or by using a higher-level helper like the `use` method. Failing to do so risks +// leaking the underlying Rust struct. +// +// * We can't assume that calling code will do the right thing, and must be prepared +// to handle Kotlin method calls executing concurrently with or even after a call to +// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. +// +// * We must never allow Rust code to operate on the underlying Rust struct after +// the destructor has been called, and must never call the destructor more than once. +// Doing so may trigger memory unsafety. +// +// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` +// is implemented to call the destructor when the Kotlin object becomes unreachable. +// This is done in a background thread. This is not a panacea, and client code should be aware that +// 1. the thread may starve if some there are objects that have poorly performing +// `drop` methods or do significant work in their `drop` methods. +// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, +// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). +// +// If we try to implement this with mutual exclusion on access to the pointer, there is the +// possibility of a race between a method call and a concurrent call to `destroy`: +// +// * Thread A starts a method call, reads the value of the pointer, but is interrupted +// before it can pass the pointer over the FFI to Rust. +// * Thread B calls `destroy` and frees the underlying Rust struct. +// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// a use-after-free. +// +// One possible solution would be to use a `ReadWriteLock`, with each method call taking +// a read lock (and thus allowed to run concurrently) and the special `destroy` method +// taking a write lock (and thus blocking on live method calls). However, we aim not to +// generate methods with any hidden blocking semantics, and a `destroy` method that might +// block if called incorrectly seems to meet that bar. +// +// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track +// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` +// has been called. These are updated according to the following rules: +// +// * The initial value of the counter is 1, indicating a live object with no in-flight calls. +// The initial value for the flag is false. +// +// * At the start of each method call, we atomically check the counter. +// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. +// If it is nonzero them we atomically increment it by 1 and proceed with the method call. +// +// * At the end of each method call, we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// * When `destroy` is called, we atomically flip the flag from false to true. +// If the flag was already true we silently fail. +// Otherwise we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, +// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. +// +// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been +// called *and* all in-flight method calls have completed, avoiding violating any of the expectations +// of the underlying Rust code. +// +// This makes a cleaner a better alternative to _not_ calling `destroy()` as +// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` +// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner +// thread may be starved, and the app will leak memory. +// +// In this case, `destroy`ing manually may be a better solution. +// +// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects +// with Rust peers are reclaimed: +// +// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: +// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: +// 3. The memory is reclaimed when the process terminates. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// + + +public interface ResourceInterface { + + fun `contains`(`property`: kotlin.String): kotlin.Boolean + + /** + * Delete this resource from the store. + * + * Named `destroy_resource` so UniFFI Kotlin does not collide with + * `Disposable.destroy()` (the FFI handle teardown). + */ + fun `destroyResource`() + + fun `get`(`property`: kotlin.String): kotlin.String? + + fun `keys`(): List + + fun `name`(): kotlin.String? + + fun `save`() + + fun `set`(`property`: kotlin.String, `value`: kotlin.String) + + fun `setName`(`name`: kotlin.String) + + fun `subject`(): kotlin.String + + fun `toJson`(): kotlin.String + + companion object +} + +open class Resource: Disposable, AutoCloseable, ResourceInterface +{ + + constructor(pointer: Pointer) { + this.pointer = pointer + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + + /** + * This constructor can be used to instantiate a fake object. Only used for tests. Any + * attempt to actually use an object constructed this way will fail as there is no + * connected Rust object. + */ + @Suppress("UNUSED_PARAMETER") + constructor(noPointer: NoPointer) { + this.pointer = null + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + + protected val pointer: Pointer? + protected val cleanable: UniffiCleaner.Cleanable + + private val wasDestroyed = AtomicBoolean(false) + private val callCounter = AtomicLong(1) + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (! this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.uniffiClonePointer()) + } finally { + // This decrement always matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + // Use a static inner class instead of a closure so as not to accidentally + // capture `this` as part of the cleanable's action. + private class UniffiCleanAction(private val pointer: Pointer?) : Runnable { + override fun run() { + pointer?.let { ptr -> + uniffiRustCall { status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_free_resource(ptr, status) + } + } + } + } + + fun uniffiClonePointer(): Pointer { + return uniffiRustCall() { status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_clone_resource(pointer!!, status) + } + } + + override fun `contains`(`property`: kotlin.String): kotlin.Boolean { + return FfiConverterBoolean.lift( + callWithPointer { + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_resource_contains( + it, FfiConverterString.lower(`property`),_status) +} + } + ) + } + + + + /** + * Delete this resource from the store. + * + * Named `destroy_resource` so UniFFI Kotlin does not collide with + * `Disposable.destroy()` (the FFI handle teardown). + */ + @Throws(AtomicSdkException::class)override fun `destroyResource`() + = + callWithPointer { + uniffiRustCallWithError(AtomicSdkException) { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_resource_destroy_resource( + it, _status) +} + } + + + + override fun `get`(`property`: kotlin.String): kotlin.String? { + return FfiConverterOptionalString.lift( + callWithPointer { + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_resource_get( + it, FfiConverterString.lower(`property`),_status) +} + } + ) + } + + + override fun `keys`(): List { + return FfiConverterSequenceString.lift( + callWithPointer { + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_resource_keys( + it, _status) +} + } + ) + } + + + override fun `name`(): kotlin.String? { + return FfiConverterOptionalString.lift( + callWithPointer { + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_resource_name( + it, _status) +} + } + ) + } + + + + @Throws(AtomicSdkException::class)override fun `save`() + = + callWithPointer { + uniffiRustCallWithError(AtomicSdkException) { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_resource_save( + it, _status) +} + } + + + + + @Throws(AtomicSdkException::class)override fun `set`(`property`: kotlin.String, `value`: kotlin.String) + = + callWithPointer { + uniffiRustCallWithError(AtomicSdkException) { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_resource_set( + it, FfiConverterString.lower(`property`),FfiConverterString.lower(`value`),_status) +} + } + + + + + @Throws(AtomicSdkException::class)override fun `setName`(`name`: kotlin.String) + = + callWithPointer { + uniffiRustCallWithError(AtomicSdkException) { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_resource_set_name( + it, FfiConverterString.lower(`name`),_status) +} + } + + + + override fun `subject`(): kotlin.String { + return FfiConverterString.lift( + callWithPointer { + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_resource_subject( + it, _status) +} + } + ) + } + + + + @Throws(AtomicSdkException::class)override fun `toJson`(): kotlin.String { + return FfiConverterString.lift( + callWithPointer { + uniffiRustCallWithError(AtomicSdkException) { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_resource_to_json( + it, _status) +} + } + ) + } + + + + + + + companion object + +} + +/** + * @suppress + */ +public object FfiConverterTypeResource: FfiConverter { + + override fun lower(value: Resource): Pointer { + return value.uniffiClonePointer() + } + + override fun lift(value: Pointer): Resource { + return Resource(value) + } + + override fun read(buf: ByteBuffer): Resource { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return lift(Pointer(buf.getLong())) + } + + override fun allocationSize(value: Resource) = 8UL + + override fun write(value: Resource, buf: ByteBuffer) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(lower(value))) + } +} + + +// This template implements a class for working with a Rust struct via a Pointer/Arc +// to the live Rust struct on the other side of the FFI. +// +// Each instance implements core operations for working with the Rust `Arc` and the +// Kotlin Pointer to work with the live Rust struct on the other side of the FFI. +// +// There's some subtlety here, because we have to be careful not to operate on a Rust +// struct after it has been dropped, and because we must expose a public API for freeing +// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each instance holds an opaque pointer to the underlying Rust struct. +// Method calls need to read this pointer from the object's state and pass it in to +// the Rust FFI. +// +// * When an instance is no longer needed, its pointer should be passed to a +// special destructor function provided by the Rust FFI, which will drop the +// underlying Rust struct. +// +// * Given an instance, calling code is expected to call the special +// `destroy` method in order to free it after use, either by calling it explicitly +// or by using a higher-level helper like the `use` method. Failing to do so risks +// leaking the underlying Rust struct. +// +// * We can't assume that calling code will do the right thing, and must be prepared +// to handle Kotlin method calls executing concurrently with or even after a call to +// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. +// +// * We must never allow Rust code to operate on the underlying Rust struct after +// the destructor has been called, and must never call the destructor more than once. +// Doing so may trigger memory unsafety. +// +// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` +// is implemented to call the destructor when the Kotlin object becomes unreachable. +// This is done in a background thread. This is not a panacea, and client code should be aware that +// 1. the thread may starve if some there are objects that have poorly performing +// `drop` methods or do significant work in their `drop` methods. +// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, +// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). +// +// If we try to implement this with mutual exclusion on access to the pointer, there is the +// possibility of a race between a method call and a concurrent call to `destroy`: +// +// * Thread A starts a method call, reads the value of the pointer, but is interrupted +// before it can pass the pointer over the FFI to Rust. +// * Thread B calls `destroy` and frees the underlying Rust struct. +// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// a use-after-free. +// +// One possible solution would be to use a `ReadWriteLock`, with each method call taking +// a read lock (and thus allowed to run concurrently) and the special `destroy` method +// taking a write lock (and thus blocking on live method calls). However, we aim not to +// generate methods with any hidden blocking semantics, and a `destroy` method that might +// block if called incorrectly seems to meet that bar. +// +// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track +// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` +// has been called. These are updated according to the following rules: +// +// * The initial value of the counter is 1, indicating a live object with no in-flight calls. +// The initial value for the flag is false. +// +// * At the start of each method call, we atomically check the counter. +// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. +// If it is nonzero them we atomically increment it by 1 and proceed with the method call. +// +// * At the end of each method call, we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// * When `destroy` is called, we atomically flip the flag from false to true. +// If the flag was already true we silently fail. +// Otherwise we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, +// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. +// +// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been +// called *and* all in-flight method calls have completed, avoiding violating any of the expectations +// of the underlying Rust code. +// +// This makes a cleaner a better alternative to _not_ calling `destroy()` as +// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` +// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner +// thread may be starved, and the app will leak memory. +// +// In this case, `destroy`ing manually may be a better solution. +// +// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects +// with Rust peers are reclaimed: +// +// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: +// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: +// 3. The memory is reclaimed when the process terminates. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// + + +public interface StoreInterface { + + fun `activeDrive`(): kotlin.String? + + fun `addPeer`(`nodeId`: kotlin.String, `name`: kotlin.String) + + fun `agent`(): AgentInfo? + + fun `announce`(`drive`: kotlin.String?) + + fun `create`(`classUrl`: kotlin.String, `name`: kotlin.String, `parent`: kotlin.String?, `properties`: Map?): Resource + + fun `createAgent`(`name`: kotlin.String): AgentInfo + + fun `createDrive`(`name`: kotlin.String): kotlin.String + + fun `delete`(`subject`: kotlin.String) + + fun `deviceName`(): kotlin.String + + fun `drives`(): List + + fun `flush`() + + fun `get`(`subject`: kotlin.String): Resource? + + fun `has`(`subject`: kotlin.String): kotlin.Boolean + + fun `livePeers`(): List + + fun `loadAgent`(`secret`: kotlin.String): AgentInfo + + fun `peerId`(): kotlin.String? + + fun `peers`(): List + + fun `query`(`parent`: kotlin.String?, `classUrl`: kotlin.String?, `property`: kotlin.String?, `value`: kotlin.String?, `limit`: kotlin.UInt?, `offset`: kotlin.UInt): List + + fun `setActiveDrive`(`subject`: kotlin.String) + + fun `setDeviceName`(`name`: kotlin.String) + + fun `setup`(`name`: kotlin.String): SetupInfo + + fun `startPeer`(): kotlin.String + + fun `syncWith`(`nodeId`: kotlin.String, `drive`: kotlin.String?): SyncReport + + fun `waitFor`(`subject`: kotlin.String, `timeoutSecs`: kotlin.Double): kotlin.String + + companion object +} + +open class Store: Disposable, AutoCloseable, StoreInterface +{ + + constructor(pointer: Pointer) { + this.pointer = pointer + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + + /** + * This constructor can be used to instantiate a fake object. Only used for tests. Any + * attempt to actually use an object constructed this way will fail as there is no + * connected Rust object. + */ + @Suppress("UNUSED_PARAMETER") + constructor(noPointer: NoPointer) { + this.pointer = null + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + + protected val pointer: Pointer? + protected val cleanable: UniffiCleaner.Cleanable + + private val wasDestroyed = AtomicBoolean(false) + private val callCounter = AtomicLong(1) + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (! this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.uniffiClonePointer()) + } finally { + // This decrement always matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + // Use a static inner class instead of a closure so as not to accidentally + // capture `this` as part of the cleanable's action. + private class UniffiCleanAction(private val pointer: Pointer?) : Runnable { + override fun run() { + pointer?.let { ptr -> + uniffiRustCall { status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_free_store(ptr, status) + } + } + } + } + + fun uniffiClonePointer(): Pointer { + return uniffiRustCall() { status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_clone_store(pointer!!, status) + } + } + + override fun `activeDrive`(): kotlin.String? { + return FfiConverterOptionalString.lift( + callWithPointer { + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_store_active_drive( + it, _status) +} + } + ) + } + + + override fun `addPeer`(`nodeId`: kotlin.String, `name`: kotlin.String) + = + callWithPointer { + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_store_add_peer( + it, FfiConverterString.lower(`nodeId`),FfiConverterString.lower(`name`),_status) +} + } + + + + + @Throws(AtomicSdkException::class)override fun `agent`(): AgentInfo? { + return FfiConverterOptionalTypeAgentInfo.lift( + callWithPointer { + uniffiRustCallWithError(AtomicSdkException) { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_store_agent( + it, _status) +} + } + ) + } + + + + @Throws(AtomicSdkException::class)override fun `announce`(`drive`: kotlin.String?) + = + callWithPointer { + uniffiRustCallWithError(AtomicSdkException) { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_store_announce( + it, FfiConverterOptionalString.lower(`drive`),_status) +} + } + + + + + @Throws(AtomicSdkException::class)override fun `create`(`classUrl`: kotlin.String, `name`: kotlin.String, `parent`: kotlin.String?, `properties`: Map?): Resource { + return FfiConverterTypeResource.lift( + callWithPointer { + uniffiRustCallWithError(AtomicSdkException) { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_store_create( + it, FfiConverterString.lower(`classUrl`),FfiConverterString.lower(`name`),FfiConverterOptionalString.lower(`parent`),FfiConverterOptionalMapStringString.lower(`properties`),_status) +} + } + ) + } + + + + @Throws(AtomicSdkException::class)override fun `createAgent`(`name`: kotlin.String): AgentInfo { + return FfiConverterTypeAgentInfo.lift( + callWithPointer { + uniffiRustCallWithError(AtomicSdkException) { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_store_create_agent( + it, FfiConverterString.lower(`name`),_status) +} + } + ) + } + + + + @Throws(AtomicSdkException::class)override fun `createDrive`(`name`: kotlin.String): kotlin.String { + return FfiConverterString.lift( + callWithPointer { + uniffiRustCallWithError(AtomicSdkException) { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_store_create_drive( + it, FfiConverterString.lower(`name`),_status) +} + } + ) + } + + + + @Throws(AtomicSdkException::class)override fun `delete`(`subject`: kotlin.String) + = + callWithPointer { + uniffiRustCallWithError(AtomicSdkException) { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_store_delete( + it, FfiConverterString.lower(`subject`),_status) +} + } + + + + override fun `deviceName`(): kotlin.String { + return FfiConverterString.lift( + callWithPointer { + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_store_device_name( + it, _status) +} + } + ) + } + + + + @Throws(AtomicSdkException::class)override fun `drives`(): List { + return FfiConverterSequenceTypeDriveInfo.lift( + callWithPointer { + uniffiRustCallWithError(AtomicSdkException) { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_store_drives( + it, _status) +} + } + ) + } + + + + @Throws(AtomicSdkException::class)override fun `flush`() + = + callWithPointer { + uniffiRustCallWithError(AtomicSdkException) { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_store_flush( + it, _status) +} + } + + + + + @Throws(AtomicSdkException::class)override fun `get`(`subject`: kotlin.String): Resource? { + return FfiConverterOptionalTypeResource.lift( + callWithPointer { + uniffiRustCallWithError(AtomicSdkException) { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_store_get( + it, FfiConverterString.lower(`subject`),_status) +} + } + ) + } + + + override fun `has`(`subject`: kotlin.String): kotlin.Boolean { + return FfiConverterBoolean.lift( + callWithPointer { + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_store_has( + it, FfiConverterString.lower(`subject`),_status) +} + } + ) + } + + + override fun `livePeers`(): List { + return FfiConverterSequenceString.lift( + callWithPointer { + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_store_live_peers( + it, _status) +} + } + ) + } + + + + @Throws(AtomicSdkException::class)override fun `loadAgent`(`secret`: kotlin.String): AgentInfo { + return FfiConverterTypeAgentInfo.lift( + callWithPointer { + uniffiRustCallWithError(AtomicSdkException) { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_store_load_agent( + it, FfiConverterString.lower(`secret`),_status) +} + } + ) + } + + + override fun `peerId`(): kotlin.String? { + return FfiConverterOptionalString.lift( + callWithPointer { + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_store_peer_id( + it, _status) +} + } + ) + } + + + override fun `peers`(): List { + return FfiConverterSequenceTypePeerInfo.lift( + callWithPointer { + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_store_peers( + it, _status) +} + } + ) + } + + + + @Throws(AtomicSdkException::class)override fun `query`(`parent`: kotlin.String?, `classUrl`: kotlin.String?, `property`: kotlin.String?, `value`: kotlin.String?, `limit`: kotlin.UInt?, `offset`: kotlin.UInt): List { + return FfiConverterSequenceTypeResource.lift( + callWithPointer { + uniffiRustCallWithError(AtomicSdkException) { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_store_query( + it, FfiConverterOptionalString.lower(`parent`),FfiConverterOptionalString.lower(`classUrl`),FfiConverterOptionalString.lower(`property`),FfiConverterOptionalString.lower(`value`),FfiConverterOptionalUInt.lower(`limit`),FfiConverterUInt.lower(`offset`),_status) +} + } + ) + } + + + + @Throws(AtomicSdkException::class)override fun `setActiveDrive`(`subject`: kotlin.String) + = + callWithPointer { + uniffiRustCallWithError(AtomicSdkException) { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_store_set_active_drive( + it, FfiConverterString.lower(`subject`),_status) +} + } + + + + override fun `setDeviceName`(`name`: kotlin.String) + = + callWithPointer { + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_store_set_device_name( + it, FfiConverterString.lower(`name`),_status) +} + } + + + + + @Throws(AtomicSdkException::class)override fun `setup`(`name`: kotlin.String): SetupInfo { + return FfiConverterTypeSetupInfo.lift( + callWithPointer { + uniffiRustCallWithError(AtomicSdkException) { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_store_setup( + it, FfiConverterString.lower(`name`),_status) +} + } + ) + } + + + + @Throws(AtomicSdkException::class)override fun `startPeer`(): kotlin.String { + return FfiConverterString.lift( + callWithPointer { + uniffiRustCallWithError(AtomicSdkException) { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_store_start_peer( + it, _status) +} + } + ) + } + + + + @Throws(AtomicSdkException::class)override fun `syncWith`(`nodeId`: kotlin.String, `drive`: kotlin.String?): SyncReport { + return FfiConverterTypeSyncReport.lift( + callWithPointer { + uniffiRustCallWithError(AtomicSdkException) { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_store_sync_with( + it, FfiConverterString.lower(`nodeId`),FfiConverterOptionalString.lower(`drive`),_status) +} + } + ) + } + + + + @Throws(AtomicSdkException::class)override fun `waitFor`(`subject`: kotlin.String, `timeoutSecs`: kotlin.Double): kotlin.String { + return FfiConverterString.lift( + callWithPointer { + uniffiRustCallWithError(AtomicSdkException) { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_store_wait_for( + it, FfiConverterString.lower(`subject`),FfiConverterDouble.lower(`timeoutSecs`),_status) +} + } + ) + } + + + + + + companion object { + + @Throws(AtomicSdkException::class) fun `inMemory`(): Store { + return FfiConverterTypeStore.lift( + uniffiRustCallWithError(AtomicSdkException) { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_constructor_store_in_memory( + _status) +} + ) + } + + + + @Throws(AtomicSdkException::class) fun `open`(`path`: kotlin.String): Store { + return FfiConverterTypeStore.lift( + uniffiRustCallWithError(AtomicSdkException) { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_constructor_store_open( + FfiConverterString.lower(`path`),_status) +} + ) + } + + + + } + +} + +/** + * @suppress + */ +public object FfiConverterTypeStore: FfiConverter { + + override fun lower(value: Store): Pointer { + return value.uniffiClonePointer() + } + + override fun lift(value: Pointer): Store { + return Store(value) + } + + override fun read(buf: ByteBuffer): Store { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return lift(Pointer(buf.getLong())) + } + + override fun allocationSize(value: Store) = 8UL + + override fun write(value: Store, buf: ByteBuffer) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(lower(value))) + } +} + + + +data class AgentInfo ( + var `subject`: kotlin.String, + var `secret`: kotlin.String, + var `publicKey`: kotlin.String, + var `name`: kotlin.String?, + var `driveNeedsSync`: kotlin.Boolean +) { + + companion object +} + +/** + * @suppress + */ +public object FfiConverterTypeAgentInfo: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): AgentInfo { + return AgentInfo( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterBoolean.read(buf), + ) + } + + override fun allocationSize(value: AgentInfo) = ( + FfiConverterString.allocationSize(value.`subject`) + + FfiConverterString.allocationSize(value.`secret`) + + FfiConverterString.allocationSize(value.`publicKey`) + + FfiConverterOptionalString.allocationSize(value.`name`) + + FfiConverterBoolean.allocationSize(value.`driveNeedsSync`) + ) + + override fun write(value: AgentInfo, buf: ByteBuffer) { + FfiConverterString.write(value.`subject`, buf) + FfiConverterString.write(value.`secret`, buf) + FfiConverterString.write(value.`publicKey`, buf) + FfiConverterOptionalString.write(value.`name`, buf) + FfiConverterBoolean.write(value.`driveNeedsSync`, buf) + } +} + + + +data class DriveInfo ( + var `subject`: kotlin.String, + var `name`: kotlin.String +) { + + companion object +} + +/** + * @suppress + */ +public object FfiConverterTypeDriveInfo: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): DriveInfo { + return DriveInfo( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: DriveInfo) = ( + FfiConverterString.allocationSize(value.`subject`) + + FfiConverterString.allocationSize(value.`name`) + ) + + override fun write(value: DriveInfo, buf: ByteBuffer) { + FfiConverterString.write(value.`subject`, buf) + FfiConverterString.write(value.`name`, buf) + } +} + + + +data class PeerInfo ( + var `nodeId`: kotlin.String, + var `name`: kotlin.String +) { + + companion object +} + +/** + * @suppress + */ +public object FfiConverterTypePeerInfo: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): PeerInfo { + return PeerInfo( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: PeerInfo) = ( + FfiConverterString.allocationSize(value.`nodeId`) + + FfiConverterString.allocationSize(value.`name`) + ) + + override fun write(value: PeerInfo, buf: ByteBuffer) { + FfiConverterString.write(value.`nodeId`, buf) + FfiConverterString.write(value.`name`, buf) + } +} + + + +data class SetupInfo ( + var `agentSubject`: kotlin.String, + var `agentSecret`: kotlin.String, + var `driveSubject`: kotlin.String +) { + + companion object +} + +/** + * @suppress + */ +public object FfiConverterTypeSetupInfo: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): SetupInfo { + return SetupInfo( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: SetupInfo) = ( + FfiConverterString.allocationSize(value.`agentSubject`) + + FfiConverterString.allocationSize(value.`agentSecret`) + + FfiConverterString.allocationSize(value.`driveSubject`) + ) + + override fun write(value: SetupInfo, buf: ByteBuffer) { + FfiConverterString.write(value.`agentSubject`, buf) + FfiConverterString.write(value.`agentSecret`, buf) + FfiConverterString.write(value.`driveSubject`, buf) + } +} + + + +data class SyncReport ( + var `imported`: kotlin.UInt, + var `pushed`: kotlin.UInt, + var `inSync`: kotlin.Boolean, + var `peerName`: kotlin.String? +) { + + companion object +} + +/** + * @suppress + */ +public object FfiConverterTypeSyncReport: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): SyncReport { + return SyncReport( + FfiConverterUInt.read(buf), + FfiConverterUInt.read(buf), + FfiConverterBoolean.read(buf), + FfiConverterOptionalString.read(buf), + ) + } + + override fun allocationSize(value: SyncReport) = ( + FfiConverterUInt.allocationSize(value.`imported`) + + FfiConverterUInt.allocationSize(value.`pushed`) + + FfiConverterBoolean.allocationSize(value.`inSync`) + + FfiConverterOptionalString.allocationSize(value.`peerName`) + ) + + override fun write(value: SyncReport, buf: ByteBuffer) { + FfiConverterUInt.write(value.`imported`, buf) + FfiConverterUInt.write(value.`pushed`, buf) + FfiConverterBoolean.write(value.`inSync`, buf) + FfiConverterOptionalString.write(value.`peerName`, buf) + } +} + + + + + +sealed class AtomicSdkException(message: String): kotlin.Exception(message) { + + class Failed(message: String) : AtomicSdkException(message) + + + companion object ErrorHandler : UniffiRustCallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): AtomicSdkException = FfiConverterTypeAtomicSdkError.lift(error_buf) + } +} + +/** + * @suppress + */ +public object FfiConverterTypeAtomicSdkError : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): AtomicSdkException { + + return when(buf.getInt()) { + 1 -> AtomicSdkException.Failed(FfiConverterString.read(buf)) + else -> throw RuntimeException("invalid error enum value, something is very wrong!!") + } + + } + + override fun allocationSize(value: AtomicSdkException): ULong { + return 4UL + } + + override fun write(value: AtomicSdkException, buf: ByteBuffer) { + when(value) { + is AtomicSdkException.Failed -> { + buf.putInt(1) + Unit + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } + +} + + + + +/** + * @suppress + */ +public object FfiConverterOptionalUInt: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): kotlin.UInt? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterUInt.read(buf) + } + + override fun allocationSize(value: kotlin.UInt?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterUInt.allocationSize(value) + } + } + + override fun write(value: kotlin.UInt?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterUInt.write(value, buf) + } + } +} + + + + +/** + * @suppress + */ +public object FfiConverterOptionalString: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): kotlin.String? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterString.read(buf) + } + + override fun allocationSize(value: kotlin.String?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterString.allocationSize(value) + } + } + + override fun write(value: kotlin.String?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterString.write(value, buf) + } + } +} + + + + +/** + * @suppress + */ +public object FfiConverterOptionalTypeResource: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): Resource? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterTypeResource.read(buf) + } + + override fun allocationSize(value: Resource?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterTypeResource.allocationSize(value) + } + } + + override fun write(value: Resource?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterTypeResource.write(value, buf) + } + } +} + + + + +/** + * @suppress + */ +public object FfiConverterOptionalTypeAgentInfo: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): AgentInfo? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterTypeAgentInfo.read(buf) + } + + override fun allocationSize(value: AgentInfo?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterTypeAgentInfo.allocationSize(value) + } + } + + override fun write(value: AgentInfo?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterTypeAgentInfo.write(value, buf) + } + } +} + + + + +/** + * @suppress + */ +public object FfiConverterOptionalMapStringString: FfiConverterRustBuffer?> { + override fun read(buf: ByteBuffer): Map? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterMapStringString.read(buf) + } + + override fun allocationSize(value: Map?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterMapStringString.allocationSize(value) + } + } + + override fun write(value: Map?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterMapStringString.write(value, buf) + } + } +} + + + + +/** + * @suppress + */ +public object FfiConverterSequenceString: FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterString.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterString.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write(value: List, buf: ByteBuffer) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterString.write(it, buf) + } + } +} + + + + +/** + * @suppress + */ +public object FfiConverterSequenceTypeResource: FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypeResource.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterTypeResource.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write(value: List, buf: ByteBuffer) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypeResource.write(it, buf) + } + } +} + + + + +/** + * @suppress + */ +public object FfiConverterSequenceTypeDriveInfo: FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypeDriveInfo.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterTypeDriveInfo.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write(value: List, buf: ByteBuffer) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypeDriveInfo.write(it, buf) + } + } +} + + + + +/** + * @suppress + */ +public object FfiConverterSequenceTypePeerInfo: FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypePeerInfo.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterTypePeerInfo.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write(value: List, buf: ByteBuffer) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypePeerInfo.write(it, buf) + } + } +} + + + + +/** + * @suppress + */ +public object FfiConverterMapStringString: FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): Map { + val len = buf.getInt() + return buildMap(len) { + repeat(len) { + val k = FfiConverterString.read(buf) + val v = FfiConverterString.read(buf) + this[k] = v + } + } + } + + override fun allocationSize(value: Map): ULong { + val spaceForMapSize = 4UL + val spaceForChildren = value.map { (k, v) -> + FfiConverterString.allocationSize(k) + + FfiConverterString.allocationSize(v) + }.sum() + return spaceForMapSize + spaceForChildren + } + + override fun write(value: Map, buf: ByteBuffer) { + buf.putInt(value.size) + // The parens on `(k, v)` here ensure we're calling the right method, + // which is important for compatibility with older android devices. + // Ref https://blog.danlew.net/2017/03/16/kotlin-puzzler-whose-line-is-it-anyways/ + value.forEach { (k, v) -> + FfiConverterString.write(k, buf) + FfiConverterString.write(v, buf) + } + } +} fun `urlDescription`(): kotlin.String { + return FfiConverterString.lift( + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_func_url_description( + _status) +} + ) + } + + fun `urlDrive`(): kotlin.String { + return FfiConverterString.lift( + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_func_url_drive( + _status) +} + ) + } + + + /** + * Well-known Atomic Data URLs, matching `atomic_data.urls` in Python. + */ fun `urlFolder`(): kotlin.String { + return FfiConverterString.lift( + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_func_url_folder( + _status) +} + ) + } + + fun `urlIsA`(): kotlin.String { + return FfiConverterString.lift( + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_func_url_is_a( + _status) +} + ) + } + + fun `urlName`(): kotlin.String { + return FfiConverterString.lift( + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_func_url_name( + _status) +} + ) + } + + fun `urlParent`(): kotlin.String { + return FfiConverterString.lift( + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_func_url_parent( + _status) +} + ) + } + + fun `urlPlainText`(): kotlin.String { + return FfiConverterString.lift( + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_func_url_plain_text( + _status) +} + ) + } + + + diff --git a/ffi/kotlin/src/test/kotlin/dev/atomicdata/IrohPeerChild.kt b/ffi/kotlin/src/test/kotlin/dev/atomicdata/IrohPeerChild.kt new file mode 100644 index 0000000000..5baac5a752 --- /dev/null +++ b/ffi/kotlin/src/test/kotlin/dev/atomicdata/IrohPeerChild.kt @@ -0,0 +1,55 @@ +package dev.atomicdata + +import java.io.File + +/** + * Second OS process for the Iroh P2P test. Invoked by [IrohTest]. + */ +object IrohPeerChild { + @JvmStatic + fun main(args: Array) { + val handshakePath = args[0] + val storePath = args[1] + val donePath = args[2] + val hs = parseObject(File(handshakePath).readText()) + val store = Store.open(storePath) + store.loadAgent(hs.getValue("secret")) + store.startPeer() + val report = store.syncWith(hs.getValue("node_id"), hs["drive"]) + val got = store.get(hs.getValue("subject")) + val ok = got != null && got.get("name") == hs["name"] + File(donePath).writeText( + buildString { + append("{") + append("\"ok\":").append(ok).append(',') + append("\"imported\":").append(report.imported).append(',') + append("\"pushed\":").append(report.pushed).append(',') + append("\"in_sync\":").append(report.inSync).append(',') + append("\"name\":").append(jsonString(got?.get("name"))).append(',') + append("\"has_drive\":").append(store.has(hs.getValue("drive"))).append(',') + append("\"child_peer\":").append(jsonString(store.peerId())) + append("}") + }, + ) + } + + private fun parseObject(raw: String): Map { + val body = raw.trim().removePrefix("{").removeSuffix("}") + val out = mutableMapOf() + var i = 0 + while (i < body.length) { + val keyStart = body.indexOf('"', i) + if (keyStart < 0) break + val keyEnd = body.indexOf('"', keyStart + 1) + val colon = body.indexOf(':', keyEnd + 1) + val valueStart = body.indexOf('"', colon + 1) + val valueEnd = body.indexOf('"', valueStart + 1) + out[body.substring(keyStart + 1, keyEnd)] = body.substring(valueStart + 1, valueEnd) + i = valueEnd + 1 + } + return out + } + + private fun jsonString(value: String?): String = + if (value == null) "null" else "\"${value.replace("\"", "\\\"")}\"" +} diff --git a/ffi/kotlin/src/test/kotlin/dev/atomicdata/IrohTest.kt b/ffi/kotlin/src/test/kotlin/dev/atomicdata/IrohTest.kt new file mode 100644 index 0000000000..eb24f1b86c --- /dev/null +++ b/ffi/kotlin/src/test/kotlin/dev/atomicdata/IrohTest.kt @@ -0,0 +1,122 @@ +package dev.atomicdata + +import java.nio.file.Files +import java.nio.file.Path +import kotlin.test.Test +import kotlin.test.assertContains +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue +import org.junit.jupiter.api.MethodOrderer +import org.junit.jupiter.api.Order +import org.junit.jupiter.api.TestMethodOrder + +@TestMethodOrder(MethodOrderer.OrderAnnotation::class) +class IrohTest { + @Test + @Order(1) + fun syncWithRequiresStartPeer() { + val store = Store.inMemory() + store.setup("Ada") + val err = + assertFailsWith { + store.syncWith("did:ad:node:" + "ab".repeat(32), null) + } + assertContains(err.message ?: "", "startPeer") + } + + @Test + @Order(2) + fun knownPeersPersistLocally() { + val store = Store.inMemory() + store.setup("Ada") + store.addPeer("did:ad:node:" + "cd".repeat(32), "Phone") + val peers = store.peers() + assertEquals(1, peers.size) + assertEquals("Phone", peers[0].name) + assertTrue(peers[0].nodeId.startsWith("did:ad:node:")) + } + + @Test + @Order(3) + fun waitForTimesOut() { + val store = Store.inMemory() + store.setup("Ada") + val err = + assertFailsWith { + store.waitFor("did:ad:does-not-change", 0.2) + } + assertContains(err.message ?: "", "timed out") + } + + @Test + @Order(4) + fun twoProcessIrohSync() { + // startPeer is process-global. This must be the only test in this + // JVM that starts Iroh, and it must own the store the node serves. + val tmp = Files.createTempDirectory("atomic-ffi-iroh") + val pathA = tmp.resolve("a").toString() + val pathB = tmp.resolve("b").toString() + val handshake = tmp.resolve("handshake.json") + val done = tmp.resolve("done.json") + + val store = Store.open(pathA) + val setup = store.setup("Ada") + store.setDeviceName("junit-parent") + val node = store.startPeer() + assertTrue(node.startsWith("did:ad:node:")) + assertEquals(node, store.peerId()) + val note = store.create(Urls.FOLDER, "From A", null, null) + store.flush() + handshake.toFile().writeText( + """ + {"node_id":"$node","secret":"${setup.agentSecret}","drive":"${setup.driveSubject}","subject":"${note.subject()}","name":"From A"} + """.trimIndent(), + ) + + val child = spawnChild(handshake, pathB, done) + try { + val deadline = System.currentTimeMillis() + 90_000 + while ( + System.currentTimeMillis() < deadline && child.isAlive && !Files.exists(done) + ) { + Thread.sleep(250) + } + if (!Files.exists(done)) { + child.destroyForcibly() + val out = child.inputStream.bufferedReader().readText() + throw AssertionError("child did not finish Iroh sync:\n$out") + } + val report = done.toFile().readText() + assertTrue("\"ok\":true" in report, report) + assertTrue("\"has_drive\":true" in report, report) + val imported = Regex("\"imported\":(\\d+)").find(report)?.groupValues?.get(1)?.toInt() + assertTrue((imported ?: 0) >= 1, report) + } finally { + if (child.isAlive) { + child.destroyForcibly() + child.waitFor() + } + } + } + + private fun spawnChild(handshake: Path, storePath: String, done: Path): Process { + val java = ProcessHandle.current().info().command().orElse("java") + val cp = System.getProperty("java.class.path") + val jna = System.getProperty("jna.library.path") + val javaLib = System.getProperty("java.library.path") + return ProcessBuilder( + java, + "-Djna.library.path=$jna", + "-Djava.library.path=$javaLib", + "-cp", + cp, + "dev.atomicdata.IrohPeerChild", + handshake.toString(), + storePath, + done.toString(), + ) + .redirectErrorStream(true) + .start() + } +} diff --git a/ffi/kotlin/src/test/kotlin/dev/atomicdata/StoreTest.kt b/ffi/kotlin/src/test/kotlin/dev/atomicdata/StoreTest.kt new file mode 100644 index 0000000000..33846993da --- /dev/null +++ b/ffi/kotlin/src/test/kotlin/dev/atomicdata/StoreTest.kt @@ -0,0 +1,190 @@ +package dev.atomicdata + +import java.nio.file.Files +import kotlin.test.Test +import kotlin.test.assertContains +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class StoreTest { + private fun memoryStore(): Store { + val store = Store.inMemory() + store.setup("Test agent") + return store + } + + @Test + fun setupCreatesAgentAndDrive() { + val store = Store.inMemory() + assertNull(store.agent()) + assertNull(store.activeDrive()) + + val info = store.setup("Ada") + assertTrue(info.agentSubject.startsWith("did:ad:agent:")) + assertTrue(info.driveSubject.startsWith("did:ad:")) + assertTrue(info.agentSecret.isNotEmpty()) + assertEquals(info.driveSubject, store.activeDrive()) + assertTrue(store.has(info.driveSubject)) + assertEquals(1, store.drives().size) + } + + @Test + fun createReadUpdate() { + val store = memoryStore() + val note = store.create( + Urls.PLAIN_TEXT, + "Hello", + null, + mapOf("description" to "first draft"), + ) + assertTrue(note.subject().startsWith("did:ad:")) + assertEquals("Hello", note.get("name")) + assertEquals("first draft", note.get("description")) + assertTrue(note.contains(Urls.PARENT)) + + note.set("description", "second draft") + note.setName("Hello again") + note.save() + + val got = store.get(note.subject()) + assertNotNull(got) + assertEquals("Hello again", got.get("name")) + assertEquals("second draft", got.get("description")) + } + + @Test + fun queryByParentAndClass() { + val store = memoryStore() + val drive = store.activeDrive()!! + val folder = store.create(Urls.FOLDER, "Alpha", null, null) + val text = store.create( + Urls.PLAIN_TEXT, + "Beta", + null, + mapOf("description" to "body"), + ) + store.create( + Urls.PLAIN_TEXT, + "Gamma", + null, + mapOf("description" to "body"), + ) + + val names = store.query(drive, null, null, null, null, 0u).mapNotNull { it.name() } + assertContains(names, "Alpha") + assertContains(names, "Beta") + assertContains(names, "Gamma") + + val folders = store.query(drive, Urls.FOLDER, null, null, null, 0u) + assertEquals(1, folders.size) + assertEquals(folder.subject(), folders[0].subject()) + + val texts = store.query(null, Urls.PLAIN_TEXT, null, null, null, 0u) + val subjects = texts.map { it.subject() } + assertContains(subjects, text.subject()) + assertFalse(subjects.contains(folder.subject())) + } + + @Test + fun destroyResourceRemovesIt() { + val store = memoryStore() + val folder = store.create(Urls.FOLDER, "Tmp", null, null) + val subject = folder.subject() + folder.destroyResource() + assertFalse(store.has(subject)) + assertNull(store.get(subject)) + } + + @Test + fun deleteRemovesResource() { + val store = memoryStore() + val folder = store.create(Urls.FOLDER, "ephemeral", null, null) + val subject = folder.subject() + store.delete(subject) + assertNull(store.get(subject)) + } + + @Test + fun missingGetReturnsNull() { + val store = memoryStore() + assertNull(store.get("did:ad:does-not-exist")) + } + + @Test + fun createRequiresParentOrDrive() { + val store = Store.inMemory() + store.createAgent("No drive") + val err = assertFailsWith { + store.create(Urls.FOLDER, "orphan", null, null) + } + assertContains(err.message ?: "", "parent or an active drive") + } + + @Test + fun independentInMemoryStores() { + val a = Store.inMemory() + val b = Store.inMemory() + val setupA = a.setup("A") + b.setup("B") + val note = a.create(Urls.FOLDER, "only-in-a", null, null) + assertNull(b.get(note.subject())) + assertNotNull(a.get(setupA.driveSubject)) + } + + @Test + fun toJsonIncludesSubject() { + val store = memoryStore() + val note = store.create(Urls.FOLDER, "serial", null, null) + val json = note.toJson() + assertContains(json, note.subject()) + assertContains(json, "serial") + } + + @Test + fun reopenFileStore() { + val dir = Files.createTempDirectory("atomic-ffi-kt") + val path = dir.toAbsolutePath().toString() + + val (subject, secret, drive) = run { + val store = Store.open(path) + val setup = store.setup("Ada") + val note = store.create( + Urls.PLAIN_TEXT, + "Persisted", + null, + mapOf("description" to "on disk"), + ) + val result = Triple(note.subject(), setup.agentSecret, setup.driveSubject) + store.flush() + note.close() + store.close() + result + } + + run { + val store = Store.open(path) + val loaded = store.get(subject) + assertNotNull(loaded) + assertEquals("Persisted", loaded.get("name")) + assertEquals("on disk", loaded.get("description")) + assertTrue(store.has(drive)) + store.loadAgent(secret) + store.setActiveDrive(drive) + loaded.set("description", "edited after reopen") + loaded.save() + store.flush() + loaded.close() + store.close() + } + + val store = Store.open(path) + val got = store.get(subject) + assertNotNull(got) + assertEquals("edited after reopen", got.get("description")) + store.close() + } +} diff --git a/ffi/src/bin/uniffi-bindgen.rs b/ffi/src/bin/uniffi-bindgen.rs new file mode 100644 index 0000000000..f6cff6cf1d --- /dev/null +++ b/ffi/src/bin/uniffi-bindgen.rs @@ -0,0 +1,3 @@ +fn main() { + uniffi::uniffi_bindgen_main() +} diff --git a/ffi/src/convert.rs b/ffi/src/convert.rs new file mode 100644 index 0000000000..b5636a8762 --- /dev/null +++ b/ffi/src/convert.rs @@ -0,0 +1,28 @@ +use atomic_lib::{urls, Value}; + +pub(crate) fn resolve_property(name: &str) -> String { + if name.starts_with("https://") || name.starts_with("http://") || name.starts_with("did:") { + return name.to_string(); + } + match name { + "name" => urls::NAME.to_string(), + "description" => urls::DESCRIPTION.to_string(), + "parent" => urls::PARENT.to_string(), + "isA" | "is_a" | "isa" => urls::IS_A.to_string(), + "shortname" => urls::SHORTNAME.to_string(), + "datatype" => urls::DATATYPE_PROP.to_string(), + "read" => urls::READ.to_string(), + "write" => urls::WRITE.to_string(), + "children" => urls::CHILDREN.to_string(), + "drives" => urls::DRIVES.to_string(), + other => format!("https://atomicdata.dev/properties/{other}"), + } +} + +pub(crate) fn string_to_value(s: &str) -> Value { + if s.starts_with("https://") || s.starts_with("http://") || s.starts_with("did:") { + Value::AtomicUrl(s.into()) + } else { + Value::String(s.to_string()) + } +} diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs new file mode 100644 index 0000000000..aad08bf654 --- /dev/null +++ b/ffi/src/lib.rs @@ -0,0 +1,351 @@ +//! UniFFI surface over `atomic_lib`. +//! +//! Same product API as the Python SDK: local redb + Iroh P2P. Kotlin (and +//! later Swift) generate from this crate. PyO3 stays in `python/`. + +uniffi::setup_scaffolding!(); + +mod convert; +mod peer; +mod resource; +mod store; + +use std::sync::OnceLock; + +pub use peer::{PeerInfo, SyncReport}; +pub use resource::Resource; +pub use store::Store; + +#[derive(Debug, thiserror::Error, uniffi::Error)] +#[uniffi(flat_error)] +pub enum AtomicSdkError { + #[error("{0}")] + Failed(String), +} + +impl From for AtomicSdkError { + fn from(msg: String) -> Self { + Self::Failed(msg) + } +} + +impl From<&str> for AtomicSdkError { + fn from(msg: &str) -> Self { + Self::Failed(msg.to_string()) + } +} + +pub(crate) fn err(e: impl ToString) -> AtomicSdkError { + AtomicSdkError::Failed(e.to_string()) +} + +pub(crate) fn runtime() -> &'static tokio::runtime::Runtime { + static RT: OnceLock = OnceLock::new(); + RT.get_or_init(|| { + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .thread_name("atomic-ffi") + .build() + .expect("failed to start tokio runtime") + }) +} + +pub(crate) fn block_on(fut: F) -> F::Output { + runtime().block_on(fut) +} + +#[derive(uniffi::Record)] +pub struct SetupInfo { + pub agent_subject: String, + pub agent_secret: String, + pub drive_subject: String, +} + +#[derive(uniffi::Record)] +pub struct AgentInfo { + pub subject: String, + pub secret: String, + pub public_key: String, + pub name: Option, + pub drive_needs_sync: bool, +} + +#[derive(uniffi::Record)] +pub struct DriveInfo { + pub subject: String, + pub name: String, +} + +/// Well-known Atomic Data URLs, matching `atomic_data.urls` in Python. +#[uniffi::export] +pub fn url_folder() -> String { + atomic_lib::urls::FOLDER.to_string() +} + +#[uniffi::export] +pub fn url_plain_text() -> String { + atomic_lib::urls::PLAIN_TEXT.to_string() +} + +#[uniffi::export] +pub fn url_drive() -> String { + atomic_lib::urls::DRIVE.to_string() +} + +#[uniffi::export] +pub fn url_name() -> String { + atomic_lib::urls::NAME.to_string() +} + +#[uniffi::export] +pub fn url_description() -> String { + atomic_lib::urls::DESCRIPTION.to_string() +} + +#[uniffi::export] +pub fn url_parent() -> String { + atomic_lib::urls::PARENT.to_string() +} + +#[uniffi::export] +pub fn url_is_a() -> String { + atomic_lib::urls::IS_A.to_string() +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use super::*; + use crate::store::Store; + + #[test] + fn setup_creates_agent_and_drive() { + let store = Store::in_memory().unwrap(); + assert!(store.agent().unwrap().is_none()); + assert!(store.active_drive().is_none()); + + let info = store.setup("Ada".into()).unwrap(); + assert!(info.agent_subject.starts_with("did:ad:agent:")); + assert!(info.drive_subject.starts_with("did:ad:")); + assert!(!info.agent_secret.is_empty()); + assert_eq!( + store.active_drive().as_deref(), + Some(info.drive_subject.as_str()) + ); + assert!(store.has(info.drive_subject.clone())); + assert_eq!(store.drives().unwrap().len(), 1); + } + + #[test] + fn create_read_update() { + let store = Store::in_memory().unwrap(); + store.setup("Test agent".into()).unwrap(); + let mut props = HashMap::new(); + props.insert("description".into(), "first draft".into()); + let note = store + .create(url_plain_text(), "Hello".into(), None, Some(props)) + .unwrap(); + assert!(note.subject().starts_with("did:ad:")); + assert_eq!(note.get("name".into()), Some("Hello".into())); + assert_eq!(note.get("description".into()), Some("first draft".into())); + assert!(note.contains(url_parent())); + + note.set("description".into(), "second draft".into()) + .unwrap(); + note.set_name("Hello again".into()).unwrap(); + note.save().unwrap(); + + let got = store.get(note.subject()).unwrap().unwrap(); + assert_eq!(got.get("name".into()), Some("Hello again".into())); + assert_eq!(got.get("description".into()), Some("second draft".into())); + } + + #[test] + fn query_by_parent_and_class() { + let store = Store::in_memory().unwrap(); + store.setup("Test agent".into()).unwrap(); + let drive = store.active_drive().unwrap(); + let folder = store + .create(url_folder(), "Alpha".into(), None, None) + .unwrap(); + let text = store + .create( + url_plain_text(), + "Beta".into(), + None, + Some(HashMap::from([("description".into(), "body".into())])), + ) + .unwrap(); + store + .create( + url_plain_text(), + "Gamma".into(), + None, + Some(HashMap::from([("description".into(), "body".into())])), + ) + .unwrap(); + + let children = store + .query(Some(drive.clone()), None, None, None, None, 0) + .unwrap(); + let names: Vec<_> = children.iter().filter_map(|r| r.name()).collect(); + assert!(names.contains(&"Alpha".to_string())); + assert!(names.contains(&"Beta".to_string())); + assert!(names.contains(&"Gamma".to_string())); + + let folders = store + .query(Some(drive), Some(url_folder()), None, None, None, 0) + .unwrap(); + assert_eq!(folders.len(), 1); + assert_eq!(folders[0].subject(), folder.subject()); + + let texts = store + .query(None, Some(url_plain_text()), None, None, None, 0) + .unwrap(); + let subjects: Vec<_> = texts.iter().map(|r| r.subject()).collect(); + assert!(subjects.contains(&text.subject())); + assert!(!subjects.contains(&folder.subject())); + } + + #[test] + fn destroy_folder() { + let store = Store::in_memory().unwrap(); + store.setup("Agent".into()).unwrap(); + let folder = store + .create(url_folder(), "Tmp".into(), None, None) + .unwrap(); + let subject = folder.subject(); + folder.destroy_resource().unwrap(); + assert!(!store.has(subject.clone())); + assert!(store.get(subject).unwrap().is_none()); + } + + #[test] + fn delete_removes_resource() { + let store = Store::in_memory().unwrap(); + store.setup("Agent".into()).unwrap(); + let folder = store + .create(url_folder(), "ephemeral".into(), None, None) + .unwrap(); + let subject = folder.subject(); + drop(folder); + store.delete(subject.clone()).unwrap(); + assert!(store.get(subject).unwrap().is_none()); + } + + #[test] + fn missing_get_returns_none() { + let store = Store::in_memory().unwrap(); + store.setup("Agent".into()).unwrap(); + assert!(store.get("did:ad:does-not-exist".into()).unwrap().is_none()); + } + + #[test] + fn create_requires_parent_or_drive() { + let store = Store::in_memory().unwrap(); + store.create_agent("No drive".into()).unwrap(); + let err = store + .create(url_folder(), "orphan".into(), None, None) + .unwrap_err(); + assert!(err.to_string().contains("parent or an active drive")); + } + + #[test] + fn independent_in_memory_stores() { + let a = Store::in_memory().unwrap(); + let b = Store::in_memory().unwrap(); + let setup_a = a.setup("A".into()).unwrap(); + b.setup("B".into()).unwrap(); + let note = a + .create(url_folder(), "only-in-a".into(), None, None) + .unwrap(); + assert!(b.get(note.subject()).unwrap().is_none()); + assert!(a.get(setup_a.drive_subject).unwrap().is_some()); + } + + #[test] + fn to_json_includes_subject() { + let store = Store::in_memory().unwrap(); + store.setup("Agent".into()).unwrap(); + let note = store + .create(url_folder(), "serial".into(), None, None) + .unwrap(); + let json = note.to_json().unwrap(); + assert!(json.contains(¬e.subject())); + assert!(json.contains("serial")); + } + + #[test] + fn known_peers_persist_locally() { + let store = Store::in_memory().unwrap(); + store.setup("Ada".into()).unwrap(); + store.add_peer(format!("did:ad:node:{}", "cd".repeat(32)), "Phone".into()); + let peers = store.peers(); + assert_eq!(peers.len(), 1); + assert_eq!(peers[0].name, "Phone"); + assert!(peers[0].node_id.starts_with("did:ad:node:")); + } + + #[test] + fn sync_with_requires_start_peer() { + let store = Store::in_memory().unwrap(); + store.setup("Ada".into()).unwrap(); + let err = store + .sync_with(format!("did:ad:node:{}", "ab".repeat(32)), None) + .unwrap_err(); + assert!(err.to_string().contains("startPeer")); + } + + #[test] + fn wait_for_times_out() { + let store = Store::in_memory().unwrap(); + store.setup("Ada".into()).unwrap(); + let err = store + .wait_for("did:ad:does-not-change".into(), 0.2) + .unwrap_err(); + assert!(err.to_string().contains("timed out")); + } + + #[test] + fn reopen_file_store() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().to_string_lossy().to_string(); + let (subject, secret, drive) = { + let store = Store::open(path.clone()).unwrap(); + let setup = store.setup("Ada".into()).unwrap(); + let mut props = HashMap::new(); + props.insert("description".into(), "on disk".into()); + let note = store + .create(url_plain_text(), "Persisted".into(), None, Some(props)) + .unwrap(); + let subject = note.subject(); + store.flush().unwrap(); + drop(note); + drop(store); + (subject, setup.agent_secret, setup.drive_subject) + }; + + { + let store = Store::open(path.clone()).unwrap(); + let loaded = store.get(subject.clone()).unwrap().unwrap(); + assert_eq!(loaded.get("name".into()), Some("Persisted".into())); + assert_eq!(loaded.get("description".into()), Some("on disk".into())); + assert!(store.has(drive.clone())); + store.load_agent(secret).unwrap(); + store.set_active_drive(drive).unwrap(); + loaded + .set("description".into(), "edited after reopen".into()) + .unwrap(); + loaded.save().unwrap(); + store.flush().unwrap(); + } + + let store = Store::open(path).unwrap(); + let got = store.get(subject).unwrap().unwrap(); + assert_eq!( + got.get("description".into()), + Some("edited after reopen".into()) + ); + } +} diff --git a/ffi/src/peer.rs b/ffi/src/peer.rs new file mode 100644 index 0000000000..5dd6d3bb51 --- /dev/null +++ b/ffi/src/peer.rs @@ -0,0 +1,128 @@ +use std::time::Duration; + +use atomic_lib::storelike::Storelike; +use atomic_lib::{Db, Subject}; + +use crate::{block_on, err, AtomicSdkError}; + +#[derive(Debug, uniffi::Record)] +pub struct SyncReport { + pub imported: u32, + pub pushed: u32, + pub in_sync: bool, + pub peer_name: Option, +} + +#[derive(Debug, uniffi::Record)] +pub struct PeerInfo { + pub node_id: String, + pub name: String, +} + +pub(crate) fn node_uri(id: &str) -> String { + let hex = atomic_lib::sync::peer::normalize_node_id(id); + if hex.starts_with("did:ad:node:") { + hex + } else { + format!("did:ad:node:{hex}") + } +} + +pub(crate) fn publish_live(response: &atomic_lib::commit::CommitResponse) { + if let Some(bytes) = &response.commit.loro_update { + if !bytes.is_empty() { + let key = response.commit.subject.pure_id(); + atomic_lib::sync::peer::broadcast_live_update(&key, bytes); + } + } +} + +pub(crate) fn start_peer(db: &Db) -> Result { + if let Some(existing) = atomic_lib::sync::peer::get_node_id() { + return Ok(node_uri(existing)); + } + let (node_id, _router) = block_on(atomic_lib::sync::peer::start(db.clone())).map_err(err)?; + Ok(node_uri(&node_id.to_string())) +} + +pub(crate) fn peer_id() -> Option { + atomic_lib::sync::peer::get_node_id().map(node_uri) +} + +pub(crate) fn announce(db: &Db, drive: Option<&str>) -> Result<(), AtomicSdkError> { + let drive = match drive { + Some(d) => d.to_string(), + None => db + .get_active_drive() + .ok_or_else(|| AtomicSdkError::from("announce() needs a drive or an active drive"))?, + }; + let Some(node_id) = atomic_lib::sync::peer::get_node_id() else { + return Err(AtomicSdkError::from( + "Peer not started. Call startPeer() first.", + )); + }; + block_on(atomic_lib::discovery::publish_node_id(&drive, node_id)).map_err(err) +} + +pub(crate) fn sync_with( + db: &Db, + node_id: &str, + drive: Option<&str>, +) -> Result { + if atomic_lib::sync::peer::get_node_id().is_none() { + return Err(AtomicSdkError::from( + "Peer not started. Call startPeer() first.", + )); + } + let drive = match drive { + Some(d) => d.to_string(), + None => db + .get_active_drive() + .ok_or_else(|| AtomicSdkError::from("syncWith() needs a drive or an active drive"))?, + }; + let outcome = block_on(atomic_lib::sync::peer::sync_drive_with_peer_outcome( + node_id, &drive, db, + )) + .map_err(err)?; + Ok(SyncReport { + imported: outcome.count as u32, + pushed: outcome.pushed as u32, + in_sync: outcome.in_sync, + peer_name: outcome.peer_name, + }) +} + +async fn recv_change(db: &Db, target: Subject) -> String { + let mut rx = db.subscribe_events(); + loop { + match rx.recv().await { + Ok(atomic_lib::DbEvent::Changed { subject, .. }) if subject == target => { + return subject.to_string(); + } + Ok(atomic_lib::DbEvent::Destroyed { subject, .. }) if subject == target => { + return format!("!{subject}"); + } + Ok(_) => continue, + Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => continue, + Err(_) => { + tokio::time::sleep(Duration::from_millis(50)).await; + rx = db.subscribe_events(); + } + } + } +} + +pub(crate) fn wait_for( + db: &Db, + subject: &str, + timeout_secs: f64, +) -> Result { + let target = Subject::from_raw(subject, db.get_base_domain().as_deref()).without_params(); + let dur = Duration::from_secs_f64(timeout_secs.max(0.0)); + match block_on(async { tokio::time::timeout(dur, recv_change(db, target)).await }) { + Ok(s) => Ok(s), + Err(_) => Err(AtomicSdkError::from(format!( + "timed out waiting for {subject}" + ))), + } +} diff --git a/ffi/src/resource.rs b/ffi/src/resource.rs new file mode 100644 index 0000000000..68498a794a --- /dev/null +++ b/ffi/src/resource.rs @@ -0,0 +1,110 @@ +use std::sync::Mutex; + +use atomic_lib::{urls, Db}; + +use crate::convert::{resolve_property, string_to_value}; +use crate::{block_on, err, AtomicSdkError}; + +#[derive(uniffi::Object)] +pub struct Resource { + inner: Mutex, + db: Db, +} + +impl std::fmt::Debug for Resource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Resource") + .field("subject", &self.subject()) + .finish() + } +} + +impl Resource { + pub(crate) fn new(inner: atomic_lib::Resource, db: Db) -> Self { + Self { + inner: Mutex::new(inner), + db, + } + } +} + +#[uniffi::export] +impl Resource { + pub fn subject(&self) -> String { + self.inner.lock().unwrap().get_subject().to_string() + } + + pub fn name(&self) -> Option { + self.inner + .lock() + .unwrap() + .get(urls::NAME) + .ok() + .map(|v| v.to_string()) + } + + pub fn set_name(&self, name: String) -> Result<(), AtomicSdkError> { + self.inner + .lock() + .unwrap() + .set_unsafe(urls::NAME.into(), atomic_lib::Value::String(name)) + .map_err(err)?; + Ok(()) + } + + pub fn set(&self, property: String, value: String) -> Result<(), AtomicSdkError> { + let prop = resolve_property(&property); + self.inner + .lock() + .unwrap() + .set_unsafe(prop, string_to_value(&value)) + .map_err(err)?; + Ok(()) + } + + pub fn get(&self, property: String) -> Option { + let prop = resolve_property(&property); + self.inner + .lock() + .unwrap() + .get(&prop) + .ok() + .map(|v| v.to_string()) + } + + pub fn contains(&self, property: String) -> bool { + let prop = resolve_property(&property); + self.inner.lock().unwrap().get(&prop).is_ok() + } + + pub fn keys(&self) -> Vec { + self.inner + .lock() + .unwrap() + .get_propvals() + .keys() + .cloned() + .collect() + } + + pub fn to_json(&self) -> Result { + self.inner.lock().unwrap().to_json_ad(None).map_err(err) + } + + pub fn save(&self) -> Result<(), AtomicSdkError> { + let mut inner = self.inner.lock().unwrap(); + let response = block_on(inner.save_locally(&self.db)).map_err(err)?; + crate::peer::publish_live(&response); + Ok(()) + } + + /// Delete this resource from the store. + /// + /// Named `destroy_resource` so UniFFI Kotlin does not collide with + /// `Disposable.destroy()` (the FFI handle teardown). + pub fn destroy_resource(&self) -> Result<(), AtomicSdkError> { + let mut inner = self.inner.lock().unwrap(); + block_on(inner.destroy(&self.db)).map_err(err)?; + Ok(()) + } +} diff --git a/ffi/src/store.rs b/ffi/src/store.rs new file mode 100644 index 0000000000..b89dba7206 --- /dev/null +++ b/ffi/src/store.rs @@ -0,0 +1,258 @@ +use std::collections::HashMap; +use std::sync::Arc; + +use atomic_lib::storelike::{Query, Storelike}; +use atomic_lib::{urls, Db, Subject, Value}; + +use crate::convert::{resolve_property, string_to_value}; +use crate::resource::Resource; +use crate::{block_on, err, AgentInfo, AtomicSdkError, DriveInfo, PeerInfo, SetupInfo, SyncReport}; + +#[derive(uniffi::Object)] +pub struct Store { + pub(crate) db: Db, +} + +impl Store { + fn subject(&self, raw: &str) -> Subject { + Subject::from_raw(raw, self.db.get_base_domain().as_deref()) + } + + pub(crate) fn wrap(&self, resource: atomic_lib::Resource) -> Arc { + Arc::new(Resource::new(resource, self.db.clone())) + } +} + +#[uniffi::export] +impl Store { + #[uniffi::constructor] + pub fn open(path: String) -> Result, AtomicSdkError> { + let base = std::path::Path::new(&path); + let uploads = base.join("uploads"); + let db = block_on(Db::init_redb_file(base, None, &uploads)).map_err(err)?; + Ok(Arc::new(Store { db })) + } + + #[uniffi::constructor] + pub fn in_memory() -> Result, AtomicSdkError> { + let db = block_on(Db::init_redb(None)).map_err(err)?; + Ok(Arc::new(Store { db })) + } + + pub fn setup(&self, name: String) -> Result { + let (agent, drive_subject) = block_on(self.db.setup(&name)).map_err(err)?; + let secret = agent.build_secret().map_err(err)?; + Ok(SetupInfo { + agent_subject: agent.subject.to_string(), + agent_secret: secret, + drive_subject, + }) + } + + pub fn create_agent(&self, name: String) -> Result { + let agent = block_on(self.db.create_agent(Some(&name))).map_err(err)?; + self.db.set_default_agent(agent.clone()); + agent_info(agent, false) + } + + pub fn load_agent(&self, secret: String) -> Result { + let result = block_on(self.db.load_agent_from_secret(&secret)).map_err(err)?; + agent_info(result.agent, result.drive_needs_sync) + } + + pub fn agent(&self) -> Result, AtomicSdkError> { + match self.db.get_default_agent() { + Ok(agent) => Ok(Some(agent_info(agent, false)?)), + Err(_) => Ok(None), + } + } + + pub fn create_drive(&self, name: String) -> Result { + block_on(self.db.create_drive(&name)).map_err(err) + } + + pub fn drives(&self) -> Result, AtomicSdkError> { + let drives = block_on(self.db.list_drives()).map_err(err)?; + Ok(drives + .into_iter() + .map(|d| DriveInfo { + subject: d.subject, + name: d.name, + }) + .collect()) + } + + pub fn active_drive(&self) -> Option { + self.db.get_active_drive() + } + + pub fn set_active_drive(&self, subject: String) -> Result<(), AtomicSdkError> { + self.db.set_active_drive(&subject).map_err(err) + } + + pub fn create( + &self, + class_url: String, + name: String, + parent: Option, + properties: Option>, + ) -> Result, AtomicSdkError> { + let parent = match parent { + Some(p) => p, + None => self.db.get_active_drive().ok_or_else(|| { + AtomicSdkError::from("create() needs a parent or an active drive") + })?, + }; + let extra_owned: Vec<(String, Value)> = properties + .unwrap_or_default() + .into_iter() + .map(|(k, v)| (resolve_property(&k), string_to_value(&v))) + .collect(); + let extra_refs: Vec<(&str, Value)> = extra_owned + .iter() + .map(|(k, v)| (k.as_str(), v.clone())) + .collect(); + let extra = if extra_refs.is_empty() { + None + } else { + Some(extra_refs) + }; + let subject = + block_on(self.db.create_resource(&class_url, &parent, &name, extra)).map_err(err)?; + self.get(subject)? + .ok_or_else(|| AtomicSdkError::from("created resource but could not read it back")) + } + + pub fn get(&self, subject: String) -> Result>, AtomicSdkError> { + let subject = self.subject(&subject); + match block_on(self.db.get_resource(&subject)) { + Ok(resource) => Ok(Some(self.wrap(resource))), + Err(_) => Ok(None), + } + } + + pub fn has(&self, subject: String) -> bool { + self.db.has_resource_locally(&subject) + } + + pub fn query( + &self, + parent: Option, + class_url: Option, + property: Option, + value: Option, + limit: Option, + offset: u32, + ) -> Result>, AtomicSdkError> { + let mut parent = parent; + if parent.is_none() && class_url.is_none() && property.is_none() { + parent = self.db.get_active_drive(); + if parent.is_none() { + return Err(AtomicSdkError::from( + "query() needs a filter or an active drive", + )); + } + } + + let mut q = Query::new(); + q.include_nested = true; + q.limit = limit.map(|n| n as usize); + q.offset = offset as usize; + if let Some(drive) = self.db.get_active_drive().or_else(|| parent.clone()) { + q.drive = Some(self.subject(&drive)); + } + if let Some(class) = &class_url { + q = q.class_filter(class); + } + if let Some(parent) = &parent { + q = q.filter(urls::PARENT, Value::String(parent.clone())); + } + if let Some(prop) = property { + let val = value + .ok_or_else(|| AtomicSdkError::from("query(property=...) also needs value="))?; + q.property = Some(resolve_property(&prop)); + q.value = Some(string_to_value(&val)); + } + + let result = block_on(self.db.query(&q)).map_err(err)?; + Ok(result.resources.into_iter().map(|r| self.wrap(r)).collect()) + } + + pub fn delete(&self, subject: String) -> Result<(), AtomicSdkError> { + let resource = self + .get(subject.clone())? + .ok_or_else(|| AtomicSdkError::from(format!("not found: {subject}")))?; + resource.destroy_resource() + } + + pub fn flush(&self) -> Result<(), AtomicSdkError> { + self.db.flush().map_err(err) + } + + pub fn start_peer(&self) -> Result { + crate::peer::start_peer(&self.db) + } + + pub fn peer_id(&self) -> Option { + crate::peer::peer_id() + } + + pub fn announce(&self, drive: Option) -> Result<(), AtomicSdkError> { + crate::peer::announce(&self.db, drive.as_deref()) + } + + pub fn sync_with( + &self, + node_id: String, + drive: Option, + ) -> Result { + crate::peer::sync_with(&self.db, &node_id, drive.as_deref()) + } + + pub fn add_peer(&self, node_id: String, name: String) { + atomic_lib::sync::peer::add_known_peer(&self.db, &node_id, &name); + } + + pub fn peers(&self) -> Vec { + atomic_lib::sync::peer::get_known_peers(&self.db) + .into_iter() + .map(|p| PeerInfo { + node_id: crate::peer::node_uri(&p.node_id), + name: p.name, + }) + .collect() + } + + pub fn live_peers(&self) -> Vec { + atomic_lib::sync::peer::live_peer_ids() + .into_iter() + .map(|id| crate::peer::node_uri(&id)) + .collect() + } + + pub fn wait_for(&self, subject: String, timeout_secs: f64) -> Result { + crate::peer::wait_for(&self.db, &subject, timeout_secs) + } + + pub fn device_name(&self) -> String { + atomic_lib::sync::peer::get_device_name(&self.db) + } + + pub fn set_device_name(&self, name: String) { + atomic_lib::sync::peer::set_device_name(&self.db, &name); + } +} + +fn agent_info( + agent: atomic_lib::agents::Agent, + drive_needs_sync: bool, +) -> Result { + let secret = agent.build_secret().map_err(err)?; + Ok(AgentInfo { + subject: agent.subject.to_string(), + secret, + public_key: agent.public_key, + name: agent.name, + drive_needs_sync, + }) +} diff --git a/ffi/uniffi.toml b/ffi/uniffi.toml new file mode 100644 index 0000000000..5855ba6389 --- /dev/null +++ b/ffi/uniffi.toml @@ -0,0 +1,3 @@ +[bindings.kotlin] +package_name = "dev.atomicdata" +cdylib_name = "atomic_ffi" diff --git a/planning/README.md b/planning/README.md index 9f936a39b9..1992c94c16 100644 --- a/planning/README.md +++ b/planning/README.md @@ -126,7 +126,6 @@ Design intent, kept for direction. None has a line of code behind it. | --- | --- | | [`reticulum-sync.md`](./reticulum-sync.md) | Atomic sync protocol over Reticulum. | | [`nextgraph-interop.md`](./nextgraph-interop.md) | `did:ng:` via a scheme-routed Store backend. PR #1360 (optional NextGraph mirror) is open. | -| [`kotlin-sdk.md`](./kotlin-sdk.md) | **Decision.** Kotlin v1 matches Python's local CRUD scope via UniFFI over a shared Rust surface. | | [`llm-wasm-gui-plugins.md`](./llm-wasm-gui-plugins.md) | **Proposal.** Browser-built JS/TS apps with scoped Loro docs. | | [`personal-information-suite.md`](./personal-information-suite.md) | Contacts, calendar, email. | | [`social-apps.md`](./social-apps.md) | Requirements for social-network-shaped apps. Companion to `zones.md`. | @@ -173,7 +172,8 @@ the document itself. | [`demo-experience.md`](./demo-experience.md) | v1 and v2 demo workspace. | | [`local-search.md`](./local-search.md) | KV inverted index in `atomic_lib` (redb/OPFS/sled): BM25 + prefix + 1-edit prefix-fuzzy + PropValSub filters. Hosted `/search` is the same engine; Tantivy is gone. | | [`cloud-sync-managed-node.md`](./cloud-sync-managed-node.md) | Onboarding, managed-node detection, enrollment, heartbeat and replication pull. Verified against the SaaS `LocalProcessNodeProvider` only; the sync-path admission gate and reaper are not built. | -| [`python-sdk.md`](./python-sdk.md) | Python bindings over `atomic_lib` via PyO3 (local redb, read/write/query). | +| [`python-sdk.md`](./python-sdk.md) | Python bindings over `atomic_lib` via PyO3 (local redb, Iroh). | +| [`kotlin-sdk.md`](./kotlin-sdk.md) | UniFFI crate `ffi/` + Kotlin package `dev.atomicdata`. Same local redb + Iroh surface as Python. Android AAR still open. | | [`completed/commit-fanout-drive-isolation.md`](./completed/commit-fanout-drive-isolation.md) | Drive-scoped WS commit fan-out + server-side drive safety net. | | [`completed/cleanup-update-encoding.md`](./completed/cleanup-update-encoding.md) | Unified `decode_update`; TS client exports compact Loro deltas. | | [`completed/sign-at-drain.md`](./completed/sign-at-drain.md) | One signed commit per dirty subject per drain pass. | diff --git a/planning/SDK-API-design.md b/planning/SDK-API-design.md index e2dd35e70f..fce1da9532 100644 --- a/planning/SDK-API-design.md +++ b/planning/SDK-API-design.md @@ -22,8 +22,8 @@ In the "old" HTTP based Atomic(Server) UX, an app developer had to: - Server & connection to it is optional, only required for back-up. - Flutter library for native iOS and android apps -- Python SDK (`python/`, import `atomic_data`) wrapping `atomic_lib` via PyO3 — local read/write/query/persist. See [`python-sdk.md`](./python-sdk.md). -- Kotlin (not built): same v1 scope, UniFFI over a shared Rust `sdk` surface — not another hand-rolled FFI. See [`kotlin-sdk.md`](./kotlin-sdk.md). Unlocks Swift and the Android Binder host. +- Python SDK (`python/`, import `atomic_data`) wrapping `atomic_lib` via PyO3 — local read/write/query/persist + Iroh. See [`python-sdk.md`](./python-sdk.md). +- Kotlin SDK (`ffi/`, package `dev.atomicdata`) wrapping `atomic_lib` via UniFFI — same v1 surface. See [`kotlin-sdk.md`](./kotlin-sdk.md). Swift and the Android Binder host generate from this crate later. - Still no full, end-to-end atomic app building tutorial available ## Future situation diff --git a/planning/kotlin-sdk.md b/planning/kotlin-sdk.md index 800c512b57..91bdf6caa3 100644 --- a/planning/kotlin-sdk.md +++ b/planning/kotlin-sdk.md @@ -2,9 +2,10 @@ ## Status -Decision, not built. Same *product* scope as the Python v1 SDK -([`python-sdk.md`](./python-sdk.md)): local read / write / query / persist -over `atomic_lib`. Different *binding*. +v1 implemented for JVM: local read / write / query / persist plus Iroh P2P. +Crate lives in `ffi/` (`atomic-ffi`), Kotlin package `dev.atomicdata`. + +Android AAR / `cargo-ndk` and the Binder host are **not** built. ## Decision @@ -20,64 +21,62 @@ Kotlin is the first language where UniFFI is the obvious tool: engine**. FRB cannot be that entry point. - [`virtual-drive.md`](./virtual-drive.md) needs the same Swift + Kotlin pair for File Provider / DocumentsProvider. -- One UDL gives Swift for free when iOS wants the same v1 surface. +- One UniFFI surface gives Swift for free when iOS wants the same v1 API. Do **not** copy `python/src/*.rs` into a `kotlin/` crate and re-wrap `Db`. -That is how the binding zoo grows. The Python v1 API is the *shape* to -expose, not the code to duplicate. +`ffi/` is that shared surface. Python keeps its PyO3 skin. -## Shared Rust surface first +## Shared Rust surface -Before (or as the first commit of) the Kotlin work, extract the generic -store API into one Rust module that every binding calls: +`ffi/` is a small crate (excluded from the Cargo workspace) that every +non-Python / non-Flutter binding should call: ```text -atomic_lib::sdk (or a small atomic-ffi crate) +atomic-ffi open / in_memory setup / load_agent / create_agent create_drive / list_drives create / get / query / delete / flush - Resource: get/set/save/destroy / to_json + Resource: get/set/save/destroy_resource / to_json + start_peer / sync_with / announce / wait_for ``` Callers: | Binding | Today | Target | | --- | --- | --- | -| Python | PyO3 talks to `Db` directly | keep PyO3; point it at `sdk` when convenient | -| Flutter | FRB `simple.rs` mixes store + canvas | store group calls `sdk`; canvas stays FRB | -| Kotlin / Swift | none | UniFFI over `sdk` | +| Python | PyO3 talks to `Db` directly | keep PyO3; point it at `ffi`/`sdk` when convenient | +| Flutter | FRB `simple.rs` mixes store + canvas | store group can call the same helpers; canvas stays FRB | +| Kotlin / Swift | UniFFI over `ffi/` (Kotlin JVM done) | Swift generate from the same crate | | Android host | none | same UniFFI, then Binder in Kotlin | | WASM | `ClientDb` | later; OPFS constraints differ | This is a thin slice of [`atomic-lib-runtime.md`](./atomic-lib-runtime.md) -`AtomicNode` — enough for local CRUD, not events / outbox / transport yet. - -`flutter/rust/src/api/simple.rs` already listed the groups. Steal those, drop -canvas/history/peer from v1. +`AtomicNode` — enough for local CRUD + Iroh, not events / outbox / WS yet. ## v1 Kotlin product scope (match Python) ```kotlin val store = Store.open(path) // or Store.inMemory() val setup = store.setup("Ada") -val note = store.create(Urls.PLAIN_TEXT, name = "Hello", parent = setup.driveSubject) +val note = store.create(Urls.PLAIN_TEXT, "Hello", setup.driveSubject, mapOf("description" to "offline")) note.set(Urls.DESCRIPTION, "offline") note.save() -val got = store.get(note.subject) -store.query(parent = setup.driveSubject) +val got = store.get(note.subject()) +store.query(setup.driveSubject, null, null, null, null, 0u) store.flush() +val node = store.startPeer() +// other.syncWith(node, null) ``` -Ship as an AAR (`dev.atomicdata:sdk`) for JVM + Android. JVM-only is enough -to test the binding; Android needs `cargo-ndk` and rustls-platform-verifier -JNI init (known pitfall from the Android build work). +`Resource.destroyResource()` deletes the resource. UniFFI already uses +`destroy()` for FFI handle teardown, so the method cannot be named +`destroy()` in Kotlin. Iroh belongs in Kotlin v1 the same way it does in Python — local CRUD -without P2P is not the product. Wrap the same `start_peer` / `sync_with` / -live-save surface. A later `SyncSession` can sit on top; do not ship Kotlin -without a way for two devices to converge. +without P2P is not the product. `startPeer` is process-global (one Iroh +`Router` / NodeID per OS process). Two nodes = two processes. ## What Kotlin adds that Python does not @@ -90,29 +89,24 @@ without a way for two devices to converge. - **redb exclusive lock.** Same as Python: one process owns the file. On Android that is the elected host, not "whoever imported the AAR". -## Layout (when built) +## Layout ```text -ffi/ # or kotlin/ — excluded from the Cargo workspace - Cargo.toml # uniffi, atomic_lib (db-redb) - src/lib.rs # thin UniFFI scaffolding - src/atomic.udl # the sdk surface - android/ # Gradle AAR - tests/ # JVM unit tests against in-memory store +ffi/ # excluded from the Cargo workspace + Cargo.toml # uniffi, atomic_lib (db-redb, iroh, discovery) + src/lib.rs # UniFFI scaffolding + src/{store,resource,peer,convert}.rs + uniffi.toml # package_name = dev.atomicdata + kotlin/ # Gradle JVM + generated bindings + JUnit + generate-kotlin.sh ``` -Exclude from the workspace like `python/` and `flutter/rust`. - -## Order - -1. Extract `atomic_lib::sdk` (or `atomic-ffi`) with in-process Rust tests. - No new language yet. This is the useful prerequisite. -2. UniFFI + JVM tests for the same cases as `python/tests/`. -3. Android AAR + `cargo-ndk`. -4. Only then Binder host/client (`android-data-reuse.md`). +## Still open -Skipping step 1 and writing Kotlin-only wrappers around `Db` will have to -be rewritten when Swift, the Android host, and `AtomicNode` show up. +1. Android AAR + `cargo-ndk` + rustls-platform-verifier JNI init. +2. Swift generate from the same crate. +3. Binder host/client (`android-data-reuse.md`). +4. Point Python at a shared `atomic_lib::sdk` (optional; PyO3 stays). ## Not this work diff --git a/planning/python-sdk.md b/planning/python-sdk.md index 1fba2784f7..c7a1d482eb 100644 --- a/planning/python-sdk.md +++ b/planning/python-sdk.md @@ -20,9 +20,9 @@ UniFFI would help if one IDL had to generate Swift + Kotlin + Python. The Python API can be idiomatic on its own (dict-like `Resource`, sync methods, context manager). Flutter already has its own bridge. -Kotlin is next and **does** use UniFFI — see [`kotlin-sdk.md`](./kotlin-sdk.md). -Do not clone `python/src` for that. Extract a shared `atomic_lib::sdk` -surface first; PyO3 can keep its skin. +Kotlin uses UniFFI in `ffi/` — see [`kotlin-sdk.md`](./kotlin-sdk.md). +Do not clone `python/src` for that. PyO3 keeps its skin; a later +`atomic_lib::sdk` can sit under both. The crate is **excluded from the Cargo workspace** (same reason as `flutter/rust`): workspace `clippy` / `nextest` should not compile PyO3 on diff --git a/python/README.md b/python/README.md index 1b8a74a23e..d8c6ccaad0 100644 --- a/python/README.md +++ b/python/README.md @@ -18,6 +18,7 @@ The other language bindings already follow this shape: | TypeScript (browser) | `wasm-bindgen` | redb in OPFS | | Dart / Flutter | `flutter_rust_bridge` | redb on disk | | Python | **PyO3 + maturin** | redb on disk | +| Kotlin / JVM | UniFFI (`ffi/`) | redb on disk | Reimplementing commits, Ed25519, and Loro in Python would drift. UniFFI is a better fit when one IDL must generate Swift + Kotlin + Python; here the Python From 3d18540658797b9d700c4d2bd8e8b09884055dc3 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 19 Aug 2026 13:50:49 +0000 Subject: [PATCH 09/16] Document Windows MSVC for the Python SDK and test it in CI. uv run pytest is now the documented path and works from python/ (uv.lock, dependency-groups). A host without Visual Studio Build Tools still cannot link the PyO3 extension; GitHub Actions runs the same command on windows-latest, which has MSVC, and on ubuntu-latest. Co-authored-by: joepmeindertsma --- .github/workflows/python-sdk.yml | 46 +++++++ AGENTS.md | 2 +- TESTING_COVERAGE.md | 9 +- docs/src/python.md | 15 ++- planning/python-sdk.md | 4 +- python/README.md | 26 +++- python/pyproject.toml | 6 + python/uv.lock | 208 +++++++++++++++++++++++++++++++ 8 files changed, 304 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/python-sdk.yml create mode 100644 python/uv.lock diff --git a/.github/workflows/python-sdk.yml b/.github/workflows/python-sdk.yml new file mode 100644 index 0000000000..e9a93c00d3 --- /dev/null +++ b/.github/workflows/python-sdk.yml @@ -0,0 +1,46 @@ +# Python SDK tests (not part of Dagger). +# +# The extension is a PyO3 cdylib. On Windows that needs MSVC's linker; +# ubuntu-latest and windows-latest both have one. A machine without +# Visual Studio Build Tools cannot `uv run pytest` from source. + +name: Python SDK + +on: + pull_request: + paths: + - "python/**" + - "lib/**" + - ".github/workflows/python-sdk.yml" + push: + paths: + - "python/**" + - "lib/**" + - ".github/workflows/python-sdk.yml" + +permissions: + contents: read + +jobs: + test: + name: pytest (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + timeout-minutes: 45 + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + defaults: + run: + working-directory: python + env: + CARGO_TERM_COLOR: always + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + with: + workspaces: python + - uses: astral-sh/setup-uv@v10.0.1 + - name: uv run pytest + run: uv run pytest -q diff --git a/AGENTS.md b/AGENTS.md index 0f6789da05..5980752f07 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -283,7 +283,7 @@ cd browser/lib && pnpm test # JS unit tests cd browser && pnpm run -r build # Full workspace build cd browser && pnpm run test-e2e:light # Playwright @smoke (feature-branch CI) cd browser && pnpm run test-e2e # Full Playwright suite (develop / tags) -cd python && maturin develop && pytest # Python SDK (excluded from workspace) +cd python && uv run pytest # Python SDK (excluded from workspace) cd ffi && cargo test && cd kotlin && ./gradlew test # Kotlin / UniFFI SDK (excluded from workspace) ``` diff --git a/TESTING_COVERAGE.md b/TESTING_COVERAGE.md index 2e9d05e3e0..474a79b094 100644 --- a/TESTING_COVERAGE.md +++ b/TESTING_COVERAGE.md @@ -449,7 +449,7 @@ templates, and offline variants stay in the full suite. Policy: | Browser e2e full | `cd browser && pnpm run test-e2e` | `endToEnd` on `develop` and `v*` tags | | Flutter Dart | `cd flutter && flutter test` | `flutterTest` | | Flutter Rust bridge | `cargo test --manifest-path flutter/rust/Cargo.toml` | `flutterTest` | -| Python SDK | `cd python && maturin develop && pytest` | **not in CI** | +| Python SDK | `cd python && uv run pytest` | `Python SDK` (ubuntu + windows) | | Kotlin / UniFFI SDK | `cd ffi && cargo test && cd kotlin && ./gradlew test` | **not in CI** | CI runs `cargo nextest run --workspace --exclude atomic-server-tauri @@ -462,7 +462,10 @@ Two things worth knowing about the runners: `--workspace` never compiles it. It is covered only by the explicit `--manifest-path` step in `flutterTest`. - **`python/` is excluded from the workspace** for the same reason (PyO3). - Tests are `pytest` after `maturin develop`; they are not in Dagger CI yet. + Tests are `uv run pytest` in `python/`. GitHub Actions workflow + `Python SDK` runs them on ubuntu-latest and windows-latest (MSVC is on + the Windows runner; a host without Visual Studio Build Tools cannot + link the extension). Not in Dagger. - **`ffi/` is excluded from the workspace** (UniFFI + Iroh). Rust tests are `cargo test` in `ffi/`; JVM tests are `./gradlew test` in `ffi/kotlin`. Not in Dagger CI yet. @@ -653,7 +656,7 @@ One 13-line smoke test, never run in CI — the pipeline has no emulator. ### 7b. Python SDK (`python/`) -Glue: `pytest` after `maturin build` covers in-memory CRUD, file-backed reopen, and a two-process Iroh sync (`tests/test_iroh.py`). Not in Dagger CI. No WS session or blobs. +Glue: `uv run pytest` covers in-memory CRUD, file-backed reopen, and a two-process Iroh sync (`tests/test_iroh.py`). GitHub Actions `Python SDK` runs that on Linux and Windows. Not in Dagger. No WS session or blobs. ### 7c. Kotlin / UniFFI SDK (`ffi/`) diff --git a/docs/src/python.md b/docs/src/python.md index 21463738d1..0c08257f1b 100644 --- a/docs/src/python.md +++ b/docs/src/python.md @@ -12,7 +12,20 @@ Source: [`python/`](https://github.com/atomicdata-dev/atomic-server/tree/develop ## Install -From a checkout of this repo (needs a Rust toolchain and Python 3.9+): +From a checkout of this repo (needs a Rust toolchain and Python 3.9+). +There is no PyPI wheel yet, so the first install compiles `atomic_lib`. + +On Windows, also install [Visual Studio Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/) +with the **Desktop development with C++** workload. `uv` / maturin link +with MSVC's `link.exe`; GNU/MinGW is not supported. + +```bash +cd python +uv sync +uv run pytest -q +``` + +Or without uv: ```bash pip install maturin diff --git a/planning/python-sdk.md b/planning/python-sdk.md index c7a1d482eb..09bac20337 100644 --- a/planning/python-sdk.md +++ b/planning/python-sdk.md @@ -59,10 +59,10 @@ makes two devices converge today. - Blobs - History / time-travel - Code-first schema (see `json-schema-code-first.md`) -- PyPI publish / Dagger CI job +- PyPI publish / Dagger CI job (GitHub Actions `Python SDK` covers pytest on Linux and Windows) ## Tests ```bash -cd python && maturin develop && pytest +cd python && uv run pytest ``` diff --git a/python/README.md b/python/README.md index d8c6ccaad0..e60f565163 100644 --- a/python/README.md +++ b/python/README.md @@ -29,12 +29,29 @@ to another device, `sync_with()`. After that, `.save()` pushes live. ## Install (from this repo) -Needs a Rust toolchain and Python 3.9+. +Needs a Rust toolchain and Python 3.9+. There is no PyPI wheel yet, so +the first install compiles `atomic_lib`. + +On **Windows** that compile needs [Visual Studio Build Tools] with the +**Desktop development with C++** workload (`link.exe`). GNU/MinGW is not +a supported target. `uv run pytest` will fail at link time without those +tools — that is the host, not the package. + +[Visual Studio Build Tools]: https://visualstudio.microsoft.com/visual-cpp-build-tools/ + +```bash +cd python +uv sync +uv run pytest -q +``` + +Or without uv: ```bash -pip install maturin +pip install maturin pytest cd python maturin develop +pytest ``` Then `import atomic_data`. @@ -103,7 +120,6 @@ Property keys accept full URLs or shortnames (`name`, `description`, `parent`, ```bash cd python -maturin develop -pip install pytest -pytest +uv sync +uv run pytest -q ``` diff --git a/python/pyproject.toml b/python/pyproject.toml index e80b729b3c..9874bb181d 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -34,6 +34,12 @@ Repository = "https://github.com/atomicdata-dev/atomic-server" [project.optional-dependencies] dev = ["pytest>=8"] +[dependency-groups] +dev = ["pytest>=8"] + +[tool.uv] +default-groups = ["dev"] + [tool.maturin] features = ["pyo3/extension-module"] module-name = "atomic_data" diff --git a/python/uv.lock b/python/uv.lock new file mode 100644 index 0000000000..078fc06f14 --- /dev/null +++ b/python/uv.lock @@ -0,0 +1,208 @@ +version = 1 +revision = 3 +requires-python = ">=3.9" +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version < '3.10'", +] + +[[package]] +name = "atomic-data" +version = "0.1.0" +source = { editable = "." } + +[package.optional-dependencies] +dev = [ + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pytest", version = "9.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pytest", version = "9.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] + +[package.metadata] +requires-dist = [{ name = "pytest", marker = "extra == 'dev'", specifier = ">=8" }] +provides-extras = ["dev"] + +[package.metadata.requires-dev] +dev = [{ name = "pytest", specifier = ">=8" }] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "packaging" +version = "26.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pygments" +version = "2.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup" }, + { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" } }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + +[[package]] +name = "pytest" +version = "9.1.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" } }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, +] + +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] From c2d08e583787d363e95c7757e232c447160c0ec9 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 19 Aug 2026 15:31:00 +0000 Subject: [PATCH 10/16] Expose HTTP schema fetch, search, and save_remote on the Python and Kotlin SDKs. The stores were already calling atomic_lib's HTTP GET for unknown https:// subjects (that is how Class/Property validation loads remote schema). Document and wrap that path, plus optional server= for /search and POST /commit. Co-authored-by: joepmeindertsma --- AGENTS.md | 4 +- CHANGELOG.md | 4 +- TESTING_COVERAGE.md | 11 +- docs/src/kotlin.md | 20 ++- docs/src/python.md | 21 ++- docs/src/tooling.md | 4 +- ffi/README.md | 15 ++- .../test/kotlin/dev/atomicdata/StoreTest.kt | 48 +++++++ ffi/src/lib.rs | 74 ++++++++--- ffi/src/resource.rs | 9 ++ ffi/src/store.rs | 120 ++++++++++++------ planning/README.md | 4 +- planning/kotlin-sdk.md | 9 +- planning/python-sdk.md | 10 +- python/README.md | 36 ++++-- python/src/lib.rs | 6 +- python/src/resource.rs | 8 ++ python/src/store.rs | 60 ++++++++- python/tests/test_sdk.py | 42 ++++++ 19 files changed, 412 insertions(+), 93 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 5980752f07..f7ee26a39d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -107,8 +107,8 @@ Atomic Server is a graph database with real-time sync, built on **Loro CRDT** fo - **`@tomic/react`** (`browser/react/`) — React hooks. - **`data-browser`** (`browser/data-browser/`) — The web app (React + TipTap + Loro), feels similar to notion. See the related AGENTS.md - **`flutter/`** — Cross-platform canvas app (Android/iOS/Web). Uses `flutter_rust_bridge` to call `atomic_lib`. See `flutter/README.md` and `flutter/AGENTS.md`. -- **`python/`** — Python SDK (`atomic_data`). PyO3 bindings over `atomic_lib` (local redb). Excluded from the Cargo workspace; build with `maturin`. See `python/README.md` and `planning/python-sdk.md`. -- **`ffi/`** — UniFFI crate (`atomic-ffi`) and Kotlin SDK (`dev.atomicdata`). Same local redb + Iroh surface as Python. Excluded from the Cargo workspace. See `ffi/README.md` and `planning/kotlin-sdk.md`. +- **`python/`** — Python SDK (`atomic_data`). PyO3 bindings over `atomic_lib` (local redb, Iroh, HTTP schema/search/`save_remote`). Excluded from the Cargo workspace; build with `maturin`. See `python/README.md` and `planning/python-sdk.md`. +- **`ffi/`** — UniFFI crate (`atomic-ffi`) and Kotlin SDK (`dev.atomicdata`). Same local redb + Iroh + HTTP surface as Python. Excluded from the Cargo workspace. See `ffi/README.md` and `planning/kotlin-sdk.md`. ### Data model diff --git a/CHANGELOG.md b/CHANGELOG.md index c433334c34..47a6defd54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -363,8 +363,8 @@ See [STATUS.md](server/STATUS.md) to learn more about which features will remain `ingest_commit_json` serializes; `sync::ws_apply::apply_commit_json` now returns the `CommitResponse` instead of `()`. See `planning/runtime-boundary-decision.md`. -- **Python SDK** (`python/`, import `atomic_data`): bindings over `atomic_lib` via PyO3. Local redb plus Iroh P2P (`start_peer`, `sync_with`, live push on save). No server required. -- **Kotlin SDK** (`ffi/`, package `dev.atomicdata`): UniFFI bindings over `atomic_lib`. Same local redb + Iroh surface as Python (`startPeer`, `syncWith`, live push on save). JVM tests included; Android AAR is later. +- **Python SDK** (`python/`, import `atomic_data`): bindings over `atomic_lib` via PyO3. Local redb plus Iroh P2P (`start_peer`, `sync_with`, live push on save). HTTP GET of `https://` subjects (schema / external resources); optional `server=` for `/search` and `save_remote()`. +- **Kotlin SDK** (`ffi/`, package `dev.atomicdata`): UniFFI bindings over `atomic_lib`. Same local redb + Iroh + HTTP surface as Python (`startPeer`, `syncWith`, `search`, `saveRemote`). JVM tests included; Android AAR is later. ## [v0.41.0-beta.2] - 2026-08-01 diff --git a/TESTING_COVERAGE.md b/TESTING_COVERAGE.md index 474a79b094..b800aa02e6 100644 --- a/TESTING_COVERAGE.md +++ b/TESTING_COVERAGE.md @@ -656,11 +656,18 @@ One 13-line smoke test, never run in CI — the pipeline has no emulator. ### 7b. Python SDK (`python/`) -Glue: `uv run pytest` covers in-memory CRUD, file-backed reopen, and a two-process Iroh sync (`tests/test_iroh.py`). GitHub Actions `Python SDK` runs that on Linux and Windows. Not in Dagger. No WS session or blobs. +Glue: `uv run pytest` covers in-memory CRUD, file-backed reopen, HTTP schema +`get()` / `search()`-requires-server / `save_remote()` error path, and a +two-process Iroh sync (`tests/test_iroh.py`). GitHub Actions `Python SDK` +runs that on Linux and Windows. Not in Dagger. No WS session or blobs. ### 7c. Kotlin / UniFFI SDK (`ffi/`) -Glue: `cargo test` in `ffi/` covers the same local CRUD + Iroh-guard cases as Python, in-process. `ffi/kotlin` JUnit covers the generated bindings plus a two-process Iroh sync (`IrohTest.twoProcessIrohSync`). Not in Dagger CI. No Android AAR, WS session, or blobs. `startPeer` is process-global — only one JVM test may start Iroh. +Glue: `cargo test` in `ffi/` covers the same local CRUD + Iroh-guard + HTTP +schema/`search` cases as Python, in-process. `ffi/kotlin` JUnit covers the +generated bindings plus a two-process Iroh sync (`IrohTest.twoProcessIrohSync`). +Not in Dagger CI. No Android AAR, WS session, or blobs. `startPeer` is +process-global — only one JVM test may start Iroh. ### 8. Known residual races diff --git a/docs/src/kotlin.md b/docs/src/kotlin.md index c63768989f..3ebfc3f273 100644 --- a/docs/src/kotlin.md +++ b/docs/src/kotlin.md @@ -7,7 +7,8 @@ wraps [`atomic_lib`](rust-lib.md) through [UniFFI](https://mozilla.github.io/uniffi-rs/) — the same Rust store the browser (WASM), Flutter app, and [Python SDK](python.md) use. Reads and writes go to a local [redb](https://github.com/cberner/redb) file. Edits are -signed Loro commits. A server is optional. +signed Loro commits. A server is optional for local CRUD and Iroh; HTTP GET / +search / `saveRemote()` are still available. Source: [`ffi/`](https://github.com/atomicdata-dev/atomic-server/tree/develop/ffi) (crate `atomic-ffi`, Kotlin package `dev.atomicdata`). @@ -70,6 +71,23 @@ store.loadAgent(secret) `Resource.destroyResource()` deletes the resource. It is not named `destroy()` because UniFFI already uses that for FFI handle teardown. +## HTTP (schema, search, remote save) + +The core ontology is bundled, so validation works offline. Unknown Class / +Property URLs are loaded with `store.get("https://…")` (HTTP GET + cache). + +```kotlin +val store = Store.open("./my-atomic-data", "https://atomicdata.dev") +// or later: store.setServer("https://atomicdata.dev") + +val schema = store.get("https://atomicdata.dev/classes/Bookmark") +val hits = store.search("notes", 10u) +note.saveRemote() // POST /commit; did:ad: needs store.server() +``` + +`has(subject)` is local-only. `search()` errors if no server is set. `save()` +stays local; `saveRemote()` is the HTTP POST. + ## P2P sync (Iroh) ```kotlin diff --git a/docs/src/python.md b/docs/src/python.md index 0c08257f1b..e53d742bb3 100644 --- a/docs/src/python.md +++ b/docs/src/python.md @@ -6,7 +6,8 @@ Local-first [Atomic Data](atomic-data-overview.md) for Python. The package wraps [`atomic_lib`](rust-lib.md) through [PyO3](https://pyo3.rs) — the same Rust store the browser (WASM) and Flutter app use. Reads and writes go to a local [redb](https://github.com/cberner/redb) -file. Edits are signed Loro commits. A server is optional. +file. Edits are signed Loro commits. A server is optional for local CRUD and +Iroh; HTTP GET / search / `save_remote()` are still available. Source: [`python/`](https://github.com/atomicdata-dev/atomic-server/tree/develop/python). @@ -68,6 +69,24 @@ store.load_agent(secret) `Store.in_memory()` is the same API without a directory. +## HTTP (schema, search, remote save) + +The core ontology (Folder, PlainText, `name`, …) is bundled, so validation +works offline. Unknown Class / Property URLs are loaded the same way +`atomic_lib` always has: `store.get("https://…")` GETs JSON-AD and caches it. + +```python +store = Store.open("./my-atomic-data", server="https://atomicdata.dev") +# or later: store.server = "https://atomicdata.dev" + +schema = store.get("https://atomicdata.dev/classes/Bookmark") +hits = store.search("notes", limit=10) +note.save_remote() # POST /commit; did:ad: needs store.server +``` + +`has(subject)` is local-only (no network). `search()` errors if no server is +set. `save()` stays local; `save_remote()` is the HTTP POST. + ## P2P sync (Iroh) ```python diff --git a/docs/src/tooling.md b/docs/src/tooling.md index 67d90eb4b9..749519fb99 100644 --- a/docs/src/tooling.md +++ b/docs/src/tooling.md @@ -9,8 +9,8 @@ Libraries and clients (all MIT licenced) that work great with [atomic-server](at - Svelte library: [@tomic/svelte](svelte.md) - Client CLI (rust): [atomic-cli](rust-cli.md) for fetching & editing data - Rust library: [atomic-lib](rust-lib.md) powers `atomic-server` and `atomic-cli`, and can be used in other Rust projects ([docs.rs](https://docs.rs/atomic_lib/latest/atomic_lib/)) -- Python SDK: [atomic-data](python.md) wraps `atomic_lib` via PyO3 (local redb store, no server required) -- Kotlin SDK: [dev.atomicdata](kotlin.md) wraps `atomic_lib` via UniFFI (local redb store, Iroh P2P) +- Python SDK: [atomic-data](python.md) wraps `atomic_lib` via PyO3 (local redb store, Iroh P2P, HTTP schema/search/`save_remote`) +- Kotlin SDK: [dev.atomicdata](kotlin.md) wraps `atomic_lib` via UniFFI (local redb store, Iroh P2P, HTTP schema/search/`saveRemote`) - [Raycast Extension](https://www.raycast.com/atomicdata-dev/atomic-data-browser): full-text search ## Want to add to this list? Some ideas for tooling diff --git a/ffi/README.md b/ffi/README.md index 3656d581ae..7cbaf0ad06 100644 --- a/ffi/README.md +++ b/ffi/README.md @@ -4,9 +4,11 @@ Local-first [Atomic Data](https://atomicdata.dev) for the JVM. This crate exposes `atomic_lib` through [UniFFI](https://mozilla.github.io/uniffi-rs/). Kotlin is the first generated language; Swift can use the same surface later. -This is **not** an HTTP client. Reads and writes go to a local [redb] +This is **not** an HTTP-only client. Reads and writes go to a local [redb] database. Edits are signed Loro CRDT commits. Sync is Iroh: -`startPeer()`, hand the node URI to another device, `syncWith()`. +`startPeer()`, hand the node URI to another device, `syncWith()`. HTTP GET of +`https://` subjects loads schema and other external resources; pass `server` +for `/search` and `saveRemote()` (POST `/commit`). [redb]: https://github.com/cberner/redb @@ -77,6 +79,15 @@ val node = store.startPeer() // did:ad:node:… `Resource.destroyResource()` deletes the resource. It is not named `destroy()` because UniFFI already uses that for FFI handle teardown. +Talk to a running AtomicServer: + +```kotlin +val store = Store.open("./my-atomic-data", "https://atomicdata.dev") +val page = store.get("https://atomicdata.dev") // HTTP GET + cache +val hits = store.search("folder", 10u) // GET /search +note.saveRemote() // POST /commit +``` + Two Iroh nodes cannot share one process — `startPeer` is process-global, same as Flutter and Python. diff --git a/ffi/kotlin/src/test/kotlin/dev/atomicdata/StoreTest.kt b/ffi/kotlin/src/test/kotlin/dev/atomicdata/StoreTest.kt index 33846993da..c945e38cf5 100644 --- a/ffi/kotlin/src/test/kotlin/dev/atomicdata/StoreTest.kt +++ b/ffi/kotlin/src/test/kotlin/dev/atomicdata/StoreTest.kt @@ -187,4 +187,52 @@ class StoreTest { assertEquals("edited after reopen", got.get("description")) store.close() } + + @Test + fun bundledSchemaIsLocal() { + val store = Store.inMemory() + assertTrue(store.has(Urls.NAME)) + val prop = store.get(Urls.NAME) + assertNotNull(prop) + assertTrue(prop.get("shortname") != null || prop.get("name") != null) + prop.close() + store.close() + } + + @Test + fun searchRequiresServer() { + val store = Store.inMemory() + val err = assertFailsWith { store.search("folder", null) } + assertContains(err.message ?: "", "server") + store.close() + } + + @Test + fun serverGetterSetter() { + val store = Store.inMemory() + assertNull(store.server()) + store.setServer("https://atomicdata.dev") + assertEquals("https://atomicdata.dev", store.server()) + store.close() + + val withServer = Store.inMemory("https://example.com") + assertEquals("https://example.com", withServer.server()) + withServer.close() + } + + @Test + fun getFetchesHttpSchemaResource() { + val store = Store.inMemory() + val subject = "https://atomicdata.dev" + assertFalse(store.has(subject)) + val resource = store.get(subject) + org.junit.jupiter.api.Assumptions.assumeTrue( + resource != null, + "https://atomicdata.dev not reachable", + ) + assertTrue(resource!!.subject().startsWith("https://")) + assertTrue(store.has(subject)) + resource.close() + store.close() + } } diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index aad08bf654..729e6ab6c6 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -1,7 +1,9 @@ //! UniFFI surface over `atomic_lib`. //! -//! Same product API as the Python SDK: local redb + Iroh P2P. Kotlin (and -//! later Swift) generate from this crate. PyO3 stays in `python/`. +//! Same product API as the Python SDK: local redb + Iroh P2P, plus HTTP GET +//! of `https://` subjects (schema / external resources), optional `server` +//! for `/search` and `saveRemote()`. Kotlin (and later Swift) generate from +//! this crate. PyO3 stays in `python/`. uniffi::setup_scaffolding!(); @@ -121,7 +123,7 @@ mod tests { #[test] fn setup_creates_agent_and_drive() { - let store = Store::in_memory().unwrap(); + let store = Store::in_memory(None).unwrap(); assert!(store.agent().unwrap().is_none()); assert!(store.active_drive().is_none()); @@ -139,7 +141,7 @@ mod tests { #[test] fn create_read_update() { - let store = Store::in_memory().unwrap(); + let store = Store::in_memory(None).unwrap(); store.setup("Test agent".into()).unwrap(); let mut props = HashMap::new(); props.insert("description".into(), "first draft".into()); @@ -163,7 +165,7 @@ mod tests { #[test] fn query_by_parent_and_class() { - let store = Store::in_memory().unwrap(); + let store = Store::in_memory(None).unwrap(); store.setup("Test agent".into()).unwrap(); let drive = store.active_drive().unwrap(); let folder = store @@ -210,7 +212,7 @@ mod tests { #[test] fn destroy_folder() { - let store = Store::in_memory().unwrap(); + let store = Store::in_memory(None).unwrap(); store.setup("Agent".into()).unwrap(); let folder = store .create(url_folder(), "Tmp".into(), None, None) @@ -223,7 +225,7 @@ mod tests { #[test] fn delete_removes_resource() { - let store = Store::in_memory().unwrap(); + let store = Store::in_memory(None).unwrap(); store.setup("Agent".into()).unwrap(); let folder = store .create(url_folder(), "ephemeral".into(), None, None) @@ -236,14 +238,14 @@ mod tests { #[test] fn missing_get_returns_none() { - let store = Store::in_memory().unwrap(); + let store = Store::in_memory(None).unwrap(); store.setup("Agent".into()).unwrap(); assert!(store.get("did:ad:does-not-exist".into()).unwrap().is_none()); } #[test] fn create_requires_parent_or_drive() { - let store = Store::in_memory().unwrap(); + let store = Store::in_memory(None).unwrap(); store.create_agent("No drive".into()).unwrap(); let err = store .create(url_folder(), "orphan".into(), None, None) @@ -253,8 +255,8 @@ mod tests { #[test] fn independent_in_memory_stores() { - let a = Store::in_memory().unwrap(); - let b = Store::in_memory().unwrap(); + let a = Store::in_memory(None).unwrap(); + let b = Store::in_memory(None).unwrap(); let setup_a = a.setup("A".into()).unwrap(); b.setup("B".into()).unwrap(); let note = a @@ -266,7 +268,7 @@ mod tests { #[test] fn to_json_includes_subject() { - let store = Store::in_memory().unwrap(); + let store = Store::in_memory(None).unwrap(); store.setup("Agent".into()).unwrap(); let note = store .create(url_folder(), "serial".into(), None, None) @@ -278,7 +280,7 @@ mod tests { #[test] fn known_peers_persist_locally() { - let store = Store::in_memory().unwrap(); + let store = Store::in_memory(None).unwrap(); store.setup("Ada".into()).unwrap(); store.add_peer(format!("did:ad:node:{}", "cd".repeat(32)), "Phone".into()); let peers = store.peers(); @@ -289,7 +291,7 @@ mod tests { #[test] fn sync_with_requires_start_peer() { - let store = Store::in_memory().unwrap(); + let store = Store::in_memory(None).unwrap(); store.setup("Ada".into()).unwrap(); let err = store .sync_with(format!("did:ad:node:{}", "ab".repeat(32)), None) @@ -299,7 +301,7 @@ mod tests { #[test] fn wait_for_times_out() { - let store = Store::in_memory().unwrap(); + let store = Store::in_memory(None).unwrap(); store.setup("Ada".into()).unwrap(); let err = store .wait_for("did:ad:does-not-change".into(), 0.2) @@ -312,7 +314,7 @@ mod tests { let dir = tempfile::tempdir().unwrap(); let path = dir.path().to_string_lossy().to_string(); let (subject, secret, drive) = { - let store = Store::open(path.clone()).unwrap(); + let store = Store::open(path.clone(), None).unwrap(); let setup = store.setup("Ada".into()).unwrap(); let mut props = HashMap::new(); props.insert("description".into(), "on disk".into()); @@ -327,7 +329,7 @@ mod tests { }; { - let store = Store::open(path.clone()).unwrap(); + let store = Store::open(path.clone(), None).unwrap(); let loaded = store.get(subject.clone()).unwrap().unwrap(); assert_eq!(loaded.get("name".into()), Some("Persisted".into())); assert_eq!(loaded.get("description".into()), Some("on disk".into())); @@ -341,11 +343,47 @@ mod tests { store.flush().unwrap(); } - let store = Store::open(path).unwrap(); + let store = Store::open(path, None).unwrap(); let got = store.get(subject).unwrap().unwrap(); assert_eq!( got.get("description".into()), Some("edited after reopen".into()) ); } + + #[test] + fn bundled_schema_is_local() { + let store = Store::in_memory(None).unwrap(); + assert!(store.has(url_name())); + let prop = store.get(url_name()).unwrap().unwrap(); + assert!(prop.get("shortname".into()).is_some() || prop.get("name".into()).is_some()); + } + + #[test] + fn search_requires_server() { + let store = Store::in_memory(None).unwrap(); + let err = store.search("folder".into(), None).unwrap_err(); + assert!(err.to_string().contains("server")); + } + + #[test] + fn server_getter_setter() { + let store = Store::in_memory(None).unwrap(); + assert!(store.server().is_none()); + store.set_server("https://atomicdata.dev".into()); + assert_eq!(store.server().as_deref(), Some("https://atomicdata.dev")); + let with = Store::in_memory(Some("https://example.com".into())).unwrap(); + assert_eq!(with.server().as_deref(), Some("https://example.com")); + } + + #[test] + fn get_fetches_http_resource() { + let store = Store::in_memory(None).unwrap(); + let subject = "https://atomicdata.dev".to_string(); + assert!(!store.has(subject.clone())); + if let Ok(Some(resource)) = store.get(subject.clone()) { + assert!(resource.subject().starts_with("https://")); + assert!(store.has(subject)); + } + } } diff --git a/ffi/src/resource.rs b/ffi/src/resource.rs index 68498a794a..ae629e8752 100644 --- a/ffi/src/resource.rs +++ b/ffi/src/resource.rs @@ -98,6 +98,15 @@ impl Resource { Ok(()) } + /// Sign the pending edits and POST the commit to an AtomicServer `/commit`. + /// + /// For `did:ad:` resources the store needs a `server` origin. HTTP(S) + /// subjects post to that subject's own host. + pub fn save_remote(&self) -> Result { + let mut inner = self.inner.lock().unwrap(); + block_on(inner.save_remote(&self.db)).map_err(err) + } + /// Delete this resource from the store. /// /// Named `destroy_resource` so UniFFI Kotlin does not collide with diff --git a/ffi/src/store.rs b/ffi/src/store.rs index b89dba7206..9cc9c6b8fa 100644 --- a/ffi/src/store.rs +++ b/ffi/src/store.rs @@ -1,5 +1,5 @@ use std::collections::HashMap; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use atomic_lib::storelike::{Query, Storelike}; use atomic_lib::{urls, Db, Subject, Value}; @@ -10,37 +10,55 @@ use crate::{block_on, err, AgentInfo, AtomicSdkError, DriveInfo, PeerInfo, Setup #[derive(uniffi::Object)] pub struct Store { - pub(crate) db: Db, + db: Mutex, } impl Store { + fn db(&self) -> Db { + self.db.lock().expect("store db lock").clone() + } + fn subject(&self, raw: &str) -> Subject { - Subject::from_raw(raw, self.db.get_base_domain().as_deref()) + Subject::from_raw(raw, self.db().get_base_domain().as_deref()) } pub(crate) fn wrap(&self, resource: atomic_lib::Resource) -> Arc { - Arc::new(Resource::new(resource, self.db.clone())) + Arc::new(Resource::new(resource, self.db())) } } #[uniffi::export] impl Store { - #[uniffi::constructor] - pub fn open(path: String) -> Result, AtomicSdkError> { + /// Open a persistent store. `server` is the AtomicServer origin used for + /// HTTP search and `save_remote()`. Schema `get()` of an `https://` + /// subject still fetches even when this is unset. + #[uniffi::constructor(default(server = None))] + pub fn open(path: String, server: Option) -> Result, AtomicSdkError> { let base = std::path::Path::new(&path); let uploads = base.join("uploads"); - let db = block_on(Db::init_redb_file(base, None, &uploads)).map_err(err)?; - Ok(Arc::new(Store { db })) + let db = block_on(Db::init_redb_file(base, server, &uploads)).map_err(err)?; + Ok(Arc::new(Store { db: Mutex::new(db) })) + } + + #[uniffi::constructor(default(server = None))] + pub fn in_memory(server: Option) -> Result, AtomicSdkError> { + let db = block_on(Db::init_redb(server)).map_err(err)?; + Ok(Arc::new(Store { db: Mutex::new(db) })) } - #[uniffi::constructor] - pub fn in_memory() -> Result, AtomicSdkError> { - let db = block_on(Db::init_redb(None)).map_err(err)?; - Ok(Arc::new(Store { db })) + /// AtomicServer origin for HTTP search and `saveRemote()`, or `null`. + pub fn server(&self) -> Option { + self.db().get_base_domain() + } + + /// Set the AtomicServer origin (`https://example.com`). + pub fn set_server(&self, url: String) { + let mut guard = self.db.lock().expect("store db lock"); + *guard = guard.clone_with_url(url); } pub fn setup(&self, name: String) -> Result { - let (agent, drive_subject) = block_on(self.db.setup(&name)).map_err(err)?; + let (agent, drive_subject) = block_on(self.db().setup(&name)).map_err(err)?; let secret = agent.build_secret().map_err(err)?; Ok(SetupInfo { agent_subject: agent.subject.to_string(), @@ -50,29 +68,30 @@ impl Store { } pub fn create_agent(&self, name: String) -> Result { - let agent = block_on(self.db.create_agent(Some(&name))).map_err(err)?; - self.db.set_default_agent(agent.clone()); + let db = self.db(); + let agent = block_on(db.create_agent(Some(&name))).map_err(err)?; + db.set_default_agent(agent.clone()); agent_info(agent, false) } pub fn load_agent(&self, secret: String) -> Result { - let result = block_on(self.db.load_agent_from_secret(&secret)).map_err(err)?; + let result = block_on(self.db().load_agent_from_secret(&secret)).map_err(err)?; agent_info(result.agent, result.drive_needs_sync) } pub fn agent(&self) -> Result, AtomicSdkError> { - match self.db.get_default_agent() { + match self.db().get_default_agent() { Ok(agent) => Ok(Some(agent_info(agent, false)?)), Err(_) => Ok(None), } } pub fn create_drive(&self, name: String) -> Result { - block_on(self.db.create_drive(&name)).map_err(err) + block_on(self.db().create_drive(&name)).map_err(err) } pub fn drives(&self) -> Result, AtomicSdkError> { - let drives = block_on(self.db.list_drives()).map_err(err)?; + let drives = block_on(self.db().list_drives()).map_err(err)?; Ok(drives .into_iter() .map(|d| DriveInfo { @@ -83,11 +102,11 @@ impl Store { } pub fn active_drive(&self) -> Option { - self.db.get_active_drive() + self.db().get_active_drive() } pub fn set_active_drive(&self, subject: String) -> Result<(), AtomicSdkError> { - self.db.set_active_drive(&subject).map_err(err) + self.db().set_active_drive(&subject).map_err(err) } pub fn create( @@ -99,7 +118,7 @@ impl Store { ) -> Result, AtomicSdkError> { let parent = match parent { Some(p) => p, - None => self.db.get_active_drive().ok_or_else(|| { + None => self.db().get_active_drive().ok_or_else(|| { AtomicSdkError::from("create() needs a parent or an active drive") })?, }; @@ -118,21 +137,27 @@ impl Store { Some(extra_refs) }; let subject = - block_on(self.db.create_resource(&class_url, &parent, &name, extra)).map_err(err)?; + block_on(self.db().create_resource(&class_url, &parent, &name, extra)).map_err(err)?; self.get(subject)? .ok_or_else(|| AtomicSdkError::from("created resource but could not read it back")) } + /// Fetch a resource by subject URL / DID. + /// + /// Local first. If the subject is an `http(s)://` URL that is not in + /// this store, this GETs JSON-AD over HTTP, caches it, and returns it. + /// That is how unknown Class / Property schema items are loaded. pub fn get(&self, subject: String) -> Result>, AtomicSdkError> { let subject = self.subject(&subject); - match block_on(self.db.get_resource(&subject)) { + match block_on(self.db().get_resource(&subject)) { Ok(resource) => Ok(Some(self.wrap(resource))), Err(_) => Ok(None), } } + /// True when the subject is already in this store (no network fetch). pub fn has(&self, subject: String) -> bool { - self.db.has_resource_locally(&subject) + self.db().has_resource_locally(&subject) } pub fn query( @@ -146,7 +171,7 @@ impl Store { ) -> Result>, AtomicSdkError> { let mut parent = parent; if parent.is_none() && class_url.is_none() && property.is_none() { - parent = self.db.get_active_drive(); + parent = self.db().get_active_drive(); if parent.is_none() { return Err(AtomicSdkError::from( "query() needs a filter or an active drive", @@ -158,7 +183,7 @@ impl Store { q.include_nested = true; q.limit = limit.map(|n| n as usize); q.offset = offset as usize; - if let Some(drive) = self.db.get_active_drive().or_else(|| parent.clone()) { + if let Some(drive) = self.db().get_active_drive().or_else(|| parent.clone()) { q.drive = Some(self.subject(&drive)); } if let Some(class) = &class_url { @@ -174,10 +199,31 @@ impl Store { q.value = Some(string_to_value(&val)); } - let result = block_on(self.db.query(&q)).map_err(err)?; + let result = block_on(self.db().query(&q)).map_err(err)?; Ok(result.resources.into_iter().map(|r| self.wrap(r)).collect()) } + /// Full-text search on the configured AtomicServer `/search` endpoint. + /// + /// Requires `server` (constructor or `setServer`). + pub fn search( + &self, + query: String, + limit: Option, + ) -> Result>, AtomicSdkError> { + if self.db().get_base_domain().is_none() { + return Err(AtomicSdkError::from( + "search() needs a server URL (Store.open(..., server=...) or store.setServer(...))", + )); + } + let opts = atomic_lib::client::search::SearchOpts { + limit, + ..Default::default() + }; + let resources = block_on(self.db().search(&query, opts)).map_err(err)?; + Ok(resources.into_iter().map(|r| self.wrap(r)).collect()) + } + pub fn delete(&self, subject: String) -> Result<(), AtomicSdkError> { let resource = self .get(subject.clone())? @@ -186,11 +232,11 @@ impl Store { } pub fn flush(&self) -> Result<(), AtomicSdkError> { - self.db.flush().map_err(err) + self.db().flush().map_err(err) } pub fn start_peer(&self) -> Result { - crate::peer::start_peer(&self.db) + crate::peer::start_peer(&self.db()) } pub fn peer_id(&self) -> Option { @@ -198,7 +244,7 @@ impl Store { } pub fn announce(&self, drive: Option) -> Result<(), AtomicSdkError> { - crate::peer::announce(&self.db, drive.as_deref()) + crate::peer::announce(&self.db(), drive.as_deref()) } pub fn sync_with( @@ -206,15 +252,15 @@ impl Store { node_id: String, drive: Option, ) -> Result { - crate::peer::sync_with(&self.db, &node_id, drive.as_deref()) + crate::peer::sync_with(&self.db(), &node_id, drive.as_deref()) } pub fn add_peer(&self, node_id: String, name: String) { - atomic_lib::sync::peer::add_known_peer(&self.db, &node_id, &name); + atomic_lib::sync::peer::add_known_peer(&self.db(), &node_id, &name); } pub fn peers(&self) -> Vec { - atomic_lib::sync::peer::get_known_peers(&self.db) + atomic_lib::sync::peer::get_known_peers(&self.db()) .into_iter() .map(|p| PeerInfo { node_id: crate::peer::node_uri(&p.node_id), @@ -231,15 +277,15 @@ impl Store { } pub fn wait_for(&self, subject: String, timeout_secs: f64) -> Result { - crate::peer::wait_for(&self.db, &subject, timeout_secs) + crate::peer::wait_for(&self.db(), &subject, timeout_secs) } pub fn device_name(&self) -> String { - atomic_lib::sync::peer::get_device_name(&self.db) + atomic_lib::sync::peer::get_device_name(&self.db()) } pub fn set_device_name(&self, name: String) { - atomic_lib::sync::peer::set_device_name(&self.db, &name); + atomic_lib::sync::peer::set_device_name(&self.db(), &name); } } diff --git a/planning/README.md b/planning/README.md index 1992c94c16..6eda21ba52 100644 --- a/planning/README.md +++ b/planning/README.md @@ -172,8 +172,8 @@ the document itself. | [`demo-experience.md`](./demo-experience.md) | v1 and v2 demo workspace. | | [`local-search.md`](./local-search.md) | KV inverted index in `atomic_lib` (redb/OPFS/sled): BM25 + prefix + 1-edit prefix-fuzzy + PropValSub filters. Hosted `/search` is the same engine; Tantivy is gone. | | [`cloud-sync-managed-node.md`](./cloud-sync-managed-node.md) | Onboarding, managed-node detection, enrollment, heartbeat and replication pull. Verified against the SaaS `LocalProcessNodeProvider` only; the sync-path admission gate and reaper are not built. | -| [`python-sdk.md`](./python-sdk.md) | Python bindings over `atomic_lib` via PyO3 (local redb, Iroh). | -| [`kotlin-sdk.md`](./kotlin-sdk.md) | UniFFI crate `ffi/` + Kotlin package `dev.atomicdata`. Same local redb + Iroh surface as Python. Android AAR still open. | +| [`python-sdk.md`](./python-sdk.md) | Python bindings over `atomic_lib` via PyO3 (local redb, Iroh, HTTP schema/search/`save_remote`). | +| [`kotlin-sdk.md`](./kotlin-sdk.md) | UniFFI crate `ffi/` + Kotlin package `dev.atomicdata`. Same local redb + Iroh + HTTP surface as Python. Android AAR still open. | | [`completed/commit-fanout-drive-isolation.md`](./completed/commit-fanout-drive-isolation.md) | Drive-scoped WS commit fan-out + server-side drive safety net. | | [`completed/cleanup-update-encoding.md`](./completed/cleanup-update-encoding.md) | Unified `decode_update`; TS client exports compact Loro deltas. | | [`completed/sign-at-drain.md`](./completed/sign-at-drain.md) | One signed commit per dirty subject per drain pass. | diff --git a/planning/kotlin-sdk.md b/planning/kotlin-sdk.md index 91bdf6caa3..5b32060ce9 100644 --- a/planning/kotlin-sdk.md +++ b/planning/kotlin-sdk.md @@ -10,7 +10,8 @@ Android AAR / `cargo-ndk` and the Binder host are **not** built. ## Decision Use **UniFFI**, not PyO3-style hand bindings, not `flutter_rust_bridge`, not -an HTTP client. +an HTTP-only client. HTTP GET / search / `saveRemote()` are part of the same +`atomic_lib` store (schema fetch for unknown Class / Property URLs). Kotlin is the first language where UniFFI is the obvious tool: @@ -34,11 +35,11 @@ non-Python / non-Flutter binding should call: ```text atomic-ffi - open / in_memory + open / in_memory (optional server origin) setup / load_agent / create_agent create_drive / list_drives - create / get / query / delete / flush - Resource: get/set/save/destroy_resource / to_json + create / get / search / query / delete / flush + Resource: get/set/save/save_remote/destroy_resource / to_json start_peer / sync_with / announce / wait_for ``` diff --git a/planning/python-sdk.md b/planning/python-sdk.md index 09bac20337..4693bc3d2b 100644 --- a/planning/python-sdk.md +++ b/planning/python-sdk.md @@ -8,7 +8,9 @@ v1 implemented: local read / write / query / persist. Package lives in ## Decision Wrap `atomic_lib` with **PyO3 + maturin**. Do not reimplement commits, Loro, -or Ed25519 in Python. Do not ship an HTTP-only client as the SDK. +or Ed25519 in Python. Do not ship an HTTP-**only** client as the SDK — local +redb + Iroh stay the default, and HTTP GET / search / `save_remote()` ride +the same `atomic_lib` store (schema fetch for unknown Class / Property URLs). This matches the existing bindings: @@ -30,11 +32,11 @@ every Rust CI run. ## v1 surface -- `Store.open(path)` / `Store.in_memory()` +- `Store.open(path, server=None)` / `Store.in_memory(server=None)` - `setup`, `load_agent`, `create_agent`, `create_drive` -- `create`, `get`, `query`, `delete`, `flush` +- `create`, `get` (HTTP GET for unknown `https://` subjects), `search`, `query`, `delete`, `flush` - Iroh: `start_peer`, `announce`, `sync_with`, `wait_for`, live push on `save` -- `Resource` dict access, `save`, `destroy`, `to_dict`, `to_json` +- `Resource` dict access, `save`, `save_remote`, `destroy`, `to_dict`, `to_json` - `atomic_data.urls` constants Python calls are synchronous. Each one `block_on`s a process-wide tokio diff --git a/python/README.md b/python/README.md index e60f565163..84c8857d4e 100644 --- a/python/README.md +++ b/python/README.md @@ -2,10 +2,12 @@ Local-first [Atomic Data](https://atomicdata.dev) SDK for Python. -This is **not** a thin HTTP client. It wraps [`atomic_lib`](../lib) — the same +This is **not** an HTTP-only client. It wraps [`atomic_lib`](../lib) — the same Rust crate that powers AtomicServer, the browser WASM store, and the Flutter app — through [PyO3](https://pyo3.rs). Reads and writes go to a local [redb] -database. Edits are signed Loro CRDT commits. A server is optional. +database. Edits are signed Loro CRDT commits. A server is optional for local +CRUD and Iroh sync; HTTP is still there for schema fetch, search, and +`save_remote()`. [redb]: https://github.com/cberner/redb @@ -24,8 +26,13 @@ Reimplementing commits, Ed25519, and Loro in Python would drift. UniFFI is a better fit when one IDL must generate Swift + Kotlin + Python; here the Python API can be idiomatic on its own, the way the Flutter bridge is. -Reads and writes are local. Sync is Iroh: `start_peer()`, hand the node URI -to another device, `sync_with()`. After that, `.save()` pushes live. +Reads and writes are local by default. Sync is Iroh: `start_peer()`, hand the +node URI to another device, `sync_with()`. After that, `.save()` pushes live. + +HTTP is part of the same store: `get("https://…")` GETs JSON-AD (that is how +unknown Class / Property schema items are loaded). Pass `server=` for +AtomicServer `/search` and `Resource.save_remote()` (POST `/commit`). The +core ontology is bundled, so Folder / PlainText / `name` work offline. ## Install (from this repo) @@ -96,6 +103,15 @@ page = store.get(subject) `Store.in_memory()` is the same API without a directory — useful in tests. +Talk to a running AtomicServer: + +```python +store = Store.open("./my-atomic-data", server="https://atomicdata.dev") +page = store.get("https://atomicdata.dev") # HTTP GET + cache +hits = store.search("folder", limit=10) # GET /search +note.save_remote() # POST /commit +``` + A `Resource` keeps a handle to the store. Drop every `Store` and `Resource` before opening the same directory again — redb takes an exclusive file lock. @@ -104,13 +120,15 @@ before opening the same directory again — redb takes an exclusive file lock. ## API -- **Store** — `open(path)`, `in_memory()`, `setup(name)`, `load_agent(secret)`, - `create(class_url, name, ...)`, `get(subject)`, `query(...)`, `delete(subject)`, - `flush()`, context manager +- **Store** — `open(path, server=None)`, `in_memory(server=None)`, `setup(name)`, + `load_agent(secret)`, `create(class_url, name, ...)`, `get(subject)` (HTTP + GET for unknown `https://` subjects), `search(query)`, `query(...)`, + `delete(subject)`, `flush()`, context manager - **Iroh** — `start_peer()`, `peer_id`, `announce()`, `sync_with(node_id)`, `add_peer()`, `peers()`, `live_peers()`, `wait_for(subject)` -- **Resource** — dict-like access, `.save()` (live-pushes if peers are up), - `.destroy()`, `.to_dict()`, `.to_json()` +- **Resource** — dict-like access, `.save()` (local + live-push if peers are + up), `.save_remote()` (POST `/commit`), `.destroy()`, `.to_dict()`, + `.to_json()` - **atomic_data.urls** — well-known class and property URLs Property keys accept full URLs or shortnames (`name`, `description`, `parent`, diff --git a/python/src/lib.rs b/python/src/lib.rs index f03829f13f..9250c47af5 100644 --- a/python/src/lib.rs +++ b/python/src/lib.rs @@ -39,10 +39,12 @@ pub(crate) fn block_on(fut: F) -> F::Output { runtime().block_on(fut) } -/// Local-first Atomic Data SDK. +/// Atomic Data SDK. /// /// Opens a redb-backed store (or an in-memory one), creates an agent + drive, -/// and reads / writes resources as signed Loro commits. No server required. +/// and reads / writes resources as signed Loro commits. Local by default. +/// HTTP GET of `https://` subjects loads schema and other external resources; +/// `server=` enables search and `save_remote()`. #[pymodule] fn atomic_data(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; diff --git a/python/src/resource.rs b/python/src/resource.rs index eda764639a..040d7a8a5b 100644 --- a/python/src/resource.rs +++ b/python/src/resource.rs @@ -110,6 +110,14 @@ impl Resource { Ok(()) } + /// Sign the pending edits and POST the commit to an AtomicServer `/commit`. + /// + /// For `did:ad:` resources the store needs a `server` origin. HTTP(S) + /// subjects post to that subject's own host. + fn save_remote(&mut self) -> PyResult { + block_on(self.inner.save_remote(&self.db)).map_err(py_err) + } + /// Sign a destroy commit and remove the resource from the store. pub fn destroy(&mut self) -> PyResult<()> { block_on(self.inner.destroy(&self.db)).map_err(py_err)?; diff --git a/python/src/store.rs b/python/src/store.rs index be18e78bbd..103f2b3050 100644 --- a/python/src/store.rs +++ b/python/src/store.rs @@ -102,21 +102,44 @@ impl Store { /// Creates the directory if needed. Data lives in `path/atomic.redb`. /// Call [`Store.flush`] (or use the store as a context manager) so recent /// writes survive a crash — redb commits are not fsynced on every write. + /// + /// `server` is the AtomicServer origin (`https://atomicdata.dev`) used for + /// HTTP search and `save_remote()`. Schema `get()` of an `https://` + /// subject still fetches even when this is unset. #[staticmethod] - fn open(path: &str) -> PyResult { + #[pyo3(signature = (path, server=None))] + fn open(path: &str, server: Option<&str>) -> PyResult { let base = std::path::Path::new(path); let uploads = base.join("uploads"); - let db = block_on(Db::init_redb_file(base, None, &uploads)).map_err(py_err)?; + let db = block_on(Db::init_redb_file( + base, + server.map(|s| s.to_string()), + &uploads, + )) + .map_err(py_err)?; Ok(Store { db }) } /// In-memory store. Lost when the process exits. Useful in tests. #[staticmethod] - fn in_memory() -> PyResult { - let db = block_on(Db::init_redb(None)).map_err(py_err)?; + #[pyo3(signature = (server=None))] + fn in_memory(server: Option<&str>) -> PyResult { + let db = block_on(Db::init_redb(server.map(|s| s.to_string()))).map_err(py_err)?; Ok(Store { db }) } + /// AtomicServer origin for HTTP search and `save_remote()`, or `None`. + #[getter] + fn server(&self) -> Option { + self.db.get_base_domain() + } + + /// Set the AtomicServer origin (`https://example.com`). + #[setter] + fn set_server(&mut self, url: &str) { + self.db = self.db.clone_with_url(url.to_string()); + } + /// Create an agent and a personal drive. Pure local — no network. /// /// Keep `agent_secret`. It is the only way to sign writes after a reopen. @@ -224,7 +247,13 @@ impl Store { .ok_or_else(|| py_err(format!("created {subject} but could not read it back"))) } - /// Fetch a resource by subject URL / DID. `None` if it is not stored. + /// Fetch a resource by subject URL / DID. + /// + /// Local first. If the subject is an `http(s)://` URL that is not in + /// this store, this GETs JSON-AD over HTTP, caches it, and returns it. + /// That is how unknown Class / Property schema items are loaded. + /// `None` if it is not stored and the fetch fails or does not apply + /// (local DIDs with no HTTP URL). fn get(&self, subject: &str) -> PyResult> { let subject = self.subject(subject); match block_on(self.db.get_resource(&subject)) { @@ -294,6 +323,27 @@ impl Store { .collect()) } + /// Full-text search on the configured AtomicServer `/search` endpoint. + /// + /// Requires `server` (constructor or `store.server = ...`). + #[pyo3(signature = (query, limit=None))] + fn search(&self, query: &str, limit: Option) -> PyResult> { + if self.db.get_base_domain().is_none() { + return Err(PyValueError::new_err( + "search() needs a server URL (Store.open(..., server=...) or store.server = ...)", + )); + } + let opts = atomic_lib::client::search::SearchOpts { + limit, + ..Default::default() + }; + let resources = block_on(self.db.search(query, opts)).map_err(py_err)?; + Ok(resources + .into_iter() + .map(|r| self.wrap_resource(r)) + .collect()) + } + /// Destroy a resource (signed commit). Raises if it is not stored. fn delete(&self, subject: &str) -> PyResult<()> { let mut resource = self diff --git a/python/tests/test_sdk.py b/python/tests/test_sdk.py index 53022be60e..d94f70788c 100644 --- a/python/tests/test_sdk.py +++ b/python/tests/test_sdk.py @@ -149,3 +149,45 @@ def test_independent_in_memory_stores(): note = a.create(urls.PLAIN_TEXT, name="only-in-a") assert b.get(note.subject) is None assert a.get(setup_a.drive_subject) is not None + + +def test_bundled_schema_is_local(): + store = Store.in_memory() + assert store.has(urls.NAME) + prop = store.get(urls.NAME) + assert prop is not None + assert prop.get("shortname") or prop.get("name") + + +def test_search_requires_server(): + store = Store.in_memory() + with pytest.raises(ValueError, match="server"): + store.search("folder") + + +def test_server_getter_setter(): + store = Store.in_memory() + assert store.server is None + store.server = "https://atomicdata.dev" + assert store.server == "https://atomicdata.dev" + with_server = Store.in_memory(server="https://example.com") + assert with_server.server == "https://example.com" + + +def test_get_fetches_http_schema_resource(): + """Unknown https:// subjects are loaded with HTTP GET (schema fetch).""" + store = Store.in_memory() + subject = "https://atomicdata.dev" + assert not store.has(subject) + resource = store.get(subject) + if resource is None: + pytest.skip("https://atomicdata.dev not reachable") + assert resource.subject.startswith("https://") + assert store.has(subject) + + +def test_save_remote_posts_over_http(store): + store.server = "http://127.0.0.1:1" + note = store.create(urls.FOLDER, name="remote") + with pytest.raises(RuntimeError): + note.save_remote() From 176884c39f76db7fdd027eed51a4201e716c7aa8 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 19 Aug 2026 15:34:58 +0000 Subject: [PATCH 11/16] Regenerate Kotlin bindings and cache HTTP GET under the resource subject. Fetched https:// resources are stored by their own subject, which can differ from the request URL, so has() is checked against resource.subject. Co-authored-by: joepmeindertsma --- .../main/kotlin/dev/atomicdata/atomic_ffi.kt | 174 ++++++++++++++++-- .../test/kotlin/dev/atomicdata/StoreTest.kt | 2 +- ffi/src/lib.rs | 2 +- python/tests/test_sdk.py | 3 +- 4 files changed, 167 insertions(+), 14 deletions(-) diff --git a/ffi/kotlin/src/main/kotlin/dev/atomicdata/atomic_ffi.kt b/ffi/kotlin/src/main/kotlin/dev/atomicdata/atomic_ffi.kt index 86811102ac..ea71259da5 100644 --- a/ffi/kotlin/src/main/kotlin/dev/atomicdata/atomic_ffi.kt +++ b/ffi/kotlin/src/main/kotlin/dev/atomicdata/atomic_ffi.kt @@ -793,6 +793,14 @@ internal interface UniffiForeignFutureCompleteVoid : com.sun.jna.Callback { + + + + + + + + @@ -843,6 +851,8 @@ fun uniffi_atomic_ffi_checksum_method_resource_name( ): Short fun uniffi_atomic_ffi_checksum_method_resource_save( ): Short +fun uniffi_atomic_ffi_checksum_method_resource_save_remote( +): Short fun uniffi_atomic_ffi_checksum_method_resource_set( ): Short fun uniffi_atomic_ffi_checksum_method_resource_set_name( @@ -887,10 +897,16 @@ fun uniffi_atomic_ffi_checksum_method_store_peers( ): Short fun uniffi_atomic_ffi_checksum_method_store_query( ): Short +fun uniffi_atomic_ffi_checksum_method_store_search( +): Short +fun uniffi_atomic_ffi_checksum_method_store_server( +): Short fun uniffi_atomic_ffi_checksum_method_store_set_active_drive( ): Short fun uniffi_atomic_ffi_checksum_method_store_set_device_name( ): Short +fun uniffi_atomic_ffi_checksum_method_store_set_server( +): Short fun uniffi_atomic_ffi_checksum_method_store_setup( ): Short fun uniffi_atomic_ffi_checksum_method_store_start_peer( @@ -968,6 +984,8 @@ fun uniffi_atomic_ffi_fn_method_resource_name(`ptr`: Pointer,uniffi_out_err: Uni ): RustBuffer.ByValue fun uniffi_atomic_ffi_fn_method_resource_save(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, ): Unit +fun uniffi_atomic_ffi_fn_method_resource_save_remote(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue fun uniffi_atomic_ffi_fn_method_resource_set(`ptr`: Pointer,`property`: RustBuffer.ByValue,`value`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, ): Unit fun uniffi_atomic_ffi_fn_method_resource_set_name(`ptr`: Pointer,`name`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, @@ -980,9 +998,9 @@ fun uniffi_atomic_ffi_fn_clone_store(`ptr`: Pointer,uniffi_out_err: UniffiRustCa ): Pointer fun uniffi_atomic_ffi_fn_free_store(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, ): Unit -fun uniffi_atomic_ffi_fn_constructor_store_in_memory(uniffi_out_err: UniffiRustCallStatus, +fun uniffi_atomic_ffi_fn_constructor_store_in_memory(`server`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, ): Pointer -fun uniffi_atomic_ffi_fn_constructor_store_open(`path`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, +fun uniffi_atomic_ffi_fn_constructor_store_open(`path`: RustBuffer.ByValue,`server`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, ): Pointer fun uniffi_atomic_ffi_fn_method_store_active_drive(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue @@ -1020,10 +1038,16 @@ fun uniffi_atomic_ffi_fn_method_store_peers(`ptr`: Pointer,uniffi_out_err: Uniff ): RustBuffer.ByValue fun uniffi_atomic_ffi_fn_method_store_query(`ptr`: Pointer,`parent`: RustBuffer.ByValue,`classUrl`: RustBuffer.ByValue,`property`: RustBuffer.ByValue,`value`: RustBuffer.ByValue,`limit`: RustBuffer.ByValue,`offset`: Int,uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue +fun uniffi_atomic_ffi_fn_method_store_search(`ptr`: Pointer,`query`: RustBuffer.ByValue,`limit`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue +fun uniffi_atomic_ffi_fn_method_store_server(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, +): RustBuffer.ByValue fun uniffi_atomic_ffi_fn_method_store_set_active_drive(`ptr`: Pointer,`subject`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, ): Unit fun uniffi_atomic_ffi_fn_method_store_set_device_name(`ptr`: Pointer,`name`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, ): Unit +fun uniffi_atomic_ffi_fn_method_store_set_server(`ptr`: Pointer,`url`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, +): Unit fun uniffi_atomic_ffi_fn_method_store_setup(`ptr`: Pointer,`name`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue fun uniffi_atomic_ffi_fn_method_store_start_peer(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, @@ -1211,6 +1235,9 @@ private fun uniffiCheckApiChecksums(lib: IntegrityCheckingUniffiLib) { if (lib.uniffi_atomic_ffi_checksum_method_resource_save() != 30956.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (lib.uniffi_atomic_ffi_checksum_method_resource_save_remote() != 19479.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (lib.uniffi_atomic_ffi_checksum_method_resource_set() != 5476.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -1256,10 +1283,10 @@ private fun uniffiCheckApiChecksums(lib: IntegrityCheckingUniffiLib) { if (lib.uniffi_atomic_ffi_checksum_method_store_flush() != 4851.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_atomic_ffi_checksum_method_store_get() != 21403.toShort()) { + if (lib.uniffi_atomic_ffi_checksum_method_store_get() != 18007.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_atomic_ffi_checksum_method_store_has() != 17296.toShort()) { + if (lib.uniffi_atomic_ffi_checksum_method_store_has() != 10953.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } if (lib.uniffi_atomic_ffi_checksum_method_store_live_peers() != 46534.toShort()) { @@ -1277,12 +1304,21 @@ private fun uniffiCheckApiChecksums(lib: IntegrityCheckingUniffiLib) { if (lib.uniffi_atomic_ffi_checksum_method_store_query() != 41621.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (lib.uniffi_atomic_ffi_checksum_method_store_search() != 65224.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_atomic_ffi_checksum_method_store_server() != 24965.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (lib.uniffi_atomic_ffi_checksum_method_store_set_active_drive() != 2781.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } if (lib.uniffi_atomic_ffi_checksum_method_store_set_device_name() != 18642.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (lib.uniffi_atomic_ffi_checksum_method_store_set_server() != 56732.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (lib.uniffi_atomic_ffi_checksum_method_store_setup() != 26554.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -1295,10 +1331,10 @@ private fun uniffiCheckApiChecksums(lib: IntegrityCheckingUniffiLib) { if (lib.uniffi_atomic_ffi_checksum_method_store_wait_for() != 64329.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_atomic_ffi_checksum_constructor_store_in_memory() != 41056.toShort()) { + if (lib.uniffi_atomic_ffi_checksum_constructor_store_in_memory() != 43359.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_atomic_ffi_checksum_constructor_store_open() != 32226.toShort()) { + if (lib.uniffi_atomic_ffi_checksum_constructor_store_open() != 48180.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } } @@ -1689,6 +1725,14 @@ public interface ResourceInterface { fun `save`() + /** + * Sign the pending edits and POST the commit to an AtomicServer `/commit`. + * + * For `did:ad:` resources the store needs a `server` origin. HTTP(S) + * subjects post to that subject's own host. + */ + fun `saveRemote`(): kotlin.String + fun `set`(`property`: kotlin.String, `value`: kotlin.String) fun `setName`(`name`: kotlin.String) @@ -1861,6 +1905,25 @@ open class Resource: Disposable, AutoCloseable, ResourceInterface + /** + * Sign the pending edits and POST the commit to an AtomicServer `/commit`. + * + * For `did:ad:` resources the store needs a `server` origin. HTTP(S) + * subjects post to that subject's own host. + */ + @Throws(AtomicSdkException::class)override fun `saveRemote`(): kotlin.String { + return FfiConverterString.lift( + callWithPointer { + uniffiRustCallWithError(AtomicSdkException) { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_resource_save_remote( + it, _status) +} + } + ) + } + + + @Throws(AtomicSdkException::class)override fun `set`(`property`: kotlin.String, `value`: kotlin.String) = callWithPointer { @@ -2068,8 +2131,18 @@ public interface StoreInterface { fun `flush`() + /** + * Fetch a resource by subject URL / DID. + * + * Local first. If the subject is an `http(s)://` URL that is not in + * this store, this GETs JSON-AD over HTTP, caches it, and returns it. + * That is how unknown Class / Property schema items are loaded. + */ fun `get`(`subject`: kotlin.String): Resource? + /** + * True when the subject is already in this store (no network fetch). + */ fun `has`(`subject`: kotlin.String): kotlin.Boolean fun `livePeers`(): List @@ -2082,10 +2155,27 @@ public interface StoreInterface { fun `query`(`parent`: kotlin.String?, `classUrl`: kotlin.String?, `property`: kotlin.String?, `value`: kotlin.String?, `limit`: kotlin.UInt?, `offset`: kotlin.UInt): List + /** + * Full-text search on the configured AtomicServer `/search` endpoint. + * + * Requires `server` (constructor or `setServer`). + */ + fun `search`(`query`: kotlin.String, `limit`: kotlin.UInt?): List + + /** + * AtomicServer origin for HTTP search and `saveRemote()`, or `null`. + */ + fun `server`(): kotlin.String? + fun `setActiveDrive`(`subject`: kotlin.String) fun `setDeviceName`(`name`: kotlin.String) + /** + * Set the AtomicServer origin (`https://example.com`). + */ + fun `setServer`(`url`: kotlin.String) + fun `setup`(`name`: kotlin.String): SetupInfo fun `startPeer`(): kotlin.String @@ -2316,6 +2406,13 @@ open class Store: Disposable, AutoCloseable, StoreInterface + /** + * Fetch a resource by subject URL / DID. + * + * Local first. If the subject is an `http(s)://` URL that is not in + * this store, this GETs JSON-AD over HTTP, caches it, and returns it. + * That is how unknown Class / Property schema items are loaded. + */ @Throws(AtomicSdkException::class)override fun `get`(`subject`: kotlin.String): Resource? { return FfiConverterOptionalTypeResource.lift( callWithPointer { @@ -2328,7 +2425,10 @@ open class Store: Disposable, AutoCloseable, StoreInterface } - override fun `has`(`subject`: kotlin.String): kotlin.Boolean { + + /** + * True when the subject is already in this store (no network fetch). + */override fun `has`(`subject`: kotlin.String): kotlin.Boolean { return FfiConverterBoolean.lift( callWithPointer { uniffiRustCall() { _status -> @@ -2403,6 +2503,39 @@ open class Store: Disposable, AutoCloseable, StoreInterface + /** + * Full-text search on the configured AtomicServer `/search` endpoint. + * + * Requires `server` (constructor or `setServer`). + */ + @Throws(AtomicSdkException::class)override fun `search`(`query`: kotlin.String, `limit`: kotlin.UInt?): List { + return FfiConverterSequenceTypeResource.lift( + callWithPointer { + uniffiRustCallWithError(AtomicSdkException) { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_store_search( + it, FfiConverterString.lower(`query`),FfiConverterOptionalUInt.lower(`limit`),_status) +} + } + ) + } + + + + /** + * AtomicServer origin for HTTP search and `saveRemote()`, or `null`. + */override fun `server`(): kotlin.String? { + return FfiConverterOptionalString.lift( + callWithPointer { + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_store_server( + it, _status) +} + } + ) + } + + + @Throws(AtomicSdkException::class)override fun `setActiveDrive`(`subject`: kotlin.String) = callWithPointer { @@ -2426,6 +2559,20 @@ open class Store: Disposable, AutoCloseable, StoreInterface + /** + * Set the AtomicServer origin (`https://example.com`). + */override fun `setServer`(`url`: kotlin.String) + = + callWithPointer { + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_method_store_set_server( + it, FfiConverterString.lower(`url`),_status) +} + } + + + + @Throws(AtomicSdkException::class)override fun `setup`(`name`: kotlin.String): SetupInfo { return FfiConverterTypeSetupInfo.lift( callWithPointer { @@ -2482,22 +2629,27 @@ open class Store: Disposable, AutoCloseable, StoreInterface companion object { - @Throws(AtomicSdkException::class) fun `inMemory`(): Store { + @Throws(AtomicSdkException::class) fun `inMemory`(`server`: kotlin.String? = null): Store { return FfiConverterTypeStore.lift( uniffiRustCallWithError(AtomicSdkException) { _status -> UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_constructor_store_in_memory( - _status) + FfiConverterOptionalString.lower(`server`),_status) } ) } - @Throws(AtomicSdkException::class) fun `open`(`path`: kotlin.String): Store { + /** + * Open a persistent store. `server` is the AtomicServer origin used for + * HTTP search and `save_remote()`. Schema `get()` of an `https://` + * subject still fetches even when this is unset. + */ + @Throws(AtomicSdkException::class) fun `open`(`path`: kotlin.String, `server`: kotlin.String? = null): Store { return FfiConverterTypeStore.lift( uniffiRustCallWithError(AtomicSdkException) { _status -> UniffiLib.INSTANCE.uniffi_atomic_ffi_fn_constructor_store_open( - FfiConverterString.lower(`path`),_status) + FfiConverterString.lower(`path`),FfiConverterOptionalString.lower(`server`),_status) } ) } diff --git a/ffi/kotlin/src/test/kotlin/dev/atomicdata/StoreTest.kt b/ffi/kotlin/src/test/kotlin/dev/atomicdata/StoreTest.kt index c945e38cf5..75d829190f 100644 --- a/ffi/kotlin/src/test/kotlin/dev/atomicdata/StoreTest.kt +++ b/ffi/kotlin/src/test/kotlin/dev/atomicdata/StoreTest.kt @@ -231,7 +231,7 @@ class StoreTest { "https://atomicdata.dev not reachable", ) assertTrue(resource!!.subject().startsWith("https://")) - assertTrue(store.has(subject)) + assertTrue(store.has(resource.subject())) resource.close() store.close() } diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 729e6ab6c6..bbb98a04bb 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -383,7 +383,7 @@ mod tests { assert!(!store.has(subject.clone())); if let Ok(Some(resource)) = store.get(subject.clone()) { assert!(resource.subject().starts_with("https://")); - assert!(store.has(subject)); + assert!(store.has(resource.subject())); } } } diff --git a/python/tests/test_sdk.py b/python/tests/test_sdk.py index d94f70788c..90a3bd756c 100644 --- a/python/tests/test_sdk.py +++ b/python/tests/test_sdk.py @@ -183,7 +183,8 @@ def test_get_fetches_http_schema_resource(): if resource is None: pytest.skip("https://atomicdata.dev not reachable") assert resource.subject.startswith("https://") - assert store.has(subject) + # Cached under the resource's own subject (may differ from the request URL). + assert store.has(resource.subject) def test_save_remote_posts_over_http(store): From b986396adc2c8911224b2765d90a0b3979062303 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 23 Aug 2026 08:14:41 +0000 Subject: [PATCH 12/16] Document property/value query and upload Python abi3 wheels from CI. Collection scripts need query(property=..., value=...), not only parent listing. Windows hosts without MSVC can pip-install the workflow artifact instead of compiling from source. PyPI is still later. Co-authored-by: joepmeindertsma --- .github/workflows/python-sdk.yml | 7 +++++++ CHANGELOG.md | 2 +- docs/src/python.md | 17 ++++++++++++++++- planning/python-sdk.md | 2 +- python/README.md | 23 ++++++++++++++++++++--- python/pyproject.toml | 2 +- python/tests/test_sdk.py | 15 +++++++++++++++ python/uv.lock | 30 +++++++++++++++++++++++++++++- 8 files changed, 90 insertions(+), 8 deletions(-) diff --git a/.github/workflows/python-sdk.yml b/.github/workflows/python-sdk.yml index e9a93c00d3..1a6de45508 100644 --- a/.github/workflows/python-sdk.yml +++ b/.github/workflows/python-sdk.yml @@ -44,3 +44,10 @@ jobs: - uses: astral-sh/setup-uv@v10.0.1 - name: uv run pytest run: uv run pytest -q + - name: Build abi3 wheel + run: uv run maturin build --release --out dist + - uses: actions/upload-artifact@v4 + with: + name: atomic-data-${{ matrix.os }} + path: python/dist/*.whl + if-no-files-found: error diff --git a/CHANGELOG.md b/CHANGELOG.md index 47a6defd54..7cc4be0cb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -363,7 +363,7 @@ See [STATUS.md](server/STATUS.md) to learn more about which features will remain `ingest_commit_json` serializes; `sync::ws_apply::apply_commit_json` now returns the `CommitResponse` instead of `()`. See `planning/runtime-boundary-decision.md`. -- **Python SDK** (`python/`, import `atomic_data`): bindings over `atomic_lib` via PyO3. Local redb plus Iroh P2P (`start_peer`, `sync_with`, live push on save). HTTP GET of `https://` subjects (schema / external resources); optional `server=` for `/search` and `save_remote()`. +- **Python SDK** (`python/`, import `atomic_data`): bindings over `atomic_lib` via PyO3. Local redb plus Iroh P2P (`start_peer`, `sync_with`, live push on save). HTTP GET of `https://` subjects (schema / external resources); optional `server=` for `/search` and `save_remote()`. GitHub Actions uploads Linux/Windows abi3 wheels (no PyPI yet). - **Kotlin SDK** (`ffi/`, package `dev.atomicdata`): UniFFI bindings over `atomic_lib`. Same local redb + Iroh + HTTP surface as Python (`startPeer`, `syncWith`, `search`, `saveRemote`). JVM tests included; Android AAR is later. ## [v0.41.0-beta.2] - 2026-08-01 diff --git a/docs/src/python.md b/docs/src/python.md index e53d742bb3..b1f8fe8f22 100644 --- a/docs/src/python.md +++ b/docs/src/python.md @@ -18,7 +18,8 @@ There is no PyPI wheel yet, so the first install compiles `atomic_lib`. On Windows, also install [Visual Studio Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/) with the **Desktop development with C++** workload. `uv` / maturin link -with MSVC's `link.exe`; GNU/MinGW is not supported. +with MSVC's `link.exe`; GNU/MinGW is not supported. The `Python SDK` +workflow uploads abi3 wheels for Linux and Windows as CI artifacts. ```bash cd python @@ -53,12 +54,26 @@ note.save() got = store.get(note.subject) print(got["name"], got["description"]) +# Collection listing: children of a drive for child in store.query(parent=setup.drive_subject): print(child.subject, child.name) +# Property / value filter (AND with parent / class_url when given) +named = store.query(property="name", value="Hello") +drafts = store.query( + parent=setup.drive_subject, + class_url=urls.PLAIN_TEXT, + property="description", + value="Edited offline", +) + store.flush() ``` +`query()` reads this store's index — it is not the HTTP `/query` endpoint. +After `get()` / `save_remote()`, the cached copies are what `query()` sees. +`search()` is the server full-text path. + `setup.agent_secret` is the only way to sign writes after you reopen the store. Keep it. diff --git a/planning/python-sdk.md b/planning/python-sdk.md index 4693bc3d2b..c439eede67 100644 --- a/planning/python-sdk.md +++ b/planning/python-sdk.md @@ -61,7 +61,7 @@ makes two devices converge today. - Blobs - History / time-travel - Code-first schema (see `json-schema-code-first.md`) -- PyPI publish / Dagger CI job (GitHub Actions `Python SDK` covers pytest on Linux and Windows) +- PyPI publish / Dagger CI job (GitHub Actions `Python SDK` covers pytest on Linux and Windows and uploads abi3 wheels as artifacts) ## Tests diff --git a/python/README.md b/python/README.md index 84c8857d4e..49eaf314f4 100644 --- a/python/README.md +++ b/python/README.md @@ -42,7 +42,9 @@ the first install compiles `atomic_lib`. On **Windows** that compile needs [Visual Studio Build Tools] with the **Desktop development with C++** workload (`link.exe`). GNU/MinGW is not a supported target. `uv run pytest` will fail at link time without those -tools — that is the host, not the package. +tools — that is the host, not the package. GitHub Actions `Python SDK` +uploads abi3 wheels (`cp39-abi3`) for Linux and Windows so you can +`pip install` a CI artifact without a compiler. [Visual Studio Build Tools]: https://visualstudio.microsoft.com/visual-cpp-build-tools/ @@ -63,6 +65,10 @@ pytest Then `import atomic_data`. +To skip the compile, download a wheel from a `Python SDK` Actions run +(`atomic-data-ubuntu-latest` / `atomic-data-windows-latest`) and +`pip install path/to/atomic_data-*.whl`. The extension is abi3 (Python 3.9+). + ## Quick start ```python @@ -82,9 +88,19 @@ note.save() got = store.get(note.subject) print(got["name"], got["description"]) +# Collection listing: children of a drive for child in store.query(parent=setup.drive_subject): print(child.subject, child.name) +# Property / value filter (AND with parent / class_url when given) +named = store.query(property="name", value="Hello") +drafts = store.query( + parent=setup.drive_subject, + class_url=urls.PLAIN_TEXT, + property="description", + value="Edited offline", +) + store.flush() # P2P: start Iroh, give this URI to another device, then sync. @@ -122,8 +138,9 @@ before opening the same directory again — redb takes an exclusive file lock. - **Store** — `open(path, server=None)`, `in_memory(server=None)`, `setup(name)`, `load_agent(secret)`, `create(class_url, name, ...)`, `get(subject)` (HTTP - GET for unknown `https://` subjects), `search(query)`, `query(...)`, - `delete(subject)`, `flush()`, context manager + GET for unknown `https://` subjects), `search(query)` (server `/search`), + `query(parent=..., class_url=..., property=..., value=...)` (local index; + filters AND together), `delete(subject)`, `flush()`, context manager - **Iroh** — `start_peer()`, `peer_id`, `announce()`, `sync_with(node_id)`, `add_peer()`, `peers()`, `live_peers()`, `wait_for(subject)` - **Resource** — dict-like access, `.save()` (local + live-push if peers are diff --git a/python/pyproject.toml b/python/pyproject.toml index 9874bb181d..abb629b552 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -35,7 +35,7 @@ Repository = "https://github.com/atomicdata-dev/atomic-server" dev = ["pytest>=8"] [dependency-groups] -dev = ["pytest>=8"] +dev = ["pytest>=8", "maturin>=1.8,<2"] [tool.uv] default-groups = ["dev"] diff --git a/python/tests/test_sdk.py b/python/tests/test_sdk.py index 90a3bd756c..01e924de09 100644 --- a/python/tests/test_sdk.py +++ b/python/tests/test_sdk.py @@ -102,6 +102,21 @@ def test_query_by_parent_and_class(store): assert a.subject not in subjects +def test_query_by_property_value(store): + drive = store.active_drive + keep = store.create(urls.PLAIN_TEXT, name="keep", description="draft") + store.create(urls.PLAIN_TEXT, name="drop", description="other") + + named = store.query(property="name", value="keep") + assert {n.subject for n in named} == {keep.subject} + + drafts = store.query(parent=drive, property="description", value="draft") + assert {n.subject for n in drafts} == {keep.subject} + + with pytest.raises(ValueError, match="value="): + store.query(property="name") + + def test_delete_removes_resource(store): # Folder has no extra required props; destroy/save validate schema. note = store.create(urls.FOLDER, name="ephemeral") diff --git a/python/uv.lock b/python/uv.lock index 078fc06f14..4c20a6b999 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -19,6 +19,7 @@ dev = [ [package.dev-dependencies] dev = [ + { name = "maturin" }, { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "pytest", version = "9.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] @@ -28,7 +29,10 @@ requires-dist = [{ name = "pytest", marker = "extra == 'dev'", specifier = ">=8" provides-extras = ["dev"] [package.metadata.requires-dev] -dev = [{ name = "pytest", specifier = ">=8" }] +dev = [ + { name = "maturin", specifier = ">=1.8,<2" }, + { name = "pytest", specifier = ">=8" }, +] [[package]] name = "colorama" @@ -75,6 +79,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] +[[package]] +name = "maturin" +version = "1.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/b3/addd877f871fb1860d46d3a4f206ecb10b946c85846805e6367631926fd3/maturin-1.14.1.tar.gz", hash = "sha256:9d6577a62cd08e0ceba7a0db06fb098e0c9b1b3429bad747a4f3a18215a1b3df", size = 369637, upload-time = "2026-06-19T05:19:49.774Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/f0/97c5a5bd9c71653a066c0976a484eaaae50b9369557838a4176b7b0bdaa5/maturin-1.14.1-py3-none-linux_armv6l.whl", hash = "sha256:522292398945442cdafa9daeb2271b2340fbde57027b818f923f88eab04174f8", size = 10207496, upload-time = "2026-06-19T05:19:09.321Z" }, + { url = "https://files.pythonhosted.org/packages/fe/83/294bca639b0e052f1e2f65199b3db258780c7d4e31408b934c9c974a1379/maturin-1.14.1-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:ffe5ad71f21d1e6603c4dd75f7fee34adf5ed5ebcebb692886549888ebb329ed", size = 19680113, upload-time = "2026-06-19T05:19:13.43Z" }, + { url = "https://files.pythonhosted.org/packages/43/b6/79c881410a3b1c187f7eb3d407aecae646c6a4433d630d72200359015e83/maturin-1.14.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f3306078070c1508fd715b9116070cbcaff5959024272a9f1e6f5cb29768b86c", size = 10169205, upload-time = "2026-06-19T05:19:16.615Z" }, + { url = "https://files.pythonhosted.org/packages/93/9d/44b6f26dcb7f7a04c5501ac2dbb6ca1490150682baa525ca5860504f9eab/maturin-1.14.1-py3-none-manylinux_2_12_i686.manylinux2010_i686.musllinux_1_1_i686.whl", hash = "sha256:cd457cd88961156e26379e1155bd287cc0ec1c8b2f1582b0660fb31b87c8842d", size = 10188098, upload-time = "2026-06-19T05:19:19.736Z" }, + { url = "https://files.pythonhosted.org/packages/1a/bd/9c0d5d6983905ce2c9edaa073a7e89355a9cf7f396988e05d32f1c37785d/maturin-1.14.1-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:dfc54ae32e6fcb18302193ab9a30b0b25eefffba994ae13238974805533ef75e", size = 10627576, upload-time = "2026-06-19T05:19:22.713Z" }, + { url = "https://files.pythonhosted.org/packages/e5/33/b096412bd6a7cb399652b260666f901adf88a687181a6dbd6a3f89f0a94e/maturin-1.14.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:a131d912b5267e640bc96d70f4914e10590aed64082ec9abacba7cea52004224", size = 10085181, upload-time = "2026-06-19T05:19:25.69Z" }, + { url = "https://files.pythonhosted.org/packages/56/8d/08c3bf469c38a23c9e6c877e338193001eb604d010fedc08341974e38528/maturin-1.14.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:be18fc568fb76884c0205456336892a75105ec398e6b667cd777c6268bd06d69", size = 10026363, upload-time = "2026-06-19T05:19:28.904Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a4/c4d1a92839f8745ab4aab988a7db884a79d6d710bd3b286fcf9316dece1a/maturin-1.14.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:994a0c8ba3ad8a92b3a9ee1b02645d200d610216b15cff5102b0fe65e8e08666", size = 13321347, upload-time = "2026-06-19T05:19:32.411Z" }, + { url = "https://files.pythonhosted.org/packages/b3/fa/170f04624d03fd07d2a8b1b67de83a127af93aef9eaa425839553347297b/maturin-1.14.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:be80866363e605d137991b491a741a84cde9ae350183c4c85f49690ca9aaaa65", size = 10877609, upload-time = "2026-06-19T05:19:35.448Z" }, + { url = "https://files.pythonhosted.org/packages/61/ad/1ae2e1d0ded282bf2c55ac13f0811d87deb425e200ae64a15785675dede9/maturin-1.14.1-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:5282dffd4b539d2be245f4e5b1a5ab6bc1033b58f4a4872f5833f9d43c954aa4", size = 10417316, upload-time = "2026-06-19T05:19:38.28Z" }, + { url = "https://files.pythonhosted.org/packages/fb/27/bf677183920718da49cd7982d6a3ffc440aad8919329f571d189f81b7bdf/maturin-1.14.1-py3-none-win32.whl", hash = "sha256:1a04de0a20188f95c721b5702eed18140bdcccb28c386797093eca3f62f4d4e0", size = 8931293, upload-time = "2026-06-19T05:19:41.183Z" }, + { url = "https://files.pythonhosted.org/packages/63/4b/585adeb9167b08d3cdff0032a938b0e72655c92003df4f52c3f696a1bcc2/maturin-1.14.1-py3-none-win_amd64.whl", hash = "sha256:3c9f94640ecc4895e94abaf834a0684430032c865b2748a36c12461fd9252fdd", size = 10314067, upload-time = "2026-06-19T05:19:44.389Z" }, + { url = "https://files.pythonhosted.org/packages/51/d4/dac8c0720ae246be1700afb6fbdbbea20fe35b13f6570b2f70faa005df77/maturin-1.14.1-py3-none-win_arm64.whl", hash = "sha256:15cea8fcb3ba47dd636f50092bb34baea8b04ac777392f23e6bf8a9a61efb894", size = 9718943, upload-time = "2026-06-19T05:19:47.49Z" }, +] + [[package]] name = "packaging" version = "26.3" From 9efb14e88a524e3a55d2496855fde1aec2be234a Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 18 Sep 2026 05:29:37 +0000 Subject: [PATCH 13/16] fix(python): point PERSONAL_DRIVE export at renamed PRIVATE_DRIVE constant lib/src/urls.rs renamed PERSONAL_DRIVE to PRIVATE_DRIVE (value unchanged); update the Python binding to reference the new Rust constant while keeping the exposed Python name for compatibility. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Fug2CneBCKqotZzd3PDSiu --- python/src/urls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/src/urls.rs b/python/src/urls.rs index b9dcc1f758..1d49f497ea 100644 --- a/python/src/urls.rs +++ b/python/src/urls.rs @@ -44,7 +44,7 @@ pub(crate) fn urls_module(py: Python<'_>) -> PyResult> { m.add("SUBJECT", urls::SUBJECT)?; m.add("SIGNER", urls::SIGNER)?; m.add("DRIVE_PROP", urls::DRIVE_PROP)?; - m.add("PERSONAL_DRIVE", urls::PERSONAL_DRIVE)?; + m.add("PERSONAL_DRIVE", urls::PRIVATE_DRIVE)?; m.add("LANGUAGE", urls::LANGUAGE)?; Ok(m) From c827c56aaab0ff4a0328621ec029ec2d8a3338e2 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 18 Sep 2026 11:15:32 +0000 Subject: [PATCH 14/16] chore(sdk): refresh Python and UniFFI lockfiles for atomic_lib 0.41.0-beta.7 Path crates still compiled against beta.2 after the rebase onto develop. Co-authored-by: joepmeindertsma --- ffi/Cargo.lock | 3 ++- python/Cargo.lock | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ffi/Cargo.lock b/ffi/Cargo.lock index d495ecdc01..1a7ca2221c 100644 --- a/ffi/Cargo.lock +++ b/ffi/Cargo.lock @@ -265,7 +265,7 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "atomic_lib" -version = "0.41.0-beta.2" +version = "0.41.0-beta.7" dependencies = [ "anyhow", "argon2", @@ -294,6 +294,7 @@ dependencies = [ "ulid", "url", "urlencoding", + "web-time", ] [[package]] diff --git a/python/Cargo.lock b/python/Cargo.lock index 5516ff8869..7ae8add67c 100644 --- a/python/Cargo.lock +++ b/python/Cargo.lock @@ -216,7 +216,7 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "atomic_lib" -version = "0.41.0-beta.2" +version = "0.41.0-beta.7" dependencies = [ "anyhow", "argon2", @@ -245,6 +245,7 @@ dependencies = [ "ulid", "url", "urlencoding", + "web-time", ] [[package]] From d08f54fad75282460b64b6a8928267b004db2043 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 18 Sep 2026 13:57:16 +0000 Subject: [PATCH 15/16] ci: honor [hosted-ci] to run Main on GitHub-hosted runners [hosted-ci] Pick treats a self-hosted runner as available whenever it is online, so a box whose Dagger engine cannot start still never falls back. Same opt-in shape as [full-e2e]. Co-authored-by: joepmeindertsma --- .github/workflows/main.yml | 11 +++++++++++ CHANGELOG.md | 4 ++++ CONTRIBUTING.md | 4 +++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index aabd7b7870..6aff64e776 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -35,6 +35,10 @@ concurrency: # Escape hatch (force hosted, skip Mancave): # Settings -> Secrets and variables -> Actions -> Variables # CI_RUNNER = ["ubuntu-latest"] +# Or `[hosted-ci]` in the commit message (same opt-in shape as +# `[full-e2e]`). Use it when the self-hosted runner is online but the +# Dagger engine on it cannot start — pick still sees "online" and would +# otherwise never fall back. # # Only this workflow uses the self-hosted runner. Releasing/deploying stay # on ubuntu-latest — a release must not depend on one PC being awake. @@ -61,6 +65,7 @@ jobs: FORCE_RUNNER: ${{ vars.CI_RUNNER }} EVENT_NAME: ${{ github.event_name }} REPO: ${{ github.repository }} + COMMIT_MSG: ${{ github.event.head_commit.message }} run: | set -euo pipefail @@ -76,6 +81,12 @@ jobs: exit 0 fi + if printf '%s' "${COMMIT_MSG:-}" | grep -q '\[hosted-ci\]'; then + echo "[hosted-ci] in commit message — forcing GitHub-hosted" + echo "host=hosted" >> "$GITHUB_OUTPUT" + exit 0 + fi + if [ -z "${GH_TOKEN:-}" ]; then echo "No CI_RUNNER_STATUS_TOKEN — assuming Mancave online" echo "host=mancave" >> "$GITHUB_OUTPUT" diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cc4be0cb4..59f38ebab2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,10 @@ See [STATUS.md](server/STATUS.md) to learn more about which features will remain "Publish :develop image" workflow covers the case where the pipeline cannot go green for reasons unrelated to whether the binary builds; it refuses `latest` and `v*` tags, which release.yml owns. +- CI: `[hosted-ci]` in a commit message runs Main on GitHub-hosted runners. + Same opt-in shape as `[full-e2e]`. The pick job treats a self-hosted + runner as available whenever it is online, so a box whose Dagger engine + cannot start still never falls back. - Error-handling hygiene on the commit and read paths. The legacy `set`/`push`/`remove` rejection in `sync::engine::ingest_commit` now checks diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 361f2d20b7..6e95a65478 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -167,7 +167,7 @@ next release reintroduces the bug. - We try to test at every level, unit tests, integration tests, e2e tests (playwright). - When tests fail, first make sure the unit tests are green, then do integration tests, then to e2e. - If e2e tests fail, try walking through the steps 1 by 1 either with the playwright debugger, or by simply reproducing the steps in your browser of choice. -- Feature-branch CI runs Playwright **light** (`@smoke`). `develop` and `v*` tags run the **full** suite. Opt in to full on a branch with a `full-e2e` PR label, `[full-e2e]` in the commit message, or `workflow_dispatch` `e2e_mode=full`. See `planning/e2e-light-heavy.md`. +- Feature-branch CI runs Playwright **light** (`@smoke`). `develop` and `v*` tags run the **full** suite. Opt in to full on a branch with a `full-e2e` PR label, `[full-e2e]` in the commit message, or `workflow_dispatch` `e2e_mode=full`. See `planning/e2e-light-heavy.md`. Put `[hosted-ci]` in a commit message to run Main on GitHub-hosted runners when the self-hosted Dagger engine cannot start. Feature-specific browser journeys belong in `browser/e2e/tests/` and run through the shared CI pipeline. For a focused run with CI's service setup, use Dagger @@ -393,6 +393,8 @@ Two consequences worth knowing: with a `full-e2e` PR label, `[full-e2e]` in the commit message, or `workflow_dispatch` `e2e_mode=full`. (Do not name the Dagger flag `--e2e-mode`: the CLI camelCases it to `e2EMode` and the call fails.) + `[hosted-ci]` in the commit message is a GitHub Actions pick-job flag + (Main on ubuntu-latest); it is not a Dagger argument. Every deploy then has to prove itself: the job polls `/server` on the target until it answers `200` (with enough patience for a store migration). A deploy From 9c2b4a118574c1ac926d9c18749be2b3724671ef Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 18 Sep 2026 16:42:59 +0000 Subject: [PATCH 16/16] ci: match hosted-ci and full Playwright flags as standalone tokens [hosted-ci] The previous commit described the full Playwright opt-in in its body, and pick grepped the whole message as a substring, so this branch ran the heavy suite on GitHub-hosted runners. Co-authored-by: joepmeindertsma --- .github/workflows/main.yml | 12 ++++++------ CHANGELOG.md | 9 +++++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6aff64e776..b0e891547e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -35,10 +35,10 @@ concurrency: # Escape hatch (force hosted, skip Mancave): # Settings -> Secrets and variables -> Actions -> Variables # CI_RUNNER = ["ubuntu-latest"] -# Or `[hosted-ci]` in the commit message (same opt-in shape as -# `[full-e2e]`). Use it when the self-hosted runner is online but the -# Dagger engine on it cannot start — pick still sees "online" and would -# otherwise never fall back. +# Or `[hosted-ci]` standing alone in the commit message (same opt-in +# shape as the full Playwright token). A mention inside a sentence does +# not count — pick greps the whole message. Use it when the self-hosted +# runner is online but the Dagger engine on it cannot start. # # Only this workflow uses the self-hosted runner. Releasing/deploying stay # on ubuntu-latest — a release must not depend on one PC being awake. @@ -81,7 +81,7 @@ jobs: exit 0 fi - if printf '%s' "${COMMIT_MSG:-}" | grep -q '\[hosted-ci\]'; then + if printf '%s' "${COMMIT_MSG:-}" | grep -qE '(^|[[:space:]])\[hosted-ci\]([[:space:]]|$)'; then echo "[hosted-ci] in commit message — forcing GitHub-hosted" echo "host=hosted" >> "$GITHUB_OUTPUT" exit 0 @@ -142,7 +142,7 @@ jobs: mode=full elif [[ "$REF" == refs/tags/v* ]]; then mode=full - elif printf '%s' "${COMMIT_MSG:-}" | grep -q '\[full-e2e\]'; then + elif printf '%s' "${COMMIT_MSG:-}" | grep -qE '(^|[[:space:]])\[full-e2e\]([[:space:]]|$)'; then mode=full elif prs=$(gh api "repos/$REPO/commits/$SHA/pulls" -H "Accept: application/vnd.github+json" 2>/dev/null); then if printf '%s' "$prs" | jq -e 'any(.[]; any(.labels[]; .name == "full-e2e"))' >/dev/null; then diff --git a/CHANGELOG.md b/CHANGELOG.md index 59f38ebab2..fb847b8b21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,10 +18,11 @@ See [STATUS.md](server/STATUS.md) to learn more about which features will remain "Publish :develop image" workflow covers the case where the pipeline cannot go green for reasons unrelated to whether the binary builds; it refuses `latest` and `v*` tags, which release.yml owns. -- CI: `[hosted-ci]` in a commit message runs Main on GitHub-hosted runners. - Same opt-in shape as `[full-e2e]`. The pick job treats a self-hosted - runner as available whenever it is online, so a box whose Dagger engine - cannot start still never falls back. +- CI: `[hosted-ci]` standing alone in a commit message runs Main on + GitHub-hosted runners (same opt-in shape as the full Playwright token). + A mention inside a sentence does not count. The pick job treats a + self-hosted runner as available whenever it is online, so a box whose + Dagger engine cannot start still never falls back. - Error-handling hygiene on the commit and read paths. The legacy `set`/`push`/`remove` rejection in `sync::engine::ingest_commit` now checks