Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,12 @@ async fn run_default_browser_suites(services: &Services, shard: Option<Shard>) -
for test in test_files(&["examples", "wasm-smoke", "tests"])? {
suite_args.push(per_test_args("examples/wasm-smoke", test));
}
for test in test_files(&["examples", "yew-web-demo", "tests"])? {
suite_args.push(per_test_args("examples/yew-web-demo", test));
}
for test in test_files(&["examples", "dioxus-web-demo", "tests"])? {
suite_args.push(per_test_args("examples/dioxus-web-demo", test));
}

let total = suite_args.len();
if let Some(shard) = shard {
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/18-file-handling.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 18: File handling

**Status**: normative for the decisions it records. R64 the file core, R65 the file server, R66 the connetto seam, R67 the native client and R68 the browser client are built. R69 the demos is in progress, its executable half, demo schemas, tab and worker content protocol and browser-stack wiring built 2026-09-17 as pull requests #28 to #31, and its offline stage, reconnect upload and two-viewer refusal proofs built 2026-09-18 with the recovery table's Frame row amended to match, leaving the demo surfaces open. R79 the peer link and R87 the quotas are not built. Every statement carries **Decided (RN)** or an **Amended (RN)** beside it, where `RN` is the phase in `plans/master-implementation-plan.md` that owns it, and that phase's section records each decision with its rejected alternatives. Chapter 07 is the historical record of the thinking that preceded these decisions and defers to this chapter wherever the two disagree.
**Status**: normative for the decisions it records. R64 the file core, R65 the file server, R66 the connetto seam, R67 the native client and R68 the browser client are built. R69 the demos is in progress, its executable half, demo schemas, tab and worker content protocol and browser-stack wiring built 2026-09-17 as pull requests #28 to #31, and its offline stage, reconnect upload and two-viewer refusal proofs built 2026-09-18 with the recovery table's Frame row amended to match, and the web demos' photos surfaces built 2026-09-18, leaving the desktop demo surface open. R79 the peer link and R87 the quotas are not built. Every statement carries **Decided (RN)** or an **Amended (RN)** beside it, where `RN` is the phase in `plans/master-implementation-plan.md` that owns it, and that phase's section records each decision with its rejected alternatives. Chapter 07 is the historical record of the thinking that preceded these decisions and defers to this chapter wherever the two disagree.

---

Expand Down
88 changes: 82 additions & 6 deletions examples/dioxus-web-demo/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 22 additions & 1 deletion examples/dioxus-web-demo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ rust-version = "1.88"
license = "MIT"
publish = false

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
# The browser platform: transports, leader election, locks, the relay hub,
# and the DB worker orchestration. The demo supplies its schema and baked
Expand All @@ -16,6 +19,8 @@ connetto-web = { path = "../../crates/connetto-web" }
# Default features off: this is a wasm build with a browser transport.
connetto-client = { path = "../../crates/connetto-client", default-features = false }
connetto-core = { path = "../../crates/connetto-core" }
# FileId and MimeClass for the photo staging closure.
connetto-file-core = { path = "../../crates/connetto-file-core" }
# The one live-query hook, bound to the component lifecycle.
connetto-dioxus = { path = "../../crates/connetto-dioxus" }
diesel = { version = "2", features = ["sqlite"] }
Expand All @@ -32,23 +37,39 @@ rosetta-uuid = { version = "0.1.3", features = ["diesel", "sqlite"] }
# `File`, `FileList` and `HtmlInputElement` back the import file picker.
web-sys = { version = "0.3", features = [
"Blob",
"BlobPropertyBag",
"BroadcastChannel",
"DedicatedWorkerGlobalScope",
"Document",
"Element",
"File",
"FileList",
"HtmlAnchorElement",
"HtmlElement",
"HtmlInputElement",
"MessageEvent",
"Request",
"RequestInit",
"Response",
"UrlSearchParams",
"Url",
"Window",
"Worker",
"WorkerGlobalScope",
"WorkerOptions",
"WorkerType",
"console",
] }
# Structured logging (R12): this demo emits through `tracing` and installs the
# developer-console destination in `main`.
tracing = { version = "0.1", default-features = false, features = ["std"] }

[dev-dependencies]
wasm-bindgen-test = "0.3"
futures-channel = "0.3"
serde_json = "1"
js-sys = "0.3"
connetto-file-client = { path = "../../crates/connetto-file-client" }

# The build script translates schema.sql and frontend.sql through pg2sqlite and
# bakes a template per tier, so the app ships every tier's schema pre-applied
# and never executes DDL at startup. Same pipeline as the smoke crate.
Expand Down
8 changes: 6 additions & 2 deletions examples/dioxus-web-demo/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,17 @@ fn write_policy_tables(documents: &[&str], views: &[String], out: &std::path::Pa
fn main() {
println!("cargo::rerun-if-changed=schema.sql");
println!("cargo::rerun-if-changed=frontend.sql");
println!("cargo::rerun-if-changed=policies.sql");
let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR").expect("cargo sets OUT_DIR"));
let synced_views = translate(&["schema.sql"], &out_dir.join("replica-ddl.sql"));
let synced_views = translate(
&["schema.sql", "policies.sql"],
&out_dir.join("replica-ddl.sql"),
);
translate(&["frontend.sql"], &out_dir.join("frontend-ddl.sql"));
// The synced tier only: the local tier is a separate database, attached
// under its own schema, and the check the map feeds reads `main`.
write_policy_tables(
&["schema.sql"],
&["schema.sql", "policies.sql"],
&synced_views,
&out_dir.join("replica-tables.rs"),
);
Expand Down
26 changes: 26 additions & 0 deletions examples/dioxus-web-demo/policies.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
-- The row-level security the backend enforces on the synced tables, kept apart
-- from schema.sql because the two reach the server as separate documents:
-- schema.sql feeds CONNETTO_PG_DDL and is what clients sync, this file feeds
-- CONNETTO_PG_POLICIES and is what the authorization model is derived from.
-- Apply both to Postgres, this one last, after schema.sql, the file server
-- DDL, roles.sql and content.sql.
-- build.rs translates the pair together, which is what splits the replica's
-- orders into a backing table, a view of the logical name, and INSTEAD OF
-- triggers. The caller is read from app.user_id, which the server binds per
-- transaction and the replica answers with the registered current_app_user()
-- function, so both ends compare against the same identity.
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY orders_p ON orders USING (owner_id = current_setting('app.user_id', true)); -- NOSONAR S1192, SQL DDL has no constants for the caller setting the three policies share

-- The same shape on the composite-key table, so its replica half is split the
-- same way and its INSTEAD OF triggers have to match a row on two key columns
-- rather than one.
ALTER TABLE order_lines ENABLE ROW LEVEL SECURITY;
CREATE POLICY order_lines_p ON order_lines USING (owner_id = current_setting('app.user_id', true));

-- The photo rows are visible to the owner of the order they hang off, and the
-- owner is repeated on the row as it is on order_lines, so the comparison
-- settles from the row itself. The file server's visibility function consults
-- exactly this table under the caller's identity.
ALTER TABLE photos ENABLE ROW LEVEL SECURITY;
CREATE POLICY photos_p ON photos USING (owner_id = current_setting('app.user_id', true));
38 changes: 20 additions & 18 deletions examples/dioxus-web-demo/schema.sql
Original file line number Diff line number Diff line change
@@ -1,31 +1,33 @@
-- The one source of truth for the demo: the Postgres dialect schema the
-- backend owns. build.rs translates this through pg2sqlite and bakes the
-- replica template database the app ships. The connetto-server for this demo
-- must be started with this same schema in CONNETTO_PG_DDL and must list
-- every synced table in CONNETTO_WRITABLE. Apply in this order: this file,
-- backend owns. build.rs translates this through pg2sqlite into the SQLite DDL
-- a first boot applies. The connetto-server for this demo must be started with
-- this same schema in CONNETTO_PG_DDL and must list every synced table in
-- CONNETTO_WRITABLE. Apply in this order: this file,
-- connetto_file_server::DEPLOYMENT_DDL, roles.sql (the non-owner role
-- required by CONNETTO_READER_URL), then content.sql.
-- The server also requires CONNETTO_AUTH, CONNETTO_AUTH_BIND, and the
-- CONNETTO_OIDC_* variables written by the dev IdP (see dev_idp.rs).
-- required by CONNETTO_READER_URL), content.sql, then policies.sql.
-- The key default is load-bearing on the client rather than here: build.rs
-- translates it through pg2sqlite into the replica's own DEFAULT (uuidv4()),
-- which mints the key when a local write omits it. Both ends mint version 4.
-- The quantity is non-null because every client schema already declares it so.
CREATE TABLE orders (id UUID PRIMARY KEY DEFAULT gen_random_uuid(), quantity BIGINT NOT NULL CHECK (quantity >= 0));
-- owner_id carries who a row belongs to, which policies.sql compares against
-- the caller. It has no default: pg2sqlite maps current_setting only inside a
-- policy expression, so a default naming the caller would translate into a
-- call the replica cannot resolve, and every write names the owner instead.
CREATE TABLE orders (id UUID PRIMARY KEY DEFAULT gen_random_uuid(), owner_id TEXT NOT NULL, quantity BIGINT NOT NULL CHECK (quantity >= 0));

-- The lines of an order, keyed by the order and the line number together. It is
-- the one table here whose key spans two columns, which the replica's own schema
-- and every key connetto encodes on the wire have to carry as a pair.
CREATE TABLE order_lines (
order_id UUID NOT NULL REFERENCES orders(id),
line_no INTEGER NOT NULL,
quantity BIGINT NOT NULL CHECK (quantity >= 0),
PRIMARY KEY (order_id, line_no)
);
-- the one table here whose key spans two columns, and the translation below
-- splits it like any policy-bearing table, so its INSTEAD OF triggers match a
-- row on both key columns rather than one. owner_id repeats rather than being
-- read through the parent order, so the policy settles from the row itself,
-- which is what keeps the change path free of a round trip.
CREATE TABLE order_lines (order_id UUID NOT NULL REFERENCES orders(id), line_no INTEGER NOT NULL, owner_id TEXT NOT NULL, quantity BIGINT NOT NULL CHECK (quantity >= 0), PRIMARY KEY (order_id, line_no));

-- The photo entry: metadata for one file's bytes, attached to an order.
-- content_id is the BLAKE3 identity the file server stores and serves under,
-- and content_state stays null until the file server's commit writes
-- `available`, so the placeholder condition is "not available" and the
-- availability flip arrives as an ordinary synced column change.
CREATE TABLE photos (id UUID PRIMARY KEY DEFAULT gen_random_uuid(), order_id UUID NOT NULL REFERENCES orders(id), content_id BYTEA NOT NULL, content_state TEXT);
-- availability flip arrives as an ordinary synced column change. owner_id
-- repeats from the parent order as it does on order_lines, so the visibility
-- policy settles from the row itself.
CREATE TABLE photos (id UUID PRIMARY KEY DEFAULT gen_random_uuid(), order_id UUID NOT NULL REFERENCES orders(id), owner_id TEXT NOT NULL, content_id BYTEA NOT NULL, content_state TEXT);
Loading
Loading