jsforce
Version:
Salesforce API Library for JavaScript
1,730 lines (1,586 loc) • 703 kB
JavaScript
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.jsforce=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/**
* @file Manages Salesforce Analytics API
* @author Shinichi Tomita <shinichi.tomita@gmail.com>
*/
var _ = require('underscore')._,
Promise = require('../promise');
/**
* Report instance to retrieving asynchronously executed result
*
* @protected
* @class Analytics~ReportInstance
* @param {Analytics~Report} report - Report
* @param {String} id - Report instance id
*/
var ReportInstance = function(report, id) {
this._report = report;
this._conn = report._conn;
this.id = id;
};
/**
* Retrieve report result asynchronously executed
*
* @method Analytics~ReportInstance#retrieve
* @param {Callback.<Analytics~ReportResult>} [callback] - Callback function
* @returns {Promise.<Analytics~ReportResult>}
*/
ReportInstance.prototype.retrieve = function(callback) {
var conn = this._conn,
report = this._report;
var url = [ conn._baseUrl(), "analytics", "reports", report.id, "instances", this.id ].join('/');
return conn._request(url).thenCall(callback);
};
/**
* Report object in Analytics API
*
* @protected
* @class Analytics~Report
* @param {Connection} conn Connection
*/
var Report = function(conn, id) {
this._conn = conn;
this.id = id;
};
/**
* Describe report metadata
*
* @method Analytics~Report#describe
* @param {Callback.<Analytics~ReportMetadata>} [callback] - Callback function
* @returns {Promise.<Analytics~ReportMetadata>}
*/
Report.prototype.describe = function(callback) {
var url = [ this._conn._baseUrl(), "analytics", "reports", this.id, "describe" ].join('/');
return this._conn._request(url).thenCall(callback);
};
/**
* Run report synchronously
*
* @method Analytics~Report#execute
* @param {Object} [options] - Options
* @param {Boolean} options.details - Flag if include detail in result
* @param {Analytics~ReportMetadata} options.metadata - Overriding report metadata
* @param {Callback.<Analytics~ReportResult>} [callback] - Callback function
* @returns {Promise.<Analytics~ReportResult>}
*/
Report.prototype.run =
Report.prototype.exec =
Report.prototype.execute = function(options, callback) {
options = options || {};
if (_.isFunction(options)) {
callback = options;
options = {};
}
var url = [ this._conn._baseUrl(), "analytics", "reports", this.id ].join('/');
if (options.details) {
url += "?includeDetails=true";
}
var params = { method : options.metadata ? 'POST' : 'GET', url : url };
if (options.metadata) {
params.headers = { "Content-Type" : "application/json" };
params.body = JSON.stringify(options.metadata);
}
return this._conn._request(params).thenCall(callback);
};
/**
* Run report asynchronously
*
* @method Analytics~Report#executeAsync
* @param {Object} [options] - Options
* @param {Boolean} options.details - Flag if include detail in result
* @param {Analytics~ReportMetadata} options.metadata - Overriding report metadata
* @param {Callback.<Analytics~ReportInstanceAttrs>} [callback] - Callback function
* @returns {Promise.<Analytics~ReportInstanceAttrs>}
*/
Report.prototype.executeAsync = function(options, callback) {
options = options || {};
if (_.isFunction(options)) {
callback = options;
options = {};
}
var url = [ this._conn._baseUrl(), "analytics", "reports", this.id, "instances" ].join('/');
if (options.details) {
url += "?includeDetails=true";
}
var params = { method : 'POST', url : url, body: "" };
if (options.metadata) {
params.headers = { "Content-Type" : "application/json" };
params.body = JSON.stringify(options.metadata);
}
return this._conn._request(params).thenCall(callback);
};
/**
* Get report instance for specified instance ID
*
* @method Analytics~Report#instance
* @param {String} id - Report instance ID
* @returns {Analytics~ReportInstance}
*/
Report.prototype.instance = function(id) {
return new ReportInstance(this, id);
};
/**
* List report instances which had been executed asynchronously
*
* @method Analytics~Report#instances
* @param {Callback.<Array.<Analytics~ReportInstanceAttrs>>} [callback] - Callback function
* @returns {Promise.<Array.<Analytics~ReportInstanceAttrs>>}
*/
Report.prototype.instances = function(callback) {
var url = [ this._conn._baseUrl(), "analytics", "reports", this.id, "instances" ].join('/');
return this._conn._request(url).thenCall(callback);
};
/**
* API class for Analytics API
*
* @class
* @param {Connection} conn Connection
*/
var Analytics = function(conn) {
this._conn = conn;
};
/**
* Get report object of Analytics API
*
* @param {String} id - Report Id
* @returns {Analytics~Report}
*/
Analytics.prototype.report = function(id) {
return new Report(this._conn, id);
};
/**
* Get recent report list
*
* @param {Callback.<Array.<Analytics~ReportInfo>>} [callback] - Callback function
* @returns {Promise.<Array.<Analytics~ReportInfo>>}
*/
Analytics.prototype.reports = function(callback) {
var url = [ this._conn._baseUrl(), "analytics", "reports" ].join('/');
return this._conn._request(url).thenCall(callback);
};
module.exports = Analytics;
},{"../promise":21,"underscore":63}],2:[function(require,module,exports){
/**
* @file Manages Salesforce Apex REST endpoint calls
* @author Shinichi Tomita <shinichi.tomita@gmail.com>
*/
/**
* API class for Apex REST endpoint call
*
* @class
* @param {Connection} conn Connection
*/
var Apex = function(conn) {
this._conn = conn;
};
/**
* @private
*/
Apex.prototype._baseUrl = function() {
return this._conn.instanceUrl + "/services/apexrest";
};
/**
* @private
*/
Apex.prototype._createRequestParams = function(method, path, body) {
var params = {
method: method,
url: this._baseUrl() + path
};
if (!/^(GET|DELETE)$/i.test(method)) {
params.headers = {
"Content-Type" : "application/json"
};
}
if (body) {
params.body = JSON.stringify(body);
}
return params;
};
/**
* Call Apex REST service in GET request
*
* @param {String} path - URL path to Apex REST service
* @param {Callback.<Object>} [callback] - Callback function
* @returns {Promise.<Object>}
*/
Apex.prototype.get = function(path, callback) {
return this._conn._request(this._createRequestParams('GET', path)).thenCall(callback);
};
/**
* Call Apex REST service in POST request
*
* @param {String} path - URL path to Apex REST service
* @param {Object} [body] - Request body
* @param {Callback.<Object>} [callback] - Callback function
* @returns {Promise.<Object>}
*/
Apex.prototype.post = function(path, body, callback) {
if (typeof body === 'function') {
callback = body;
body = undefined;
}
var params = this._createRequestParams('POST', path, body);
return this._conn._request(params).thenCall(callback);
};
/**
* Call Apex REST service in PUT request
*
* @param {String} path - URL path to Apex REST service
* @param {Object} [body] - Request body
* @param {Callback.<Object>} [callback] - Callback function
* @returns {Promise.<Object>}
*/
Apex.prototype.put = function(path, body, callback) {
if (typeof body === 'function') {
callback = body;
body = undefined;
}
var params = this._createRequestParams('PUT', path, body);
return this._conn._request(params).thenCall(callback);
};
/**
* Call Apex REST service in PATCH request
*
* @param {String} path - URL path to Apex REST service
* @param {Object} [body] - Request body
* @param {Callback.<Object>} [callback] - Callback function
* @returns {Promise.<Object>}
*/
Apex.prototype.patch = function(path, body, callback) {
if (typeof body === 'function') {
callback = body;
body = undefined;
}
var params = this._createRequestParams('PATCH', path, body);
return this._conn._request(params).thenCall(callback);
};
/**
* Synonym of Apex#delete()
*
* @method Apex#del
*
* @param {String} path - URL path to Apex REST service
* @param {Object} [body] - Request body
* @param {Callback.<Object>} [callback] - Callback function
* @returns {Promise.<Object>}
*/
/**
* Call Apex REST service in DELETE request
*
* @method Apex#delete
*
* @param {String} path - URL path to Apex REST service
* @param {Object} [body] - Request body
* @param {Callback.<Object>} [callback] - Callback function
* @returns {Promise.<Object>}
*/
Apex.prototype.del =
Apex.prototype["delete"] = function(path, callback) {
return this._conn._request(this._createRequestParams('DELETE', path)).thenCall(callback);
};
module.exports = Apex;
},{}],3:[function(require,module,exports){
(function (process){
/*global process*/
/**
* @file Manages Salesforce Bulk API related operations
* @author Shinichi Tomita <shinichi.tomita@gmail.com>
*/
var util = require('util'),
stream = require('stream'),
Stream = stream.Stream,
events = require('events'),
_ = require('underscore')._,
Connection = require('../connection'),
RecordStream = require('../record-stream'),
CSV = require('../csv'),
Promise = require('../promise');
/*--------------------------------------------*/
/**
* Class for Bulk API Job
*
* @protected
* @class Bulk~Job
* @extends events.EventEmitter
*
* @param {Bulk} bulk - Bulk API object
* @param {String} [type] - SObject type
* @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 {String} [jobId] - Job ID (if already available)
*/
var Job = function(bulk, type, operation, options, jobId) {
this._bulk = bulk;
this.type = type;
this.operation = operation;
this.options = options || {};
this.id = jobId;
this.state = this.id ? 'Open' : 'Unknown';
this._batches = {};
};
util.inherits(Job, events.EventEmitter);
/**
* Return latest jobInfo from cache
*
* @method Bulk~Job#open
* @param {Callback.<Bulk~JobInfo>} [callback] - Callback function
* @returns {Promise.<Bulk~JobInfo>}
*/
Job.prototype.info = function(callback) {
var self = this;
// if cache is not available, check the latest
if (!this._jobInfo) {
this._jobInfo = this.check();
}
return this._jobInfo.thenCall(callback);
};
/**
* Open new job and get jobinfo
*
* @method Bulk~Job#open
* @param {Callback.<Bulk~JobInfo>} [callback] - Callback function
* @returns {Promise.<Bulk~JobInfo>}
*/
Job.prototype.open = function(callback) {
var self = this;
var bulk = this._bulk;
var logger = bulk._logger;
// if not requested opening job
if (!this._jobInfo) {
var body = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<jobInfo xmlns="http://www.force.com/2009/06/asyncapi/dataload">',
'<operation>' + this.operation.toLowerCase() + '</operation>',
'<object>' + this.type + '</object>',
(this.options.extIdField ?
'<externalIdFieldName>'+this.options.extIdField+'</externalIdFieldName>' :
''),
'<contentType>CSV</contentType>',
'</jobInfo>'
].join('');
this._jobInfo = bulk._request({
method : 'POST',
path : "/job",
body : body,
headers : {
"Content-Type" : "application/xml; charset=utf-8"
},
responseType: "application/xml"
}).then(function(res) {
self.emit("open", res.jobInfo);
self.id = res.jobInfo.id;
self.state = res.jobInfo.state;
return res.jobInfo;
}, function(err) {
self.emit("error", err);
throw err;
});
}
return this._jobInfo.thenCall(callback);
};
/**
* Create a new batch instance in the job
*
* @method Bulk~Job#createBatch
* @returns {Bulk~Batch}
*/
Job.prototype.createBatch = function() {
var batch = new Batch(this);
var self = this;
batch.on('queue', function() {
self._batches[batch.id] = batch;
});
return batch;
};
/**
* Get a batch instance specified by given batch ID
*
* @method Bulk~Job#batch
* @param {String} batchId - Batch ID
* @returns {Bulk~Batch}
*/
Job.prototype.batch = function(batchId) {
var batch = this._batches[batchId];
if (!batch) {
batch = new Batch(this, batchId);
this._batches[batchId] = batch;
}
return batch;
};
/**
* Check the job status from server
*
* @method Bulk~Job#check
* @param {Callback.<Bulk~JobInfo>} [callback] - Callback function
* @returns {Promise.<Bulk~JobInfo>}
*/
Job.prototype.check = function(callback) {
var self = this;
var bulk = this._bulk;
var logger = bulk._logger;
this._jobInfo = this._waitAssign().then(function() {
return bulk._request({
method : 'GET',
path : "/job/" + self.id,
responseType: "application/xml"
});
}).then(function(res) {
logger.debug(res.jobInfo);
self.id = res.jobInfo.id;
self.type = res.jobInfo.object;
self.operation = res.jobInfo.operation;
self.state = res.jobInfo.state;
return res.jobInfo;
});
return this._jobInfo.thenCall(callback);
};
/**
* Wait till the job is assigned to server
*
* @method Bulk~Job#info
* @param {Callback.<Bulk~JobInfo>} [callback] - Callback function
* @returns {Promise.<Bulk~JobInfo>}
*/
Job.prototype._waitAssign = function(callback) {
return (this.id ? new Promise({ id: this.id }) : this.open()).thenCall(callback);
};
/**
* List all registered batch info in job
*
* @method Bulk~Job#list
* @param {Callback.<Array.<Bulk~BatchInfo>>} [callback] - Callback function
* @returns {Promise.<Array.<Bulk~BatchInfo>>}
*/
Job.prototype.list = function(callback) {
var self = this;
var bulk = this._bulk;
var logger = bulk._logger;
return this._waitAssign().then(function() {
return bulk._request({
method : 'GET',
path : "/job/" + self.id + "/batch",
responseType: "application/xml"
});
}).then(function(res) {
logger.debug(res.batchInfoList.batchInfo);
var batchInfoList = res.batchInfoList;
batchInfoList = _.isArray(batchInfoList.batchInfo) ? batchInfoList.batchInfo : [ batchInfoList.batchInfo ];
return batchInfoList;
}).thenCall(callback);
};
/**
* Close opened job
*
* @method Bulk~Job#close
* @param {Callback.<Bulk~JobInfo>} [callback] - Callback function
* @returns {Promise.<Bulk~JobInfo>}
*/
Job.prototype.close = function() {
var self = this;
return this._changeState("Closed").then(function(jobInfo) {
self.id = null;
self.emit("close", jobInfo);
return jobInfo;
}, function(err) {
self.emit("error", err);
throw err;
});
};
/**
* Set the status to abort
*
* @method Bulk~Job#abort
* @param {Callback.<Bulk~JobInfo>} [callback] - Callback function
* @returns {Promise.<Bulk~JobInfo>}
*/
Job.prototype.abort = function() {
var self = this;
return this._changeState("Aborted").then(function(jobInfo) {
self.id = null;
self.emit("abort", jobInfo);
return jobInfo;
}, function(err) {
self.emit("error", err);
throw err;
});
};
/**
* @private
*/
Job.prototype._changeState = function(state, callback) {
var self = this;
var bulk = this._bulk;
var logger = bulk._logger;
this._jobInfo = this._waitAssign().then(function() {
var body = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<jobInfo xmlns="http://www.force.com/2009/06/asyncapi/dataload">',
'<state>' + state + '</state>',
'</jobInfo>'
].join('');
return bulk._request({
method : 'POST',
path : "/job/" + self.id,
body : body,
headers : {
"Content-Type" : "application/xml; charset=utf-8"
},
responseType: "application/xml"
});
}).then(function(res) {
logger.debug(res.jobInfo);
self.state = res.jobInfo.state;
return res.jobInfo;
});
return this._jobInfo.thenCall(callback);
};
/*--------------------------------------------*/
/**
* Batch (extends RecordStream implements Sendable)
*
* @protected
* @class Bulk~Batch
* @extends {RecordStream}
* @implements {Promise.<Array.<RecordResult>>}
* @param {Bulk~Job} job - Bulk job object
* @param {String} [batchId] - Batch ID (if already available)
*/
var Batch = function(job, batchId) {
Batch.super_.apply(this);
this.sendable = true;
this.job = job;
this.id = batchId;
this._bulk = job._bulk;
this._csvStream = new RecordStream.CSVStream({ nullValue: '#N/A' });
this._csvStream.stream().pipe(this.stream());
this._deferred = Promise.defer();
};
util.inherits(Batch, RecordStream);
/**
* Execute batch operation
*
* @method Bulk~Batch#execute
* @param {Array.<Record>|stream.Stream|String} [input] - Input source for batch operation. Accepts array of records, CSv string, and CSV data input stream.
* @param {Callback.<Array.<RecordResult>>} [callback] - Callback function
* @returns {Bulk~Batch}
*/
Batch.prototype.run =
Batch.prototype.exec =
Batch.prototype.execute = function(input, callback) {
var self = this;
if (typeof input === 'function') { // if input argument is omitted
callback = input;
input = null;
}
// if batch is already executed
if (this._result) {
throw new Error("Batch already executed.");
}
var rdeferred = Promise.defer();
this._result = rdeferred.promise;
this._result.then(function(res) {
self._deferred.resolve(res);
}, function(err) {
self._deferred.reject(err);
});
this.once('response', function(res) {
rdeferred.resolve(res);
});
this.once('error', function(err) {
rdeferred.reject(err);
});
if (input instanceof Stream) {
input.pipe(this.stream());
} else {
var data;
if (_.isArray(input)) {
_.forEach(input, function(record) { self.send(record); });
self.end();
} else if (_.isString(input)){
data = input;
var stream = this.stream();
stream.write(data);
stream.end();
}
}
// return Batch instance for chaining
return this.thenCall(callback);
};
/**
* Promise/A+ interface
* http://promises-aplus.github.io/promises-spec/
*
* Delegate to deferred promise, return promise instance for batch result
*
* @method Bulk~Batch#then
*/
Batch.prototype.then = function(onResolved, onReject, onProgress) {
return this._deferred.promise.then(onResolved, onReject, onProgress);
};
/**
* Promise/A+ extension
* Call "then" using given node-style callback function
*
* @method Bulk~Batch#thenCall
*/
Batch.prototype.thenCall = function(callback) {
if (_.isFunction(callback)) {
this.then(function(res) {
process.nextTick(function() {
callback(null, res);
});
}, function(err) {
process.nextTick(function() {
callback(err);
});
});
}
return this;
};
/**
* @override
*/
Batch.prototype.send = function(record) {
record = _.clone(record);
if (this.job.operation === "insert") {
delete record.Id;
} else if (this.job.operation === "delete") {
record = { Id: record.Id };
}
delete record.type;
delete record.attributes;
return this._csvStream.send(record);
};
/**
* @override
*/
Batch.prototype.end = function(record) {
if (record) {
this.send(record);
}
this.sendable = false;
this._csvStream.end();
};
/**
* Check batch status in server
*
* @method Bulk~Batch#check
* @param {Callback.<Bulk~BatchInfo>} [callback] - Callback function
* @returns {Promise.<Bulk~BatchInfo>}
*/
Batch.prototype.check = function(callback) {
var self = this;
var bulk = this._bulk;
var logger = bulk._logger;
var jobId = this.job.id;
var batchId = this.id;
if (!jobId || !batchId) {
throw new Error("Batch not started.");
}
return bulk._request({
method : 'GET',
path : "/job/" + jobId + "/batch/" + batchId,
responseType: "application/xml"
}).then(function(res) {
logger.debug(res.batchInfo);
return res.batchInfo;
}).thenCall(callback);
};
/**
* Polling the batch result and retrieve
*
* @method Bulk~Batch#poll
* @param {Number} interval - Polling interval in milliseconds
* @param {Number} timeout - Polling timeout in milliseconds
*/
Batch.prototype.poll = function(interval, timeout) {
var self = this;
var jobId = this.job.id;
var batchId = this.id;
if (!jobId || !batchId) {
throw new Error("Batch not started.");
}
var startTime = new Date().getTime();
var poll = function() {
var now = new Date().getTime();
if (startTime + timeout < now) {
self.emit('error', new Error("polling time out"));
return;
}
self.check(function(err, res) {
if (err) {
self.emit('error', err);
} else {
if (res.state === "Failed") {
if (parseInt(res.numberRecordsProcessed, 10) > 0) {
self.retrieve();
} else {
self.emit('error', new Error(res.stateMessage));
}
} else if (res.state === "Completed") {
self.retrieve();
} else {
self.emit('progress', res);
setTimeout(poll, interval);
}
}
});
};
setTimeout(poll, interval);
};
/**
* Retrieve batch result
*
* @method Bulk~Batch#retrieve
* @param {Callback.<Array.<RecordResult>>} [callback] - Callback function
* @returns {Promise.<Array.<RecordResult>|Array.<Bulk~BatchResultReference>>}
*/
Batch.prototype.retrieve = function(callback) {
var self = this;
var bulk = this._bulk;
var jobId = this.job.id;
var job = this.job;
var batchId = this.id;
if (!jobId || !batchId) {
throw new Error("Batch not started.");
}
return job.info().then(function(jobInfo) {
return bulk._request({
method : 'GET',
path : "/job/" + jobId + "/batch/" + batchId + "/result"
});
}).then(function(res) {
var results;
if (job.operation === 'query') {
var conn = bulk._conn;
var resultIds = res['result-list'].result;
results = res['result-list'].result;
results = _.map(_.isArray(results) ? results : [ results ], function(id) {
return {
id: id,
batchId: batchId,
jobId: jobId
};
});
} else {
results = _.map(res, function(ret) {
return {
id: ret.Id || null,
success: ret.Success === "true",
errors: ret.Error ? [ ret.Error ] : []
};
});
}
self.emit('response', results);
return results;
}, function(err) {
self.emit('error', err);
throw err;
}).thenCall(callback);
};
/**
* Fetch query result as a record stream
* @param {String} resultId - Result id
* @returns {RecordStream.CSVStream} - Record stream, convertible to CSV data stream
*/
Batch.prototype.result = function(resultId) {
var jobId = this.job.id;
var batchId = this.id;
if (!jobId || !batchId) {
throw new Error("Batch not started.");
}
return new RecordStream.CSVStream(null,
this._bulk._request({
method : 'GET',
path : "/job/" + jobId + "/batch/" + batchId + "/result/" + resultId
}).stream()
);
};
/**
* @override
*/
Batch.prototype.stream = function() {
if (!this._stream) {
this._stream = new BatchStream(this);
}
return this._stream;
};
/*--------------------------------------------*/
/**
* Batch uploading stream (extends WritableStream)
*
* @private
* @class Bulk~BatchStream
* @extends stream.Stream
*/
var BatchStream = function(batch) {
BatchStream.super_.call(this);
this.batch = batch;
this.writable = true;
};
util.inherits(BatchStream, Stream);
/**
* @private
*/
BatchStream.prototype._getRequestStream = function() {
var batch = this.batch;
var bulk = batch._bulk;
var logger = bulk._logger;
if (!this._reqStream) {
this._reqStream = bulk._request({
method : 'POST',
path : "/job/" + batch.job.id + "/batch",
headers: {
"Content-Type": "text/csv"
},
responseType: "application/xml"
}, function(err, res) {
if (err) {
batch.emit('error', err);
} else {
logger.debug(res.batchInfo);
batch.id = res.batchInfo.id;
batch.emit('queue', res.batchInfo);
}
}).stream();
}
return this._reqStream;
};
/**
* @override
*/
BatchStream.prototype.write = function(data) {
var batch = this.batch;
if (!batch.job.id) {
this._queue(data);
return;
}
return this._getRequestStream().write(data);
};
/**
* @override
*/
BatchStream.prototype.end = function(data) {
var batch = this.batch;
if (!batch.job.id) {
this._ending = true;
if (data) {
this._queue(data);
}
return;
}
this.writable = false;
this._getRequestStream().end(data);
};
/**
* @private
*/
BatchStream.prototype._queue = function(data) {
var bstream = this;
var batch = this.batch;
var job = batch.job;
if (!this._buffer) {
this._buffer = [];
job.open(function(err) {
if (err) {
batch.emit("error", err);
return;
}
bstream._buffer.forEach(function(data) {
bstream.write(data);
});
if (bstream._ending) {
bstream.end();
}
bstream._buffer = [];
});
}
this._buffer.push(data);
};
/*--------------------------------------------*/
/**
* Class for Bulk API
*
* @class
* @param {Connection} conn - Connection object
*/
var Bulk = function(conn) {
this._conn = conn;
this._logger = conn._logger;
};
/**
* Polling interval in milliseconds
* @type {Number}
*/
Bulk.prototype.pollInterval = 1000;
/**
* Polling timeout in milliseconds
* @type {Number}
*/
Bulk.prototype.pollTimeout = 10000;
/** @private **/
Bulk.prototype._request = function(params, callback) {
var conn = this._conn;
params = _.clone(params);
var baseUrl = [ conn.instanceUrl, "services/async", conn.version ].join('/');
params.url = baseUrl + params.path;
var options = {
responseContentType: params.responseType,
beforesend: function(conn, params) {
params.headers["X-SFDC-SESSION"] = conn.accessToken;
},
parseError: function(err) {
return {
code: err.error.exceptionCode,
message: err.error.exceptionMessage
};
}
};
delete params.path;
delete params.responseType;
return this._conn._request(params, callback, options);
};
/**
* Create and start bulkload job and batch
*
* @param {String} type - SObject type
* @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}
*/
Bulk.prototype.load = function(type, operation, options, input, callback) {
var self = this;
if (!type || !operation) {
throw new Error("Insufficient arguments. At least, 'type' and 'operation' are required.");
}
if (operation.toLowerCase() !== 'upsert') { // options is only for upsert operation
callback = input;
input = options;
options = null;
}
var job = this.createJob(type, operation, options);
var batch = job.createBatch();
var cleanup = function() { job.close(); };
batch.on('response', cleanup);
batch.on('error', cleanup);
batch.on('queue', function() { batch.poll(self.pollInterval, self.pollTimeout); });
return batch.execute(input, callback);
};
/**
* Execute bulk query and get record stream
*
* @param {String} soql - SOQL to execute in bulk job
* @returns {RecordStream.CSVStream} - Record stream, convertible to CSV data stream
*/
Bulk.prototype.query = function(soql) {
var m = soql.replace(/\([\s\S]+\)/g, '').match(/FROM\s+(\w+)/i);
if (!m) {
throw new Error("No sobject type found in query, maybe caused by invalid SOQL.");
}
var type = m[1];
var self = this;
var rstream = new RecordStream.CSVStream();
this.load(type, "query", soql).then(function(results) {
// Ideally, it should merge result files into one stream.
// Currently only first batch result is the target (mostly enough).
var r = results[0];
var result = self.job(r.jobId).batch(r.batchId).result(r.id);
result.stream().pipe(rstream.stream());
});
return rstream;
};
/**
* Create a new job instance
*
* @param {String} type - SObject type
* @param {String} operation - Bulk load operation ('insert', 'update', 'upsert', 'delete', or 'hardDelete')
* @param {Object} [options] - Options for bulk loading operation
* @returns {Bulk~Job}
*/
Bulk.prototype.createJob = function(type, operation, options) {
var job = new Job(this, type, operation, options);
job.open();
return job;
};
/**
* Get a job instance specified by given job ID
*
* @param {String} jobId - Job ID
* @returns {Bulk~Job}
*/
Bulk.prototype.job = function(jobId) {
return new Job(this, null, null, null, jobId);
};
/*--------------------------------------------*/
module.exports = Bulk;
}).call(this,require('_process'))
},{"../connection":14,"../csv":16,"../promise":21,"../record-stream":23,"_process":39,"events":37,"stream":43,"underscore":63,"util":46}],4:[function(require,module,exports){
/**
* @file Manages Salesforce Chatter REST API calls
* @author Shinichi Tomita <shinichi.tomita@gmail.com>
*/
var util = require('util'),
_ = require('underscore'),
Promise = require('../promise');
/**
* API class for Chatter REST API call
*
* @class
* @param {Connection} conn Connection
*/
var Chatter = module.exports = function(conn) {
this._conn = conn;
};
/**
* Sending request to API endpoint
* @private
*/
Chatter.prototype._request = function(params, callback) {
if (/^(put|post|patch)$/i.test(params.method)) {
if (_.isObject(params.body)) {
params.headers = {
"Content-Type": "application/json"
};
params.body = JSON.stringify(params.body);
}
}
params.url = this._normalizeUrl(params.url);
return this._conn._request(params, callback);
};
/**
* Convert path to site root relative url
* @private
*/
Chatter.prototype._normalizeUrl = function(url) {
if (url.indexOf('/chatter/') === 0 || url.indexOf('/connect/') === 0) {
return '/services/data/v' + this._conn.version + url;
} else if (/^\/v[\d]+\.[\d]+\//.test(url)) {
return '/services/data' + url;
} else if (url.indexOf('/services/') !== 0 && url[0] === '/') {
return '/services/data/v' + this._conn.version + '/chatter' + url;
} else {
return url;
}
};
/**
* @typedef {Object} Chatter~RequestParams
* @prop {String} method - HTTP method
* @prop {String} url - Resource URL
* @prop {String} [body] - HTTP body (in POST/PUT/PATCH methods)
*/
/**
* @typedef {Object} Chatter~RequestResult
*/
/**
* Make a request for chatter API resource
*
* @param {Chatter~RequestParams} params - Paramters representing HTTP request
* @param {Callback.<Chatter~RequestResult>} [callback] - Callback func
* @returns {Chatter~Request}
*/
Chatter.prototype.request = function(params, callback) {
return new Request(this, params).thenCall(callback);
};
/**
* Make a resource request to chatter API
*
* @param {String} url - Resource URL
* @param {Object} [queryParams] - Query parameters (in hash object)
* @returns {Chatter~Resource}
*/
Chatter.prototype.resource = function(url, queryParams) {
return new Resource(this, url, queryParams);
};
/**
* @typedef {Object} Chatter~BatchRequestResult
* @prop {Boolean} hasError - Flag if the batch has one or more errors
* @prop {Array.<Object>} results - Batch request results in array
* @prop {Number} results.statusCode - HTTP response status code
* @prop {Chatter~RequestResult} results.result - Parsed HTTP response body
*/
/**
* Make a batch request to chatter API
*
* @params {Array.<Chatter~Request>} requests - Chatter API requests
* @param {Callback.<Chatter~BatchRequestResult>} [callback] - Callback func
* @returns {Promise.<Chatter~BatchRequestResult>}
*/
Chatter.prototype.batch = function(requests, callback) {
var self = this;
var batchRequests = [], batchDeferreds = [];
_.forEach(requests, function(request) {
var deferred = Promise.defer();
request._promise = deferred.promise;
batchRequests.push(request.batchParams());
batchDeferreds.push(deferred);
});
var params = {
method: 'POST',
url: this._normalizeUrl('/connect/batch'),
body: {
batchRequests: batchRequests
}
};
return this._request(params).then(function(res) {
_.forEach(res.results, function(result, i) {
var deferred = batchDeferreds[i];
if (result.statusCode >= 400) {
deferred.reject(result.result);
} else {
deferred.resolve(result.result);
}
});
return res;
}).thenCall(callback);
};
/*--------------------------------------------*/
/**
* A class representing chatter API request
*
* @protected
* @class Chatter~Request
* @implements {Promise.<Chatter~RequestResult>}
* @param {Chatter} chatter - Chatter API object
* @param {Chatter~RequestParams} params - Paramters representing HTTP request
*/
var Request = function(chatter, params) {
this._chatter = chatter;
this._params = params;
this._promise = null;
};
/**
* @typedef {Object} Chatter~BatchRequestParams
* @prop {String} method - HTTP method
* @prop {String} url - Resource URL
* @prop {String} [richInput] - HTTP body (in POST/PUT/PATCH methods)
*/
/**
* Retrieve parameters in batch request form
*
* @method Chatter~Request#batchParams
* @returns {Chatter~BatchRequestParams}
*/
Request.prototype.batchParams = function() {
var params = this._params;
var batchParams = {
method: params.method,
url: this._chatter._normalizeUrl(params.url)
};
if (this._params.body) {
batchParams.richInput = this._params.body;
}
return batchParams;
};
/**
* Retrieve parameters in batch request form
*
* @method Chatter~Request#promise
* @returns {Promise.<Chatter~RequestResult>}
*/
Request.prototype.promise = function() {
return this._promise || this._chatter._request(this._params);
};
/**
* Returns Node.js Stream object for request
*
* @method Chatter~Request#stream
* @returns {stream.Stream}
*/
Request.prototype.stream = function() {
return this._chatter._request(this._params).stream();
};
/**
* Promise/A+ interface
* http://promises-aplus.github.io/promises-spec/
*
* Delegate to deferred promise, return promise instance for batch result
*
* @method Chatter~Request#then
*/
Request.prototype.then = function(onResolve, onReject) {
return this.promise().then(onResolve, onReject);
};
/**
* Promise/A+ extension
* Call "then" using given node-style callback function
*
* @method Chatter~Request#thenCall
*/
Request.prototype.thenCall = function(callback) {
return _.isFunction(callback) ? this.promise().thenCall(callback) : this;
};
/*--------------------------------------------*/
/**
* A class representing chatter API resource
*
* @protected
* @class Chatter~Resource
* @extends Chatter~Request
* @param {Chatter} chatter - Chatter API object
* @param {String} url - Resource URL
* @param {Object} [queryParams] - Query parameters (in hash object)
*/
var Resource = function(chatter, url, queryParams) {
if (queryParams) {
var qstring = _.map(_.keys(queryParams), function(name) {
return name + "=" + encodeURIComponent(queryParams[name]);
}).join('&');
url += (url.indexOf('?') > 0 ? '&' : '?') + qstring;
}
Resource.super_.call(this, chatter, { method: 'GET', url: url });
this._url = url;
};
util.inherits(Resource, Request);
/**
* Create a new resource
*
* @method Chatter~Resource#create
* @param {Object} data - Data to newly post
* @param {Callback.<Chatter~RequestResult>} [callback] - Callback function
* @returns {Chatter~Request}
*/
Resource.prototype.create = function(data, callback) {
return this._chatter.request({
method: 'POST',
url: this._url,
body: data
}).thenCall(callback);
};
/**
* Retrieve resource content
*
* @method Chatter~Resource#retrieve
* @param {Callback.<Chatter~RequestResult>} [callback] - Callback function
* @returns {Chatter~Request}
*/
Resource.prototype.retrieve = function(callback) {
return this.thenCall(callback);
};
/**
* Update specified resource
*
* @method Chatter~Resource#update
* @param {Obejct} data - Data to update
* @param {Callback.<Chatter~RequestResult>} [callback] - Callback function
* @returns {Chatter~Request}
*/
Resource.prototype.update = function(data, callback) {
return this._chatter.request({
method: 'POST',
url: this._url,
body: data
}).thenCall(callback);
};
/**
* Synonym of Resource#delete()
*
* @method Chatter~Resource#del
* @param {Callback.<Chatter~RequestResult>} [callback] - Callback function
* @returns {Chatter~Request}
*/
/**
* Delete specified resource
*
* @method Chatter~Resource#delete
* @param {Callback.<Chatter~RequestResult>} [callback] - Callback function
* @returns {Chatter~Request}
*/
Resource.prototype.del =
Resource.prototype["delete"] = function(callback) {
return this._chatter.request({
method: 'DELETE',
url: this._url
}).thenCall(callback);
};
},{"../promise":21,"underscore":63,"util":46}],5:[function(require,module,exports){
(function (process,Buffer){
/*global process, Buffer */
/**
* @file Manages Salesforce Metadata API
* @author Shinichi Tomita <shinichi.tomita@gmail.com>
*/
var util = require('util'),
events = require('events'),
stream = require('stream'),
Stream = stream.Stream,
_ = require('underscore'),
Promise = require('../promise'),
SOAP = require('../soap');
/*--------------------------------------------*/
/**
* Class for Salesforce Metadata API
*
* @class
* @param {Connection} conn - Connection object
*/
var Metadata = module.exports = function(conn) {
this._conn = conn;
};
/**
* Polling interval in milliseconds
* @type {Number}
*/
Metadata.prototype.pollInterval = 1000;
/**
* Polling timeout in milliseconds
* @type {Number}
*/
Metadata.prototype.pollTimeout = 10000;
/**
* Call Metadata API SOAP endpoint
*
* @private
*/
Metadata.prototype._invoke = function(method, message, callback) {
var soapEndpoint = new SOAP({
sessionId: this._conn.accessToken,
serverUrl: this._conn.instanceUrl + "/services/Soap/m/" + this._conn.version,
xmlns: "http://soap.sforce.com/2006/04/metadata"
}, this._conn._transport);
return soapEndpoint.invoke(method, message).then(function(res) {
return res.result;
}).thenCall(callback);
};
/**
* @typedef {Object} Metadata~MetadataInfo
* @prop {String} fullName - The name of the component
*/
/**
* Asynchronously adds one or more new metadata components to the organization.
*
* @param {String} type - The type of metadata to create
* @param {Metadata~MetadataInfo|Array.<Metadata~MetadataInfo>} metadata - Metadata to create
* @param {Callback.<Metadata~AsyncResult|Array.<Metadata~AsyncResult>>} [callback] - Callback function
* @returns {Metadata~AsyncResultLocator}
*/
Metadata.prototype.createAsync = function(type, metadata, callback) {
if (Number(this._conn.version) > 30) {
throw new Error("Async metadata CRUD calls are not supported on ver 31.0 or later.");
}
var convert = function(md) {
md["@xsi:type"] = type;
return md;
};
var isArray = _.isArray(metadata);
metadata = isArray ? _.map(metadata, convert) : convert(metadata);
var res = this._invoke("create", { metadata: metadata });
return new AsyncResultLocator(this, res, isArray).thenCall(callback);
};
/**
* @private
*/
function convertToSaveResult(result) {
var saveResult = _.clone(result);
saveResult.success = saveResult.success === 'true';
return saveResult;
}
/**
* Synonym of Metadata#create().
*
* @method createSync
* @param {String} type - The type of metadata to create
* @param {Metadata~MetadataInfo|Array.<Metadata~MetadataInfo>} metadata - Metadata to create
* @param {Callback.<Metadata~SaveResult|Array.<Metadata~SaveResult>>} [callback] - Callback function
* @returns {Promise.<Metadata~SaveResult|Array.<Metadata~SaveResult>>}
*/
/**
* Synchronously adds one or more new metadata components to the organization.
*
* @method Metadata#create
* @param {String} type - The type of metadata to create
* @param {Metadata~MetadataInfo|Array.<Metadata~MetadataInfo>} metadata - Metadata to create
* @param {Callback.<Metadata~SaveResult|Array.<Metadata~SaveResult>>} [callback] - Callback function
* @returns {Promise.<Metadata~SaveResult|Array.<Metadata~SaveResult>>}
*/
Metadata.prototype.createSync =
Metadata.prototype.create = function(type, metadata, callback) {
var convert = function(md) {
md["@xsi:type"] = type;
return md;
};
var isArray = _.isArray(metadata);
metadata = isArray ? _.map(metadata, convert) : convert(metadata);
return this._invoke("createMetadata", { metadata: metadata }).then(function(results) {
return _.isArray(results) ? _.map(results, convertToSaveResult) : convertToSaveResult(results);
}).thenCall(callback);
};
/**
* @private
*/
function convertToMetadataInfo(rec) {
var metadataInfo = _.clone(rec);
delete metadataInfo.$;
return metadataInfo;
}
/**
* Synonym of Metadata#read()
*
* @method Metadata#readSync
* @param {String} type - The type of metadata to read
* @param {String|Array.<String>} fullNames - full name(s) of metadata objects to read
* @param {Callback.<Metadata~ReadResult|Array.<Metadata~ReadResult>>} [callback] - Callback function
* @returns {Promise.<Array.<Metadata~MetadataInfo|Array.<Metadata~MetadataInfo>>>}
*/
/**
* Synchronously read specified metadata components in the organization.
*
* @method Metadata#read
* @param {String} type - The type of metadata to read
* @param {String|Array.<String>} fullNames - full name(s) of metadata objects to read
* @param {Callback.<Metadata~ReadResult|Array.<Metadata~ReadResult>>} [callback] - Callback function
* @returns {Promise.<Array.<Metadata~MetadataInfo|Array.<Metadata~MetadataInfo>>>}
*/
Metadata.prototype.readSync =
Metadata.prototype.read = function(type, fullNames, callback) {
return this._invoke("readMetadata", { type: type, fullNames: fullNames }).then(function(res) {
return _.isArray(res.records) ? _.map(res.records, convertToMetadataInfo) : convertToMetadataInfo(res.records);
}).thenCall(callback);
};
/**
* @typedef {Object} Metadata~UpdateMetadataInfo
* @prop {String} currentName - The API name of the component or field before the update
* @prop {Metadata~MetadataInfo} metadata - Full specification of the component or field you wish to update
*/
/**
* Asynchronously updates one or more metadata components in the organization.
*
* @param {String} type - The type of metadata to update
* @param {Metadata~UpdateMetadataInfo|Array.<Metadata~UpdateMetadataInfo>} updateMetadata - Updating metadata
* @param {Callback.<Metadata~AsyncResult|Array.<Metadata~AsyncResult>>} [callback] - Callback function
* @returns {Metadata~AsyncResultLocator}
*/
Metadata.prototype.updateAsync = function(type, updateMetadata, callback) {
if (Number(this._conn.version) > 30) {
throw new Error("Async metadata CRUD calls are not supported on ver 31.0 or later.");
}
var convert = function(umd) {
umd.metadata["@xsi:type"] = type;
return umd;
};
var isArray = _.isArray(updateMetadata);
updateMetadata = isArray ? _.map(updateMetadata, convert) : convert(updateMetadata);
var res = this._invoke("update", { updateMetadata: updateMetadata });
return new AsyncResultLocator(this, res, isArray).thenCall(callback);
};
/**
* Synonym of Metadata#update().
*
* @method Metadata#updateSync
* @param {String} type - The type of metadata to update
* @param {Metadata~MetadataInfo|Array.<Metadata~MetadataInfo>} updateMetadata - Updating metadata
* @param {Callback.<Metadata~SaveResult|Array.<Metadata~SaveResult>>} [callback] - Callback function
* @returns {Promise.<Metadata~SaveResult|Array.<Metadata~SaveResult>>}
*/
/**
* Synchronously updates one or more metadata components in the organization.
*
* @method Metadata#update
* @param {String} type - The type of metadata to update
* @param {Metadata~MetadataInfo|Array.<Metadata~MetadataInfo>} updateMetadata - Updating metadata
* @param {Callback.<Metadata~SaveResult|Array.<Metadata~SaveResult>>} [callback] - Callback function
* @returns {Promise.<Metadata~SaveResult|Array.<Metadata~SaveResult>>}
*/
Metadata.prototype.updateSync =
Metadata.prototype.update = function(type, metadata, callback) {
var convert = function(md) {
md["@xsi:type"] = type;
return md;
};
var isArray = _.isArray(metadata);
metadata = isArray ? _.map(metadata, convert) : convert(metadata);
return this._invoke("updateMetadata", { metadata: metadata }).then(function(results) {
return _.isArray(results) ? _.map(results, convertToSaveResult) : convertToSaveResult(results);
}).thenCall(callback);
};
/**
* Upserts one or more components in your organization's data.
*
* @param {String} type - The type of metadata to upsert
* @param {Metadata~MetadataInfo|Array.<Metadata~MetadataInfo>} metadata - Upserting metadata
* @param {Callback.<Metadata~SaveResult|Array.<Metadata~SaveResult>>} [callback] - Callback function
* @returns {Promise.<Metadata~SaveResult|Array.<Metadata~SaveResult>>}
*/
Metadata.prototype.upsertSync =
Metadata.prototype.upsert = function(type, metadata, callback) {
var convert = function(md) {
md["@xsi:type"] = type;
return md;
};
var isArray = _.isArray(metadata);
metadata = isArray ? _.map(metadata, convert) : convert(metadata);
return this._invoke("upsertMetadata", { metadata: metadata }).then(function(results) {
return _.isArray(results) ? _.map(results, convertToSaveResult) : convertToSaveResult(results);
}).thenCall(callback);
};
/**
* Asynchronously deletes specified metadata components in the organization.
*
* @param {String} type - The type of metadata to delete
* @param {String|Metadata~MetadataInfo|Array.<String>|Array.<Metadata~MetadataInfo>} metadata - The fullName of metadata or metadata info to delete. If it is passed in fullName, the type parameter should not be empty.
* @param {Callback.<Metadata~AsyncResult|Array.<Metadata~AsyncResult>>} [callback] - Callback function
* @returns {Metadata~AsyncResultLocator}
*/
Metadata.prototype.deleteAsync = function(type, metadata, callback) {
if (Number(this._conn.version) > 30) {
throw new Error("Async metadata CRUD calls are not supported on ver 31.0 or later.");
}
var convert = function(md) {
if (_.isString(md)) {
md = { fullName : md };
}
md["@xsi:type"] = type;
return md;
};
var isArray = _.isArray(metadata);
metadata = isArray ? _.map(metadata, convert) : convert(metadata);
var res = this._invoke("delete", { metadata: metadata });
return new AsyncResultLocator(this, res, isArray).thenCall(callback);
};
/**
* Synonym of Metadata#delete().
*
* @deprecated
* @method Metadata#del
* @param {String} [type] - The type of metadata to delete
* @param {String|Metadata~MetadataInfo|Array.<String>|Array.<Metadata~MetadataInfo>} metadata - The fullName of metadata or metadata info to delete. If it is passed in fullName, the type parameter should not be empty.
* @param {Callback.<Metadata~AsyncResult|Array.<Metadata~AsyncResult>>} [callback] - Callback function
* @returns {Metadata~AsyncResultLocator}
*/
/**
* Synonym of Metadata#delete().
*
* @method Metadata#deleteSync
* @param {String} type - The type of metadata to delete
* @param {String|Array.<String>} fullNames - The fullName of metadata to delete.
* @param {Callback.<Metadata~SaveResult|Array.<Metadata~SaveResult>>} [callback] - Callback function
* @returns {Promise.<Metadata~SaveResult|Array.<Metadata~SaveResult>>}
*/
/**
* Synchronously deletes specified metadata components in the organization.
*
* @method Metadata#delete
* @param {String} type - The type of metadata to delete
* @param {String|Array.<String>} fullNames - The fullName of metadata to delete.
* @param {Callback.<Metadata~SaveResult|Array.<Metadata~SaveResult>>} [callback] - Callback function
* @returns {Promise.<Metadata~SaveResult|Array.<Metadata~SaveResult>>}
*/
Metadata.prototype.del =
Metadata.prototype.deleteSync =
Metadata.prototype["delete"] = function(type, fullNames, callback) {
return this._invoke("deleteMetadata", { type: type, fullNames: fullNames }).then(function(results) {
return _.isArray(results) ? _.map(results, convertToSaveResult) : convertToSaveResult(results);
}).thenCall(callback);
};
/**
* Rename fullname of a metadata component in the organization
*
* @param {String} type - The type of metadata to delete
* @param {String} oldFullName - The original fullName of metadata
* @param {String} newFullName - The new fullName of metadata
* @param {Callback.<Metadata~SaveResult>} [callback] - Callback function
* @returns {Promise.<Metad