jsforce2 1.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +74 -0
- package/bin/jsforce +3 -0
- package/bower.json +30 -0
- package/build/jsforce-api-analytics.js +393 -0
- package/build/jsforce-api-analytics.min.js +2 -0
- package/build/jsforce-api-analytics.min.js.map +1 -0
- package/build/jsforce-api-apex.js +183 -0
- package/build/jsforce-api-apex.min.js +2 -0
- package/build/jsforce-api-apex.min.js.map +1 -0
- package/build/jsforce-api-bulk.js +1054 -0
- package/build/jsforce-api-bulk.min.js +2 -0
- package/build/jsforce-api-bulk.min.js.map +1 -0
- package/build/jsforce-api-chatter.js +320 -0
- package/build/jsforce-api-chatter.min.js +2 -0
- package/build/jsforce-api-chatter.min.js.map +1 -0
- package/build/jsforce-api-metadata.js +3020 -0
- package/build/jsforce-api-metadata.min.js +2 -0
- package/build/jsforce-api-metadata.min.js.map +1 -0
- package/build/jsforce-api-soap.js +403 -0
- package/build/jsforce-api-soap.min.js +2 -0
- package/build/jsforce-api-soap.min.js.map +1 -0
- package/build/jsforce-api-streaming.js +3479 -0
- package/build/jsforce-api-streaming.min.js +2 -0
- package/build/jsforce-api-streaming.min.js.map +1 -0
- package/build/jsforce-api-tooling.js +319 -0
- package/build/jsforce-api-tooling.min.js +2 -0
- package/build/jsforce-api-tooling.min.js.map +1 -0
- package/build/jsforce-core.js +25250 -0
- package/build/jsforce-core.min.js +2 -0
- package/build/jsforce-core.min.js.map +1 -0
- package/build/jsforce.js +31637 -0
- package/build/jsforce.min.js +2 -0
- package/build/jsforce.min.js.map +1 -0
- package/core.js +1 -0
- package/index.js +1 -0
- package/lib/VERSION.js +2 -0
- package/lib/_required.js +29 -0
- package/lib/api/analytics.js +387 -0
- package/lib/api/apex.js +177 -0
- package/lib/api/bulk.js +862 -0
- package/lib/api/chatter.js +314 -0
- package/lib/api/index.js +8 -0
- package/lib/api/metadata.js +848 -0
- package/lib/api/soap.js +397 -0
- package/lib/api/streaming-extension.js +136 -0
- package/lib/api/streaming.js +270 -0
- package/lib/api/tooling.js +313 -0
- package/lib/browser/canvas.js +90 -0
- package/lib/browser/client.js +241 -0
- package/lib/browser/core.js +5 -0
- package/lib/browser/jsforce.js +6 -0
- package/lib/browser/jsonp.js +52 -0
- package/lib/browser/request.js +70 -0
- package/lib/cache.js +252 -0
- package/lib/cli/cli.js +431 -0
- package/lib/cli/repl.js +337 -0
- package/lib/connection.js +1881 -0
- package/lib/core.js +16 -0
- package/lib/csv.js +50 -0
- package/lib/date.js +163 -0
- package/lib/http-api.js +300 -0
- package/lib/jsforce.js +10 -0
- package/lib/logger.js +52 -0
- package/lib/oauth2.js +206 -0
- package/lib/process.js +275 -0
- package/lib/promise.js +164 -0
- package/lib/query.js +881 -0
- package/lib/quick-action.js +90 -0
- package/lib/record-stream.js +305 -0
- package/lib/record.js +107 -0
- package/lib/registry/file-registry.js +48 -0
- package/lib/registry/index.js +3 -0
- package/lib/registry/registry.js +111 -0
- package/lib/require.js +14 -0
- package/lib/soap.js +207 -0
- package/lib/sobject.js +558 -0
- package/lib/soql-builder.js +236 -0
- package/lib/transport.js +233 -0
- package/package.json +110 -0
|
@@ -0,0 +1,1881 @@
|
|
|
1
|
+
/*global Buffer */
|
|
2
|
+
/**
|
|
3
|
+
* @file Connection class to keep the API session information and manage requests
|
|
4
|
+
* @author Shinichi Tomita <shinichi.tomita@gmail.com>
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
var events = require('events'),
|
|
10
|
+
inherits = require('inherits'),
|
|
11
|
+
_ = require('lodash/core'),
|
|
12
|
+
Promise = require('./promise'),
|
|
13
|
+
Logger = require('./logger'),
|
|
14
|
+
OAuth2 = require('./oauth2'),
|
|
15
|
+
Query = require('./query'),
|
|
16
|
+
SObject = require('./sobject'),
|
|
17
|
+
QuickAction = require('./quick-action'),
|
|
18
|
+
HttpApi = require('./http-api'),
|
|
19
|
+
Transport = require('./transport'),
|
|
20
|
+
Process = require('./process'),
|
|
21
|
+
Cache = require('./cache');
|
|
22
|
+
|
|
23
|
+
var defaults = {
|
|
24
|
+
loginUrl: "https://login.salesforce.com",
|
|
25
|
+
instanceUrl: "",
|
|
26
|
+
version: "42.0"
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/*
|
|
30
|
+
* Constant of maximum records num in DML operation (update/delete)
|
|
31
|
+
*/
|
|
32
|
+
var MAX_DML_COUNT = 200;
|
|
33
|
+
|
|
34
|
+
/*
|
|
35
|
+
* Constant of maximum number of requests that can be batched
|
|
36
|
+
*/
|
|
37
|
+
var MAX_BATCH_REQUESTS = 25;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Connection class to keep the API session information and manage requests
|
|
41
|
+
*
|
|
42
|
+
* @constructor
|
|
43
|
+
* @extends events.EventEmitter
|
|
44
|
+
* @param {Object} [options] - Connection options
|
|
45
|
+
* @param {OAuth2|Object} [options.oauth2] - OAuth2 instance or options to be passed to OAuth2 constructor
|
|
46
|
+
* @param {String} [options.logLevel] - Output logging level (DEBUG|INFO|WARN|ERROR|FATAL)
|
|
47
|
+
* @param {String} [options.version] - Salesforce API Version (without "v" prefix)
|
|
48
|
+
* @param {Number} [options.maxRequest] - Max number of requests allowed in parallel call
|
|
49
|
+
* @param {String} [options.loginUrl] - Salesforce Login Server URL (e.g. https://login.salesforce.com/)
|
|
50
|
+
* @param {String} [options.instanceUrl] - Salesforce Instance URL (e.g. https://na1.salesforce.com/)
|
|
51
|
+
* @param {String} [options.serverUrl] - Salesforce SOAP service endpoint URL (e.g. https://na1.salesforce.com/services/Soap/u/28.0)
|
|
52
|
+
* @param {String} [options.accessToken] - Salesforce OAuth2 access token
|
|
53
|
+
* @param {String} [options.sessionId] - Salesforce session ID
|
|
54
|
+
* @param {String} [options.refreshToken] - Salesforce OAuth2 refresh token
|
|
55
|
+
* @param {String|Object} [options.signedRequest] - Salesforce Canvas signed request (Raw Base64 string, JSON string, or deserialized JSON)
|
|
56
|
+
* @param {String} [options.proxyUrl] - Cross-domain proxy server URL, used in browser client, non Visualforce app.
|
|
57
|
+
* @param {String} [options.httpProxy] - URL of HTTP proxy server, used in server client.
|
|
58
|
+
* @param {Object} [options.callOptions] - Call options used in each SOAP/REST API request. See manual.
|
|
59
|
+
*/
|
|
60
|
+
var Connection = module.exports = function(options) {
|
|
61
|
+
options = options || {};
|
|
62
|
+
|
|
63
|
+
this._logger = new Logger(options.logLevel);
|
|
64
|
+
|
|
65
|
+
var oauth2 = options.oauth2 || {
|
|
66
|
+
loginUrl : options.loginUrl,
|
|
67
|
+
clientId : options.clientId,
|
|
68
|
+
clientSecret : options.clientSecret,
|
|
69
|
+
redirectUri : options.redirectUri,
|
|
70
|
+
proxyUrl: options.proxyUrl,
|
|
71
|
+
httpProxy: options.httpProxy
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* OAuth2 object
|
|
76
|
+
* @member {OAuth2} Connection#oauth2
|
|
77
|
+
*/
|
|
78
|
+
this.oauth2 = oauth2 = oauth2 instanceof OAuth2 ? oauth2 : new OAuth2(oauth2);
|
|
79
|
+
|
|
80
|
+
this.loginUrl = options.loginUrl || oauth2.loginUrl || defaults.loginUrl;
|
|
81
|
+
this.version = options.version || defaults.version;
|
|
82
|
+
this.maxRequest = options.maxRequest || this.maxRequest || 10;
|
|
83
|
+
|
|
84
|
+
/** @private */
|
|
85
|
+
if (options.proxyUrl) {
|
|
86
|
+
this._transport = new Transport.ProxyTransport(options.proxyUrl);
|
|
87
|
+
} else if (options.httpProxy) {
|
|
88
|
+
this._transport = new Transport.HttpProxyTransport(options.httpProxy);
|
|
89
|
+
} else {
|
|
90
|
+
this._transport = new Transport();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this.callOptions = options.callOptions;
|
|
94
|
+
|
|
95
|
+
/*
|
|
96
|
+
* Fire connection:new event to notify jsforce plugin modules
|
|
97
|
+
*/
|
|
98
|
+
var jsforce = require('./core');
|
|
99
|
+
jsforce.emit('connection:new', this);
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Streaming API object
|
|
103
|
+
* @member {Streaming} Connection#streaming
|
|
104
|
+
*/
|
|
105
|
+
// this.streaming = new Streaming(this);
|
|
106
|
+
/**
|
|
107
|
+
* Bulk API object
|
|
108
|
+
* @member {Bulk} Connection#bulk
|
|
109
|
+
*/
|
|
110
|
+
// this.bulk = new Bulk(this);
|
|
111
|
+
/**
|
|
112
|
+
* Tooling API object
|
|
113
|
+
* @member {Tooling} Connection#tooling
|
|
114
|
+
*/
|
|
115
|
+
// this.tooling = new Tooling(this);
|
|
116
|
+
/**
|
|
117
|
+
* Analytics API object
|
|
118
|
+
* @member {Analytics} Connection#analytics
|
|
119
|
+
*/
|
|
120
|
+
// this.analytics = new Analytics(this);
|
|
121
|
+
/**
|
|
122
|
+
* Chatter API object
|
|
123
|
+
* @member {Chatter} Connection#chatter
|
|
124
|
+
*/
|
|
125
|
+
// this.chatter = new Chatter(this);
|
|
126
|
+
/**
|
|
127
|
+
* Metadata API object
|
|
128
|
+
* @member {Metadata} Connection#metadata
|
|
129
|
+
*/
|
|
130
|
+
// this.metadata = new Metadata(this);
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* SOAP API object
|
|
134
|
+
* @member {SoapApi} Connection#soap
|
|
135
|
+
*/
|
|
136
|
+
// this.soap = new SoapApi(this);
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Apex REST API object
|
|
140
|
+
* @member {Apex} Connection#apex
|
|
141
|
+
*/
|
|
142
|
+
// this.apex = new Apex(this);
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @member {Process} Connection#process
|
|
146
|
+
*/
|
|
147
|
+
this.process = new Process(this);
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Cache object for result
|
|
151
|
+
* @member {Cache} Connection#cache
|
|
152
|
+
*/
|
|
153
|
+
this.cache = new Cache();
|
|
154
|
+
|
|
155
|
+
// Allow to delegate connection refresh to outer function
|
|
156
|
+
var self = this;
|
|
157
|
+
var refreshFn = options.refreshFn;
|
|
158
|
+
if (!refreshFn && this.oauth2.clientId) {
|
|
159
|
+
refreshFn = oauthRefreshFn;
|
|
160
|
+
}
|
|
161
|
+
if (refreshFn) {
|
|
162
|
+
this._refreshDelegate = new HttpApi.SessionRefreshDelegate(this, refreshFn);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
var cacheOptions = {
|
|
166
|
+
key: function(type) {
|
|
167
|
+
return type
|
|
168
|
+
? type.type ? "describe." + type.type : "describe." + type
|
|
169
|
+
: "describe";
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
this.describe$ = this.cache.makeCacheable(this.describe, this, cacheOptions);
|
|
173
|
+
this.describe = this.cache.makeResponseCacheable(this.describe, this, cacheOptions);
|
|
174
|
+
this.describeSObject$ = this.describe$;
|
|
175
|
+
this.describeSObject = this.describe;
|
|
176
|
+
|
|
177
|
+
var batchCacheOptions = {
|
|
178
|
+
key: function(options) {
|
|
179
|
+
var types = options.types;
|
|
180
|
+
var autofetch = options.autofetch || false;
|
|
181
|
+
var typesToFetch = types.length > MAX_BATCH_REQUESTS
|
|
182
|
+
? (autofetch ? types : types.slice(0, MAX_BATCH_REQUESTS))
|
|
183
|
+
: types;
|
|
184
|
+
var keys = [];
|
|
185
|
+
typesToFetch.forEach(function (type) { keys.push('describe.' + type); });
|
|
186
|
+
return keys;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
this.batchDescribe = this.cache.makeResponseCacheable(this.batchDescribe, this, batchCacheOptions);
|
|
190
|
+
this.batchDescribeSObjects = this.batchDescribe;
|
|
191
|
+
|
|
192
|
+
cacheOptions = { key: 'describeGlobal' };
|
|
193
|
+
this.describeGlobal$ = this.cache.makeCacheable(this.describeGlobal, this, cacheOptions);
|
|
194
|
+
this.describeGlobal = this.cache.makeResponseCacheable(this.describeGlobal, this, cacheOptions);
|
|
195
|
+
|
|
196
|
+
this.initialize(options);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
inherits(Connection, events.EventEmitter);
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Initialize connection.
|
|
203
|
+
*
|
|
204
|
+
* @protected
|
|
205
|
+
* @param {Object} options - Initialization options
|
|
206
|
+
* @param {String} [options.instanceUrl] - Salesforce Instance URL (e.g. https://na1.salesforce.com/)
|
|
207
|
+
* @param {String} [options.serverUrl] - Salesforce SOAP service endpoint URL (e.g. https://na1.salesforce.com/services/Soap/u/28.0)
|
|
208
|
+
* @param {String} [options.accessToken] - Salesforce OAuth2 access token
|
|
209
|
+
* @param {String} [options.sessionId] - Salesforce session ID
|
|
210
|
+
* @param {String} [options.refreshToken] - Salesforce OAuth2 refresh token
|
|
211
|
+
* @param {String|Object} [options.signedRequest] - Salesforce Canvas signed request (Raw Base64 string, JSON string, or deserialized JSON)
|
|
212
|
+
* @param {UserInfo} [options.userInfo] - Logged in user information
|
|
213
|
+
*/
|
|
214
|
+
Connection.prototype.initialize = function(options) {
|
|
215
|
+
if (!options.instanceUrl && options.serverUrl) {
|
|
216
|
+
options.instanceUrl = options.serverUrl.split('/').slice(0, 3).join('/');
|
|
217
|
+
}
|
|
218
|
+
this.instanceUrl = options.instanceUrl || options.serverUrl || this.instanceUrl || defaults.instanceUrl;
|
|
219
|
+
|
|
220
|
+
this.accessToken = options.sessionId || options.accessToken || this.accessToken;
|
|
221
|
+
this.refreshToken = options.refreshToken || this.refreshToken;
|
|
222
|
+
if (this.refreshToken && !this._refreshDelegate) {
|
|
223
|
+
throw new Error("Refresh token is specified without oauth2 client information or refresh function");
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
this.signedRequest = options.signedRequest && parseSignedRequest(options.signedRequest);
|
|
227
|
+
if (this.signedRequest) {
|
|
228
|
+
this.accessToken = this.signedRequest.client.oauthToken;
|
|
229
|
+
if (Transport.CanvasTransport.supported) {
|
|
230
|
+
this._transport = new Transport.CanvasTransport(this.signedRequest);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (options.userInfo) {
|
|
235
|
+
this.userInfo = options.userInfo;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
this.limitInfo = {};
|
|
239
|
+
|
|
240
|
+
this.sobjects = {};
|
|
241
|
+
this.cache.clear();
|
|
242
|
+
this.cache.get('describeGlobal').removeAllListeners('value');
|
|
243
|
+
this.cache.get('describeGlobal').on('value', _.bind(function(res) {
|
|
244
|
+
if (res.result) {
|
|
245
|
+
var types = _.map(res.result.sobjects, function(so) { return so.name; });
|
|
246
|
+
types.forEach(this.sobject, this);
|
|
247
|
+
}
|
|
248
|
+
}, this));
|
|
249
|
+
|
|
250
|
+
if (this.tooling) {
|
|
251
|
+
this.tooling.initialize();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
this._sessionType = options.sessionId ? "soap" : "oauth2";
|
|
255
|
+
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
/** @private **/
|
|
259
|
+
function oauthRefreshFn(conn, callback) {
|
|
260
|
+
conn.oauth2.refreshToken(conn.refreshToken, function(err, res) {
|
|
261
|
+
if (err) { return callback(err); }
|
|
262
|
+
var userInfo = parseIdUrl(res.id);
|
|
263
|
+
conn.initialize({
|
|
264
|
+
instanceUrl : res.instance_url,
|
|
265
|
+
accessToken : res.access_token,
|
|
266
|
+
userInfo : userInfo
|
|
267
|
+
});
|
|
268
|
+
callback(null, res.access_token, res);
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** @private **/
|
|
273
|
+
function parseSignedRequest(sr) {
|
|
274
|
+
if (_.isString(sr)) {
|
|
275
|
+
if (sr[0] === '{') { // might be JSON
|
|
276
|
+
return JSON.parse(sr);
|
|
277
|
+
} else { // might be original base64-encoded signed request
|
|
278
|
+
var msg = sr.split('.').pop(); // retrieve latter part
|
|
279
|
+
var json = Buffer.from(msg, 'base64').toString('utf-8');
|
|
280
|
+
return JSON.parse(json);
|
|
281
|
+
}
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
return sr;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
/** @private **/
|
|
289
|
+
Connection.prototype._baseUrl = function() {
|
|
290
|
+
return [ this.instanceUrl, "services/data", "v" + this.version ].join('/');
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Convert path to absolute url
|
|
295
|
+
* @private
|
|
296
|
+
*/
|
|
297
|
+
Connection.prototype._normalizeUrl = function(url) {
|
|
298
|
+
if (url[0] === '/') {
|
|
299
|
+
if (url.indexOf('/services/') === 0) {
|
|
300
|
+
return this.instanceUrl + url;
|
|
301
|
+
} else {
|
|
302
|
+
return this._baseUrl() + url;
|
|
303
|
+
}
|
|
304
|
+
} else {
|
|
305
|
+
return url;
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Send REST API request with given HTTP request info, with connected session information.
|
|
311
|
+
*
|
|
312
|
+
* Endpoint URL can be absolute URL ('https://na1.salesforce.com/services/data/v32.0/sobjects/Account/describe')
|
|
313
|
+
* , relative path from root ('/services/data/v32.0/sobjects/Account/describe')
|
|
314
|
+
* , or relative path from version root ('/sobjects/Account/describe').
|
|
315
|
+
*
|
|
316
|
+
* @param {String|Object} request - HTTP request object or URL to GET request
|
|
317
|
+
* @param {String} request.method - HTTP method URL to send HTTP request
|
|
318
|
+
* @param {String} request.url - URL to send HTTP request
|
|
319
|
+
* @param {Object} [request.headers] - HTTP request headers in hash object (key-value)
|
|
320
|
+
* @param {Object} [options] - HTTP API request options
|
|
321
|
+
* @param {Callback.<Object>} [callback] - Callback function
|
|
322
|
+
* @returns {Promise.<Object>}
|
|
323
|
+
*/
|
|
324
|
+
Connection.prototype.request = function(request, options, callback) {
|
|
325
|
+
if (typeof options === 'function') {
|
|
326
|
+
callback = options;
|
|
327
|
+
options = null;
|
|
328
|
+
}
|
|
329
|
+
options = options || {};
|
|
330
|
+
var self = this;
|
|
331
|
+
|
|
332
|
+
// if request is simple string, regard it as url in GET method
|
|
333
|
+
if (_.isString(request)) {
|
|
334
|
+
request = { method: 'GET', url: request };
|
|
335
|
+
}
|
|
336
|
+
// if url is given in relative path, prepend base url or instance url before.
|
|
337
|
+
request.url = this._normalizeUrl(request.url);
|
|
338
|
+
|
|
339
|
+
var httpApi = new HttpApi(this, options);
|
|
340
|
+
|
|
341
|
+
// log api usage and its quota
|
|
342
|
+
httpApi.on('response', function(response) {
|
|
343
|
+
if (response.headers && response.headers["sforce-limit-info"]) {
|
|
344
|
+
var apiUsage = response.headers["sforce-limit-info"].match(/api\-usage=(\d+)\/(\d+)/);
|
|
345
|
+
if (apiUsage) {
|
|
346
|
+
self.limitInfo = {
|
|
347
|
+
apiUsage: {
|
|
348
|
+
used: parseInt(apiUsage[1], 10),
|
|
349
|
+
limit: parseInt(apiUsage[2], 10)
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
return httpApi.request(request).thenCall(callback);
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Send HTTP GET request
|
|
360
|
+
*
|
|
361
|
+
* Endpoint URL can be absolute URL ('https://na1.salesforce.com/services/data/v32.0/sobjects/Account/describe')
|
|
362
|
+
* , relative path from root ('/services/data/v32.0/sobjects/Account/describe')
|
|
363
|
+
* , or relative path from version root ('/sobjects/Account/describe').
|
|
364
|
+
*
|
|
365
|
+
* @param {String} url - Endpoint URL to request HTTP GET
|
|
366
|
+
* @param {Object} [options] - HTTP API request options
|
|
367
|
+
* @param {Callback.<Object>} [callback] - Callback function
|
|
368
|
+
* @returns {Promise.<Object>}
|
|
369
|
+
*/
|
|
370
|
+
Connection.prototype.requestGet = function(url, options, callback) {
|
|
371
|
+
var request = {
|
|
372
|
+
method: "GET",
|
|
373
|
+
url: url
|
|
374
|
+
};
|
|
375
|
+
return this.request(request, options, callback);
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Send HTTP POST request with JSON body, with connected session information
|
|
381
|
+
*
|
|
382
|
+
* Endpoint URL can be absolute URL ('https://na1.salesforce.com/services/data/v32.0/sobjects/Account/describe')
|
|
383
|
+
* , relative path from root ('/services/data/v32.0/sobjects/Account/describe')
|
|
384
|
+
* , or relative path from version root ('/sobjects/Account/describe').
|
|
385
|
+
*
|
|
386
|
+
* @param {String} url - Endpoint URL to request HTTP POST
|
|
387
|
+
* @param {Object} body - Any JS object which can be serialized to JSON
|
|
388
|
+
* @param {Object} [options] - HTTP API request options
|
|
389
|
+
* @param {Callback.<Object>} [callback] - Callback function
|
|
390
|
+
* @returns {Promise.<Object>}
|
|
391
|
+
*/
|
|
392
|
+
Connection.prototype.requestPost = function(url, body, options, callback) {
|
|
393
|
+
var request = {
|
|
394
|
+
method: "POST",
|
|
395
|
+
url: url,
|
|
396
|
+
body: JSON.stringify(body),
|
|
397
|
+
headers: { "content-type": "application/json" }
|
|
398
|
+
};
|
|
399
|
+
return this.request(request, options, callback);
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Send HTTP PUT request with JSON body, with connected session information
|
|
404
|
+
*
|
|
405
|
+
* Endpoint URL can be absolute URL ('https://na1.salesforce.com/services/data/v32.0/sobjects/Account/describe')
|
|
406
|
+
* , relative path from root ('/services/data/v32.0/sobjects/Account/describe')
|
|
407
|
+
* , or relative path from version root ('/sobjects/Account/describe').
|
|
408
|
+
*
|
|
409
|
+
* @param {String} url - Endpoint URL to request HTTP PUT
|
|
410
|
+
* @param {Object} body - Any JS object which can be serialized to JSON
|
|
411
|
+
* @param {Object} [options] - HTTP API request options
|
|
412
|
+
* @param {Callback.<Object>} [callback] - Callback function
|
|
413
|
+
* @returns {Promise.<Object>}
|
|
414
|
+
*/
|
|
415
|
+
Connection.prototype.requestPut = function(url, body, options, callback) {
|
|
416
|
+
var request = {
|
|
417
|
+
method: "PUT",
|
|
418
|
+
url: url,
|
|
419
|
+
body: JSON.stringify(body),
|
|
420
|
+
headers: { "content-type": "application/json" }
|
|
421
|
+
};
|
|
422
|
+
return this.request(request, options, callback);
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Send HTTP PATCH request with JSON body
|
|
427
|
+
*
|
|
428
|
+
* Endpoint URL can be absolute URL ('https://na1.salesforce.com/services/data/v32.0/sobjects/Account/describe')
|
|
429
|
+
* , relative path from root ('/services/data/v32.0/sobjects/Account/describe')
|
|
430
|
+
* , or relative path from version root ('/sobjects/Account/describe').
|
|
431
|
+
*
|
|
432
|
+
* @param {String} url - Endpoint URL to request HTTP PATCH
|
|
433
|
+
* @param {Object} body - Any JS object which can be serialized to JSON
|
|
434
|
+
* @param {Object} [options] - HTTP API request options
|
|
435
|
+
* @param {Callback.<Object>} [callback] - Callback function
|
|
436
|
+
* @returns {Promise.<Object>}
|
|
437
|
+
*/
|
|
438
|
+
Connection.prototype.requestPatch = function(url, body, options, callback) {
|
|
439
|
+
var request = {
|
|
440
|
+
method: "PATCH",
|
|
441
|
+
url: url,
|
|
442
|
+
body: JSON.stringify(body),
|
|
443
|
+
headers: { "content-type": "application/json" }
|
|
444
|
+
};
|
|
445
|
+
return this.request(request, options, callback);
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Send HTTP DELETE request
|
|
450
|
+
*
|
|
451
|
+
* Endpoint URL can be absolute URL ('https://na1.salesforce.com/services/data/v32.0/sobjects/Account/describe')
|
|
452
|
+
* , relative path from root ('/services/data/v32.0/sobjects/Account/describe')
|
|
453
|
+
* , or relative path from version root ('/sobjects/Account/describe').
|
|
454
|
+
*
|
|
455
|
+
* @param {String} url - Endpoint URL to request HTTP DELETE
|
|
456
|
+
* @param {Object} [options] - HTTP API request options
|
|
457
|
+
* @param {Callback.<Object>} [callback] - Callback function
|
|
458
|
+
* @returns {Promise.<Object>}
|
|
459
|
+
*/
|
|
460
|
+
Connection.prototype.requestDelete = function(url, options, callback) {
|
|
461
|
+
var request = {
|
|
462
|
+
method: "DELETE",
|
|
463
|
+
url: url
|
|
464
|
+
};
|
|
465
|
+
return this.request(request, options, callback);
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
/** @private */
|
|
470
|
+
function formatDate(date) {
|
|
471
|
+
function pad(number) {
|
|
472
|
+
if (number < 10) {
|
|
473
|
+
return '0' + number;
|
|
474
|
+
}
|
|
475
|
+
return number;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return date.getUTCFullYear() +
|
|
479
|
+
'-' + pad(date.getUTCMonth() + 1) +
|
|
480
|
+
'-' + pad(date.getUTCDate()) +
|
|
481
|
+
'T' + pad(date.getUTCHours()) +
|
|
482
|
+
':' + pad(date.getUTCMinutes()) +
|
|
483
|
+
':' + pad(date.getUTCSeconds()) +
|
|
484
|
+
'+00:00';
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
/** @private **/
|
|
489
|
+
function parseIdUrl(idUrl) {
|
|
490
|
+
var idUrls = idUrl.split("/");
|
|
491
|
+
var userId = idUrls.pop(), orgId = idUrls.pop();
|
|
492
|
+
return {
|
|
493
|
+
id: userId,
|
|
494
|
+
organizationId: orgId,
|
|
495
|
+
url: idUrl
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* @callback Callback
|
|
501
|
+
* @type {Function}
|
|
502
|
+
* @param {Error} err - Callback error
|
|
503
|
+
* @param {T} response - Callback response
|
|
504
|
+
* @template T
|
|
505
|
+
*/
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* @typedef {Object} QueryResult
|
|
509
|
+
* @prop {Boolean} done - Flag if the query is fetched all records or not
|
|
510
|
+
* @prop {String} [nextRecordsUrl] - URL locator for next record set, (available when done = false)
|
|
511
|
+
* @prop {Number} totalSize - Total size for query
|
|
512
|
+
* @prop {Array.<Record>} [records] - Array of records fetched
|
|
513
|
+
*/
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Execute query by using SOQL
|
|
517
|
+
*
|
|
518
|
+
* @param {String} soql - SOQL string
|
|
519
|
+
* @param {Object} [options] - Query options
|
|
520
|
+
* @param {Object} [options.headers] - Additional HTTP request headers sent in query request
|
|
521
|
+
* @param {Callback.<QueryResult>} [callback] - Callback function
|
|
522
|
+
* @returns {Query.<QueryResult>}
|
|
523
|
+
*/
|
|
524
|
+
Connection.prototype.query = function(soql, options, callback) {
|
|
525
|
+
if (typeof options === 'function') {
|
|
526
|
+
callback = options;
|
|
527
|
+
options = undefined;
|
|
528
|
+
}
|
|
529
|
+
var query = new Query(this, soql, options);
|
|
530
|
+
if (callback) {
|
|
531
|
+
query.run(callback);
|
|
532
|
+
}
|
|
533
|
+
return query;
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Execute query by using SOQL, including deleted records
|
|
538
|
+
*
|
|
539
|
+
* @param {String} soql - SOQL string
|
|
540
|
+
* @param {Object} [options] - Query options
|
|
541
|
+
* @param {Object} [options.headers] - Additional HTTP request headers sent in query request
|
|
542
|
+
* @param {Callback.<QueryResult>} [callback] - Callback function
|
|
543
|
+
* @returns {Query.<QueryResult>}
|
|
544
|
+
*/
|
|
545
|
+
Connection.prototype.queryAll = function(soql, options, callback) {
|
|
546
|
+
if (typeof options === 'function') {
|
|
547
|
+
callback = options;
|
|
548
|
+
options = undefined;
|
|
549
|
+
}
|
|
550
|
+
var query = new Query(this, soql, options);
|
|
551
|
+
query.scanAll(true);
|
|
552
|
+
if (callback) {
|
|
553
|
+
query.run(callback);
|
|
554
|
+
}
|
|
555
|
+
return query;
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Query next record set by using query locator
|
|
560
|
+
*
|
|
561
|
+
* @param {String} locator - Next record set locator
|
|
562
|
+
* @param {Object} [options] - Query options
|
|
563
|
+
* @param {Object} [options.headers] - Additional HTTP request headers sent in query request
|
|
564
|
+
* @param {Callback.<QueryResult>} [callback] - Callback function
|
|
565
|
+
* @returns {Query.<QueryResult>}
|
|
566
|
+
*/
|
|
567
|
+
Connection.prototype.queryMore = function(locator, options, callback) {
|
|
568
|
+
if (typeof options === 'function') {
|
|
569
|
+
callback = options;
|
|
570
|
+
options = undefined;
|
|
571
|
+
}
|
|
572
|
+
var query = new Query(this, { locator: locator }, options);
|
|
573
|
+
if (callback) {
|
|
574
|
+
query.run(callback);
|
|
575
|
+
}
|
|
576
|
+
return query;
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
/** @private */
|
|
580
|
+
Connection.prototype._ensureVersion = function(majorVersion) {
|
|
581
|
+
var versions = this.version.split('.');
|
|
582
|
+
return parseInt(versions[0], 10) >= majorVersion;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/** @private */
|
|
586
|
+
Connection.prototype._supports = function(feature) {
|
|
587
|
+
switch (feature) {
|
|
588
|
+
case 'sobject-collection':
|
|
589
|
+
return this._ensureVersion(42);
|
|
590
|
+
default:
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Retrieve specified records
|
|
597
|
+
*
|
|
598
|
+
* @param {String} type - SObject Type
|
|
599
|
+
* @param {String|Array.<String>} ids - A record ID or array of record IDs
|
|
600
|
+
* @param {Object} [options] - Options for rest api.
|
|
601
|
+
* @param {Array.<String>} [options.fields] - Fetching field names in retrieving record
|
|
602
|
+
* @param {Object} [options.headers] - Additional HTTP request headers sent in retrieve request
|
|
603
|
+
* @param {Callback.<Record|Array.<Record>>} [callback] - Callback function
|
|
604
|
+
* @returns {Promise.<Record|Array.<Record>>}
|
|
605
|
+
*/
|
|
606
|
+
Connection.prototype.retrieve = function(type, ids, options, callback) {
|
|
607
|
+
if (typeof options === 'function') {
|
|
608
|
+
callback = options;
|
|
609
|
+
options = {};
|
|
610
|
+
}
|
|
611
|
+
options = options || {};
|
|
612
|
+
return (
|
|
613
|
+
_.isArray(ids) ?
|
|
614
|
+
(this._supports('sobject-collection') ? // check whether SObject collection API is supported
|
|
615
|
+
this._retrieveMany(type, ids, options) :
|
|
616
|
+
this._retrieveParallel(type, ids, options)) :
|
|
617
|
+
this._retrieveSingle(type, ids, options)
|
|
618
|
+
).thenCall(callback);
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
/** @private */
|
|
622
|
+
Connection.prototype._retrieveSingle = function(type, id, options) {
|
|
623
|
+
if (!id) {
|
|
624
|
+
return Promise.reject(new Error('Invalid record ID. Specify valid record ID value'));
|
|
625
|
+
}
|
|
626
|
+
var url = [ this._baseUrl(), "sobjects", type, id ].join('/');
|
|
627
|
+
if (options.fields) {
|
|
628
|
+
url += '?fields=' + options.fields.join(',');
|
|
629
|
+
}
|
|
630
|
+
return this.request({
|
|
631
|
+
method: 'GET',
|
|
632
|
+
url: url,
|
|
633
|
+
headers: options.headers,
|
|
634
|
+
});
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
/** @private */
|
|
638
|
+
Connection.prototype._retrieveParallel = function(type, ids, options) {
|
|
639
|
+
if (ids.length > this.maxRequest) {
|
|
640
|
+
return Promise.reject(new Error("Exceeded max limit of concurrent call"));
|
|
641
|
+
}
|
|
642
|
+
var self = this;
|
|
643
|
+
return Promise.all(
|
|
644
|
+
ids.map(function(id) {
|
|
645
|
+
return self._retrieveSingle(type, id, options).catch(function(err) {
|
|
646
|
+
if (options.allOrNone || err.errorCode !== 'NOT_FOUND') {
|
|
647
|
+
throw err;
|
|
648
|
+
}
|
|
649
|
+
return null;
|
|
650
|
+
});
|
|
651
|
+
})
|
|
652
|
+
);
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
/** @private */
|
|
656
|
+
Connection.prototype._retrieveMany = function(type, ids, options) {
|
|
657
|
+
if (ids.length === 0) {
|
|
658
|
+
return Promise.resolve([]);
|
|
659
|
+
}
|
|
660
|
+
var url = [ this._baseUrl(), "composite", "sobjects", type ].join('/');
|
|
661
|
+
var self = this;
|
|
662
|
+
return (
|
|
663
|
+
options.fields ?
|
|
664
|
+
Promise.resolve(options.fields) :
|
|
665
|
+
new Promise(function(resolve, reject) {
|
|
666
|
+
self.describe$(type, function(err, so) {
|
|
667
|
+
if (err) {
|
|
668
|
+
reject(err);
|
|
669
|
+
} else {
|
|
670
|
+
var fields = so.fields.map(function(field) {
|
|
671
|
+
return field.name;
|
|
672
|
+
});
|
|
673
|
+
resolve(fields);
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
})
|
|
677
|
+
).then(function(fields) {
|
|
678
|
+
return self.request({
|
|
679
|
+
method : 'POST',
|
|
680
|
+
url : url,
|
|
681
|
+
body : JSON.stringify({
|
|
682
|
+
ids : ids,
|
|
683
|
+
fields : fields
|
|
684
|
+
}),
|
|
685
|
+
headers : _.defaults(options.headers || {}, {
|
|
686
|
+
"Content-Type" : "application/json"
|
|
687
|
+
})
|
|
688
|
+
});
|
|
689
|
+
});
|
|
690
|
+
};
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* @typedef RecordResult
|
|
695
|
+
* @prop {Boolean} success - The result is succeessful or not
|
|
696
|
+
* @prop {String} [id] - Record ID
|
|
697
|
+
* @prop {Array.<Object>} [errors] - Errors (available when success = false)
|
|
698
|
+
*/
|
|
699
|
+
|
|
700
|
+
/** @private */
|
|
701
|
+
Connection.prototype._toRecordResult = function(id, err) {
|
|
702
|
+
var error = {
|
|
703
|
+
statusCode: err.errorCode,
|
|
704
|
+
message: err.message
|
|
705
|
+
};
|
|
706
|
+
if (err.content) { error.content = err.content; } // preserve External id duplication message
|
|
707
|
+
if (err.fields) { error.fields = err.fields; } // preserve DML exception occurred fields
|
|
708
|
+
var result = {
|
|
709
|
+
success: false,
|
|
710
|
+
errors: [error]
|
|
711
|
+
};
|
|
712
|
+
if (id) { result.id = id; }
|
|
713
|
+
return result;
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* Synonym of Connection#create()
|
|
718
|
+
*
|
|
719
|
+
* @method Connection#insert
|
|
720
|
+
* @param {String} type - SObject Type
|
|
721
|
+
* @param {Object|Array.<Object>} records - A record or array of records to create
|
|
722
|
+
* @param {Object} [options] - Options for rest api.
|
|
723
|
+
* @param {Boolean} [options.allOrNone] - If true, any failed records in a call cause all changes for the call to be rolled back
|
|
724
|
+
* @param {Boolean} [options.allowRecursive] - If true, when records goes over the max num of collection API (=200), records are divided into several chunks and requested recursively.
|
|
725
|
+
* @param {Object} [options.headers] - Additional HTTP request headers sent in retrieve request
|
|
726
|
+
* @param {Callback.<RecordResult|Array.<RecordResult>>} [callback] - Callback function
|
|
727
|
+
* @returns {Promise.<RecordResult|Array.<RecordResult>>}
|
|
728
|
+
*/
|
|
729
|
+
/**
|
|
730
|
+
* Create records
|
|
731
|
+
*
|
|
732
|
+
* @method Connection#create
|
|
733
|
+
* @param {String} type - SObject Type
|
|
734
|
+
* @param {Record|Array.<Record>} records - A record or array of records to create
|
|
735
|
+
* @param {Object} [options] - Options for rest api.
|
|
736
|
+
* @param {Boolean} [options.allOrNone] - If true, any failed records in a call cause all changes for the call to be rolled back
|
|
737
|
+
* @param {Boolean} [options.allowRecursive] - If true, when records goes over the max num of collection API (=200), records are divided into several chunks and requested recursively.
|
|
738
|
+
* @param {Object} [options.headers] - Additional HTTP request headers sent in retrieve request
|
|
739
|
+
* @param {Callback.<RecordResult|Array.<RecordResult>>} [callback] - Callback function
|
|
740
|
+
* @returns {Promise.<RecordResult|Array.<RecordResult>>}
|
|
741
|
+
*/
|
|
742
|
+
Connection.prototype.insert =
|
|
743
|
+
Connection.prototype.create = function(type, records, options, callback) {
|
|
744
|
+
if (!_.isString(type)) {
|
|
745
|
+
// reverse order
|
|
746
|
+
callback = options;
|
|
747
|
+
options = records;
|
|
748
|
+
records = type;
|
|
749
|
+
type = null;
|
|
750
|
+
}
|
|
751
|
+
if (typeof options === 'function') {
|
|
752
|
+
callback = options;
|
|
753
|
+
options = {};
|
|
754
|
+
}
|
|
755
|
+
options = options || {};
|
|
756
|
+
return (
|
|
757
|
+
_.isArray(records) ?
|
|
758
|
+
(this._supports('sobject-collection') ? // check whether SObject collection API is supported
|
|
759
|
+
this._createMany(type, records, options) :
|
|
760
|
+
this._createParallel(type, records, options)) :
|
|
761
|
+
this._createSingle(type, records, options)
|
|
762
|
+
).thenCall(callback);
|
|
763
|
+
};
|
|
764
|
+
|
|
765
|
+
/** @private */
|
|
766
|
+
Connection.prototype._createSingle = function(type, record, options) {
|
|
767
|
+
var sobjectType = type || (record.attributes && record.attributes.type) || record.type;
|
|
768
|
+
if (!sobjectType) {
|
|
769
|
+
return Promise.reject(new Error('No SObject Type defined in record'));
|
|
770
|
+
}
|
|
771
|
+
record = _.clone(record);
|
|
772
|
+
delete record.Id;
|
|
773
|
+
delete record.type;
|
|
774
|
+
delete record.attributes;
|
|
775
|
+
var url = [ this._baseUrl(), "sobjects", sobjectType ].join('/');
|
|
776
|
+
return this.request({
|
|
777
|
+
method : 'POST',
|
|
778
|
+
url : url,
|
|
779
|
+
body : JSON.stringify(record),
|
|
780
|
+
headers : _.defaults(options.headers || {}, {
|
|
781
|
+
"Content-Type" : "application/json"
|
|
782
|
+
})
|
|
783
|
+
});
|
|
784
|
+
};
|
|
785
|
+
|
|
786
|
+
/** @private */
|
|
787
|
+
Connection.prototype._createParallel = function(type, records, options) {
|
|
788
|
+
if (records.length > this.maxRequest) {
|
|
789
|
+
return Promise.reject(new Error("Exceeded max limit of concurrent call"));
|
|
790
|
+
}
|
|
791
|
+
var self = this;
|
|
792
|
+
return Promise.all(
|
|
793
|
+
records.map(function(record) {
|
|
794
|
+
return self._createSingle(type, record, options).catch(function(err) {
|
|
795
|
+
// be aware that allOrNone in parallel mode will not revert the other successful requests
|
|
796
|
+
// it only raises error when met at least one failed request.
|
|
797
|
+
if (options.allOrNone || !err.errorCode) {
|
|
798
|
+
throw err;
|
|
799
|
+
}
|
|
800
|
+
return this._toRecordResult(null, err);
|
|
801
|
+
});
|
|
802
|
+
})
|
|
803
|
+
);
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
/** @private */
|
|
807
|
+
Connection.prototype._createMany = function(type, records, options) {
|
|
808
|
+
if (records.length === 0) {
|
|
809
|
+
return Promise.resolve([]);
|
|
810
|
+
}
|
|
811
|
+
if (records.length > MAX_DML_COUNT && options.allowRecursive) {
|
|
812
|
+
var self = this;
|
|
813
|
+
return self._createMany(type, records.slice(0, MAX_DML_COUNT), options).then(function(rets1) {
|
|
814
|
+
return self._createMany(type, records.slice(MAX_DML_COUNT), options).then(function(rets2) {
|
|
815
|
+
return rets1.concat(rets2);
|
|
816
|
+
});
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
records = _.map(records, function(record) {
|
|
820
|
+
var sobjectType = type || (record.attributes && record.attributes.type) || record.type;
|
|
821
|
+
if (!sobjectType) {
|
|
822
|
+
return Promise.reject(new Error('No SObject Type defined in record'));
|
|
823
|
+
}
|
|
824
|
+
record = _.clone(record);
|
|
825
|
+
delete record.Id;
|
|
826
|
+
delete record.type;
|
|
827
|
+
record.attributes = { type : sobjectType };
|
|
828
|
+
return record;
|
|
829
|
+
});
|
|
830
|
+
var url = [ this._baseUrl(), "composite", "sobjects" ].join('/');
|
|
831
|
+
return this.request({
|
|
832
|
+
method : 'POST',
|
|
833
|
+
url : url,
|
|
834
|
+
body : JSON.stringify({
|
|
835
|
+
allOrNone : options.allOrNone || false,
|
|
836
|
+
records : records
|
|
837
|
+
}),
|
|
838
|
+
headers : _.defaults(options.headers || {}, {
|
|
839
|
+
"Content-Type" : "application/json"
|
|
840
|
+
})
|
|
841
|
+
});
|
|
842
|
+
};
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* Update records
|
|
846
|
+
*
|
|
847
|
+
* @param {String} type - SObject Type
|
|
848
|
+
* @param {Record|Array.<Record>} records - A record or array of records to update
|
|
849
|
+
* @param {Object} [options] - Options for rest api.
|
|
850
|
+
* @param {Boolean} [options.allOrNone] - If true, any failed records in a call cause all changes for the call to be rolled back
|
|
851
|
+
* @param {Boolean} [options.allowRecursive] - If true, when records goes over the max num of collection API (=200), records are divided into several chunks and requested recursively.
|
|
852
|
+
* @param {Object} [options.headers] - Additional HTTP request headers sent in retrieve request
|
|
853
|
+
* @param {Callback.<RecordResult|Array.<RecordResult>>} [callback] - Callback function
|
|
854
|
+
* @returns {Promise.<RecordResult|Array.<RecordResult>>}
|
|
855
|
+
*/
|
|
856
|
+
Connection.prototype.update = function(type, records, options, callback) {
|
|
857
|
+
if (!_.isString(type)) {
|
|
858
|
+
// reverse order
|
|
859
|
+
callback = options;
|
|
860
|
+
options = records;
|
|
861
|
+
records = type;
|
|
862
|
+
type = null;
|
|
863
|
+
}
|
|
864
|
+
if (typeof options === 'function') {
|
|
865
|
+
callback = options;
|
|
866
|
+
options = {};
|
|
867
|
+
}
|
|
868
|
+
options = options || {};
|
|
869
|
+
return (
|
|
870
|
+
_.isArray(records) ?
|
|
871
|
+
(this._supports('sobject-collection') ? // check whether SObject collection API is supported
|
|
872
|
+
this._updateMany(type, records, options) :
|
|
873
|
+
this._updateParallel(type, records, options)) :
|
|
874
|
+
this._updateSingle(type, records, options)
|
|
875
|
+
).thenCall(callback);
|
|
876
|
+
};
|
|
877
|
+
|
|
878
|
+
/** @private */
|
|
879
|
+
Connection.prototype._updateSingle = function(type, record, options) {
|
|
880
|
+
var id = record.Id;
|
|
881
|
+
if (!id) {
|
|
882
|
+
return Promise.reject(new Error('Record id is not found in record.'));
|
|
883
|
+
}
|
|
884
|
+
var sobjectType = type || (record.attributes && record.attributes.type) || record.type;
|
|
885
|
+
if (!sobjectType) {
|
|
886
|
+
return Promise.reject(new Error('No SObject Type defined in record'));
|
|
887
|
+
}
|
|
888
|
+
record = _.clone(record);
|
|
889
|
+
delete record.Id;
|
|
890
|
+
delete record.type;
|
|
891
|
+
delete record.attributes;
|
|
892
|
+
var url = [ this._baseUrl(), "sobjects", sobjectType, id ].join('/');
|
|
893
|
+
return this.request({
|
|
894
|
+
method : 'PATCH',
|
|
895
|
+
url : url,
|
|
896
|
+
body : JSON.stringify(record),
|
|
897
|
+
headers : _.defaults(options.headers || {}, {
|
|
898
|
+
"Content-Type" : "application/json"
|
|
899
|
+
})
|
|
900
|
+
}, {
|
|
901
|
+
noContentResponse: { id : id, success : true, errors : [] }
|
|
902
|
+
});
|
|
903
|
+
};
|
|
904
|
+
|
|
905
|
+
/** @private */
|
|
906
|
+
Connection.prototype._updateParallel = function(type, records, options) {
|
|
907
|
+
if (records.length > this.maxRequest) {
|
|
908
|
+
return Promise.reject(new Error("Exceeded max limit of concurrent call"));
|
|
909
|
+
}
|
|
910
|
+
var self = this;
|
|
911
|
+
return Promise.all(
|
|
912
|
+
records.map(function(record) {
|
|
913
|
+
return self._updateSingle(type, record, options).catch(function(err) {
|
|
914
|
+
// be aware that allOrNone in parallel mode will not revert the other successful requests
|
|
915
|
+
// it only raises error when met at least one failed request.
|
|
916
|
+
if (options.allOrNone || !err.errorCode) {
|
|
917
|
+
throw err;
|
|
918
|
+
}
|
|
919
|
+
return this._toRecordResult(record.Id, err);
|
|
920
|
+
});
|
|
921
|
+
})
|
|
922
|
+
);
|
|
923
|
+
};
|
|
924
|
+
|
|
925
|
+
/** @private */
|
|
926
|
+
Connection.prototype._updateMany = function(type, records, options) {
|
|
927
|
+
if (records.length === 0) {
|
|
928
|
+
return Promise.resolve([]);
|
|
929
|
+
}
|
|
930
|
+
if (records.length > MAX_DML_COUNT && options.allowRecursive) {
|
|
931
|
+
var self = this;
|
|
932
|
+
return self._updateMany(type, records.slice(0, MAX_DML_COUNT), options).then(function(rets1) {
|
|
933
|
+
return self._updateMany(type, records.slice(MAX_DML_COUNT), options).then(function(rets2) {
|
|
934
|
+
return rets1.concat(rets2);
|
|
935
|
+
});
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
records = _.map(records, function(record) {
|
|
939
|
+
var id = record.Id;
|
|
940
|
+
if (!id) {
|
|
941
|
+
throw new Error('Record id is not found in record.');
|
|
942
|
+
}
|
|
943
|
+
var sobjectType = type || (record.attributes && record.attributes.type) || record.type;
|
|
944
|
+
if (!sobjectType) {
|
|
945
|
+
throw new Error('No SObject Type defined in record');
|
|
946
|
+
}
|
|
947
|
+
record = _.clone(record);
|
|
948
|
+
delete record.Id;
|
|
949
|
+
record.id = id;
|
|
950
|
+
delete record.type;
|
|
951
|
+
record.attributes = { type : sobjectType };
|
|
952
|
+
return record;
|
|
953
|
+
});
|
|
954
|
+
var url = [ this._baseUrl(), "composite", "sobjects" ].join('/');
|
|
955
|
+
return this.request({
|
|
956
|
+
method : 'PATCH',
|
|
957
|
+
url : url,
|
|
958
|
+
body : JSON.stringify({
|
|
959
|
+
allOrNone : options.allOrNone || false,
|
|
960
|
+
records : records
|
|
961
|
+
}),
|
|
962
|
+
headers : _.defaults(options.headers || {}, {
|
|
963
|
+
"Content-Type" : "application/json"
|
|
964
|
+
})
|
|
965
|
+
});
|
|
966
|
+
};
|
|
967
|
+
|
|
968
|
+
/**
|
|
969
|
+
* Upsert records
|
|
970
|
+
*
|
|
971
|
+
* @param {String} type - SObject Type
|
|
972
|
+
* @param {Record|Array.<Record>} records - Record or array of records to upsert
|
|
973
|
+
* @param {String} extIdField - External ID field name
|
|
974
|
+
* @param {Object} [options] - Options for rest api.
|
|
975
|
+
* @param {Boolean} [options.allOrNone] - If true, any failed records in a call cause all changes for the call to be rolled back
|
|
976
|
+
* @param {Object} [options.headers] - Additional HTTP request headers sent in retrieve request
|
|
977
|
+
* @param {Callback.<RecordResult|Array.<RecordResult>>} [callback] - Callback
|
|
978
|
+
* @returns {Promise.<RecordResult|Array.<RecordResult>>}
|
|
979
|
+
*/
|
|
980
|
+
Connection.prototype.upsert = function(type, records, extIdField, options, callback) {
|
|
981
|
+
// You can omit "type" argument, when the record includes type information.
|
|
982
|
+
if (!_.isString(type)) {
|
|
983
|
+
// reverse order
|
|
984
|
+
callback = options;
|
|
985
|
+
options = extIdField;
|
|
986
|
+
extIdField = records;
|
|
987
|
+
records = type;
|
|
988
|
+
type = null;
|
|
989
|
+
}
|
|
990
|
+
if (typeof options === 'function') {
|
|
991
|
+
callback = options;
|
|
992
|
+
options = {};
|
|
993
|
+
}
|
|
994
|
+
options = options || {};
|
|
995
|
+
var self = this;
|
|
996
|
+
var isArray = _.isArray(records);
|
|
997
|
+
records = isArray ? records : [ records ];
|
|
998
|
+
if (records.length > this.maxRequest) {
|
|
999
|
+
return Promise.reject(new Error("Exceeded max limit of concurrent call")).thenCall(callback);
|
|
1000
|
+
}
|
|
1001
|
+
return Promise.all(
|
|
1002
|
+
_.map(records, function(record) {
|
|
1003
|
+
var sobjectType = type || (record.attributes && record.attributes.type) || record.type;
|
|
1004
|
+
var extId = record[extIdField];
|
|
1005
|
+
record = _.clone(record);
|
|
1006
|
+
delete record[extIdField];
|
|
1007
|
+
delete record.type;
|
|
1008
|
+
delete record.attributes;
|
|
1009
|
+
|
|
1010
|
+
var url = [ self._baseUrl(), "sobjects", sobjectType, extIdField, extId ].join('/');
|
|
1011
|
+
return self.request({
|
|
1012
|
+
method : 'PATCH',
|
|
1013
|
+
url : url,
|
|
1014
|
+
body : JSON.stringify(record),
|
|
1015
|
+
headers : _.defaults(options.headers || {}, {
|
|
1016
|
+
"Content-Type" : "application/json"
|
|
1017
|
+
})
|
|
1018
|
+
}, {
|
|
1019
|
+
noContentResponse: { success : true, errors : [] }
|
|
1020
|
+
})
|
|
1021
|
+
.catch(function(err) {
|
|
1022
|
+
// be aware that `allOrNone` option in upsert method will not revert the other successful requests
|
|
1023
|
+
// it only raises error when met at least one failed request.
|
|
1024
|
+
if (!isArray || options.allOrNone || !err.errorCode) { throw err; }
|
|
1025
|
+
return self._toRecordResult(null, err);
|
|
1026
|
+
})
|
|
1027
|
+
})
|
|
1028
|
+
).then(function(results) {
|
|
1029
|
+
return !isArray && _.isArray(results) ? results[0] : results;
|
|
1030
|
+
}).thenCall(callback);
|
|
1031
|
+
};
|
|
1032
|
+
|
|
1033
|
+
/**
|
|
1034
|
+
* Synonym of Connection#destroy()
|
|
1035
|
+
*
|
|
1036
|
+
* @method Connection#delete
|
|
1037
|
+
* @param {String} type - SObject Type
|
|
1038
|
+
* @param {String|Array.<String>} ids - A ID or array of IDs to delete
|
|
1039
|
+
* @param {Object} [options] - Options for rest api.
|
|
1040
|
+
* @param {Boolean} [options.allOrNone] - If true, any failed records in a call cause all changes for the call to be rolled back
|
|
1041
|
+
* @param {Boolean} [options.allowRecursive] - If true, when ids goes over the max num of collection API (=200), ids are divided into several chunks and requested recursively.
|
|
1042
|
+
* @param {Object} [options.headers] - Additional HTTP request headers sent in retrieve request
|
|
1043
|
+
* @param {Callback.<RecordResult|Array.<RecordResult>>} [callback] - Callback
|
|
1044
|
+
* @returns {Promise.<RecordResult|Array.<RecordResult>>}
|
|
1045
|
+
*/
|
|
1046
|
+
/**
|
|
1047
|
+
* Synonym of Connection#destroy()
|
|
1048
|
+
*
|
|
1049
|
+
* @method Connection#del
|
|
1050
|
+
* @param {String} type - SObject Type
|
|
1051
|
+
* @param {String|Array.<String>} ids - A ID or array of IDs to delete
|
|
1052
|
+
* @param {Object} [options] - Options for rest api.
|
|
1053
|
+
* @param {Boolean} [options.allOrNone] - If true, any failed records in a call cause all changes for the call to be rolled back
|
|
1054
|
+
* @param {Boolean} [options.allowRecursive] - If true, when ids goes over the max num of collection API (=200), ids are divided into several chunks and requested recursively.
|
|
1055
|
+
* @param {Object} [options.headers] - Additional HTTP request headers sent in retrieve request
|
|
1056
|
+
* @param {Callback.<RecordResult|Array.<RecordResult>>} [callback] - Callback
|
|
1057
|
+
* @returns {Promise.<RecordResult|Array.<RecordResult>>}
|
|
1058
|
+
*/
|
|
1059
|
+
/**
|
|
1060
|
+
* Delete records
|
|
1061
|
+
*
|
|
1062
|
+
* @method Connection#destroy
|
|
1063
|
+
* @param {String} type - SObject Type
|
|
1064
|
+
* @param {String|Array.<String>} ids - A ID or array of IDs to delete
|
|
1065
|
+
* @param {Object} [options] - Options for rest api.
|
|
1066
|
+
* @param {Boolean} [options.allOrNone] - If true, any failed records in a call cause all changes for the call to be rolled back
|
|
1067
|
+
* @param {Boolean} [options.allowRecursive] - If true, when ids goes over the max num of collection API (=200), ids are divided into several chunks and requested recursively.
|
|
1068
|
+
* @param {Object} [options.headers] - Additional HTTP request headers sent in retrieve request
|
|
1069
|
+
* @param {Callback.<RecordResult|Array.<RecordResult>>} [callback] - Callback
|
|
1070
|
+
* @returns {Promise.<RecordResult|Array.<RecordResult>>}
|
|
1071
|
+
*/
|
|
1072
|
+
Connection.prototype["delete"] =
|
|
1073
|
+
Connection.prototype.del =
|
|
1074
|
+
Connection.prototype.destroy = function(type, ids, options, callback) {
|
|
1075
|
+
if (typeof options === 'function') {
|
|
1076
|
+
callback = options;
|
|
1077
|
+
options = {};
|
|
1078
|
+
}
|
|
1079
|
+
options = options || {};
|
|
1080
|
+
return (
|
|
1081
|
+
_.isArray(ids) ?
|
|
1082
|
+
(this._supports('sobject-collection') ? // check whether SObject collection API is supported
|
|
1083
|
+
this._destroyMany(type, ids, options) :
|
|
1084
|
+
this._destroyParallel(type, ids, options)) :
|
|
1085
|
+
this._destroySingle(type, ids, options)
|
|
1086
|
+
).thenCall(callback);
|
|
1087
|
+
};
|
|
1088
|
+
|
|
1089
|
+
/** @private */
|
|
1090
|
+
Connection.prototype._destroySingle = function(type, id, options) {
|
|
1091
|
+
var url = [ this._baseUrl(), "sobjects", type, id ].join('/');
|
|
1092
|
+
return this.request({
|
|
1093
|
+
method : 'DELETE',
|
|
1094
|
+
url : url,
|
|
1095
|
+
headers: options.headers || null
|
|
1096
|
+
}, {
|
|
1097
|
+
noContentResponse: { id : id, success : true, errors : [] }
|
|
1098
|
+
});
|
|
1099
|
+
};
|
|
1100
|
+
|
|
1101
|
+
/** @private */
|
|
1102
|
+
Connection.prototype._destroyParallel = function(type, ids, options) {
|
|
1103
|
+
if (ids.length > this.maxRequest) {
|
|
1104
|
+
return Promise.reject(new Error("Exceeded max limit of concurrent call"));
|
|
1105
|
+
}
|
|
1106
|
+
var self = this;
|
|
1107
|
+
return Promise.all(
|
|
1108
|
+
ids.map(function(id) {
|
|
1109
|
+
return self._destroySingle(type, id, options).catch(function(err) {
|
|
1110
|
+
// be aware that `allOrNone` option in parallel mode will not revert the other successful requests
|
|
1111
|
+
// it only raises error when met at least one failed request.
|
|
1112
|
+
if (options.allOrNone || !err.errorCode) {
|
|
1113
|
+
throw err;
|
|
1114
|
+
}
|
|
1115
|
+
return this._toRecordResult(id, err);
|
|
1116
|
+
});
|
|
1117
|
+
})
|
|
1118
|
+
);
|
|
1119
|
+
};
|
|
1120
|
+
|
|
1121
|
+
|
|
1122
|
+
/** @private */
|
|
1123
|
+
Connection.prototype._destroyMany = function(type, ids, options) {
|
|
1124
|
+
if (ids.length === 0) {
|
|
1125
|
+
return Promise.resolve([]);
|
|
1126
|
+
}
|
|
1127
|
+
if (ids.length > MAX_DML_COUNT && options.allowRecursive) {
|
|
1128
|
+
var self = this;
|
|
1129
|
+
return self._destroyMany(type, ids.slice(0, MAX_DML_COUNT), options).then(function(rets1) {
|
|
1130
|
+
return self._destroyMany(type, ids.slice(MAX_DML_COUNT), options).then(function(rets2) {
|
|
1131
|
+
return rets1.concat(rets2);
|
|
1132
|
+
});
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
var url = [ this._baseUrl(), "composite", "sobjects?ids=" ].join('/') + ids.join(',');
|
|
1136
|
+
if (options.allOrNone) {
|
|
1137
|
+
url += '&allOrNone=true';
|
|
1138
|
+
}
|
|
1139
|
+
return this.request({
|
|
1140
|
+
method : 'DELETE',
|
|
1141
|
+
url : url,
|
|
1142
|
+
headers: options.headers || null
|
|
1143
|
+
});
|
|
1144
|
+
};
|
|
1145
|
+
|
|
1146
|
+
/**
|
|
1147
|
+
* Execute search by SOSL
|
|
1148
|
+
*
|
|
1149
|
+
* @param {String} sosl - SOSL string
|
|
1150
|
+
* @param {Callback.<Array.<RecordResult>>} [callback] - Callback function
|
|
1151
|
+
* @returns {Promise.<Array.<RecordResult>>}
|
|
1152
|
+
*/
|
|
1153
|
+
Connection.prototype.search = function(sosl, callback) {
|
|
1154
|
+
var url = this._baseUrl() + "/search?q=" + encodeURIComponent(sosl);
|
|
1155
|
+
return this.request(url).thenCall(callback);
|
|
1156
|
+
};
|
|
1157
|
+
|
|
1158
|
+
/**
|
|
1159
|
+
* Result returned by describeSObject call
|
|
1160
|
+
*
|
|
1161
|
+
* @typedef {Object} DescribeSObjectResult
|
|
1162
|
+
*/
|
|
1163
|
+
/**
|
|
1164
|
+
* Parameter for describeSObject call
|
|
1165
|
+
*
|
|
1166
|
+
* @typedef {Object} DescribeSObjectOptions
|
|
1167
|
+
*/
|
|
1168
|
+
/**
|
|
1169
|
+
* Synonym of Connection#describe()
|
|
1170
|
+
*
|
|
1171
|
+
* @method Connection#describeSObject
|
|
1172
|
+
* @param {String|DescribeSObjectOptions} type - SObject Type or options object
|
|
1173
|
+
* @param {String} type.type - The name of the SObject
|
|
1174
|
+
* @param {String} type.ifModifiedSince - Date value for If-Modified-Since header; undefined resolved if not modified after this date
|
|
1175
|
+
* @param {Callback.<DescribeSObjectResult>} [callback] - Callback function
|
|
1176
|
+
* @returns {Promise.<DescribeSObjectResult>}
|
|
1177
|
+
*/
|
|
1178
|
+
/**
|
|
1179
|
+
* Describe SObject metadata
|
|
1180
|
+
*
|
|
1181
|
+
* @method Connection#describe
|
|
1182
|
+
* @param {String|DescribeSObjectOptions} type - SObject Type or options object
|
|
1183
|
+
* @param {String} type.type - The name of the SObject
|
|
1184
|
+
* @param {String} type.ifModifiedSince - Date value for If-Modified-Since header; undefined resolved if not modified after this date
|
|
1185
|
+
* @param {Callback.<DescribeSObjectResult>} [callback] - Callback function
|
|
1186
|
+
* @returns {Promise.<DescribeSObjectResult>}
|
|
1187
|
+
*/
|
|
1188
|
+
Connection.prototype.describe =
|
|
1189
|
+
Connection.prototype.describeSObject = function(type, callback) {
|
|
1190
|
+
var name = type.type ? type.type : type;
|
|
1191
|
+
var url = [ this._baseUrl(), "sobjects", name, "describe" ].join('/');
|
|
1192
|
+
var headers = type.ifModifiedSince
|
|
1193
|
+
? { 'If-Modified-Since': type.ifModifiedSince }
|
|
1194
|
+
: {};
|
|
1195
|
+
return this.request({
|
|
1196
|
+
method: 'GET',
|
|
1197
|
+
url: url,
|
|
1198
|
+
headers: headers
|
|
1199
|
+
}).then(function (resp) {
|
|
1200
|
+
if (resp === '') {
|
|
1201
|
+
return Promise.resolve(undefined);
|
|
1202
|
+
} else {
|
|
1203
|
+
return Promise.resolve(resp);
|
|
1204
|
+
}
|
|
1205
|
+
}).thenCall(callback);
|
|
1206
|
+
};
|
|
1207
|
+
|
|
1208
|
+
/**
|
|
1209
|
+
* Result returned by batchDescribeSObjects call
|
|
1210
|
+
*
|
|
1211
|
+
* @typedef {Object[]} DescribeSObjectResult
|
|
1212
|
+
*/
|
|
1213
|
+
/**
|
|
1214
|
+
* Parameter for describeSObject call
|
|
1215
|
+
*
|
|
1216
|
+
* @typedef {Object} BatchDescribeSObjectOptions
|
|
1217
|
+
*/
|
|
1218
|
+
/**
|
|
1219
|
+
* Synonym of Connection#batchDescribe()
|
|
1220
|
+
*
|
|
1221
|
+
* @method Connection#batchDescribeSObjects
|
|
1222
|
+
* @param {BatchDescribeSObjectOptions} options - options for function
|
|
1223
|
+
* @param {String[]} options.types - names of objects to fetch
|
|
1224
|
+
* @param {Boolean} options.autofetch - whether to automatically fetch metadata for large numbers of
|
|
1225
|
+
* types (one batch request returns a maximum of 25 results); when true, will make
|
|
1226
|
+
* subsequent requests until all object metadata is fetched; when false (default),
|
|
1227
|
+
* will make one batch request for maximum of 25 results
|
|
1228
|
+
* @param {number} options.maxConcurrentRequests - maximum number of concurrent requests sent to the org;
|
|
1229
|
+
* default and maximum is 15
|
|
1230
|
+
* @param {Callback.<DescribeSObjectResult[]>} [callback] - Callback function
|
|
1231
|
+
* @returns {Promise.<DescribeSObjectResult[]>}
|
|
1232
|
+
*/
|
|
1233
|
+
/**
|
|
1234
|
+
* Batch describe SObject metadata
|
|
1235
|
+
*
|
|
1236
|
+
* @method Connection#batchDescribe
|
|
1237
|
+
* @param {BatchDescribeSObjectOptions} options - options for function
|
|
1238
|
+
* @param {String[]} options.types - names of objects to fetch
|
|
1239
|
+
* @param {Boolean} options.autofetch - whether to automatically fetch metadata for large numbers of
|
|
1240
|
+
* types (one batch request returns a maximum of 25 results); when true, will make
|
|
1241
|
+
* subsequent requests until all object metadata is fetched; when false (default),
|
|
1242
|
+
* will make one batch request for maximum of 25 results
|
|
1243
|
+
* @param {number} options.maxConcurrentRequests - maximum number of concurrent requests sent to the org;
|
|
1244
|
+
* default and maximum is 15
|
|
1245
|
+
* @param {Callback.<DescribeSObjectResult[]>} [callback] - Callback function
|
|
1246
|
+
* @returns {Promise.<DescribeSObjectResult[]>}
|
|
1247
|
+
*/
|
|
1248
|
+
Connection.prototype.batchDescribe = Connection.prototype.batchDescribeSObjects = function (
|
|
1249
|
+
options,
|
|
1250
|
+
callback
|
|
1251
|
+
) {
|
|
1252
|
+
var self = this;
|
|
1253
|
+
var types = options.types;
|
|
1254
|
+
var autofetch = options.autofetch || false;
|
|
1255
|
+
var maxConcurrentRequests = Math.min((options.maxConcurrentRequests || 15), 15);
|
|
1256
|
+
var batches = [];
|
|
1257
|
+
do {
|
|
1258
|
+
var batch = types.length > MAX_BATCH_REQUESTS ? types.slice(0, MAX_BATCH_REQUESTS) : types;
|
|
1259
|
+
batches.push(batch);
|
|
1260
|
+
types = types.length > MAX_BATCH_REQUESTS ? types.slice(MAX_BATCH_REQUESTS) : [];
|
|
1261
|
+
} while (types.length > 0 && autofetch);
|
|
1262
|
+
var requestBatches = [];
|
|
1263
|
+
do {
|
|
1264
|
+
var requestBatch = batches.length > maxConcurrentRequests ? batches.slice(0, maxConcurrentRequests) : batches;
|
|
1265
|
+
requestBatches.push(requestBatch);
|
|
1266
|
+
batches = batches.length > maxConcurrentRequests ? batches.slice(maxConcurrentRequests) : [];
|
|
1267
|
+
} while (batches.length > 0);
|
|
1268
|
+
return self.doBatchDescribeRequestBatches(requestBatches)
|
|
1269
|
+
.thenCall(callback);
|
|
1270
|
+
};
|
|
1271
|
+
|
|
1272
|
+
Connection.prototype.doBatchDescribeRequestBatches = function(requestBatches) {
|
|
1273
|
+
// make each batch of requests sequentially to avoid org limits of max concurrent requests
|
|
1274
|
+
var self = this;
|
|
1275
|
+
var sobjects = [];
|
|
1276
|
+
var firstBatch = requestBatches.shift();
|
|
1277
|
+
return self.doBatchOfBatchDescribeRequests(firstBatch).then(
|
|
1278
|
+
function (sobjectArray) {
|
|
1279
|
+
sobjectArray.forEach(function (sobject) { sobjects.push(sobject); });
|
|
1280
|
+
if (requestBatches.length > 0) {
|
|
1281
|
+
return self.doBatchDescribeRequestBatches(requestBatches).then(
|
|
1282
|
+
function (results) {
|
|
1283
|
+
results.forEach(function (result) { sobjects.push(result); });
|
|
1284
|
+
return Promise.resolve(sobjects);
|
|
1285
|
+
}
|
|
1286
|
+
)
|
|
1287
|
+
} else {
|
|
1288
|
+
return Promise.resolve(sobjects);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
)
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
/** private */
|
|
1295
|
+
Connection.prototype.doBatchOfBatchDescribeRequests = function(requestBatch) {
|
|
1296
|
+
// make up to maxConcurrentRequest requests in parallel
|
|
1297
|
+
var self = this;
|
|
1298
|
+
return Promise.all(
|
|
1299
|
+
requestBatch.map(function (batch) { return self.doBatchDescribeRequest(batch); } )
|
|
1300
|
+
).then(function (results) {
|
|
1301
|
+
var sobjects = [];
|
|
1302
|
+
results.forEach(function (sobjectArray) {
|
|
1303
|
+
sobjectArray.forEach(function (sobject) { sobjects.push(sobject); })
|
|
1304
|
+
});
|
|
1305
|
+
return Promise.resolve(sobjects);
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
/** private */
|
|
1310
|
+
Connection.prototype.doBatchDescribeRequest = function(types) {
|
|
1311
|
+
var self = this;
|
|
1312
|
+
var sobjects = [];
|
|
1313
|
+
var url = [self._baseUrl(), "composite/batch"].join("/");
|
|
1314
|
+
var version = "v" + self.version;
|
|
1315
|
+
var batchRequests = [];
|
|
1316
|
+
types.forEach(function (type) {
|
|
1317
|
+
batchRequests.push({
|
|
1318
|
+
method: "GET",
|
|
1319
|
+
url: [version, "sobjects", type, "describe"].join("/")
|
|
1320
|
+
});
|
|
1321
|
+
});
|
|
1322
|
+
return this.request({
|
|
1323
|
+
method: "POST",
|
|
1324
|
+
url: url,
|
|
1325
|
+
body: JSON.stringify({ batchRequests: batchRequests }),
|
|
1326
|
+
headers: {
|
|
1327
|
+
"Content-Type": "application/json"
|
|
1328
|
+
}
|
|
1329
|
+
}).then(function (response) {
|
|
1330
|
+
if (response.results) {
|
|
1331
|
+
var i = 0;
|
|
1332
|
+
for (var i = 0; i < response.results.length; i++) {
|
|
1333
|
+
var subResp = response.results[i];
|
|
1334
|
+
if (Array.isArray(subResp.result)) {
|
|
1335
|
+
if (subResp.result[0].errorCode && subResp.result[0].message) {
|
|
1336
|
+
this._logger.error(
|
|
1337
|
+
'Error: ' + subResp.result[0].errorCode + ' ' +
|
|
1338
|
+
subResp.result[0].message + ' - ' + typesToFetch[i]
|
|
1339
|
+
);
|
|
1340
|
+
}
|
|
1341
|
+
} else {
|
|
1342
|
+
sobjects.push(subResp.result);
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
return Promise.resolve(sobjects);
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
/**
|
|
1351
|
+
* Result returned by describeGlobal call
|
|
1352
|
+
*
|
|
1353
|
+
* @typedef {Object} DescribeGlobalResult
|
|
1354
|
+
*/
|
|
1355
|
+
/**
|
|
1356
|
+
* Describe global SObjects
|
|
1357
|
+
*
|
|
1358
|
+
* @param {Callback.<DescribeGlobalResult>} [callback] - Callback function
|
|
1359
|
+
* @returns {Promise.<DescribeGlobalResult>}
|
|
1360
|
+
*/
|
|
1361
|
+
Connection.prototype.describeGlobal = function(callback) {
|
|
1362
|
+
var url = this._baseUrl() + "/sobjects";
|
|
1363
|
+
return this.request(url).thenCall(callback);
|
|
1364
|
+
};
|
|
1365
|
+
|
|
1366
|
+
|
|
1367
|
+
/**
|
|
1368
|
+
* Get SObject instance
|
|
1369
|
+
*
|
|
1370
|
+
* @param {String} type - SObject Type
|
|
1371
|
+
* @returns {SObject}
|
|
1372
|
+
*/
|
|
1373
|
+
Connection.prototype.sobject = function(type) {
|
|
1374
|
+
this.sobjects = this.sobjects || {};
|
|
1375
|
+
var sobject = this.sobjects[type] =
|
|
1376
|
+
this.sobjects[type] || new SObject(this, type);
|
|
1377
|
+
return sobject;
|
|
1378
|
+
};
|
|
1379
|
+
|
|
1380
|
+
/**
|
|
1381
|
+
* Get identity information of current user
|
|
1382
|
+
*
|
|
1383
|
+
* @param {Object} [options] - Identity call options
|
|
1384
|
+
* @param {Object} [options.headers] - Additional HTTP request headers sent in identity request
|
|
1385
|
+
* @param {Callback.<IdentityInfo>} [callback] - Callback function
|
|
1386
|
+
* @returns {Promise.<IdentityInfo>}
|
|
1387
|
+
*/
|
|
1388
|
+
Connection.prototype.identity = function(options, callback) {
|
|
1389
|
+
if (typeof options === 'function') {
|
|
1390
|
+
callback = options;
|
|
1391
|
+
options = {};
|
|
1392
|
+
}
|
|
1393
|
+
options = options || {};
|
|
1394
|
+
var self = this;
|
|
1395
|
+
var idUrl = this.userInfo && this.userInfo.url;
|
|
1396
|
+
return Promise.resolve(
|
|
1397
|
+
idUrl ?
|
|
1398
|
+
{ identity: idUrl } :
|
|
1399
|
+
this.request({ method: 'GET', url: this._baseUrl(), headers: options.headers })
|
|
1400
|
+
).then(function(res) {
|
|
1401
|
+
var url = res.identity;
|
|
1402
|
+
return self.request({ method: 'GET', url: url });
|
|
1403
|
+
}).then(function(res) {
|
|
1404
|
+
self.userInfo = {
|
|
1405
|
+
id: res.user_id,
|
|
1406
|
+
organizationId: res.organization_id,
|
|
1407
|
+
url: res.id
|
|
1408
|
+
};
|
|
1409
|
+
return res;
|
|
1410
|
+
}).thenCall(callback);
|
|
1411
|
+
};
|
|
1412
|
+
|
|
1413
|
+
/**
|
|
1414
|
+
* @typedef UserInfo
|
|
1415
|
+
* @prop {String} id - User ID
|
|
1416
|
+
* @prop {String} organizationId - Organization ID
|
|
1417
|
+
* @prop {String} url - Identity URL of the user
|
|
1418
|
+
*/
|
|
1419
|
+
|
|
1420
|
+
/**
|
|
1421
|
+
* Authorize (using oauth2 web server flow)
|
|
1422
|
+
*
|
|
1423
|
+
* @param {String} code - Authorization code
|
|
1424
|
+
* @param {Object} [params] - Optional parameters to send in token retrieval
|
|
1425
|
+
* @param {String} [params.code_verifier] - Code verifier value (RFC 7636 - Proof Key of Code Exchange)
|
|
1426
|
+
* @param {Callback.<UserInfo>} [callback] - Callback function
|
|
1427
|
+
* @returns {Promise.<UserInfo>}
|
|
1428
|
+
*/
|
|
1429
|
+
Connection.prototype.authorize = function(code, params, callback) {
|
|
1430
|
+
if (typeof params === 'function') {
|
|
1431
|
+
callback = params;
|
|
1432
|
+
params = {};
|
|
1433
|
+
}
|
|
1434
|
+
var self = this;
|
|
1435
|
+
var logger = this._logger;
|
|
1436
|
+
|
|
1437
|
+
return this.oauth2.requestToken(code, params).then(function(res) {
|
|
1438
|
+
var userInfo = parseIdUrl(res.id);
|
|
1439
|
+
self.initialize({
|
|
1440
|
+
instanceUrl : res.instance_url,
|
|
1441
|
+
accessToken : res.access_token,
|
|
1442
|
+
refreshToken : res.refresh_token,
|
|
1443
|
+
userInfo: userInfo
|
|
1444
|
+
});
|
|
1445
|
+
logger.debug("<login> completed. user id = " + userInfo.id + ", org id = " + userInfo.organizationId);
|
|
1446
|
+
return userInfo;
|
|
1447
|
+
|
|
1448
|
+
}).thenCall(callback);
|
|
1449
|
+
|
|
1450
|
+
};
|
|
1451
|
+
|
|
1452
|
+
|
|
1453
|
+
/**
|
|
1454
|
+
* Login to Salesforce
|
|
1455
|
+
*
|
|
1456
|
+
* @param {String} username - Salesforce username
|
|
1457
|
+
* @param {String} password - Salesforce password (and security token, if required)
|
|
1458
|
+
* @param {Callback.<UserInfo>} [callback] - Callback function
|
|
1459
|
+
* @returns {Promise.<UserInfo>}
|
|
1460
|
+
*/
|
|
1461
|
+
Connection.prototype.login = function(username, password, callback) {
|
|
1462
|
+
// register refreshDelegate for session expiration
|
|
1463
|
+
this._refreshDelegate = new HttpApi.SessionRefreshDelegate(this, createUsernamePasswordRefreshFn(username, password));
|
|
1464
|
+
if (this.oauth2 && this.oauth2.clientId && this.oauth2.clientSecret) {
|
|
1465
|
+
return this.loginByOAuth2(username, password, callback);
|
|
1466
|
+
} else {
|
|
1467
|
+
return this.loginBySoap(username, password, callback);
|
|
1468
|
+
}
|
|
1469
|
+
};
|
|
1470
|
+
|
|
1471
|
+
/** @private **/
|
|
1472
|
+
function createUsernamePasswordRefreshFn(username, password) {
|
|
1473
|
+
return function(conn, callback) {
|
|
1474
|
+
conn.login(username, password, function(err) {
|
|
1475
|
+
if (err) { return callback(err); }
|
|
1476
|
+
callback(null, conn.accessToken);
|
|
1477
|
+
});
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
/**
|
|
1482
|
+
* Login by OAuth2 username & password flow
|
|
1483
|
+
*
|
|
1484
|
+
* @param {String} username - Salesforce username
|
|
1485
|
+
* @param {String} password - Salesforce password (and security token, if required)
|
|
1486
|
+
* @param {Callback.<UserInfo>} [callback] - Callback function
|
|
1487
|
+
* @returns {Promise.<UserInfo>}
|
|
1488
|
+
*/
|
|
1489
|
+
Connection.prototype.loginByOAuth2 = function(username, password, callback) {
|
|
1490
|
+
var self = this;
|
|
1491
|
+
var logger = this._logger;
|
|
1492
|
+
return this.oauth2.authenticate(username, password).then(function(res) {
|
|
1493
|
+
var userInfo = parseIdUrl(res.id);
|
|
1494
|
+
self.initialize({
|
|
1495
|
+
instanceUrl : res.instance_url,
|
|
1496
|
+
accessToken : res.access_token,
|
|
1497
|
+
userInfo: userInfo
|
|
1498
|
+
});
|
|
1499
|
+
logger.debug("<login> completed. user id = " + userInfo.id + ", org id = " + userInfo.organizationId);
|
|
1500
|
+
return userInfo;
|
|
1501
|
+
|
|
1502
|
+
}).thenCall(callback);
|
|
1503
|
+
|
|
1504
|
+
};
|
|
1505
|
+
|
|
1506
|
+
/**
|
|
1507
|
+
* @private
|
|
1508
|
+
*/
|
|
1509
|
+
function esc(str) {
|
|
1510
|
+
return str && String(str).replace(/&/g, '&').replace(/</g, '<')
|
|
1511
|
+
.replace(/>/g, '>').replace(/"/g, '"');
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
/**
|
|
1515
|
+
* Login by SOAP web service API
|
|
1516
|
+
*
|
|
1517
|
+
* @param {String} username - Salesforce username
|
|
1518
|
+
* @param {String} password - Salesforce password (and security token, if required)
|
|
1519
|
+
* @param {Callback.<UserInfo>} [callback] - Callback function
|
|
1520
|
+
* @returns {Promise.<UserInfo>}
|
|
1521
|
+
*/
|
|
1522
|
+
Connection.prototype.loginBySoap = function(username, password, callback) {
|
|
1523
|
+
var self = this;
|
|
1524
|
+
var logger = this._logger;
|
|
1525
|
+
var body = [
|
|
1526
|
+
'<se:Envelope xmlns:se="http://schemas.xmlsoap.org/soap/envelope/">',
|
|
1527
|
+
'<se:Header/>',
|
|
1528
|
+
'<se:Body>',
|
|
1529
|
+
'<login xmlns="urn:partner.soap.sforce.com">',
|
|
1530
|
+
'<username>' + esc(username) + '</username>',
|
|
1531
|
+
'<password>' + esc(password) + '</password>',
|
|
1532
|
+
'</login>',
|
|
1533
|
+
'</se:Body>',
|
|
1534
|
+
'</se:Envelope>'
|
|
1535
|
+
].join('');
|
|
1536
|
+
|
|
1537
|
+
var soapLoginEndpoint = [ this.loginUrl, "services/Soap/u", this.version ].join('/');
|
|
1538
|
+
|
|
1539
|
+
return this._transport.httpRequest({
|
|
1540
|
+
method : 'POST',
|
|
1541
|
+
url : soapLoginEndpoint,
|
|
1542
|
+
body : body,
|
|
1543
|
+
headers : {
|
|
1544
|
+
"Content-Type" : "text/xml",
|
|
1545
|
+
"SOAPAction" : '""'
|
|
1546
|
+
}
|
|
1547
|
+
}).then(function(response) {
|
|
1548
|
+
var m;
|
|
1549
|
+
if (response.statusCode >= 400) {
|
|
1550
|
+
m = response.body.match(/<faultstring>([^<]+)<\/faultstring>/);
|
|
1551
|
+
var faultstring = m && m[1];
|
|
1552
|
+
throw new Error(faultstring || response.body);
|
|
1553
|
+
}
|
|
1554
|
+
logger.debug("SOAP response = " + response.body);
|
|
1555
|
+
m = response.body.match(/<serverUrl>([^<]+)<\/serverUrl>/);
|
|
1556
|
+
var serverUrl = m && m[1];
|
|
1557
|
+
m = response.body.match(/<sessionId>([^<]+)<\/sessionId>/);
|
|
1558
|
+
var sessionId = m && m[1];
|
|
1559
|
+
m = response.body.match(/<userId>([^<]+)<\/userId>/);
|
|
1560
|
+
var userId = m && m[1];
|
|
1561
|
+
m = response.body.match(/<organizationId>([^<]+)<\/organizationId>/);
|
|
1562
|
+
var orgId = m && m[1];
|
|
1563
|
+
var idUrl = soapLoginEndpoint.split('/').slice(0, 3).join('/');
|
|
1564
|
+
idUrl += "/id/" + orgId + "/" + userId;
|
|
1565
|
+
var userInfo = {
|
|
1566
|
+
id: userId,
|
|
1567
|
+
organizationId: orgId,
|
|
1568
|
+
url: idUrl
|
|
1569
|
+
};
|
|
1570
|
+
self.initialize({
|
|
1571
|
+
serverUrl: serverUrl.split('/').slice(0, 3).join('/'),
|
|
1572
|
+
sessionId: sessionId,
|
|
1573
|
+
userInfo: userInfo
|
|
1574
|
+
});
|
|
1575
|
+
logger.debug("<login> completed. user id = " + userId + ", org id = " + orgId);
|
|
1576
|
+
return userInfo;
|
|
1577
|
+
|
|
1578
|
+
}).thenCall(callback);
|
|
1579
|
+
|
|
1580
|
+
};
|
|
1581
|
+
|
|
1582
|
+
/**
|
|
1583
|
+
* Logout the current session
|
|
1584
|
+
*
|
|
1585
|
+
* @param {Boolean} [revoke] - Revokes API Access if set to true
|
|
1586
|
+
* @param {Callback.<undefined>} [callback] - Callback function
|
|
1587
|
+
* @returns {Promise.<undefined>}
|
|
1588
|
+
*/
|
|
1589
|
+
Connection.prototype.logout = function(revoke, callback) {
|
|
1590
|
+
if (typeof revoke === 'function') {
|
|
1591
|
+
callback = revoke;
|
|
1592
|
+
revoke = false;
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
if (this._sessionType === "oauth2") {
|
|
1596
|
+
return this.logoutByOAuth2(revoke, callback);
|
|
1597
|
+
} else {
|
|
1598
|
+
return this.logoutBySoap(revoke, callback);
|
|
1599
|
+
}
|
|
1600
|
+
};
|
|
1601
|
+
|
|
1602
|
+
/**
|
|
1603
|
+
* Logout the current session by revoking access token via OAuth2 session revoke
|
|
1604
|
+
*
|
|
1605
|
+
* @param {Boolean} [revoke] - Revokes API Access if set to true
|
|
1606
|
+
* @param {Callback.<undefined>} [callback] - Callback function
|
|
1607
|
+
* @returns {Promise.<undefined>}
|
|
1608
|
+
*/
|
|
1609
|
+
Connection.prototype.logoutByOAuth2 = function(revoke, callback) {
|
|
1610
|
+
if (typeof revoke === 'function') {
|
|
1611
|
+
callback = revoke;
|
|
1612
|
+
revoke = false;
|
|
1613
|
+
}
|
|
1614
|
+
var self = this;
|
|
1615
|
+
var logger = this._logger;
|
|
1616
|
+
|
|
1617
|
+
return this.oauth2.revokeToken(revoke ? this.refreshToken : this.accessToken).then(function() {
|
|
1618
|
+
// Destroy the session bound to this connection
|
|
1619
|
+
self.accessToken = null;
|
|
1620
|
+
self.userInfo = null;
|
|
1621
|
+
self.refreshToken = null;
|
|
1622
|
+
self.instanceUrl = null;
|
|
1623
|
+
self.cache.clear();
|
|
1624
|
+
|
|
1625
|
+
// nothing useful returned by logout API, just return
|
|
1626
|
+
return undefined;
|
|
1627
|
+
}).thenCall(callback);
|
|
1628
|
+
};
|
|
1629
|
+
|
|
1630
|
+
|
|
1631
|
+
/**
|
|
1632
|
+
* Logout the session by using SOAP web service API
|
|
1633
|
+
*
|
|
1634
|
+
* @param {Boolean} [revoke] - Revokes API Access if set to true
|
|
1635
|
+
* @param {Callback.<undefined>} [callback] - Callback function
|
|
1636
|
+
* @returns {Promise.<undefined>}
|
|
1637
|
+
*/
|
|
1638
|
+
Connection.prototype.logoutBySoap = function(revoke, callback) {
|
|
1639
|
+
if (typeof revoke === 'function') {
|
|
1640
|
+
callback = revoke;
|
|
1641
|
+
revoke = false;
|
|
1642
|
+
}
|
|
1643
|
+
var self = this;
|
|
1644
|
+
var logger = this._logger;
|
|
1645
|
+
|
|
1646
|
+
var body = [
|
|
1647
|
+
'<se:Envelope xmlns:se="http://schemas.xmlsoap.org/soap/envelope/">',
|
|
1648
|
+
'<se:Header>',
|
|
1649
|
+
'<SessionHeader xmlns="urn:partner.soap.sforce.com">',
|
|
1650
|
+
'<sessionId>' + esc(revoke ? this.refreshToken : this.accessToken) + '</sessionId>',
|
|
1651
|
+
'</SessionHeader>',
|
|
1652
|
+
'</se:Header>',
|
|
1653
|
+
'<se:Body>',
|
|
1654
|
+
'<logout xmlns="urn:partner.soap.sforce.com"/>',
|
|
1655
|
+
'</se:Body>',
|
|
1656
|
+
'</se:Envelope>'
|
|
1657
|
+
].join('');
|
|
1658
|
+
|
|
1659
|
+
return this._transport.httpRequest({
|
|
1660
|
+
method : 'POST',
|
|
1661
|
+
url : [ this.instanceUrl, "services/Soap/u", this.version ].join('/'),
|
|
1662
|
+
body : body,
|
|
1663
|
+
headers : {
|
|
1664
|
+
"Content-Type" : "text/xml",
|
|
1665
|
+
"SOAPAction" : '""'
|
|
1666
|
+
}
|
|
1667
|
+
}).then(function(response) {
|
|
1668
|
+
logger.debug("SOAP statusCode = " + response.statusCode + ", response = " + response.body);
|
|
1669
|
+
if (response.statusCode >= 400) {
|
|
1670
|
+
var m = response.body.match(/<faultstring>([^<]+)<\/faultstring>/);
|
|
1671
|
+
var faultstring = m && m[1];
|
|
1672
|
+
throw new Error(faultstring || response.body);
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
// Destroy the session bound to this connection
|
|
1676
|
+
self.accessToken = null;
|
|
1677
|
+
self.userInfo = null;
|
|
1678
|
+
self.refreshToken = null;
|
|
1679
|
+
self.instanceUrl = null;
|
|
1680
|
+
self.cache.clear();
|
|
1681
|
+
|
|
1682
|
+
// nothing useful returned by logout API, just return
|
|
1683
|
+
return undefined;
|
|
1684
|
+
|
|
1685
|
+
}).thenCall(callback);
|
|
1686
|
+
};
|
|
1687
|
+
|
|
1688
|
+
/**
|
|
1689
|
+
* List recently viewed records
|
|
1690
|
+
*
|
|
1691
|
+
* @param {String} [type] - SObject type
|
|
1692
|
+
* @param {Number} [limit] - Limit num to fetch
|
|
1693
|
+
* @param {Callback.<Array.<RecordResult>>} [callback] - Callback function
|
|
1694
|
+
* @returns {Promise.<Array.<RecordResult>>}
|
|
1695
|
+
*/
|
|
1696
|
+
Connection.prototype.recent = function(type, limit, callback) {
|
|
1697
|
+
if (!_.isString(type)) {
|
|
1698
|
+
callback = limit;
|
|
1699
|
+
limit = type;
|
|
1700
|
+
type = undefined;
|
|
1701
|
+
}
|
|
1702
|
+
if (!_.isNumber(limit)) {
|
|
1703
|
+
callback = limit;
|
|
1704
|
+
limit = undefined;
|
|
1705
|
+
}
|
|
1706
|
+
var url;
|
|
1707
|
+
if (type) {
|
|
1708
|
+
url = [ this._baseUrl(), "sobjects", type ].join('/');
|
|
1709
|
+
return this.request(url).then(function(res) {
|
|
1710
|
+
return limit ? res.recentItems.slice(0, limit) : res.recentItems;
|
|
1711
|
+
}).thenCall(callback);
|
|
1712
|
+
} else {
|
|
1713
|
+
url = this._baseUrl() + "/recent";
|
|
1714
|
+
if (limit) {
|
|
1715
|
+
url += "?limit=" + limit;
|
|
1716
|
+
}
|
|
1717
|
+
return this.request(url).thenCall(callback);
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
};
|
|
1721
|
+
|
|
1722
|
+
/**
|
|
1723
|
+
* @typedef {Object} UpdatedRecordsInfo
|
|
1724
|
+
* @prop {String} latestDateCovered - The timestamp of the last date covered.
|
|
1725
|
+
* @prop {Array.<String>} ids - Updated record IDs.
|
|
1726
|
+
*/
|
|
1727
|
+
|
|
1728
|
+
/**
|
|
1729
|
+
* Retrieve updated records
|
|
1730
|
+
*
|
|
1731
|
+
* @param {String} type - SObject Type
|
|
1732
|
+
* @param {String|Date} start - start date or string representing the start of the interval
|
|
1733
|
+
* @param {String|Date} end - start date or string representing the end of the interval must be > start
|
|
1734
|
+
* @param {Callback.<UpdatedRecordsInfo>} [callback] - Callback function
|
|
1735
|
+
* @returns {Promise.<UpdatedRecordsInfo>}
|
|
1736
|
+
*/
|
|
1737
|
+
Connection.prototype.updated = function (type, start, end, callback) {
|
|
1738
|
+
var url = [ this._baseUrl(), "sobjects", type, "updated" ].join('/');
|
|
1739
|
+
|
|
1740
|
+
if (typeof start === 'string') {
|
|
1741
|
+
start = new Date(start);
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
if (start instanceof Date) {
|
|
1745
|
+
start = formatDate(start);
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
if (start) {
|
|
1749
|
+
url += "?start=" + encodeURIComponent(start);
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
if (typeof end === 'string') {
|
|
1753
|
+
end = new Date(end);
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
if (end instanceof Date) {
|
|
1757
|
+
end = formatDate(end);
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
if (end) {
|
|
1761
|
+
url += "&end=" + encodeURIComponent(end);
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
return this.request(url).thenCall(callback);
|
|
1765
|
+
};
|
|
1766
|
+
|
|
1767
|
+
/**
|
|
1768
|
+
* @typedef {Object} DeletedRecordsInfo
|
|
1769
|
+
* @prop {String} earliestDateAvailable - The timestamp of the earliest date available
|
|
1770
|
+
* @prop {String} latestDateCovered - The timestamp of the last date covered
|
|
1771
|
+
* @prop {Array.<Object>} deletedRecords - Updated records
|
|
1772
|
+
* @prop {String} deletedRecords.id - Record ID
|
|
1773
|
+
* @prop {String} deletedRecords.deletedDate - The timestamp when this record was deleted
|
|
1774
|
+
*/
|
|
1775
|
+
|
|
1776
|
+
/**
|
|
1777
|
+
* Retrieve deleted records
|
|
1778
|
+
*
|
|
1779
|
+
* @param {String} type - SObject Type
|
|
1780
|
+
* @param {String|Date} start - start date or string representing the start of the interval
|
|
1781
|
+
* @param {String|Date} end - start date or string representing the end of the interval
|
|
1782
|
+
* @param {Callback.<DeletedRecordsInfo>} [callback] - Callback function
|
|
1783
|
+
* @returns {Promise.<DeletedRecordsInfo>}
|
|
1784
|
+
*/
|
|
1785
|
+
Connection.prototype.deleted = function (type, start, end, callback) {
|
|
1786
|
+
var url = [ this._baseUrl(), "sobjects", type, "deleted" ].join('/');
|
|
1787
|
+
|
|
1788
|
+
if (typeof start === 'string') {
|
|
1789
|
+
start = new Date(start);
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
if (start instanceof Date) {
|
|
1793
|
+
start = formatDate(start);
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
if (start) {
|
|
1797
|
+
url += "?start=" + encodeURIComponent(start);
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
if (typeof end === 'string') {
|
|
1801
|
+
end = new Date(end);
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
if (end instanceof Date) {
|
|
1805
|
+
end = formatDate(end);
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
if (end) {
|
|
1809
|
+
url += "&end=" + encodeURIComponent(end);
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
return this.request(url).thenCall(callback);
|
|
1813
|
+
};
|
|
1814
|
+
|
|
1815
|
+
|
|
1816
|
+
/**
|
|
1817
|
+
* @typedef {Object} TabsInfo - See the API document for detail structure
|
|
1818
|
+
*/
|
|
1819
|
+
|
|
1820
|
+
/**
|
|
1821
|
+
* Returns a list of all tabs
|
|
1822
|
+
*
|
|
1823
|
+
* @param {Callback.<TabsInfo>} [callback] - Callback function
|
|
1824
|
+
* @returns {Promise.<TabsInfo>}
|
|
1825
|
+
*/
|
|
1826
|
+
Connection.prototype.tabs = function(callback) {
|
|
1827
|
+
var url = [ this._baseUrl(), "tabs" ].join('/');
|
|
1828
|
+
return this.request(url).thenCall(callback);
|
|
1829
|
+
};
|
|
1830
|
+
|
|
1831
|
+
|
|
1832
|
+
/**
|
|
1833
|
+
* @typedef {Object} LimitsInfo - See the API document for detail structure
|
|
1834
|
+
*/
|
|
1835
|
+
|
|
1836
|
+
/**
|
|
1837
|
+
* Returns curren system limit in the organization
|
|
1838
|
+
*
|
|
1839
|
+
* @param {Callback.<LimitsInfo>} [callback] - Callback function
|
|
1840
|
+
* @returns {Promise.<LimitsInfo>}
|
|
1841
|
+
*/
|
|
1842
|
+
Connection.prototype.limits = function(callback) {
|
|
1843
|
+
var url = [ this._baseUrl(), "limits" ].join('/');
|
|
1844
|
+
return this.request(url).thenCall(callback);
|
|
1845
|
+
};
|
|
1846
|
+
|
|
1847
|
+
|
|
1848
|
+
/**
|
|
1849
|
+
* @typedef {Object} ThemeInfo - See the API document for detail structure
|
|
1850
|
+
*/
|
|
1851
|
+
|
|
1852
|
+
/**
|
|
1853
|
+
* Returns a theme info
|
|
1854
|
+
*
|
|
1855
|
+
* @param {Callback.<ThemeInfo>} [callback] - Callback function
|
|
1856
|
+
* @returns {Promise.<ThemeInfo>}
|
|
1857
|
+
*/
|
|
1858
|
+
Connection.prototype.theme = function(callback) {
|
|
1859
|
+
var url = [ this._baseUrl(), "theme" ].join('/');
|
|
1860
|
+
return this.request(url).thenCall(callback);
|
|
1861
|
+
};
|
|
1862
|
+
|
|
1863
|
+
/**
|
|
1864
|
+
* Returns all registered global quick actions
|
|
1865
|
+
*
|
|
1866
|
+
* @param {Callback.<Array.<QuickAction~QuickActionInfo>>} [callback] - Callback function
|
|
1867
|
+
* @returns {Promise.<Array.<QuickAction~QuickActionInfo>>}
|
|
1868
|
+
*/
|
|
1869
|
+
Connection.prototype.quickActions = function(callback) {
|
|
1870
|
+
return this.request("/quickActions").thenCall(callback);
|
|
1871
|
+
};
|
|
1872
|
+
|
|
1873
|
+
/**
|
|
1874
|
+
* Get reference for specified global quick aciton
|
|
1875
|
+
*
|
|
1876
|
+
* @param {String} actionName - Name of the global quick action
|
|
1877
|
+
* @returns {QuickAction}
|
|
1878
|
+
*/
|
|
1879
|
+
Connection.prototype.quickAction = function(actionName) {
|
|
1880
|
+
return new QuickAction(this, "/quickActions/" + actionName);
|
|
1881
|
+
};
|