Syncbase:  Client cleanup

* Split into multiple classes.

* Classes inherit from NamedResource (similar, but cleaner, to what we
  did in JS).

* Make tableScan and dbExec return Dart streams (idiomatic).l

* Replace complex type declarations on internal variables with just
  "var".  Dart style recommends only using the full type declaration for
  public API (which we still do).  In most cases, dartanalyzer can infer
  the type anyways, so we don't lose any safety as long as we run
  dartanalyzer.

* Wrap lines at 80 chars (Dart convention) now that method names are
  shorter.

Change-Id: Id0738ee83dfa1b1687efa1a6ec04dbbfb030a184
diff --git a/Makefile b/Makefile
index 6b644ab..8922652 100644
--- a/Makefile
+++ b/Makefile
@@ -160,7 +160,7 @@
 # Formats dart files to follow dart style conventions.
 .PHONY: dartfmt
 dartfmt:
-	dartfmt --overwrite --line-length 120 $(DART_FILES)
+	dartfmt --overwrite $(DART_FILES)
 
 # Lints src and test files with dartanalyzer. This takes a few seconds.
 .PHONY: dartanalyzer
diff --git a/dart/bin/syncbase.dart b/dart/bin/syncbase.dart
index a4ff32d..01c3f09 100644
--- a/dart/bin/syncbase.dart
+++ b/dart/bin/syncbase.dart
@@ -12,9 +12,11 @@
 
   // TODO(nlacasse): Switch to serving these files over http rather than
   // directly from the filesystem, so they can be accessed by Android.
-  String url = 'file://' + args[1].replaceFirst('dart/bin/syncbase.dart', 'gen/mojo/syncbase_server.mojo');
+  String url = 'file://' +
+      args[1].replaceFirst(
+          'dart/bin/syncbase.dart', 'gen/mojo/syncbase_server.mojo');
 
   SyncbaseClient c = new SyncbaseClient(app, url);
-  bool exists = await c.appExists('foo');
-  print('appExists(foo): $exists');
+  bool exists = await c.app('foo').exists();
+  print('app(foo).exists(): $exists');
 }
diff --git a/dart/lib/initialized_application.dart b/dart/lib/initialized_application.dart
index d95f8dc..4447e6f 100644
--- a/dart/lib/initialized_application.dart
+++ b/dart/lib/initialized_application.dart
@@ -11,7 +11,8 @@
   final _initializeCompleter = new Completer();
   Future get initialized => _initializeCompleter.future;
 
-  InitializedApplication.fromHandle(int handle) : super.fromHandle(new MojoHandle(handle));
+  InitializedApplication.fromHandle(int handle)
+      : super.fromHandle(new MojoHandle(handle));
 
   void initialize(List<String> args, String url) {
     _initializeCompleter.complete();
diff --git a/dart/lib/src/app.dart b/dart/lib/src/app.dart
new file mode 100644
index 0000000..53d254e
--- /dev/null
+++ b/dart/lib/src/app.dart
@@ -0,0 +1,45 @@
+part of syncbase_client;
+
+class SyncbaseApp extends NamedResource {
+  SyncbaseApp._internal(_proxy, fullName)
+      : super._internal(_proxy, '', fullName);
+
+  // noSqlDatabase returns the noSqlDatabase with the given relativeName.
+  // relativeName must not contain slashes.
+  SyncbaseNoSqlDatabase noSqlDatabase(String relativeName) {
+    return new SyncbaseNoSqlDatabase._internal(_proxy, fullName, relativeName);
+  }
+
+  Future create(mojom.Perms perms) async {
+    var v = await _proxy.ptr.appCreate(fullName, perms);
+    if (isError(v.err)) throw v.err;
+    return;
+  }
+
+  Future delete() async {
+    var v = await _proxy.ptr.appDelete(fullName);
+    if (isError(v.err)) throw v.err;
+    return;
+  }
+
+  Future<bool> exists() async {
+    var v = await _proxy.ptr.appExists(fullName);
+    if (isError(v.err)) throw v.err;
+    return v.exists;
+  }
+
+  Future<mojom.Perms> getPermissions() async {
+    var v = await _proxy.ptr.appGetPermissions(fullName);
+    if (isError(v.err)) throw v.err;
+
+    // TODO(nlacasse): We need to return the version too.  Create a struct type
+    // that combines perms and version?
+    return v.perms;
+  }
+
+  Future setPermissions(mojom.Perms perms, String version) async {
+    var v = await _proxy.ptr.appSetPermissions(fullName, perms, version);
+    if (isError(v.err)) throw v.err;
+    return;
+  }
+}
diff --git a/dart/lib/src/named_resource.dart b/dart/lib/src/named_resource.dart
new file mode 100644
index 0000000..85f750e
--- /dev/null
+++ b/dart/lib/src/named_resource.dart
@@ -0,0 +1,19 @@
+part of syncbase_client;
+
+// NamedResource is the superclass of resources with names.
+class NamedResource {
+  final String _parentFullName;
+  final String fullName;
+  final String relativeName;
+  final mojom.SyncbaseProxy _proxy;
+
+  NamedResource._internal(_proxy, _parentFullName, relativeName)
+      : this._proxy = _proxy,
+        this._parentFullName = _parentFullName,
+        this.relativeName = relativeName,
+        this.fullName = _parentFullName + '/' + relativeName {
+    if (relativeName.contains('/')) {
+      throw 'relativeName cannot contain "/": $relativeName';
+    }
+  }
+}
diff --git a/dart/lib/src/nosql/database.dart b/dart/lib/src/nosql/database.dart
new file mode 100644
index 0000000..adf2bc8
--- /dev/null
+++ b/dart/lib/src/nosql/database.dart
@@ -0,0 +1,110 @@
+part of syncbase_client;
+
+class SyncbaseNoSqlDatabase extends NamedResource {
+  SyncbaseNoSqlDatabase._internal(_proxy, _parentFullName, relativeName)
+      : super._internal(_proxy, _parentFullName, relativeName);
+
+  // table returns a table with the given relativeName.  relativeName must not
+  // contain slashes.
+  SyncbaseTable table(String relativeName) {
+    return new SyncbaseTable._internal(_proxy, fullName, relativeName);
+  }
+
+  // syncGroup returns a syncGroup with the given name.
+  SyncbaseSyncGroup syncGroup(String name) {
+    return new SyncbaseSyncGroup._internal(_proxy, this.fullName, name);
+  }
+
+  Future<List<String>> getSyncGroupNames() async {
+    var v = await _proxy.ptr.dbGetSyncGroupNames(fullName);
+    if (isError(v.err)) throw v.err;
+    return v.names;
+  }
+
+  Future create(mojom.Perms perms) async {
+    var v = await _proxy.ptr.dbCreate(fullName, perms);
+    if (isError(v.err)) throw v.err;
+    return;
+  }
+
+  Future delete() async {
+    var v = await _proxy.ptr.dbDelete(fullName);
+    if (isError(v.err)) throw v.err;
+    return;
+  }
+
+  Future<bool> exists() async {
+    var v = await _proxy.ptr.dbExists(fullName);
+    if (isError(v.err)) throw v.err;
+    return v.exists;
+  }
+
+  Stream<mojom.Result> exec(String query) {
+    StreamController<mojom.Result> sc = new StreamController();
+    mojom.ExecStream execStream = new ExecStreamImpl._fromStreamController(sc);
+
+    // Call dbExec asynchronously.
+    _proxy.ptr.dbExec(fullName, query, execStream).then((v) {
+      // TODO(nlacasse): Is throwing the correct behavior here?  Consider
+      // returning a tuple (Stream<mojom.Result>, Future) and resolve the
+      // Future at the end of the RPC (with an error if applicable).  Then
+      // errors will be handled the same in this method as in all the other
+      // methods that return Futures.  (Even though the other methods seem to
+      // "throw", they are actually resolving a Future since the function is
+      // declared with "async".)
+      if (isError(v.err)) throw v.err;
+    });
+
+    return sc.stream;
+  }
+
+  // TODO(nlacasse): Make a BatchDatabase class similar to what we did in JS.
+  Future<String> beginBatch(mojom.BatchOptions opts) async {
+    var v = await _proxy.ptr.dbBeginBatch(fullName, opts);
+    if (isError(v.err)) throw v.err;
+    return v.batchDn;
+  }
+
+  Future commit() async {
+    var v = await _proxy.ptr.dbCommit(fullName);
+    if (isError(v.err)) throw v.err;
+    return;
+  }
+
+  Future abort() async {
+    var v = await _proxy.ptr.dbAbort(fullName);
+    if (isError(v.err)) throw v.err;
+    return;
+  }
+
+  Future<mojom.Perms> getPermissions() async {
+    var v = await _proxy.ptr.dbGetPermissions(fullName);
+    if (isError(v.err)) throw v.err;
+
+    // TODO(nlacasse): We need to return the version too.  Create a struct type
+    // that combines perms and version?
+    return v.perms;
+  }
+
+  Future setPermissions(mojom.Perms perms, String version) async {
+    var v = await _proxy.ptr.dbSetPermissions(fullName, perms, version);
+    if (isError(v.err)) throw v.err;
+    return;
+  }
+}
+
+class ExecStreamImpl implements mojom.ExecStream {
+  final StreamController<mojom.Result> sc;
+  ExecStreamImpl._fromStreamController(this.sc);
+
+  onResult(mojom.Result result) {
+    sc.add(result);
+  }
+
+  onDone(Error err) {
+    if (isError(err)) {
+      sc.addError(err);
+    }
+    sc.close();
+  }
+}
diff --git a/dart/lib/src/nosql/row.dart b/dart/lib/src/nosql/row.dart
new file mode 100644
index 0000000..fb1f034
--- /dev/null
+++ b/dart/lib/src/nosql/row.dart
@@ -0,0 +1,30 @@
+part of syncbase_client;
+
+class SyncbaseRow extends NamedResource {
+  SyncbaseRow._internal(_proxy, _parentFullName, relativeName)
+      : super._internal(_proxy, _parentFullName, relativeName);
+
+  Future<bool> exists() async {
+    var v = await _proxy.ptr.rowExists(fullName);
+    if (isError(v.err)) throw v.err;
+    return v.exists;
+  }
+
+  Future<Uint8List> get() async {
+    var v = await _proxy.ptr.rowGet(fullName);
+    if (isError(v.err)) throw v.err;
+    return v.value;
+  }
+
+  Future put(Uint8List value) async {
+    var v = await _proxy.ptr.rowPut(fullName, value);
+    if (isError(v.err)) throw v.err;
+    return;
+  }
+
+  Future delete() async {
+    var v = await _proxy.ptr.rowDelete(fullName);
+    if (isError(v.err)) throw v.err;
+    return;
+  }
+}
diff --git a/dart/lib/src/nosql/syncgroup.dart b/dart/lib/src/nosql/syncgroup.dart
new file mode 100644
index 0000000..433e37e
--- /dev/null
+++ b/dart/lib/src/nosql/syncgroup.dart
@@ -0,0 +1,61 @@
+part of syncbase_client;
+
+class SyncbaseSyncGroup {
+  final String name;
+  final String _dbName;
+  final mojom.SyncbaseProxy _proxy;
+
+  SyncbaseSyncGroup._internal(this._proxy, this._dbName, this.name);
+
+  Future create(
+      mojom.SyncGroupSpec spec, mojom.SyncGroupMemberInfo myInfo) async {
+    var v = await _proxy.ptr.dbCreateSyncGroup(_dbName, name, spec, myInfo);
+    if (isError(v.err)) throw v.err;
+    return;
+  }
+
+  Future join(mojom.SyncGroupMemberInfo myInfo) async {
+    var v = await _proxy.ptr.dbJoinSyncGroup(_dbName, name, myInfo);
+    if (isError(v.err)) throw v.err;
+    return;
+  }
+
+  Future leave() async {
+    var v = await _proxy.ptr.dbLeaveSyncGroup(_dbName, name);
+    if (isError(v.err)) throw v.err;
+    return;
+  }
+
+  Future destroy() async {
+    var v = await _proxy.ptr.dbDestroySyncGroup(_dbName, name);
+    if (isError(v.err)) throw v.err;
+    return;
+  }
+
+  Future eject(String memberName) async {
+    var v = await _proxy.ptr.dbEjectFromSyncGroup(_dbName, name, memberName);
+    if (isError(v.err)) throw v.err;
+    return;
+  }
+
+  Future<mojom.SyncGroupSpec> getSpec() async {
+    var v = await _proxy.ptr.dbGetSyncGroupSpec(_dbName, name);
+    if (isError(v.err)) throw v.err;
+
+    // TODO(nlacasse): We need to return the version too.  Create a struct type
+    // that combines spec and version?
+    return v.spec;
+  }
+
+  Future setSpec(mojom.SyncGroupSpec spec, String version) async {
+    var v = await _proxy.ptr.dbSetSyncGroupSpec(_dbName, name, spec, version);
+    if (isError(v.err)) throw v.err;
+    return;
+  }
+
+  Future<Map<String, mojom.SyncGroupMemberInfo>> getMembers() async {
+    var v = await _proxy.ptr.dbGetSyncGroupMembers(_dbName, name);
+    if (isError(v.err)) throw v.err;
+    return v.infos;
+  }
+}
diff --git a/dart/lib/src/nosql/table.dart b/dart/lib/src/nosql/table.dart
new file mode 100644
index 0000000..78ca108
--- /dev/null
+++ b/dart/lib/src/nosql/table.dart
@@ -0,0 +1,87 @@
+part of syncbase_client;
+
+class SyncbaseTable extends NamedResource {
+  SyncbaseTable._internal(_proxy, _parentFullName, relativeName)
+      : super._internal(_proxy, _parentFullName, relativeName);
+
+  SyncbaseRow row(String key) {
+    return new SyncbaseRow._internal(_proxy, fullName, key);
+  }
+
+  Future create(mojom.Perms perms) async {
+    var v = await _proxy.ptr.tableCreate(fullName, perms);
+    if (isError(v.err)) throw v.err;
+    return;
+  }
+
+  Future delete() async {
+    var v = await _proxy.ptr.tableDelete(fullName);
+    if (isError(v.err)) throw v.err;
+    return;
+  }
+
+  Future<bool> exists() async {
+    var v = await _proxy.ptr.tableExists(fullName);
+    if (isError(v.err)) throw v.err;
+    return v.exists;
+  }
+
+  Future deleteRowRange(Uint8List start, Uint8List limit) async {
+    var v = await _proxy.ptr.tableDeleteRowRange(fullName, start, limit);
+    if (isError(v.err)) throw v.err;
+    return;
+  }
+
+  Stream<mojom.KeyValue> scan(Uint8List start, Uint8List limit) {
+    StreamController<mojom.KeyValue> sc = new StreamController();
+    mojom.ScanStream scanStream = new ScanStreamImpl._fromStreamController(sc);
+
+    // Call tableScan asynchronously.
+    _proxy.ptr.tableScan(fullName, start, limit, scanStream).then((v) {
+      // TODO(nlacasse): Is throwing the correct behavior here?  Consider
+      // returning a tuple (Stream<mojom.KeyValue>, Future) and resolve the
+      // Future at the end of the RPC (with an error if applicable).  Then
+      // errors will be handled the same in this method as in all the other
+      // methods that return Futures.  (Even though the other methods seem to
+      // "throw", they are actually resolving a Future since the function is
+      // declared with "async".)
+      if (isError(v.err)) throw v.err;
+    });
+
+    return sc.stream;
+  }
+
+  Future<List<mojom.PrefixPerms>> getPermissions(String key) async {
+    var v = await _proxy.ptr.tableGetPermissions(fullName, key);
+    if (isError(v.err)) throw v.err;
+    return v.permsArr;
+  }
+
+  Future setPermissions(String prefix, mojom.Perms perms) async {
+    var v = await _proxy.ptr.tableSetPermissions(fullName, prefix, perms);
+    if (isError(v.err)) throw v.err;
+    return;
+  }
+
+  Future deletePermissions(String prefix) async {
+    var v = await _proxy.ptr.tableDeletePermissions(fullName, prefix);
+    if (isError(v.err)) throw v.err;
+    return;
+  }
+}
+
+class ScanStreamImpl implements mojom.ScanStream {
+  final StreamController<mojom.KeyValue> sc;
+  ScanStreamImpl._fromStreamController(this.sc);
+
+  onKeyValue(mojom.KeyValue keyValue) {
+    sc.add(keyValue);
+  }
+
+  onDone(Error err) {
+    if (isError(err)) {
+      sc.addError(err);
+    }
+    sc.close();
+  }
+}
diff --git a/dart/lib/syncbase_client.dart b/dart/lib/syncbase_client.dart
index 4e0d897..f61c628 100644
--- a/dart/lib/syncbase_client.dart
+++ b/dart/lib/syncbase_client.dart
@@ -9,38 +9,45 @@
 
 // Export struct types from syncbase.mojom.
 export 'gen/dart-gen/mojom/lib/mojo/syncbase.mojom.dart'
-    show BatchOptions, ExecStream, Perms, ScanStream, SyncGroupMemberInfo, SyncGroupSpec;
+    show BatchOptions, Perms, SyncGroupMemberInfo, SyncGroupSpec;
+
+part 'src/app.dart';
+part 'src/named_resource.dart';
+part 'src/nosql/database.dart';
+part 'src/nosql/row.dart';
+part 'src/nosql/syncgroup.dart';
+part 'src/nosql/table.dart';
 
 bool isError(mojom.Error err) {
   return err != null && err.id != '';
 }
 
+// TODO(nlacasse): Write some tests for this interface now that syncbase runs
+// as a mojo service.  Currently we rely on dartanalyzer for correctness.
+
 class SyncbaseClient {
-  final Application _app;
+  final Application _mojoApp;
   final mojom.SyncbaseProxy _proxy;
   final String url;
 
-  SyncbaseClient(this._app, this.url) : _proxy = new mojom.SyncbaseProxy.unbound() {
+  SyncbaseClient(this._mojoApp, this.url)
+      : _proxy = new mojom.SyncbaseProxy.unbound() {
     print('connecting to $url');
-    _app.connectToService(url, _proxy);
+    _mojoApp.connectToService(url, _proxy);
     print('connected');
   }
 
+  // Closes the connection to the syncbase server.
+  // TODO(nlacasse): Is this necessary?
   Future close({bool immediate: false}) {
     return _proxy.close();
   }
 
-  // TODO(nlacasse): Break this SyncbaseClient class into multiple classes, one
-  // for each level.
+  // app returns the app with the given name, which should not contain slashes.
+  SyncbaseApp app(String name) => new SyncbaseApp._internal(_proxy, name);
 
-  // TODO(nlacasse): Write some tests for this interface once syncbase runs as
-  // a mojo service.  Currently we rely on dartanalyzer for correctness.
-
-  ////////////////////////////////////////
-  // Service
-
-  Future<mojom.Perms> serviceGetPermissions() async {
-    mojom.SyncbaseServiceGetPermissionsResponseParams v = await _proxy.ptr.serviceGetPermissions();
+  Future<mojom.Perms> getPermissions() async {
+    var v = await _proxy.ptr.serviceGetPermissions();
     if (isError(v.err)) throw v.err;
 
     // TODO(nlacasse): We need to return the version too.  Create a struct type
@@ -48,244 +55,8 @@
     return v.perms;
   }
 
-  Future serviceSetPermissions(mojom.Perms perms, String version) async {
-    mojom.SyncbaseServiceSetPermissionsResponseParams v = await _proxy.ptr.serviceSetPermissions(perms, version);
-    if (isError(v.err)) throw v.err;
-    return;
-  }
-
-  ////////////////////////////////////////
-  // App
-
-  Future appCreate(String name, mojom.Perms perms) async {
-    mojom.SyncbaseAppCreateResponseParams v = await _proxy.ptr.appCreate(name, perms);
-    if (isError(v.err)) throw v.err;
-    return;
-  }
-
-  Future appDelete(String name) async {
-    mojom.SyncbaseAppDeleteResponseParams v = await _proxy.ptr.appDelete(name);
-    if (isError(v.err)) throw v.err;
-    return;
-  }
-
-  Future<bool> appExists(String name) async {
-    mojom.SyncbaseAppExistsResponseParams v = await _proxy.ptr.appExists(name);
-    if (isError(v.err)) throw v.err;
-    return v.exists;
-  }
-
-  Future<mojom.Perms> appGetPermissions(String name) async {
-    mojom.SyncbaseAppGetPermissionsResponseParams v = await _proxy.ptr.appGetPermissions(name);
-    if (isError(v.err)) throw v.err;
-
-    // TODO(nlacasse): We need to return the version too.  Create a struct type
-    // that combines perms and version?
-    return v.perms;
-  }
-
-  Future appSetPermissions(String name, mojom.Perms perms, String version) async {
-    mojom.SyncbaseAppSetPermissionsResponseParams v = await _proxy.ptr.appSetPermissions(name, perms, version);
-    if (isError(v.err)) throw v.err;
-    return;
-  }
-
-  ////////////////////////////////////////
-  // nosql.Database
-
-  Future dbCreate(String name, mojom.Perms perms) async {
-    mojom.SyncbaseDbCreateResponseParams v = await _proxy.ptr.dbCreate(name, perms);
-    if (isError(v.err)) throw v.err;
-    return;
-  }
-
-  Future dbDelete(String name) async {
-    mojom.SyncbaseDbDeleteResponseParams v = await _proxy.ptr.dbDelete(name);
-    if (isError(v.err)) throw v.err;
-    return;
-  }
-
-  Future<bool> dbExists(String name) async {
-    mojom.SyncbaseDbExistsResponseParams v = await _proxy.ptr.dbExists(name);
-    if (isError(v.err)) throw v.err;
-    return v.exists;
-  }
-
-  Future dbExec(String name, String query, mojom.ExecStream stream) async {
-    mojom.SyncbaseDbExecResponseParams v = await _proxy.ptr.dbExec(name, query, stream);
-    if (isError(v.err)) throw v.err;
-    return;
-  }
-
-  Future<mojom.Perms> dbGetPermissions(String name) async {
-    mojom.SyncbaseDbGetPermissionsResponseParams v = await _proxy.ptr.dbGetPermissions(name);
-    if (isError(v.err)) throw v.err;
-
-    // TODO(nlacasse): We need to return the version too.  Create a struct type
-    // that combines perms and version?
-    return v.perms;
-  }
-
-  Future<String> dbBeginBatch(String name, mojom.BatchOptions opts) async {
-    mojom.SyncbaseDbBeginBatchResponseParams v = await _proxy.ptr.dbBeginBatch(name, opts);
-    if (isError(v.err)) throw v.err;
-    return v.batchDn;
-  }
-
-  Future dbCommit(String name) async {
-    mojom.SyncbaseDbCommitResponseParams v = await _proxy.ptr.dbCommit(name);
-    if (isError(v.err)) throw v.err;
-    return;
-  }
-
-  Future dbAbort(String name) async {
-    mojom.SyncbaseDbAbortResponseParams v = await _proxy.ptr.dbAbort(name);
-    if (isError(v.err)) throw v.err;
-    return;
-  }
-
-  Future dbSetPermissions(String name, mojom.Perms perms, String version) async {
-    mojom.SyncbaseDbSetPermissionsResponseParams v = await _proxy.ptr.dbSetPermissions(name, perms, version);
-    if (isError(v.err)) throw v.err;
-    return;
-  }
-
-  ////////////////////////////////////////
-  // nosql.Database:SyncGroupManager
-
-  Future<List<String>> dbGetSyncGroupNames(String name) async {
-    mojom.SyncbaseDbGetSyncGroupNamesResponseParams v = await _proxy.ptr.dbGetSyncGroupNames(name);
-    if (isError(v.err)) throw v.err;
-    return v.names;
-  }
-
-  Future dbCreateSyncGroup(
-      String name, String sgName, mojom.SyncGroupSpec spec, mojom.SyncGroupMemberInfo myInfo) async {
-    mojom.SyncbaseDbCreateSyncGroupResponseParams v = await _proxy.ptr.dbCreateSyncGroup(name, sgName, spec, myInfo);
-    if (isError(v.err)) throw v.err;
-    return;
-  }
-
-  Future dbJoinSyncGroup(String name, String sgName, mojom.SyncGroupMemberInfo myInfo) async {
-    mojom.SyncbaseDbJoinSyncGroupResponseParams v = await _proxy.ptr.dbJoinSyncGroup(name, sgName, myInfo);
-    if (isError(v.err)) throw v.err;
-    return;
-  }
-
-  Future dbLeaveSyncGroup(String name, String sgName) async {
-    mojom.SyncbaseDbLeaveSyncGroupResponseParams v = await _proxy.ptr.dbLeaveSyncGroup(name, sgName);
-    if (isError(v.err)) throw v.err;
-    return;
-  }
-
-  Future dbDestroySyncGroup(String name, String sgName) async {
-    mojom.SyncbaseDbDestroySyncGroupResponseParams v = await _proxy.ptr.dbDestroySyncGroup(name, sgName);
-    if (isError(v.err)) throw v.err;
-    return;
-  }
-
-  Future dbEjectFromSyncGroup(String name, String sgName, String memberName) async {
-    mojom.SyncbaseDbEjectFromSyncGroupResponseParams v =
-        await _proxy.ptr.dbEjectFromSyncGroup(name, sgName, memberName);
-    if (isError(v.err)) throw v.err;
-    return;
-  }
-
-  Future<mojom.SyncGroupSpec> dbGetSyncGroupSpec(String name, String sgName) async {
-    mojom.SyncbaseDbGetSyncGroupSpecResponseParams v = await _proxy.ptr.dbGetSyncGroupSpec(name, sgName);
-    if (isError(v.err)) throw v.err;
-
-    // TODO(nlacasse): We need to return the version too.  Create a struct type
-    // that combines spec and version?
-    return v.spec;
-  }
-
-  Future dbSetSyncGroupSpec(String name, String sgName, mojom.SyncGroupSpec spec, String version) async {
-    mojom.SyncbaseDbSetSyncGroupSpecResponseParams v = await _proxy.ptr.dbSetSyncGroupSpec(name, sgName, spec, version);
-    if (isError(v.err)) throw v.err;
-    return;
-  }
-
-  Future<Map<String, mojom.SyncGroupMemberInfo>> dbGetSyncGroupMembers(String name, String sgName) async {
-    mojom.SyncbaseDbGetSyncGroupMembersResponseParams v = await _proxy.ptr.dbGetSyncGroupMembers(name, sgName);
-    if (isError(v.err)) throw v.err;
-    return v.infos;
-  }
-
-  ////////////////////////////////////////
-  // nosql.Table
-
-  Future tableCreate(String name, mojom.Perms perms) async {
-    mojom.SyncbaseTableCreateResponseParams v = await _proxy.ptr.tableCreate(name, perms);
-    if (isError(v.err)) throw v.err;
-    return;
-  }
-
-  Future tableDelete(String name) async {
-    mojom.SyncbaseTableDeleteResponseParams v = await _proxy.ptr.tableDelete(name);
-    if (isError(v.err)) throw v.err;
-    return;
-  }
-
-  Future<bool> tableExists(String name) async {
-    mojom.SyncbaseTableExistsResponseParams v = await _proxy.ptr.tableExists(name);
-    if (isError(v.err)) throw v.err;
-    return v.exists;
-  }
-
-  Future tableDeleteRowRange(String name, Uint8List start, Uint8List limit) async {
-    mojom.SyncbaseTableDeleteRowRangeResponseParams v = await _proxy.ptr.tableDeleteRowRange(name, start, limit);
-    if (isError(v.err)) throw v.err;
-    return;
-  }
-
-  Future tableScan(String name, Uint8List start, Uint8List limit, mojom.ScanStream stream) async {
-    mojom.SyncbaseTableScanResponseParams v = await _proxy.ptr.tableScan(name, start, limit, stream);
-    if (isError(v.err)) throw v.err;
-    return;
-  }
-
-  Future<List<mojom.PrefixPerms>> tableGetPermissions(String name, String key) async {
-    mojom.SyncbaseTableGetPermissionsResponseParams v = await _proxy.ptr.tableGetPermissions(name, key);
-    if (isError(v.err)) throw v.err;
-    return v.permsArr;
-  }
-
-  Future tableSetPermissions(String name, String prefix, mojom.Perms perms) async {
-    mojom.SyncbaseTableSetPermissionsResponseParams v = await _proxy.ptr.tableSetPermissions(name, prefix, perms);
-    if (isError(v.err)) throw v.err;
-    return;
-  }
-
-  Future tableDeletePermissions(String name, String prefix) async {
-    mojom.SyncbaseTableDeletePermissionsResponseParams v = await _proxy.ptr.tableDeletePermissions(name, prefix);
-    if (isError(v.err)) throw v.err;
-    return;
-  }
-
-  ////////////////////////////////////////
-  // nosql.Row
-
-  Future<bool> rowExists(String name) async {
-    mojom.SyncbaseRowExistsResponseParams v = await _proxy.ptr.rowExists(name);
-    if (isError(v.err)) throw v.err;
-    return v.exists;
-  }
-
-  Future<Uint8List> rowGet(String name) async {
-    mojom.SyncbaseRowGetResponseParams v = await _proxy.ptr.rowGet(name);
-    if (isError(v.err)) throw v.err;
-    return v.value;
-  }
-
-  Future rowPut(String name, Uint8List value) async {
-    mojom.SyncbaseRowPutResponseParams v = await _proxy.ptr.rowPut(name, value);
-    if (isError(v.err)) throw v.err;
-    return;
-  }
-
-  Future rowDelete(String name) async {
-    mojom.SyncbaseRowDeleteResponseParams v = await _proxy.ptr.rowDelete(name);
+  Future setPermissions(mojom.Perms perms, String version) async {
+    var v = await _proxy.ptr.serviceSetPermissions(perms, version);
     if (isError(v.err)) throw v.err;
     return;
   }
diff --git a/dart/test/echo_test.dart b/dart/test/echo_test.dart
index df9fe34..593f507 100644
--- a/dart/test/echo_test.dart
+++ b/dart/test/echo_test.dart
@@ -13,7 +13,9 @@
 
   // TODO(nlacasse): Switch to serving these files over http rather than
   // directly from the filesystem, so they can be accessed by Android.
-  String url = 'file://' + args[1].replaceFirst('dart/test/echo_test.dart', 'gen/mojo/echo_server.mojo');
+  String url = 'file://' +
+      args[1].replaceFirst(
+          'dart/test/echo_test.dart', 'gen/mojo/echo_server.mojo');
 
   EchoClient c = new EchoClient(app, url);
 
diff --git a/dart/test/syncbase_test.dart b/dart/test/syncbase_test.dart
index d8bcb3f..200c9d3 100755
--- a/dart/test/syncbase_test.dart
+++ b/dart/test/syncbase_test.dart
@@ -1,5 +1,4 @@
 #!mojo mojo:dart_content_handler
-
 import 'dart:async';
 
 import 'package:mojo/core.dart' show MojoHandle;
@@ -14,7 +13,9 @@
 
   // TODO(nlacasse): Switch to serving these files over http rather than
   // directly from the filesystem, so they can be accessed by Android.
-  String url = 'file://' + args[1].replaceFirst('dart/test/syncbase_test.dart', 'gen/mojo/syncbase_server.mojo');
+  String url = 'file://' +
+      args[1].replaceFirst(
+          'dart/test/syncbase_test.dart', 'gen/mojo/syncbase_server.mojo');
 
   SyncbaseClient c = new SyncbaseClient(app, url);
 
@@ -22,8 +23,8 @@
     app.resetConnections();
   });
 
-  test('appExists(foo) should be false', () {
-    expect(c.appExists('foo'), completion(isFalse));
+  test('app(foo).exists() should be false', () {
+    expect(c.app('foo').exists(), completion(isFalse));
   });
 
   // Append a final test to terminate shell connection.