feat: Implement the sync protocol (#54)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
parent
83fbef8d2c
commit
f85239c3a4
16 changed files with 3031 additions and 306 deletions
|
|
@ -2,7 +2,7 @@ include: ../../analysis_options.yaml
|
|||
|
||||
analyzer:
|
||||
exclude:
|
||||
- lib/src/generated/**
|
||||
# - lib/src/generated/**
|
||||
- test/integration/test_tools/serverpod_test_tools.dart
|
||||
|
||||
formatter:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
export 'package:serverpod_offline_sync_client/serverpod_offline_sync_client.dart';
|
||||
|
||||
export 'src/business/crdt_sync.dart';
|
||||
export 'src/endpoints/crdt_sync_endpoint.dart';
|
||||
export 'src/generated/endpoints.dart';
|
||||
|
||||
// The models are exported by the client package to ensure the same class as the
|
||||
// one expected by the functions.
|
||||
// one expected by the functions. Never export the server-side protocol.
|
||||
// export 'src/generated/protocol.dart';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
import 'package:serverpod/serverpod.dart';
|
||||
import 'package:serverpod_offline_sync_client/serverpod_offline_sync_client.dart';
|
||||
|
||||
/// Extension methods for [Serverpod] to configure the CRDT sync on the server.
|
||||
extension CrdtSyncInitialize on Serverpod {
|
||||
/// Configures the endpoint with the given sync tables.
|
||||
///
|
||||
/// Must be called during server startup before any sync requests are made.
|
||||
/// Will override any previous initialization.
|
||||
///
|
||||
/// [syncBatchSize] controls the maximum number of merge changes carried by
|
||||
/// each sync stream chunk.
|
||||
///
|
||||
/// [continuousSyncInterval] controls how long a continuous sync session waits
|
||||
/// after completing one sync round before checking for local changes again.
|
||||
void initializeCrdtSync({
|
||||
required List<Table> syncTables,
|
||||
int syncBatchSize = CrdtSync.defaultSyncBatchSize,
|
||||
Duration continuousSyncInterval = CrdtSync.defaultContinuousSyncInterval,
|
||||
}) {
|
||||
CrdtSync.initialize(
|
||||
syncTables: syncTables,
|
||||
serializationManager: serializationManager,
|
||||
syncBatchSize: syncBatchSize,
|
||||
continuousSyncInterval: continuousSyncInterval,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:serverpod/serverpod.dart';
|
||||
import 'package:serverpod_offline_sync_client/serverpod_offline_sync_client.dart';
|
||||
|
||||
/// Endpoint for CRDT-based offline-first synchronization.
|
||||
class CrdtSyncEndpoint extends Endpoint {
|
||||
@override
|
||||
bool get requireLogin => true;
|
||||
|
||||
/// Opens a bidirectional CRDT sync session with the authenticated client.
|
||||
Stream<CrdtSyncStreamEvent> sync(
|
||||
Session session, {
|
||||
required Stream<CrdtSyncStreamEvent> changes,
|
||||
bool once = false,
|
||||
}) async* {
|
||||
yield* CrdtSync.instance.sync(
|
||||
session,
|
||||
userId: UuidValue.withValidation(session.authenticated!.userIdentifier),
|
||||
inbound: changes,
|
||||
once: once,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
// import 'package:serverpod/serverpod.dart';
|
||||
// import 'package:serverpod_offline_sync_shared/serverpod_offline_sync_shared.dart';
|
||||
|
||||
// // import '../generated/protocol.dart';
|
||||
|
||||
// /// TODO: Create the classes.
|
||||
// abstract class CrdtDataEntry {}
|
||||
|
||||
// /// Endpoint for the CRDT sync.
|
||||
// class SyncOfflineChangesEndpoint extends Endpoint {
|
||||
// @override
|
||||
// bool get requireLogin => true;
|
||||
|
||||
// /// Syncs the offline changes between server and client.
|
||||
// ///
|
||||
// /// This endpoint is used to sync the offline changes between server and
|
||||
// /// client. After a handshake on the [migrationVersion] to ensure that the
|
||||
// /// merge can happen, the client will send all [offlineChanges] since the
|
||||
// /// [lastSyncHlc] HLC and the server will send all online changes through
|
||||
// /// the returning stream.
|
||||
// ///
|
||||
// /// The connection will be kept open until one end disconnects, forwarding
|
||||
// /// the changes to the other end.
|
||||
// Stream<CrdtDataEntry> syncOfflineChanges(
|
||||
// Session session, {
|
||||
// required String migrationVersion,
|
||||
// required Hlc lastSyncHlc,
|
||||
|
||||
// /// TODO: Create the classes.
|
||||
// /// CrdtDataEntry will be a sealed class with the changes.
|
||||
// ///
|
||||
// /// - CrdtDataRow: New rows inserted.
|
||||
// /// - CrdtDataField: Fields updated.
|
||||
// /// - CrdtDataDeleted: Rows deleted.
|
||||
// required Stream<CrdtDataEntry> offlineChanges,
|
||||
// }) async* {
|
||||
// // TODO: Implement the business layer for the CRDT sync.
|
||||
// // yield* CrdtSync.syncOfflineChanges(session);
|
||||
// }
|
||||
// }
|
||||
8
packages/serverpod_offline_sync_server/lib/src/generated/analysis_options.yaml
generated
Normal file
8
packages/serverpod_offline_sync_server/lib/src/generated/analysis_options.yaml
generated
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
analyzer:
|
||||
errors:
|
||||
dead_code: ignore
|
||||
duplicate_ignore: ignore
|
||||
unnecessary_type_check: ignore
|
||||
|
||||
formatter:
|
||||
trailing_commas: preserve
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
### A set of remote CRDT changes to merge into the local database.
|
||||
class: CrdtMergeSet
|
||||
fields:
|
||||
### Remote row insertions.
|
||||
inserts: List<CrdtMergeInsert>
|
||||
|
||||
### Remote field updates.
|
||||
updates: List<CrdtMergeUpdate>
|
||||
|
||||
### Remote tombstone updates.
|
||||
deletes: List<CrdtMergeDelete>
|
||||
|
|
@ -11,9 +11,5 @@ fields:
|
|||
### Unique identifier for the node.
|
||||
uuidNodeId: UuidValue, default=random_v7, unique
|
||||
|
||||
# TODO: Make this non-nullable once the migration version is implemented.
|
||||
### The migration version of the CRDT data that the node has seen.
|
||||
lastSeenMigrationVersion: String?
|
||||
|
||||
### The last HLC timestamp when the node has synchronized.
|
||||
lastReceivedHlc: Hlc?
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
### Marks a graceful end of a one-shot sync session.
|
||||
###
|
||||
### Both peers send [CrdtSyncClose] after merging the final batch in a
|
||||
### one-shot session so each side can shut down without closing the transport
|
||||
### early.
|
||||
class: CrdtSyncClose
|
||||
extends: CrdtSyncStreamEvent
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
### Session handshake sent once by each peer before the first sync batch.
|
||||
###
|
||||
### [localNodeId] identifies the sending peer. [syncTablesHash] is the
|
||||
### deterministic schema hash both sides must agree on before merging data.
|
||||
class: CrdtSyncConnect
|
||||
extends: CrdtSyncStreamEvent
|
||||
fields:
|
||||
### This peer's node identifier.
|
||||
localNodeId: UuidValue
|
||||
|
||||
### The hash of the synchronized schema configured on this peer.
|
||||
syncTablesHash: String
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
### Marks the end of a framed sync batch.
|
||||
class: CrdtSyncEndOfBatch
|
||||
extends: CrdtSyncStreamEvent
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
### A chunk of merge changes carried inside a framed sync batch.
|
||||
class: CrdtSyncMergeChunk
|
||||
extends: CrdtSyncStreamEvent
|
||||
fields:
|
||||
### The merge changes to apply.
|
||||
changes: List<CrdtMergeChange>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
### One-time checkpoint sent by each peer after connect to exchange required
|
||||
### information for the sync session.
|
||||
class: CrdtSyncSinceHlc
|
||||
extends: CrdtSyncStreamEvent
|
||||
fields:
|
||||
### The last HLC timestamp that the sender has received from the other peer.
|
||||
lastSyncFromPeer: Hlc
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
### A CRDT sync event sent over a bidirectional stream.
|
||||
###
|
||||
### This is the base class of the CRDT sync protocol.
|
||||
class: CrdtSyncStreamEvent
|
||||
sealed: true
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
### Marks a timeout of an idle sync session.
|
||||
###
|
||||
### This is a synthetic event that will be injected in the inbound stream
|
||||
### being awaited if it does not emit any events for a given timeout. It is
|
||||
### what allows both sides to stay silent if they have nothing to send.
|
||||
class: CrdtSyncIdleTimeout
|
||||
extends: CrdtSyncStreamEvent
|
||||
|
|
@ -10,11 +10,12 @@ resolution: workspace
|
|||
|
||||
dependencies:
|
||||
meta: ^1.16.0
|
||||
serverpod: 3.5.0-beta.6
|
||||
serverpod: 3.5.0-beta.9
|
||||
serverpod_database: 3.5.0-beta.9
|
||||
serverpod_offline_sync_client: 0.0.1
|
||||
serverpod_offline_sync_shared: 0.0.1
|
||||
|
||||
dev_dependencies:
|
||||
lints: '>=3.0.0 <7.0.0'
|
||||
serverpod_test: 3.5.0-beta.6
|
||||
serverpod_test: 3.5.0-beta.9
|
||||
test: ^1.25.5
|
||||
|
|
|
|||
Loading…
Reference in a new issue