syncbase.js: Partial tests and implementation of service and app
interfaces for the JavaScript syncbase.
This CL adds tests and implementation for creating and listing
apps and getting and setting app permissions which partially
covers service and apps parts of the syncbase client.
Change-Id: I16613d7d2998395e035a5d83a00de9e4913b5f80
diff --git a/package.json b/package.json
index d526d21..7391470 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "syncbase",
"version": "0.0.0",
"description": "",
- "main": "index.js",
+ "main": "src/syncbase.js",
"dependencies": {
"debug": "~2.2.0"
},
@@ -12,7 +12,8 @@
"mkdirp": "~0.5.1",
"run-parallel": "~1.1.1",
"which": "~1.1.1",
- "minimist": "~1.1.1"
+ "minimist": "~1.1.1",
+ "async": "~1.0.0"
},
"repository": {
"type": "git",
diff --git a/src/app.js b/src/app.js
index cad2394..d4f9969 100644
--- a/src/app.js
+++ b/src/app.js
@@ -2,14 +2,40 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+var vanadium = require('vanadium');
+
+var vdl = require('./gen-vdl/v.io/syncbase/v23/services/syncbase');
+
+var wireSignature = vdl.App.prototype._serviceDescription;
+
module.exports = App;
-function App(name) {
- if (typeof this !== App) {
- return new App(name);
+function App(fullName, name) {
+ if (!(this instanceof App)) {
+ return new App(fullName, name);
}
- this.name = name;
+ /**
+ * @property name
+ * @type {string}
+ */
+ Object.defineProperty(this, 'name', {
+ value: name,
+ writable: false,
+ enumerable: true
+ });
+
+ /**
+ * @property name
+ * @type {string}
+ */
+ Object.defineProperty(this, 'fullName', {
+ value: fullName,
+ writable: false,
+ enumerable: true
+ });
+
+ this._wireObj = null;
}
// noSqlDatabase returns the noSqlDatabase with the given name. relativeName
@@ -21,10 +47,29 @@
// create creates this app. If perms is empty, we inherit (copy) the Service
// perms.
-App.prototype.create = function(ctx, perms) {};
+App.prototype.create = function(ctx, perms, cb) {
+ this._wire(ctx).create(ctx, perms, cb);
+};
// delete deletes this app.
-App.prototype.delete = function(ctx) {};
+App.prototype.delete = function(ctx, cb) {
+ this._wire(ctx).delete(ctx, cb);
+};
-App.prototype.getPermissions = function(ctx) {};
-App.prototype.setPermissions = function(ctx) {};
+App.prototype.getPermissions = function(ctx, cb) {
+ this._wire(ctx).getPermissions(ctx, cb);
+};
+
+App.prototype.setPermissions = function(ctx, perms, version, cb) {
+ this._wire(ctx).setPermissions(ctx, perms, version, cb);
+};
+
+App.prototype._wire = function(ctx) {
+ if (!this._wireObj) {
+ var rt = vanadium.runtimeForContext(ctx);
+ var client = rt.newClient();
+ this._wireObj = client.bindWithSignature(this.fullName, [wireSignature]);
+ }
+
+ return this._wireObj;
+};
\ No newline at end of file
diff --git a/src/gen-vdl/v.io/syncbase/v23/services/syncbase/nosql/index.js b/src/gen-vdl/v.io/syncbase/v23/services/syncbase/nosql/index.js
index 68a33e5..b9c866f 100644
--- a/src/gen-vdl/v.io/syncbase/v23/services/syncbase/nosql/index.js
+++ b/src/gen-vdl/v.io/syncbase/v23/services/syncbase/nosql/index.js
@@ -21,22 +21,56 @@
// Types:
var _type1 = new vdl.Type();
+var _type2 = new vdl.Type();
+var _type3 = new vdl.Type();
+var _type4 = new vdl.Type();
var _typeBatchOptions = new vdl.Type();
+var _typeKeyValue = new vdl.Type();
var _typePrefixPermissions = new vdl.Type();
+var _typeSyncGroupMemberInfo = new vdl.Type();
+var _typeSyncGroupSpec = new vdl.Type();
_type1.kind = vdl.kind.LIST;
_type1.name = "";
-_type1.elem = _typePrefixPermissions;
+_type1.elem = vdl.types.STRING;
+_type2.kind = vdl.kind.MAP;
+_type2.name = "";
+_type2.elem = _typeSyncGroupMemberInfo;
+_type2.key = vdl.types.STRING;
+_type3.kind = vdl.kind.LIST;
+_type3.name = "";
+_type3.elem = vdl.types.BYTE;
+_type4.kind = vdl.kind.LIST;
+_type4.name = "";
+_type4.elem = _typePrefixPermissions;
_typeBatchOptions.kind = vdl.kind.STRUCT;
_typeBatchOptions.name = "v.io/syncbase/v23/services/syncbase/nosql.BatchOptions";
_typeBatchOptions.fields = [{name: "Hint", type: vdl.types.STRING}, {name: "ReadOnly", type: vdl.types.BOOL}];
+_typeKeyValue.kind = vdl.kind.STRUCT;
+_typeKeyValue.name = "v.io/syncbase/v23/services/syncbase/nosql.KeyValue";
+_typeKeyValue.fields = [{name: "Key", type: vdl.types.STRING}, {name: "Value", type: _type3}];
_typePrefixPermissions.kind = vdl.kind.STRUCT;
_typePrefixPermissions.name = "v.io/syncbase/v23/services/syncbase/nosql.PrefixPermissions";
_typePrefixPermissions.fields = [{name: "Prefix", type: vdl.types.STRING}, {name: "Perms", type: new access.Permissions()._type}];
+_typeSyncGroupMemberInfo.kind = vdl.kind.STRUCT;
+_typeSyncGroupMemberInfo.name = "v.io/syncbase/v23/services/syncbase/nosql.SyncGroupMemberInfo";
+_typeSyncGroupMemberInfo.fields = [{name: "SyncPriority", type: vdl.types.BYTE}];
+_typeSyncGroupSpec.kind = vdl.kind.STRUCT;
+_typeSyncGroupSpec.name = "v.io/syncbase/v23/services/syncbase/nosql.SyncGroupSpec";
+_typeSyncGroupSpec.fields = [{name: "Description", type: vdl.types.STRING}, {name: "Perms", type: new access.Permissions()._type}, {name: "Prefixes", type: _type1}, {name: "MountTables", type: _type1}, {name: "IsPrivate", type: vdl.types.BOOL}];
_type1.freeze();
+_type2.freeze();
+_type3.freeze();
+_type4.freeze();
_typeBatchOptions.freeze();
+_typeKeyValue.freeze();
_typePrefixPermissions.freeze();
+_typeSyncGroupMemberInfo.freeze();
+_typeSyncGroupSpec.freeze();
module.exports.BatchOptions = (vdl.registry.lookupOrCreateConstructor(_typeBatchOptions));
+module.exports.KeyValue = (vdl.registry.lookupOrCreateConstructor(_typeKeyValue));
module.exports.PrefixPermissions = (vdl.registry.lookupOrCreateConstructor(_typePrefixPermissions));
+module.exports.SyncGroupMemberInfo = (vdl.registry.lookupOrCreateConstructor(_typeSyncGroupMemberInfo));
+module.exports.SyncGroupSpec = (vdl.registry.lookupOrCreateConstructor(_typeSyncGroupSpec));
@@ -65,6 +99,263 @@
+function SyncGroupManager(){}
+module.exports.SyncGroupManager = SyncGroupManager;
+
+
+
+SyncGroupManager.prototype.getSyncGroupNames = function(ctx, serverCall) {
+ throw new Error('Method GetSyncGroupNames not implemented');
+};
+
+
+SyncGroupManager.prototype.createSyncGroup = function(ctx, serverCall, sgName, spec, myInfo) {
+ throw new Error('Method CreateSyncGroup not implemented');
+};
+
+
+SyncGroupManager.prototype.joinSyncGroup = function(ctx, serverCall, sgName, myInfo) {
+ throw new Error('Method JoinSyncGroup not implemented');
+};
+
+
+SyncGroupManager.prototype.leaveSyncGroup = function(ctx, serverCall, sgName) {
+ throw new Error('Method LeaveSyncGroup not implemented');
+};
+
+
+SyncGroupManager.prototype.destroySyncGroup = function(ctx, serverCall, sgName) {
+ throw new Error('Method DestroySyncGroup not implemented');
+};
+
+
+SyncGroupManager.prototype.ejectFromSyncGroup = function(ctx, serverCall, sgName, member) {
+ throw new Error('Method EjectFromSyncGroup not implemented');
+};
+
+
+SyncGroupManager.prototype.getSyncGroupSpec = function(ctx, serverCall, sgName) {
+ throw new Error('Method GetSyncGroupSpec not implemented');
+};
+
+
+SyncGroupManager.prototype.setSyncGroupSpec = function(ctx, serverCall, sgName, spec, version) {
+ throw new Error('Method SetSyncGroupSpec not implemented');
+};
+
+
+SyncGroupManager.prototype.getSyncGroupMembers = function(ctx, serverCall, sgName) {
+ throw new Error('Method GetSyncGroupMembers not implemented');
+};
+
+
+
+SyncGroupManager.prototype._serviceDescription = {
+ name: 'SyncGroupManager',
+ pkgPath: 'v.io/syncbase/v23/services/syncbase/nosql',
+ doc: "// SyncGroupManager is the interface for SyncGroup operations.\n// TODO(hpucha): Add blessings to create/join and add a refresh method.",
+ embeds: [],
+ methods: [
+
+
+ {
+ name: 'GetSyncGroupNames',
+ doc: "// GetSyncGroupNames returns the global names of all SyncGroups attached\n// to this database.",
+ inArgs: [],
+ outArgs: [{
+ name: '',
+ doc: "",
+ type: _type1
+ },
+ ],
+ inStream: null,
+ outStream: null,
+ tags: [canonicalize.reduce(new access.Tag("Read", true), new access.Tag()._type), ]
+ },
+
+
+ {
+ name: 'CreateSyncGroup',
+ doc: "// CreateSyncGroup creates a new SyncGroup with the given\n// spec.\n//\n// Requires: Client must have at least Read access on the\n// Database; prefix ACL must exist at each SyncGroup prefix;\n// Client must have at least Read access on each of these\n// prefix ACLs.",
+ inArgs: [{
+ name: 'sgName',
+ doc: "",
+ type: vdl.types.STRING
+ },
+ {
+ name: 'spec',
+ doc: "",
+ type: _typeSyncGroupSpec
+ },
+ {
+ name: 'myInfo',
+ doc: "",
+ type: _typeSyncGroupMemberInfo
+ },
+ ],
+ outArgs: [],
+ inStream: null,
+ outStream: null,
+ tags: [canonicalize.reduce(new access.Tag("Read", true), new access.Tag()._type), ]
+ },
+
+
+ {
+ name: 'JoinSyncGroup',
+ doc: "// JoinSyncGroup joins the specified SyncGroup.\n//\n// Requires: Client must have at least Read access on the\n// Database, and on the SyncGroup ACL.",
+ inArgs: [{
+ name: 'sgName',
+ doc: "",
+ type: vdl.types.STRING
+ },
+ {
+ name: 'myInfo',
+ doc: "",
+ type: _typeSyncGroupMemberInfo
+ },
+ ],
+ outArgs: [{
+ name: 'spec',
+ doc: "",
+ type: _typeSyncGroupSpec
+ },
+ ],
+ inStream: null,
+ outStream: null,
+ tags: [canonicalize.reduce(new access.Tag("Read", true), new access.Tag()._type), ]
+ },
+
+
+ {
+ name: 'LeaveSyncGroup',
+ doc: "// LeaveSyncGroup leaves the SyncGroup, and synced data will\n// continue to be available.\n//\n// Requires: Client must have at least Read access on the Database.",
+ inArgs: [{
+ name: 'sgName',
+ doc: "",
+ type: vdl.types.STRING
+ },
+ ],
+ outArgs: [],
+ inStream: null,
+ outStream: null,
+ tags: [canonicalize.reduce(new access.Tag("Read", true), new access.Tag()._type), ]
+ },
+
+
+ {
+ name: 'DestroySyncGroup',
+ doc: "// DestroySyncGroup destroys the SyncGroup, and synced data\n// will continue to be available.\n//\n// Requires: Client must have at least Read access on the\n// Database, and must have Admin access on the SyncGroup ACL.",
+ inArgs: [{
+ name: 'sgName',
+ doc: "",
+ type: vdl.types.STRING
+ },
+ ],
+ outArgs: [],
+ inStream: null,
+ outStream: null,
+ tags: [canonicalize.reduce(new access.Tag("Read", true), new access.Tag()._type), ]
+ },
+
+
+ {
+ name: 'EjectFromSyncGroup',
+ doc: "// EjectFromSyncGroup ejects a member from the\n// SyncGroup. Ejected member will not able to sync further,\n// but will retain any data previously available locally.\n//\n// Requires: Client must have at least Read access on the\n// Database, and must have Admin access on the SyncGroup ACL.",
+ inArgs: [{
+ name: 'sgName',
+ doc: "",
+ type: vdl.types.STRING
+ },
+ {
+ name: 'member',
+ doc: "",
+ type: vdl.types.STRING
+ },
+ ],
+ outArgs: [],
+ inStream: null,
+ outStream: null,
+ tags: [canonicalize.reduce(new access.Tag("Read", true), new access.Tag()._type), ]
+ },
+
+
+ {
+ name: 'GetSyncGroupSpec',
+ doc: "// GetSyncGroupSpec gets the SyncGroup spec. version allows\n// for atomic read-modify-write of the spec by providing\n// optimistic concurrency control.\n//\n// Requires: Client must have at least Read access on the\n// Database, and on the SyncGroup ACL.",
+ inArgs: [{
+ name: 'sgName',
+ doc: "",
+ type: vdl.types.STRING
+ },
+ ],
+ outArgs: [{
+ name: 'spec',
+ doc: "",
+ type: _typeSyncGroupSpec
+ },
+ {
+ name: 'version',
+ doc: "",
+ type: vdl.types.STRING
+ },
+ ],
+ inStream: null,
+ outStream: null,
+ tags: [canonicalize.reduce(new access.Tag("Read", true), new access.Tag()._type), ]
+ },
+
+
+ {
+ name: 'SetSyncGroupSpec',
+ doc: "// SetSyncGroupSpec sets the SyncGroup spec. version may be\n// either empty, or the value from a recent Get. If not empty,\n// Set will only succeed if version matches that specified in\n// Set.\n//\n// Requires: Client must have at least Read access on the\n// Database, and must have Admin access on the SyncGroup ACL.",
+ inArgs: [{
+ name: 'sgName',
+ doc: "",
+ type: vdl.types.STRING
+ },
+ {
+ name: 'spec',
+ doc: "",
+ type: _typeSyncGroupSpec
+ },
+ {
+ name: 'version',
+ doc: "",
+ type: vdl.types.STRING
+ },
+ ],
+ outArgs: [],
+ inStream: null,
+ outStream: null,
+ tags: [canonicalize.reduce(new access.Tag("Read", true), new access.Tag()._type), ]
+ },
+
+
+ {
+ name: 'GetSyncGroupMembers',
+ doc: "// GetSyncGroupMembers gets the info on members who joined\n// this SyncGroup.\n//\n// Requires: Client must have at least Read access on the\n// Database, and on the SyncGroup ACL.",
+ inArgs: [{
+ name: 'sgName',
+ doc: "",
+ type: vdl.types.STRING
+ },
+ ],
+ outArgs: [{
+ name: 'members',
+ doc: "",
+ type: _type2
+ },
+ ],
+ inStream: null,
+ outStream: null,
+ tags: [canonicalize.reduce(new access.Tag("Read", true), new access.Tag()._type), ]
+ },
+
+ ]
+};
+
+
+
function Database(){}
module.exports.Database = Database;
@@ -103,6 +394,51 @@
Database.prototype.getPermissions = function(ctx, serverCall) {
throw new Error('Method GetPermissions not implemented');
};
+
+
+Database.prototype.getSyncGroupNames = function(ctx, serverCall) {
+ throw new Error('Method GetSyncGroupNames not implemented');
+};
+
+
+Database.prototype.createSyncGroup = function(ctx, serverCall, sgName, spec, myInfo) {
+ throw new Error('Method CreateSyncGroup not implemented');
+};
+
+
+Database.prototype.joinSyncGroup = function(ctx, serverCall, sgName, myInfo) {
+ throw new Error('Method JoinSyncGroup not implemented');
+};
+
+
+Database.prototype.leaveSyncGroup = function(ctx, serverCall, sgName) {
+ throw new Error('Method LeaveSyncGroup not implemented');
+};
+
+
+Database.prototype.destroySyncGroup = function(ctx, serverCall, sgName) {
+ throw new Error('Method DestroySyncGroup not implemented');
+};
+
+
+Database.prototype.ejectFromSyncGroup = function(ctx, serverCall, sgName, member) {
+ throw new Error('Method EjectFromSyncGroup not implemented');
+};
+
+
+Database.prototype.getSyncGroupSpec = function(ctx, serverCall, sgName) {
+ throw new Error('Method GetSyncGroupSpec not implemented');
+};
+
+
+Database.prototype.setSyncGroupSpec = function(ctx, serverCall, sgName, spec, version) {
+ throw new Error('Method SetSyncGroupSpec not implemented');
+};
+
+
+Database.prototype.getSyncGroupMembers = function(ctx, serverCall, sgName) {
+ throw new Error('Method GetSyncGroupMembers not implemented');
+};
@@ -115,6 +451,11 @@
pkgPath: 'v.io/v23/services/permissions',
doc: "// Object provides access control for Vanadium objects.\n//\n// Vanadium services implementing dynamic access control would typically embed\n// this interface and tag additional methods defined by the service with one of\n// Admin, Read, Write, Resolve etc. For example, the VDL definition of the\n// object would be:\n//\n// package mypackage\n//\n// import \"v.io/v23/security/access\"\n// import \"v.io/v23/services/permissions\"\n//\n// type MyObject interface {\n// permissions.Object\n// MyRead() (string, error) {access.Read}\n// MyWrite(string) error {access.Write}\n// }\n//\n// If the set of pre-defined tags is insufficient, services may define their\n// own tag type and annotate all methods with this new type.\n//\n// Instead of embedding this Object interface, define SetPermissions and\n// GetPermissions in their own interface. Authorization policies will typically\n// respect annotations of a single type. For example, the VDL definition of an\n// object would be:\n//\n// package mypackage\n//\n// import \"v.io/v23/security/access\"\n//\n// type MyTag string\n//\n// const (\n// Blue = MyTag(\"Blue\")\n// Red = MyTag(\"Red\")\n// )\n//\n// type MyObject interface {\n// MyMethod() (string, error) {Blue}\n//\n// // Allow clients to change access via the access.Object interface:\n// SetPermissions(perms access.Permissions, version string) error {Red}\n// GetPermissions() (perms access.Permissions, version string, err error) {Blue}\n// }"
},
+ {
+ name: 'SyncGroupManager',
+ pkgPath: 'v.io/syncbase/v23/services/syncbase/nosql',
+ doc: "// SyncGroupManager is the interface for SyncGroup operations.\n// TODO(hpucha): Add blessings to create/join and add a refresh method."
+ },
],
methods: [
@@ -229,6 +570,200 @@
outStream: null,
tags: [canonicalize.reduce(new access.Tag("Admin", true), new access.Tag()._type), ]
},
+
+
+ {
+ name: 'GetSyncGroupNames',
+ doc: "// GetSyncGroupNames returns the global names of all SyncGroups attached\n// to this database.",
+ inArgs: [],
+ outArgs: [{
+ name: '',
+ doc: "",
+ type: _type1
+ },
+ ],
+ inStream: null,
+ outStream: null,
+ tags: [canonicalize.reduce(new access.Tag("Read", true), new access.Tag()._type), ]
+ },
+
+
+ {
+ name: 'CreateSyncGroup',
+ doc: "// CreateSyncGroup creates a new SyncGroup with the given\n// spec.\n//\n// Requires: Client must have at least Read access on the\n// Database; prefix ACL must exist at each SyncGroup prefix;\n// Client must have at least Read access on each of these\n// prefix ACLs.",
+ inArgs: [{
+ name: 'sgName',
+ doc: "",
+ type: vdl.types.STRING
+ },
+ {
+ name: 'spec',
+ doc: "",
+ type: _typeSyncGroupSpec
+ },
+ {
+ name: 'myInfo',
+ doc: "",
+ type: _typeSyncGroupMemberInfo
+ },
+ ],
+ outArgs: [],
+ inStream: null,
+ outStream: null,
+ tags: [canonicalize.reduce(new access.Tag("Read", true), new access.Tag()._type), ]
+ },
+
+
+ {
+ name: 'JoinSyncGroup',
+ doc: "// JoinSyncGroup joins the specified SyncGroup.\n//\n// Requires: Client must have at least Read access on the\n// Database, and on the SyncGroup ACL.",
+ inArgs: [{
+ name: 'sgName',
+ doc: "",
+ type: vdl.types.STRING
+ },
+ {
+ name: 'myInfo',
+ doc: "",
+ type: _typeSyncGroupMemberInfo
+ },
+ ],
+ outArgs: [{
+ name: 'spec',
+ doc: "",
+ type: _typeSyncGroupSpec
+ },
+ ],
+ inStream: null,
+ outStream: null,
+ tags: [canonicalize.reduce(new access.Tag("Read", true), new access.Tag()._type), ]
+ },
+
+
+ {
+ name: 'LeaveSyncGroup',
+ doc: "// LeaveSyncGroup leaves the SyncGroup, and synced data will\n// continue to be available.\n//\n// Requires: Client must have at least Read access on the Database.",
+ inArgs: [{
+ name: 'sgName',
+ doc: "",
+ type: vdl.types.STRING
+ },
+ ],
+ outArgs: [],
+ inStream: null,
+ outStream: null,
+ tags: [canonicalize.reduce(new access.Tag("Read", true), new access.Tag()._type), ]
+ },
+
+
+ {
+ name: 'DestroySyncGroup',
+ doc: "// DestroySyncGroup destroys the SyncGroup, and synced data\n// will continue to be available.\n//\n// Requires: Client must have at least Read access on the\n// Database, and must have Admin access on the SyncGroup ACL.",
+ inArgs: [{
+ name: 'sgName',
+ doc: "",
+ type: vdl.types.STRING
+ },
+ ],
+ outArgs: [],
+ inStream: null,
+ outStream: null,
+ tags: [canonicalize.reduce(new access.Tag("Read", true), new access.Tag()._type), ]
+ },
+
+
+ {
+ name: 'EjectFromSyncGroup',
+ doc: "// EjectFromSyncGroup ejects a member from the\n// SyncGroup. Ejected member will not able to sync further,\n// but will retain any data previously available locally.\n//\n// Requires: Client must have at least Read access on the\n// Database, and must have Admin access on the SyncGroup ACL.",
+ inArgs: [{
+ name: 'sgName',
+ doc: "",
+ type: vdl.types.STRING
+ },
+ {
+ name: 'member',
+ doc: "",
+ type: vdl.types.STRING
+ },
+ ],
+ outArgs: [],
+ inStream: null,
+ outStream: null,
+ tags: [canonicalize.reduce(new access.Tag("Read", true), new access.Tag()._type), ]
+ },
+
+
+ {
+ name: 'GetSyncGroupSpec',
+ doc: "// GetSyncGroupSpec gets the SyncGroup spec. version allows\n// for atomic read-modify-write of the spec by providing\n// optimistic concurrency control.\n//\n// Requires: Client must have at least Read access on the\n// Database, and on the SyncGroup ACL.",
+ inArgs: [{
+ name: 'sgName',
+ doc: "",
+ type: vdl.types.STRING
+ },
+ ],
+ outArgs: [{
+ name: 'spec',
+ doc: "",
+ type: _typeSyncGroupSpec
+ },
+ {
+ name: 'version',
+ doc: "",
+ type: vdl.types.STRING
+ },
+ ],
+ inStream: null,
+ outStream: null,
+ tags: [canonicalize.reduce(new access.Tag("Read", true), new access.Tag()._type), ]
+ },
+
+
+ {
+ name: 'SetSyncGroupSpec',
+ doc: "// SetSyncGroupSpec sets the SyncGroup spec. version may be\n// either empty, or the value from a recent Get. If not empty,\n// Set will only succeed if version matches that specified in\n// Set.\n//\n// Requires: Client must have at least Read access on the\n// Database, and must have Admin access on the SyncGroup ACL.",
+ inArgs: [{
+ name: 'sgName',
+ doc: "",
+ type: vdl.types.STRING
+ },
+ {
+ name: 'spec',
+ doc: "",
+ type: _typeSyncGroupSpec
+ },
+ {
+ name: 'version',
+ doc: "",
+ type: vdl.types.STRING
+ },
+ ],
+ outArgs: [],
+ inStream: null,
+ outStream: null,
+ tags: [canonicalize.reduce(new access.Tag("Read", true), new access.Tag()._type), ]
+ },
+
+
+ {
+ name: 'GetSyncGroupMembers',
+ doc: "// GetSyncGroupMembers gets the info on members who joined\n// this SyncGroup.\n//\n// Requires: Client must have at least Read access on the\n// Database, and on the SyncGroup ACL.",
+ inArgs: [{
+ name: 'sgName',
+ doc: "",
+ type: vdl.types.STRING
+ },
+ ],
+ outArgs: [{
+ name: 'members',
+ doc: "",
+ type: _type2
+ },
+ ],
+ inStream: null,
+ outStream: null,
+ tags: [canonicalize.reduce(new access.Tag("Read", true), new access.Tag()._type), ]
+ },
]
};
@@ -255,6 +790,11 @@
};
+Table.prototype.scan = function(ctx, serverCall, start, limit) {
+ throw new Error('Method Scan not implemented');
+};
+
+
Table.prototype.setPermissions = function(ctx, serverCall, prefix, perms) {
throw new Error('Method SetPermissions not implemented');
};
@@ -308,7 +848,7 @@
{
name: 'DeleteRowRange',
- doc: "// DeleteRowRange deletes all rows in the given range. If the last row that is\n// covered by a prefix from SetPermissions is deleted, that (prefix, perms)\n// pair is removed.\n// TODO(sadovsky): Automatic GC does not interact well with sync. This API\n// needs to be revisited.",
+ doc: "// DeleteRowRange deletes all rows in the given range. If the last row that is\n// covered by a prefix from SetPermissions is deleted, that (prefix, perms)\n// pair is removed. If limit is \"\", all rows with keys >= start are included.\n// TODO(sadovsky): Automatic GC interacts poorly with sync. Revisit this API.",
inArgs: [{
name: 'start',
doc: "",
@@ -328,6 +868,31 @@
{
+ name: 'Scan',
+ doc: "// Scan returns all rows in the given range. The returned stream reads from a\n// consistent snapshot taken at the time of the Scan RPC. If limit is \"\", all\n// rows with keys >= start are included.",
+ inArgs: [{
+ name: 'start',
+ doc: "",
+ type: vdl.types.STRING
+ },
+ {
+ name: 'limit',
+ doc: "",
+ type: vdl.types.STRING
+ },
+ ],
+ outArgs: [],
+ inStream: null,
+ outStream: {
+ name: '',
+ doc: '',
+ type: _typeKeyValue
+ },
+ tags: [canonicalize.reduce(new access.Tag("Read", true), new access.Tag()._type), ]
+ },
+
+
+ {
name: 'SetPermissions',
doc: "// SetPermissions sets the permissions for all current and future rows with\n// the given prefix. If the prefix overlaps with an existing prefix, the\n// longest prefix that matches a row applies. For example:\n// SetPermissions(ctx, Prefix(\"a/b\"), perms1)\n// SetPermissions(ctx, Prefix(\"a/b/c\"), perms2)\n// The permissions for row \"a/b/1\" are perms1, and the permissions for row\n// \"a/b/c/1\" are perms2.\n//\n// SetPermissions will fail if called with a prefix that does not match any\n// rows.",
inArgs: [{
@@ -360,7 +925,7 @@
outArgs: [{
name: '',
doc: "",
- type: _type1
+ type: _type4
},
],
inStream: null,
@@ -413,7 +978,7 @@
Row.prototype._serviceDescription = {
name: 'Row',
pkgPath: 'v.io/syncbase/v23/services/syncbase/nosql',
- doc: "// Row represents a single row in a Table.\n// All access checks are performed against the most specific matching prefix\n// permissions in the Table.",
+ doc: "// Row represents a single row in a Table.\n// All access checks are performed against the most specific matching prefix\n// permissions in the Table.\n// NOTE(sadovsky): Currently we send []byte values over the wire for Get, Put,\n// and Scan. If there's a way to avoid encoding/decoding on the server side, we\n// can use vdl.Value everywhere without sacrificing performance.",
embeds: [],
methods: [
@@ -425,7 +990,7 @@
outArgs: [{
name: '',
doc: "",
- type: vdl.types.ANY
+ type: _type3
},
],
inStream: null,
@@ -440,7 +1005,7 @@
inArgs: [{
name: 'value',
doc: "",
- type: vdl.types.ANY
+ type: _type3
},
],
outArgs: [],
diff --git a/src/nosql/database.js b/src/nosql/database.js
index 8ed8b5f..e75f5ac 100644
--- a/src/nosql/database.js
+++ b/src/nosql/database.js
@@ -7,7 +7,7 @@
// Database represents a collection of Tables. Batches, queries, sync, watch,
// etc. all operate at the Database level.
function Database(fullName, name) {
- if (typeof this !== Database) {
+ if (!(this instanceof Database)) {
return new Database(fullName, name);
}
diff --git a/src/nosql/row.js b/src/nosql/row.js
index b513ea7..eecb4e8 100644
--- a/src/nosql/row.js
+++ b/src/nosql/row.js
@@ -8,7 +8,7 @@
function Row(fullName, key) {
// TODO(aghassemi) We may need to escape the key. Align with Go implementation
// when that decision is made.
- if (typeof this !== Row) {
+ if (!(this instanceof Row)) {
return new Row(fullName, key);
}
diff --git a/src/nosql/table.js b/src/nosql/table.js
index 2534009..9510096 100644
--- a/src/nosql/table.js
+++ b/src/nosql/table.js
@@ -6,7 +6,7 @@
// Table represents a collection of Rows.
function Table(fullName, name) {
- if (typeof this !== Table) {
+ if (!(this instanceof Table)) {
return new Table(fullName, name);
}
diff --git a/src/service.js b/src/service.js
index 308fa52..00f3a64 100644
--- a/src/service.js
+++ b/src/service.js
@@ -2,20 +2,79 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+var vanadium = require('vanadium');
+
+var App = require('./app');
+var vdl = require('./gen-vdl/v.io/syncbase/v23/services/syncbase');
+
+// TODO(aghassemi): This looks clunky,
+// https://github.com/vanadium/issues/issues/499 to deal with it.
+var wireSignature = vdl.Service.prototype._serviceDescription;
+
module.exports = Service;
-function Service() {
- if (typeof this !== Service) {
- return new Service();
+function Service(fullName) {
+ if (!(this instanceof Service)) {
+ return new Service(fullName);
}
+
+ /**
+ * @property name
+ * @type {string}
+ */
+ Object.defineProperty(this, 'fullName', {
+ value: fullName,
+ writable: false,
+ enumerable: true
+ });
+
+ this._wireObj = null;
}
// app returns the app with the given name. relativeName should not contain
// slashes.
-Service.prototype.app = function(ctx, relativeName) {};
+Service.prototype.app = function(relativeName) {
+ var fullName = vanadium.naming.join(this.fullName, relativeName);
+ return new App(fullName, relativeName);
+};
// listApps returns a list of all app names.
-Service.prototype.listApps = function(ctx) {};
+Service.prototype.listApps = function(ctx, cb) {
+ var rt = vanadium.runtimeForContext(ctx);
+ var namespace = rt.namespace();
+ var appNames = [];
-Service.prototype.getPermissions = function(ctx) {};
-Service.prototype.setPermissions = function(ctx) {};
+ //TODO(nlacasse): Refactor the glob->list into a common util
+ var stream = namespace.glob(ctx, vanadium.naming.join(this.fullName, '*'),
+ function(err) {
+ if (err) {
+ return cb(err);
+ }
+
+ cb(null, appNames);
+ }).stream;
+
+ stream.on('data', function(globResult) {
+ var fullName = globResult.name;
+ var name = vanadium.naming.basename(fullName);
+ appNames.push(name);
+ });
+};
+
+Service.prototype.getPermissions = function(ctx, cb) {
+ this._wire(ctx).getPermissions(ctx, cb);
+};
+
+Service.prototype.setPermissions = function(ctx, perms, version, cb) {
+ this._wire(ctx).setPermissions(ctx, perms, version, cb);
+};
+
+Service.prototype._wire = function(ctx, cb) {
+ if (!this._wireObj) {
+ var rt = vanadium.runtimeForContext(ctx);
+ var client = rt.newClient();
+ this._wireObj = client.bindWithSignature(this.fullName, [wireSignature]);
+ }
+
+ return this._wireObj;
+};
\ No newline at end of file
diff --git a/src/syncbase.js b/src/syncbase.js
new file mode 100644
index 0000000..d698ee4
--- /dev/null
+++ b/src/syncbase.js
@@ -0,0 +1,13 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+var Service = require('./service');
+
+module.exports = {
+ newService: newService
+};
+
+function newService(fullName) {
+ return new Service(fullName);
+}
\ No newline at end of file
diff --git a/test/integration/app-name.js b/test/integration/app-name.js
new file mode 100644
index 0000000..0a7558c
--- /dev/null
+++ b/test/integration/app-name.js
@@ -0,0 +1,21 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+
+/*
+ * TODO(aghassemi) We want to have each test run in a completely clean state of
+ * the syncbase but currently it is hard to do that since we would need to
+ * restart a running service. For now we use a unique app name to isolate tests
+ * but this is not the ideal approach.
+ */
+
+/*
+ * Provides unique names for the duration of a full testing runtime so that
+ * each test can create its own unique apps in the singleton syncbase and
+ * therefore be independent of other tests.
+ */
+var counter = 0;
+module.exports = function() {
+ return 'testApp_' + counter++;
+};
\ No newline at end of file
diff --git a/test/integration/service-name.js b/test/integration/service-name.js
new file mode 100644
index 0000000..a463b6e
--- /dev/null
+++ b/test/integration/service-name.js
@@ -0,0 +1,5 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+module.exports = 'test/syncbased';
\ No newline at end of file
diff --git a/test/integration/test-app.js b/test/integration/test-app.js
new file mode 100644
index 0000000..6f43e94
--- /dev/null
+++ b/test/integration/test-app.js
@@ -0,0 +1,289 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+var async = require('async');
+var test = require('prova');
+var vanadium = require('vanadium');
+
+var getUniqueAppName = require('./app-name');
+var SERVICE_NAME = require('./service-name');
+var syncbase = require('../..');
+
+var DEFAULT_PERMISSIONS = new Map([
+ ['Read', {
+ 'in': ['...'],
+ 'notIn': []
+ }],
+ ['Write', {
+ 'in': ['...'],
+ 'notIn': []
+ }],
+ ['Admin', {
+ 'in': ['...'],
+ 'notIn': []
+ }]
+]);
+
+/*
+ * TODO(aghassemi) We should refactor some of the testing functionality,
+ * specially around verifying children and setting/getting permissions, into a
+ * common util as these types of test will be common across different layers.
+ */
+
+test('Creating a service and checking its full name', function(t) {
+ setup(t, function(err, o) {
+ if (err) {
+ return t.end(err);
+ }
+
+ var service = syncbase.newService(SERVICE_NAME);
+ t.equals(service.fullName, SERVICE_NAME, 'Service name matches');
+ o.teardown(t.end);
+ });
+});
+
+test('Getting a handle to an app', function(t) {
+ setup(t, function(err, o) {
+ if (err) {
+ return t.end(err, 'Failed to setup');
+ }
+
+ var APP_NAME = getUniqueAppName();
+
+ var service = syncbase.newService(SERVICE_NAME);
+ var app = service.app(APP_NAME);
+
+ t.equals(app.name, APP_NAME, 'App name matches');
+ t.equals(app.fullName, vanadium.naming.join(SERVICE_NAME, APP_NAME),
+ 'App full name matches');
+
+ o.teardown(t.end);
+ });
+});
+
+test('Creating and listing apps', function(t) {
+ setup(t, function(err, o) {
+ if (err) {
+ return t.end(err, 'Failed to setup');
+ }
+
+ var service = syncbase.newService(SERVICE_NAME);
+
+ // Create multiple apps
+ var expectedAppNames = [
+ getUniqueAppName(),
+ getUniqueAppName(),
+ getUniqueAppName()
+ ];
+
+ createAppsAndVerifyExistance(t, service, o.ctx, expectedAppNames,
+ function() {
+ o.teardown(t.end);
+ }
+ );
+ });
+});
+
+test('Deleting an app', function(t) {
+ setup(t, function(err, o) {
+ if (err) {
+ return t.end(err);
+ }
+
+ var service = syncbase.newService(SERVICE_NAME);
+ var appName = getUniqueAppName();
+
+ createAppsAndVerifyExistance(t, service, o.ctx, [appName], deleteApp);
+
+ function deleteApp() {
+ service.app(appName).delete(o.ctx, verifyItNoLongerExists);
+ }
+
+ function verifyItNoLongerExists(err) {
+ if (err) {
+ t.fail(err, 'Failed to delete app');
+ return o.teardown(t.end);
+ }
+
+ service.listApps(o.ctx, function(err, apps) {
+ if (err) {
+ t.fail(err, 'Failed to list apps');
+ return o.teardown(t.end);
+ }
+
+ t.ok(apps.indexOf(appName) < 0, 'App is no longer listed');
+ return o.teardown(t.end);
+ });
+ }
+ });
+});
+
+test('Getting permissions of an app', function(t) {
+ setup(t, function(err, o) {
+ if (err) {
+ return t.end(err);
+ }
+
+ var service = syncbase.newService(SERVICE_NAME);
+ var appName = getUniqueAppName();
+
+ createAppsAndVerifyExistance(t, service, o.ctx, [appName], getPermissions);
+
+ function getPermissions() {
+ service.app(appName).getPermissions(o.ctx, verifyPermissions);
+ }
+
+ function verifyPermissions(err, perms, version) {
+ if (err) {
+ t.fail(err, 'Failed to get permissions for app');
+ return o.teardown(t.end);
+ }
+
+ t.equal(perms.size, DEFAULT_PERMISSIONS.size,
+ 'Permissions size matches');
+ DEFAULT_PERMISSIONS.forEach(function(value, key) {
+ t.deepEqual(perms.get(key), value, 'Permission value matches');
+ });
+ t.equal(version, '0', 'Version matches');
+
+ return o.teardown(t.end);
+ }
+ });
+});
+
+test('Setting permissions of an app', function(t) {
+ setup(t, function(err, o) {
+ if (err) {
+ return t.end(err);
+ }
+
+ var service = syncbase.newService(SERVICE_NAME);
+ var appName = getUniqueAppName();
+ var NEW_PERMS = new Map([
+ ['Read', {
+ 'in': ['...', 'canRead'],
+ 'notIn': ['cantRead']
+ }],
+ ['Write', {
+ 'in': ['...', 'canWrite'],
+ 'notIn': ['cantWrite']
+ }],
+ ['Admin', {
+ 'in': ['...', 'canAdmin'],
+ 'notIn': ['cantAdmin']
+ }]
+ ]);
+
+ createAppsAndVerifyExistance(t, service, o.ctx, [appName], setPermissions);
+
+ function setPermissions() {
+ service.app(appName)
+ .setPermissions(o.ctx, NEW_PERMS, '0', getPermissions);
+ }
+
+ function getPermissions(err) {
+ if (err) {
+ t.fail(err, 'Failed to set permissions for app');
+ return o.teardown(t.end);
+ }
+ service.app(appName).getPermissions(o.ctx, verifyPermissions);
+ }
+
+ function verifyPermissions(err, perms, version) {
+ if (err) {
+ t.fail(err, 'Failed to get permissions for app');
+ return o.teardown(t.end);
+ }
+
+ t.equal(perms.size, NEW_PERMS.size,
+ 'Permissions size matches');
+ NEW_PERMS.forEach(function(value, key) {
+ t.deepEqual(perms.get(key), value, 'Permission value matches');
+ });
+ // Version should have been incremented after setPermission call
+ t.equal(version, '1', 'Version matches');
+
+ return o.teardown(t.end);
+ }
+ });
+});
+
+// Helper function that creates bunch apps in parallel and calls the callback
+// when all are created.
+function createAppsAndVerifyExistance(t, service, ctx, appNames, cb) {
+ async.parallel(create(), verify);
+
+ // Returns an array of functions that create apps for the given appNames.
+ function create() {
+ return appNames.map(function(appName) {
+ return function(callback) {
+ service.app(appName).create(ctx, DEFAULT_PERMISSIONS, callback);
+ };
+ });
+ }
+
+ function verify(err) {
+ if (err) {
+ t.fail('Failed to create apps');
+ return cb(err);
+ }
+
+ service.listApps(ctx, verifyResults);
+
+ function verifyResults(err, apps) {
+ if (err) {
+ t.fail(err, 'Failed to list apps');
+ return cb(err);
+ }
+
+ var matchCounter = 0;
+ appNames.forEach(function(appName) {
+ if (apps.indexOf(appName) >= 0) {
+ matchCounter++;
+ }
+ });
+
+ var diff = appNames.length - matchCounter;
+ if (diff === 0) {
+ t.pass('All ' + matchCounter + ' expected app name(s) were listed');
+ return cb();
+ } else {
+ var failedErr = new Error(
+ 'Some (' + diff + ') expected app name(s) were not listed'
+ );
+ t.fail(failedErr);
+ return cb(failedErr);
+ }
+ }
+ }
+}
+
+// Helper function to create a Vanadium runtime and context.
+function setup(t, cb) {
+ vanadium.init(function(err, rt) {
+ if (err) {
+ return cb(err);
+ }
+
+ var ctx = rt.getContext();
+ var runtime = runtime;
+
+ function teardown(cb) {
+ rt.close(function(err) {
+ if (err) {
+ t.fail('Failed to close the runtime');
+ return cb(err);
+ }
+
+ return cb();
+ });
+ }
+
+ cb(null, {
+ ctx: ctx,
+ runtime: runtime,
+ teardown: teardown
+ });
+ });
+}
\ No newline at end of file
diff --git a/test/integration/test-syncbase-rpcs.js b/test/integration/test-syncbase-rpcs.js
deleted file mode 100644
index c2543e1..0000000
--- a/test/integration/test-syncbase-rpcs.js
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-var test = require('prova');
-var vanadium = require('vanadium');
-
-// TODO(nlacasse): These tests make RPCs directly to the syncbased server.
-// Most users should use the syncbase client library instead of making RPCs
-// directly. Get rid of these tests once we have tests that excercise the
-// client library.
-
-// Helper function to create a Vanadium runtime, context, and client.
-function setup(t, cb) {
- vanadium.init(function(err, rt) {
- if (err) {
- return cb(err);
- }
-
- var ctx = rt.getContext();
- var client = rt.newClient();
-
- function teardown(cb) {
- rt.close(function(err) {
- t.error(err, 'rt.close should not error');
- cb(null);
- });
- }
-
- cb(null, {
- ctx: ctx,
- client: client,
- rt: rt,
- teardown: teardown
- });
- });
-}
-
-test('test bindTo syncbased', function(t) {
- setup(t, function(err, o) {
- if (err) {
- return t.end(err);
- }
-
- o.client.bindTo(o.ctx, 'test/syncbased', function(err, syncbase) {
- if (err) {
- t.error(err);
- return o.teardown(t.end);
- }
-
- t.ok(syncbase, 'syncbase service is defined');
- o.teardown(t.end);
- });
- });
-});
-
-test('test create app', function(t) {
- setup(t, function(err, o) {
- if (err) {
- return t.end(err);
- }
-
- var appName = 'myCoolApp';
-
- o.client.bindTo(o.ctx, 'test/syncbased/' + appName, function(err, app) {
- if (err) {
- t.error(err);
- return o.teardown(t.end);
- }
-
- // TODO(nlacasse): Test with a real permissions map.
- var perms = new Map();
-
- app.create(o.ctx, perms, function(err) {
- t.error(err);
- o.teardown(t.end);
- });
- });
- });
-});