0.5.2 • Published 8 years ago

kinvey-backend-sdk v0.5.2

Weekly downloads
18
License
Apache-2.0
Repository
github
Last release
8 years ago

Kinvey Backend SDK (beta)

This is the SDK for Backend code execution for kinvey-hosted Data Link Connectors and Logic tasks. The module provides a framework for building kinvey-backed Data Link Connectors easily.

This module provides an easy way to connect to a Kinvey Business Logic (BL) instance running on docker. It is up to the user to download, configure and start the docker image itself before running code using this module. Two utility functions are provided to automate setting up the docker image.

Installation

To install this project, add it to your package.json file and install it via npm

npm install kinvey-backend-sdk

To use this module, require it in your project and

const sdk = require('kinvey-backend-sdk');

You then must initialize the sdk to retrieve a reference to the backend service:

const service = sdk.service((err, service) => {
  // code goes here
};

When running locally, you can specify a host and port to listen on by passing an options object with an optional host and port. If no host/port is specified, localhost:10001 will be used:

const service = sdk.service({ host: 'somehost', port: 7777 }, (err, service) => {
  // code goes here
});

To run your code locally, execute node . in the root of your project. Routes conform to the Kinvey Data Link specification.

DataLink framework

The DataLink framework can be accessed via the sdk's dataLink property.

const dataLink = service.dataLink;

Registering ServiceObjects

Once you initialize the DataLink framework, you define your entites by defining ServiceObjects, and then wire up data access event handlers to those ServiceObjects. To register a ServiceObject, use the serviceObject method of the dataLink framework:

// To register the 'widgets' ServiceObject:
const widgets = service.dataLink.serviceObject('widgets');

Data Events

Each ServiceObject exposes data events that are invoked by Kinvey collections. The data event takes a single handler function to be executed.

eventdescription
onInsertexecuted on inserts (or POST to REST API)
onUpdateexecuted on updates (or PUT to the REST API)
onDeleteByIdexecuted when a single entity is to be deleted
onDeleteByQueryexecuted when a query is included as part of a DELETE
onDeleteAllexecuted when the DELETE command is invoked
onGetByIdget a single entity by Id
onGetByQueryretrieve results based on a query
onGetAllget all entities in a given ServiceObject
onGetCountget the count of the entities in a ServiceObject
onGetCountByQueryget the count of the entities in a query result

For example, to get all entities in widgets:

widgets.onGetAll(callbackFunction);

Data Handler Functions

The data events take a handler funciton, which takes three arguments: request, complete, and modules. request represents the request made to Kinvey, and complete is a completion handler for completing the data request. The modules modules argument is an object containing several libraries for accessing Kinvey functionality via your service (for more information, see the section on modules).

request object

The request object contains the following properties

propertydescription
methodthe HTTP method (GET, POST, PUT, DELETE)
headersthe HTTP request headers
entityIdthe entityId, if specified
serviceObjectNamethe name of the serviceObject
bodythe HTTP body
querythe query object

Completion Handler

The completion handlers object follows a builder pattern for creating the datalink's response. The pattern for the completion handler is complete(<entity>).<status>.<done|next>

For example, a sample completion handler is:

complete(myEntity).ok().next()
complete

The complete handler takes either an entity, an array of entities, or an error description. The result of the complete handler is an object of status functions.

// Sets the response to include an entity.
complete({"foo", "bar"});

// Sets the response to include an array of entities
complete([{"foo":"bar"}, {"abc":"123}]);

// Sets the response to an error string, to be used with error status codes
complete("Record 123 was not found");
status functions

Status functions set the valid status codes for a Data Link Connector. The status function also sets the body to a Kinvey-formatted error, and uses the value passed into the complete function as the debug property, if it is present.

The available status functions are:

FunctionStatus CodeDescription
ok200Used for a normal success response
created201Used for creating new records
accepted202Used when the request has been submitted for processing, but will be processed asynchronously
notFound404Used when the entity or entities could not be found
badRequest400Used when the request is invalid
unauthorized401Used when the request has not been authorized for the given user context
forbidden403Used when the specific request is forbidden for some reason
notAllowed405Used when the specific request or request method is not allowed
notImplemented501Used when the specific handler has no implementation
runtimeError550Used for custom runtime errors

For example:

// Return that the record has been created
complete(myRecord).created();

// Entity wasn't found
complete("The given entity wasn't found").notFound();
End processing

Once the status is set, you can end the processing of the dataLink request with either done or next. Most requests should normally end with next, which will continue the Kinvey request pipeline with Business Logic. done will return the response that was set in the DataLink, and end request processing without executing any further business logic.

// This will continue the request chain
complete(myEntity).ok().next();

// This will end the request chain with no further processing
complete(myEntity).ok().done();

Example

The following is an example

const sdk = require('kinvey-backend-sdk');
const service = sdk.service(function(err, service) {
  const dataLink = service.dataLink;   // gets the datalink object from the service

  function getRecordById(request, complete, modules) {
    let entityId = request.entityId;
    let entity = null;

    // Do some logic to get the entity id from the remote data store
    // Assume that data is retrieved and stored in "entity" variable

    // After entity is retrieved, check to see if it exists
    if (typeof entity === 'undefined' || entity === null) {
      return complete("The entity could not be found").notFound().next();
    } else  {
      // return the entity
      return complete(entity).ok().next();
    }
  }

  // set the serviceObject
  const widgets = dataLink.serviceObject('widgets');

  // wire up the event that we want to process
  widgets.onGetById(getRecordById);

  // wire up the events that we are not implementing
  widgets.onGetByQuery(notImplementedHandler);
  widgets.onGetAll(notImplementedHandler);
  widgets.onGetCount(notImplementedHandler);
  widgets.onInsert(notImplementedHandler);
  widgets.onUpdate(notImplementedHandler);
  widgets.onDeleteAll(notImplementedHandler);
  widgets.onDeleteByQuery(notImplementedHandler);
  widgets.onDeleteById(notImplementedHandler);
};

Business Logic framework

The Business Logic framework can be accessed via the sdk's businessLogic property.

const dataLink = sdk.businessLogic;

Registering Business Logic Handlers

In order to register a business logic handler, you define that handler and give it a name by using the register method of the businessLogic framework:

// To register the '' handler:
const widgets = sdk.businessLogic.register('someEventHanlerName', eventHandlerCallbackFunction);

In the console, when you define business logic, you will be presented your list of event handlers to tie to a collection hook or custom endpoint.

Business Logic Handler Functions

Like the DataLink handlers, business logic events take a handler funciton, which takes three arguments: request, complete, and modules. request represents the request made to Kinvey, and complete is a completion handler for completing the business logic. The modules modules argument is an object containing several libraries for accessing Kinvey functionality via your service (for more information, see the section on modules).

request object

The request object contains the following properties:

propertydescription
methodthe HTTP method (GET, POST, PUT, DELETE)
headersthe HTTP request headers
entityIdthe entityId included in the request, if specified
collectionNamethe name of the collection
bodythe HTTP body
querythe query object

Completion Handler

The completion handlers object follows a builder pattern for creating the business logic's response. The pattern for the completion handler is complete(<entity>).<status>.<done|next>

For example, a sample completion handler is:

complete(myEntity).ok().next()

The entity is optional, as it will not always be returned.

You can also alter the request object by making changes to the query, body, or headers objects. If the request body is modified, it will be persisted back for subsequent steps in the request pipeline.

complete

The complete handler takes either an entity, an array of entities, or an error description. The result of the complete handler is an object of status functions.

// No entity as part of the response
complete();

// Sets the response to include an entity.
complete({"foo", "bar"});

// Sets the response to include an array of entities
complete([{"foo":"bar"}, {"abc":"123}]);

// Sets the response to an error string, to be used with error status codes
complete("Record 123 was not found");
status functions

Status functions set the valid status codes for the request. The status function also sets the body to a Kinvey-formatted error, and uses the value passed into the complete function as the debug property, if it is present.

The available status functions are:

FunctionStatus CodeDescription
ok200Used for a normal success response
created201Used for creating new records
accepted202Used when the request has been submitted for processing, but will be processed asynchronously
notFound404Used when the entity or entities could not be found
badRequest400Used when the request is invalid
unauthorized401Used when the request has not been authorized for the given user context
forbidden403Used when the specific request is forbidden for some reason
notAllowed405Used when the specific request or request method is not allowed
notImplemented501Used when the specific handler has no implementation
runtimeError550Used for custom runtime errors

For example:

// Return that the record has been created
complete(myRecord).created();

// Entity wasn't found
complete("The given entity wasn't found").notFound();
End processing

Once the status is set, you can end the processing of the business logic request with either done or next. Most requests should normally end with next, which will continue the Kinvey request pipeline with Business Logic. done will return the response that was set in the handler, and end request processing without executing any further part of the kinvey request pipeline.

// This will continue the request chain
complete(myEntity).ok().next();

// This will end the request chain with no further processing
complete(myEntity).ok().done();

Example

The following is an example

const sdk = require('kinvey-backend-sdk');
const request = require('request'); // assumes that the request module was added to package.json
const service = sdk.service(function(err, service) {
  
  const bl = service.businessLogic;   // gets the businessLogic object from the service

  function getRedLineSchedule(req, complete, modules) {
    requset.get('http://developer.mbta.com/Data/Red.json', (err, response, body) => {
      // if error, return an error
      if (err) {
        return complete("Could not complete request").runtimeError().done();
      }
      
      //otherwise, return the results
      return complete(body).ok().done();
    });
    
   }

  // set the handler
  bl.register('getRedLineData', getRedLineSchedule);
};

You can include both dataLink and business logic handlers in the same flex service, but it is recommended to separate the two.

Executing a long-running script

Because the services are persisted, you can execute long running tasks that run in the background. However, all requests still need to be completed in less than 60 seconds.

To accomplish this, you can execute a function asynchronously using one of the Timer functions from node.js: setImmediate, setTimeout, or setInterval.

For example:

const sdk = require('kinvey-backend-sdk');
const request = require('request'); // assumes that the request module was added to package.json
const service = sdk.service(function(err, service) {
  
  const bl = service.businessLogic;   // gets the businessLogic object from the service

    function calcSomeData() {
        // do something
    }

  function calcAndPostData() {
    auth = {
      user: '<MY_APP_KEY>',
      pass: '<MY_APP_SECRET>'
     };
     
    options = {
        url: 'https://baas.kinvey.com/appdata/<MY_APP_KEY?/someCollection',
        auth: auth,
        json: {
            someData: calcSomeData(),
            date: new Date().toString()
        }
     }
        
    request.get(options, (err, response, body) => {
      // if error, return an error
      if (err) {
        return console.log('Error: ' + err);
      }
      
      //otherwise, just return
      return; 
    });
    
  }
   
  function initiateCalcAndPost(req, complete, modules) {
    // Since the calc and post data function may take a long time, execute it asynchronously
    setImmediate(calcAndPostData);  
    
    // Immediately complete the business logic script.  The response will be returned to the caller, and calcAndPostData will execute in the background.  
    complete().accepted().done();
    
  // set the handler to point to the initiateCalcAndPost header
  bl.register('getRedLineData', initiateCalcAndPost);
};

In the above example, the handler receives a request and executes the initiateCalcAndPost function. The function schedules an immediate asynchronous execution of calcAndPostData, and then executes the complete handler, returning control to the client and sending the response (in this case, accepted, because the request is accepted for processing). The service stays running in the background, and the long-running calcAndPostData function is executed asynchronously.

Modules

Modules are a set of libraries inteded for accessing Kinvey-specific functionality. The optional modules argument is passed as the third argument to all handler functions. For example:

// data handler
function onGetById(request, complete, modules) {
  const appKey = modules.backendContext.getAppKey();
  // etc...
}

You can use any non-Kinvey libraries or modules you want by including them in your package.json as you would for a standard node.js project.

The following modules are available:

  • backendContext Provides methods to access information about the current backend context.
  • dataStore Fetch, query, and write to Kinvey collections.
  • email Send Email notifications
  • Kinvey Entity Kinvey entity utilities
  • kinveyDate Kinvey date utilities
  • push Send push notifications to a user's device
  • Query Create queries to be used by the dataStore.
  • requestContext Provides methods to access information about the current request context.
  • tempObjectStore Key-value store for persisting temporary data between a pre- and post-hook.

backendContext

Used to return data and perform functions against the current backend context.

MethodBehavior
getAppKey()Returns the current backend appKey
getAppSecret()Returns the current backend appSecret
getMasterSecret()Returns the current backend masterSecret

One common use case for these functions is for calling requests against the current backend to provide a method to obtain the correct authentication credentials. This allows for BL scripts to be migrated from backend to backend without code changes, such as in the case of seperate development and production backends.

const request = require('request');

function myHandler(request, complete, modules){
  
  const context = modules.backendContext;

  const appKey = context.getAppKey();
  const masterSecret = context.getMasterSecret();
  
  const uri = 'https://' + request.headers.host + '/appdata/'+ appKey +'/myCollection/';
  const authString = "Basic " + utils.base64.encode(appKey + ":" + masterSecret);
  const requestOptions = {
    uri:uri, 
    headers: {
      "Authorization":authString
    }
  };
  
  var auth = request.get(requestOptions, (error, res, body) => {
    if (error){
      complete(error).runtimeError.done();
    } else {
      complete(body).ok().next();
    }
  });
}

dataStore

Use the dataStore module to interact with Kinvey Data at the collection level. The dataStore module can be used to interact with both the Kinvey DataStore or with external data such as RAPID connectors or Flex Data Services.

To initialize the DataStore:

const store = modules.dataStore();

The dataStore method also takes an optional options argument is an optional object containing store options for the current store. The options are:

OptionDescription
useMasterSecretUses the mastersecret credentials to access the datastore if set to true. If not included or set to false, will use the current user credentials.
skipBlIf set to true, skips BL processing when accessing the datastore. If false or not included, it will default to executing any BL associated with the store.

For example:

const options = {
  useBl: true,
  useMasterSecret: false
}

const store = modules.dataStore(options);

The dataStore object contains a single method, collection for specifying the collection to query.

const myCollection = modules.dataStore().collection('myCollection');

The collection object contains methods for accessing data within the collection. All methods take a callback(err, results) function.

MethodDescription
find(query, callback)Finds the records contained in the query. The query is a Query object created with modules.Query. If no Query object is supplied, the find method will return all entities in the collection.
findById(entityId, callback)Finds a single entity by its _id.
save(entity, callback)Saves the provided entity to the dataStore, either as an insert or update. (NOTE: if an _id is provided and that _id already exists, the save will update the existing entity by replacing it with the entity passed into this function).
remove(query, callback)Removes the records contained in the query. The query is a Query object created with modules.Query.
removeById(entityId, callback)Removes a single entity by its _id.
count(query, callback)Gets a count of all records that would be returned by the query. The query is a Query object created with modules.Query. If no Query object is supplied, the count method will return a count of the number of entities in the collection.

For example:

  const store = dataStore({ useMasterSecret: true });
  const products = store.collection('products');
  products.findById(1234, (err, result) => {
    if (err) {
      return complete(err).runtimeError().done();
    }
    result.status = 'Approved';
    products.save(result, (err, savedResult) => {
      if (err) {
        return complete(err).runtimeError().done();
      }
      complete(savedResult).ok().next();
    });
  });
});

NOTE When testing dataStore in particular locally, special headers need to be added to your local tests. These headers will be added automatically by Kinvey in production use. For information on the required headers, see the section on testing locally

Email

Use the email module to send email to any valid email address.

Available Methods:

MethodAction
send(from, to, subject, text_body, reply_to, html_body, cc, bcc, callback)Send an email to to from from using the subject and body. reply_to is an optional argument for the reply to line of the message. For the email body, text_body is for the plain-text version of the email and is required. html_body is an optional parameter that accepts an HTML-formatted string, which creates an optional html body for the email message. cc and bcc are optional arguments for adding cc and bcc users.

For example, to send a text email:

	const email = modules.email;
	email.send('my-app@my-app.com',
			   request.body.friendEmail,
			   'Join my octagon in my-app!',
			   "You've been invited to join " + request.body.name + "'s octagon in my-app!", function(err, result) {
			   complete.ok().next();
			   });

To send an HTML email, it is important to note that the reply-to must be included; if you want the reply-to to be the same as the from address, you can either also pass the from address in the reply_to argument, or simply pass a null value (which will default the reply to to the from address). For example:""

	const email = modules.email;
	email.send('my-app@my-app.com',
           request.body.friendEmail,
           'Join my octacgon in my-app!',
           "You've been invited to join " + request.body.name + "'s octagon in my-app!",
           null,
           '<html>You've been invited to join  <b>'+request.body.first_name+'</b>'s octagon in my-app!</html>', callback);

Email calls are asynchronous in nature. They can be invoked without a callback, but are not guaranteed to complete before continuing to the next statement. However, once invoked they will complete the sending of the email, even if the function ends via a complete handler.

Note: The email module is currently only available for services running on the Kinvey Microservices Runtime, and is not available externally or for local testing.

Kinvey Entity

A Kinvey Entity is a JSON object containing Kinvey-specific metadata for use with AppData collections. Kinvey Entities also contain methods to manipulate Kinvey metadata.

The kinveyEntity module provides the following methods:

MethodBehavior
entity()Returns a Kinvey Entity with system generated _id.
entity(id)Returns a Kinvey Entity with the supplied id as the _id property.
entity(JsonObject)Updates JsonObject with Kinvey-specific metadata and functions, and returns a Kinvey Entity as the result.
isKinveyEntity(someObject)Returns true if someObject is a Kinvey Entity returned from one of the entity() methods above.

For example, to create a Kinvey Entity with only Kinvey metadata:

function someHandler(request, complete, modules) {
  complete(modules.kinveyEntity.entity()).ok().done();
}

The response would contain an entity containing no additional attributes, except for Kinvey metatada attributes:

{
  "_acl": {
    "creator": "kid_VT5YYv2KWJ"
  },
  "_kmd": {
    "lmt": "2013-05-08T13:48:36+00:00",
    "ect": "2013-05-08T13:48:36+00:00"
  },
  "_id": "518a57b4d79b4d4308000002"
}

To use your own _id, you can simply pass that ID as a String into the method:

function handler(request, complete, modules){
  var myID = "000-22-2343";
  complete()modules.kinvey.entity(myID)).ok().done();
}
{
  "_acl": {
    "creator": "kid_VT5YYv2KWJ"
  },
  "_kmd": {
    "lmt": "2013-05-08T13:51:02+00:00",
    "ect": "2013-05-08T13:51:02+00:00"
  },
  "_id": "000-22-2343"
}

You can add Kinvey metatada to your own JavaScript object by passing that object into the function.

function handler(request, complete, modules){
  function employee() {
    this.firstName = "John";
    this.lastName = "Doe";
    this.status = "Active";
    this.dob = "5/5/1985";
    this.doh = "1/12/2012";
  }
    
  complete(modules.kinvey.entity(new employee())).ok().done();
}

This example results in the Kinvey metadata being added to your employee object.

{
  "firstName": "John",
  "lastName": "Doe",
  "status": "Active",
  "dob": "5/5/1985",
  "doh": "1/12/2012",
  "_acl": {
    "creator": "kid_VT5YYv2KWJ"
  },
  "_kmd": {
    "etc": "2013-05-08T13:57:11+00:00",
    "lmt": "2013-05-08T13:57:11+00:00"
  },
  "_id": "518a59b7d79b4d4308000003"
}

Note that you can add properties directly to a Kinvey Entity and pass it directly into a `dataStore method that takes an entity as an argument:

function handler(request, complete, modules) {
	const entity = modules.kinveyEntity.entity();
	entity.name = "Sachin";
	modules.dataStore().collection('People').save(entity, (err, result) => 
	  if (err) {
	    // handle error
	  } else {
	    complete(result).ok().next(); 
	  }
	});
}

Note: If you call entity(JsonObject) with an object that already contains Kinvey metadata, the original metadata will be maintained. Only lmt will be updated with the current date and time.

_acl Namespace

Kinvey Entities returned from entity() are also imbued with an _acl namespace containing methods for accessing that entity's user and group access privileges:

MethodBehavior
_acl.getCreator()Return entity creator user ID as a String
_acl.getReaders()Return Array of String containing user IDs with read access
_acl.getWriters()Return Array of String containing user IDs with write access
_acl.getReaderGroups()Return Array of String containing group IDs with read access
_acl.getWriterGroups()Return Array of String containing group IDs with write access
_acl.addReader(userId)Grant read access to userId, passed as String. Returns the _acl namespace for method chaining.
_acl.addWriter(userId)Grant write access to userId, passed as String. Returns the _acl namespace for method chaining.
_acl.addReaderGroup(groupId)Grant read access to groupId, passed as String. Returns the _acl namespace for method chaining.
_acl.addWriterGroup(groupId)Grant write access to groupId, passed as String. Returns the _acl namespace for method chaining.
_acl.removeReader(userId)Revoke read access for userId, passed as String. Returns the _acl namespace for method chaining. (userId will retain read access if part of a group with read access)
_acl.removeWriter(userId)Revoke write access for userId, passed as String. Returns the _acl namespace for method chaining. (userId will retain write access if part of a group with write access)
_acl.removeReaderGroup(groupId)Revoke group read access for groupId, passed as String. Returns the _acl namespace for method chaining.
_acl.removeWriterGroup(groupId)Revoke group write access for groupId, passed as String. Returns the _acl namespace for method chaining.
_acl.getGloballyReadable()Return bool indicating whether entity is readable by all users
_acl.getGloballyWritable()Return bool indicating whether entity is writable by all users
_acl.setGloballyReadable(gr)Set global readability to gr, passed as bool. Returns the _acl namespace for method chaining.
_acl.setGloballyWritable(gw)Set global writability to gw, passed as bool. Returns the _acl namespace for method chaining.
ACL method chaining

All _acl methods can be chained to the modules.kinveyEntity.entity() by chaining the _acl namespace followed by the desired method. Furthermore the _acl modifier methods (those that return the _acl namespace) can be chained to each other. Since the _acl modifier methods return the _acl namespace, there's no need to restate the _acl namespace for the next method in the chain.

For example, the following line will create a Kinvey Entity, add a reader, add a writer, then return all readers. NOTE: Since getReaders() returns an array, no futher _acl methods can be chained to it.

modules.kinveyEntity.entity(someObject)._acl.addReader(readUserId).addWriter(writeUserId).getReaders();
Example

The example below uses kinveyEntity.entity()._acl along with the Request Context module to grant write access on the requested entity to the user making the request:

function handler(request, complete, modules) {
	const currentUser = modules.requestContext.getAuthenticatedUserId();
    const serviceObjectName = request.collectionName;
	const entityId = request.entityId;
	
	const collection = modules.dataStore().collection(collectionName);
	
	collection.findById({entityId, (err, doc) => {
	  	if (err){
	  	  //handle error
	  	} else if (doc) {
                  
	  	  const entity = modules.kinveyEntity.entity(doc)._acl.addWriter(currentUser);
	  	  //Note we directly pass the Kinvey Entity to the save method. 
	  	  collection.save(entity, (err, result) => {
	  	    complete(result).ok().next();
	  	  });
	  	} else {
                  // entity not found
                  complete(new Error "Entity not found").runtimeError().done();
                }
	});
}

Kinvey Date

A utility for converting dates.

MethodBehavior
toKinveyDateString(date)Converts a Date object, a moment, or a date string into the Kinvey date format. If the passed in date is not a valid date, "Invalid date" will be returned.
fromKinveyDateString(str, format)Decodes a Kinvey date string into the specified format. Valid formats are 'date', 'moment' and 'string'. If no format is provided, a JavaScript Date object is returned. If the passed in str is not a valid date, "Invalid date" will be returned.

For example, to convert a date to to a Kinvey Date string:

myDate = new Date();
kinveyDate = modules.kinveyDate.toKinveyDateString(myDate);

To convert a Kinvey Date string to a JavaScript date object:

myDate = modules.kinveyDate.fromKinveyDateString(kinveyDate, 'date');
// note:  this could also be accomplished via modules.kinveyDate.fromKinveyDateString(kinveyDate);

To convert a Kinvey Date string to a moment:

myMoment = modules.kinveyDate.fromKinveyDateString(kinveyDate, 'moment');

To convert a Kinvey Date into a standard ISO-8601 date string

myDateString = modules.kinveyDate.fromKinveyDateString(kinveyDate, 'string');

Push

Use the push module to send a push notification to a device registered with your app.

Available Methods:

MethodAction
broadcastMessage(message, calback)Broadcasts message to all devices registered with this app
sendMessage(users, message, callback)Sends messages to the users in users
sendPayload(users, iOSAps, iOSExtras, androidPayload, callback)Sends a custom payload to the users in users
broadcastPayload(iOSAps, iOSExtras, androidPayload, callback)Broadcasts custom payloads to all devices registered with this app.

For example, to send a broadcast message:

	var push = modules.push;
	if (request.body.sendMessageToAll){
		push.broadcastMessage(request.body.message, (err, result) => {
 		  complete.ok().next();
 		});
	}

The users argument to send is either an array of Kinvey User documents or a single user document. The message will be sent to all iOS or Android devices that are contained in the documents in users.

You can query the user collection with the dataStore module to get a list of users.

For example:

	const push = modules.push;
	const dataStore = dataStore();
	const query = new modules.Query();
	
	query.equalTo('firstName', request.body.firstName);
	
	dataStore.collection('user').find(query, (err, userColl) => {
	  if (err) {
		complete(err).runtimeError().done();
	  } else {
		userColl.forEach((user) => {
		  push.sendMessage(user, `People who are named ${user.firstName} are awesome!", (err, result) => {
		  //complete here 
		  });
		});
	  }
	});
Payloads

The methods sendPayload and broadcastPayload allow for more robust push messages. For iOS, the payload consists of two arguments, iOSAps and iOSExtras. iOSAps is a JSON object that can have the following properties:

FieldValue
alertAn alert message to display to the user
badgeThe badge number to appear on the notification
soundThe name of the sound file in the application package to play
content-availableIf set to 1, the remote notification acts as a "silent" notification

iOSExtras is a JSON object with custom name-value pairs to be included in the push notification.

For Android, the payload is contained in the androidPayload attribute, which is any JSON object to be sent to Google Cloud Messaging (GCM).

Each of the arguments above are null-safe.

For example, to send a broadcast with payloads:

const iOSAps = { alert: "You have a new message", badge: 2, sound: "notification.wav" }
const iOSExtras = {from: "Kinvey", subject: "Welcome to Business Logic"}
const androidPayload = {message: "You have a new Message", from: "Kinvey", subject: "Welcome to BL" }
const push = modules.push;
push.broadcastPayload(iOSAps, iOSExtras, androidPayload, callback);

Push calls are asynchronous in nature. They can be invoked without a callback, but are not guaranteed to complete before continuing to the next statement. However, once invoked they will complete the sending of the push messages, even if the business logic is terminated via a completion handler.

Note: The email module is currently only available for services running on the Kinvey Microservices Runtime, and is not available externally or for local testing.

Query

The Query module allows you to build queries for use in the dataStore module.

const query = new modules.Query();

Operators

All operator methods as exposed by the Query module follow the same pattern: the first argument must be the field under condition, while the other arguments specify the exact condition on that field. All operators return the query itself, so it is easy to concatenate multiple conditions on one line.

For example, to select all models with a rate between 25 and 50:

const query = new modules.Query();
query.greaterThanOrEqualTo('rate', 25).lessThanOrEqualTo('rate', 50);
Comparison Operators
  • equalTo matches if the field is = the supplied value.
  • greaterThan matches if the field is > the supplied value.
  • greaterThanOrEqualTo matches if the field is >= the supplied value.
  • lessThan matches if the field is < the supplied value.
  • lessThanOrEqualTo matches if the field is <= the supplied value.
  • notEqualTo matches if the field is != the supplied value.
  • exists matches if the field exists.
  • mod matches if the field modulo the supplied divisor (second argument) has the supplied remainder (third argument).
  • matches matches if the field matches the supplied regular expression.

Note: Regular expressions need to be anchored (prefixed with ^), and case sensitive. To do case insensitive search, create a normalized (i.e. all lowercase) field in your collection and perform the match on that field.

Array Operators
  • contains matches if any of the supplied values is an element in the field.
  • containsAll matches if the supplied values are all elements in the field.
  • notContainedIn matches if the supplied value is not an element in the field.
  • size matches if the number of elements in the field equals the supplied value.

Modifiers

Query modifiers control how query results are presented. A distinction is made between limit, skip, and sort modifiers.

Limit and Skip

Limit and skip modifiers allow for paging of results. Set the limit to the number of results you want to show per page. The skip modifier indicates how many results are skipped from the beginning.

// Show results 20–40
const query = new modules.Query();
query.limit = 20;
query.skip = 20;
Sort

Query results are sorted either in ascending or descending order. It is possible to add multiple fields to sort on.

// Sort on last name (ascending), then on age (descending).
const query = new modules.Query();
query.ascending('last_name');
query.descending('age');

Note Data is sorted lexicographically, meaning B comes before a, and 10 before 2.

Field Selection

By default, all fields in an entity will be retrieved. You can, however, specify specific fields to retrieve. This can be useful to save bandwidth.

const query = new modules.Query();
query.fields = [ 'last_name', 'age' ];

Saving entities after retrieving them using Field Selection will result in the loss of all fields not selected. Further, these partial entities will not be available for use with Caching & Offline Saving.

Compound Queries

You can combine filters with modifiers within a single query.

// Returns the first five users with last_name “Doe”, sorted by first_name.
const query = new modules.Query();
query.limit = 5;
query.equalTo('last_name', 'Doe');
query.ascending('first_name');
Joining Operators

It is very easy to join multiple queries into one. In order of precedence, the three joining operators are listed below in order of precendence.

  • and joins two or more queries using a logical AND operation.
  • nor joins two or more queries using a logical NOR operation.
  • or joins two or more queries using a logical OR operation.

The example below demonstrates how to join two separate queries.

const query = new modules.Query();
query.equalTo('last_name', 'Doe');
const secondQuery = new modules.Query();
secondQuery.equalTo('last_name', 'Roe')

// Selects all users with last_name “Doe” or “Roe”.
query.or(secondQuery);

Alternatively, the snippet above can be shortened using the join operator inline.

// Selects all users with last_name “Doe” or “Roe”.
const query = new modules.Query();
query.equalTo('last_name', 'Doe').or().equalTo('last_name', 'Roe');

You can build arbitrary complex queries using any join operators. The rule of thumb is to take the precendence order into account when building queries to make sure the correct results are returned.

Location Queries

Note Location Queries are only available on fields named _geoloc. The value of this field should be an array holding two elements: longitude and latitude (in that order). Kinvey indexes that attribute for optimal location querying.

The following Location Operators are supported by modules.Query:

  • near matches if the field is within the supplied radius (in miles) of the supplied coordinate.
  • withinBox matches if the field is within the box specified by the two supplied coordinates.
  • withinPolygon matches if the field is within the polygon specified by the supplied list of coordinates.

For example, a query to retrieve all restaurants close (10 mile radius) to a users’ current location could look as follows:

// Get users current position
function handler(request, complete, modules) {
  const coord = [request.body.longitude, request.body.latitude];

  // Query for restaurants close by.
  var query = new modules.Query();
  query.near('_geoloc', coord, 10);

  var dataStore = modules.dataStore().collection('restaurants');
  var stream = dataStore.find(query, (err, result) => {  
  	if (err) {
  	  return complete(err).runtimeError().done():
  	}
  	complete(result).ok().next();
  });
});

requestContext

Used to return data and perform functions against the current request context.

MethodBehavior
getAuthenticatedUserId()Returns the ID of the user who submitted the request. When the app or master secret is used to authenticate, the application ID is returned.
getAuthenticatedUsername()Returns the username of the user who submitted the request.
getAuthoritativeUsername()Returns the authoritative username of the user who submitted the request. The authoritative username is determined based on the type of user. If the user logged in with a token obtained from Mobile Identity Connect, and a MIC Enterprise ID is defined, this method returns the value defined in that field from the enterprise identity source. If the user logged in in with a MIC token and no MIC enterprise id is defined, or a social login, the social provider's id is used. If the user logs in with a Kinvey native login, the user.username value is used.
getSecurityContext()Returns the context of the current user - possible values are user for a user account, app for the app account with app secret, and master for the app account with master secret

Custom Request Properties

Kinvey Client libraries provide API to pass Custom Request Properties when communicating with Kinvey services. The requestContext module provides methods to read and manipulate those properties.

MethodBehavior
getCustomRequestProperty(propertyName)Takes string value propertyName and returns value of key propertyName from the Custom Request Properties object. Returns undefined if key propertyName does not exist.
setCustomReqeustProperty(propertyName, propertyValue)Creates Custom Request Property propertyName using the provided string propertyName, and assigns propertyValue as its value. If propertyName doesn't exist, it will be created. propertyValue can be any valid JSON value.

The Custom Request Properties object will be available throughout the entire Kinvey Request pipeline. For example, you can add/modify a Custom Request Property in a preFetch hook, and those changes will be available in a postFetch hook on the same transaction.

Example Code
//Assume {officeLocation: 'Paris'} was provided as the Custom Request Properties object

function handlerForPrefetch(request, complete, modules){
 
  const requestContext = modules.requestContext;
  const officeLocation = requestContext.getCustomRequestProperty('officeLocation');
  
  if (officeLocation === 'Paris'){
  	  try {
  	     //Perform some 'preprocessing' logic for requests from Paris
  	     //...
  	     //Set didPreprocess to true in the Custom Request Properties
  	     requestContext.setCustomRequestProperty('didPreprocess', true);
  	  } catch (error) {
	     //If preprocessing fails due to an error, set didPreprocess to false
	     requestContext.setCustomRequestProperty('didPreprocess', false);
  	  }  
  }
 
  //Continue the execution
  complete().ok().next();
  
}

The above code checks the Custom Request Properties for a property, officeLocation. If the officeLocation is Paris, then some custom pre-processing is done as part of the pre-fetch hook. If the pre-processing succeeds, we set didPreprocess to true in the Custom Request Properties. If an error is encountered during preprocessing, we set didPreprocess to false.

We could implement a post-fetch hook that looks for the didPreprocess flag on the Custom Request Properties object:

//Assume {officeLocation: 'Paris', didPreprocess: true} was provided as the Custom Request Properties object

function handlerForPostFetch(request, complete, modules){
 
  const requestContext = modules.requestContext;
  const officeLocation = requestContext.getCustomRequestProperty('officeLocation');
  const didPreprocess = requestContext.getCustomRequestProperty('didPreprocess');
  
  if (officeLocation === 'Paris'){
  	  if (didPreprocess === true) {
  	  	//perform some post-processing specific to pre-process success
  	  } else {
  	  	//optionally perform some other post-processing
  	  }
  }
 
  //Finish the execution
  complete().ok().done();
  
}

For details on passing Custom Request Properties to Kinvey backend services, please see the API reference for the Kinvey Client Library of your choice.

Client App Version

All of the Kinvey client libraries provide API to pass a client-app-specific version string (for implementation details, see the Kinvey Dev Center reference documentation for the library of your choice). The requestContext module provides a clientAppVersion namespace, which includes API to retrieve details about the version of the client app making the request.

Note Kinvey recommends (but does not require) using version strings that conform to the pattern major.minor.patch, where all values are integers and minor and patch are optional. The majorVersion(), minorVersion() and patchVersion() methods defined below are convenience methods designed to work with version strings conforming to this pattern. If major, minor or patch contain any non-digit characters (i.e. 'v1' contains a non-digit character, 'v'), the corresponding convenience method will return NaN.

The requestContext.clientAppVersion namespace provides the following methods:

MethodBehavior
stringValue()Returns the client app version value as a String. Returns null if no client app version is passed in.
majorVersion()Returns the first dot-separated field of the client app version string (or the entire version string if there are no 'dot' characters) as a Number, or NaN if that field contains any non-digit characters. See A note on version numbering above for details on formatting version strings.
minorVersion()Returns the second dot-separated field of the client-app version string as a Number, or NaN if that field contains any non-digit characters. See A note on version numbering above for details on formatting version strings.
patchVersion()Returns the third dot-separated field of the client-app version string as a Number, or NaN if that field contains any non-digit characters. See A note on version numbering above for details on formatting version strings.
Example Code

Assuming a client app version string of "1.1.5":

function handler(request, complete, modules){
  const context = modules.requestContext;
  
  const majorVersion = context.clientAppVersion.majorVersion(); //majorVersion will be 1
  const minorVersion = context.clientAppVersion.minorVersion(); //minorVersion will be 1
  const patchVersion = context.clientAppVersion.patchVersion(); //patchVersion will be 5
  
  if (majorVersion < 2) {								//will be true
  	//Perform some version 1 compatible logic
  	if ((minorVersion >= 1) && (patchVersion === 5)) {	//Will be true
  		//perform some logic for a specific patch release
  	}
  } else if (majorVersion >= 2) {						//Will be false
  	//Perform some version 2+ compatible logic
  }
 
  
  //Finish the execution
  complete().ok().done();
  
}

Assuming a client app version string of "1.0.1-beta":

function handler(request, response, modules){
  const context = modules.backendContext;
  
  const versionString = context.clientAppVersion.stringValue(); //versionString will be "1.0.1-beta"
  const majorVersion = context.clientAppVersion.majorVersion();   //majorVersion will be 1
  const patchVersion = context.clientAppVersion.patchVersion();   //patchVersion will be NaN
  
  if (majorVersion < 2) { 						//Will be true
  	//Perform some version 1 compatible logic
  	if (patchVersion > 1) {						//Will be FALSE as patchVersion is NaN
  		//Perform some patch-specific logic
  	}
  }
   
  if (versionString.match("beta") !== null){	//Will be true
  	//Perform some beta-specific logic
  }
  
  //Finish the execution
  complete().ok().done();
  
}

Temp Object Store

A key-value store for persisting temporary data between a pre- and post-hook. tempObjectStore is an ephemeral store that only lives for the duration of a single request, but will allow for data that is written in a pre-hook to be read in a dataLino and/or in the post-hook. The module implements three methods:

MethodBehavior
set(key, value)Writes a value for the given key to the tempObjectStore
get(key)Returns the value of the supplied key
getAll()Returns all key/values in the tempObjectStore as an object

For example, to store an item in a handler:

function handler1(request, complete, modules) {
  var tempObjectStore = modules.tempObjectStore;
  tempObjectStore.set('token', request.body.token);
  complete().ok().next();
}

Then, to retrieve it:

function handler2(request, response, modules) {
  var tempObjectStore = modules.tempObjectStore;
  var token = tempObjectStore.get('token');
  complete({ message: `Object for token ${token} was saved successfully.` }).ok().done();
}

Note: tempObjectStore is meant for storing small, temporary amounts of data to be passed between different functions. Large amounts of data / large objects should not be stored in the tempObjectStore for performance reasons.

Testing Locally

In production use, certain data is sent to the the service by Kinvey for use in certain modules. This data can be sent via HTTP headers when testing locally. Different modules within the backend-sdk require these arguments. All are optional, but required if you want to make use of certain features in local testing.

HeaderDescriptionWhat requires it
X-Kinvey-App-MetadataA stringified JSON object that contains information about the app context from which this request is being executed.backendContext, dataStore
X-Kinvey-Original-Request-HeadersA stringified JSON object that contains the request headers that belong to the request context of the BL or data requestrequestContext, dataStore, the request argument in all handlers (if you want to access request headers)
X-Kinvey-UsernameThe username of the user making the request.requestContext, dataStore
X-Kinvey-User-IdThe User IdrequestContext, dataStore

The X-Kinvey-App-Metadata object contains:

PropertyDescription
_idThe environmentId
appsecretThe app secret
mastersecretThe master secret
baasUrlThe base URL for the Kinvey instance you are using. For example, https://baas.kinvey.com

The X-Kinvey-Original-Request-Headers object can contain any number of custom headers, but should contain:

PropertyDescription
x-kinvey-api-versionThe API version to use (number). Note this is important for the dataStore module.
authorizationThe authorization string (either Basic or Kinvey auth) for accessing Kinvey's REST service.
x-kinvey-client-app-versionThe Client App Version string
x-kinvey-custom-request-propertiesA JSON-stringified The custom request properties.
0.5.2

8 years ago

0.5.1

8 years ago

0.5.0

8 years ago

0.4.4

8 years ago

0.4.3

8 years ago

0.4.2

8 years ago

0.4.1

8 years ago

0.4.0

9 years ago

0.3.4

9 years ago

0.3.3

9 years ago

0.3.2

9 years ago

0.3.1

9 years ago

0.3.0

9 years ago

0.2.3

9 years ago

0.2.2

9 years ago

0.2.1

9 years ago

0.2.0

9 years ago

0.1.2

9 years ago

0.1.1

9 years ago

0.1.0

9 years ago

0.0.17

9 years ago

0.0.15

9 years ago

0.0.14

9 years ago

0.0.13

9 years ago

0.0.12

9 years ago

0.0.11

9 years ago

0.0.10

9 years ago

0.0.9

9 years ago

0.0.8

9 years ago

0.0.7

9 years ago

0.0.6

9 years ago

0.0.5

9 years ago

0.0.4

9 years ago

0.0.3

9 years ago

0.0.2

9 years ago

0.0.1

9 years ago

0.0.0

9 years ago