UNPKG

jsforce

Version:

Salesforce API Library for JavaScript

1,730 lines (1,586 loc) 703 kB
!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