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.
Files changed (80) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +74 -0
  3. package/bin/jsforce +3 -0
  4. package/bower.json +30 -0
  5. package/build/jsforce-api-analytics.js +393 -0
  6. package/build/jsforce-api-analytics.min.js +2 -0
  7. package/build/jsforce-api-analytics.min.js.map +1 -0
  8. package/build/jsforce-api-apex.js +183 -0
  9. package/build/jsforce-api-apex.min.js +2 -0
  10. package/build/jsforce-api-apex.min.js.map +1 -0
  11. package/build/jsforce-api-bulk.js +1054 -0
  12. package/build/jsforce-api-bulk.min.js +2 -0
  13. package/build/jsforce-api-bulk.min.js.map +1 -0
  14. package/build/jsforce-api-chatter.js +320 -0
  15. package/build/jsforce-api-chatter.min.js +2 -0
  16. package/build/jsforce-api-chatter.min.js.map +1 -0
  17. package/build/jsforce-api-metadata.js +3020 -0
  18. package/build/jsforce-api-metadata.min.js +2 -0
  19. package/build/jsforce-api-metadata.min.js.map +1 -0
  20. package/build/jsforce-api-soap.js +403 -0
  21. package/build/jsforce-api-soap.min.js +2 -0
  22. package/build/jsforce-api-soap.min.js.map +1 -0
  23. package/build/jsforce-api-streaming.js +3479 -0
  24. package/build/jsforce-api-streaming.min.js +2 -0
  25. package/build/jsforce-api-streaming.min.js.map +1 -0
  26. package/build/jsforce-api-tooling.js +319 -0
  27. package/build/jsforce-api-tooling.min.js +2 -0
  28. package/build/jsforce-api-tooling.min.js.map +1 -0
  29. package/build/jsforce-core.js +25250 -0
  30. package/build/jsforce-core.min.js +2 -0
  31. package/build/jsforce-core.min.js.map +1 -0
  32. package/build/jsforce.js +31637 -0
  33. package/build/jsforce.min.js +2 -0
  34. package/build/jsforce.min.js.map +1 -0
  35. package/core.js +1 -0
  36. package/index.js +1 -0
  37. package/lib/VERSION.js +2 -0
  38. package/lib/_required.js +29 -0
  39. package/lib/api/analytics.js +387 -0
  40. package/lib/api/apex.js +177 -0
  41. package/lib/api/bulk.js +862 -0
  42. package/lib/api/chatter.js +314 -0
  43. package/lib/api/index.js +8 -0
  44. package/lib/api/metadata.js +848 -0
  45. package/lib/api/soap.js +397 -0
  46. package/lib/api/streaming-extension.js +136 -0
  47. package/lib/api/streaming.js +270 -0
  48. package/lib/api/tooling.js +313 -0
  49. package/lib/browser/canvas.js +90 -0
  50. package/lib/browser/client.js +241 -0
  51. package/lib/browser/core.js +5 -0
  52. package/lib/browser/jsforce.js +6 -0
  53. package/lib/browser/jsonp.js +52 -0
  54. package/lib/browser/request.js +70 -0
  55. package/lib/cache.js +252 -0
  56. package/lib/cli/cli.js +431 -0
  57. package/lib/cli/repl.js +337 -0
  58. package/lib/connection.js +1881 -0
  59. package/lib/core.js +16 -0
  60. package/lib/csv.js +50 -0
  61. package/lib/date.js +163 -0
  62. package/lib/http-api.js +300 -0
  63. package/lib/jsforce.js +10 -0
  64. package/lib/logger.js +52 -0
  65. package/lib/oauth2.js +206 -0
  66. package/lib/process.js +275 -0
  67. package/lib/promise.js +164 -0
  68. package/lib/query.js +881 -0
  69. package/lib/quick-action.js +90 -0
  70. package/lib/record-stream.js +305 -0
  71. package/lib/record.js +107 -0
  72. package/lib/registry/file-registry.js +48 -0
  73. package/lib/registry/index.js +3 -0
  74. package/lib/registry/registry.js +111 -0
  75. package/lib/require.js +14 -0
  76. package/lib/soap.js +207 -0
  77. package/lib/sobject.js +558 -0
  78. package/lib/soql-builder.js +236 -0
  79. package/lib/transport.js +233 -0
  80. 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, '&amp;').replace(/</g, '&lt;')
1511
+ .replace(/>/g, '&gt;').replace(/"/g, '&quot;');
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
+ };