NAV Navbar
  • Introduction
  • Getting Started
  • Schemas
  • Models
  • Grunts
  • Raw JSForce Queries
  • Recipe Book
  • API Reference
  • Arbiter
  • Model
  • Query
  • Introduction

    npmCoverage StatusBuild

    Fork me on GitHub

    Arbiter is a Salesforce ORM for Nodejs with the goal of making modeling and querying Salesforce as painless as possible. With powerful schemas for validation and field remappings, Arbiter is the perfect layer to have between you and Salesforce.

    Arbiter is built on top of the popular JSforce query library. People familiar with its api will feel right at home in Arbiter since most of the api is mirrored. Also, JSForce connection objects are exposed providing escape hatches when needed, or an eased migration to Arbiter.

    What you get with Arbiter:

    What Arbiter doesn't give you:

    Arbiter does everything it can to get you down the happy path of making queries and getting access to your Salesforce data. Many of the helpers and additions are to help handle edge that arise when querying nested relations from Salesforce.

    Getting Started

    Installation

    -S is a shorthand for --save

    npm install -S arbiter
    

    Install using either npm or yarn.

    Define

    const arbiter = require('arbiter')
    const { Schema } = arbiter
    
    arbiter.configure({
      username: 'Johnny English',
      password: 'Secret_Agent_Man',
      connection: {
        loginUrl: 'http://login.salesforce.com',
      }
    })
    
    // create schema
    const opportunitySchema = new Schema('Opportunity', {
      oppName: 'Name',
      contract: new Schema('Contract', {
        status: {
          sf: 'Status',
          writable: true,
          enum: [
            'Active',
            'Inactive',
            'Suspended'
          ]
        }
      })
    })
    
    // create model with schema
    const Opportunity = arbiter.model('Opportunity', opportunitySchema)
    
    // Give it a go!
    Opportunity.findOne({ 'contract.status': 'Active' })
      .select(['oppName', 'contract.*'])
      .throwIfNotFound()
      .exec()
      .then(opp => {
        // you have an opportunity!
      })
    

    To use Arbiter you will need to configure a connection to Salesforce. This only needs to happen once somewhere in your application. Arbiter will then maintain and monitor the connection and make it available to your models and their queries.

    After that you can build up a model by first creating a schema, which defines field mappings and validations, then adding it to the model.

    Then you are ready to start writing queries.

    Schemas

    const schema = new arbiter.Schema('Opportunity', {
      // key: value for simple mapping of fields
      name: 'Name',
      // field definition when you
      // need more than just mapping for a field
      description: {
        // mapping to salesforce
        sf: 'Description',
        // if we want to be able to update field
        writable: true,
        // type validation when updating
        // 'date', 'boolean', 'number', or 'string'
        type: 'string'
      },
      status: {
        sf: 'Status',
        writable: true,
        // when only certain values are allowed
        enum: [
          'Active',
          'Inactive'
        ],
        // when creating a new object and we don't specify value
        // default to value, can also be a function!
        default: 'Active'
      },
      customField: {
        sf: 'Custom_Field__c',
        // when creating a new object you have to supply this field
        // or save will reject
        required: true,
        writable: true
      }
    })
    

    const schema = new arbiter.Schema(SFObject, mappings)

    Schemas are the driving force behind how Arbiter queries work so they deserve to be talked about first. Simply put, they are mappings of fields from how you want to reference them to the actual name of the field in Salesforce. Beyond that you can configure them to allow updates and validations on fields.

    The first argument when creating a schema is the name of the Salesforce object that will be queried. At nested levels the name is how the parent schema references that object. The second argument is the mappings for the fields you want to be able to query. Once you have defined the mapping, you never reference the field by its Salesforce name again. Arbiter takes care of mapping to the real value behind the scenes for you. You can also only query for fields that you have defined in your schema.

    Based on schema to the right we would be able to query for a name field which we cannot update or change. A description which is updateable, but must be a string. A status which is updateable but only to certain options, and customField which if we are creating a new Opportunity cannot be null.

    Nested Schemas

    Using nested definition

    const schema = new arbiter.Schema('Opportunity', {
      name: 'Name',
      project: new arbiter.Schema('Project__c', {
        status: 'Status',
        proposal: new arbiter.Schema('Proposal__c', {
          proposalStarted: 'Proposal_Started__c',
          propsoalCompleted: 'Proposal_Completed__c'
        })
      })
    })
    

    Usage with schema.addChildSchema(key, schema)

    // create all schemas first
    const oppSchema = new arbiter.Schema('Opportunity', {
      name: 'Name'
    })
    const projSchema = new arbiter.Schema('Project__c', {
      status: 'Status'
    })
    const propSchema = new arbiter.Schema('Proposal__c', {
      proposalStarted: 'Proposal_Started__c',
      proposalCompleted: 'Proposal_Completed__c'
    })
    
    // then add them to each other
    oppSchema.addChildSchema('project', projSchema)
    projSchema.addChildSchema('proposal', propSchema)
    // or add schemas at a path
    // the parts of the path have to exist
    // setting this before setting the project schema woudldn't work
    oppSchema.addChildSchema('project.proposal', propSchema)
    

    Arbiter allows schemas to be nested in each other. This nesting represents the relationships that you have defined in your company's Salesforce schema. By nesting schemas you are making it possible to query through relations just like you can in SOQL queries. As with regular fields the keys become the mapping to how we want to reference the relation.

    In the example we name all the nested schema names based off of how parent schema references them. In the code to the right we named the project Project__c because thats how our Opportunity references it. Even if really the Project__c is a My_Company_Project__c object in our Salesforce.

    There are two apis for defining nested schemas as shown on the right. Both styles outcomes are the exact same.

    Feature

    Lets say we do a query based on the schema to the right to get the proposalCompleted field off of the proposal. What if in our query the project doesn't actually exist for our Opportunity? This would normally lead to errors when you tried to read opportunity.project.proposal.proposalCompleted since the project doesn't exist.

    Arbiter will always scaffold out all the objects you request and place null in any fields you ask for. Then all the fields that come back from query will get filled in. This means you never need to check if objects exist along the path making defensive code a thing of the past. If you ever need to check to see if an object is not there then just look at its id property. If you see null you know that it does not exist.

    Models

    const model = arbiter.model(NameForModel, schemaInstance)

    const aShema = new arbiter.Schema('SalesforceObject', {
      createdDate: 'CreatedDate',
      status: 'Staus'
    })
    
    const MyModel = arbiter.model('MyModel', aSchema)
    
    MyModel.find({ status: 'Active' })
      // query instance methods
      .limit(10)
      .sort('-createdDate')
      .exec()
    
    // stash complicated queries
    MyModel.getByStatus = function (status) {
      return model.find({ status }).exec()
    }
    

    Models are the glue that bring schemas and queries together. They are just objects making them a great place to stash complicated queries.

    A query instance gets returned by calling any one of the model's find functions

    Associations

    const accountSchema = new arbiter.Schema('Account', {
      description: 'Description',
      rating: 'Rating'
    })
    
    const Account = arbiter.model('Account', accountSchema)
    
    const oppSchema = new arbiter.Schema('Opportunity', {
      accountId: 'AccountId',
      status: 'Status'
    })
    
    const Opportunity = arbiter.model('Opportunity', oppSchema)
    
    // normally we wouldn't be able to get fields off of
    // account through the Opportunity
    // but with Arbiter we can
    
    Opportunity.setAssocitions({
      // the name we want to give the association
      // this comes into play later when we ask to get it
      account: {
        // what field on the current model do you join on
        from: 'accountId',
        // what field on the other object do you join on
        // IMPORTANT this still using the mapping of the field
        to: 'id',
        // what model to use for second query
        model: Account
        // hasOne for when theres only one
        // hasMany when it should be an array
        relation: Opportunity.relations.hasOne
      }
    })
    
    Opportunity.findOne({ status: 'Open' })
      .with('account', accountQuery => {
        accountQuery.select('rating')
      })
      .exec()
    
    // or
    Opportunity.findOne({ status: 'Open' })
      .with({
        account: accountQuery => {
          accountQuery.select('rating')
        }
      })
    

    The above will return an result shaped like this:

    {
      "id": "some id...",
      "account": {
        "rating": "some rating"
      }
    }
    

    Nested Schemas are powerful because they allow you to query through relations in one query and get the results you need. What do you do when you have a reference to another object but you can't query through it? Thats were associations come in. They allow you to describe to Arbiter how to do the joins and then Arbiter will make the queries and handle zipping the results together. Multiple queries still need to happen to get the data, but it can be a lifesaver to not have to write the manual code to do this yourself.

    Just showing off

    Case.find()
      .limit(10)
      // grab the comments for the cases we find
      // for every comment also go fetch the owner of the comment
      .with({
        comments: commentsQuery => {
          commentsQuery.select('*')
            .with('owner', ownerQuery => {
              ownerQuery.select('firstName')
            })
        },
        recordType: recordTypeQuery => {
          recordTypeQuery.select('name')
        }
      })
      .exec()
    

    That would result in this, only showing one result

    [
      {
        "id": "some case Id",
        "recordType": {
          "name": "A RecordType name"
        },
        "comments": [
          {
            "id": "some comment id",
            "comment": "The guy was talking....",
            "owner": {
              "firstName": "Peter"
            }
          }
        ]
      }
    ]
    

    Whats even more impressive is that you can call off multiple associations at once and/or call associations on your assocations. This all is kicked off by calling query.with()

    Grunts

    Assuming that status is a writable field and passes validation defined in schema

    model.findOne({ status: 'Open' })
      .select('status')
      .exec()
      .then(grunt => {
        // we can add other fields to the grunt that are not in the schema
        // these will not get sent with any saves
        grunt.extraField = 'Some extra field on grunt'
        grunt.status = 'Closed'
        return grunt.save()
      })
      .then(savedGrunt => {...})
    

    Updating many grunts

    model.find({ isActive: true })
      .limit(10)
      .then(grunts => {
        const updates = grunts.map(grunt => {
          grunt.isActive = false
          return grunt.save()
        })
        return Promise.all(updates)
      })
      .then(updatedGrunts => {...})
    

    Grunts are objects returned by queries. For the most part you can use them just like regular objects, but they do have some tricks up their sleeves.

    Grunts are Proxied objects. The handler for a grunt is aware of what fields are writable in the schema and will run validations and mappings as you mutate writable fields on a grunt. Grunts are not limited to only fields that came back from a query or are in the schema. Feel free to add whatever additional fields you want and use grunts as transfer objects. The handler will make sure to keep track of only the updates that are writable and that have passed your validations as defined in the schema. The benefit of the approach is that you don't pay a cost for having these smart objects. If you only want to use them for their query power and not do mutations there is no slow down in performance. It also means you can query for 100's of grunts and it won't affect your query performance either.

    You won't interface directly with the handler, but knowing it's there and what it is doing will make it easier to reason about what is happening. To update Salesforce with the writable changes on a grunt simply call grunt.save(). Any errors in your mutations will show up in the catch of the returned promise. The resolved value of the promise is the grunt itself.

    Nested mutations

    Updating nested values

    model.findOne()
      .select('account.contract.createdDate')
      .then(grunt => {
        // we want to update the createdDate which is nested
        // since an id is present, calling save will do an update
        const contractGrunt = contractModel.new(grunt.contract)
        contractGrunt.createdDate = new Date()
        return contractGrunt.save()
      })
    

    While models are able to query into nested relations the grunts they produce are not responsible to update the nested values. The grunts will have all the fields on them that you asked for, they just aren't responsible to save all of them. In order to update nested fields you will need to make a new grunt out of the data you want through the model.new() api and then mutate and save the changes. If grunts have an id property they will call update, if no id is present on a grunt it will call a create query.

    This is in part to make error handling and ordering of the saves explicit. If Arbiter allowed grunts to save every nested level on them it would be hard to imagine a clean api when an error occurred while saving.

    Raw JSForce Queries

    Raw connection object

    const arbiter = require('arbiter')
    
    arbiter.getConnection()
      .then(conn => {
        // raw jsforce connection object
      })
    

    model.sobject() call

    const Opportunity = arbiter.model('Opportunity', aSchema)
    
    Opportunity.sobject()
      .then(sobject => {
        // same as conn.sobject('whatever schema points to').then(...)
      })
    

    Arbiter is built using JSForce behind the scenes. After it maps your values to what they really are it builds up a JSForce query. If for whatever reason you need to use a regular JSForce query, they are available to you. Keep in mind that when using raw queries you are not going through the schema and will not receive back prettied results, or even grunts. This is a direct call into JSForce api and what they return.

    Recipe Book

    Composable queries

    Returning the query instance for additional modification

    const arbiter = require('arbiter')
    // leaving out schema details for brevity
    const oppSchema = new arbiter.Schema('Case', {})
    
    const Case = arbiter.model('Case', oppSchema)
    
    // Notice that by calling find and where
    // that we are adding up the state of the two
    // one doesn't overwrite the other unless they
    // contain the same field
    Case.byStatus = function (status, whereClauses = {}) {
      return Case.find({ status })
        .select('*')
        .where(whereClauses)
    }
    

    A lot of flexibility is available by leveraging the composabilty of queries. Since their state is cumulative you can accept outside input and add it to the query. We have also found that it is advantageous to not execute the query inside a function attached to the model. By returning the query you allow the outside world to attach more fields and other query state to shape the query different for their needs. Doing this will make a lot more of your queries reusable in other places in your app and trims down on code.

    At a minimum consider having all of your query functions accept a where clause argument. This is generally the part that makes one query unique over another or helps make queries more composable.

    This is not always possible and sometimes you have to execute queries in order to supply the right data or maybe you don't want the abstraction of the query to show up outside of the model. But if you find yourself seeing an existing query that is very close to what you need consider changing your apis to this style and being more extensible.

    Reusable Schemas

    Extract the field config into a seperate file

    // opportunity.js
    module.exports = {
      name: 'Name',
      status: {
        sf: 'Status',
        enum: ['Active', 'Suspended', 'Inactive'],
        writable: true,
        default: 'Active'
      }
    }
    
    // service.js
    module.exports = {
      name: 'Name',
      assignedTo: {
        sf: 'Assigned_To__c',
        writable: true,
        required: true
      }
    }
    
    // building the schema
    const arbiter = require('arbiter')
    const opportunityConfig = require('./opportunity')
    const serviceConfig = require('./service')
    
    // notice how we don't have to define the field configs here now
    const opportunitySchema = new arbiter.Schema('Opporunity', opportunityConfig)
    const serviceSchema = new arbiter.Schema('Service__c', serviceConfig)
    
    opportunitySchema.addChildSchema('service', serviceSchema)
    

    There might come a point when working with Arbiter when you have to use the same schema object in multiple schema trees. You could just redefine the schema fields and deal with the possiblilty of the two getting out of sync. Or what you can do is pull out the basic structure of each of your schemas into seperate files. Then require those files when you are building up your schema trees.

    This makes it so that you have a lot less schemas to write over and over and you can move much faster. It also means that you can no longer do a nested schema definition and you will need to the the addChildSchema api for building up the objects.

    This is optional but highly recommended since writing schema configs can be cumbersome and its nice to only have to do it once per object you will interface with.

    API Reference

    Arbiter

    The main import of the library houses the manager of the connection to Salesforce as well as a place to define Schemas and models. It is also where you go to kick off a JSForce query.

    configure

    const arbiter = require('arbiter')
    // showing required fields
    arbiter.configure({
      username: 'Johnny English',
      password: 'Secret_Agent_Man',
      connection: {
        loginUrl: 'https://login.salesforce.com'
      }
    })
    

    Sets up connection to Salesforce. It needs to happen somewhere in your application one time. You are free to define models and Schemas before it is configured, but it most be done before you attempt a query.

    Arguments

    Return Value

    undefined

    getConnection

    const arbiter = require('arbiter')
    
    arbiter.getConnection()
      .then(conn => {
        /*
          You have a jsforce connection
        */
      })
    

    Gets connection to salesforce. This is a JSForce connection and any queries done through this connection will not have any mappings or validations from the schema. Use this as an escape hatch or while migrating to Arbiter. Arbiter uses the same connection throughout all of its queries so any changes to the connection here will show up everywhere until the connection expires as defined in the call to arbiter.configure()

    Arguments

    None

    Return Value

    JSForce Connection instance

    model

    const arbiter = require('arbiter')
    
    const schemaInstance = new arbiter.Schema('Opportunity', {...})
    const model = arbiter.model('SomeName', schemaInstance)
    

    Creates a new model instance

    Arguments

    Return Value

    model

    Schema

    const arbiter = require('arbiter')
    
    const schema = new arbiter.Schema('Opportunity', {
      status: {
        sf: 'Status',
        writable: true,
        enum: [
          'Active',
          'Inactive',
          'Suspended'
        ],
        default: 'Active'
      },
      accountId: 'AccountId'
    })
    

    Creates a schema instance defining mapping and validation for fields.

    Arguments

    The fields of the config can either be simple key: value pairings which sets the key as the way you want to reference the field. The value can also be an object in order to extend the configuration. All the possible values are shown in example to right.

    Expanded config for a field in schema config

    {
      // only required field
      sf: 'Some_Salesforce_Field__c',
      // boolean, date, string, number
      type: 'string',
      // must be one of these
      enum: [ null, 'value', 'value2' ],
      // we want to be able to have grunts update this field
      writable: true,
      // when saving a new object this field cannot be null/undefined
      required: true,
      // defaults, this makes having required redundant since it will always
      // be there because of this having a default
      default: 'A Default',
      // can also be a function
      default() { return 'A default' }
    }
    

    Model

    An object that can get connections from pool and kick off queries.

    sobject

    model.sobject()
      .then(sobject => {
        return sobject.find(...)
      })
    

    Same as calling

    arbiter.getConnection()
      .then(conn => conn.sobject('SomeSObject'))
    

    Gets a JSForce connection and sets that connection to the sobject that the model is linked to. This is found out by looking at the schema that was added to the model.

    Argumens

    None

    Return Value

    JSForce SObject instance

    setAssociations

    model.setAssocations({
      singeAssociation: {
        from: 'a field on me',
        to: 'a field on them',
        model: SomeOtherModel,
        // this field should be a single value
        relation: model.relations.hasOne
      },
      pluralAssocation: {
        from: 'a field on me',
        to: 'a field on them',
        model: SomeModel,
        // this field should be an array of things
        relation: model.relations.hasMany
      }
    })
    

    Assocations are a way to have Arbiter be able to execute multiple queries and join the results together. In your Salesforce you will most likely have many relationships that allow you to do queries across relations. Associations are for the cases where you cannot traverse the relationship and instead would have to do multiple queries to get the data for associated objects. Using this api allows you to show models how to do the "joins" in order to get all the data.

    The keys of the object become the name of the association you can ask for later when you call query.with()

    Arguments

    For convenience models have a relations.hasMany and relations.hasOne property on them that point to the respective strings.

    Return Value

    undefined

    new

    const newObject = model.new()
    newObject.status = 'Active'
    newObject.save()
      .then(() => /* created grunt! */)
    

    Example of passing in some starting fields

    // these fields will still need to pass validation of the schema
    const newObject = model.new({
      status: 'Active'
    })
    
    newObject.save().then(...)
    

    Creates an instance of a grunt. This is one way of creating salesforce objects on the fly or doing a direct update of an object without querying for it first. When calling save() on a grunt if it's given an id field it will do an update. Otherwise it will attempt to create the object.

    Arguments

    Return Value

    grunt instance

    find

    model.find()
      .select('*')
      .exec()
    

    Passing in where clause

    model.find({ 'account.lastName': 'Rollins' })
    

    Same as doing:

    model.find().where({ 'account.lastName': 'Rollins' })
    

    Kicks off a query. Optional where clause options can be passed to find.

    Arguments

    Return Value

    query instance

    findOne

    model.findOne({ isActive: true })
    

    Same as doing

    model.find({ isActive: true }).first()
    

    Shorthand that exists for calling a query that is only looking for an individual record back

    Arguments

    Return Value

    query instance

    findById

    model.findById('1')
    

    Same as doing:

    model.find().where({ id: '1' }).first()
    

    Kicks off a query. Returns a single result, if one is found.

    Arguments

    Return Value

    query instance

    findByIds

    model.findByIds([ '1', '2', '3' ])
    

    Same as doing:

    model.find().where({ id: [ '1', '2', '3' ]})
    

    Kicks off a query searching for objects from a collection of ids.

    Arguments

    Return Value

    query instance

    Query

    Queries are created by calling any of the find methods on a model find, findById, findByIds and have many methods to shape your request. Besides any of the methods that execute a query (then, exec, execute) query methods can be called in any order. Also most query functions state is cumulative. Calling select, where, or with keeps adding to the same state instead of wiping it out everytime. You can use this to your advantage to build highly composable queries.

    select

    All call signatures

    model.find()
      // as positional arguments
      .select('id', 'isDeleted', 'status', 'contract.created')
    
    model.find()
      // as array
      .select([ 'id', 'isDeleted', 'status', 'contract.created' ])
    
    model.find()
      // as single string of comma delimited fields
      .select('id, isDeleted, status, contract.created')
    

    Selects the fields you want returned in a query.

    Arguments

    Return Value

    query instance

    fields

    alias for select

    where

    model.find()
      .where({
        status: 'Active',
        isDeleted: false
      })
    

    Also accepts a RAW SOQL String

    // keep in mind this needs to be the Salesforce naming of fields
    // this is good for some edge case queries like one below
    model.find()
      .where("DISTANCE(Location__c, GEOLOCATION(37.775,-122.418), 'mi') < 20")
    

    Adds where clauses to the query.

    Arguments

    Return Value

    query instance

    limit

    model.find()
      .limit(5)
    

    Sets a limit on the query.

    Arguments

    Return Value

    query instance

    skip

    model.find()
      .skip(10)
    

    Sets up a skip on query.

    Arguments

    Return Value

    query instance

    offset

    alias to skip

    sort

    minus in front of field signifys descending order

    model.find()
      .sort('-createdDate status')
    

    Other signatures

    model.find()
      .sort({ createdData: -1, status: 1 })
    

    Sets up sorting of query.

    Arguments

    Return Value

    query instance

    first

    model.find()
      .first()
    

    Executing the query does something similar to

    model.find()
      .exec()
      .then(results => results[0])
    

    Sets the query state that you only want the first result that comes back. This makes the return a single value instead of an array.

    Arguments

    None

    Return Value

    query instance

    with

    As positional arguments

    model.find({ status: 'Suspended' })
      .select('*')
      // with this syntax you would have to call `with` multiple times
      // to fetch multiple associations
      .with('association', associationQuery => {
        // notice we don't call any of the functions to start query
        // Arbiter does this for you when it is ready to do query
        associationQuery.select('field, anotherField, yetAnotherField')
      })
    

    As an object to with

    model.find({ status: 'Active' })
      .select('*')
      .with({
        // you can pass multiple associations here too if you need to
        association (associationQuery) {
          associationQuery.select('field, anotherField, yetAnotherField')
        }
      })
    

    Sets up an association query to be called once outer query has executed. The associations that you can fetch go hand in hand with the associations that have been configured on the models with setAssociations().

    These are very powerful for joining together objects that you normally can't query through in a regular SOQL query. Also they can be nested, since the query function for an association is just creating a query instance the entire query api is available to you. The only things that aren't would be the functions to execute the inner queries since Arbiter will need to do that once it is ready.

    Two apis exist for calling with. The with(associationName, queryCB) style and the with(asocciationObject) style. The associationObject style might be better if you need to fetch multiple associations but that is always possible with the other style as long as you call with multiple times.

    Arguments

    2 argument version

    Object style

    throwIfNotFound

    model.find()
      .throwIfNotFound()
    

    Handles when query is on a single value or an array of values

    // when query would maybe return one thing
    model.findOne()
      .throwIfNotFound(new Error('whoops'))
    
    // or when query returns multiple things
    model.find()
      .throwIfNotFound('error message')
    

    Helper for handling when query might return an empty array or null for a query. Doing so will cause the query to return a rejected promise. This covers some edge cases that can happen when you are operating on data in your .then() not realizing that the query returned no results.

    Arguments

    Return Value

    query instance

    allowMutations

    model.find()
      // assuming that status is a writable field
      .select('status')
      .allowMuations(['status'])
      .then(grunt => {
        grunt.status = 'something new'
        // any other updates that are writable in schema would fail
        return grunt.save()
      })
    

    Configures the returned grunts to allow mutations on only certain fields. The fields that are passed must be listed as writable in the schema. This is handy for sanitizing queries and restricting writes to certain values that are writable some of the time but you need to protect at other times

    Arguments

    Return Value

    query instance

    rejectMuations

    model.find()
      .rejectMutations(['status'])
      .then(grunt => {
        // cannot change status even if listed as writable in schema
      })
    

    The inverse of allowMuations use which ever one is easier to describe the fields you want to allow or reject field mutations in the query

    Arguments

    execute

    model.find()
      .execute()
    

    Triggers a query to get executed. All of the state built up to this point gets turned into a query and sent over connection

    Arguments

    None

    Return Value

    Promise

    exec

    alias for execute

    then

    Make query promise like

    model.find()
      .then(onSuccessFn, onRejectFn)
    

    alias for execute except for that it allows passing of success and reject handlers for the query. This makes the query conform to a promise interface

    Arguments

    Return Value

    Promise