crdt_grpc_sync 1.0.1
crdt_grpc_sync: ^1.0.1 copied to clipboard
A gRPC network sync layer for crdt, mirrors & inspired by crdt_sync, to get full P2P, local-first sync in any Dart app.
import 'dart:convert';
import 'dart:io';
import 'package:crdt/map_crdt.dart';
import 'package:crdt_grpc_sync/crdt_grpc_sync.dart';
import 'package:uuid/uuid.dart';
/// Hub-and-spoke chat: one server relays messages to all clients.
///
/// Run without args to start the hub server:
/// dart run example/example.dart
///
/// Run with a gRPC URL to connect as a spoke:
/// dart run example/example.dart grpc://localhost:8080
Future<void> main(List<String> args) async {
final crdt = MapCrdt(['chat']);
print('Welcome to CRDT Chat (hub topology).');
stdout.write('Your name: ');
final author = stdin.readLineSync()?.trim() ?? 'anonymous';
print('Hi $author. Type anything to send a message.');
final verbose = args.contains('--verbose');
final realUri = args.where((a) => a != '--verbose').toList();
String? remoteAuthor;
if (realUri.isEmpty) {
// Run as server
await listen(
crdt,
8080,
config: CrdtSyncServiceConfig(
handshakeDataBuilder: (peerId, peerData) => {'name': author},
onClientConnected: (crdtSync, peerData) {
final data = peerData as Map?;
remoteAuthor = data?['name'] as String? ?? 'unknown';
print('Client joined: $remoteAuthor');
},
onClientDisconnected: (peerId, code, reason) {
final name = remoteAuthor;
print('Client left: ${name ?? "unknown"}');
},
syncConfig: CrdtSyncConfig(verbose: verbose),
),
);
print('Server listening on port 8080');
if (verbose) print('Verbose mode enabled');
} else {
// Run as client
final uri = Uri.tryParse(realUri.first);
if (uri == null || (uri.scheme != 'grpc' && uri.scheme != 'grpcs')) {
print('Invalid URI: ${realUri.first}. Use grpc://host:port');
exit(1);
}
CrdtSyncClient(
crdt,
uri,
syncConfig: CrdtSyncConfig(verbose: verbose),
handshakeDataBuilder: () => {'name': author},
onConnecting: () => print('Connecting...'),
onConnected: (nodeId, info) {
final data = info as Map?;
remoteAuthor = data?['name'] as String? ?? 'unknown';
print('Connected to $remoteAuthor');
},
onDisconnected: (nodeId, code, reason) {
final name = remoteAuthor;
print('Disconnected: $name ($code $reason)');
},
).connect();
}
// Listen for incoming chat messages
crdt.onTablesChanged.listen((e) {
final records = crdt.getChangeset(modifiedOn: e.hlc)['chat'] ?? [];
for (final record in records) {
final message = record['value'] as Map<String, dynamic>?;
if (message != null) {
print('[${message['author']}] ${message['line']}');
}
}
});
// Read stdin and broadcast as CRDT records
stdin.transform(utf8.decoder).transform(const LineSplitter()).listen((line) {
crdt.put('chat', const Uuid().v4(), {'author': author, 'line': line});
});
}