Skip to content

Commit a268458

Browse files
committed
chore: bump to 0.1.1 with coverage and performance updates
1 parent e51cbe0 commit a268458

15 files changed

Lines changed: 1060 additions & 34 deletions

packages/flutter_taerae/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 0.1.1
2+
3+
- Optimize `TaeraeGraphController` by caching sorted node/edge snapshots.
4+
- Expand controller behavior coverage, including mutation/no-op notification paths.
5+
- Raise `lib/` statement coverage to 100%.
6+
17
## 0.1.0
28

39
- Promote plugin template into a usable Flutter package for `taerae_core`.

packages/flutter_taerae/example/pubspec.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ packages:
8989
path: ".."
9090
relative: true
9191
source: path
92-
version: "0.1.0"
92+
version: "0.1.1"
9393
flutter_test:
9494
dependency: "direct dev"
9595
description: flutter
@@ -249,7 +249,7 @@ packages:
249249
path: "../../taerae_core"
250250
relative: true
251251
source: path
252-
version: "0.1.0"
252+
version: "0.1.1"
253253
term_glyph:
254254
dependency: transitive
255255
description:

packages/flutter_taerae/lib/src/taerae_graph_controller.dart

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,17 @@ class TaeraeGraphController extends ChangeNotifier {
1717
}
1818

1919
TaeraeGraph _graph;
20+
List<TaeraeNode>? _cachedNodes;
21+
List<TaeraeEdge>? _cachedEdges;
2022

2123
/// Immutable copy of the underlying graph.
2224
TaeraeGraph get graph => _graph.copy();
2325

2426
/// Current nodes sorted by id.
25-
List<TaeraeNode> get nodes => _readNodes(_graph.toJson());
27+
List<TaeraeNode> get nodes => _cachedNodes ??= _readNodes(_graph.toJson());
2628

2729
/// Current edges sorted by id.
28-
List<TaeraeEdge> get edges => _readEdges(_graph.toJson());
30+
List<TaeraeEdge> get edges => _cachedEdges ??= _readEdges(_graph.toJson());
2931

3032
/// Whether a node with [id] exists.
3133
bool containsNode(String id) => _graph.containsNode(id);
@@ -86,6 +88,7 @@ class TaeraeGraphController extends ChangeNotifier {
8688
labels: labels,
8789
properties: properties,
8890
);
91+
_invalidateDerivedCaches();
8992
notifyListeners();
9093
return node;
9194
}
@@ -94,6 +97,7 @@ class TaeraeGraphController extends ChangeNotifier {
9497
bool removeNode(String id) {
9598
final bool removed = _graph.removeNode(id);
9699
if (removed) {
100+
_invalidateDerivedCaches();
97101
notifyListeners();
98102
}
99103
return removed;
@@ -114,6 +118,7 @@ class TaeraeGraphController extends ChangeNotifier {
114118
type: type,
115119
properties: properties,
116120
);
121+
_invalidateDerivedCaches();
117122
notifyListeners();
118123
return edge;
119124
}
@@ -122,6 +127,7 @@ class TaeraeGraphController extends ChangeNotifier {
122127
bool removeEdge(String id) {
123128
final bool removed = _graph.removeEdge(id);
124129
if (removed) {
130+
_invalidateDerivedCaches();
125131
notifyListeners();
126132
}
127133
return removed;
@@ -140,12 +146,14 @@ class TaeraeGraphController extends ChangeNotifier {
140146
}
141147

142148
_graph.clear();
149+
_invalidateDerivedCaches();
143150
notifyListeners();
144151
}
145152

146153
/// Replaces the current graph and notifies listeners.
147154
void replaceGraph(TaeraeGraph graph) {
148155
_graph = graph.copy();
156+
_invalidateDerivedCaches();
149157
notifyListeners();
150158
}
151159

@@ -164,6 +172,7 @@ class TaeraeGraphController extends ChangeNotifier {
164172
/// Imports graph state from JSON and notifies listeners.
165173
void importFromJson(Map<String, Object?> json) {
166174
_graph = TaeraeGraph.fromJson(json);
175+
_invalidateDerivedCaches();
167176
notifyListeners();
168177
}
169178

@@ -173,6 +182,11 @@ class TaeraeGraphController extends ChangeNotifier {
173182
importFromJson(_readJsonMap(decoded, 'source'));
174183
}
175184

185+
void _invalidateDerivedCaches() {
186+
_cachedNodes = null;
187+
_cachedEdges = null;
188+
}
189+
176190
static List<TaeraeNode> _readNodes(Map<String, Object?> json) {
177191
final Object? rawNodes = json['nodes'];
178192
if (rawNodes is! List<Object?>) {
@@ -206,11 +220,7 @@ class TaeraeGraphController extends ChangeNotifier {
206220

207221
final Map<String, Object?> map = <String, Object?>{};
208222
for (final MapEntry<Object?, Object?> entry in value.entries) {
209-
final Object? rawKey = entry.key;
210-
if (rawKey is! String) {
211-
throw FormatException('Expected all keys in "$key" to be strings.');
212-
}
213-
map[rawKey] = entry.value;
223+
map[entry.key as String] = entry.value;
214224
}
215225
return map;
216226
}

packages/flutter_taerae/pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: flutter_taerae
22
description: "Local-first embedded graph package for Flutter with ChangeNotifier state APIs, durability controls, and GraphRAG-ready extensions."
3-
version: 0.1.0
3+
version: 0.1.1
44
homepage: https://github.com/taerae-dev/taerae
55
repository: https://github.com/taerae-dev/taerae
66
issue_tracker: https://github.com/taerae-dev/taerae/issues
@@ -20,7 +20,7 @@ dependencies:
2020
sdk: flutter
2121
flutter_web_plugins:
2222
sdk: flutter
23-
taerae_core: ^0.1.0
23+
taerae_core: ^0.1.1
2424
web: ^1.0.0
2525
plugin_platform_interface: ^2.0.2
2626

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import 'package:flutter_taerae/flutter_taerae.dart';
2+
import 'package:flutter_taerae/flutter_taerae_platform_interface.dart';
3+
import 'package:flutter_test/flutter_test.dart';
4+
5+
class _ThrowingBasePlatform extends TaeraeFlutterPlatform {}
6+
7+
void main() {
8+
group('TaeraeFlutterPlatform', () {
9+
test('base implementation throws for unimplemented API', () {
10+
final _ThrowingBasePlatform platform = _ThrowingBasePlatform();
11+
expect(platform.getPlatformVersion, throwsUnimplementedError);
12+
});
13+
});
14+
15+
group('TaeraeGraphController', () {
16+
test('creates from json and returns a defensive graph copy', () {
17+
final TaeraeGraphController controller = TaeraeGraphController.fromJson(
18+
<String, Object?>{
19+
'nodes': <Object?>[
20+
<String, Object?>{
21+
'id': 'n1',
22+
'labels': <Object?>['User'],
23+
},
24+
<String, Object?>{'id': 'n2'},
25+
],
26+
'edges': <Object?>[
27+
<String, Object?>{'id': 'e1', 'from': 'n1', 'to': 'n2'},
28+
],
29+
},
30+
);
31+
32+
final TaeraeGraph detached = controller.graph..removeNode('n1');
33+
expect(detached.containsNode('n1'), isFalse);
34+
expect(controller.containsNode('n1'), isTrue);
35+
expect(controller.edgeById('e1')?.id, equals('e1'));
36+
expect(controller.nodeById('n1')?.id, equals('n1'));
37+
});
38+
39+
test('nodes and edges getters are sorted and cache until invalidated', () {
40+
final TaeraeGraphController controller = TaeraeGraphController()
41+
..upsertNode('b')
42+
..upsertNode('a')
43+
..upsertEdge('e2', 'a', 'b')
44+
..upsertEdge('e1', 'b', 'a');
45+
46+
final List<TaeraeNode> firstNodes = controller.nodes;
47+
final List<TaeraeNode> secondNodes = controller.nodes;
48+
final List<TaeraeEdge> firstEdges = controller.edges;
49+
final List<TaeraeEdge> secondEdges = controller.edges;
50+
51+
expect(
52+
firstNodes.map((TaeraeNode node) => node.id),
53+
equals(<String>['a', 'b']),
54+
);
55+
expect(
56+
firstEdges.map((TaeraeEdge edge) => edge.id),
57+
equals(<String>['e1', 'e2']),
58+
);
59+
expect(identical(firstNodes, secondNodes), isTrue);
60+
expect(identical(firstEdges, secondEdges), isTrue);
61+
62+
controller.upsertNode('c');
63+
final List<TaeraeNode> thirdNodes = controller.nodes;
64+
expect(identical(firstNodes, thirdNodes), isFalse);
65+
expect(
66+
thirdNodes.map((TaeraeNode node) => node.id),
67+
equals(<String>['a', 'b', 'c']),
68+
);
69+
});
70+
71+
test('query helpers delegate to graph operations', () {
72+
final TaeraeGraphController controller = TaeraeGraphController()
73+
..upsertNode('n1', labels: const <String>['User'])
74+
..upsertNode(
75+
'n2',
76+
labels: const <String>['User'],
77+
properties: const <String, Object?>{'team': 'A'},
78+
)
79+
..upsertNode('n3')
80+
..upsertEdge('e1', 'n1', 'n2', type: 'friend')
81+
..upsertEdge('e2', 'n3', 'n1', type: 'friend');
82+
83+
expect(controller.nodesByLabel('User').length, equals(2));
84+
expect(
85+
controller.nodesWhereProperty('team', 'A').single.id,
86+
equals('n2'),
87+
);
88+
expect(controller.outgoing('n1').single.id, equals('e1'));
89+
expect(controller.incoming('n1').single.id, equals('e2'));
90+
expect(
91+
controller.neighbors('n1').map((TaeraeNode node) => node.id),
92+
unorderedEquals(<String>['n2', 'n3']),
93+
);
94+
expect(
95+
controller.shortestPathBfs('n3', 'n2'),
96+
equals(const <String>['n3', 'n1', 'n2']),
97+
);
98+
});
99+
100+
test('notifies only on effective mutations and clear behavior', () {
101+
final TaeraeGraphController controller = TaeraeGraphController();
102+
int notifyCount = 0;
103+
controller.addListener(() {
104+
notifyCount += 1;
105+
});
106+
107+
expect(controller.removeNode('missing'), isFalse);
108+
expect(controller.removeEdge('missing'), isFalse);
109+
controller.clear(); // Empty no-op.
110+
expect(notifyCount, equals(0));
111+
112+
controller
113+
..upsertNode('n1')
114+
..upsertNode('n2')
115+
..upsertEdge('e1', 'n1', 'n2');
116+
expect(notifyCount, equals(3));
117+
118+
expect(controller.removeEdge('e1'), isTrue);
119+
expect(notifyCount, equals(4));
120+
expect(controller.removeNode('n1'), isTrue);
121+
expect(notifyCount, equals(5));
122+
123+
controller.clear();
124+
expect(notifyCount, equals(6));
125+
controller.clear(); // Already empty.
126+
expect(notifyCount, equals(6));
127+
});
128+
129+
test(
130+
'replace/import/export helpers work with pretty json and validation',
131+
() {
132+
final TaeraeGraph replacement = TaeraeGraph()
133+
..upsertNode('r1')
134+
..upsertNode('r2')
135+
..upsertEdge('re1', 'r1', 'r2');
136+
137+
final TaeraeGraphController controller = TaeraeGraphController();
138+
int notifyCount = 0;
139+
controller.addListener(() {
140+
notifyCount += 1;
141+
});
142+
143+
controller.replaceGraph(replacement);
144+
expect(controller.containsEdge('re1'), isTrue);
145+
expect(notifyCount, equals(1));
146+
147+
final Map<String, Object?> exported = controller.exportToJson();
148+
expect(exported['nodes'], isA<List<Object?>>());
149+
final String pretty = controller.exportToJsonString(pretty: true);
150+
expect(pretty.contains('\n'), isTrue);
151+
152+
controller.importFromJson(<String, Object?>{
153+
'nodes': <Object?>[
154+
<String, Object?>{'id': 'i1'},
155+
],
156+
'edges': <Object?>[],
157+
});
158+
expect(controller.containsNode('i1'), isTrue);
159+
expect(notifyCount, equals(2));
160+
161+
controller.importFromJsonString('{"nodes":[{"id":"s1"}],"edges":[]}');
162+
expect(controller.containsNode('s1'), isTrue);
163+
expect(notifyCount, equals(3));
164+
165+
expect(
166+
() => controller.importFromJsonString('[]'),
167+
throwsFormatException,
168+
);
169+
},
170+
);
171+
});
172+
}

packages/taerae_core/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 0.1.1
2+
3+
- Optimize `TaeraeGraphLog` replay/read paths to stream NDJSON operations.
4+
- Add broad branch and validation tests for graph core, persistence, and GraphRAG.
5+
- Raise `lib/` statement coverage to 100% with additional regression coverage.
6+
17
## 0.1.0
28

39
- Introduced immutable graph models: `TaeraeNode` and `TaeraeEdge`.

0 commit comments

Comments
 (0)