-
Notifications
You must be signed in to change notification settings - Fork 138
Sync Function API
The sync function is the core API you'll be interacting with on the Sync Gateway. For simple applications it may be the only server-side code you need to write. For more complex applications it will still be a primary touch point for managing data routing and access control.
Learn about channels and user authentication here and read this page for a detailed example.
If you don't supply a sync function we'll use this as a default:
function (doc) {
channel(doc.channels);
}
The sync function is called with three arguments, which allow it to be used for validation as well as data routing. (Your implementation can omit the second and third parameters if it doesn't need them; JavaScript simply ignores extra parameters passed to a function.)
function (doc, oldDoc, userCtx) {
// your code here
}
-
doc
The first argument is the document that is being saved. This will match the JSON that was saved by the mobile client and replicated to Sync Gateway. There are no metadata or other fields added, although the
_id
and_rev
fields are available. -
oldDoc
If the document has been saved before, the revision that is being replaced will be available here. In the case of a document with conflicting revision, the provisional winning revision will be passed as the
oldDoc
parameter. Read more about Conflict Detection and Mangement. -
userCtx
This is an object that contains details about the currently authenticated user. It has these fields:
-
name
This is the name of the user, which will be unique across the database, and which should not change over time. A common use for this field is to ensure that a field like
doc.author
is the same asuserCtx.name
so that it's not possible to impersonate another user by writing a document which lists someone else as the author. If the user is not authenticated, the name field will contain""
-- an empty string. -
roles
This field is an array of strings which the administrator can associate with a user. So for instance you can add the role
"manager"
to some of your users, and then grant access to some channels to all managers without having to grant it to all of them individually. In general granting channel access to roles instead of individual users will result in Sync Gateway maintaining more parsimonious internal data structures, giving improved performance. The reasonroles
is available here, is so you can also use role membership to control what kind of documents a user can write. -
channels
This field allows you to peek at the channels a given user can read from. Because the sync function determines which channels a document lands in, it can also prevent the user from writing to channels they can't access (if you choose.) Note that by default a user can write to any channel, even those they can't read.
-
From within the sync function you create changes in the Sync Gateway configuration via callback functions. Each call manages a small amount of configuration state. It is also tied back to the document which initiated the call, so that when the document is modified, any configuration made by an old version of the document is replaced with configuration derived from the newer version. Via these APIs, documents are mapped to channels. They can also grant access to channels, either to users or roles. Finally, you can reject an update completely by throwing an error. The error message will be returned to the synchronizing client, which will log it or potentially display it to the user.
The sync function can prevent a document from persisting or syncing to any other users by calling throw()
with an error object. This also prevents the document from changing any other gateway configation. Here is an example sync function which disallows all writes to the database it is in.
function (doc) {
throw({forbidden : "read only!"})
}
The key of the error object may be either forbidden
(corresponding to an HTTP 403 error code) or unauthorized
(corresponding to HTTP 401 error). The forbidden
error should be used if the user is already authenticated, and they account they are syncing with is not permitted to modify or create the document. The unauthorized
error should be used if the account is not authenticated. Some user agents will trigger a login workflow when presented with a 401 error.
A quick rule of thumb: most of the time you should use the throw({forbidden : "your message here"})
as most applications will require users to be authenticated before any reads or writes can occur.
The channel
call routes the document to the named channel. It accepts either a string channel name, or an array of strings, if the document should be added to multiple channels in a single call. The channel function can me called zero or more times from the sync function, for any document. The default function (listed at the top of this document) routes documents to the channels listed on them. Here is an example that routes all "published" documents to the "public" channel, and all "unpublished" documents to a drafts channel specific to the channel author.
As a convenience, it is legal to call channel
with a null
or undefined
argument; it simply does nothing. This allows you to do something like channel(doc.channels)
without having to first check whether doc.channels
exists.
function (doc, oldDoc, userCtx) {
if (doc.published) {
channel("public");
} else {
channel("drafts-" + userCtx.name);
}
}
The access
call grants access to channel to a given user or list of users. It can be called multiple times from a sync function.
The effects of the access
call last as long as this revision is current. If a new revision is saved, the access
calls made by the sync
function will replace the original access. If the document is deleted, the access is revoked. The effects of all access calls by all active documents are effectively unioned together, so if any document grants a user access to a channel, that user has access to the channel. (Note that revoking access to a channel will not delete the documents which have already been synced to a user's device.)
The access call takes two arguments, the user (or users) and the channel (or channels). These are all valid ways to call it:
access("jchris", "mtv")
access("jchris", ["mtv", "mtv2", "vh1"])
access(["snej", "jchris"], "vh1")
access(["snej", "jchris"], ["mtv", "mtv2", "vh1"])
As a convenience, either argument may be null
or undefined
, in which case nothing happens.
Here is an example function which grants access to a channel for all the users listed on a document:
function (doc, oldDoc, userCtx) {
if (doc.members && doc.channel_name) {
access(doc.members, doc.channel_name);
}
// we should also put this document on the channel it manages
channel(doc.channel_name)
}
If a user name in an access
call begins with the prefix role:
, the rest of the name is interpreted as a role, not a user. The call then grants access to the channel(s) for all users with that role.
TODO: this is not yet implemented
The best resource to see a full example is the Chat App Data Model article.