UNPKG

synctos

Version:

The Syncmaker. A tool to build comprehensive sync functions for Couchbase Sync Gateway.

475 lines (387 loc) 76.8 kB
# Introduction [![Build Status](https://travis-ci.org/Kashoo/synctos.svg?branch=master)](https://travis-ci.org/Kashoo/synctos) [![npm version](https://badge.fury.io/js/synctos.svg)](https://www.npmjs.com/package/synctos) [![dependencies Status](https://david-dm.org/Kashoo/synctos/master/status.svg)](https://david-dm.org/Kashoo/synctos/master) [![devDependencies Status](https://david-dm.org/Kashoo/synctos/master/dev-status.svg)](https://david-dm.org/Kashoo/synctos/master?type=dev) Synctos: The Syncmaker. A utility to aid with the process of designing well-structured sync functions for Couchbase Sync Gateway. With this utility, you define all your JSON document types in a declarative JavaScript object format that eliminates much of the boilerplate normally required for [sync functions](http://developer.couchbase.com/documentation/mobile/current/develop/guides/sync-gateway/sync-function-api-guide/index.html) with comprehensive validation of document contents and permissions. Not only is it invaluable in protecting the integrity of the documents that are stored in a Sync Gateway database, whenever a document fails validation, sync functions generated with synctos return specific, detailed error messages that make it easy for a client app developer to figure out exactly what went wrong. An included test fixture module also provides a simple framework to write unit tests for generated sync functions. To learn more about Sync Gateway, check out [Couchbase](http://www.couchbase.com/)'s comprehensive [developer documentation](http://developer.couchbase.com/documentation/mobile/current/guides/sync-gateway/index.html). And, for a comprehensive introduction to synctos, see the post [Validating your Sync Gateway documents with synctos](https://blog.couchbase.com/validating-your-sync-gateway-documents-with-synctos/) on the official Couchbase blog. For validation of documents in Apache CouchDB, see the [couchster](https://github.com/OldSneerJaw/couchster) project. # Table of Contents - [Introduction](#introduction) - [Table of Contents](#table-of-contents) - [Installation](#installation) - [Usage](#usage) - [Running](#running) - [Validating](#validating) - [Specifications](#specifications) - [Document type definitions](#document-type-definitions) - [Essential document constraints](#essential-document-constraints) - [Advanced document constraints](#advanced-document-constraints) - [Content validation](#content-validation) - [Simple type validation](#simple-type-validation) - [Complex type validation](#complex-type-validation) - [Multi-type validation](#multi-type-validation) - [Universal validation constraints](#universal-validation-constraints) - [Predefined validators](#predefined-validators) - [Dynamic constraint validation](#dynamic-constraint-validation) - [Definition file](#definition-file) - [Modularity](#modularity) - [Helper functions](#helper-functions) - [Testing](#testing) # Installation Synctos is distributed as an [npm](https://www.npmjs.com/) package, and the minimum version of [Node.js](https://nodejs.org/) that it officially supports is v8.9.0. Both of these required components can be acquired at once by [installing](https://nodejs.org/en/download/package-manager/) Node.js. If your project does not already have an npm `package.json` file, run `npm init` to create one. Don't worry too much about the answers to the questions it asks right now; the file it produces can be updated as needed later. Next, to install synctos locally (i.e. in your project's `node_modules` directory) and to add it to your project as a development dependency automatically, run `npm install synctos --save-dev` from the project's root directory. For more info on npm package management, see the official npm documentation for [How to install local packages](https://docs.npmjs.com/getting-started/installing-npm-packages-locally) and [Working with package.json](https://docs.npmjs.com/getting-started/using-a-package.json). **A note on JavaScript/ECMAScript compatibility:** Sync Gateway uses the [otto](https://github.com/robertkrimen/otto) JavaScript engine to execute sync functions from within its [Go](https://golang.org/) codebase. The version of otto, and thus the version of ECMAScript, that are supported varies depending on the version of Sync Gateway: * Sync Gateway 1.x is pinned to commit [5282a5a](https://github.com/robertkrimen/otto/tree/5282a5a45ba989692b3ae22f730fa6b9dd67662f) of otto, which does not support any of the features introduced in ECMAScript 2015 (aka ES6/ES2015). In fact, it does not promise compatibility with ECMAScript 5, offering only that "For now, otto is a hybrid ECMA3/ECMA5 interpreter. Parts of the specification are still works in progress." * Sync Gateway 2.x is pinned to commit [a813c59](https://github.com/robertkrimen/otto/tree/a813c59b1b4471ff7ecd3b533bac2f7e7d178784) of otto, which also does not support any of the features introduced in ECMAScript 2015, but it does at least offer full compatibility with ECMAScript 5, aside from a handful of [noted](https://github.com/robertkrimen/otto/tree/a813c59b1b4471ff7ecd3b533bac2f7e7d178784#regular-expression-incompatibility) RegExp incompatibilities. It is for compatibility with these specific versions of otto that synctos intentionally generates code that does not make use of many of the conveniences of modern JavaScript (e.g. `let`, `const`, `for...of` loops, arrow functions, spread operators, etc.). For the same reason, it is always best to verify sync functions that are generated by synctos or otherwise within a live instance of Sync Gateway before deploying to production to ensure that your own custom code is supported by the corresponding version of the otto JS interpreter. As a convenience, otto - and, by extension, Sync Gateway - does support the [Underscore.js](http://underscorejs.org/) utility belt library (specifically version [1.4.4](https://cdn.rawgit.com/jashkenas/underscore/1.4.4/index.html)), which provides a great many useful functions. As such, it may help to fill in some of the gaps that would otherwise be filled by a newer version of the ECMAScript specification. # Usage ### Running Once synctos is installed, you can run it from your project's directory as follows: ```bash node_modules/.bin/synctos /path/to/my-document-definitions.js /path/to/my-generated-sync-function.js ``` Or as a custom [script](https://docs.npmjs.com/misc/scripts) in your project's `package.json` as follows: ```javascript "scripts": { "build": "synctos /path/to/my-document-definitions.js /path/to/my-generated-sync-function.js" } ``` This will take the sync document definitions that are defined in `/path/to/my-document-definitions.js` and build a new sync function that is output to `/path/to/my-generated-sync-function.js`. The generated sync function contents can then be inserted into the definition of a bucket/database in a Sync Gateway [configuration file](http://developer.couchbase.com/documentation/mobile/current/guides/sync-gateway/config-properties/index.html) as a multi-line string surrounded with backquotes/backticks ( \` ). Generated sync functions are compatible with Sync Gateway 1.x and 2.x. **NOTE**: Due to a [known issue](https://github.com/couchbase/sync_gateway/issues/1866) in Sync Gateway versions up to and including 1.2.1, when specifying a bucket/database's sync function in a configuration file as a multi-line string, you will have to be sure to escape any literal backslash characters in the sync function body. For example, if your sync function contains a regular expression like `new RegExp('\\w+')`, you will have to escape the backslashes when inserting the sync function into the configuration file so that it becomes `new RegExp('\\\\w+')`. The issue has been resolved in Sync Gateway version 1.3.0 and later. ### Validating To validate that your document definitions file is structured correctly and does not contain any obvious semantic violations, execute the built in validation script as follows: ```bash node_modules/.bin/synctos-validate /path/to/my-document-definitions.js ``` Or as a custom [script](https://docs.npmjs.com/misc/scripts) in your project's `package.json` as follows: ```javascript "scripts": { "validate": "synctos-validate /path/to/my-document-definitions.js" } ``` If the specified document definitions contain any violations, the utility will exit with a non-zero status code and output a list of the violations to standard error (stderr). Otherwise, if validation was successful, the utility will exit normally and will not output anything. Be aware that the validation utility cannot verify the behaviour of custom functions (e.g. dynamic constraints, custom actions, custom validation functions) in a document definition. However, the [Testing](#testing) section of the README describes how to write test cases for such custom code. ### Specifications Document definitions must conform to the following specification. See the `samples/` directory and Kashoo's official [document definitions](https://github.com/Kashoo/kashoo-document-definitions) repository for some examples. At the top level, the document definitions object contains a property for each document type that is to be supported by the Sync Gateway bucket. For example: ```javascript { myDocType1: { channels: ..., typeFilter: ..., propertyValidators: ... }, myDocType2: { channels: ..., typeFilter: ..., propertyValidators: ... } } ``` #### Document type definitions Each document type is defined as an object with a number of properties that control authorization, content validation and access control. ##### Essential document constraints The following properties include the basics necessary to build a document definition: * `typeFilter`: (required) A function that is used to identify documents of this type. It accepts as function parameters (1) the new document, (2) the old document that is being replaced (if any) and (3) the name of the current document type. For the sake of convenience, a simple type filter function (`simpleTypeFilter`) is available that attempts to match the document's `type` property value to the document type's name (e.g. if a document definition is named "message", then a candidate document's `type` property value must be "message" to be considered a document of that type); if the document definition does not include an explicit `type` property validator, then, for convenience, the `type` property will be implicitly included in the document definition and validated with the built in `typeIdValidator` (see the validator's description for more info). NOTE: In cases where the document is in the process of being deleted, the first parameter's `_deleted` property will be `true`, so be sure to account for such cases. And, if the old document has been deleted or simply does not exist, the second parameter will be `null`. An example of the simple type filter: ```javascript typeFilter: simpleTypeFilter ``` And an example of a more complex custom type filter: ```javascript typeFilter: function(doc, oldDoc, currentDocType) { var typePropertyMatches; if (oldDoc) { if (doc._deleted) { typePropertyMatches = oldDoc.type === currentDocType; } else { typePropertyMatches = doc.type === oldDoc.type && oldDoc.type === currentDocType; } } else { // The old document does not exist or was deleted - we can rely on the new document's type typePropertyMatches = doc.type === currentDocType; } if (typePropertyMatches) { return true; } else { // The type property did not match - fall back to matching the document ID pattern var docIdRegex = /^message\.[A-Za-z0-9_-]+$/; return docIdRegex.test(doc._id); } } ``` * `channels`: (required if `authorizedRoles` and `authorizedUsers` are undefined - see the [Advanced document properties](#advanced-document-properties) section for more info) The [channels](http://developer.couchbase.com/documentation/mobile/current/develop/guides/sync-gateway/channels/index.html) to assign to documents of this type. If used in combination with the `authorizedRoles` and/or `authorizedUsers` properties, authorization will be granted if the user making the modification matches at least one of the channels and/or authorized roles/usernames for the corresponding operation type (add, replace or remove). May be specified as either a plain object or a function that returns a dynamically-constructed object and accepts as parameters (1) the new document and (2) the old document that is being replaced (if any). NOTE: In cases where the document is in the process of being deleted, the first parameter's `_deleted` property will be `true`, and if the old document has been deleted or simply does not exist, the second parameter will be `null`. Either way the object is specified, it may include the following properties, each of which may be either an array of channel names or a single channel name as a string: * `view`: (optional) The channel(s) that confer read-only access to documents of this type. * `add`: (required if `write` is undefined) The channel(s) that confer the ability to create new documents of this type. Any user with a matching channel also gains implicit read access. Use the [special channel](https://developer.couchbase.com/documentation/mobile/current/guides/sync-gateway/channels/index.html#special-channels) "!" to allow any authenticated user to add a document of this type. * `replace`: (required if `write` is undefined) The channel(s) that confer the ability to replace existing documents of this type. Any user with a matching channel also gains implicit read access. Use the special channel "!" to allow any authenticated user to replace a document of this type. * `remove`: (required if `write` is undefined) The channel(s) that confer the ability to delete documents of this type. Any user with a matching channel also gains implicit read access. Use the special channel "!" to allow any authenticated user to delete a document of this type. * `write`: (required if one or more of `add`, `replace` or `remove` are undefined) The channel(s) that confer the ability to add, replace or remove documents of this type. Exists as a convenience in cases where the add, replace and remove operations should share the same channel(s). Any user with a matching channel also gains implicit read access. Use the special channel "!" to allow any authenticated user to write a document of this type. For example: ```javascript channels: { add: [ 'create', 'new' ], replace: 'update', remove: 'delete' } ``` Or: ```javascript channels: function(doc, oldDoc) { return { view: doc._id + '-readonly', write: [ doc._id + '-edit', doc._id + '-admin' ] }; } ``` * `propertyValidators`: (required) An object/hash of validators that specify the format of each of the document type's supported properties. Each entry consists of a key that specifies the property name and a value that specifies the validation to perform on that property. Each property element must declare a type and, optionally, some number of additional parameters. Any property that is not declared here will be rejected by the sync function unless the `allowUnknownProperties` field is set to `true`. In addition to a static value (e.g. `propertyValidators: { ... }`), this property may also be assigned a value dynamically via a function (e.g. `propertyValidators: function(doc, oldDoc) { ... }`) where the parameters are as follows: (1) the document (if deleted, the `_deleted` property will be `true`) and (2) the document that is being replaced (if any; it will be `null` if it has been deleted or does not exist). An example static definition: ```javascript propertyValidators: { myProp1: { type: 'boolean', required: true }, myProp2: { type: 'array', mustNotBeEmpty: true } } ``` And a dynamic definition: ```javascript propertyValidators: function(doc, oldDoc) { var dynamicProp = (doc._id.indexOf('foobar') >= 0) ? { type: 'string' } : { type: 'float' } return { myDynamicProp: dynamicProp }; } ``` ##### Advanced document constraints Additional properties that provide finer grained control over documents: * `allowUnknownProperties`: (optional) Whether to allow the existence of properties that are not explicitly declared in the document type definition. Not applied recursively to objects that are nested within documents of this type. In addition to a static value (e.g. `allowUnknownProperties: true`), this property may also be assigned a value dynamically via a function (e.g. `allowUnknownProperties: function(doc, oldDoc) { ... }`) where the parameters are as follows: (1) the document (if deleted, the `_deleted` property will be `true`) and (2) the document that is being replaced (if any; it will be `null` if it has been deleted or does not exist). Defaults to `false`. * `documentIdRegexPattern`: (optional) A [regular expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp) pattern that must be satisfied by the document's ID for a new document of this type to be created. Note that the constraint is not applied when a document is being replaced or deleted. In addition to a static value (e.g. `documentIdRegexPattern: /^payment\.[a-zA-Z0-9_-]+$/`), this constraint may also be assigned a value dynamically via a function (e.g. `documentIdRegexPattern: function(doc) { ... }`) where it receives a single parameter as follows: (1) the new document. No restriction by default. * `immutable`: (optional) The document cannot be replaced or deleted after it is created. Note that, when this property is enabled, even if attachments are allowed for this document type (see the `allowAttachments` parameter for more info), it will not be possible to create, modify or delete attachments in a document that already exists, which means that they must be created inline in the document's `_attachments` property when the document is first created. In addition to a static value (e.g. `immutable: true`), this property may also be assigned a value dynamically via a function (e.g. `immutable: function(doc, oldDoc) { ... }`) where the parameters are as follows: (1) the document (if deleted, the `_deleted` property will be `true`) and (2) the document that is being replaced (if any; it will be `null` if it has been deleted or does not exist). Defaults to `false`. * `cannotReplace`: (optional) As with the `immutable` constraint, the document cannot be replaced after it is created. However, this constraint does not prevent the document from being deleted. Note that, even if attachments are allowed for this document type (see the `allowAttachments` parameter for more info), it will not be possible to create, modify or delete attachments in a document that already exists, which means that they must be created inline in the document's `_attachments` property when the document is first created. In addition to a static value (e.g. `cannotReplace: true`), this property may also be assigned a value dynamically via a function (e.g. `cannotReplace: function(doc, oldDoc) { ... }`) where the parameters are as follows: (1) the document (if deleted, the `_deleted` property will be `true`) and (2) the document that is being replaced (if any; it will be `null` if it has been deleted or does not exist). Defaults to `false`. * `cannotDelete`: (optional) As with the `immutable` constraint, the document cannot be deleted after it is created. However, this constraint does not prevent the document from being replaced. In addition to a static value (e.g. `cannotDelete: true`), this property may also be assigned a value dynamically via a function (e.g. `cannotDelete: function(doc, oldDoc) { ... }`) where the parameters are as follows: (1) the document (if deleted, the `_deleted` property will be `true`) and (2) the document that is being replaced (if any; it will be `null` if it has been deleted or does not exist). Defaults to `false`. * `authorizedRoles`: (required if `channels` and `authorizedUsers` are undefined) The [roles](http://developer.couchbase.com/documentation/mobile/current/guides/sync-gateway/authorizing-users/index.html#roles) that are authorized to add, replace and remove documents of this type. If used in combination with the `channels` and/or `authorizedUsers` properties, authorization will be granted if the user making the modification matches at least one of the roles and/or authorized channels/usernames for the corresponding operation type (add, replace or remove). May be specified as either a plain object or a function that returns a dynamically-constructed object and accepts as parameters (1) the new document and (2) the old document that is being replaced (if any). NOTE: In cases where the document is in the process of being deleted, the first parameter's `_deleted` property will be `true`, and if the old document has been deleted or simply does not exist, the second parameter will be `null`. Either way the object is specified, it may include the following properties, each of which may be either an array of role names or a single role name as a string: * `add`: (optional) The role(s) that confer the ability to create new documents of this type. * `replace`: (optional) The role(s) that confer the ability to replace existing documents of this type. * `remove`: (optional) The role(s) that confer the ability to delete documents of this type. * `write`: (optional) The role(s) that confer the ability to add, replace or remove documents of this type. Exists as a convenience in cases where the add, replace and remove operations should share the same role(s). For example: ```javascript authorizedRoles: { add: 'manager', replace: [ 'manager', 'employee' ], remove: 'manager' } ``` Or: ```javascript authorizedRoles: function(doc, oldDoc) { return { write: oldDoc ? oldDoc.roles : doc.roles }; } ``` * `authorizedUsers`: (required if `channels` and `authorizedRoles` are undefined) The names of [users](http://developer.couchbase.com/documentation/mobile/current/guides/sync-gateway/authorizing-users/index.html#authorizing-users) that are explicitly authorized to add, replace and remove documents of this type. If used in combination with the `channels` and/or `authorizedRoles` properties, authorization will be granted if the user making the modification matches at least one of the usernames and/or authorized channels/roles for the corresponding operation type (add, replace or remove). May be specified as either a plain object or a function that returns a dynamically-constructed object and accepts as parameters (1) the new document and (2) the old document that is being replaced (if any). NOTE: In cases where the document is in the process of being deleted, the first parameter's `_deleted` property will be `true`, and if the old document has been deleted or simply does not exist, the second parameter will be `null`. Either way the object is specified, it may include the following properties, each of which may be either an array of usernames or a single username as a string: * `add`: (optional) The user(s) that have the ability to create new documents of this type. * `replace`: (optional) The user(s) that have the ability to replace existing documents of this type. * `remove`: (optional) The user(s) that have the ability to delete documents of this type. * `write`: (optional) The user(s) that have the ability to add, replace or remove documents of this type. Exists as a convenience in cases where the add, replace and remove operations should share the same user(s). For example: ```javascript authorizedUsers: { add: [ 'sally', 'roger', 'samantha' ], replace: [ 'roger', 'samantha' ], remove: 'samantha' } ``` Or: ```javascript authorizedUsers: function(doc, oldDoc) { return { write: oldDoc ? oldDoc.users : doc.users }; } ``` * `accessAssignments`: (optional) Defines either the channel access to assign to users/roles or the role access to assign to users when a document of the corresponding type is successfully created or replaced. The constraint can be defined as either a list, where each entry is an object that defines `users`, `roles` and/or `channels` properties, depending on the access assignment type, or it can be defined dynamically as a function that accepts the following parameters: (1) the new document and (2) the old document that is being replaced/deleted (if any). NOTE: If the old document has been deleted or simply does not exist, the second parameter will be `null`. When a document that included access assignments is deleted, its access assignments will be revoked. The assignment types are specified as follows: * Channel access assignments: * `type`: May be either "channel", `null` or missing/`undefined`. * `channels`: An array of channel names to assign to users and/or roles. * `roles`: An array of role names to which to assign the channels. * `users`: An array of usernames to which to assign the channels. * Role access assignments: * `type`: Must be "role". * `roles`: An array of role names to assign to users. * `users`: An array of usernames to which to assign the roles. An example of a static mix of channel and role access assignments: ```javascript accessAssignments: [ { type: 'role', users: [ 'user3', 'user4' ], roles: [ 'role1', 'role2' ] }, { type: 'channel', users: [ 'user1', 'user2' ], channels: [ 'channel1' ] }, { type: 'channel', users: function(doc, oldDoc) { return doc.users; }, roles: function(doc, oldDoc) { return doc.roles; }, channels: function(doc, oldDoc) { return [ doc._id + '-channel3', doc._id + '-channel4' ]; } }, ] ``` And an example of dynamic channel and role access assignments: ```javascript accessAssignments: function(doc, oldDoc) { var accessAssignments = [ ]; if (doc.channels) { accessAssignments.push( { type: 'channel', channels: doc.channels, roles: doc.channelRoles, users: doc.channelUsers }); } if (doc.roles) { accessAssignments.push( { type: 'role', roles: doc.roles, users: doc.roleUsers }); } return assignments; } ``` * `allowAttachments`: (optional) Whether to allow the addition of [file attachments](http://developer.couchbase.com/documentation/mobile/current/references/sync-gateway/rest-api/index.html#/attachment/put__db___doc___attachment_) for the document type. In addition to a static value (e.g. `allowAttachments: true`), this property may also be assigned a value dynamically via a function (e.g. `allowAttachments: function(doc, oldDoc) { ... }`) where the parameters are as follows: (1) the document (if deleted, the `_deleted` property will be `true`) and (2) the document that is being replaced (if any; it will be `null` if it has been deleted or does not exist). Defaults to `false` to prevent malicious/misbehaving clients from polluting the bucket/database with unwanted files. See the `attachmentConstraints` property and the `attachmentReference` validation type for more options. * `attachmentConstraints`: (optional) Various constraints to apply to file attachments associated with a document type. Its settings only apply if the document definition's `allowAttachments` property is `true`. In addition to a static value (e.g. `attachmentConstraints: { }`), this property may also be assigned a value dynamically via a function (e.g. `attachmentConstraints: function(doc, oldDoc) { ... }`) where the parameters are as follows: (1) the document (if deleted, the `_deleted` property will be `true`) and (2) the document that is being replaced (if any; it will be `null` if it has been deleted or does not exist). Additional parameters: * `maximumAttachmentCount`: (optional) The maximum number of attachments that may be assigned to a single document of this type. In addition to a static value (e.g. `maximumAttachmentCount: 2`), this property may also be assigned a value dynamically via a function (e.g. `maximumAttachmentCount: function(doc, oldDoc) { ... }`) where the parameters are as follows: (1) the document (if deleted, the `_deleted` property will be `true`) and (2) the document that is being replaced (if any; it will be `null` if it has been deleted or does not exist). Unlimited by default. * `maximumIndividualSize`: (optional) The maximum file size, in bytes, allowed for any single attachment assigned to a document of this type. May not be greater than 20MB (20,971,520 bytes), as Couchbase Server/Sync Gateway sets that as the hard limit per document or attachment. In addition to a static value (e.g. `maximumIndividualSize: 256`), this property may also be assigned a value dynamically via a function (e.g. `maximumIndividualSize: function(doc, oldDoc) { ... }`) where the parameters are as follows: (1) the document (if deleted, the `_deleted` property will be `true`) and (2) the document that is being replaced (if any; it will be `null` if it has been deleted or does not exist). Unlimited by default. * `maximumTotalSize`: (optional) The maximum total size, in bytes, of _all_ attachments assigned to a single document of this type. In other words, when the sizes of all of a document's attachments are added together, it must not exceed this value. In addition to a static value (e.g. `maximumTotalSize: 1024`), this property may also be assigned a value dynamically via a function (e.g. `maximumTotalSize: function(doc, oldDoc) { ... }`) where the parameters are as follows: (1) the document (if deleted, the `_deleted` property will be `true`) and (2) the document that is being replaced (if any; it will be `null` if it has been deleted or does not exist). Unlimited by default. * `supportedExtensions`: (optional) An array of case-insensitive file extensions that are allowed for an attachment's filename (e.g. "txt", "jpg", "pdf"). In addition to a static value (e.g. `supportedExtensions: [ 'png', 'gif', 'jpg' ]`), this property may also be assigned a value dynamically via a function (e.g. `supportedExtensions: function(doc, oldDoc) { ... }`) where the parameters are as follows: (1) the document (if deleted, the `_deleted` property will be `true`) and (2) the document that is being replaced (if any; it will be `null` if it has been deleted or does not exist). No restriction by default. * `supportedContentTypes`: (optional) An array of content/MIME types that are allowed for an attachment's contents (e.g. "image/png", "text/html", "application/xml"). In addition to a static value (e.g. `supportedContentTypes: [ 'image/png', 'image/gif', 'image/jpeg' ]`), this property may also be assigned a value dynamically via a function (e.g. `supportedContentTypes: function(doc, oldDoc) { ... }`) where the parameters are as follows: (1) the document (if deleted, the `_deleted` property will be `true`) and (2) the document that is being replaced (if any; it will be `null` if it has been deleted or does not exist). No restriction by default. * `requireAttachmentReferences`: (optional) Whether every one of a document's attachments must have a corresponding `attachmentReference`-type property referencing it. In addition to a static value (e.g. `requireAttachmentReferences: true`), this property may also be assigned a value dynamically via a function (e.g. `requireAttachmentReferences: function(doc, oldDoc) { ... }`) where the parameters are as follows: (1) the document (if deleted, the `_deleted` property will be `true`) and (2) the document that is being replaced (if any; it will be `null` if it has been deleted or does not exist). Defaults to `false`. * `filenameRegexPattern`: (optional) A regular expression pattern that must be satisfied by each attachment's filename. In addition to a static value (e.g. `filenameRegexPattern: /^foo|bar$/`), this property may also be assigned a value dynamically via a function (e.g. `filenameRegexPattern: function(doc, oldDoc) { ... }`) where the parameters are as follows: (1) the document (if deleted, the `_deleted` property will be `true`) and (2) the document that is being replaced (if any; it will be `null` if it has been deleted or does not exist). No restriction by default. * `expiry`: (optional) Specifies when documents of this type will [expire](https://developer.couchbase.com/documentation/mobile/current/guides/sync-gateway/sync-function-api-guide/index.html#expiry-value) and subsequently get purged from the database. The constraint is only applied when a document is created or replaced. NOTE: This constraint is only supported by Sync Gateway 2.0 and later and, furthermore, it is not supported when using a Walrus database (i.e. the development/testing backend for Sync Gateway databases where contents are typically stored in memory only, rather than Couchbase Server). The constraint's value may be specified in one of the following ways: * an integer whose value is greater than 2,592,000 (i.e. the number of seconds in 30 days), indicating an absolute point in time as the number of seconds since the Unix epoch (1970-01-01T00:00Z) * an integer whose value is at most 2,592,000 (i.e. the number of seconds in 30 days), indicating a relative offset as the number of seconds in the future * a string in [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) date and time format. Unlike for other constraints that accept a date/time string, this constraint does not consider any component of the date to be optional. In other words, the string must include year, month, day, hour, minute, second and time zone offset (e.g. "2018-04-22T14:47:35-07:00"). * a JavaScript [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) object * a function that accepts as parameters (1) the new document and (2) the old document that is being replaced (if any; it will be `null` if it has been deleted or does not exist) and returns any of the aforementioned results * `customActions`: (optional) Defines custom actions to be executed at various events during the generated sync function's execution. Specified as an object where each property specifies a JavaScript function to be executed when the corresponding event is completed. In each case, the function accepts as parameters (1) the new document, (2) the old document that is being replaced/deleted (if any) and (3) an object that is populated with metadata generated by each event. In cases where the document is in the process of being deleted, the first parameter's `_deleted` property will be `true`, so be sure to account for such cases. If the document does not yet exist, the second parameter will be `null` and, in some cases where the document previously existed (i.e. it was deleted), the second parameter _may_ be non-null and its `_deleted` property will be `true`. At each stage of the generated sync function's execution, the third parameter (the custom action metadata parameter) is augmented with properties that provide additional context to the custom action being executed. Custom actions may call functions from the [standard sync function API](http://developer.couchbase.com/documentation/mobile/current/guides/sync-gateway/sync-function-api-guide/index.html) (e.g. `requireAccess`, `requireAdmin`, `requireRole`, `requireUser`, `access`, `role`, `channel`) and may indicate errors via the `throw` statement to prevent the document from being written. The custom actions that are available, in the order their corresponding events occur: 1. `onTypeIdentificationSucceeded`: Executed immediately after the document's type is determined and before checking authorization. The custom action metadata object parameter contains the following properties: * `documentTypeId`: The unique ID of the document type. * `documentDefinition`: The full definition of the document type. 2. `onAuthorizationSucceeded`: Executed immediately after the user is authorized to make the modification and before validating document contents. Not executed if user authorization is denied. The custom action metadata object parameter includes properties from all previous events in addition to the following properties: * `authorization`: An object that indicates which channels, roles and users were used to authorize the current operation, as specified by the `channels`, `roles` and `users` list properties. 3. `onValidationSucceeded`: Executed immediately after the document's contents are validated and before channels are assigned to users/roles and the document. Not executed if the document's contents are invalid. The custom action metadata object parameter includes properties from all previous events but does not include any additional properties. 4. `onAccessAssignmentsSucceeded`: Executed immediately after channel access is assigned to users/roles and before document expiry is set. Not executed if the document definition does not include an `accessAssignments` constraint. The custom action metadata object parameter includes properties from all previous events in addition to the following properties: * `accessAssignments`: A list that contains each of the access assignments that were applied. Each element is an object that represents either a channel access assignment or a role access assignment depending on the value of its `type` property. The assignment types are specified as follows: * Channel access assignments: * `type`: Value of "channel". * `channels`: A list of channels that were assigned to the users/roles. * `usersAndRoles`: A list of the combined users and/or roles to which the channels were assigned. Note that, as per the sync function API, each role element's value is prefixed with "role:". * Role access assignments: * `type`: Value of "role". * `roles`: A list of roles that were assigned to the users. * `users`: A list of users to which the roles were assigned. Note that, as per the sync function API, each role element's value is prefixed with "role:". 6. `onExpiryAssignmentSucceeded`: Executed immediately after document [expiry](https://developer.couchbase.com/documentation/mobile/current/guides/sync-gateway/sync-function-api-guide/index.html#expiry-value) is set and before channels are assigned to the document. Not executed if the document definition does not include an `expiry` constraint. NOTE: Only supported by Sync Gateway 2.0 and later. The custom action metadata object parameter includes properties from all previous events in addition to the following properties: * `expiryDate`: A JavaScript `Date` object that specifies the absolute point in time at which the document will expire and be purged from the database. 7. `onDocumentChannelAssignmentSucceeded`: Executed immediately after channels are assigned to the document. The last step before the sync function is finished executing and the document revision is written. The custom action metadata object parameter includes properties from all previous events in addition to the following properties: * `documentChannels`: A list of channels that were assigned to the document. An example of an `onAuthorizationSucceeded` custom action that stores a property in the metadata object parameter for later use by the `onDocumentChannelAssignmentSucceeded` custom action: ```javascript customActions: { onAuthorizationSucceeded: function(doc, oldDoc, customActionMetadata) { var extraChannel = customActionMetadata.documentTypeId + '-modify'; if (oldDoc && !oldDoc._deleted) { // If the document is being replaced or deleted, ensure the user has the document type's "-modify" channel in addition to one of // the channels from the document definition's "channels" property that was already authorized requireAccess(extraChannel); } // Store the extra modification validation channel name for future use customActionMetadata.extraModifyChannel = extraChannel; }, onDocumentChannelAssignmentSucceeded: function(doc, oldDoc, customActionMetadata) { // Ensure the extra modification validation channel is also assigned to the document channel(customActionMetadata.extraModifyChannel); } } ``` #### Content validation There are a number of validation types that can be used to define each property/element/key's expected format in a document. ##### Simple type validation Validation for simple data types (e.g. integers, floating point numbers, strings, dates/times, etc.): * `string`: The value is a string of characters. Additional parameters: * `mustNotBeEmpty`: If `true`, an empty string is not allowed. Defaults to `false`. * `mustBeTrimmed`: If `true`, a string that has leading or trailing whitespace characters is not allowed. Defaults to `false`. * `regexPattern`: A regular expression pattern that must be satisfied for values to be accepted (e.g. `new RegExp('\\d+')` or `/[A-Za-z]+/`). No restriction by default. * `minimumLength`: The minimum number of characters (inclusive) allowed in the string. No restriction by default. * `maximumLength`: The maximum number of characters (inclusive) allowed in the string. No restriction by default. * `minimumValue`: Reject strings with an alphanumeric sort order that is less than this. No restriction by default. * `minimumValueExclusive`: Reject strings with an alphanumeric sort order that is less than or equal to this. No restriction by default. * `maximumValue`: Reject strings with an alphanumeric sort order that is greater than this. No restriction by default. * `maximumValueExclusive`: Reject strings with an alphanumeric sort order that is greater than or equal to this. No restriction by default. * `mustEqualIgnoreCase`: The item's value must be equal to the specified value, ignoring differences in case. For example, `"CAD"` and `"cad"` would be considered equal by this constraint. No restriction by default. * `integer`: The value is a number with no fractional component. Additional parameters: * `minimumValue`: Reject values that are less than this. No restriction by default. * `minimumValueExclusive`: Reject values that are less than or equal to this. No restriction by default. * `maximumValue`: Reject values that are greater than this. No restriction by default. * `maximumValueExclusive`: Reject values that are greater than or equal to this. No restriction by default. * `float`: The value is a number with an optional fractional component (i.e. it is either an integer or a floating point number). Additional parameters: * `minimumValue`: Reject values that are less than this. No restriction by default. * `minimumValueExclusive`: Reject values that are less than or equal to this. No restriction by default. * `maximumValue`: Reject values that are greater than this. No restriction by default. * `maximumValueExclusive`: Reject values that are greater than or equal to this. No restriction by default. * `boolean`: The value is either `true` or `false`. No additional parameters. * `datetime`: The value is a simplified [ECMAScript ISO 8601](https://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15) date string with optional time and time zone components (e.g. "2016-06-18T18:57:35.328-08:00"). If both time and time zone are omitted, the time is assumed to be midnight UTC. If a time is provided but the time zone is omitted, the time zone is assumed to be the Sync Gateway server's local time zone. Additional parameters: * `minimumValue`: Reject date/times that are less than this. May be either an ECMAScript ISO 8601 date string with optional time and time zone components OR a JavaScript [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) object. No restriction by default. * `minimumValueExclusive`: Reject date/times that are less than or equal to this. May be either an ECMAScript ISO 8601 date string with optional time and time zone components OR a JavaScript `Date` object. No restriction by default. * `maximumValue`: Reject date/times that are greater than this. May be either an ECMAScript ISO 8601 date string with optional time and time zone components OR a JavaScript `Date` object. No restriction by default. * `maximumValueExclusive`: Reject date/times that are greater than or equal to this. May be either an ECMAScript ISO 8601 date string with optional time and time zone components OR a JavaScript `Date` object. No restriction by default. * `date`: The value is a simplified [ECMAScript ISO 8601](https://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15) date string _without_ time and time zone components (e.g. "2016-06-18"). For the purposes of date comparisons (e.g. by way of the `minimumValue`, `maximumValue`, etc. parameters), the time is assumed to be midnight UTC. Additional parameters: * `minimumValue`: Reject dates that are less than this. May be either an ECMAScript ISO 8601 date string without time and time zone components OR a JavaScript `Date` object. No restriction by default. * `minimumValueExclusive`: Reject dates that are less than or equal to this. May be either an ECMAScript ISO 8601 date string without time and time zone components OR a JavaScript `Date` object. No restriction by default. * `maximumValue`: Reject dates that are greater than this. May be either an ECMAScript ISO 8601 date string without time and time zone components OR a JavaScript `Date` object. No restriction by default. * `maximumValueExclusive`: Reject dates that are greater than or equal to this. May be either an ECMAScript ISO 8601 date string without time and time zone components OR a JavaScript `Date` object. No restriction by default. * `time`: The value is a simplified [ECMAScript ISO 8601](https://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15) time string _without_ date and time zone components (e.g. "18:57:35.328"). Additional parameters: * `minimumValue`: Reject times that are less than this. Must be an ECMAScript ISO 8601 time string without date and time zone components. * `minimumValueExclusive`: Reject times that are less than or equal to this. Must be an ECMAScript ISO 8601 time string without date and time zone components. * `maximumValue`: Reject times that are greater than this. Must be an ECMAScript ISO 8601 time string without date and time zone components. * `maximumValueExclusive`: Reject times that are greater than or equal to this. Must be an ECMAScript ISO 8601 time string without date and time zone components. * `timezone`: The value is a simplified [ECMAScript ISO 8601](https://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15) time zone string _without_ date and zone components (e.g. "Z" or "-05:00"). Additional parameters: * `minimumValue`: Reject time zones that are less than this. Must be an ECMAScript ISO 8601 time zone string. * `minimumValueExclusive`: Reject time zones that are less than or equal to this. Must be an ECMAScript ISO 8601 time zone string. * `maximumValue`: Reject time zones that are greater than this. Must be an ECMAScript ISO 8601 time zone string. * `maximumValueExclusive`: Reject time zones that are greater than or equal to this. Must be an ECMAScript ISO 8601 time zone string. * `enum`: The value must be one of the specified predefined string and/or integer values. Additional parameters: * `predefinedValues`: A list of strings and/or integers that are to be accepted. If this parameter is omitted from an `enum` property's configuration, that property will not accept a value of any kind. For example: `[ 1, 2, 3, 'a', 'b', 'c' ]` * `uuid`: The value must be a string representation of a [universally unique identifier](https://en.wikipedia.org/wiki/Universally_unique_identifier) (UUID). A UUID may contain either uppercase or lowercase letters so that, for example, both "1511fba4-e039-42cc-9ac2-9f2fa29eecfc" and "DFF421EA-0AB2-45C9-989C-12C76E7282B8" are valid. Additional parameters: * `minimumValue`: Reject UUIDs that are less than this. No restriction by default. * `minimumValueExclusive`: Reject UUIDs that are less than or equal to this. No restriction by default. * `maximumValue`: Reject UUIDs that are greater than this. No restriction by default. * `maximumValueExclusive`: Reject UUIDs that are greater than or equal to this. No restriction by default. * `attachmentReference`: The value is the name of one of the document's file attachments. Note that, because the addition of an attachment is often a separate Sync Gateway API operation from the creation/replacement of the associated document, this validation type is only applied if the attachment is actually present in the document. However, since the sync function is run twice in such situations (i.e. once when the _document_ is created/replaced and once when the _attachment_ is created/replaced), the validation will be performed eventually. The top-level `allowAttachments` property should be `true` so that documents of this type can actually store attachments. Additional parameters: * `supportedExtensions`: An array of case-insensitive file extensions that are allowed for the attachment's filename (e.g. "txt", "jpg", "pdf"). Takes precedence over the document-wide `supportedExtensions` constraint for the referenced attachment. No restriction by default. * `supportedContentTypes`: An array of content/MIME types that are allowed for the attachment's contents (e.g. "image/png", "text/html", "application/xml"). Takes precedence over the document-wide `supportedContentTypes` constraint for the referenced attachment. No restriction by default. * `maximumSize`: The maximum file size, in bytes, of the attachment. May not be greater than 20MB (20,971,520