swift: Port golang's syncbase/model.go to Swift

Currently only the interface are ported -- the accompanying CGO is
not part of this CL.

However this CL does include the first stab at JSON support and an API
around it for Syncbase's get/put.

Change-Id: I8cc69fd5bc6298919bfa3f3fad10c876295619fc
diff --git a/lib/Syncbase/Batch.swift b/lib/Syncbase/Batch.swift
new file mode 100644
index 0000000..6640b7d
--- /dev/null
+++ b/lib/Syncbase/Batch.swift
@@ -0,0 +1,50 @@
+// 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.
+
+import Foundation
+
+internal class BatchHandle {
+}
+
+public enum Batch {
+  public typealias BatchCompletionHandler = SyncbaseError? -> Void
+  public typealias Operation = Void -> BatchCompletionHandler
+
+  /**
+   Runs the given batch operation, managing retries and BatchDatabase's commit() and abort()s.
+
+   - Parameter db:      database on which the batch operation is to be performed
+   - Parameter opts:    batch configuration
+   - Parameter op:      batch operation
+   - Parameter completionHandler:      future result called when runInBatch finishes
+   */
+  public static func runInBatch(db: Database, opts: BatchOptions, op: Operation, completionHandler: BatchCompletionHandler) {
+    preconditionFailure("stub")
+  }
+}
+
+public struct BatchOptions {
+  /// Arbitrary string, typically used to describe the intent behind a batch.
+  /// Hints are surfaced to clients during conflict resolution.
+  /// TODO(sadovsky): Use "any" here?
+  public let hint: String? = nil
+
+  /// ReadOnly specifies whether the batch should allow writes.
+  /// If ReadOnly is set to true, Abort() should be used to release any resources
+  /// associated with this batch (though it is not strictly required), and
+  /// Commit() will always fail.
+  public let readOnly: Bool = false
+}
+
+public protocol BatchDatabase: DatabaseHandle {
+  /// Commit persists the pending changes to the database.
+  /// If the batch is readonly, Commit() will fail with ErrReadOnlyBatch; Abort()
+  /// should be used instead.
+  func commit() throws
+
+  /// Abort notifies the server that any pending changes can be discarded.
+  /// It is not strictly required, but it may allow the server to release locks
+  /// or other resources sooner than if it was not called.
+  func abort() throws
+}
diff --git a/lib/Syncbase/Blob.swift b/lib/Syncbase/Blob.swift
new file mode 100644
index 0000000..9562fe2
--- /dev/null
+++ b/lib/Syncbase/Blob.swift
@@ -0,0 +1,135 @@
+// 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.
+
+import Foundation
+
+/// BlobRef is a reference to a blob.
+public typealias BlobRef = String
+
+public enum BlobDevType: Int {
+  /// Blobs migrate toward servers, which store them.  (example: server in cloud)
+  case BlobDevTypeServer = 0
+  /// Ordinary devices (example: laptop)
+  case BlobDevTypeNormal = 1
+  /// Blobs migrate from leaves, which have less storage (examples: a camera, phone)
+  case BlobDevTypeLeaf = 2
+}
+
+let kNullBlobRef = BlobRef("")
+
+/// Blob is the interface for a Blob in the store.
+public protocol Blob {
+  /// Ref returns Syncbase's BlobRef for this blob.
+  func ref() -> BlobRef
+
+  /// Put appends the byte stream to the blob.
+  func put() throws -> BlobWriter
+
+  /// Commit marks the blob as immutable.
+  func commit() throws
+
+  /// Size returns the count of bytes written as part of the blob
+  /// (committed or uncommitted).
+  func size() throws -> Int64
+
+  /// Delete locally deletes the blob (committed or uncommitted).
+  func delete() throws
+
+  /// Get returns the byte stream from a committed blob starting at offset.
+  func get(offset: Int64) throws -> BlobReader
+
+  /// Fetch initiates fetching a blob if not locally found. priority
+  /// controls the network priority of the blob. Higher priority blobs are
+  /// fetched before the lower priority ones. However an ongoing blob
+  /// transfer is not interrupted. Status updates are streamed back to the
+  /// client as fetch is in progress.
+  func fetch(priority: UInt64) throws -> BlobStatus
+
+  /// Pin locally pins the blob so that it is not evicted.
+  func pin() throws
+
+  /// Unpin locally unpins the blob so that it can be evicted if needed.
+  func unpin() throws
+
+  /// Keep locally caches the blob with the specified rank. Lower
+  /// ranked blobs are more eagerly evicted.
+  func keep(rank: UInt64) throws
+}
+
+/// BlobWriter is an interface for putting a blob.
+public protocol BlobWriter {
+  /// Send places the bytes given by the client onto the output
+  /// stream. Returns throwss encountered while sending. Blocks if there is
+  /// no buffer space.
+  func Send(data: NSData) throws
+
+  /// Close indicates that no more bytes will be sent.
+  func Close() throws
+}
+
+/// BlobReader is an interface for getting a blob.
+public protocol BlobReader {
+  /// Advance() stages bytes so that they may be retrieved via
+  /// Value(). Returns true iff there are bytes to retrieve. Advance() must
+  /// be called before Value() is called. The caller is expected to read
+  /// until Advance() returns false, or to call Cancel().
+  func advance() -> Bool
+
+  /// Value() returns the bytes that were staged by Advance(). May panic if
+  /// Advance() returned false or was not called. Never blocks.
+  func value() -> NSData
+
+  /// Err() returns any throws encountered by Advance. Never blocks.
+  func err() throws
+
+  /// Cancel notifies the stream provider that it can stop producing
+  /// elements.  The client must call Cancel if it does not iterate through
+  /// all elements (i.e. until Advance returns false). Cancel is idempotent
+  /// and can be called concurrently with a goroutine that is iterating via
+  /// Advance.  Cancel causes Advance to subsequently return false. Cancel
+  /// does not block.
+  func cancel()
+}
+
+/// BlobStatus is an interface for getting the status of a blob transfer.
+public protocol BlobStatus {
+  /// Advance() stages an item so that it may be retrieved via
+  /// Value(). Returns true iff there are items to retrieve. Advance() must
+  /// be called before Value() is called. The caller is expected to read
+  /// until Advance() returns false, or to call Cancel().
+  func advance() -> Bool
+
+  /// Value() returns the item that was staged by Advance(). May panic if
+  /// Advance() returned false or was not called. Never blocks.
+  func value() -> BlobFetchStatus
+
+  /// Err() returns any throws encountered by Advance. Never blocks.
+  func err() -> SyncbaseError?
+
+  /// Cancel notifies the stream provider that it can stop producing
+  /// elements.  The client must call Cancel if it does not iterate through
+  /// all elements (i.e. until Advance returns false). Cancel is idempotent
+  /// and can be called concurrently with a goroutine that is iterating via
+  /// Advance.  Cancel causes Advance to subsequently return false. Cancel
+  /// does not block.
+  func cancel()
+}
+
+//// BlobFetchStatus describes the progress of an asynchronous blob fetch.
+public struct BlobFetchStatus {
+  //// State of the blob fetch request.
+  public let state: BlobFetchState
+  //// Total number of bytes received.
+  public let received: Int64
+  //// Blob size.
+  public let total: Int64
+}
+
+public enum BlobFetchState: Int {
+  case BlobFetchStatePending = 0
+  case BlobFetchStateLocating
+  case BlobFetchStateFetching
+  case BlobFetchStateDone
+}
+
diff --git a/lib/Syncbase/Collection.swift b/lib/Syncbase/Collection.swift
new file mode 100644
index 0000000..a889704
--- /dev/null
+++ b/lib/Syncbase/Collection.swift
@@ -0,0 +1,110 @@
+/// 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.
+
+import Foundation
+
+/// CollectionRow encapsulates a collection id and row key or row prefix.
+public struct CollectionRow {
+  let collectionId: CollectionId
+  let row: String
+}
+
+/// SyncgroupMemberInfo contains per-member metadata.
+public struct SyncgroupMemberInfo {
+  let syncPriority: UInt8
+  let blobDevType: BlobDevType /// See BlobDevType* constants.
+}
+
+/// Collection represents a set of Rows.
+///
+/// TODO(sadovsky): Currently we provide Get/Put/Delete methods on both
+/// Collection and Row, because we're not sure which will feel more natural.
+/// Eventually, we'll need to pick one.
+public protocol Collection {
+  /// Id returns the id of this Collection.
+  var collectionId: CollectionId { get }
+
+  /// FullName returns the object name (encoded) of this Collection.
+  var fullName: String { get }
+
+  /// Exists returns true only if this Collection exists. Insufficient
+  /// permissions cause Exists to return false instead of an throws.
+  /// TODO(ivanpi): Exists may fail with an throws if higher levels of hierarchy
+  /// do not exist.
+  func exists() throws -> Bool
+
+  /// Create creates this Collection.
+  /// TODO(sadovsky): Specify what happens if perms is nil.
+  func create(permissions: Permissions) throws
+
+  /// Destroy destroys this Collection, permanently removing all of its data.
+  /// TODO(sadovsky): Specify what happens to syncgroups.
+  func destroy() throws
+
+  /// GetPermissions returns the current Permissions for the Collection.
+  /// The Read bit on the ACL does not affect who this Collection's rows are
+  /// synced to; all members of syncgroups that include this Collection will
+  /// receive the rows in this Collection. It only determines which clients
+  /// are allowed to retrieve the value using a Syncbase RPC.
+  func getPermissions() throws -> Permissions
+
+  /// SetPermissions replaces the current Permissions for the Collection.
+  func setPermissions(permissions: Permissions) throws
+
+  /// Row returns the Row with the given key.
+  func row(key: String) -> Row
+
+  /**
+   Get loads the value stored under the given key into inout parameter value.
+
+   Sets value to nil if nothing is stored undeder the given key.
+
+   By passing the typed target output value using inout, get is able to cast the value to the
+   desired type automatically. If the types do not match, an exception is thrown.
+
+   For example:
+
+   ```
+   /// Using inout
+   var isRed: Bool?
+   try collection.get("isRed", &isRed)
+
+   /// Using return value
+   let isRed: Bool = try collection.get("isRed")
+   ```
+   */
+  func get<T: SyncbaseJsonConvertible>(key: String, inout value: T?) throws
+
+  /// Get loads the value stored under the given key.
+  func get<T: SyncbaseJsonConvertible>(key: String) throws -> T?
+
+  /// Put writes the given value to this Collection under the given key.
+  func put(key: String, value: SyncbaseJsonConvertible) throws
+
+  /// Delete deletes the row for the given key.
+  func delete(key: String) throws
+
+  /// DeleteRange deletes all rows in the given half-open range [start, limit).
+  /// If limit is "", all rows with keys >= start are included.
+  /// TODO(sadovsky): Document how this deletion is considered during conflict
+  /// detection: is it considered as a range deletion, or as a bunch of point
+  /// deletions?
+  /// See helpers Prefix(), Range(), SingleRow().
+  func deleteRange(r: RowRange) throws
+
+  /// Scan returns all rows in the given half-open range [start, limit). If limit
+  /// is "", all rows with keys >= start are included.
+  /// Concurrency semantics: It is legal to perform writes concurrently with
+  /// Scan. The returned stream reads from a consistent snapshot taken at the
+  /// time of the RPC (or at the time of BeginBatch, if in a batch), and will not
+  /// reflect subsequent writes to keys not yet reached by the stream.
+  /// See helpers Prefix(), Range(), SingleRow().
+  func scan(r: RowRange) -> ScanStream
+}
+
+/// Returns the decoded value, or throws an error if the value could not be decoded.
+public typealias GetValueFromScanStream = () throws -> SyncbaseJsonConvertible
+
+/// Stream resulting from a scan on a scollection for a given row range.
+public typealias ScanStream = AnonymousStream<(String, GetValueFromScanStream)>
diff --git a/lib/Syncbase/Database.swift b/lib/Syncbase/Database.swift
new file mode 100644
index 0000000..d6801e4
--- /dev/null
+++ b/lib/Syncbase/Database.swift
@@ -0,0 +1,107 @@
+// 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.
+
+import Foundation
+
+/// DatabaseHandle is the set of methods that work both with and without a batch.
+/// It allows clients to pass the handle to helper methods that are batch-agnostic.
+public protocol DatabaseHandle {
+  /// Id returns the id of this DatabaseHandle..
+  var databaseId: DatabaseId { get }
+
+  /// FullName returns the object name (encoded) of this DatabaseHandle.
+  var fullName: String { get }
+
+  /// Collection returns the Collection with the given relative name.
+  /// The user blessing is derived from the context.
+  func collection(name: String) -> Collection?
+
+  /// CollectionForId returns the Collection with the given user blessing and name.
+  func collection(collectionId: CollectionId) -> Collection?
+
+  /// ListCollections returns a list of all Collection ids that the caller is
+  /// allowed to see. The list is sorted by blessing, then by name.
+  func listCollections() throws -> [CollectionId]
+
+  /// Returns a ResumeMarker that points to the current end of the event log.
+  func getResumeMarker() throws -> ResumeMarker
+}
+
+public protocol Database: DatabaseHandle, AccessController {
+  /// Create creates this Database.
+  /// TODO(sadovsky): Specify what happens if perms is nil.
+  func create(permissions: Permissions?) throws
+
+  /// Destroy destroys this Database, permanently removing all of its data.
+  /// TODO(sadovsky): Specify what happens to syncgroups.
+  func destroy() throws
+
+  // Exists returns true only if this Database exists. Insufficient permissions
+  // cause Exists to return false instead of an error.
+  func exists() throws -> Bool
+
+  /** BeginBatch creates a new batch. Instead of calling this function directly,
+   clients are encouraged to use the RunInBatch() helper function, which
+   detects "concurrent batch" errors and handles retries internally.
+
+   Default concurrency semantics:
+
+   - Reads (e.g. gets, scans) inside a batch operate over a consistent
+   snapshot taken during BeginBatch(), and will see the effects of prior
+   writes performed inside the batch.
+
+   - Commit() may fail with ErrConcurrentBatch, indicating that after
+   BeginBatch() but before Commit(), some concurrent routine wrote to a key
+   that matches a key or row-range read inside this batch.
+
+   - Other methods will never fail with error ErrConcurrentBatch, even if it
+   is known that Commit() will fail with this error.
+
+   Once a batch has been committed or aborted, subsequent method calls will
+   fail with no effect.
+
+   Concurrency semantics can be configured using BatchOptions.
+   TODO(sadovsky): Use varargs for options.
+   */
+  func beginBatch(options: BatchOptions?) throws -> BatchDatabase
+
+  /// Watch allows a client to watch for updates to the database. For each watch
+  /// request, the client will receive a reliable stream of watch events without
+  /// reordering. See watch.GlobWatcher for a detailed explanation of the
+  /// behavior.
+  ///
+  /// If a nil ResumeMarker is provided, the WatchStream will begin with a Change
+  /// batch containing the initial state. Otherwise, the WatchStream will contain
+  /// only changes since the provided ResumeMarker.
+  ///
+  /// TODO(sadovsky): Watch should return just a WatchStream, similar to how Scan
+  /// returns just a ScanStream.
+  func watch(collection: CollectionId, prefix: String, resumeMarker: ResumeMarker?) throws -> WatchStream
+
+  /// Syncgroup returns a handle to the syncgroup with the given name.
+  func syncgroup(sgName: String) -> Syncgroup
+
+  /// GetSyncgroupNames returns the names of all syncgroups attached to this
+  /// database.
+  /// TODO(sadovsky): Rename to ListSyncgroups, for parity with ListDatabases.
+  func getSyncgroupNames() throws -> [String]
+
+  /// CreateBlob creates a new blob and returns a handle to it.
+  func createBlob() throws -> Blob
+
+  /// Blob returns a handle to the blob with the given BlobRef.
+  func blob(blobRef: BlobRef) -> Blob
+
+  /// PauseSync pauses sync for this database. Incoming sync, as well as outgoing
+  /// sync of subsequent writes, will be disabled until ResumeSync is called.
+  /// PauseSync is idempotent.
+  func pauseSync() throws
+
+  /// ResumeSync resumes sync for this database. ResumeSync is idempotent.
+  func resumeSync() throws
+
+  /// Close cleans up any state associated with this database handle, including
+  /// closing the conflict resolution stream (if open).
+  func close()
+}
diff --git a/lib/Syncbase/Errors.swift b/lib/Syncbase/Errors.swift
new file mode 100644
index 0000000..95bd8ac
--- /dev/null
+++ b/lib/Syncbase/Errors.swift
@@ -0,0 +1,34 @@
+// 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.
+
+import Foundation
+
+public enum SyncbaseError: ErrorType, CustomStringConvertible {
+  case NotInDevMode
+  case UnknownBatch
+  case NotBoundToBatch
+  case ReadOnlyBatch
+  case ConcurrentBatch
+  case BlobNotCommitted
+  case SyncgroupJoinFailed
+  case BadExecStreamHeader
+  case InvalidName(name: String)
+  case CorruptDatabase(path: String)
+
+  public var description: String {
+    switch (self) {
+    case NotInDevMode: return "not running with --dev=true"
+    case UnknownBatch: return "unknown batch, perhaps the server restarted"
+    case NotBoundToBatch: return "not bound to batch"
+    case ReadOnlyBatch: return "batch is read-only"
+    case ConcurrentBatch: return "concurrent batch"
+    case BlobNotCommitted: return "blob is not yet committed"
+    case SyncgroupJoinFailed: return "syncgroup join failed"
+    case BadExecStreamHeader: return "Exec stream header improperly formatted"
+    case InvalidName(let name): return "invalid name: \(name)"
+    case CorruptDatabase(let path):
+      return "database corrupt, moved to path \(path); client must create a new database"
+    }
+  }
+}
\ No newline at end of file
diff --git a/lib/Syncbase/Id.swift b/lib/Syncbase/Id.swift
new file mode 100644
index 0000000..aefec42
--- /dev/null
+++ b/lib/Syncbase/Id.swift
@@ -0,0 +1,15 @@
+// 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.
+
+import Foundation
+
+public struct DatabaseId {
+  let name: String
+  let blessing: String
+}
+
+public struct CollectionId {
+  let name: String
+  let blessing: String
+}
diff --git a/lib/Syncbase/Marshal.swift b/lib/Syncbase/Marshal.swift
new file mode 100644
index 0000000..b9c74dc
--- /dev/null
+++ b/lib/Syncbase/Marshal.swift
@@ -0,0 +1,201 @@
+// 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.
+
+import Foundation
+
+public enum JsonErrors: ErrorType {
+  case ArrayContainsInvalidTypes
+  case DictionaryContainsInvalidTypes
+  case CastError(value: Any, target: Any)
+  case EncodingError
+}
+
+public enum JsonDataType: Int {
+  case Bool = 0,
+    Int, Int8, Int16, Int32, Int64,
+    UInt, UInt8, UInt16, UInt32, UInt64,
+    Float, Double,
+    String,
+    Array, Dictionary,
+    RawJson
+}
+
+public protocol SyncbaseJsonConvertible {
+  func toSyncbaseJson() throws -> (NSData, JsonDataType)
+}
+
+/// Serializes primitives to JSON using Apple's NSJSONSerializer. This function gets around
+/// Apple's limitation of requiring top
+private func hackNSJsonSerializer(obj: AnyObject) throws -> NSData {
+  var data: NSData?
+  try SyncbaseUtil.catchObjcException {
+    data = try? NSJSONSerialization.dataWithJSONObject([obj], options: [])
+  }
+  // Hack of first and last runes, which also happen to be single byte UTF8s
+  guard data != nil else {
+    throw JsonErrors.EncodingError
+  }
+  return data!.subdataWithRange(NSMakeRange(1, data!.length - 2))
+}
+
+extension Bool: SyncbaseJsonConvertible {
+  public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+    return (try hackNSJsonSerializer(self), JsonDataType.Bool)
+  }
+}
+
+// Wish we could put an extension on a protocol like IntegerLiterableConvertable or IntegerType
+// but we can't as of Swift 2.2, so we enumerate all the possiblities
+
+extension Int: SyncbaseJsonConvertible {
+  public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+    return (try hackNSJsonSerializer(self), JsonDataType.Int)
+  }
+}
+
+extension Int8: SyncbaseJsonConvertible {
+  public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+    return (try hackNSJsonSerializer(Int(self)), JsonDataType.Int8)
+  }
+}
+
+extension Int16: SyncbaseJsonConvertible {
+  public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+    return (try hackNSJsonSerializer(Int(self)), JsonDataType.Int16)
+  }
+}
+
+extension Int32: SyncbaseJsonConvertible {
+  public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+    return (try hackNSJsonSerializer(Int(self)), JsonDataType.Int32)
+  }
+}
+
+extension Int64: SyncbaseJsonConvertible {
+  public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+    return (try hackNSJsonSerializer(NSNumber(longLong: self)), JsonDataType.Int64)
+  }
+}
+
+extension UInt: SyncbaseJsonConvertible {
+  public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+    return (try hackNSJsonSerializer(self), JsonDataType.UInt)
+  }
+}
+
+extension UInt8: SyncbaseJsonConvertible {
+  public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+    return (try hackNSJsonSerializer(UInt(self)), JsonDataType.UInt8)
+  }
+}
+
+extension UInt16: SyncbaseJsonConvertible {
+  public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+    return (try hackNSJsonSerializer(UInt(self)), JsonDataType.UInt16)
+  }
+}
+
+extension UInt32: SyncbaseJsonConvertible {
+  public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+    return (try hackNSJsonSerializer(UInt(self)), JsonDataType.UInt32)
+  }
+}
+
+extension UInt64: SyncbaseJsonConvertible {
+  public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+    return (try hackNSJsonSerializer(NSNumber(unsignedLongLong: self)), JsonDataType.UInt64)
+  }
+}
+
+extension Float: SyncbaseJsonConvertible {
+  public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+    return (try hackNSJsonSerializer(self), JsonDataType.Float)
+  }
+}
+
+extension Double: SyncbaseJsonConvertible {
+  public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+    return (try hackNSJsonSerializer(self), JsonDataType.Double)
+  }
+}
+
+extension String: SyncbaseJsonConvertible {
+  public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+    return (try hackNSJsonSerializer(self), JsonDataType.String)
+  }
+}
+
+// Wish we could do this:
+// extension Array : SyncbaseJsonConvertible where Element: AnyObject {
+// but it's not valid Swift 2.2
+
+extension Array where Element: AnyObject {
+  public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+    return (try! NSJSONSerialization.dataWithJSONObject(self, options: []), JsonDataType.Array)
+  }
+}
+
+extension NSArray: SyncbaseJsonConvertible {
+  public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+    return (try! NSJSONSerialization.dataWithJSONObject(self, options: []), JsonDataType.Array)
+  }
+}
+
+extension NSDictionary: SyncbaseJsonConvertible {
+  public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+    return (try! NSJSONSerialization.dataWithJSONObject(self, options: []), JsonDataType.Dictionary)
+  }
+}
+
+// Annoyingly we can't directly cast an Array that we have no type information on into an AnyObject
+// or similarly the same for Dictionary. So instead we're forced to copy all the elements and test
+// all individual types inside.
+extension Array: SyncbaseJsonConvertible {
+  public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+    let copy: [AnyObject] = try self.map { elem in
+      guard let jsonable = elem as? AnyObject else {
+        throw JsonErrors.ArrayContainsInvalidTypes
+      }
+      return jsonable
+    }
+    var data: NSData?
+    try SyncbaseUtil.catchObjcException {
+      data = try? NSJSONSerialization.dataWithJSONObject(copy, options: [])
+    }
+    guard data != nil else {
+      throw JsonErrors.EncodingError
+    }
+    return (data!, JsonDataType.Array)
+  }
+}
+
+extension Dictionary: SyncbaseJsonConvertible {
+  public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+    let copy = NSMutableDictionary()
+    try self.forEach { (key, value) in
+      guard let strKey = key as? String else {
+        throw JsonErrors.DictionaryContainsInvalidTypes
+      }
+      guard let jsonable = value as? AnyObject else {
+        throw JsonErrors.DictionaryContainsInvalidTypes
+      }
+      copy[strKey] = jsonable
+    }
+
+    var data: NSData?
+    try SyncbaseUtil.catchObjcException {
+      data = try? NSJSONSerialization.dataWithJSONObject(copy, options: [])
+    }
+    guard data != nil else {
+      throw JsonErrors.EncodingError
+    }
+    return (data!, JsonDataType.Dictionary)
+  }
+}
+
+extension NSData: SyncbaseJsonConvertible {
+  public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+    return (self, JsonDataType.RawJson)
+  }
+}
diff --git a/lib/Syncbase/Permissions.swift b/lib/Syncbase/Permissions.swift
new file mode 100644
index 0000000..0c8b356
--- /dev/null
+++ b/lib/Syncbase/Permissions.swift
@@ -0,0 +1,64 @@
+// 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.
+
+import Foundation
+
+/// Permissions maps string tags to access lists specifying the blessings
+/// required to invoke methods with that tag.
+///
+/// These tags are meant to add a layer of interposition between the set of
+/// users (blessings, specifically) and the set of methods, much like "Roles" do
+/// in Role Based Access Control.
+/// (http://en.wikipedia.org/wiki/Role-based_access_control)
+public typealias Permissions = [String: BlessingPattern]
+
+/// BlessingPattern is a pattern that is matched by specific blessings.
+///
+/// A pattern can either be a blessing (slash-separated human-readable string)
+/// or a blessing ending in "/$". A pattern ending in "/$" is matched exactly
+/// by the blessing specified by the pattern string with the "/$" suffix stripped
+/// out. For example, the pattern "a/b/c/$" is matched by exactly by the blessing
+/// "a/b/c".
+///
+/// A pattern not ending in "/$" is more permissive, and is also matched by blessings
+/// that are extensions of the pattern (including the pattern itself). For example, the
+/// pattern "a/b/c" is matched by the blessings "a/b/c", "a/b/c/x", "a/b/c/x/y", etc.
+public typealias BlessingPattern = String
+
+public typealias PermissionsVersion = String
+
+/// AccessList represents a set of blessings that should be granted access.
+///
+/// See also: https://vanadium.github.io/glossary.html#access-list
+public struct AcccessList {
+  /// allowed denotes the set of blessings (represented as BlessingPatterns) that
+  /// should be granted access, unless blacklisted by an entry in notAllowed.
+  ///
+  /// For example:
+  /// allowed: {"alice:family"}
+  /// grants access to a principal that presents at least one of
+  /// "alice:family", "alice:family:friend", "alice:family:friend:spouse" etc.
+  /// as a blessing.
+  public let allowed: [BlessingPattern]
+
+  /// notAllowed denotes the set of blessings (and their delegates) that
+  /// have been explicitly blacklisted from the allowed set.
+  ///
+  /// For example:
+  /// allowed: {"alice:friend"}, notAllowed: {"alice:friend:bob"}
+  /// grants access to principals that present "alice:friend",
+  /// "alice:friend:carol" etc. but NOT to a principal that presents
+  /// "alice:friend:bob" or "alice:friend:bob:spouse" etc.
+  public let notAllowed: [BlessingPattern]
+}
+
+/// AccessController provides access control for various syncbase objects.
+public protocol AccessController {
+  /// setPermissions replaces the current Permissions for an object.
+  func setPermissions(perms: Permissions, version: PermissionsVersion) throws
+
+  /// GetPermissions returns the current Permissions for an object.
+  /// For detailed documentation, see Object.GetPermissions.
+  func getPermissions() throws -> (Permissions, PermissionsVersion)
+}
diff --git a/lib/Syncbase/Row.swift b/lib/Syncbase/Row.swift
new file mode 100644
index 0000000..556c7c8
--- /dev/null
+++ b/lib/Syncbase/Row.swift
@@ -0,0 +1,101 @@
+// 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.
+
+import Foundation
+
+public protocol Row {
+  /// Key returns the key for this Row.
+  var key: String { get }
+
+  /// FullName returns the object name (encoded) of this Row.
+  var fullName: String { get }
+
+  /// Exists returns true only if this Row exists. Insufficient permissions cause
+  /// Exists to return false instead of an error.
+  /// TODO(ivanpi): Exists may fail with an error if higher levels of hierarchy
+  /// do not exist.
+  func exists() throws -> Bool
+
+  /**
+   Get loads the value stored in this Row into the given value. If the given
+   value's type does not match the stored value's type, get will throw an error.
+
+   Expected usage:
+
+   ```
+   var isRed: Bool?
+   try row.get(&isRed)
+   ```
+   */
+  func get<T: SyncbaseJsonConvertible>(inout value: T?) throws
+
+  /**
+   Get returns the value stored in this Row. If the given value's type does not match the
+   requested generic type, get will throw an error.
+
+   Expected usage:
+
+   ```
+   let isRed: Bool? = try row.get()
+   ```
+   */
+  func get<T: SyncbaseJsonConvertible>() throws -> T?
+
+  /// Put writes the given value for this Row.
+  func put(value: SyncbaseJsonConvertible) throws
+
+  /// Delete deletes this Row.
+  func delete() throws
+}
+
+/// RowRange represents all rows with keys in [start, limit).
+/// If limit is "", all rows with keys >= start are included.
+public protocol RowRange {
+  var start: String { get }
+  /// If limit is "", all rows with keys >= start are included.
+  var limit: String { get }
+}
+
+/// StandardRowRange represents all rows with keys in [start, limit).
+/// If limit is "", all rows with keys >= start are included.
+public struct StandardRowRange: RowRange {
+  public let start: String
+  /// If limit is "", all rows with keys >= start are included.
+  public let limit: String
+}
+
+public struct SingleRow: RowRange {
+  public let row: String
+
+  public var start: String {
+    return row
+  }
+
+  public var limit: String {
+    return row + "\0"
+  }
+}
+
+/// PrefixRange represents all rows with keys that have some prefix.
+public struct PrefixRange: RowRange {
+  public let prefix: String
+
+  /// Returns the start of the row range for the given prefix.
+  public var start: String {
+    return prefix
+  }
+
+  public var limit: String {
+    var utf8 = Array(prefix.utf8)
+    while utf8.count > 0 {
+      if utf8.last! == 255 {
+        utf8.removeLast() // chop off what would otherwise be a trailing \x00
+      } else {
+        utf8[utf8.count - 1] += 1 // add 1
+        break // no carry
+      }
+    }
+    return String(bytes: utf8, encoding: NSUTF8StringEncoding)!
+  }
+}
diff --git a/lib/Syncbase/Security.swift b/lib/Syncbase/Security.swift
index 34e1d8d..83d6e92 100644
--- a/lib/Syncbase/Security.swift
+++ b/lib/Syncbase/Security.swift
@@ -8,10 +8,10 @@
 import VanadiumCore
 
 public protocol VanadiumBlesser {
-  func blessingsFromGoogle(googleOauthToken:String) -> Promise<NSData>
+  func blessingsFromGoogle(googleOauthToken: String) -> Promise<NSData>
 }
 
-enum VanadiumUrls : String, URLStringConvertible {
+enum VanadiumUrls: String, URLStringConvertible {
   case DevBlessings = "https://dev.v.io/auth/google/bless"
 
   var URLString: String {
@@ -19,30 +19,30 @@
   }
 }
 
-enum BlessingOutputFormat : String {
+enum BlessingOutputFormat: String {
   case Base64VOM = "base64vom"
 }
 
-public enum VanadiumBlesserError : ErrorType {
+public enum VanadiumBlesserError: ErrorType {
   case EmptyResult
-  case NotBase64Encoded(invalid:String)
+  case NotBase64Encoded(invalid: String)
 }
 
 public extension VanadiumBlesser {
-  public func blessingsFromGoogle(googleOauthToken:String) -> Promise<NSData> {
+  public func blessingsFromGoogle(googleOauthToken: String) -> Promise<NSData> {
     // Make sure the Syncbase instance exists, which inits V23
     let _ = Syncbase.instance
     let p = Promise<NSData>()
     do {
-      let params:[String:AnyObject] = [
-          "token": googleOauthToken,
-          "public_key": try VanadiumCore.Principal.publicKey(),
-          "output_format": BlessingOutputFormat.Base64VOM.rawValue]
+      let params: [String: AnyObject] = [
+        "token": googleOauthToken,
+        "public_key": try VanadiumCore.Principal.publicKey(),
+        "output_format": BlessingOutputFormat.Base64VOM.rawValue]
       let request = Alamofire.request(.GET,
-                        VanadiumUrls.DevBlessings,
-                        parameters: params,
-                        encoding: ParameterEncoding.URLEncodedInURL,
-                        headers:nil)
+        VanadiumUrls.DevBlessings,
+        parameters: params,
+        encoding: ParameterEncoding.URLEncodedInURL,
+        headers: nil)
       request.responseString { resp in
         guard let base64 = resp.result.value else {
           if let err = resp.result.error {
@@ -72,10 +72,10 @@
 }
 
 public protocol OAuthCredentials {
-  var oauthToken:String { get }
+  var oauthToken: String { get }
 }
 
-public struct GoogleCredentials : OAuthCredentials, VanadiumBlesser {
+public struct GoogleCredentials: OAuthCredentials, VanadiumBlesser {
   public let oauthToken: String
 
   public init(oauthToken: String) {
diff --git a/lib/Syncbase/Service.swift b/lib/Syncbase/Service.swift
new file mode 100644
index 0000000..d91508a
--- /dev/null
+++ b/lib/Syncbase/Service.swift
@@ -0,0 +1,22 @@
+// 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.
+
+import Foundation
+
+/// Service represents a Vanadium Syncbase service.
+public protocol Service: AccessController {
+  /// FullName returns the object name (encoded) of this Service.
+  var fullName: String { get }
+
+  /// DatabaseForId returns the Database with the given app blessing and name (from the Id struct).
+  /// This is equivalent to calling database(id.name, id.blessing)
+  func database(databaseId: DatabaseId) -> Database
+
+  /// DatabaseForId returns the Database with the given name and app blessing.
+  func database(name: String) throws -> Database
+
+  /// ListDatabases returns a list of all Database ids that the caller is allowed to see.
+  /// The list is sorted by blessing, then by name.
+  func listDatabases() throws -> [DatabaseId]
+}
diff --git a/lib/Syncbase/Stream.swift b/lib/Syncbase/Stream.swift
new file mode 100644
index 0000000..6d13773
--- /dev/null
+++ b/lib/Syncbase/Stream.swift
@@ -0,0 +1,72 @@
+// 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.
+
+import Foundation
+
+/// Base protocol for iterating through elements of unknown length.
+public protocol Stream: GeneratorType {
+  /// Err returns a non-nil error iff the stream encountered any errors. Err does
+  /// not block.
+  func err() -> SyncbaseError?
+
+  /// Cancel notifies the stream provider that it can stop producing elements.
+  /// The client must call Cancel if it does not iterate through all elements
+  /// (i.e. until Advance returns false). Cancel is idempotent and can be called
+  /// concurrently with a goroutine that is iterating via Advance.
+  /// Cancel causes Advance to subsequently return false. Cancel does not block.
+  mutating func cancel()
+}
+
+/// Typed-stream backed by anonymous callbacks
+public struct AnonymousStream<T>: Stream {
+  public typealias Element = T
+  public typealias FetchNextFunction = Void -> (T?, SyncbaseError?)
+  let fetchNextFunction: FetchNextFunction
+  public typealias CancelFunction = Void -> Void
+  let cancelFunction: CancelFunction
+  private var lastErr: SyncbaseError?
+  private var isCancelled: Bool = false
+  internal init(fetchNextFunction: FetchNextFunction, cancelFunction: CancelFunction) {
+    self.fetchNextFunction = fetchNextFunction
+    self.cancelFunction = cancelFunction
+  }
+
+  /// Advance to the next element and return it, or `nil` if no next
+  /// element exists.
+  ///
+  /// - Requires: `next()` has not been applied to a copy of `self`
+  /// since the copy was made, and no preceding call to `self.next()`
+  /// has returned `nil`.  Specific implementations of this protocol
+  /// are encouraged to respond to violations of this requirement by
+  /// calling `preconditionFailure("...")`.
+  public mutating func next() -> T? {
+    guard !isCancelled else {
+      return nil
+    }
+    let (result, err) = fetchNextFunction()
+    if let ret = result {
+      return ret
+    }
+    lastErr = err
+    return nil
+  }
+
+  /// Err returns a non-nil error iff the stream encountered any errors. Err does
+  /// not block.
+  public func err() -> SyncbaseError? {
+    return lastErr
+  }
+
+  /// Cancel notifies the stream provider that it can stop producing elements.
+  /// The client must call Cancel if it does not iterate through all elements
+  /// (i.e. until Advance returns false). Cancel is idempotent and can be called
+  /// concurrently with a goroutine that is iterating via Advance.
+  /// Cancel causes Advance to subsequently return false. Cancel does not block.
+  public mutating func cancel() {
+    if !isCancelled {
+      cancelFunction()
+      isCancelled = true
+    }
+  }
+}
diff --git a/lib/Syncbase/Syncbase.h b/lib/Syncbase/Syncbase.h
index 514ed51..8e90958 100644
--- a/lib/Syncbase/Syncbase.h
+++ b/lib/Syncbase/Syncbase.h
@@ -9,3 +9,5 @@
 
 //! Project version string for Syncbase.
 FOUNDATION_EXPORT const unsigned char SyncbaseVersionString[];
+
+#import "Util.h"
\ No newline at end of file
diff --git a/lib/Syncbase/Syncbase.swift b/lib/Syncbase/Syncbase.swift
index 66056f0..3681463 100644
--- a/lib/Syncbase/Syncbase.swift
+++ b/lib/Syncbase/Syncbase.swift
@@ -3,44 +3,80 @@
 // license that can be found in the LICENSE file.
 
 import Foundation
-
 import VanadiumCore
 
-public let instance = Syncbase.instance
+//public let instance = Syncbase.instance
 
-public class Syncbase {
-  private static var _instance: Syncbase? = nil
+public struct Syncbase: Service {
+  // TODO(zinman): Figure out what the local name is supposed to be be
+  private static let kLocalName = ""
+  public let fullName: String
+  private static var _localInstance: Syncbase? = nil
 
-  /// The singleton instance of Syncbase. This is the primary class that houses the simplified
-  /// API.
+  /// The singleton instance of Syncbase that represents the local store. This is the primary
+  /// Syncbase client you want to work with, unless you explicitly want to connect to a remote
+  /// Syncbase via RPC (then use the constructor with its object name passed)
   ///
   /// You won't be able to sync with anybody unless you grant yourself a blessing via the authorize
   /// method.
   public static var instance: Syncbase {
     get {
-      if (_instance == nil) {
+      if (_localInstance == nil) {
         do {
-          _instance = try Syncbase()
+          _localInstance = try Syncbase(fullName: kLocalName)
         } catch let err {
           VanadiumCore.log.warning("Couldn't instantiate an instance of Syncbase: \(err)")
         }
       }
-      return _instance!
+      return _localInstance!
     }
+
     set {
-      if (_instance != nil) {
-        fatalError("You cannot create another instance of V23")
+      if (_localInstance != nil) {
+        fatalError("You cannot create another local instance of Syncbase")
       }
-      _instance = newValue
+      _localInstance = newValue
     }
   }
 
-  /// Private constructor for V23.
-  private init() throws {
-    try V23.configure()
+  /**
+   Returns a new client handle to a syncbase service running at the given name. The common scenario
+   is to only use Syncbase.instance for the local store -- this constructor is used for connecting
+   to remote Syncbases.
+
+   - Parameter fullName: full (i.e., object) name of the syncbase service
+   */
+  public init(fullName: String) throws {
+    self.fullName = fullName
+    // STUB
   }
 
-  deinit {
+  /// Create a database using the relative name and user's blessings.
+  public func database(name: String) throws -> Database {
+    return database(DatabaseId(name: name, blessing: try Principal.blessingsDebugString()))
+  }
 
+  /// DatabaseForId returns the Database with the given app blessing and name (from the Id struct).
+  public func database(databaseId: DatabaseId) -> Database {
+    preconditionFailure("Implement me")
+  }
+
+  /// ListDatabases returns a list of all Database ids that the caller is allowed to see.
+  /// The list is sorted by blessing, then by name.
+  public func listDatabases() throws -> [DatabaseId] {
+    preconditionFailure("Implement me")
+  }
+}
+
+extension Syncbase: AccessController {
+  /// setPermissions replaces the current Permissions for an object.
+  public func setPermissions(perms: Permissions, version: PermissionsVersion) throws {
+    preconditionFailure("Implement me")
+  }
+
+  /// getPermissions returns the current Permissions for an object.
+  /// For detailed documentation, see Object.GetPermissions.
+  public func getPermissions() throws -> (Permissions, PermissionsVersion) {
+    preconditionFailure("Implement me")
   }
 }
diff --git a/lib/Syncbase/Syncgroup.swift b/lib/Syncbase/Syncgroup.swift
new file mode 100644
index 0000000..8ea8352
--- /dev/null
+++ b/lib/Syncbase/Syncgroup.swift
@@ -0,0 +1,89 @@
+// 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.
+
+import Foundation
+
+public struct VersionedSpec {
+  let spec: SyncgroupSpec
+  let version: String
+}
+
+public protocol Syncgroup {
+  /// Create creates a new syncgroup with the given spec.
+  ///
+  /// Requires: Client must have at least Read access on the Database; all
+  /// Collections specified in prefixes must exist; Client must have at least
+  /// Read access on each of the Collection ACLs.
+  func create(spec: SyncgroupSpec, myInfo: SyncgroupMemberInfo) throws
+
+  /// Join joins a syncgroup.
+  ///
+  /// Requires: Client must have at least Read access on the Database and on the
+  /// syncgroup ACL.
+  func join(myInfo: SyncgroupMemberInfo) throws -> SyncgroupSpec
+
+  /// Leave leaves the syncgroup. Previously synced data will continue
+  /// to be available.
+  ///
+  /// Requires: Client must have at least Read access on the Database.
+  func leave() throws
+
+  /// Destroy destroys the syncgroup. Previously synced data will
+  /// continue to be available to all members.
+  ///
+  /// Requires: Client must have at least Read access on the Database, and must
+  /// have Admin access on the syncgroup ACL.
+  func destroy() throws
+
+  /// Eject ejects a member from the syncgroup. The ejected member
+  /// will not be able to sync further, but will retain any data it has already
+  /// synced.
+  ///
+  /// Requires: Client must have at least Read access on the Database, and must
+  /// have Admin access on the syncgroup ACL.
+  func eject(member: String) throws
+
+  /// GetSpec gets the syncgroup spec. version allows for atomic
+  /// read-modify-write of the spec - see comment for SetSpec.
+  ///
+  /// Requires: Client must have at least Read access on the Database and on the
+  /// syncgroup ACL.
+  func getSpec() throws -> VersionedSpec
+
+  /// SetSpec sets the syncgroup spec. version may be either empty or
+  /// the value from a previous Get. If not empty, Set will only succeed if the
+  /// current version matches the specified one.
+  ///
+  /// Requires: Client must have at least Read access on the Database, and must
+  /// have Admin access on the syncgroup ACL.
+  func setSpec(versionedSpec: VersionedSpec) throws
+
+  /// GetMembers gets the info objects for members of the syncgroup.
+  ///
+  /// Requires: Client must have at least Read access on the Database and on the
+  /// syncgroup ACL.
+  func getMembers() throws -> [String: SyncgroupMemberInfo]
+}
+
+public struct SyncgroupSpec {
+  /// Human-readable description of this syncgroup.
+  let description: String
+  /// Permissions governing access to this syncgroup.
+  let permissions: Permissions
+  /// Data (collectionId-rowPrefix pairs) covered by this syncgroup.
+  let prefixes: [CollectionRow]
+  /// Mount tables at which to advertise this syncgroup, for rendezvous purposes.
+  /// (Note that in addition to these mount tables, Syncbase also uses
+  /// network-neighborhood-based discovery for rendezvous.)
+  /// We expect most clients to specify a single mount table, but we accept an
+  /// array of mount tables to permit the mount table to be changed over time
+  /// without disruption.
+  /// TODO(hpucha): Figure out a convention for advertising syncgroups in the
+  /// mount table.
+  let mountTables: [String]
+  /// Specifies the privacy of this syncgroup. More specifically, specifies
+  /// whether blobs in this syncgroup can be served to clients presenting
+  /// blobrefs obtained from other syncgroups.
+  let isPrivate: Bool
+}
diff --git a/lib/Syncbase/Util.h b/lib/Syncbase/Util.h
new file mode 100644
index 0000000..f5ddc42
--- /dev/null
+++ b/lib/Syncbase/Util.h
@@ -0,0 +1,9 @@
+// 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.
+
+#import <Foundation/Foundation.h>
+
+@interface SyncbaseUtil : NSObject
++ (BOOL) catchObjcException:(void (^ _Nonnull)())block error:(NSError * _Nullable * _Nonnull)error;
+@end
\ No newline at end of file
diff --git a/lib/Syncbase/Util.m b/lib/Syncbase/Util.m
new file mode 100644
index 0000000..750b0d5
--- /dev/null
+++ b/lib/Syncbase/Util.m
@@ -0,0 +1,24 @@
+// 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.
+
+#import "Util.h"
+
+@implementation SyncbaseUtil
+
++ (BOOL) catchObjcException:(void (^ _Nonnull)())block error:(NSError * _Nullable * _Nonnull)error {
+  @try {
+    block();
+    return true;
+  } @catch (NSException *exception) {
+    if (!error) return false;
+    *error = [NSError errorWithDomain:@"io.v.Syncbase"
+                                 code:-100
+                             userInfo:@{@"name": exception.name,
+                                        @"reason": exception.reason,
+                                        @"userInfo": exception.userInfo}];
+    return false;
+  }
+}
+
+@end
\ No newline at end of file
diff --git a/lib/Syncbase/Util.swift b/lib/Syncbase/Util.swift
new file mode 100644
index 0000000..4398139
--- /dev/null
+++ b/lib/Syncbase/Util.swift
@@ -0,0 +1,122 @@
+// 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.
+
+import Foundation
+
+internal extension NSNumber {
+  /// Returns true if value can be cast to NSNumber or NSNumber?
+  internal static func isNSNumber<V>(value: V) -> Bool {
+    // Can't do this:
+    // if value is NSNumber {
+    // because it will always succeed through type coercion, even if value is an Int.
+    // Instead we use the dynamic (runtime) type
+    let type = value.dynamicType
+    return type is NSNumber.Type || type is NSNumber?.Type
+  }
+
+  /// Returns true if target can be set/cast from this NSNumber without precision-loss or type
+  /// conversion. For example ```NSNumber.init(bool: true) as Float``` will return a Swift Float of
+  /// value 1, where as this function would return false as the types are unrelated -- only
+  /// casting as Bool would return true. This function checks for 32/64-bit correctness with types.
+  ///
+  /// Caveat: Currently this function makes no attempt to determine signed/unsigned correctness of
+  /// the underlying data, although this is sometimes knowable with NSNumber.objCType.
+  internal func isTargetCastable<T>(inout target: T?) -> Bool {
+    // Allow matching types of this size, and bigger. Signed and unsigned of same size are not allowed.
+    // Swift: https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html
+    // C types: https://developer.apple.com/library/ios/documentation/General/Conceptual/CocoaTouch64BitGuide/Major64-BitChanges/Major64-BitChanges.html
+    switch CFNumberGetType(self as CFNumberRef) {
+      // Obj-C bool is stored as Char, but if it's a @(YES) or @(NO) they are all a shared instance.
+    case .CharType where (unsafeAddressOf(self) == unsafeAddressOf(kCFBooleanFalse) ||
+      unsafeAddressOf(self) == unsafeAddressOf(kCFBooleanTrue)):
+      // We know we have a bool... make sure it's compatible
+      let type = target.dynamicType
+      guard type == Bool?.self || type == NSNumber?.self || type == AnyObject?.self || type == NSObject?.self else {
+        return false
+      }
+      // Handle fixed lengths on 32/64 bit
+    case .SInt8Type, .CharType:
+      let type = target.dynamicType
+      guard type == Int?.self || type == UInt?.self ||
+      type == Int8?.self || type == Int16?.self || type == Int32?.self || type == Int64?.self ||
+      type == UInt8?.self || type == UInt16?.self || type == UInt32?.self || type == UInt64?.self ||
+      type == CChar?.self || type == CShort?.self || type == CInt?.self || type == CLong?.self || type == CLongLong?.self ||
+      type == NSNumber?.self || type == AnyObject?.self || type == NSObject?.self else {
+        return false
+      }
+    case .SInt16Type, .ShortType:
+      let type = target.dynamicType
+      guard type == Int?.self || type == UInt?.self ||
+      type == Int16?.self || type == Int32?.self || type == Int64?.self ||
+      type == UInt16?.self || type == UInt32?.self || type == UInt64?.self ||
+      type == CShort?.self || type == CInt?.self || type == CLong?.self || type == CLongLong?.self ||
+      type == NSNumber?.self || type == AnyObject?.self || type == NSObject?.self else {
+        return false
+      }
+    case .SInt32Type, .IntType:
+      let type = target.dynamicType
+      guard type == Int?.self || type == UInt?.self ||
+      type == Int32?.self || type == Int64?.self ||
+      type == UInt32?.self || type == UInt64?.self ||
+      type == CInt?.self || type == CLong?.self || type == CLongLong?.self ||
+      type == NSNumber?.self || type == AnyObject?.self || type == NSObject?.self else {
+        return false
+      }
+    case .SInt64Type, .LongLongType:
+      let type = target.dynamicType
+      guard (type == Int?.self && sizeof(Int) == sizeof(CLongLong)) ||
+      (type == UInt?.self && sizeof(UInt) == sizeof(CLongLong)) ||
+      type == Int64?.self || type == UInt64?.self ||
+      type == CLongLong?.self ||
+      type == NSNumber?.self || type == AnyObject?.self || type == NSObject?.self else {
+        return false
+      }
+    case .Float32Type, .FloatType:
+      let type = target.dynamicType
+      guard type == Float?.self ||
+      type == Double?.self ||
+      type == Float32?.self ||
+      type == Float64?.self ||
+      type == CFloat?.self || type == CDouble?.self || type == CGFloat?.self ||
+      type == NSNumber?.self || type == AnyObject?.self || type == NSObject?.self else {
+        return false
+      }
+    case .Float64Type, .DoubleType: /* 64-bit IEEE 754 */
+      let type = target.dynamicType
+      guard type == Double?.self ||
+      type == Float64?.self ||
+      type == CDouble?.self || type == CGFloat?.self ||
+      type == NSNumber?.self || type == AnyObject?.self || type == NSObject?.self else {
+        return false
+      }
+
+      // Handle 32/64-bit types
+    case .LongType, .NSIntegerType:
+      let type = target.dynamicType
+      guard type == Int?.self || type == UInt?.self ||
+      (type == Int32?.self && sizeof(Int32) == sizeof(NSInteger)) ||
+      (type == UInt32?.self && sizeof(UInt32) == sizeof(NSInteger)) ||
+      type == Int64?.self || type == UInt64?.self ||
+      type == NSNumber?.self || type == AnyObject?.self || type == NSObject?.self ||
+      type == CLong?.self || type == CLongLong?.self else {
+        return false
+      }
+    case .CGFloatType:
+      let type = target.dynamicType
+      guard (type == Float?.self && sizeof(CGFloat) == sizeof(Float)) ||
+      type == Double?.self ||
+      (type == Float32?.self && sizeof(CGFloat) == sizeof(Float32)) ||
+      type == Float64?.self ||
+      type == NSNumber?.self || type == AnyObject?.self || type == NSObject?.self else {
+        return false
+      }
+      // Misc
+    case .CFIndexType:
+      guard target.dynamicType == CFIndex?.self else {
+        return false
+      }
+    }
+    return true
+  }
+}
diff --git a/lib/Syncbase/Vom.swift b/lib/Syncbase/Vom.swift
new file mode 100644
index 0000000..ad4489f
--- /dev/null
+++ b/lib/Syncbase/Vom.swift
@@ -0,0 +1,7 @@
+// 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.
+
+import Foundation
+
+typealias VomRawBytes = NSData
diff --git a/lib/Syncbase/Watch.swift b/lib/Syncbase/Watch.swift
new file mode 100644
index 0000000..3ec0ac6
--- /dev/null
+++ b/lib/Syncbase/Watch.swift
@@ -0,0 +1,48 @@
+// 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.
+
+import Foundation
+
+public struct ResumeMarker {
+  internal let data: [UInt8]
+}
+
+public struct WatchChange {
+  public enum ChangeType: Int {
+    case PutChange = 0, DeleteDelete
+  }
+
+  /// Collection is the id of the collection that contains the changed row.
+  let collectionId: CollectionId
+
+  /// Row is the key of the changed row.
+  let row: String
+
+  /// ChangeType describes the type of the change. If ChangeType is PutChange,
+  /// then the row exists in the collection, and Value can be called to obtain
+  /// the new value for this row. If ChangeType is DeleteChange, then the row was
+  /// removed from the collection.
+  let changeType: ChangeType
+
+  /// value is the new value for the row if the ChangeType is PutChange, or nil
+  /// otherwise.
+  let value: VomRawBytes
+
+  /// ResumeMarker provides a compact representation of all the messages that
+  /// have been received by the caller for the given Watch call.
+  /// This marker can be provided in the Request message to allow the caller
+  /// to resume the stream watching at a specific point without fetching the
+  /// initial state.
+  let resumeMarker: ResumeMarker
+
+  /// FromSync indicates whether the change came from sync. If FromSync is false,
+  /// then the change originated from the local device.
+  let isFromSync: Bool
+
+  /// If true, this WatchChange is followed by more WatchChanges that are in the
+  /// same batch as this WatchChange.
+  let isContinued: Bool
+}
+
+public typealias WatchStream = AnonymousStream<WatchChange>
diff --git a/lib/SyncbaseTests/JsonTests.swift b/lib/SyncbaseTests/JsonTests.swift
new file mode 100644
index 0000000..3e6eaf5
--- /dev/null
+++ b/lib/SyncbaseTests/JsonTests.swift
@@ -0,0 +1,99 @@
+//
+//  JsonTests.swift
+//  Vanadium
+//
+//  Created by zinman on 4/26/16.
+//  Copyright © 2016 Google, Inc. All rights reserved.
+//
+
+import XCTest
+@testable import Syncbase
+
+class JsonTests: XCTestCase {
+  // Emulate syncbase's put
+  func toJson(any:SyncbaseJsonConvertible) -> (NSData, JsonDataType) {
+    return try! any.toSyncbaseJson()
+  }
+
+  func toString(json:NSData) -> String {
+    return String(data:json, encoding: NSUTF8StringEncoding)!
+  }
+
+  func testBasicEncoding() {
+    var (data, type) = try! 5.toSyncbaseJson()
+    XCTAssertEqual(toString(data), "5")
+    XCTAssertEqual(type, JsonDataType.Int)
+
+    (data, type) = toJson(5)
+    XCTAssertEqual(toString(data), "5")
+    XCTAssertEqual(type, JsonDataType.Int)
+
+    (data, type) = try! true.toSyncbaseJson()
+    XCTAssertEqual(toString(data), "true")
+    XCTAssertEqual(type, JsonDataType.Bool)
+
+    (data, type) = try! false.toSyncbaseJson()
+    XCTAssertEqual(toString(data), "false")
+    XCTAssertEqual(type, JsonDataType.Bool)
+
+    (data, type) = try! Int8(5).toSyncbaseJson()
+    XCTAssertEqual(toString(data), "5")
+    XCTAssertEqual(type, JsonDataType.Int8)
+
+    (data, type) = try! Int16(5).toSyncbaseJson()
+    XCTAssertEqual(toString(data), "5")
+    XCTAssertEqual(type, JsonDataType.Int16)
+
+    (data, type) = try! Int32(5).toSyncbaseJson()
+    XCTAssertEqual(toString(data), "5")
+    XCTAssertEqual(type, JsonDataType.Int32)
+
+    (data, type) = try! Int64(5).toSyncbaseJson()
+    XCTAssertEqual(toString(data), "5")
+    XCTAssertEqual(type, JsonDataType.Int64)
+
+    (data, type) = try! Int64(Int64(Int32.max) + 10).toSyncbaseJson()
+    XCTAssertEqual(toString(data), "\(Int64(Int32.max) + 10)")
+    XCTAssertEqual(type, JsonDataType.Int64)
+
+    (data, type) = try! Float(5.0).toSyncbaseJson()
+    XCTAssertEqual(toString(data), "5") // 5 is ok here since we pass knowledge it's a float
+    XCTAssertEqual(type, JsonDataType.Float)
+
+    (data, type) = try! Double(5.1).toSyncbaseJson()
+    XCTAssertEqual(toString(data), "5.1") // 5 is ok here since we pass knowledge it's a float
+    XCTAssertEqual(type, JsonDataType.Double)
+
+    (data, type) = try! "Hello world! 👠".toSyncbaseJson()
+    XCTAssertEqual(toString(data), "\"Hello world! 👠\"")
+    XCTAssertEqual(type, JsonDataType.String)
+
+    (data, type) = try! [1,2,3,4].toSyncbaseJson()
+    XCTAssertEqual(toString(data), "[1,2,3,4]")
+    XCTAssertEqual(type, JsonDataType.Array)
+
+    (data, type) = toJson([1,2,3,4])
+    XCTAssertEqual(toString(data), "[1,2,3,4]")
+    XCTAssertEqual(type, JsonDataType.Array)
+
+    (data, type) = try! ["a", "b", "c"].toSyncbaseJson()
+    XCTAssertEqual(toString(data), "[\"a\",\"b\",\"c\"]")
+    XCTAssertEqual(type, JsonDataType.Array)
+
+    (data, type) = try! ["a":1].toSyncbaseJson()
+    XCTAssertEqual(toString(data), "{\"a\":1}")
+    XCTAssertEqual(type, JsonDataType.Dictionary)
+
+    (data, type) = try! ["b": true].toSyncbaseJson()
+    XCTAssertEqual(toString(data), "{\"b\":true}")
+    XCTAssertEqual(type, JsonDataType.Dictionary)
+
+    (data, type) = try! ["c": "👠"].toSyncbaseJson()
+    XCTAssertEqual(toString(data), "{\"c\":\"👠\"}")
+    XCTAssertEqual(type, JsonDataType.Dictionary)
+
+    (data, type) = toJson(["c": "👠"])
+    XCTAssertEqual(toString(data), "{\"c\":\"👠\"}")
+    XCTAssertEqual(type, JsonDataType.Dictionary)
+  }
+}
diff --git a/lib/SyncbaseTests/NSNumberTests.swift b/lib/SyncbaseTests/NSNumberTests.swift
new file mode 100644
index 0000000..6e91b6b
--- /dev/null
+++ b/lib/SyncbaseTests/NSNumberTests.swift
@@ -0,0 +1,144 @@
+// 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.
+
+import XCTest
+@testable import Syncbase
+
+class NSNumberTests: XCTestCase {
+  func testISNSNumber() {
+    XCTAssertTrue(NSNumber.isNSNumber(NSNumber(bool: true)))
+    XCTAssertFalse(NSNumber.isNSNumber(true))
+    XCTAssertTrue(NSNumber.isNSNumber(true as AnyObject))
+    XCTAssertTrue(NSNumber.isNSNumber(true as NSNumber))
+    XCTAssertFalse(NSNumber.isNSNumber(""))
+    XCTAssertFalse(NSNumber.isNSNumber(5))
+    XCTAssertTrue(NSNumber.isNSNumber(5 as AnyObject))
+  }
+
+  func testConservationOfPrecision() {
+    var anyobj:AnyObject?
+    var nsnumber:NSNumber?
+    var bool:Bool? = true
+    var int:Int? = Int.max
+    var int8:Int8? = Int8.max
+    var int16:Int16? = Int16.max
+    var int32:Int32? = Int32.max
+    var int64:Int64? = Int64.max
+    var float:Float? = Float(Int32.max)
+    var double:Double? = Double(Int64.max)
+    var long:CLong? = CLong.max
+    var uint:UInt? = UInt.max
+    var uint8:UInt8? = UInt8.max
+    var uint16:UInt16? = UInt16.max
+    var uint32:UInt32? = UInt32.max
+    var uint64:UInt64? = UInt64.max
+    var ulong:CUnsignedLong? = CUnsignedLong.max
+
+    // Preserve booleans
+    XCTAssertTrue((true as NSNumber).isTargetCastable(&bool))
+    XCTAssertTrue((true as NSNumber).isTargetCastable(&anyobj))
+    XCTAssertTrue((true as NSNumber).isTargetCastable(&nsnumber))
+    XCTAssertFalse((true as NSNumber).isTargetCastable(&int))
+    XCTAssertFalse((true as NSNumber).isTargetCastable(&float))
+    XCTAssertFalse((true as NSNumber).isTargetCastable(&long))
+
+    // Preserved size
+    XCTAssertTrue(NSNumber(char: int8!).isTargetCastable(&int8))
+    XCTAssertTrue(NSNumber(char: int8!).isTargetCastable(&int16))
+    XCTAssertTrue(NSNumber(char: int8!).isTargetCastable(&int32))
+    XCTAssertTrue(NSNumber(char: int8!).isTargetCastable(&int64))
+    XCTAssertTrue(NSNumber(char: int8!).isTargetCastable(&int))
+    XCTAssertTrue(NSNumber(char: int8!).isTargetCastable(&long))
+    XCTAssertFalse(NSNumber(char: int8!).isTargetCastable(&float))
+    XCTAssertFalse(NSNumber(char: int8!).isTargetCastable(&double))
+
+    XCTAssertFalse(NSNumber(short: int16!).isTargetCastable(&int8))
+    XCTAssertTrue(NSNumber(short: int16!).isTargetCastable(&int16))
+    XCTAssertTrue(NSNumber(short: int16!).isTargetCastable(&int32))
+    XCTAssertTrue(NSNumber(short: int16!).isTargetCastable(&int64))
+    XCTAssertTrue(NSNumber(short: int16!).isTargetCastable(&int))
+    XCTAssertTrue(NSNumber(short: int16!).isTargetCastable(&long))
+    XCTAssertFalse(NSNumber(short: int16!).isTargetCastable(&float))
+    XCTAssertFalse(NSNumber(short: int16!).isTargetCastable(&double))
+
+    XCTAssertFalse(NSNumber(int: int32!).isTargetCastable(&int8))
+    XCTAssertFalse(NSNumber(int: int32!).isTargetCastable(&int16))
+    XCTAssertTrue(NSNumber(int: int32!).isTargetCastable(&int32))
+    XCTAssertTrue(NSNumber(int: int32!).isTargetCastable(&int64))
+    XCTAssertTrue(NSNumber(int: int32!).isTargetCastable(&int))
+    XCTAssertTrue(NSNumber(int: int32!).isTargetCastable(&uint))
+    XCTAssertTrue(NSNumber(int: int32!).isTargetCastable(&long))
+    XCTAssertFalse(NSNumber(int: int32!).isTargetCastable(&float))
+    XCTAssertFalse(NSNumber(int: int32!).isTargetCastable(&double))
+
+    if sizeof(CLong) == sizeof(Int64) {
+      XCTAssertFalse(NSNumber(long: int!).isTargetCastable(&int8))
+      XCTAssertFalse(NSNumber(long: int!).isTargetCastable(&int16))
+      XCTAssertFalse(NSNumber(long: int!).isTargetCastable(&int32))
+      XCTAssertTrue(NSNumber(long: int!).isTargetCastable(&int64))
+      XCTAssertTrue(NSNumber(long: int!).isTargetCastable(&long))
+      XCTAssertTrue(NSNumber(long: int!).isTargetCastable(&int))
+      XCTAssertTrue(NSNumber(long: int!).isTargetCastable(&uint))
+      XCTAssertFalse(NSNumber(long: int!).isTargetCastable(&float))
+      XCTAssertFalse(NSNumber(long: int!).isTargetCastable(&double))
+    } else {
+      XCTAssertFalse(NSNumber(long: int!).isTargetCastable(&int8))
+      XCTAssertFalse(NSNumber(long: int!).isTargetCastable(&int16))
+      XCTAssertTrue(NSNumber(long: int!).isTargetCastable(&int32))
+      XCTAssertTrue(NSNumber(long: int!).isTargetCastable(&int64))
+      XCTAssertTrue(NSNumber(long: int!).isTargetCastable(&long))
+      XCTAssertFalse(NSNumber(long: int!).isTargetCastable(&float))
+      XCTAssertFalse(NSNumber(long: int!).isTargetCastable(&double))
+    }
+
+    XCTAssertFalse(NSNumber(longLong: int64!).isTargetCastable(&int8))
+    XCTAssertFalse(NSNumber(longLong: int64!).isTargetCastable(&int16))
+    XCTAssertFalse(NSNumber(longLong: int64!).isTargetCastable(&int32))
+    if sizeof(Int) == sizeof(Int64) {
+      XCTAssertTrue(NSNumber(longLong: int64!).isTargetCastable(&int))
+    } else {
+      XCTAssertFalse(NSNumber(longLong: int64!).isTargetCastable(&int))
+    }
+    XCTAssertTrue(NSNumber(longLong: int64!).isTargetCastable(&int64))
+    XCTAssertTrue(NSNumber(longLong: int64!).isTargetCastable(&long))
+    XCTAssertFalse(NSNumber(longLong: int64!).isTargetCastable(&float))
+    XCTAssertFalse(NSNumber(longLong: int64!).isTargetCastable(&double))
+
+    XCTAssertFalse(NSNumber(unsignedLongLong: uint64!).isTargetCastable(&int8))
+    XCTAssertFalse(NSNumber(unsignedLongLong: uint64!).isTargetCastable(&int16))
+    XCTAssertFalse(NSNumber(unsignedLongLong: uint64!).isTargetCastable(&int32))
+    XCTAssertFalse(NSNumber(unsignedLongLong: uint64!).isTargetCastable(&uint8))
+    XCTAssertFalse(NSNumber(unsignedLongLong: uint64!).isTargetCastable(&uint16))
+    XCTAssertFalse(NSNumber(unsignedLongLong: uint64!).isTargetCastable(&uint32))
+    XCTAssertTrue(NSNumber(unsignedLongLong: uint64!).isTargetCastable(&uint64))
+    XCTAssertTrue(NSNumber(unsignedLongLong: uint64!).isTargetCastable(&ulong))
+    // TODO(zinman): Be smarter about when we allow this or not
+    if sizeof(Int) == sizeof(Int64) {
+      XCTAssertTrue(NSNumber(unsignedLongLong: uint64!).isTargetCastable(&int))
+    } else {
+      XCTAssertFalse(NSNumber(unsignedLongLong: uint64!).isTargetCastable(&int))
+    }
+    XCTAssertTrue(NSNumber(unsignedLongLong: uint64!).isTargetCastable(&int64))
+    XCTAssertTrue(NSNumber(unsignedLongLong: uint64!).isTargetCastable(&long))
+    XCTAssertFalse(NSNumber(unsignedLongLong: uint64!).isTargetCastable(&float))
+    XCTAssertFalse(NSNumber(unsignedLongLong: uint64!).isTargetCastable(&double))
+
+    XCTAssertFalse(NSNumber(float: float!).isTargetCastable(&int8))
+    XCTAssertFalse(NSNumber(float: float!).isTargetCastable(&int16))
+    XCTAssertFalse(NSNumber(float: float!).isTargetCastable(&int32))
+    XCTAssertFalse(NSNumber(float: float!).isTargetCastable(&int64))
+    XCTAssertFalse(NSNumber(float: float!).isTargetCastable(&int))
+    XCTAssertFalse(NSNumber(float: float!).isTargetCastable(&long))
+    XCTAssertTrue(NSNumber(float: float!).isTargetCastable(&float))
+    XCTAssertTrue(NSNumber(float: float!).isTargetCastable(&double))
+
+    XCTAssertFalse(NSNumber(double: double!).isTargetCastable(&int8))
+    XCTAssertFalse(NSNumber(double: double!).isTargetCastable(&int16))
+    XCTAssertFalse(NSNumber(double: double!).isTargetCastable(&int32))
+    XCTAssertFalse(NSNumber(double: double!).isTargetCastable(&int64))
+    XCTAssertFalse(NSNumber(double: double!).isTargetCastable(&long))
+    XCTAssertFalse(NSNumber(double: double!).isTargetCastable(&float))
+    XCTAssertTrue(NSNumber(double: double!).isTargetCastable(&double))
+  }
+}
diff --git a/lib/SyncbaseTests/SyncbaseTests.swift b/lib/SyncbaseTests/SyncbaseTests.swift
deleted file mode 100644
index 08a20f0..0000000
--- a/lib/SyncbaseTests/SyncbaseTests.swift
+++ /dev/null
@@ -1,18 +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.
-
-import XCTest
-@testable import Syncbase
-
-class SyncbaseTests: XCTestCase {
-  override func setUp() {
-    super.setUp()
-    // Put setup code here. This method is called before the invocation of each test method in the class.
-  }
-
-  override func tearDown() {
-    // Put teardown code here. This method is called after the invocation of each test method in the class.
-    super.tearDown()
-  }
-}
diff --git a/lib/Vanadium.xcodeproj/project.pbxproj b/lib/Vanadium.xcodeproj/project.pbxproj
index 5c3f4a2..bf40ad1 100644
--- a/lib/Vanadium.xcodeproj/project.pbxproj
+++ b/lib/Vanadium.xcodeproj/project.pbxproj
@@ -7,10 +7,28 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		305A8E751CCF64E7007656D0 /* Batch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E641CCF64E7007656D0 /* Batch.swift */; };
+		305A8E761CCF64E7007656D0 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E651CCF64E7007656D0 /* Blob.swift */; };
+		305A8E771CCF64E7007656D0 /* Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E661CCF64E7007656D0 /* Collection.swift */; };
+		305A8E781CCF64E7007656D0 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E671CCF64E7007656D0 /* Database.swift */; };
+		305A8E791CCF64E7007656D0 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E681CCF64E7007656D0 /* Errors.swift */; };
+		305A8E7A1CCF64E7007656D0 /* Id.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E691CCF64E7007656D0 /* Id.swift */; };
+		305A8E7B1CCF64E7007656D0 /* Marshal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E6A1CCF64E7007656D0 /* Marshal.swift */; };
+		305A8E7C1CCF64E7007656D0 /* Permissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E6B1CCF64E7007656D0 /* Permissions.swift */; };
+		305A8E7D1CCF64E7007656D0 /* Row.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E6C1CCF64E7007656D0 /* Row.swift */; };
+		305A8E7E1CCF64E7007656D0 /* Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E6D1CCF64E7007656D0 /* Service.swift */; };
+		305A8E7F1CCF64E7007656D0 /* Stream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E6E1CCF64E7007656D0 /* Stream.swift */; };
+		305A8E801CCF64E7007656D0 /* Syncgroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E6F1CCF64E7007656D0 /* Syncgroup.swift */; };
+		305A8E811CCF64E7007656D0 /* Util.h in Headers */ = {isa = PBXBuildFile; fileRef = 305A8E701CCF64E7007656D0 /* Util.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		305A8E821CCF64E7007656D0 /* Util.m in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E711CCF64E7007656D0 /* Util.m */; };
+		305A8E831CCF64E7007656D0 /* Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E721CCF64E7007656D0 /* Util.swift */; };
+		305A8E841CCF64E7007656D0 /* Vom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E731CCF64E7007656D0 /* Vom.swift */; };
+		305A8E851CCF64E7007656D0 /* Watch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E741CCF64E7007656D0 /* Watch.swift */; };
+		30B111C41CCEA9C7000F3750 /* NSNumberTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B111C31CCEA9C7000F3750 /* NSNumberTests.swift */; };
+		30B1120B1CCF586C000F3750 /* JsonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B1120A1CCF586C000F3750 /* JsonTests.swift */; };
 		93B980D31CAB9B6100CE42E0 /* Principal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93B980D21CAB9B6100CE42E0 /* Principal.swift */; };
 		93B980DC1CAB9D3D00CE42E0 /* Syncbase.h in Headers */ = {isa = PBXBuildFile; fileRef = 93B980DB1CAB9D3D00CE42E0 /* Syncbase.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		93B980E31CAB9D3D00CE42E0 /* Syncbase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93B980D91CAB9D3D00CE42E0 /* Syncbase.framework */; };
-		93B980E81CAB9D3D00CE42E0 /* SyncbaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93B980E71CAB9D3D00CE42E0 /* SyncbaseTests.swift */; };
 		93B980F31CAB9D6E00CE42E0 /* Security.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93B980F21CAB9D6E00CE42E0 /* Security.swift */; };
 		93B980F51CAB9D9000CE42E0 /* Syncbase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93B980F41CAB9D9000CE42E0 /* Syncbase.swift */; };
 		93BC21591C9E199F0074C612 /* VanadiumCore.h in Headers */ = {isa = PBXBuildFile; fileRef = 93BC21581C9E199F0074C612 /* VanadiumCore.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -118,12 +136,30 @@
 /* End PBXContainerItemProxy section */
 
 /* Begin PBXFileReference section */
+		305A8E641CCF64E7007656D0 /* Batch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Batch.swift; sourceTree = "<group>"; };
+		305A8E651CCF64E7007656D0 /* Blob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Blob.swift; sourceTree = "<group>"; };
+		305A8E661CCF64E7007656D0 /* Collection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Collection.swift; sourceTree = "<group>"; };
+		305A8E671CCF64E7007656D0 /* Database.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Database.swift; sourceTree = "<group>"; };
+		305A8E681CCF64E7007656D0 /* Errors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = "<group>"; };
+		305A8E691CCF64E7007656D0 /* Id.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Id.swift; sourceTree = "<group>"; };
+		305A8E6A1CCF64E7007656D0 /* Marshal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Marshal.swift; sourceTree = "<group>"; };
+		305A8E6B1CCF64E7007656D0 /* Permissions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Permissions.swift; sourceTree = "<group>"; };
+		305A8E6C1CCF64E7007656D0 /* Row.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Row.swift; sourceTree = "<group>"; };
+		305A8E6D1CCF64E7007656D0 /* Service.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Service.swift; sourceTree = "<group>"; };
+		305A8E6E1CCF64E7007656D0 /* Stream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Stream.swift; sourceTree = "<group>"; };
+		305A8E6F1CCF64E7007656D0 /* Syncgroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Syncgroup.swift; sourceTree = "<group>"; };
+		305A8E701CCF64E7007656D0 /* Util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Util.h; sourceTree = "<group>"; };
+		305A8E711CCF64E7007656D0 /* Util.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Util.m; sourceTree = "<group>"; };
+		305A8E721CCF64E7007656D0 /* Util.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Util.swift; sourceTree = "<group>"; };
+		305A8E731CCF64E7007656D0 /* Vom.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Vom.swift; sourceTree = "<group>"; };
+		305A8E741CCF64E7007656D0 /* Watch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Watch.swift; sourceTree = "<group>"; };
+		30B111C31CCEA9C7000F3750 /* NSNumberTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSNumberTests.swift; sourceTree = "<group>"; };
+		30B1120A1CCF586C000F3750 /* JsonTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JsonTests.swift; sourceTree = "<group>"; };
 		93B980D21CAB9B6100CE42E0 /* Principal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Principal.swift; path = security/Principal.swift; sourceTree = "<group>"; };
 		93B980D91CAB9D3D00CE42E0 /* Syncbase.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Syncbase.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		93B980DB1CAB9D3D00CE42E0 /* Syncbase.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Syncbase.h; sourceTree = "<group>"; };
 		93B980DD1CAB9D3D00CE42E0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		93B980E21CAB9D3D00CE42E0 /* SyncbaseTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SyncbaseTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
-		93B980E71CAB9D3D00CE42E0 /* SyncbaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncbaseTests.swift; sourceTree = "<group>"; };
 		93B980E91CAB9D3D00CE42E0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		93B980F21CAB9D6E00CE42E0 /* Security.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Security.swift; sourceTree = "<group>"; };
 		93B980F41CAB9D9000CE42E0 /* Syncbase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Syncbase.swift; sourceTree = "<group>"; };
@@ -202,10 +238,27 @@
 		93B980DA1CAB9D3D00CE42E0 /* Syncbase */ = {
 			isa = PBXGroup;
 			children = (
-				93B980DB1CAB9D3D00CE42E0 /* Syncbase.h */,
+				305A8E641CCF64E7007656D0 /* Batch.swift */,
+				305A8E651CCF64E7007656D0 /* Blob.swift */,
+				305A8E661CCF64E7007656D0 /* Collection.swift */,
+				305A8E671CCF64E7007656D0 /* Database.swift */,
+				305A8E681CCF64E7007656D0 /* Errors.swift */,
+				305A8E691CCF64E7007656D0 /* Id.swift */,
 				93B980DD1CAB9D3D00CE42E0 /* Info.plist */,
+				305A8E6A1CCF64E7007656D0 /* Marshal.swift */,
+				305A8E6B1CCF64E7007656D0 /* Permissions.swift */,
+				305A8E6C1CCF64E7007656D0 /* Row.swift */,
 				93B980F21CAB9D6E00CE42E0 /* Security.swift */,
+				305A8E6D1CCF64E7007656D0 /* Service.swift */,
+				305A8E6E1CCF64E7007656D0 /* Stream.swift */,
+				93B980DB1CAB9D3D00CE42E0 /* Syncbase.h */,
 				93B980F41CAB9D9000CE42E0 /* Syncbase.swift */,
+				305A8E6F1CCF64E7007656D0 /* Syncgroup.swift */,
+				305A8E701CCF64E7007656D0 /* Util.h */,
+				305A8E711CCF64E7007656D0 /* Util.m */,
+				305A8E721CCF64E7007656D0 /* Util.swift */,
+				305A8E731CCF64E7007656D0 /* Vom.swift */,
+				305A8E741CCF64E7007656D0 /* Watch.swift */,
 			);
 			path = Syncbase;
 			sourceTree = "<group>";
@@ -213,8 +266,9 @@
 		93B980E61CAB9D3D00CE42E0 /* SyncbaseTests */ = {
 			isa = PBXGroup;
 			children = (
-				93B980E71CAB9D3D00CE42E0 /* SyncbaseTests.swift */,
 				93B980E91CAB9D3D00CE42E0 /* Info.plist */,
+				30B111C31CCEA9C7000F3750 /* NSNumberTests.swift */,
+				30B1120A1CCF586C000F3750 /* JsonTests.swift */,
 			);
 			path = SyncbaseTests;
 			sourceTree = "<group>";
@@ -365,6 +419,7 @@
 			isa = PBXHeadersBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				305A8E811CCF64E7007656D0 /* Util.h in Headers */,
 				93B980DC1CAB9D3D00CE42E0 /* Syncbase.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -612,8 +667,24 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				305A8E7C1CCF64E7007656D0 /* Permissions.swift in Sources */,
+				305A8E7A1CCF64E7007656D0 /* Id.swift in Sources */,
+				305A8E781CCF64E7007656D0 /* Database.swift in Sources */,
+				305A8E831CCF64E7007656D0 /* Util.swift in Sources */,
+				305A8E761CCF64E7007656D0 /* Blob.swift in Sources */,
+				305A8E801CCF64E7007656D0 /* Syncgroup.swift in Sources */,
+				305A8E821CCF64E7007656D0 /* Util.m in Sources */,
+				305A8E851CCF64E7007656D0 /* Watch.swift in Sources */,
+				305A8E771CCF64E7007656D0 /* Collection.swift in Sources */,
+				305A8E7E1CCF64E7007656D0 /* Service.swift in Sources */,
+				305A8E751CCF64E7007656D0 /* Batch.swift in Sources */,
+				305A8E7D1CCF64E7007656D0 /* Row.swift in Sources */,
 				93B980F31CAB9D6E00CE42E0 /* Security.swift in Sources */,
+				305A8E7F1CCF64E7007656D0 /* Stream.swift in Sources */,
+				305A8E841CCF64E7007656D0 /* Vom.swift in Sources */,
 				93B980F51CAB9D9000CE42E0 /* Syncbase.swift in Sources */,
+				305A8E7B1CCF64E7007656D0 /* Marshal.swift in Sources */,
+				305A8E791CCF64E7007656D0 /* Errors.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -621,7 +692,8 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				93B980E81CAB9D3D00CE42E0 /* SyncbaseTests.swift in Sources */,
+				30B111C41CCEA9C7000F3750 /* NSNumberTests.swift in Sources */,
+				30B1120B1CCF586C000F3750 /* JsonTests.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
diff --git a/lib/VanadiumCoreTests/PromiseTests.swift b/lib/VanadiumCoreTests/PromiseTests.swift
index ae132cd..3590085 100644
--- a/lib/VanadiumCoreTests/PromiseTests.swift
+++ b/lib/VanadiumCoreTests/PromiseTests.swift
@@ -5,7 +5,7 @@
 import XCTest
 import VanadiumCore
 
-enum TestErrors : ErrorType {
+enum TestErrors: ErrorType {
   case SomeError
   case SomeOtherError
 }
@@ -25,7 +25,7 @@
       }
     try! p.resolve(5)
     XCTAssertTrue(alwaysRan)
-    
+
     var ranNow = false
     p.onResolve { obj in
       ranNow = true
@@ -68,7 +68,7 @@
     }
     try! p.reject(TestErrors.SomeError)
   }
-  
+
   func testRejectionDoesntRunOnResolve() {
     let p = Promise<Int>()
     p.onReject { err in
@@ -76,12 +76,12 @@
     }
     try! p.resolve(5)
   }
-  
+
   func testThenTransforms() {
     let p = Promise<Int>()
     try! p.resolve(5)
-    
-    var finalResult:String = ""
+
+    var finalResult: String = ""
     p.then { obj -> String in
       XCTAssertEqual(obj, 5)
       return "hello"
@@ -94,7 +94,7 @@
 
   func testThenPropagatesError() {
     let p = Promise<Int>()
-    
+
     var didReject = false
     p.then { obj -> String in
       XCTAssertEqual(obj, 5)
@@ -105,7 +105,7 @@
     try! p.reject(TestErrors.SomeError)
     XCTAssertTrue(didReject)
   }
-  
+
   func testThenReturningPromiseWorks() {
     let p = Promise<Int>()
     let bgQueue = dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)
@@ -116,7 +116,7 @@
       }
       return newP
     }
-    
+
     try! p.resolve(5)
     let finalStatus = try! finalP.await()
     switch (finalStatus) {