Skip to content

Commit a7dbb6b

Browse files
committed
feat: add list transaction operations
1 parent 8f737bc commit a7dbb6b

27 files changed

+691
-190
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ pubspec.lock
1010
.fvm/
1111
custom_lint.log
1212
grumpy_lints.log
13+
build/

lib/src/module/infra/services/canonical_module_registry_service.dart

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class CanonicalModuleRegistryService<T, Config extends Object>
5757
_modulesByType[module.runtimeType] = module;
5858
} else if (!identical(existing, module)) {
5959
log(
60-
'Canonicalizing duplicate ${module.runtimeType}.'
60+
'Canonicalizing duplicate ${module.logTag}.'
6161
' Using existing instance: $existing',
6262
);
6363
}
@@ -184,7 +184,7 @@ class CanonicalModuleRegistryService<T, Config extends Object>
184184
final toDeactivate = _activeModules.difference(required);
185185

186186
log(
187-
'Syncing modules: toActivate=$toActivate, toDeactivate=$toDeactivate, required=$required',
187+
'Reconciling modules: toActivate=$toActivate, toDeactivate=$toDeactivate, required=$required',
188188
);
189189

190190
final order = _topological(required);
@@ -196,20 +196,19 @@ class CanonicalModuleRegistryService<T, Config extends Object>
196196
// This module was mounted through legacy module import mounting.
197197
// Adopt it so we don't try to create a duplicate DI scope.
198198
_mountedModules.add(module);
199-
log('Adopted externally mounted module: ${module.runtimeType}');
199+
log('Adopted externally mounted module: $module');
200200
} else {
201-
log('Initializing ${module.logTag}');
201+
log('Initializing $module');
202202

203203
await module.initialize();
204-
log('Cock');
205204
_mountedModules.add(module);
206-
log('Mounted module: ${module.runtimeType}');
205+
log('Mounted module: $module');
207206
}
208207
}
209-
208+
log('Activating $module');
210209
await module.activate();
211210
_activeModules.add(module);
212-
log('Activated module: ${module.runtimeType}');
211+
log('Activated module: $module');
213212
}
214213

215214
final deactivateOrder = _topological(_activeModules).reversed;
@@ -218,7 +217,7 @@ class CanonicalModuleRegistryService<T, Config extends Object>
218217

219218
await module.deactivate();
220219
_activeModules.remove(module);
221-
log('Deactivated module: ${module.runtimeType}');
220+
log('Deactivated module: $module');
222221
}
223222

224223
log('Sync complete.');

lib/src/module/module.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,6 @@ abstract class Module<RouteType, Config extends Object>
182182

183183
log('Initializing...');
184184

185-
return;
186-
187185
_isInitializing = true;
188186

189187
try {
@@ -248,6 +246,9 @@ abstract class Module<RouteType, Config extends Object>
248246

249247
/// The routes provided by this module.
250248
List<Route<RouteType, Config>> get routes;
249+
250+
@override
251+
String toString() => '$logTag<$RouteType,$Config>';
251252
}
252253

253254
/// A function that binds a [Builder] for a specific [Base] type with a given [Config].

lib/src/routing/domain/models/leaf_route.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,7 @@ class LeafRoute<T, Config extends Object> extends Route<T, Config> {
2828
String toString() {
2929
return 'ViewRoute(path: $path, view: $view, middleware: $middleware, children: $children)';
3030
}
31+
32+
/// `true` if this route is the root route.
33+
bool get isRoot => path == '/';
3134
}

lib/src/routing/domain/services/routing_service.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ abstract class RoutingService<T, Config extends Object> extends Service {
7575
/// A stream of all view change events.
7676
Stream<ViewChangedEvent<T, Config>> get viewStream;
7777

78+
/// Waits until the current route is ready and the view has been rendered.
79+
@visibleForTesting
80+
Future<void> get currentNavigation;
81+
7882
@override
7983
bool get singelton => true;
8084
}

lib/src/routing/infra/services/routing_kit_routing_service.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ class RoutingKitRoutingService<T, Config extends Object>
1818
/// Centralized module lifecycle manager.
1919
final ModuleRegistryService<T, Config> moduleRegistry;
2020

21+
Future<void> _currentNavigation = Future.value();
22+
2123
RouteContext? _context;
2224

2325
final Map<Uri, (Future<bool>, LeafRoute<T, Config>)> _pendingNavigations = {};
@@ -262,6 +264,7 @@ class RoutingKitRoutingService<T, Config extends Object>
262264
if (leaf is ModuleRoute<T, Config>) {
263265
leaf =
264266
leaf.root ??
267+
leaf.module.routes.root ??
265268
(throw ArgumentError.value(
266269
path,
267270
'path',
@@ -283,6 +286,7 @@ class RoutingKitRoutingService<T, Config extends Object>
283286
final future = _navigate(uri, leaf, skipPreview, handler);
284287

285288
_pendingNavigations[uri] = (future, leaf);
289+
_currentNavigation = future;
286290
await future;
287291
} catch (e, s) {
288292
log('Navigation to $path failed with error', e, s);
@@ -356,4 +360,7 @@ class RoutingKitRoutingService<T, Config extends Object>
356360
@override
357361
Stream<ViewChangedEvent<T, Config>> get viewStream =>
358362
_viewChangeController.stream.asBroadcastStream();
363+
364+
@override
365+
Future<void> get currentNavigation => _currentNavigation;
359366
}

lib/src/routing/routing.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export 'domain/domain.dart';
2+
export 'utils/utils.dart';
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import 'package:grumpy/grumpy.dart';
2+
3+
/// Helper extension for handling lists of routes.
4+
extension RouteX<T, Config extends Object> on List<Route<T, Config>> {
5+
/// Returns the root leaf route of this list or null if none is found.
6+
LeafRoute<T, Config>? get root {
7+
for (final route in this) {
8+
if (route is LeafRoute<T, Config> && route.isRoot) {
9+
return route;
10+
}
11+
}
12+
return null;
13+
}
14+
}

lib/src/routing/utils/utils.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export 'route_utils.dart';
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import 'package:grumpy/grumpy.dart';
2+
3+
/// {@template list_add_element_tx_operation}
4+
/// A [TxOperation] for adding an element to a list.
5+
/// {@endtemplate}
6+
class ListAddElementTxOperation<Element, TResult>
7+
extends TxOperation<List<Element>, TResult> {
8+
/// {@macro list_add_element_tx_operation}
9+
ListAddElementTxOperation({
10+
required super.name,
11+
required super.id,
12+
required super.baseVersion,
13+
required this.element,
14+
required this.createElement,
15+
required this.apply,
16+
this.shouldRollbackOnError = TxOperation.alwaysRollback,
17+
}) : super(touchedKeys: const <String>{});
18+
19+
@override
20+
Set<String> get touchedKeys => {createElement.toString()};
21+
22+
/// The optimistic element to be added.
23+
final Element element;
24+
25+
/// Creates the element on the remote and returns the commit result.
26+
final Future<TResult> Function() createElement;
27+
28+
/// Applies commit result to the optimistic element and returns the confirmed element.
29+
final Element Function(Element optimisticElement, TResult createResult) apply;
30+
31+
/// {@macro shouldRollbackOnError}
32+
final bool Function(Object error, StackTrace? stackTrace)
33+
shouldRollbackOnError;
34+
35+
@override
36+
List<Element>? applyConfirmed(List<Element> confirmed, TResult result) {
37+
final appliedElement = apply(element, result);
38+
return [...confirmed, appliedElement];
39+
}
40+
41+
@override
42+
Future<TResult> commit(_) => createElement();
43+
44+
@override
45+
List<Element> optimisticApply(List<Element> current) {
46+
return [...current, element];
47+
}
48+
49+
@override
50+
bool shouldRollback(Object error, StackTrace? stackTrace) =>
51+
shouldRollbackOnError(error, stackTrace);
52+
}

0 commit comments

Comments
 (0)