feat(samples): three devices sharing one database, with no server between them - #667
Merged
Conversation
…ween them samples/Rask.Example.Crdt runs three replicas -- Phone, Laptop, Tablet -- each with its own SQLite file and its own replica identity, sharing a bucket and nothing else. Normally these would be three phones; here they are three files in one process, which is the only difference that matters. Item 7 of #642. Stacked on #665 and feat/crdt-bucket-sync. The thing the sample exists to show: take two devices offline, edit DIFFERENT FIELDS of the same todo on each -- the priority on one, the done flag on the other -- then bring both back and sync. Both edits survive. Do the same with a LastModified column and one of them is gone. An E2E drives exactly that through a browser, so the claim is tested rather than asserted in prose. Each device's link to the bucket has a switch, and flipping it makes every call fail as a real client would see it. So the demo exercises the real offline path rather than a case the engine knows about, and the status line says "your edits are saved and will sync later" rather than reporting a failure -- the edit is already committed to that device's own database. FolderObjectStore is the other half: the same IObjectStore over a directory, which is what lets the sample run with no cloud credentials. It is not only a test double. It also covers a single-machine deployment with no reason to pay for object storage, and -- the interesting case -- A FOLDER SOMETHING ELSE ALREADY REPLICATES: pointed at a Syncthing share, devices converge with no central server at all; pointed at iCloud Drive or Dropbox, the replication is somebody else's problem. Objects are written beside their key and moved into place, so a reader listing concurrently sees either nothing or the whole object, which matters most when another process is replicating the folder while it is being written. Keys that would escape the root are refused rather than normalised, since a key can come back from a listing of a folder other people also write to. TryCreateAsync maps to the filesystem's own atomic create, so the conditional-create pattern works there too. cr-sqlite's native binary is per-platform and is not redistributed here, so without RASK_CRSQLITE_PATH the page explains what to download instead of failing at the first query -- a missing extension otherwise surfaces as "no such function: crsql_as_crr", which says nothing about what to do. That state is asserted too, so one of the two paths always runs in the E2E gate rather than the whole file quietly skipping on a machine without the binary. Verified by mutation: breaking the setup-card assertion turns it red on a run with no extension, so it is not passing vacuously. The sample joins the standalone-app shape of Rask.Example.Sqlite -- plain Bootstrap classes over the core elements, no Rask.Bootstrap or validation packages -- so it is added to the same in-repo implicit-usings exclusion list, whose generated usings would otherwise not resolve.
pal-tamas
force-pushed
the
feat/crdt-sample
branch
from
August 10, 2026 09:12
59cead9 to
b40e457
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three devices — Phone, Laptop, Tablet — each with its own SQLite file and its own replica identity,
sharing a bucket and nothing else. Normally these would be three phones; here they are three files in
one process, which is the only difference that matters. Item 7 of #642.
The thing to try
Take two devices offline, edit different fields of the same todo on each — the priority on one, the
done flag on the other — bring both back and press Sync everyone. Both edits survive. Do the same with
a
LastModifiedcolumn and one of them is gone.An E2E drives exactly that through a browser, so the claim is tested rather than asserted in prose.
Each device's link to the bucket has a switch, and flipping it makes every call fail as a real client
would see it — so the demo exercises the real offline path rather than a case the engine knows about.
The status line says "your edits are saved and will sync later" rather than reporting a failure,
because the edit is already committed to that device's own database.
FolderObjectStoreThe same
IObjectStoreover a directory. It is what lets the sample run with no cloud credentials, butit is not only a test double:
no central server at all; pointed at iCloud Drive or Dropbox, the replication is somebody else's
problem.
Objects are written beside their key and moved into place, so a reader listing concurrently sees either
nothing or the whole object — which matters most when another process is replicating the folder while it
is being written. Keys that would escape the root are refused rather than normalised, since a key can
come back from a listing of a folder other people also write to.
TryCreateAsyncmaps to thefilesystem's own atomic create, so the conditional-create pattern works there too.
The missing-binary problem, handled honestly
cr-sqlite ships per-platform and is not redistributed here. Without
RASK_CRSQLITE_PATHthe pageexplains what to download instead of failing at the first query — a missing extension otherwise surfaces
as
no such function: crsql_as_crr, which says nothing about what to do.That state is asserted too, so the E2E file never quietly skips in full on a machine without the
binary; one of the two paths always runs. Verified by mutation: breaking the setup-card assertion turns
it red on a no-extension run, so it is not passing vacuously.
Testing
The four new journeys ran inside the real gate with the extension present, and pass without it via the
setup-card path.
Local gates:
dotnet formatclean,dotnet build -warnaserrorclean, 336 + 320 unit, 64 browserjourneys, 26 CLI build-gate.
A note on the build
Building the E2E project tripped the MSBuild node-reuse bake failure (#650) — the guard added in
#652 caught it and named the fix (
-nodeReuse:false). Pre-existing, not introduced here, but worthknowing it is still reachable from an ordinary project build.