Skip to content

Commit 7fb2e7e

Browse files
committed
Refactor: replace dependency on sql_crdt with crdt
This version introduces a major refactor which results in multiple breaking changes in line with `crdt` 5.0.0. This package is now compatible with [crdt_sync](https://github.com/cachapa/crdt_sync), thereby abstracting the communication protocol and network management for real-time remote synchronization. Changes: - Removed direct interaction with the `Record` and `Hlc` classes - Added multiple table support
1 parent 714360d commit 7fb2e7e

File tree

11 files changed

+497
-379
lines changed

11 files changed

+497
-379
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
## 3.0.0
2+
This version introduces a major refactor which results in multiple breaking changes in line with `crdt` 5.0.0.
3+
4+
This package is now compatible with [crdt_sync](https://github.com/cachapa/crdt_sync), thereby abstracting the communication protocol and network management for real-time remote synchronization.
5+
6+
Changes:
7+
- Removed direct interaction with the `Record` and `Hlc` classes
8+
- Added multiple table support
9+
110
## 2.0.4
211
- Avoid repeated records when generating changesets using `since`
312

README.md

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,41 @@
1-
# hive_crdt
1+
Conflict-free Replicated Data Types (CRDTs) using by [Hive](https://pub.dev/packages/hive) stores for data persistence.
22

3-
A CRDT backed by a [Hive](https://pub.dev/packages/hive) store.
4-
5-
Depends on the [crdt](https://pub.dev/packages/crdt) package.
3+
This package implements [crdt](https://github.com/cachapa/crdt) and is compatible with [crdt_sync](https://github.com/cachapa/crdt_sync).
64

75
## Usage
86

9-
A simple usage example:
10-
117
```dart
12-
import 'package:hive_crdt/hive_crdt.dart';
8+
import 'package:hive/hive.dart';
9+
import 'package:hive_crdt/src/hive_adapters.dart';
10+
import 'package:hive_crdt/src/hive_crdt.dart';
11+
12+
Future<void> main() async {
13+
// Initialize Hive
14+
Hive
15+
..init('test_store')
16+
..registerAdapter(RecordAdapter(42));
17+
18+
var crdt1 = await HiveCrdt.open(prefix: 'crdt1', ['table']);
19+
var crdt2 = await HiveCrdt.open(prefix: 'crdt2', ['table']);
20+
21+
print('Inserting 2 records in crdt1…');
22+
await crdt1.put('table', 'a', 1);
23+
await crdt1.put('table', 'b', 1);
1324
14-
// TODO: Generate id on first launch
15-
const nodeId = 'random_id';
25+
print('crdt1: ${crdt1.getMap('table')}');
1626
17-
main() async {
18-
var crdt = await HiveCrdt.open('test', nodeId);
19-
crdt.put('x', 1);
20-
crdt.get('x'); // 1
27+
print('\nInserting a conflicting record in crdt2…');
28+
await crdt2.put('table', 'a', 2);
29+
30+
print('crdt2: ${crdt2.getMap('table')}');
31+
32+
print('\nMerging crdt2 into crdt1…');
33+
await crdt1.merge(crdt2.getChangeset());
34+
35+
print('crdt1: ${crdt1.getMap('table')}');
2136
}
2237
```
38+
39+
## Features and bugs
40+
41+
Please file feature requests and bugs at the [issue tracker](https://github.com/cachapa/hive_crdt/issues).

example/example.dart

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import 'package:hive/hive.dart';
2+
import 'package:hive_crdt/src/hive_adapters.dart';
3+
import 'package:hive_crdt/src/hive_crdt.dart';
4+
5+
Future<void> main() async {
6+
// Initialize Hive
7+
Hive
8+
..init('test_store')
9+
..registerAdapter(RecordAdapter(42));
10+
11+
var crdt1 = await HiveCrdt.open(prefix: 'crdt1', ['table']);
12+
var crdt2 = await HiveCrdt.open(prefix: 'crdt2', ['table']);
13+
14+
print('Inserting 2 records in crdt1…');
15+
await crdt1.put('table', 'a', 1);
16+
await crdt1.put('table', 'b', 1);
17+
18+
print('crdt1: ${crdt1.getMap('table')}');
19+
20+
print('\nInserting a conflicting record in crdt2…');
21+
await crdt2.put('table', 'a', 2);
22+
23+
print('crdt2: ${crdt2.getMap('table')}');
24+
25+
print('\nMerging crdt2 into crdt1…');
26+
await crdt1.merge(crdt2.getChangeset());
27+
28+
print('crdt1: ${crdt1.getMap('table')}');
29+
}

example/hive_crdt_example.dart

Lines changed: 0 additions & 10 deletions
This file was deleted.

lib/hive_adapters.dart

Lines changed: 0 additions & 53 deletions
This file was deleted.

lib/hive_crdt.dart

Lines changed: 3 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,4 @@
1-
import 'package:crdt/crdt.dart';
2-
import 'package:hive/hive.dart';
1+
library hive_crdt;
32

4-
export 'package:crdt/crdt.dart';
5-
6-
class HiveCrdt<K, V> extends Crdt<K, V> {
7-
@override
8-
final String nodeId;
9-
final Box<Record> _box;
10-
11-
HiveCrdt(Box<Record> box, this.nodeId) : _box = box;
12-
13-
static Future<HiveCrdt<K, V>> open<K, V>(String name, String nodeId,
14-
{String? path}) async {
15-
final box = await Hive.openBox<Record>(name, path: path);
16-
return HiveCrdt<K, V>(box, nodeId);
17-
}
18-
19-
@override
20-
bool containsKey(K key) => _box.containsKey(_encode(key));
21-
22-
@override
23-
Record<V>? getRecord(K key) => _box.get(_encode(key)) as Record<V>?;
24-
25-
@override
26-
void putRecord(K key, Record<V> value) => _box.put(_encode(key), value);
27-
28-
@override
29-
void putRecords(Map<K, Record<V>> recordMap) => _box.putAll(recordMap);
30-
31-
@override
32-
Map<K, Record<V>> recordMap({Hlc? modifiedSince}) => (_box.toMap()
33-
..removeWhere((_, record) =>
34-
record.modified.logicalTime <= (modifiedSince?.logicalTime ?? 0)))
35-
.map((key, value) => MapEntry(_decode(key), value as Record<V>));
36-
37-
@override
38-
Stream<MapEntry<K, V?>> watch({K? key}) => _box
39-
.watch(key: key)
40-
.map((event) => MapEntry<K, V?>(event.key, event.value?.value));
41-
42-
@override
43-
void purge() => _box.clear();
44-
45-
Future<void> close() => _box.close();
46-
47-
/// Permanently deletes the store from disk. Useful for testing.
48-
Future<void> deleteStore() => _box.deleteFromDisk();
49-
50-
dynamic _encode(K key) =>
51-
key is DateTime ? key.toUtc().toIso8601String() : key;
52-
53-
K _decode(dynamic key) => K == DateTime ? DateTime.parse(key) : key;
54-
}
3+
export 'src/hive_adapters.dart';
4+
export 'src/hive_crdt.dart';

lib/src/hive_adapters.dart

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import 'package:crdt/crdt.dart';
2+
import 'package:crdt/map_crdt.dart';
3+
import 'package:hive/hive.dart';
4+
5+
class RecordAdapter extends TypeAdapter<Record> {
6+
@override
7+
final int typeId;
8+
9+
RecordAdapter(this.typeId);
10+
11+
@override
12+
Record read(BinaryReader reader) {
13+
final value = reader.read();
14+
final isDeleted = reader.readBool();
15+
final hlc = reader.readString().toHlc;
16+
final modified = reader.readString().toHlc;
17+
return Record(value, isDeleted, hlc, modified);
18+
}
19+
20+
@override
21+
void write(BinaryWriter writer, Record obj) {
22+
writer.write(obj.value);
23+
writer.writeBool(obj.isDeleted);
24+
writer.writeString(obj.hlc.toString());
25+
writer.writeString(obj.modified.toString());
26+
}
27+
}

lib/src/hive_crdt.dart

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import 'dart:async';
2+
3+
import 'package:crdt/map_crdt.dart';
4+
import 'package:hive/hive.dart';
5+
6+
export 'package:crdt/crdt.dart';
7+
8+
class HiveCrdt extends MapCrdtBase {
9+
final Map<String, Box<Record>> _boxes;
10+
11+
@override
12+
bool get isEmpty => _boxes.values.fold(true, (p, e) => p && e.isEmpty);
13+
14+
@override
15+
bool get isNotEmpty => !isEmpty;
16+
17+
HiveCrdt._(super.tables, this._boxes);
18+
19+
/// Create or open tables and store them in [path].
20+
/// Table names are used as file names.
21+
/// Use [prefix] to prepend the filename, otherwise Hive will prevent opening
22+
/// multiple boxes with the same tables. Useful for testing.
23+
static Future<HiveCrdt> open(Iterable<String> tables,
24+
{String? path, String? prefix}) async {
25+
assert(tables.isNotEmpty);
26+
assert(tables.length == tables.toSet().length);
27+
28+
final boxes = {
29+
for (final table in tables)
30+
table: await Hive.openBox<Record>('${prefix ?? ''}_$table', path: path)
31+
};
32+
33+
final crdt = HiveCrdt._(tables, boxes);
34+
return crdt;
35+
}
36+
37+
/// Closes all tables
38+
Future<void> close() => Future.wait(_boxes.values.map((e) => e.close()));
39+
40+
/// Deletes all data from every table
41+
Future<void> purge() => Future.wait(_boxes.values.map((e) => e.clear()));
42+
43+
/// Deletes all tables from disk
44+
Future<void> delete() =>
45+
Future.wait(_boxes.values.map((e) => e.deleteFromDisk()));
46+
47+
@override
48+
Record? getRecord(String table, String key) => _boxes[table]!.get(key);
49+
50+
@override
51+
Map<String, Record> getRecords(String table) =>
52+
_boxes[table]!.toMap().cast<String, Record>();
53+
54+
@override
55+
Future<void> putRecords(Map<String, Map<String, Record>> dataset) async {
56+
for (final entry in dataset.entries) {
57+
await _boxes[entry.key]!.putAll(entry.value);
58+
}
59+
}
60+
61+
Stream<(String key, dynamic value)> watch(String table, {String? key}) {
62+
if (!tables.contains(table)) throw 'Unknown table: $table';
63+
return _boxes[table]!
64+
.watch(key: key)
65+
.map((event) => (event.key, event.value?.value));
66+
}
67+
}

pubspec.yaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: hive_crdt
22
description: Dart implementation of Conflict-free Replicated Data Types (CRDTs), backed by a Hive store.
3-
version: 2.0.4
3+
version: 3.0.0
44
homepage: https://github.com/cachapa/hive_crdt
55
repository: https://github.com/cachapa/hive_crdt
66
issue_tracker: https://github.com/cachapa/hive_crdt/issues
@@ -9,9 +9,11 @@ environment:
99
sdk: '>=3.0.0 <4.0.0'
1010

1111
dependencies:
12-
crdt: ^4.0.3
12+
crdt: ^5.0.0
13+
# path: ../crdt
1314
hive: ^2.2.3
1415

1516
dev_dependencies:
1617
lints: any
1718
test: any
19+
uuid: any

0 commit comments

Comments
 (0)