text: /**
   * @file Represents Salesforce SObject
   * @author Shinichi Tomita <shinichi.tomita@gmail.com>
   */
  var _      = require('underscore'),
      Record = require('./record'),
      Query  = require('./query'),
      Cache  = require('./cache');
  
  /**
   * A class for organizing all SObject access
   *
   * @constructor
   */
  var SObject = module.exports = function(conn, type) {
    this._conn = conn;
    this.type = type;
    var cacheOptions = { key: "describe." + this.type };
    this.describe$ = conn.cache.makeCacheable(this.describe, this, cacheOptions);
    this.describe = conn.cache.makeResponseCacheable(this.describe, this, cacheOptions);
  };
  
  /**
   * Synonym of SObject#create()
   *
   * @method SObject#insert
   * @param {Record|Array.<Record>} records - A record or array of records to create
   * @param {Callback.<RecordResult|Array.<RecordResult>>} [callback] - Callback function
   * @returns {Promise.<RecordResult|Array.<RecordResult>>}
   */
  /**
   * Create records
   *
   * @method SObject#create
   * @param {Record|Array.<Record>} records - A record or array of records to create
   * @param {Object} [options] - Options for rest api.
   * @param {Callback.<RecordResult|Array.<RecordResult>>} [callback] - Callback function
   * @returns {Promise.<RecordResult|Array.<RecordResult>>}
   */
  SObject.prototype.insert =
  SObject.prototype.create = function(records, options, callback) {
    if (typeof options === 'function') {
      callback = options;
      options = {};
    }
    return this._conn.create(this.type, records, options, callback);
  };
  
  /**
   * Retrieve specified records
   *
   * @param {String|Array.<String>} ids - A record ID or array of record IDs 
   * @param {Object} [options] - Options for rest api.
   * @param {Callback.<Record|Array.<Record>>} [callback] - Callback function
   * @returns {Promise.<Record|Array.<Record>>}
   */
  SObject.prototype.retrieve = function(ids, options, callback) {
    if (typeof options === 'function') {
      callback = options;
      options = {};
    }
    return this._conn.retrieve(this.type, ids, options, callback);
  };
  
  /**
   * Update records
   *
   * @param {Record|Array.<Record>} records - A record or array of records to update
   * @param {Object} [options] - Options for rest api.
   * @param {Callback.<RecordResult|Array.<RecordResult>>} [callback] - Callback function
   * @returns {Promise.<RecordResult|Array.<RecordResult>>}
   */
  SObject.prototype.update = function(records, options, callback) {
    if (typeof options === 'function') {
      callback = options;
      options = {};
    }
    return this._conn.update(this.type, records, options, callback);
  };
  
  /**
   * Upsert records
   *
   * @param {Record|Array.<Record>} records - Record or array of records to upsert
   * @param {String} extIdField - External ID field name
   * @param {Object} [options] - Options for rest api.
   * @param {Callback.<RecordResult|Array.<RecordResult>>} [callback] - Callback
   * @returns {Promise.<RecordResult|Array.<RecordResult>>}
   */
  SObject.prototype.upsert = function(records, extIdField, options, callback) {
    if (typeof options === 'function') {
      callback = options;
      options = {};
    }
    return this._conn.upsert(this.type, records, extIdField, options, callback);
  };
  
  /**
   * Synonym of SObject#destroy()
   *
   * @method SObject#delete
   * @param {String|Array.<String>} ids - A ID or array of IDs to delete
   * @param {Callback.<RecordResult|Array.<RecordResult>>} [callback] - Callback function
   * @returns {Promise.<RecordResult|Array.<RecordResult>>}
   */
  /**
   * Synonym of SObject#destroy()
   *
   * @method SObject#del
   * @param {String|Array.<String>} ids - A ID or array of IDs to delete
   * @param {Callback.<RecordResult|Array.<RecordResult>>} [callback] - Callback function
   * @returns {Promise.<RecordResult|Array.<RecordResult>>}
   */
  /**
   * Delete records
   *
   * @method SObject#destroy
   * @param {String|Array.<String>} ids - A ID or array of IDs to delete
   * @param {Object} [options] - Options for rest api.
   * @param {Callback.<RecordResult|Array.<RecordResult>>} [callback] - Callback function
   * @returns {Promise.<RecordResult|Array.<RecordResult>>}
   */
  SObject.prototype["delete"] =
  SObject.prototype.del =
  SObject.prototype.destroy = function(ids, options, callback) {
    if (typeof options === 'function') {
      callback = options;
      options = {};
    }
    return this._conn.destroy(this.type, ids, options, callback);
  };
  
  /**
   * Describe SObject metadata
   *
   * @param {Callback.<DescribeSObjectResult>} [callback] - Callback function
   * @returns {Promise.<DescribeSObjectResult>}
   */
  SObject.prototype.describe = function(callback) {
    return this._conn.describe(this.type, callback);
  };
  
  /**
   * Get record representation instance by given id
   *
   * @param {String} id - A record ID
   * @returns {RecordReference}
   */
  SObject.prototype.record = function(id) {
    return new Record(this._conn, this.type, id);
  };
  
  /**
   * Find and fetch records which matches given conditions
   *
   * @param {Object|String} [conditions] - Conditions in JSON object (MongoDB-like), or raw SOQL WHERE clause string.
   * @param {Object|Array.<String>|String} [fields] - Fields to fetch. Format can be in JSON object (MongoDB-like), array of field names, or comma-separated field names.
   * @param {Object} [options] - Query options.
   * @param {Number} [options.limit] - Maximum number of records the query will return.
   * @param {Number} [options.offset] - Offset number where begins returning results.
   * @param {Number} [options.skip] - Synonym of options.offset.
   * @param {Callback.<Array.<Record>>} [callback] - Callback function
   * @returns {Query.<Array.<Record>>}
   */
  SObject.prototype.find = function(conditions, fields, options, callback) {
    if (typeof conditions === 'function') {
      callback = conditions;
      conditions = {};
      fields = null;
      options = null;
    } else if (typeof fields === 'function') {
      callback = fields;
      fields = null;
      options = null;
    } else if (typeof options === 'function') {
      callback = options;
      options = null;
    }
    options = options || {};
    var config = {
      fields: fields,
      includes: options.includes,
      table: this.type,
      conditions: conditions,
      limit: options.limit,
      offset: options.offset || options.skip
    };
    var query = new Query(this._conn, config);
    query.setResponseTarget(Query.ResponseTargets.Records);
    if (callback) { query.run(callback); }
    return query;
  };
  
  /**
   * Fetch one record which matches given conditions
   *
   * @param {Object|String} [conditions] - Conditions in JSON object (MongoDB-like), or raw SOQL WHERE clause string.
   * @param {Object|Array.<String>|String} [fields] - Fields to fetch. Format can be in JSON object (MongoDB-like), array of field names, or comma-separated field names.
   * @param {Object} [options] - Query options.
   * @param {Number} [options.limit] - Maximum number of records the query will return.
   * @param {Number} [options.offset] - Offset number where begins returning results.
   * @param {Number} [options.skip] - Synonym of options.offset.
   * @param {Callback.<Record>} [callback] - Callback function
   * @returns {Query.<Record>}
   */
  SObject.prototype.findOne = function(conditions, fields, options, callback) {
    if (typeof conditions === 'function') {
      callback = conditions;
      conditions = {};
      fields = null;
      options = null;
    } else if (typeof fields === 'function') {
      callback = fields;
      fields = null;
      options = null;
    } else if (typeof options === 'function') {
      callback = options;
      options = null;
    }
    options = _.extend(options || {}, { limit: 1 });
    var query = this.find(conditions, fields, options);
    query.setResponseTarget(Query.ResponseTargets.SingleRecord);
    if (callback) { query.run(callback); }
    return query;
  };
  
  /**
   * Find and fetch records only by specifying fields to fetch.
   *
   * @param {Object|Array.<String>|String} [fields] - Fields to fetch. Format can be in JSON object (MongoDB-like), array of field names, or comma-separated field names.
   * @param {Callback.<Array.<Record>>} [callback] - Callback function
   * @returns {Query.<Array.<Record>>}
   */
  SObject.prototype.select = function(fields, callback) {
    return this.find(null, fields, null, callback);
  };
  
  /**
   * Count num of records which matches given conditions
   *
   * @param {Object|String} [conditions] - Conditions in JSON object (MongoDB-like), or raw SOQL WHERE clause string.
   * @param {Callback.<Number>} [callback] - Callback function
   * @returns {Query.<Number>}
   */
  SObject.prototype.count = function(conditions, callback) {
    if (typeof conditions === 'function') {
      callback = conditions;
      conditions = {};
    }
    var query = this.find(conditions, { "count()" : true });
    query.setResponseTarget("Count");
    if (callback) { query.run(callback); }
    return query;
  };
  
  
  /**
   * Call Bulk#load() to execute bulkload, returning batch object
   *
   * @param {String} operation - Bulk load operation ('insert', 'update', 'upsert', 'delete', or 'hardDelete')
   * @param {Object} [options] - Options for bulk loading operation
   * @param {String} [options.extIdField] - External ID field name (used when upsert operation).
   * @param {Array.<Record>|stream.Stream|String} [input] - Input source for bulkload. Accepts array of records, CSv string, and CSV data input stream.
   * @param {Callback.<Array.<RecordResult>>} [callback] - Callback function
   * @returns {Bulk~Batch}
   */
  SObject.prototype.bulkload = function(operation, options, input, callback) {
    return this._conn.bulk.load(this.type, operation, options, input, callback);
  };
  
  /**
   * Synonym of SObject#createBulk()
   *
   * @method SObject#insertBulk
   * @param {Array.<Record>|stream.Stream|String} [input] - Input source for bulk insert. Accepts array of records, CSv string, and CSV data input stream.
   * @param {Callback.<Array.<RecordResult>>} [callback] - Callback function
   * @returns {Bulk~Batch}
   */
  /**
   * Bulkly insert input data using bulk API
   *
   * @method SObject#createBulk
   * @param {Array.<Record>|stream.Stream|String} [input] - Input source for bulk insert. Accepts array of records, CSv string, and CSV data input stream.
   * @param {Callback.<Array.<RecordResult>>} [callback] - Callback function
   * @returns {Bulk~Batch}
   */
  SObject.prototype.insertBulk =
  SObject.prototype.createBulk = function(input, callback) {
    return this.bulkload("insert", input, callback);
  };
  
  /**
   * Bulkly update records by input data using bulk API
   *
   * @param {Array.<Record>|stream.Stream|String} [input] - Input source for bulk update Accepts array of records, CSv string, and CSV data input stream.
   * @param {Callback.<Array.<RecordResult>>} [callback] - Callback function
   * @returns {Bulk~Batch}
   */
  SObject.prototype.updateBulk = function(input, callback) {
    return this.bulkload("update", input, callback);
  };
  
  /**
   * Bulkly upsert records by input data using bulk API
   *
   * @param {Array.<Record>|stream.Stream|String} [input] - Input source for bulk upsert. Accepts array of records, CSv string, and CSV data input stream.
   * @param {String} [options.extIdField] - External ID field name
   * @param {Callback.<Array.<RecordResult>>} [callback] - Callback function
   * @returns {Bulk~Batch}
   */
  SObject.prototype.upsertBulk = function(input, extIdField, callback) {
    return this.bulkload("upsert", { extIdField: extIdField }, input, callback);
  };
  
  /**
   * Synonym of SObject#destroyBulk()
   *
   * @method SObject#deleteBulk
   * @param {Array.<Record>|stream.Stream|String} [input] - Input source for bulk delete. Accepts array of records, CSv string, and CSV data input stream.
   * @param {Callback.<Array.<RecordResult>>} [callback] - Callback function
   * @returns {Bulk~Batch}
   */
  /**
   * Bulkly delete records specified by input data using bulk API
   *
   * @method SObject#destroyBulk
   * @param {Array.<Record>|stream.Stream|String} [input] - Input source for bulk delete. Accepts array of records, CSv string, and CSV data input stream.
   * @param {Callback.<Array.<RecordResult>>} [callback] - Callback function
   * @returns {Bulk~Batch}
   */
  SObject.prototype.deleteBulk =
  SObject.prototype.destroyBulk = function(input, callback) {
    return this.bulkload("delete", input, callback);
  };
  
  /**
   * Synonym of SObject#destroyHardBulk()
   *
   * @method SObject#deleteHardBulk
   * @param {Array.<Record>|stream.Stream|String} [input] - Input source for bulk delete. Accepts array of records, CSv string, and CSV data input stream.
   * @param {Callback.<Array.<RecordResult>>} [callback] - Callback function
   * @returns {Bulk~Batch}
   */
  /**
   * Bulkly hard delete records specified in input data using bulk API
   *
   * @method SObject#destroyHardBulk
   * @param {Array.<Record>|stream.Stream|String} [input] - Input source for bulk delete. Accepts array of records, CSv string, and CSV data input stream.
   * @param {Callback.<Array.<RecordResult>>} [callback] - Callback function
   * @returns {Bulk~Batch}
   */
  SObject.prototype.deleteHardBulk =
  SObject.prototype.destroyHardBulk = function(input, callback) {
    return this.bulkload("hardDelete", input, callback);
  };
  
  /**
   * Retrieve recently accessed records
   *
   * @param {Callback.<Array.<RecordResult>>} [callback] - Callback function
   * @returns {Promise.<Array.<RecordResult>>}
   */
  SObject.prototype.recent = function (callback) {
    return this._conn.recent(this.type, callback);
  };
  
  /**
   * Retrieve the updated records
   *
   * @param {String|Date} start - start date or string representing the start of the interval
   * @param {String|Date} end - start date or string representing the end of the interval, must be > start
   * @param {Callback.<UpdatedRecordsInfo>} [callback] - Callback function
   * @returns {Promise.<UpdatedRecordsInfo>}
   */
  SObject.prototype.updated = function (start, end, callback) {
    return this._conn.updated(this.type, start, end, callback);
  };
  
  /**
   * Retrieve the deleted records
   *
   * @param {String|Date} start - start date or string representing the start of the interval
   * @param {String|Date} end - start date or string representing the end of the interval, must be > start
   * @param {Callback.<DeletedRecordsInfo>} [callback] - Callback function
   * @returns {Promise.<DeletedRecordsInfo>}
   */
  SObject.prototype.deleted = function (start, end, callback) {
    return this._conn.deleted(this.type, start, end, callback);
  };