@jsforce/jsforce-node 0.0.1 → 3.0.0-next.2
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 +54 -0
- package/index.d.ts +4 -0
- package/index.js +1 -0
- package/lib/VERSION.d.ts +2 -0
- package/lib/VERSION.js +3 -0
- package/lib/api/analytics/types.d.ts +509 -0
- package/lib/api/analytics/types.js +2 -0
- package/lib/api/analytics.d.ts +163 -0
- package/lib/api/analytics.js +342 -0
- package/lib/api/apex.d.ts +44 -0
- package/lib/api/apex.js +86 -0
- package/lib/api/bulk.d.ts +253 -0
- package/lib/api/bulk.js +678 -0
- package/lib/api/bulk2.d.ts +324 -0
- package/lib/api/bulk2.js +800 -0
- package/lib/api/chatter.d.ts +133 -0
- package/lib/api/chatter.js +248 -0
- package/lib/api/metadata/schema.d.ts +16117 -0
- package/lib/api/metadata/schema.js +9094 -0
- package/lib/api/metadata.d.ts +189 -0
- package/lib/api/metadata.js +406 -0
- package/lib/api/soap/schema.d.ts +3167 -0
- package/lib/api/soap/schema.js +1787 -0
- package/lib/api/soap.d.ts +76 -0
- package/lib/api/soap.js +155 -0
- package/lib/api/streaming/extension.d.ts +94 -0
- package/lib/api/streaming/extension.js +151 -0
- package/lib/api/streaming.d.ts +160 -0
- package/lib/api/streaming.js +252 -0
- package/lib/api/tooling.d.ts +284 -0
- package/lib/api/tooling.js +202 -0
- package/lib/api/wsdl/wsdl2schema.d.ts +1 -0
- package/lib/api/wsdl/wsdl2schema.js +354 -0
- package/lib/browser/canvas.d.ts +12 -0
- package/lib/browser/canvas.js +77 -0
- package/lib/browser/client.d.ts +82 -0
- package/lib/browser/client.js +244 -0
- package/lib/browser/jsonp.d.ts +12 -0
- package/lib/browser/jsonp.js +69 -0
- package/lib/browser/registry.d.ts +3 -0
- package/lib/browser/registry.js +5 -0
- package/lib/browser/request.d.ts +10 -0
- package/lib/browser/request.js +202 -0
- package/lib/cache.d.ts +74 -0
- package/lib/cache.js +159 -0
- package/lib/connection.d.ts +356 -0
- package/lib/connection.js +1153 -0
- package/lib/core.d.ts +17 -0
- package/lib/core.js +55 -0
- package/lib/csv.d.ts +23 -0
- package/lib/csv.js +35 -0
- package/lib/date.d.ts +82 -0
- package/lib/date.js +201 -0
- package/lib/http-api.d.ts +75 -0
- package/lib/http-api.js +295 -0
- package/lib/index.d.ts +13 -0
- package/lib/index.js +32 -0
- package/lib/jsforce.d.ts +26 -0
- package/lib/jsforce.js +67 -0
- package/lib/jwtOAuth2.d.ts +8 -0
- package/lib/jwtOAuth2.js +23 -0
- package/lib/oauth2.d.ts +92 -0
- package/lib/oauth2.js +245 -0
- package/lib/process.d.ts +157 -0
- package/lib/process.js +143 -0
- package/lib/query.d.ts +341 -0
- package/lib/query.js +817 -0
- package/lib/quick-action.d.ts +44 -0
- package/lib/quick-action.js +46 -0
- package/lib/record-reference.d.ts +46 -0
- package/lib/record-reference.js +65 -0
- package/lib/record-stream.d.ts +83 -0
- package/lib/record-stream.js +233 -0
- package/lib/registry/base.d.ts +43 -0
- package/lib/registry/base.js +96 -0
- package/lib/registry/empty.d.ts +7 -0
- package/lib/registry/empty.js +13 -0
- package/lib/registry/file.d.ts +11 -0
- package/lib/registry/file.js +51 -0
- package/lib/registry/index.d.ts +8 -0
- package/lib/registry/index.js +21 -0
- package/lib/registry/sfdx.d.ts +56 -0
- package/lib/registry/sfdx.js +133 -0
- package/lib/registry/types.d.ts +47 -0
- package/lib/registry/types.js +2 -0
- package/lib/request-helper.d.ts +23 -0
- package/lib/request-helper.js +102 -0
- package/lib/request.d.ts +11 -0
- package/lib/request.js +75 -0
- package/lib/session-refresh-delegate.d.ts +31 -0
- package/lib/session-refresh-delegate.js +69 -0
- package/lib/soap.d.ts +60 -0
- package/lib/soap.js +257 -0
- package/lib/sobject.d.ts +258 -0
- package/lib/sobject.js +376 -0
- package/lib/soql-builder.d.ts +25 -0
- package/lib/soql-builder.js +226 -0
- package/lib/transport.d.ts +63 -0
- package/lib/transport.js +175 -0
- package/lib/types/common.d.ts +560 -0
- package/lib/types/common.js +2 -0
- package/lib/types/index.d.ts +7 -0
- package/lib/types/index.js +23 -0
- package/lib/types/projection.d.ts +26 -0
- package/lib/types/projection.js +2 -0
- package/lib/types/record.d.ts +44 -0
- package/lib/types/record.js +2 -0
- package/lib/types/schema.d.ts +50 -0
- package/lib/types/schema.js +2 -0
- package/lib/types/soap.d.ts +43 -0
- package/lib/types/soap.js +2 -0
- package/lib/types/standard-schema.d.ts +16199 -0
- package/lib/types/standard-schema.js +2 -0
- package/lib/types/util.d.ts +7 -0
- package/lib/types/util.js +2 -0
- package/lib/util/formatter.d.ts +8 -0
- package/lib/util/formatter.js +24 -0
- package/lib/util/function.d.ts +32 -0
- package/lib/util/function.js +52 -0
- package/lib/util/get-body-size.d.ts +4 -0
- package/lib/util/get-body-size.js +39 -0
- package/lib/util/logger.d.ts +29 -0
- package/lib/util/logger.js +102 -0
- package/lib/util/promise.d.ts +19 -0
- package/lib/util/promise.js +25 -0
- package/lib/util/stream.d.ts +12 -0
- package/lib/util/stream.js +88 -0
- package/package.json +262 -6
- package/typings/faye/index.d.ts +16 -0
- package/typings/index.d.ts +1 -0
|
@@ -0,0 +1,1153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.Connection = void 0;
|
|
30
|
+
/**
|
|
31
|
+
*
|
|
32
|
+
*/
|
|
33
|
+
const events_1 = require("events");
|
|
34
|
+
const jsforce_1 = __importDefault(require("./jsforce"));
|
|
35
|
+
const transport_1 = __importStar(require("./transport"));
|
|
36
|
+
const logger_1 = require("./util/logger");
|
|
37
|
+
const oauth2_1 = __importDefault(require("./oauth2"));
|
|
38
|
+
const cache_1 = __importDefault(require("./cache"));
|
|
39
|
+
const http_api_1 = __importDefault(require("./http-api"));
|
|
40
|
+
const session_refresh_delegate_1 = __importDefault(require("./session-refresh-delegate"));
|
|
41
|
+
const query_1 = __importDefault(require("./query"));
|
|
42
|
+
const sobject_1 = __importDefault(require("./sobject"));
|
|
43
|
+
const quick_action_1 = __importDefault(require("./quick-action"));
|
|
44
|
+
const process_1 = __importDefault(require("./process"));
|
|
45
|
+
const formatter_1 = require("./util/formatter");
|
|
46
|
+
const form_data_1 = __importDefault(require("form-data"));
|
|
47
|
+
/**
|
|
48
|
+
*
|
|
49
|
+
*/
|
|
50
|
+
const defaultConnectionConfig = {
|
|
51
|
+
loginUrl: 'https://login.salesforce.com',
|
|
52
|
+
instanceUrl: '',
|
|
53
|
+
version: '50.0',
|
|
54
|
+
logLevel: 'NONE',
|
|
55
|
+
maxRequest: 10,
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
*
|
|
59
|
+
*/
|
|
60
|
+
function esc(str) {
|
|
61
|
+
return String(str || '')
|
|
62
|
+
.replace(/&/g, '&')
|
|
63
|
+
.replace(/</g, '<')
|
|
64
|
+
.replace(/>/g, '>')
|
|
65
|
+
.replace(/"/g, '"');
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
*
|
|
69
|
+
*/
|
|
70
|
+
function parseSignedRequest(sr) {
|
|
71
|
+
if (typeof sr === 'string') {
|
|
72
|
+
if (sr[0] === '{') {
|
|
73
|
+
// might be JSON
|
|
74
|
+
return JSON.parse(sr);
|
|
75
|
+
} // might be original base64-encoded signed request
|
|
76
|
+
const msg = sr.split('.').pop(); // retrieve latter part
|
|
77
|
+
if (!msg) {
|
|
78
|
+
throw new Error('Invalid signed request');
|
|
79
|
+
}
|
|
80
|
+
const json = Buffer.from(msg, 'base64').toString('utf-8');
|
|
81
|
+
return JSON.parse(json);
|
|
82
|
+
}
|
|
83
|
+
return sr;
|
|
84
|
+
}
|
|
85
|
+
/** @private **/
|
|
86
|
+
function parseIdUrl(url) {
|
|
87
|
+
const [organizationId, id] = url.split('/').slice(-2);
|
|
88
|
+
return { id, organizationId, url };
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Session Refresh delegate function for OAuth2 authz code flow
|
|
92
|
+
* @private
|
|
93
|
+
*/
|
|
94
|
+
async function oauthRefreshFn(conn, callback) {
|
|
95
|
+
try {
|
|
96
|
+
if (!conn.refreshToken) {
|
|
97
|
+
throw new Error('No refresh token found in the connection');
|
|
98
|
+
}
|
|
99
|
+
const res = await conn.oauth2.refreshToken(conn.refreshToken);
|
|
100
|
+
const userInfo = parseIdUrl(res.id);
|
|
101
|
+
conn._establish({
|
|
102
|
+
instanceUrl: res.instance_url,
|
|
103
|
+
accessToken: res.access_token,
|
|
104
|
+
userInfo,
|
|
105
|
+
});
|
|
106
|
+
callback(undefined, res.access_token, res);
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
if (err instanceof Error) {
|
|
110
|
+
callback(err);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
throw err;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Session Refresh delegate function for username/password login
|
|
119
|
+
* @private
|
|
120
|
+
*/
|
|
121
|
+
function createUsernamePasswordRefreshFn(username, password) {
|
|
122
|
+
return async (conn, callback) => {
|
|
123
|
+
try {
|
|
124
|
+
await conn.login(username, password);
|
|
125
|
+
if (!conn.accessToken) {
|
|
126
|
+
throw new Error('Access token not found after login');
|
|
127
|
+
}
|
|
128
|
+
callback(null, conn.accessToken);
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
if (err instanceof Error) {
|
|
132
|
+
callback(err);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
throw err;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* @private
|
|
142
|
+
*/
|
|
143
|
+
function toSaveResult(err) {
|
|
144
|
+
return {
|
|
145
|
+
success: false,
|
|
146
|
+
errors: [err],
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
*
|
|
151
|
+
*/
|
|
152
|
+
function raiseNoModuleError(name) {
|
|
153
|
+
throw new Error(`API module '${name}' is not loaded, load 'jsforce/api/${name}' explicitly`);
|
|
154
|
+
}
|
|
155
|
+
/*
|
|
156
|
+
* Constant of maximum records num in DML operation (update/delete)
|
|
157
|
+
*/
|
|
158
|
+
const MAX_DML_COUNT = 200;
|
|
159
|
+
/**
|
|
160
|
+
*
|
|
161
|
+
*/
|
|
162
|
+
class Connection extends events_1.EventEmitter {
|
|
163
|
+
static _logger = (0, logger_1.getLogger)('connection');
|
|
164
|
+
version;
|
|
165
|
+
loginUrl;
|
|
166
|
+
instanceUrl;
|
|
167
|
+
accessToken;
|
|
168
|
+
refreshToken;
|
|
169
|
+
userInfo;
|
|
170
|
+
limitInfo = {};
|
|
171
|
+
oauth2;
|
|
172
|
+
sobjects = {};
|
|
173
|
+
cache;
|
|
174
|
+
_callOptions;
|
|
175
|
+
_maxRequest;
|
|
176
|
+
_logger;
|
|
177
|
+
_logLevel;
|
|
178
|
+
_transport;
|
|
179
|
+
_sessionType;
|
|
180
|
+
_refreshDelegate;
|
|
181
|
+
// describe: (name: string) => Promise<DescribeSObjectResult>;
|
|
182
|
+
describe$;
|
|
183
|
+
describe$$;
|
|
184
|
+
describeSObject;
|
|
185
|
+
describeSObject$;
|
|
186
|
+
describeSObject$$;
|
|
187
|
+
// describeGlobal: () => Promise<DescribeGlobalResult>;
|
|
188
|
+
describeGlobal$;
|
|
189
|
+
describeGlobal$$;
|
|
190
|
+
// API libs are not instantiated here so that core module to remain without dependencies to them
|
|
191
|
+
// It is responsible for developers to import api libs explicitly if they are using 'jsforce/core' instead of 'jsforce'.
|
|
192
|
+
get analytics() {
|
|
193
|
+
return raiseNoModuleError('analytics');
|
|
194
|
+
}
|
|
195
|
+
get apex() {
|
|
196
|
+
return raiseNoModuleError('apex');
|
|
197
|
+
}
|
|
198
|
+
get bulk() {
|
|
199
|
+
return raiseNoModuleError('bulk');
|
|
200
|
+
}
|
|
201
|
+
get bulk2() {
|
|
202
|
+
return raiseNoModuleError('bulk2');
|
|
203
|
+
}
|
|
204
|
+
get chatter() {
|
|
205
|
+
return raiseNoModuleError('chatter');
|
|
206
|
+
}
|
|
207
|
+
get metadata() {
|
|
208
|
+
return raiseNoModuleError('metadata');
|
|
209
|
+
}
|
|
210
|
+
get soap() {
|
|
211
|
+
return raiseNoModuleError('soap');
|
|
212
|
+
}
|
|
213
|
+
get streaming() {
|
|
214
|
+
return raiseNoModuleError('streaming');
|
|
215
|
+
}
|
|
216
|
+
get tooling() {
|
|
217
|
+
return raiseNoModuleError('tooling');
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
*
|
|
221
|
+
*/
|
|
222
|
+
constructor(config = {}) {
|
|
223
|
+
super();
|
|
224
|
+
const { loginUrl, instanceUrl, version, oauth2, maxRequest, logLevel, proxyUrl, httpProxy, } = config;
|
|
225
|
+
this.loginUrl = loginUrl || defaultConnectionConfig.loginUrl;
|
|
226
|
+
this.instanceUrl = instanceUrl || defaultConnectionConfig.instanceUrl;
|
|
227
|
+
this.version = version || defaultConnectionConfig.version;
|
|
228
|
+
this.oauth2 =
|
|
229
|
+
oauth2 instanceof oauth2_1.default
|
|
230
|
+
? oauth2
|
|
231
|
+
: new oauth2_1.default({
|
|
232
|
+
loginUrl: this.loginUrl,
|
|
233
|
+
proxyUrl,
|
|
234
|
+
httpProxy,
|
|
235
|
+
...oauth2,
|
|
236
|
+
});
|
|
237
|
+
let refreshFn = config.refreshFn;
|
|
238
|
+
if (!refreshFn && this.oauth2.clientId) {
|
|
239
|
+
refreshFn = oauthRefreshFn;
|
|
240
|
+
}
|
|
241
|
+
if (refreshFn) {
|
|
242
|
+
this._refreshDelegate = new session_refresh_delegate_1.default(this, refreshFn);
|
|
243
|
+
}
|
|
244
|
+
this._maxRequest = maxRequest || defaultConnectionConfig.maxRequest;
|
|
245
|
+
this._logger = logLevel
|
|
246
|
+
? Connection._logger.createInstance(logLevel)
|
|
247
|
+
: Connection._logger;
|
|
248
|
+
this._logLevel = logLevel;
|
|
249
|
+
this._transport = proxyUrl
|
|
250
|
+
? new transport_1.XdProxyTransport(proxyUrl)
|
|
251
|
+
: httpProxy
|
|
252
|
+
? new transport_1.HttpProxyTransport(httpProxy)
|
|
253
|
+
: new transport_1.default();
|
|
254
|
+
this._callOptions = config.callOptions;
|
|
255
|
+
this.cache = new cache_1.default();
|
|
256
|
+
const describeCacheKey = (type) => type ? `describe.${type}` : 'describe';
|
|
257
|
+
const describe = Connection.prototype.describe;
|
|
258
|
+
this.describe = this.cache.createCachedFunction(describe, this, {
|
|
259
|
+
key: describeCacheKey,
|
|
260
|
+
strategy: 'NOCACHE',
|
|
261
|
+
});
|
|
262
|
+
this.describe$ = this.cache.createCachedFunction(describe, this, {
|
|
263
|
+
key: describeCacheKey,
|
|
264
|
+
strategy: 'HIT',
|
|
265
|
+
});
|
|
266
|
+
this.describe$$ = this.cache.createCachedFunction(describe, this, {
|
|
267
|
+
key: describeCacheKey,
|
|
268
|
+
strategy: 'IMMEDIATE',
|
|
269
|
+
});
|
|
270
|
+
this.describeSObject = this.describe;
|
|
271
|
+
this.describeSObject$ = this.describe$;
|
|
272
|
+
this.describeSObject$$ = this.describe$$;
|
|
273
|
+
const describeGlobal = Connection.prototype.describeGlobal;
|
|
274
|
+
this.describeGlobal = this.cache.createCachedFunction(describeGlobal, this, { key: 'describeGlobal', strategy: 'NOCACHE' });
|
|
275
|
+
this.describeGlobal$ = this.cache.createCachedFunction(describeGlobal, this, { key: 'describeGlobal', strategy: 'HIT' });
|
|
276
|
+
this.describeGlobal$$ = this.cache.createCachedFunction(describeGlobal, this, { key: 'describeGlobal', strategy: 'IMMEDIATE' });
|
|
277
|
+
const { accessToken, refreshToken, sessionId, serverUrl, signedRequest, } = config;
|
|
278
|
+
this._establish({
|
|
279
|
+
accessToken,
|
|
280
|
+
refreshToken,
|
|
281
|
+
instanceUrl,
|
|
282
|
+
sessionId,
|
|
283
|
+
serverUrl,
|
|
284
|
+
signedRequest,
|
|
285
|
+
});
|
|
286
|
+
jsforce_1.default.emit('connection:new', this);
|
|
287
|
+
}
|
|
288
|
+
/* @private */
|
|
289
|
+
_establish(options) {
|
|
290
|
+
const { accessToken, refreshToken, instanceUrl, sessionId, serverUrl, signedRequest, userInfo, } = options;
|
|
291
|
+
this.instanceUrl = serverUrl
|
|
292
|
+
? serverUrl.split('/').slice(0, 3).join('/')
|
|
293
|
+
: instanceUrl || this.instanceUrl;
|
|
294
|
+
this.accessToken = sessionId || accessToken || this.accessToken;
|
|
295
|
+
this.refreshToken = refreshToken || this.refreshToken;
|
|
296
|
+
if (this.refreshToken && !this._refreshDelegate) {
|
|
297
|
+
throw new Error('Refresh token is specified without oauth2 client information or refresh function');
|
|
298
|
+
}
|
|
299
|
+
const signedRequestObject = signedRequest && parseSignedRequest(signedRequest);
|
|
300
|
+
if (signedRequestObject) {
|
|
301
|
+
this.accessToken = signedRequestObject.client.oauthToken;
|
|
302
|
+
if (transport_1.CanvasTransport.supported) {
|
|
303
|
+
this._transport = new transport_1.CanvasTransport(signedRequestObject);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
this.userInfo = userInfo || this.userInfo;
|
|
307
|
+
this._sessionType = sessionId ? 'soap' : 'oauth2';
|
|
308
|
+
this._resetInstance();
|
|
309
|
+
}
|
|
310
|
+
/* @priveate */
|
|
311
|
+
_clearSession() {
|
|
312
|
+
this.accessToken = null;
|
|
313
|
+
this.refreshToken = null;
|
|
314
|
+
this.instanceUrl = defaultConnectionConfig.instanceUrl;
|
|
315
|
+
this.userInfo = null;
|
|
316
|
+
this._sessionType = null;
|
|
317
|
+
}
|
|
318
|
+
/* @priveate */
|
|
319
|
+
_resetInstance() {
|
|
320
|
+
this.limitInfo = {};
|
|
321
|
+
this.sobjects = {};
|
|
322
|
+
// TODO impl cache
|
|
323
|
+
this.cache.clear();
|
|
324
|
+
this.cache.get('describeGlobal').removeAllListeners('value');
|
|
325
|
+
this.cache.get('describeGlobal').on('value', ({ result }) => {
|
|
326
|
+
if (result) {
|
|
327
|
+
for (const so of result.sobjects) {
|
|
328
|
+
this.sobject(so.name);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
/*
|
|
333
|
+
if (this.tooling) {
|
|
334
|
+
this.tooling._resetInstance();
|
|
335
|
+
}
|
|
336
|
+
*/
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Authorize the connection using OAuth2 flow.
|
|
340
|
+
* Typically, just pass the code returned from authorization server in the first argument to complete authorization.
|
|
341
|
+
* If you want to authorize with grant types other than `authorization_code`, you can also pass params object with the grant type.
|
|
342
|
+
*
|
|
343
|
+
* @returns {Promise<UserInfo>} An object that contains the user ID, org ID and identity URL.
|
|
344
|
+
*
|
|
345
|
+
*/
|
|
346
|
+
async authorize(codeOrParams, params = {}) {
|
|
347
|
+
const res = await this.oauth2.requestToken(codeOrParams, params);
|
|
348
|
+
const userInfo = parseIdUrl(res.id);
|
|
349
|
+
this._establish({
|
|
350
|
+
instanceUrl: res.instance_url,
|
|
351
|
+
accessToken: res.access_token,
|
|
352
|
+
refreshToken: res.refresh_token,
|
|
353
|
+
userInfo,
|
|
354
|
+
});
|
|
355
|
+
this._logger.debug(`<login> completed. user id = ${userInfo.id}, org id = ${userInfo.organizationId}`);
|
|
356
|
+
return userInfo;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
*
|
|
360
|
+
*/
|
|
361
|
+
async login(username, password) {
|
|
362
|
+
this._refreshDelegate = new session_refresh_delegate_1.default(this, createUsernamePasswordRefreshFn(username, password));
|
|
363
|
+
if (this.oauth2 && this.oauth2.clientId && this.oauth2.clientSecret) {
|
|
364
|
+
return this.loginByOAuth2(username, password);
|
|
365
|
+
}
|
|
366
|
+
return this.loginBySoap(username, password);
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Login by OAuth2 username & password flow
|
|
370
|
+
*/
|
|
371
|
+
async loginByOAuth2(username, password) {
|
|
372
|
+
const res = await this.oauth2.authenticate(username, password);
|
|
373
|
+
const userInfo = parseIdUrl(res.id);
|
|
374
|
+
this._establish({
|
|
375
|
+
instanceUrl: res.instance_url,
|
|
376
|
+
accessToken: res.access_token,
|
|
377
|
+
userInfo,
|
|
378
|
+
});
|
|
379
|
+
this._logger.info(`<login> completed. user id = ${userInfo.id}, org id = ${userInfo.organizationId}`);
|
|
380
|
+
return userInfo;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
*
|
|
384
|
+
*/
|
|
385
|
+
async loginBySoap(username, password) {
|
|
386
|
+
if (!username || !password) {
|
|
387
|
+
return Promise.reject(new Error('no username password given'));
|
|
388
|
+
}
|
|
389
|
+
const body = [
|
|
390
|
+
'<se:Envelope xmlns:se="http://schemas.xmlsoap.org/soap/envelope/">',
|
|
391
|
+
'<se:Header/>',
|
|
392
|
+
'<se:Body>',
|
|
393
|
+
'<login xmlns="urn:partner.soap.sforce.com">',
|
|
394
|
+
`<username>${esc(username)}</username>`,
|
|
395
|
+
`<password>${esc(password)}</password>`,
|
|
396
|
+
'</login>',
|
|
397
|
+
'</se:Body>',
|
|
398
|
+
'</se:Envelope>',
|
|
399
|
+
].join('');
|
|
400
|
+
const soapLoginEndpoint = [
|
|
401
|
+
this.loginUrl,
|
|
402
|
+
'services/Soap/u',
|
|
403
|
+
this.version,
|
|
404
|
+
].join('/');
|
|
405
|
+
const response = await this._transport.httpRequest({
|
|
406
|
+
method: 'POST',
|
|
407
|
+
url: soapLoginEndpoint,
|
|
408
|
+
body,
|
|
409
|
+
headers: {
|
|
410
|
+
'Content-Type': 'text/xml',
|
|
411
|
+
SOAPAction: '""',
|
|
412
|
+
},
|
|
413
|
+
});
|
|
414
|
+
let m;
|
|
415
|
+
if (response.statusCode >= 400) {
|
|
416
|
+
m = response.body.match(/<faultstring>([^<]+)<\/faultstring>/);
|
|
417
|
+
const faultstring = m && m[1];
|
|
418
|
+
throw new Error(faultstring || response.body);
|
|
419
|
+
}
|
|
420
|
+
this._logger.debug(`SOAP response = ${response.body}`);
|
|
421
|
+
m = response.body.match(/<serverUrl>([^<]+)<\/serverUrl>/);
|
|
422
|
+
const serverUrl = m && m[1];
|
|
423
|
+
m = response.body.match(/<sessionId>([^<]+)<\/sessionId>/);
|
|
424
|
+
const sessionId = m && m[1];
|
|
425
|
+
m = response.body.match(/<userId>([^<]+)<\/userId>/);
|
|
426
|
+
const userId = m && m[1];
|
|
427
|
+
m = response.body.match(/<organizationId>([^<]+)<\/organizationId>/);
|
|
428
|
+
const organizationId = m && m[1];
|
|
429
|
+
if (!serverUrl || !sessionId || !userId || !organizationId) {
|
|
430
|
+
throw new Error('could not extract session information from login response');
|
|
431
|
+
}
|
|
432
|
+
const idUrl = [this.loginUrl, 'id', organizationId, userId].join('/');
|
|
433
|
+
const userInfo = { id: userId, organizationId, url: idUrl };
|
|
434
|
+
this._establish({
|
|
435
|
+
serverUrl: serverUrl.split('/').slice(0, 3).join('/'),
|
|
436
|
+
sessionId,
|
|
437
|
+
userInfo,
|
|
438
|
+
});
|
|
439
|
+
this._logger.info(`<login> completed. user id = ${userId}, org id = ${organizationId}`);
|
|
440
|
+
return userInfo;
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Logout the current session
|
|
444
|
+
*/
|
|
445
|
+
async logout(revoke) {
|
|
446
|
+
this._refreshDelegate = undefined;
|
|
447
|
+
if (this._sessionType === 'oauth2') {
|
|
448
|
+
return this.logoutByOAuth2(revoke);
|
|
449
|
+
}
|
|
450
|
+
return this.logoutBySoap(revoke);
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Logout the current session by revoking access token via OAuth2 session revoke
|
|
454
|
+
*/
|
|
455
|
+
async logoutByOAuth2(revoke) {
|
|
456
|
+
const token = revoke ? this.refreshToken : this.accessToken;
|
|
457
|
+
if (token) {
|
|
458
|
+
await this.oauth2.revokeToken(token);
|
|
459
|
+
}
|
|
460
|
+
// Destroy the session bound to this connection
|
|
461
|
+
this._clearSession();
|
|
462
|
+
this._resetInstance();
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Logout the session by using SOAP web service API
|
|
466
|
+
*/
|
|
467
|
+
async logoutBySoap(revoke) {
|
|
468
|
+
const body = [
|
|
469
|
+
'<se:Envelope xmlns:se="http://schemas.xmlsoap.org/soap/envelope/">',
|
|
470
|
+
'<se:Header>',
|
|
471
|
+
'<SessionHeader xmlns="urn:partner.soap.sforce.com">',
|
|
472
|
+
`<sessionId>${esc(revoke ? this.refreshToken : this.accessToken)}</sessionId>`,
|
|
473
|
+
'</SessionHeader>',
|
|
474
|
+
'</se:Header>',
|
|
475
|
+
'<se:Body>',
|
|
476
|
+
'<logout xmlns="urn:partner.soap.sforce.com"/>',
|
|
477
|
+
'</se:Body>',
|
|
478
|
+
'</se:Envelope>',
|
|
479
|
+
].join('');
|
|
480
|
+
const response = await this._transport.httpRequest({
|
|
481
|
+
method: 'POST',
|
|
482
|
+
url: [this.instanceUrl, 'services/Soap/u', this.version].join('/'),
|
|
483
|
+
body,
|
|
484
|
+
headers: {
|
|
485
|
+
'Content-Type': 'text/xml',
|
|
486
|
+
SOAPAction: '""',
|
|
487
|
+
},
|
|
488
|
+
});
|
|
489
|
+
this._logger.debug(`SOAP statusCode = ${response.statusCode}, response = ${response.body}`);
|
|
490
|
+
if (response.statusCode >= 400) {
|
|
491
|
+
const m = response.body.match(/<faultstring>([^<]+)<\/faultstring>/);
|
|
492
|
+
const faultstring = m && m[1];
|
|
493
|
+
throw new Error(faultstring || response.body);
|
|
494
|
+
}
|
|
495
|
+
// Destroy the session bound to this connection
|
|
496
|
+
this._clearSession();
|
|
497
|
+
this._resetInstance();
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Send REST API request with given HTTP request info, with connected session information.
|
|
501
|
+
*
|
|
502
|
+
* Endpoint URL can be absolute URL ('https://na1.salesforce.com/services/data/v32.0/sobjects/Account/describe')
|
|
503
|
+
* , relative path from root ('/services/data/v32.0/sobjects/Account/describe')
|
|
504
|
+
* , or relative path from version root ('/sobjects/Account/describe').
|
|
505
|
+
*/
|
|
506
|
+
request(request, options = {}) {
|
|
507
|
+
// if request is simple string, regard it as url in GET method
|
|
508
|
+
let request_ = typeof request === 'string' ? { method: 'GET', url: request } : request;
|
|
509
|
+
// if url is given in relative path, prepend base url or instance url before.
|
|
510
|
+
request_ = {
|
|
511
|
+
...request_,
|
|
512
|
+
url: this._normalizeUrl(request_.url),
|
|
513
|
+
};
|
|
514
|
+
const httpApi = new http_api_1.default(this, options);
|
|
515
|
+
// log api usage and its quota
|
|
516
|
+
httpApi.on('response', (response) => {
|
|
517
|
+
if (response.headers && response.headers['sforce-limit-info']) {
|
|
518
|
+
const apiUsage = response.headers['sforce-limit-info'].match(/api-usage=(\d+)\/(\d+)/);
|
|
519
|
+
if (apiUsage) {
|
|
520
|
+
this.limitInfo = {
|
|
521
|
+
apiUsage: {
|
|
522
|
+
used: parseInt(apiUsage[1], 10),
|
|
523
|
+
limit: parseInt(apiUsage[2], 10),
|
|
524
|
+
},
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
return httpApi.request(request_);
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Send HTTP GET request
|
|
533
|
+
*
|
|
534
|
+
* Endpoint URL can be absolute URL ('https://na1.salesforce.com/services/data/v32.0/sobjects/Account/describe')
|
|
535
|
+
* , relative path from root ('/services/data/v32.0/sobjects/Account/describe')
|
|
536
|
+
* , or relative path from version root ('/sobjects/Account/describe').
|
|
537
|
+
*/
|
|
538
|
+
requestGet(url, options) {
|
|
539
|
+
const request = { method: 'GET', url };
|
|
540
|
+
return this.request(request, options);
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Send HTTP POST request with JSON body, with connected session information
|
|
544
|
+
*
|
|
545
|
+
* Endpoint URL can be absolute URL ('https://na1.salesforce.com/services/data/v32.0/sobjects/Account/describe')
|
|
546
|
+
* , relative path from root ('/services/data/v32.0/sobjects/Account/describe')
|
|
547
|
+
* , or relative path from version root ('/sobjects/Account/describe').
|
|
548
|
+
*/
|
|
549
|
+
requestPost(url, body, options) {
|
|
550
|
+
const request = {
|
|
551
|
+
method: 'POST',
|
|
552
|
+
url,
|
|
553
|
+
body: JSON.stringify(body),
|
|
554
|
+
headers: { 'content-type': 'application/json' },
|
|
555
|
+
};
|
|
556
|
+
return this.request(request, options);
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Send HTTP PUT request with JSON body, with connected session information
|
|
560
|
+
*
|
|
561
|
+
* Endpoint URL can be absolute URL ('https://na1.salesforce.com/services/data/v32.0/sobjects/Account/describe')
|
|
562
|
+
* , relative path from root ('/services/data/v32.0/sobjects/Account/describe')
|
|
563
|
+
* , or relative path from version root ('/sobjects/Account/describe').
|
|
564
|
+
*/
|
|
565
|
+
requestPut(url, body, options) {
|
|
566
|
+
const request = {
|
|
567
|
+
method: 'PUT',
|
|
568
|
+
url,
|
|
569
|
+
body: JSON.stringify(body),
|
|
570
|
+
headers: { 'content-type': 'application/json' },
|
|
571
|
+
};
|
|
572
|
+
return this.request(request, options);
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Send HTTP PATCH request with JSON body
|
|
576
|
+
*
|
|
577
|
+
* Endpoint URL can be absolute URL ('https://na1.salesforce.com/services/data/v32.0/sobjects/Account/describe')
|
|
578
|
+
* , relative path from root ('/services/data/v32.0/sobjects/Account/describe')
|
|
579
|
+
* , or relative path from version root ('/sobjects/Account/describe').
|
|
580
|
+
*/
|
|
581
|
+
requestPatch(url, body, options) {
|
|
582
|
+
const request = {
|
|
583
|
+
method: 'PATCH',
|
|
584
|
+
url,
|
|
585
|
+
body: JSON.stringify(body),
|
|
586
|
+
headers: { 'content-type': 'application/json' },
|
|
587
|
+
};
|
|
588
|
+
return this.request(request, options);
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Send HTTP DELETE request
|
|
592
|
+
*
|
|
593
|
+
* Endpoint URL can be absolute URL ('https://na1.salesforce.com/services/data/v32.0/sobjects/Account/describe')
|
|
594
|
+
* , relative path from root ('/services/data/v32.0/sobjects/Account/describe')
|
|
595
|
+
* , or relative path from version root ('/sobjects/Account/describe').
|
|
596
|
+
*/
|
|
597
|
+
requestDelete(url, options) {
|
|
598
|
+
const request = { method: 'DELETE', url };
|
|
599
|
+
return this.request(request, options);
|
|
600
|
+
}
|
|
601
|
+
/** @private **/
|
|
602
|
+
_baseUrl() {
|
|
603
|
+
return [this.instanceUrl, 'services/data', `v${this.version}`].join('/');
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Convert path to absolute url
|
|
607
|
+
* @private
|
|
608
|
+
*/
|
|
609
|
+
_normalizeUrl(url) {
|
|
610
|
+
if (url[0] === '/') {
|
|
611
|
+
if (url.indexOf(this.instanceUrl + '/services/') === 0) {
|
|
612
|
+
return url;
|
|
613
|
+
}
|
|
614
|
+
if (url.indexOf('/services/') === 0) {
|
|
615
|
+
return this.instanceUrl + url;
|
|
616
|
+
}
|
|
617
|
+
return this._baseUrl() + url;
|
|
618
|
+
}
|
|
619
|
+
return url;
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
*
|
|
623
|
+
*/
|
|
624
|
+
query(soql, options) {
|
|
625
|
+
return new query_1.default(this, soql, options);
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Execute search by SOSL
|
|
629
|
+
*
|
|
630
|
+
* @param {String} sosl - SOSL string
|
|
631
|
+
* @param {Callback.<Array.<RecordResult>>} [callback] - Callback function
|
|
632
|
+
* @returns {Promise.<Array.<RecordResult>>}
|
|
633
|
+
*/
|
|
634
|
+
search(sosl) {
|
|
635
|
+
const url = this._baseUrl() + '/search?q=' + encodeURIComponent(sosl);
|
|
636
|
+
return this.request(url);
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
*
|
|
640
|
+
*/
|
|
641
|
+
queryMore(locator, options) {
|
|
642
|
+
return new query_1.default(this, { locator }, options);
|
|
643
|
+
}
|
|
644
|
+
/* */
|
|
645
|
+
_ensureVersion(majorVersion) {
|
|
646
|
+
const versions = this.version.split('.');
|
|
647
|
+
return parseInt(versions[0], 10) >= majorVersion;
|
|
648
|
+
}
|
|
649
|
+
/* */
|
|
650
|
+
_supports(feature) {
|
|
651
|
+
switch (feature) {
|
|
652
|
+
case 'sobject-collection': // sobject collection is available only in API ver 42.0+
|
|
653
|
+
return this._ensureVersion(42);
|
|
654
|
+
default:
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
async retrieve(type, ids, options = {}) {
|
|
659
|
+
return Array.isArray(ids)
|
|
660
|
+
? // check the version whether SObject collection API is supported (42.0)
|
|
661
|
+
this._ensureVersion(42)
|
|
662
|
+
? this._retrieveMany(type, ids, options)
|
|
663
|
+
: this._retrieveParallel(type, ids, options)
|
|
664
|
+
: this._retrieveSingle(type, ids, options);
|
|
665
|
+
}
|
|
666
|
+
/** @private */
|
|
667
|
+
async _retrieveSingle(type, id, options) {
|
|
668
|
+
if (!id) {
|
|
669
|
+
throw new Error('Invalid record ID. Specify valid record ID value');
|
|
670
|
+
}
|
|
671
|
+
let url = [this._baseUrl(), 'sobjects', type, id].join('/');
|
|
672
|
+
const { fields, headers } = options;
|
|
673
|
+
if (fields) {
|
|
674
|
+
url += `?fields=${fields.join(',')}`;
|
|
675
|
+
}
|
|
676
|
+
return this.request({ method: 'GET', url, headers });
|
|
677
|
+
}
|
|
678
|
+
/** @private */
|
|
679
|
+
async _retrieveParallel(type, ids, options) {
|
|
680
|
+
if (ids.length > this._maxRequest) {
|
|
681
|
+
throw new Error('Exceeded max limit of concurrent call');
|
|
682
|
+
}
|
|
683
|
+
return Promise.all(ids.map((id) => this._retrieveSingle(type, id, options).catch((err) => {
|
|
684
|
+
if (options.allOrNone || err.errorCode !== 'NOT_FOUND') {
|
|
685
|
+
throw err;
|
|
686
|
+
}
|
|
687
|
+
return null;
|
|
688
|
+
})));
|
|
689
|
+
}
|
|
690
|
+
/** @private */
|
|
691
|
+
async _retrieveMany(type, ids, options) {
|
|
692
|
+
if (ids.length === 0) {
|
|
693
|
+
return [];
|
|
694
|
+
}
|
|
695
|
+
const url = [this._baseUrl(), 'composite', 'sobjects', type].join('/');
|
|
696
|
+
const fields = options.fields ||
|
|
697
|
+
(await this.describe$(type)).fields.map((field) => field.name);
|
|
698
|
+
return this.request({
|
|
699
|
+
method: 'POST',
|
|
700
|
+
url,
|
|
701
|
+
body: JSON.stringify({ ids, fields }),
|
|
702
|
+
headers: {
|
|
703
|
+
...(options.headers || {}),
|
|
704
|
+
'content-type': 'application/json',
|
|
705
|
+
},
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* @param type
|
|
710
|
+
* @param records
|
|
711
|
+
* @param options
|
|
712
|
+
*/
|
|
713
|
+
async create(type, records, options = {}) {
|
|
714
|
+
const ret = Array.isArray(records)
|
|
715
|
+
? // check the version whether SObject collection API is supported (42.0)
|
|
716
|
+
this._ensureVersion(42)
|
|
717
|
+
? await this._createMany(type, records, options)
|
|
718
|
+
: await this._createParallel(type, records, options)
|
|
719
|
+
: await this._createSingle(type, records, options);
|
|
720
|
+
return ret;
|
|
721
|
+
}
|
|
722
|
+
/** @private */
|
|
723
|
+
async _createSingle(type, record, options) {
|
|
724
|
+
const { Id, type: rtype, attributes, ...rec } = record;
|
|
725
|
+
const sobjectType = type || (attributes && attributes.type) || rtype;
|
|
726
|
+
if (!sobjectType) {
|
|
727
|
+
throw new Error('No SObject Type defined in record');
|
|
728
|
+
}
|
|
729
|
+
const url = [this._baseUrl(), 'sobjects', sobjectType].join('/');
|
|
730
|
+
let contentType, body;
|
|
731
|
+
if (options && options.multipartFileFields) {
|
|
732
|
+
// Send the record as a multipart/form-data request. Useful for fields containing large binary blobs.
|
|
733
|
+
const form = new form_data_1.default();
|
|
734
|
+
// Extract the fields requested to be sent separately from the JSON
|
|
735
|
+
Object.entries(options.multipartFileFields).forEach(([fieldName, fileDetails]) => {
|
|
736
|
+
form.append(fieldName, Buffer.from(rec[fieldName], 'base64'), fileDetails);
|
|
737
|
+
delete rec[fieldName];
|
|
738
|
+
});
|
|
739
|
+
// Serialize the remaining fields as JSON
|
|
740
|
+
form.append(type, JSON.stringify(rec), {
|
|
741
|
+
contentType: 'application/json',
|
|
742
|
+
});
|
|
743
|
+
contentType = form.getHeaders()['content-type']; // This is necessary to ensure the 'boundary' is present
|
|
744
|
+
body = form;
|
|
745
|
+
}
|
|
746
|
+
else {
|
|
747
|
+
// Default behavior: send the request as JSON
|
|
748
|
+
contentType = 'application/json';
|
|
749
|
+
body = JSON.stringify(rec);
|
|
750
|
+
}
|
|
751
|
+
return this.request({
|
|
752
|
+
method: 'POST',
|
|
753
|
+
url,
|
|
754
|
+
body: body,
|
|
755
|
+
headers: {
|
|
756
|
+
...(options.headers || {}),
|
|
757
|
+
'content-type': contentType,
|
|
758
|
+
},
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
/** @private */
|
|
762
|
+
async _createParallel(type, records, options) {
|
|
763
|
+
if (records.length > this._maxRequest) {
|
|
764
|
+
throw new Error('Exceeded max limit of concurrent call');
|
|
765
|
+
}
|
|
766
|
+
return Promise.all(records.map((record) => this._createSingle(type, record, options).catch((err) => {
|
|
767
|
+
// be aware that allOrNone in parallel mode will not revert the other successful requests
|
|
768
|
+
// it only raises error when met at least one failed request.
|
|
769
|
+
if (options.allOrNone || !err.errorCode) {
|
|
770
|
+
throw err;
|
|
771
|
+
}
|
|
772
|
+
return toSaveResult(err);
|
|
773
|
+
})));
|
|
774
|
+
}
|
|
775
|
+
/** @private */
|
|
776
|
+
async _createMany(type, records, options) {
|
|
777
|
+
if (records.length === 0) {
|
|
778
|
+
return Promise.resolve([]);
|
|
779
|
+
}
|
|
780
|
+
if (records.length > MAX_DML_COUNT && options.allowRecursive) {
|
|
781
|
+
return [
|
|
782
|
+
...(await this._createMany(type, records.slice(0, MAX_DML_COUNT), options)),
|
|
783
|
+
...(await this._createMany(type, records.slice(MAX_DML_COUNT), options)),
|
|
784
|
+
];
|
|
785
|
+
}
|
|
786
|
+
const _records = records.map((record) => {
|
|
787
|
+
const { Id, type: rtype, attributes, ...rec } = record;
|
|
788
|
+
const sobjectType = type || (attributes && attributes.type) || rtype;
|
|
789
|
+
if (!sobjectType) {
|
|
790
|
+
throw new Error('No SObject Type defined in record');
|
|
791
|
+
}
|
|
792
|
+
return { attributes: { type: sobjectType }, ...rec };
|
|
793
|
+
});
|
|
794
|
+
const url = [this._baseUrl(), 'composite', 'sobjects'].join('/');
|
|
795
|
+
return this.request({
|
|
796
|
+
method: 'POST',
|
|
797
|
+
url,
|
|
798
|
+
body: JSON.stringify({
|
|
799
|
+
allOrNone: options.allOrNone || false,
|
|
800
|
+
records: _records,
|
|
801
|
+
}),
|
|
802
|
+
headers: {
|
|
803
|
+
...(options.headers || {}),
|
|
804
|
+
'content-type': 'application/json',
|
|
805
|
+
},
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Synonym of Connection#create()
|
|
810
|
+
*/
|
|
811
|
+
insert = this.create;
|
|
812
|
+
/**
|
|
813
|
+
* @param type
|
|
814
|
+
* @param records
|
|
815
|
+
* @param options
|
|
816
|
+
*/
|
|
817
|
+
update(type, records, options = {}) {
|
|
818
|
+
return Array.isArray(records)
|
|
819
|
+
? // check the version whether SObject collection API is supported (42.0)
|
|
820
|
+
this._ensureVersion(42)
|
|
821
|
+
? this._updateMany(type, records, options)
|
|
822
|
+
: this._updateParallel(type, records, options)
|
|
823
|
+
: this._updateSingle(type, records, options);
|
|
824
|
+
}
|
|
825
|
+
/** @private */
|
|
826
|
+
async _updateSingle(type, record, options) {
|
|
827
|
+
const { Id: id, type: rtype, attributes, ...rec } = record;
|
|
828
|
+
if (!id) {
|
|
829
|
+
throw new Error('Record id is not found in record.');
|
|
830
|
+
}
|
|
831
|
+
const sobjectType = type || (attributes && attributes.type) || rtype;
|
|
832
|
+
if (!sobjectType) {
|
|
833
|
+
throw new Error('No SObject Type defined in record');
|
|
834
|
+
}
|
|
835
|
+
const url = [this._baseUrl(), 'sobjects', sobjectType, id].join('/');
|
|
836
|
+
return this.request({
|
|
837
|
+
method: 'PATCH',
|
|
838
|
+
url,
|
|
839
|
+
body: JSON.stringify(rec),
|
|
840
|
+
headers: {
|
|
841
|
+
...(options.headers || {}),
|
|
842
|
+
'content-type': 'application/json',
|
|
843
|
+
},
|
|
844
|
+
}, {
|
|
845
|
+
noContentResponse: { id, success: true, errors: [] },
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
/** @private */
|
|
849
|
+
async _updateParallel(type, records, options) {
|
|
850
|
+
if (records.length > this._maxRequest) {
|
|
851
|
+
throw new Error('Exceeded max limit of concurrent call');
|
|
852
|
+
}
|
|
853
|
+
return Promise.all(records.map((record) => this._updateSingle(type, record, options).catch((err) => {
|
|
854
|
+
// be aware that allOrNone in parallel mode will not revert the other successful requests
|
|
855
|
+
// it only raises error when met at least one failed request.
|
|
856
|
+
if (options.allOrNone || !err.errorCode) {
|
|
857
|
+
throw err;
|
|
858
|
+
}
|
|
859
|
+
return toSaveResult(err);
|
|
860
|
+
})));
|
|
861
|
+
}
|
|
862
|
+
/** @private */
|
|
863
|
+
async _updateMany(type, records, options) {
|
|
864
|
+
if (records.length === 0) {
|
|
865
|
+
return [];
|
|
866
|
+
}
|
|
867
|
+
if (records.length > MAX_DML_COUNT && options.allowRecursive) {
|
|
868
|
+
return [
|
|
869
|
+
...(await this._updateMany(type, records.slice(0, MAX_DML_COUNT), options)),
|
|
870
|
+
...(await this._updateMany(type, records.slice(MAX_DML_COUNT), options)),
|
|
871
|
+
];
|
|
872
|
+
}
|
|
873
|
+
const _records = records.map((record) => {
|
|
874
|
+
const { Id: id, type: rtype, attributes, ...rec } = record;
|
|
875
|
+
if (!id) {
|
|
876
|
+
throw new Error('Record id is not found in record.');
|
|
877
|
+
}
|
|
878
|
+
const sobjectType = type || (attributes && attributes.type) || rtype;
|
|
879
|
+
if (!sobjectType) {
|
|
880
|
+
throw new Error('No SObject Type defined in record');
|
|
881
|
+
}
|
|
882
|
+
return { id, attributes: { type: sobjectType }, ...rec };
|
|
883
|
+
});
|
|
884
|
+
const url = [this._baseUrl(), 'composite', 'sobjects'].join('/');
|
|
885
|
+
return this.request({
|
|
886
|
+
method: 'PATCH',
|
|
887
|
+
url,
|
|
888
|
+
body: JSON.stringify({
|
|
889
|
+
allOrNone: options.allOrNone || false,
|
|
890
|
+
records: _records,
|
|
891
|
+
}),
|
|
892
|
+
headers: {
|
|
893
|
+
...(options.headers || {}),
|
|
894
|
+
'content-type': 'application/json',
|
|
895
|
+
},
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
*
|
|
900
|
+
* @param type
|
|
901
|
+
* @param records
|
|
902
|
+
* @param extIdField
|
|
903
|
+
* @param options
|
|
904
|
+
*/
|
|
905
|
+
async upsert(type, records, extIdField, options = {}) {
|
|
906
|
+
const isArray = Array.isArray(records);
|
|
907
|
+
const _records = Array.isArray(records) ? records : [records];
|
|
908
|
+
if (_records.length > this._maxRequest) {
|
|
909
|
+
throw new Error('Exceeded max limit of concurrent call');
|
|
910
|
+
}
|
|
911
|
+
const results = await Promise.all(_records.map((record) => {
|
|
912
|
+
const { [extIdField]: extId, type: rtype, attributes, ...rec } = record;
|
|
913
|
+
const url = [this._baseUrl(), 'sobjects', type, extIdField, extId].join('/');
|
|
914
|
+
return this.request({
|
|
915
|
+
method: 'PATCH',
|
|
916
|
+
url,
|
|
917
|
+
body: JSON.stringify(rec),
|
|
918
|
+
headers: {
|
|
919
|
+
...(options.headers || {}),
|
|
920
|
+
'content-type': 'application/json',
|
|
921
|
+
},
|
|
922
|
+
}, {
|
|
923
|
+
noContentResponse: { success: true, errors: [] },
|
|
924
|
+
}).catch((err) => {
|
|
925
|
+
// Be aware that `allOrNone` option in upsert method
|
|
926
|
+
// will not revert the other successful requests.
|
|
927
|
+
// It only raises error when met at least one failed request.
|
|
928
|
+
if (!isArray || options.allOrNone || !err.errorCode) {
|
|
929
|
+
throw err;
|
|
930
|
+
}
|
|
931
|
+
return toSaveResult(err);
|
|
932
|
+
});
|
|
933
|
+
}));
|
|
934
|
+
return isArray ? results : results[0];
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* @param type
|
|
938
|
+
* @param ids
|
|
939
|
+
* @param options
|
|
940
|
+
*/
|
|
941
|
+
async destroy(type, ids, options = {}) {
|
|
942
|
+
return Array.isArray(ids)
|
|
943
|
+
? // check the version whether SObject collection API is supported (42.0)
|
|
944
|
+
this._ensureVersion(42)
|
|
945
|
+
? this._destroyMany(type, ids, options)
|
|
946
|
+
: this._destroyParallel(type, ids, options)
|
|
947
|
+
: this._destroySingle(type, ids, options);
|
|
948
|
+
}
|
|
949
|
+
/** @private */
|
|
950
|
+
async _destroySingle(type, id, options) {
|
|
951
|
+
const url = [this._baseUrl(), 'sobjects', type, id].join('/');
|
|
952
|
+
return this.request({
|
|
953
|
+
method: 'DELETE',
|
|
954
|
+
url,
|
|
955
|
+
headers: options.headers || {},
|
|
956
|
+
}, {
|
|
957
|
+
noContentResponse: { id, success: true, errors: [] },
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
/** @private */
|
|
961
|
+
async _destroyParallel(type, ids, options) {
|
|
962
|
+
if (ids.length > this._maxRequest) {
|
|
963
|
+
throw new Error('Exceeded max limit of concurrent call');
|
|
964
|
+
}
|
|
965
|
+
return Promise.all(ids.map((id) => this._destroySingle(type, id, options).catch((err) => {
|
|
966
|
+
// Be aware that `allOrNone` option in parallel mode
|
|
967
|
+
// will not revert the other successful requests.
|
|
968
|
+
// It only raises error when met at least one failed request.
|
|
969
|
+
if (options.allOrNone || !err.errorCode) {
|
|
970
|
+
throw err;
|
|
971
|
+
}
|
|
972
|
+
return toSaveResult(err);
|
|
973
|
+
})));
|
|
974
|
+
}
|
|
975
|
+
/** @private */
|
|
976
|
+
async _destroyMany(type, ids, options) {
|
|
977
|
+
if (ids.length === 0) {
|
|
978
|
+
return [];
|
|
979
|
+
}
|
|
980
|
+
if (ids.length > MAX_DML_COUNT && options.allowRecursive) {
|
|
981
|
+
return [
|
|
982
|
+
...(await this._destroyMany(type, ids.slice(0, MAX_DML_COUNT), options)),
|
|
983
|
+
...(await this._destroyMany(type, ids.slice(MAX_DML_COUNT), options)),
|
|
984
|
+
];
|
|
985
|
+
}
|
|
986
|
+
let url = [this._baseUrl(), 'composite', 'sobjects?ids='].join('/') + ids.join(',');
|
|
987
|
+
if (options.allOrNone) {
|
|
988
|
+
url += '&allOrNone=true';
|
|
989
|
+
}
|
|
990
|
+
return this.request({
|
|
991
|
+
method: 'DELETE',
|
|
992
|
+
url,
|
|
993
|
+
headers: options.headers || {},
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* Synonym of Connection#destroy()
|
|
998
|
+
*/
|
|
999
|
+
delete = this.destroy;
|
|
1000
|
+
/**
|
|
1001
|
+
* Synonym of Connection#destroy()
|
|
1002
|
+
*/
|
|
1003
|
+
del = this.destroy;
|
|
1004
|
+
/**
|
|
1005
|
+
* Describe SObject metadata
|
|
1006
|
+
*/
|
|
1007
|
+
async describe(type) {
|
|
1008
|
+
const url = [this._baseUrl(), 'sobjects', type, 'describe'].join('/');
|
|
1009
|
+
const body = await this.request(url);
|
|
1010
|
+
return body;
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Describe global SObjects
|
|
1014
|
+
*/
|
|
1015
|
+
async describeGlobal() {
|
|
1016
|
+
const url = `${this._baseUrl()}/sobjects`;
|
|
1017
|
+
const body = await this.request(url);
|
|
1018
|
+
return body;
|
|
1019
|
+
}
|
|
1020
|
+
sobject(type) {
|
|
1021
|
+
const so = this.sobjects[type] ||
|
|
1022
|
+
new sobject_1.default(this, type);
|
|
1023
|
+
this.sobjects[type] = so;
|
|
1024
|
+
return so;
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* Get identity information of current user
|
|
1028
|
+
*/
|
|
1029
|
+
async identity(options = {}) {
|
|
1030
|
+
let url = this.userInfo && this.userInfo.url;
|
|
1031
|
+
if (!url) {
|
|
1032
|
+
const res = await this.request({
|
|
1033
|
+
method: 'GET',
|
|
1034
|
+
url: this._baseUrl(),
|
|
1035
|
+
headers: options.headers,
|
|
1036
|
+
});
|
|
1037
|
+
url = res.identity;
|
|
1038
|
+
}
|
|
1039
|
+
url += '?format=json';
|
|
1040
|
+
if (this.accessToken) {
|
|
1041
|
+
url += `&oauth_token=${encodeURIComponent(this.accessToken)}`;
|
|
1042
|
+
}
|
|
1043
|
+
const res = await this.request({ method: 'GET', url });
|
|
1044
|
+
this.userInfo = {
|
|
1045
|
+
id: res.user_id,
|
|
1046
|
+
organizationId: res.organization_id,
|
|
1047
|
+
url: res.id,
|
|
1048
|
+
};
|
|
1049
|
+
return res;
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* List recently viewed records
|
|
1053
|
+
*/
|
|
1054
|
+
async recent(type, limit) {
|
|
1055
|
+
/* eslint-disable no-param-reassign */
|
|
1056
|
+
if (typeof type === 'number') {
|
|
1057
|
+
limit = type;
|
|
1058
|
+
type = undefined;
|
|
1059
|
+
}
|
|
1060
|
+
let url;
|
|
1061
|
+
if (type) {
|
|
1062
|
+
url = [this._baseUrl(), 'sobjects', type].join('/');
|
|
1063
|
+
const { recentItems } = await this.request(url);
|
|
1064
|
+
return limit ? recentItems.slice(0, limit) : recentItems;
|
|
1065
|
+
}
|
|
1066
|
+
url = `${this._baseUrl()}/recent`;
|
|
1067
|
+
if (limit) {
|
|
1068
|
+
url += `?limit=${limit}`;
|
|
1069
|
+
}
|
|
1070
|
+
return this.request(url);
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Retrieve updated records
|
|
1074
|
+
*/
|
|
1075
|
+
async updated(type, start, end) {
|
|
1076
|
+
/* eslint-disable no-param-reassign */
|
|
1077
|
+
let url = [this._baseUrl(), 'sobjects', type, 'updated'].join('/');
|
|
1078
|
+
if (typeof start === 'string') {
|
|
1079
|
+
start = new Date(start);
|
|
1080
|
+
}
|
|
1081
|
+
start = (0, formatter_1.formatDate)(start);
|
|
1082
|
+
url += `?start=${encodeURIComponent(start)}`;
|
|
1083
|
+
if (typeof end === 'string') {
|
|
1084
|
+
end = new Date(end);
|
|
1085
|
+
}
|
|
1086
|
+
end = (0, formatter_1.formatDate)(end);
|
|
1087
|
+
url += `&end=${encodeURIComponent(end)}`;
|
|
1088
|
+
const body = await this.request(url);
|
|
1089
|
+
return body;
|
|
1090
|
+
}
|
|
1091
|
+
/**
|
|
1092
|
+
* Retrieve deleted records
|
|
1093
|
+
*/
|
|
1094
|
+
async deleted(type, start, end) {
|
|
1095
|
+
/* eslint-disable no-param-reassign */
|
|
1096
|
+
let url = [this._baseUrl(), 'sobjects', type, 'deleted'].join('/');
|
|
1097
|
+
if (typeof start === 'string') {
|
|
1098
|
+
start = new Date(start);
|
|
1099
|
+
}
|
|
1100
|
+
start = (0, formatter_1.formatDate)(start);
|
|
1101
|
+
url += `?start=${encodeURIComponent(start)}`;
|
|
1102
|
+
if (typeof end === 'string') {
|
|
1103
|
+
end = new Date(end);
|
|
1104
|
+
}
|
|
1105
|
+
end = (0, formatter_1.formatDate)(end);
|
|
1106
|
+
url += `&end=${encodeURIComponent(end)}`;
|
|
1107
|
+
const body = await this.request(url);
|
|
1108
|
+
return body;
|
|
1109
|
+
}
|
|
1110
|
+
/**
|
|
1111
|
+
* Returns a list of all tabs
|
|
1112
|
+
*/
|
|
1113
|
+
async tabs() {
|
|
1114
|
+
const url = [this._baseUrl(), 'tabs'].join('/');
|
|
1115
|
+
const body = await this.request(url);
|
|
1116
|
+
return body;
|
|
1117
|
+
}
|
|
1118
|
+
/**
|
|
1119
|
+
* Returns current system limit in the organization
|
|
1120
|
+
*/
|
|
1121
|
+
async limits() {
|
|
1122
|
+
const url = [this._baseUrl(), 'limits'].join('/');
|
|
1123
|
+
const body = await this.request(url);
|
|
1124
|
+
return body;
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Returns a theme info
|
|
1128
|
+
*/
|
|
1129
|
+
async theme() {
|
|
1130
|
+
const url = [this._baseUrl(), 'theme'].join('/');
|
|
1131
|
+
const body = await this.request(url);
|
|
1132
|
+
return body;
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* Returns all registered global quick actions
|
|
1136
|
+
*/
|
|
1137
|
+
async quickActions() {
|
|
1138
|
+
const body = await this.request('/quickActions');
|
|
1139
|
+
return body;
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Get reference for specified global quick action
|
|
1143
|
+
*/
|
|
1144
|
+
quickAction(actionName) {
|
|
1145
|
+
return new quick_action_1.default(this, `/quickActions/${actionName}`);
|
|
1146
|
+
}
|
|
1147
|
+
/**
|
|
1148
|
+
* Module which manages process rules and approval processes
|
|
1149
|
+
*/
|
|
1150
|
+
process = new process_1.default(this);
|
|
1151
|
+
}
|
|
1152
|
+
exports.Connection = Connection;
|
|
1153
|
+
exports.default = Connection;
|