refactor: Move the core implementation to the shared package with no manual queries (#107)

This commit is contained in:
Marcelo Mendonça Soares 2026-08-04 11:04:04 -03:00 • committed by GitHub
commit 9f2ce4d9e6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -156,7 +156,7 @@ A report similar to the one above is generated per pull request as a comment on
## Technical fundamentals
**Architecture.** The engine is a transparent layer between the application and its database. Because identical code runs on the client's SQLite and the server's Postgres, there are no asymmetric merge results to reconcile. Migrations are also generated symmetrically for both ends, which makes the migration process result in the exact same data on both ends. The entire engine is built respecting the three mathematical properties that must hold for a CRDT to be consistent: idempotence, associativity, and commutativity.
**Architecture.** The engine is a transparent layer between the application and its database. Its models and database implementation live in the shared package, while the client package only binds the generated module caller to the shared sync transport. Because identical code runs on the client's SQLite and the server's Postgres, there are no asymmetric merge results to reconcile. Migrations are also generated symmetrically for both ends, which makes the migration process result in the exact same data on both ends. The entire engine is built respecting the three mathematical properties that must hold for a CRDT to be consistent: idempotence, associativity, and commutativity.
**Metadata model.** Three metadata tables track change history *without copying row data*: `crdt_data_rows` (insert + visibility), `crdt_data_fields` (update), and `crdt_data_tombstone` (delete/restore). HLCs are used to order field-level values and break ties. A monotone causal-length tombstone governs row existence, so add/delete/restore advance generations forward and can never oscillate. All metadata is normalized, with normalized IDs cached, to keep both the storage and performance impact minimal.
@ -176,7 +176,6 @@ The package is functionally in a production-ready state, but some APIs need to i
### Technical debts
- **Move the core of the implementation to a shared package instead of the client package**. Since the same code runs on the client and server, and requires table models, the first implementation was done in the client package. This means that users have to import a client package on their servers, crossing an isolation that is not common on Serverpod projects. To fix this, support for tables on shared models on Serverpod must be added, mapped on Serverpod's issue #5400. Then the core of the package must be moved to a shared package and all manually written SQL queries will be replaced by the Serverpod ORM.
- **Require that all foreign-key relations are declared deferrable**. Because the package does not store a copy of the data with the metadata, there are some scenarios in which the stored data might violate the causal order of the foreign-key constraints and a merge can fail irreversibly. To prevent this, all foreign-key constraints must be deferrable, which will be enabled by Serverpod's issue #5338.
- **Rename the `scope` name for data isolation to `space`**. This is a small refactor to avoid confusion with the `scope` keyword used in the Serverpod Auth modules. Using `space` is also clearer for usage on an application level.
- **Reorganize the package structure to facilitate the review**. The package is currently organized in a way that does not facilitate the review process. The `recorder.dart` file is extended through part files, sharing a too many private properties and methods. The resolver must be split into smaller, scoped files, as well as its unique and foreign-key resolver utils.