chore: Prepare the release of 0.0.4 with serverpod-4.0.0-rc.1 support (#113)

This commit is contained in:
Marcelo Mendonça Soares 2026-08-31 21:15:35 -03:00 • committed by GitHub
commit fbad5c61fe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
140 changed files with 22326 additions and 805 deletions

View file

@ -5,6 +5,11 @@ runs:
using: composite
steps:
- uses: dart-lang/setup-dart@v1
with:
sdk: 3.12.2
# Composite actions set GITHUB_ACTION_PATH to this folder, so the
# default matcher looks for dart-analyzer.json here and fails setup.
problem-matcher: false
- shell: bash
run: dart pub global activate melos

View file

@ -1,9 +1,13 @@
## 0.0.4
- chore: Updated Serverpod to `4.0.0-rc.1`.
## 0.0.3
- fix: Requires non-nullable foreign keys to be `deferred`.
- fix: Rejects `onDelete=Restrict` on synced tables in favor of `onDelete=NoAction`.
- fix: Throws proper `DatabaseException` instead of bare `Exception` on the database.
- chore: Updated Serverpod to `4.0.0-beta.4`.
- chore: Updated Serverpod to `4.0.0-rc.1`.
## 0.0.2

View file

@ -101,48 +101,47 @@ Add the packages to the server and client `pubspec.yaml` files.
```yaml
# your_project_client/pubspec.yaml
dependencies:
serverpod_offline_sync_client: 0.0.3
serverpod_offline_sync_client: 0.0.4
```
```yaml
# your_project_server/pubspec.yaml
dependencies:
serverpod_offline_sync_server: 0.0.3
serverpod_offline_sync_server: 0.0.4
```
> [!NOTE]
> Version `0.0.3` requires Serverpod `4.0.0-beta.4`.
> Version `0.0.4` requires Serverpod `4.0.0-rc.1`.
After adding the dependencies, list the `serverpod_offline_sync` module on the
`generator.yaml` file:
`generator.yaml` file and enable the experimental `database: sync` option:
```yaml
modules:
serverpod_offline_sync:
nickname: offline_sync
experimental_features:
databaseSync: true
```
The next call to `serverpod generate` and `serverpod create-migration` will
include the `offline_sync` module in the project. Note that the client tables
will only be present on the client's migration if at least one model on the
project is defined with `database: all` or `database: client`.
project is defined with `database: sync`, `database: all`, or `database: client`.
### 2. Prepare the synced tables
Each model you want to sync must be defined with `database: all` (so it exists
on both the client and the server), have a UUID primary key, and declare a
`scopeId` field. You mostly never set `scopeId` nor see its value on reads; it
is maintained by the CRDT sync layer.
Each model you want to sync must be defined with `database: sync`. That generates
the table on both the client and the server, uses a UUID primary key, and
injects the `scopeId` ownership field. You mostly never set `scopeId` nor see
its value on reads; it is maintained by the CRDT sync layer.
```yaml
class: Person
table: person
database: all
database: sync
fields:
id: UuidValue?, defaultPersist=random_v7
### Owner scope of this row. Maintained by the CRDT sync layer.
### The user never sets this field nor sees its value on reads.
scopeId: int?, relation(parent=crdt_scopes, onDelete=Cascade)
name: String
```
@ -263,13 +262,15 @@ sync session for changes to take effect.
### Data modeling limitations
Because of the nature of merge conflicts, the package imposes some data-modeling
limitations, enforced at runtime during initialization. Most are fundamental to
the design and can never be lifted.
limitations. With `database: sync`, Serverpod reports most of them at generate
time. The rest are enforced at runtime during initialization. Most are
fundamental to the design and can never be lifted.
- Every synced table must:
- Exist on both the client and the server with `database: all`.
- Have a UUID primary key.
- Declare the field `scopeId: int?, relation(parent=crdt_scopes, onDelete=Cascade)`.
- Every synced table must be defined with `database: sync`.
- Will exist on both the client and the server, like `database: all`.
- The type of the `id` will be `UuidValue` with `defaultPersist=random_v7`.
- A `scopeId` field will be added as a cascade relation to `crdt_scopes`.
- Both `id` and `scopeId` can be manually declared for more control.
- Unique indexes must include `scopeId` together with the target columns.
- Global unique indexes are unsupported, except for FK-only indexes.
- Unique indexes are only supported with at least one

View file

@ -3,7 +3,7 @@ description: Local CRDT vs baseline performance impact measurement.
publish_to: none
environment:
sdk: '^3.10.3'
sdk: '^3.12.2'
resolution: workspace
@ -11,7 +11,7 @@ dependencies:
benchmark_harness: ^2.4.0
intl: ^0.20.2
path: ^1.9.1
serverpod_database: 4.0.0-beta.4
serverpod_database: 4.0.0-rc.1
serverpod_offline_sync:
path: ../packages/serverpod_offline_sync
serverpod_offline_sync_server:

View file

@ -5,7 +5,7 @@ publish_to: 'none'
version: 1.0.0+1
environment:
sdk: ^3.10.3
sdk: '^3.12.2'
dependencies:
flutter:
@ -16,7 +16,7 @@ dependencies:
http: ^1.5.0
path: ^1.9.1
path_provider: ^2.1.5
serverpod_database: 4.0.0-beta.4
serverpod_database: 4.0.0-rc.1
serverpod_offline_sync_client:
path: ../../packages/serverpod_offline_sync_client
serverpod_offline_sync_test_client:

View file

@ -17,8 +17,8 @@ typedef CrdtSyncTransport =
class CrdtSyncSession {
CrdtSyncSession._({
required this.done,
required Future<void> Function() cancel,
}) : _cancel = cancel;
required this._cancel,
});
/// Completes when the sync session ends normally or after [cancel].
final Future<void> done;

View file

@ -55,18 +55,16 @@ bool _grantsEqual(List<CrdtScopeGrant>? a, List<CrdtScopeGrant> b) {
/// control flow); this owns the scopes. It needs only a [DatabaseSession] and
/// the membership helpers, so it carries no protocol or transport concerns.
class CrdtScopeSyncSession {
/// Creates a scope session for [userId] acting in [mode] against [_session].
/// Creates a scope session for [_userId] acting in [_mode] against [_session].
///
/// [peerNodeId] is the remote peer's CRDT node, learned from its connect
/// [_peerNodeId] is the remote peer's CRDT node, learned from its connect
/// handshake before this session is constructed.
CrdtScopeSyncSession(
this._session, {
required UuidValue userId,
required CrdtSyncPeerMode mode,
required UuidValue peerNodeId,
}) : _userId = userId,
_mode = mode,
_peerNodeId = peerNodeId;
required this._userId,
required this._mode,
required this._peerNodeId,
});
final DatabaseSession _session;
final UuidValue _userId;

View file

@ -46,7 +46,7 @@ class CrdtSync {
int syncBatchSize = defaultSyncBatchSize,
/// Delay between continuous sync rounds.
Duration continuousSyncInterval = defaultContinuousSyncInterval,
this._continuousSyncInterval = defaultContinuousSyncInterval,
}) : _syncTables = syncTables,
_serializationManager = serializationManager,
_databaseContext =
@ -55,8 +55,7 @@ class CrdtSync {
syncTables: syncTables,
serializationManager: serializationManager,
),
_syncBatchSize = syncBatchSize,
_continuousSyncInterval = continuousSyncInterval {
_syncBatchSize = syncBatchSize {
if (syncBatchSize < 1) {
throw ArgumentError.value(syncBatchSize, 'syncBatchSize', 'Must be >= 1');
}

View file

@ -67,14 +67,11 @@ class CrdtDatabase implements Database {
CrdtDatabase._(
this._delegate,
this._context, {
required List<Table> syncTables,
required int syncBatchSize,
required Duration continuousSyncInterval,
required this._syncTables,
required this._syncBatchSize,
required this._continuousSyncInterval,
required UuidValue? persistentUserId,
}) : _syncTables = syncTables,
_syncBatchSize = syncBatchSize,
_continuousSyncInterval = continuousSyncInterval,
_recorder = CrdtMutationRecorder(
}) : _recorder = CrdtMutationRecorder(
_delegate,
context: _context,
persistentUserId: persistentUserId,

View file

@ -56,10 +56,9 @@ class CrdtForeignKeyProjector {
/// Creates a projector over the given recorder context.
CrdtForeignKeyProjector(
this._context, {
required CrdtForeignKeyGraph foreignKeys,
required CrdtUniqueConflictResolver uniqueResolver,
}) : _foreignKeys = foreignKeys,
_uniqueResolver = uniqueResolver;
required this._foreignKeys,
required this._uniqueResolver,
});
final CrdtRecorderContext _context;
final CrdtForeignKeyGraph _foreignKeys;

View file

@ -8,9 +8,9 @@
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: dead_code, unnecessary_null_comparison
// ignore_for_file: dead_code, no_leading_underscores_for_library_prefixes
// ignore_for_file: unnecessary_null_comparison
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_database/serverpod_database.dart' as _isd;
import 'package:serverpod_offline_sync/serverpod_offline_sync.dart'
as _icw2tu00;

View file

@ -10,6 +10,7 @@
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_serialization/serverpod_serialization.dart' as _iss;
/// Why a synced user tombstone changed row visibility.
@ -18,8 +19,7 @@ enum CrdtDataDeletedReason implements _iss.SerializableModel {
userReinsert,
userDelete,
userCascadeDelete,
uniqueLoser,
;
uniqueLoser;
static CrdtDataDeletedReason fromJson(int index) {
switch (index) {

View file

@ -8,9 +8,9 @@
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: dead_code, unnecessary_null_comparison
// ignore_for_file: dead_code, no_leading_underscores_for_library_prefixes
// ignore_for_file: unnecessary_null_comparison
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_database/serverpod_database.dart' as _isd;
import 'package:serverpod_offline_sync/serverpod_offline_sync.dart'
as _icw2tu00;

View file

@ -8,9 +8,9 @@
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: dead_code, unnecessary_null_comparison
// ignore_for_file: dead_code, no_leading_underscores_for_library_prefixes
// ignore_for_file: unnecessary_null_comparison
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_database/serverpod_database.dart' as _isd;
import 'package:serverpod_offline_sync/serverpod_offline_sync.dart'
as _icw2tu00;

View file

@ -10,6 +10,7 @@
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_serialization/serverpod_serialization.dart' as _iss;
/// Why a foreign key projection override is active.
@ -26,8 +27,7 @@ enum CrdtForeignKeyOverrideReason implements _iss.SerializableModel {
/// cannot repair the value (restrict, no action, cascade, or the column is
/// non-nullable), so the row is hidden by projection while the attempt is
/// preserved.
missingParent,
;
missingParent;
static CrdtForeignKeyOverrideReason fromJson(int index) {
switch (index) {

View file

@ -8,9 +8,9 @@
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: dead_code, unnecessary_null_comparison
// ignore_for_file: dead_code, no_leading_underscores_for_library_prefixes
// ignore_for_file: unnecessary_null_comparison
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_database/serverpod_database.dart' as _isd;
import 'package:serverpod_offline_sync/serverpod_offline_sync.dart'
as _icw2tu00;

View file

@ -10,6 +10,7 @@
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_serialization/serverpod_serialization.dart' as _iss;
/// Materialized visibility state for a CRDT row.
@ -22,8 +23,7 @@ enum CrdtDataRowVisibility implements _iss.SerializableModel {
foreignKeyRestrictRestore,
userDelete,
userCascadeDelete,
foreignKeyCascade,
;
foreignKeyCascade;
static CrdtDataRowVisibility fromJson(int index) {
switch (index) {

View file

@ -10,6 +10,7 @@
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_serialization/serverpod_serialization.dart' as _iss;
/// Base class for HLC timestamps.

View file

@ -10,6 +10,7 @@
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_offline_sync/serverpod_offline_sync.dart'
as _icw2tu00;
import 'package:serverpod_serialization/serverpod_serialization.dart' as _iss;

View file

@ -10,6 +10,7 @@
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_database/serverpod_database.dart' as _isd;
import 'package:serverpod_offline_sync/serverpod_offline_sync.dart'
as _icw2tu00;

Some files were not shown because too many files have changed in this diff Show more