@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
package/lib/query.js
ADDED
|
@@ -0,0 +1,817 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.SubQuery = exports.Query = exports.ResponseTargets = void 0;
|
|
27
|
+
/**
|
|
28
|
+
* @file Manages query for records in Salesforce
|
|
29
|
+
* @author Shinichi Tomita <shinichi.tomita@gmail.com>
|
|
30
|
+
*/
|
|
31
|
+
const events_1 = require("events");
|
|
32
|
+
const logger_1 = require("./util/logger");
|
|
33
|
+
const record_stream_1 = __importStar(require("./record-stream"));
|
|
34
|
+
const soql_builder_1 = require("./soql-builder");
|
|
35
|
+
const ResponseTargetValues = [
|
|
36
|
+
'QueryResult',
|
|
37
|
+
'Records',
|
|
38
|
+
'SingleRecord',
|
|
39
|
+
'Count',
|
|
40
|
+
];
|
|
41
|
+
exports.ResponseTargets = ResponseTargetValues.reduce((values, target) => ({ ...values, [target]: target }), {});
|
|
42
|
+
/**
|
|
43
|
+
*
|
|
44
|
+
*/
|
|
45
|
+
const DEFAULT_BULK_THRESHOLD = 200;
|
|
46
|
+
const DEFAULT_BULK_API_VERSION = 1;
|
|
47
|
+
/**
|
|
48
|
+
* Query
|
|
49
|
+
*/
|
|
50
|
+
class Query extends events_1.EventEmitter {
|
|
51
|
+
static _logger = (0, logger_1.getLogger)('query');
|
|
52
|
+
_conn;
|
|
53
|
+
_logger;
|
|
54
|
+
_soql;
|
|
55
|
+
_locator;
|
|
56
|
+
_config = {};
|
|
57
|
+
_children = [];
|
|
58
|
+
_options;
|
|
59
|
+
_executed = false;
|
|
60
|
+
_finished = false;
|
|
61
|
+
_chaining = false;
|
|
62
|
+
_promise;
|
|
63
|
+
_stream;
|
|
64
|
+
totalSize = 0;
|
|
65
|
+
totalFetched = 0;
|
|
66
|
+
records = [];
|
|
67
|
+
/**
|
|
68
|
+
*
|
|
69
|
+
*/
|
|
70
|
+
constructor(conn, config, options) {
|
|
71
|
+
super();
|
|
72
|
+
this._conn = conn;
|
|
73
|
+
this._logger = conn._logLevel
|
|
74
|
+
? Query._logger.createInstance(conn._logLevel)
|
|
75
|
+
: Query._logger;
|
|
76
|
+
if (typeof config === 'string') {
|
|
77
|
+
this._soql = config;
|
|
78
|
+
this._logger.debug(`config is soql: ${config}`);
|
|
79
|
+
}
|
|
80
|
+
else if (typeof config.locator === 'string') {
|
|
81
|
+
const locator = config.locator;
|
|
82
|
+
this._logger.debug(`config is locator: ${locator}`);
|
|
83
|
+
this._locator = locator.includes('/')
|
|
84
|
+
? this.urlToLocator(locator)
|
|
85
|
+
: locator;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
this._logger.debug(`config is QueryConfig: ${config}`);
|
|
89
|
+
const { fields, includes, sort, ..._config } = config;
|
|
90
|
+
this._config = _config;
|
|
91
|
+
this.select(fields);
|
|
92
|
+
if (includes) {
|
|
93
|
+
this.includeChildren(includes);
|
|
94
|
+
}
|
|
95
|
+
if (sort) {
|
|
96
|
+
this.sort(sort);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
this._options = {
|
|
100
|
+
headers: {},
|
|
101
|
+
maxFetch: 10000,
|
|
102
|
+
autoFetch: false,
|
|
103
|
+
scanAll: false,
|
|
104
|
+
responseTarget: 'QueryResult',
|
|
105
|
+
...(options || {}),
|
|
106
|
+
};
|
|
107
|
+
// promise instance
|
|
108
|
+
this._promise = new Promise((resolve, reject) => {
|
|
109
|
+
this.on('response', resolve);
|
|
110
|
+
this.on('error', reject);
|
|
111
|
+
});
|
|
112
|
+
this._stream = new record_stream_1.Serializable();
|
|
113
|
+
this.on('record', (record) => this._stream.push(record));
|
|
114
|
+
this.on('end', () => this._stream.push(null));
|
|
115
|
+
this.on('error', (err) => {
|
|
116
|
+
try {
|
|
117
|
+
this._stream.emit('error', err);
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
// eslint-disable-line no-empty
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Select fields to include in the returning result
|
|
126
|
+
*/
|
|
127
|
+
select(fields = '*') {
|
|
128
|
+
if (this._soql) {
|
|
129
|
+
throw Error('Cannot set select fields for the query which has already built SOQL.');
|
|
130
|
+
}
|
|
131
|
+
function toFieldArray(fields) {
|
|
132
|
+
return typeof fields === 'string'
|
|
133
|
+
? fields.split(/\s*,\s*/)
|
|
134
|
+
: Array.isArray(fields)
|
|
135
|
+
? fields
|
|
136
|
+
.map(toFieldArray)
|
|
137
|
+
.reduce((fs, f) => [...fs, ...f], [])
|
|
138
|
+
: Object.entries(fields)
|
|
139
|
+
.map(([f, v]) => {
|
|
140
|
+
if (typeof v === 'number' || typeof v === 'boolean') {
|
|
141
|
+
return v ? [f] : [];
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
return toFieldArray(v).map((p) => `${f}.${p}`);
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
.reduce((fs, f) => [...fs, ...f], []);
|
|
148
|
+
}
|
|
149
|
+
if (fields) {
|
|
150
|
+
this._config.fields = toFieldArray(fields);
|
|
151
|
+
}
|
|
152
|
+
// force convert query record type without changing instance;
|
|
153
|
+
return this;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Set query conditions to filter the result records
|
|
157
|
+
*/
|
|
158
|
+
where(conditions) {
|
|
159
|
+
if (this._soql) {
|
|
160
|
+
throw Error('Cannot set where conditions for the query which has already built SOQL.');
|
|
161
|
+
}
|
|
162
|
+
this._config.conditions = conditions;
|
|
163
|
+
return this;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Limit the returning result
|
|
167
|
+
*/
|
|
168
|
+
limit(limit) {
|
|
169
|
+
if (this._soql) {
|
|
170
|
+
throw Error('Cannot set limit for the query which has already built SOQL.');
|
|
171
|
+
}
|
|
172
|
+
this._config.limit = limit;
|
|
173
|
+
return this;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Skip records
|
|
177
|
+
*/
|
|
178
|
+
skip(offset) {
|
|
179
|
+
if (this._soql) {
|
|
180
|
+
throw Error('Cannot set skip/offset for the query which has already built SOQL.');
|
|
181
|
+
}
|
|
182
|
+
this._config.offset = offset;
|
|
183
|
+
return this;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Synonym of Query#skip()
|
|
187
|
+
*/
|
|
188
|
+
offset = this.skip;
|
|
189
|
+
sort(sort, dir) {
|
|
190
|
+
if (this._soql) {
|
|
191
|
+
throw Error('Cannot set sort for the query which has already built SOQL.');
|
|
192
|
+
}
|
|
193
|
+
if (typeof sort === 'string' && typeof dir !== 'undefined') {
|
|
194
|
+
this._config.sort = [[sort, dir]];
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
this._config.sort = sort;
|
|
198
|
+
}
|
|
199
|
+
return this;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Synonym of Query#sort()
|
|
203
|
+
*/
|
|
204
|
+
orderby = this.sort;
|
|
205
|
+
include(childRelName, conditions, fields, options = {}) {
|
|
206
|
+
if (this._soql) {
|
|
207
|
+
throw Error('Cannot include child relationship into the query which has already built SOQL.');
|
|
208
|
+
}
|
|
209
|
+
const childConfig = {
|
|
210
|
+
fields: fields === null ? undefined : fields,
|
|
211
|
+
table: childRelName,
|
|
212
|
+
conditions: conditions === null ? undefined : conditions,
|
|
213
|
+
limit: options.limit,
|
|
214
|
+
offset: options.offset,
|
|
215
|
+
sort: options.sort,
|
|
216
|
+
};
|
|
217
|
+
// eslint-disable-next-line no-use-before-define
|
|
218
|
+
const childQuery = new SubQuery(this._conn, childRelName, childConfig, this);
|
|
219
|
+
this._children.push(childQuery);
|
|
220
|
+
return childQuery;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Include child relationship queries, but not moving down to the children context
|
|
224
|
+
*/
|
|
225
|
+
includeChildren(includes) {
|
|
226
|
+
if (this._soql) {
|
|
227
|
+
throw Error('Cannot include child relationship into the query which has already built SOQL.');
|
|
228
|
+
}
|
|
229
|
+
for (const crname of Object.keys(includes)) {
|
|
230
|
+
const { conditions, fields, ...options } = includes[crname];
|
|
231
|
+
this.include(crname, conditions, fields, options);
|
|
232
|
+
}
|
|
233
|
+
return this;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Setting maxFetch query option
|
|
237
|
+
*/
|
|
238
|
+
maxFetch(maxFetch) {
|
|
239
|
+
this._options.maxFetch = maxFetch;
|
|
240
|
+
return this;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Switching auto fetch mode
|
|
244
|
+
*/
|
|
245
|
+
autoFetch(autoFetch) {
|
|
246
|
+
this._options.autoFetch = autoFetch;
|
|
247
|
+
return this;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Set flag to scan all records including deleted and archived.
|
|
251
|
+
*/
|
|
252
|
+
scanAll(scanAll) {
|
|
253
|
+
this._options.scanAll = scanAll;
|
|
254
|
+
return this;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
*
|
|
258
|
+
*/
|
|
259
|
+
setResponseTarget(responseTarget) {
|
|
260
|
+
if (responseTarget in exports.ResponseTargets) {
|
|
261
|
+
this._options.responseTarget = responseTarget;
|
|
262
|
+
}
|
|
263
|
+
// force change query response target without changing instance
|
|
264
|
+
return this;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Execute query and fetch records from server.
|
|
268
|
+
*/
|
|
269
|
+
execute(options_ = {}) {
|
|
270
|
+
if (this._executed) {
|
|
271
|
+
throw new Error('re-executing already executed query');
|
|
272
|
+
}
|
|
273
|
+
if (this._finished) {
|
|
274
|
+
throw new Error('executing already closed query');
|
|
275
|
+
}
|
|
276
|
+
const options = {
|
|
277
|
+
headers: options_.headers || this._options.headers,
|
|
278
|
+
responseTarget: options_.responseTarget || this._options.responseTarget,
|
|
279
|
+
autoFetch: options_.autoFetch || this._options.autoFetch,
|
|
280
|
+
maxFetch: options_.maxFetch || this._options.maxFetch,
|
|
281
|
+
scanAll: options_.scanAll || this._options.scanAll,
|
|
282
|
+
};
|
|
283
|
+
// collect fetched records in array
|
|
284
|
+
// only when response target is Records and
|
|
285
|
+
// either callback or chaining promises are available to this query.
|
|
286
|
+
this.once('fetch', () => {
|
|
287
|
+
if (options.responseTarget === exports.ResponseTargets.Records &&
|
|
288
|
+
this._chaining) {
|
|
289
|
+
this._logger.debug('--- collecting all fetched records ---');
|
|
290
|
+
const records = [];
|
|
291
|
+
const onRecord = (record) => records.push(record);
|
|
292
|
+
this.on('record', onRecord);
|
|
293
|
+
this.once('end', () => {
|
|
294
|
+
this.removeListener('record', onRecord);
|
|
295
|
+
this.emit('response', records, this);
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
// flag to prevent re-execution
|
|
300
|
+
this._executed = true;
|
|
301
|
+
(async () => {
|
|
302
|
+
// start actual query
|
|
303
|
+
this._logger.debug('>>> Query start >>>');
|
|
304
|
+
try {
|
|
305
|
+
await this._execute(options);
|
|
306
|
+
this._logger.debug('*** Query finished ***');
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
this._logger.debug('--- Query error ---', error);
|
|
310
|
+
this.emit('error', error);
|
|
311
|
+
}
|
|
312
|
+
})();
|
|
313
|
+
// return Query instance for chaining
|
|
314
|
+
return this;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Synonym of Query#execute()
|
|
318
|
+
*/
|
|
319
|
+
exec = this.execute;
|
|
320
|
+
/**
|
|
321
|
+
* Synonym of Query#execute()
|
|
322
|
+
*/
|
|
323
|
+
run = this.execute;
|
|
324
|
+
locatorToUrl() {
|
|
325
|
+
return this._locator
|
|
326
|
+
? [this._conn._baseUrl(), '/query/', this._locator].join('')
|
|
327
|
+
: '';
|
|
328
|
+
}
|
|
329
|
+
urlToLocator(url) {
|
|
330
|
+
return url.split('/').pop();
|
|
331
|
+
}
|
|
332
|
+
constructResponse(rawDone, responseTarget) {
|
|
333
|
+
switch (responseTarget) {
|
|
334
|
+
case 'Count':
|
|
335
|
+
return this.totalSize;
|
|
336
|
+
case 'SingleRecord':
|
|
337
|
+
return this.records?.[0] ?? null;
|
|
338
|
+
case 'Records':
|
|
339
|
+
return this.records;
|
|
340
|
+
// QueryResult is default response target
|
|
341
|
+
default:
|
|
342
|
+
return {
|
|
343
|
+
...{
|
|
344
|
+
records: this.records,
|
|
345
|
+
totalSize: this.totalSize,
|
|
346
|
+
done: rawDone ?? true, // when no records, done is omitted
|
|
347
|
+
},
|
|
348
|
+
...(this._locator ? { nextRecordsUrl: this.locatorToUrl() } : {}),
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* @private
|
|
354
|
+
*/
|
|
355
|
+
async _execute(options) {
|
|
356
|
+
const { headers, responseTarget, autoFetch, maxFetch, scanAll } = options;
|
|
357
|
+
this._logger.debug('execute with options', options);
|
|
358
|
+
let url;
|
|
359
|
+
if (this._locator) {
|
|
360
|
+
url = this.locatorToUrl();
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
const soql = await this.toSOQL();
|
|
364
|
+
this._logger.debug(`SOQL = ${soql}`);
|
|
365
|
+
url = [
|
|
366
|
+
this._conn._baseUrl(),
|
|
367
|
+
'/',
|
|
368
|
+
scanAll ? 'queryAll' : 'query',
|
|
369
|
+
'?q=',
|
|
370
|
+
encodeURIComponent(soql),
|
|
371
|
+
].join('');
|
|
372
|
+
}
|
|
373
|
+
const data = await this._conn.request({ method: 'GET', url, headers });
|
|
374
|
+
this.emit('fetch');
|
|
375
|
+
this.totalSize = data.totalSize;
|
|
376
|
+
this.records = this.records?.concat(maxFetch - this.records.length > data.records.length
|
|
377
|
+
? data.records
|
|
378
|
+
: data.records.slice(0, maxFetch - this.records.length));
|
|
379
|
+
this._locator = data.nextRecordsUrl
|
|
380
|
+
? this.urlToLocator(data.nextRecordsUrl)
|
|
381
|
+
: undefined;
|
|
382
|
+
this._finished =
|
|
383
|
+
this._finished ||
|
|
384
|
+
data.done ||
|
|
385
|
+
!autoFetch ||
|
|
386
|
+
// this is what the response looks like when there are no results
|
|
387
|
+
(data.records.length === 0 && data.done === undefined);
|
|
388
|
+
// streaming record instances
|
|
389
|
+
const numRecords = data.records?.length ?? 0;
|
|
390
|
+
let totalFetched = this.totalFetched;
|
|
391
|
+
for (let i = 0; i < numRecords; i++) {
|
|
392
|
+
if (totalFetched >= maxFetch) {
|
|
393
|
+
this._finished = true;
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
const record = data.records[i];
|
|
397
|
+
this.emit('record', record, totalFetched, this);
|
|
398
|
+
totalFetched += 1;
|
|
399
|
+
}
|
|
400
|
+
this.totalFetched = totalFetched;
|
|
401
|
+
if (this._finished) {
|
|
402
|
+
const response = this.constructResponse(data.done, responseTarget);
|
|
403
|
+
// only fire response event when it should be notified per fetch
|
|
404
|
+
if (responseTarget !== exports.ResponseTargets.Records) {
|
|
405
|
+
this.emit('response', response, this);
|
|
406
|
+
}
|
|
407
|
+
this.emit('end');
|
|
408
|
+
return response;
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
return this._execute(options);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
stream(type = 'csv') {
|
|
415
|
+
if (!this._finished && !this._executed) {
|
|
416
|
+
this.execute({ autoFetch: true });
|
|
417
|
+
}
|
|
418
|
+
return type === 'record' ? this._stream : this._stream.stream(type);
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Pipe the queried records to another stream
|
|
422
|
+
* This is for backward compatibility; Query is not a record stream instance anymore in 2.0.
|
|
423
|
+
* If you want a record stream instance, use `Query#stream('record')`.
|
|
424
|
+
*/
|
|
425
|
+
pipe(stream) {
|
|
426
|
+
return this.stream('record').pipe(stream);
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* @protected
|
|
430
|
+
*/
|
|
431
|
+
async _expandFields(sobject_) {
|
|
432
|
+
if (this._soql) {
|
|
433
|
+
throw new Error('Cannot expand fields for the query which has already built SOQL.');
|
|
434
|
+
}
|
|
435
|
+
const { fields = [], table = '' } = this._config;
|
|
436
|
+
const sobject = sobject_ || table;
|
|
437
|
+
this._logger.debug(`_expandFields: sobject = ${sobject}, fields = ${fields.join(', ')}`);
|
|
438
|
+
const [efields] = await Promise.all([
|
|
439
|
+
this._expandAsteriskFields(sobject, fields),
|
|
440
|
+
...this._children.map(async (childQuery) => {
|
|
441
|
+
await childQuery._expandFields();
|
|
442
|
+
return [];
|
|
443
|
+
}),
|
|
444
|
+
]);
|
|
445
|
+
this._config.fields = efields;
|
|
446
|
+
this._config.includes = this._children
|
|
447
|
+
.map((cquery) => {
|
|
448
|
+
const cconfig = cquery._query._config;
|
|
449
|
+
return [cconfig.table, cconfig];
|
|
450
|
+
})
|
|
451
|
+
.reduce((includes, [ctable, cconfig]) => ({
|
|
452
|
+
...includes,
|
|
453
|
+
[ctable]: cconfig,
|
|
454
|
+
}), {});
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
*
|
|
458
|
+
*/
|
|
459
|
+
async _findRelationObject(relName) {
|
|
460
|
+
const table = this._config.table;
|
|
461
|
+
if (!table) {
|
|
462
|
+
throw new Error('No table information provided in the query');
|
|
463
|
+
}
|
|
464
|
+
this._logger.debug(`finding table for relation "${relName}" in "${table}"...`);
|
|
465
|
+
const sobject = await this._conn.describe$(table);
|
|
466
|
+
const upperRname = relName.toUpperCase();
|
|
467
|
+
for (const cr of sobject.childRelationships) {
|
|
468
|
+
if ((cr.relationshipName || '').toUpperCase() === upperRname &&
|
|
469
|
+
cr.childSObject) {
|
|
470
|
+
return cr.childSObject;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
throw new Error(`No child relationship found: ${relName}`);
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
*
|
|
477
|
+
*/
|
|
478
|
+
async _expandAsteriskFields(sobject, fields) {
|
|
479
|
+
const expandedFields = await Promise.all(fields.map(async (field) => this._expandAsteriskField(sobject, field)));
|
|
480
|
+
return expandedFields.reduce((eflds, flds) => [...eflds, ...flds], []);
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
*
|
|
484
|
+
*/
|
|
485
|
+
async _expandAsteriskField(sobject, field) {
|
|
486
|
+
this._logger.debug(`expanding field "${field}" in "${sobject}"...`);
|
|
487
|
+
const fpath = field.split('.');
|
|
488
|
+
if (fpath[fpath.length - 1] === '*') {
|
|
489
|
+
const so = await this._conn.describe$(sobject);
|
|
490
|
+
this._logger.debug(`table ${sobject} has been described`);
|
|
491
|
+
if (fpath.length > 1) {
|
|
492
|
+
const rname = fpath.shift();
|
|
493
|
+
for (const f of so.fields) {
|
|
494
|
+
if (f.relationshipName &&
|
|
495
|
+
rname &&
|
|
496
|
+
f.relationshipName.toUpperCase() === rname.toUpperCase()) {
|
|
497
|
+
const rfield = f;
|
|
498
|
+
const referenceTo = rfield.referenceTo || [];
|
|
499
|
+
const rtable = referenceTo.length === 1 ? referenceTo[0] : 'Name';
|
|
500
|
+
const fpaths = await this._expandAsteriskField(rtable, fpath.join('.'));
|
|
501
|
+
return fpaths.map((fp) => `${rname}.${fp}`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return [];
|
|
505
|
+
}
|
|
506
|
+
return so.fields.map((f) => f.name);
|
|
507
|
+
}
|
|
508
|
+
return [field];
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Explain plan for executing query
|
|
512
|
+
*/
|
|
513
|
+
async explain() {
|
|
514
|
+
const soql = await this.toSOQL();
|
|
515
|
+
this._logger.debug(`SOQL = ${soql}`);
|
|
516
|
+
const url = `/query/?explain=${encodeURIComponent(soql)}`;
|
|
517
|
+
return this._conn.request(url);
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Return SOQL expression for the query
|
|
521
|
+
*/
|
|
522
|
+
async toSOQL() {
|
|
523
|
+
if (this._soql) {
|
|
524
|
+
return this._soql;
|
|
525
|
+
}
|
|
526
|
+
await this._expandFields();
|
|
527
|
+
return (0, soql_builder_1.createSOQL)(this._config);
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Promise/A+ interface
|
|
531
|
+
* http://promises-aplus.github.io/promises-spec/
|
|
532
|
+
*
|
|
533
|
+
* Delegate to deferred promise, return promise instance for query result
|
|
534
|
+
*/
|
|
535
|
+
then(onResolve, onReject) {
|
|
536
|
+
this._chaining = true;
|
|
537
|
+
if (!this._finished && !this._executed) {
|
|
538
|
+
this.execute();
|
|
539
|
+
}
|
|
540
|
+
if (!this._promise) {
|
|
541
|
+
throw new Error('invalid state: promise is not set after query execution');
|
|
542
|
+
}
|
|
543
|
+
return this._promise.then(onResolve, onReject);
|
|
544
|
+
}
|
|
545
|
+
catch(onReject) {
|
|
546
|
+
return this.then(null, onReject);
|
|
547
|
+
}
|
|
548
|
+
promise() {
|
|
549
|
+
// TODO(cristian): verify this is correct
|
|
550
|
+
return Promise.resolve(this);
|
|
551
|
+
}
|
|
552
|
+
destroy(type, options) {
|
|
553
|
+
if (typeof type === 'object' && type !== null) {
|
|
554
|
+
options = type;
|
|
555
|
+
type = undefined;
|
|
556
|
+
}
|
|
557
|
+
options = options || {};
|
|
558
|
+
const type_ = type || this._config.table;
|
|
559
|
+
if (!type_) {
|
|
560
|
+
throw new Error('SOQL based query needs SObject type information to bulk delete.');
|
|
561
|
+
}
|
|
562
|
+
// Set the threshold number to pass to bulk API
|
|
563
|
+
const thresholdNum = options.allowBulk === false
|
|
564
|
+
? -1
|
|
565
|
+
: typeof options.bulkThreshold === 'number'
|
|
566
|
+
? options.bulkThreshold
|
|
567
|
+
: // determine threshold if the connection version supports SObject collection API or not
|
|
568
|
+
this._conn._ensureVersion(42)
|
|
569
|
+
? DEFAULT_BULK_THRESHOLD
|
|
570
|
+
: this._conn._maxRequest / 2;
|
|
571
|
+
const bulkApiVersion = options.bulkApiVersion ?? DEFAULT_BULK_API_VERSION;
|
|
572
|
+
return new Promise((resolve, reject) => {
|
|
573
|
+
const createBatch = () => this._conn
|
|
574
|
+
.sobject(type_)
|
|
575
|
+
.deleteBulk()
|
|
576
|
+
.on('response', resolve)
|
|
577
|
+
.on('error', reject);
|
|
578
|
+
let records = [];
|
|
579
|
+
let batch = null;
|
|
580
|
+
const handleRecord = (rec) => {
|
|
581
|
+
if (!rec.Id) {
|
|
582
|
+
const err = new Error('Queried record does not include Salesforce record ID.');
|
|
583
|
+
this.emit('error', err);
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
const record = { Id: rec.Id };
|
|
587
|
+
if (batch) {
|
|
588
|
+
batch.write(record);
|
|
589
|
+
}
|
|
590
|
+
else {
|
|
591
|
+
records.push(record);
|
|
592
|
+
if (thresholdNum >= 0 &&
|
|
593
|
+
records.length > thresholdNum &&
|
|
594
|
+
bulkApiVersion === 1) {
|
|
595
|
+
// Use bulk delete instead of SObject REST API
|
|
596
|
+
batch = createBatch();
|
|
597
|
+
for (const record of records) {
|
|
598
|
+
batch.write(record);
|
|
599
|
+
}
|
|
600
|
+
records = [];
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
const handleEnd = () => {
|
|
605
|
+
if (batch) {
|
|
606
|
+
batch.end();
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
const ids = records.map((record) => record.Id);
|
|
610
|
+
if (records.length > thresholdNum && bulkApiVersion === 2) {
|
|
611
|
+
this._conn.bulk2
|
|
612
|
+
.loadAndWaitForResults({
|
|
613
|
+
object: type_,
|
|
614
|
+
operation: 'delete',
|
|
615
|
+
input: records,
|
|
616
|
+
})
|
|
617
|
+
.then((allResults) => resolve(this.mapBulkV2ResultsToSaveResults(allResults)), reject);
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
this._conn
|
|
621
|
+
.sobject(type_)
|
|
622
|
+
.destroy(ids, { allowRecursive: true })
|
|
623
|
+
.then(resolve, reject);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
this.stream('record')
|
|
628
|
+
.on('data', handleRecord)
|
|
629
|
+
.on('end', handleEnd)
|
|
630
|
+
.on('error', reject);
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Synonym of Query#destroy()
|
|
635
|
+
*/
|
|
636
|
+
delete = this.destroy;
|
|
637
|
+
/**
|
|
638
|
+
* Synonym of Query#destroy()
|
|
639
|
+
*/
|
|
640
|
+
del = this.destroy;
|
|
641
|
+
update(mapping, type, options) {
|
|
642
|
+
if (typeof type === 'object' && type !== null) {
|
|
643
|
+
options = type;
|
|
644
|
+
type = undefined;
|
|
645
|
+
}
|
|
646
|
+
options = options || {};
|
|
647
|
+
const type_ = type || (this._config && this._config.table);
|
|
648
|
+
if (!type_) {
|
|
649
|
+
throw new Error('SOQL based query needs SObject type information to bulk update.');
|
|
650
|
+
}
|
|
651
|
+
const updateStream = typeof mapping === 'function'
|
|
652
|
+
? record_stream_1.default.map(mapping)
|
|
653
|
+
: record_stream_1.default.recordMapStream(mapping);
|
|
654
|
+
// Set the threshold number to pass to bulk API
|
|
655
|
+
const thresholdNum = options.allowBulk === false
|
|
656
|
+
? -1
|
|
657
|
+
: typeof options.bulkThreshold === 'number'
|
|
658
|
+
? options.bulkThreshold
|
|
659
|
+
: // determine threshold if the connection version supports SObject collection API or not
|
|
660
|
+
this._conn._ensureVersion(42)
|
|
661
|
+
? DEFAULT_BULK_THRESHOLD
|
|
662
|
+
: this._conn._maxRequest / 2;
|
|
663
|
+
const bulkApiVersion = options.bulkApiVersion ?? DEFAULT_BULK_API_VERSION;
|
|
664
|
+
return new Promise((resolve, reject) => {
|
|
665
|
+
const createBatch = () => this._conn
|
|
666
|
+
.sobject(type_)
|
|
667
|
+
.updateBulk()
|
|
668
|
+
.on('response', resolve)
|
|
669
|
+
.on('error', reject);
|
|
670
|
+
let records = [];
|
|
671
|
+
let batch = null;
|
|
672
|
+
const handleRecord = (record) => {
|
|
673
|
+
if (batch) {
|
|
674
|
+
batch.write(record);
|
|
675
|
+
}
|
|
676
|
+
else {
|
|
677
|
+
records.push(record);
|
|
678
|
+
}
|
|
679
|
+
if (thresholdNum >= 0 &&
|
|
680
|
+
records.length > thresholdNum &&
|
|
681
|
+
bulkApiVersion === 1) {
|
|
682
|
+
// Use bulk update instead of SObject REST API
|
|
683
|
+
batch = createBatch();
|
|
684
|
+
for (const record of records) {
|
|
685
|
+
batch.write(record);
|
|
686
|
+
}
|
|
687
|
+
records = [];
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
const handleEnd = () => {
|
|
691
|
+
if (batch) {
|
|
692
|
+
batch.end();
|
|
693
|
+
}
|
|
694
|
+
else {
|
|
695
|
+
if (records.length > thresholdNum && bulkApiVersion === 2) {
|
|
696
|
+
this._conn.bulk2
|
|
697
|
+
.loadAndWaitForResults({
|
|
698
|
+
object: type_,
|
|
699
|
+
operation: 'update',
|
|
700
|
+
input: records,
|
|
701
|
+
})
|
|
702
|
+
.then((allResults) => resolve(this.mapBulkV2ResultsToSaveResults(allResults)), reject);
|
|
703
|
+
}
|
|
704
|
+
else {
|
|
705
|
+
this._conn
|
|
706
|
+
.sobject(type_)
|
|
707
|
+
.update(records, { allowRecursive: true })
|
|
708
|
+
.then(resolve, reject);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
this.stream('record')
|
|
713
|
+
.on('error', reject)
|
|
714
|
+
.pipe(updateStream)
|
|
715
|
+
.on('data', handleRecord)
|
|
716
|
+
.on('end', handleEnd)
|
|
717
|
+
.on('error', reject);
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
mapBulkV2ResultsToSaveResults(bulkJobAllResults) {
|
|
721
|
+
const successSaveResults = bulkJobAllResults.successfulResults.map((r) => {
|
|
722
|
+
const saveResult = {
|
|
723
|
+
id: r.sf__Id,
|
|
724
|
+
success: true,
|
|
725
|
+
errors: [],
|
|
726
|
+
};
|
|
727
|
+
return saveResult;
|
|
728
|
+
});
|
|
729
|
+
const failedSaveResults = bulkJobAllResults.failedResults.map((r) => {
|
|
730
|
+
const saveResult = {
|
|
731
|
+
success: false,
|
|
732
|
+
errors: [
|
|
733
|
+
{
|
|
734
|
+
errorCode: r.sf__Error,
|
|
735
|
+
message: r.sf__Error,
|
|
736
|
+
},
|
|
737
|
+
],
|
|
738
|
+
};
|
|
739
|
+
return saveResult;
|
|
740
|
+
});
|
|
741
|
+
return [...successSaveResults, ...failedSaveResults];
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
exports.Query = Query;
|
|
745
|
+
/*--------------------------------------------*/
|
|
746
|
+
/**
|
|
747
|
+
* SubQuery object for representing child relationship query
|
|
748
|
+
*/
|
|
749
|
+
class SubQuery {
|
|
750
|
+
_relName;
|
|
751
|
+
_query;
|
|
752
|
+
_parent;
|
|
753
|
+
/**
|
|
754
|
+
*
|
|
755
|
+
*/
|
|
756
|
+
constructor(conn, relName, config, parent) {
|
|
757
|
+
this._relName = relName;
|
|
758
|
+
this._query = new Query(conn, config);
|
|
759
|
+
this._parent = parent;
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
*
|
|
763
|
+
*/
|
|
764
|
+
select(fields) {
|
|
765
|
+
// force convert query record type without changing instance
|
|
766
|
+
this._query = this._query.select(fields);
|
|
767
|
+
return this;
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
*
|
|
771
|
+
*/
|
|
772
|
+
where(conditions) {
|
|
773
|
+
this._query = this._query.where(conditions);
|
|
774
|
+
return this;
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Limit the returning result
|
|
778
|
+
*/
|
|
779
|
+
limit(limit) {
|
|
780
|
+
this._query = this._query.limit(limit);
|
|
781
|
+
return this;
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Skip records
|
|
785
|
+
*/
|
|
786
|
+
skip(offset) {
|
|
787
|
+
this._query = this._query.skip(offset);
|
|
788
|
+
return this;
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Synonym of SubQuery#skip()
|
|
792
|
+
*/
|
|
793
|
+
offset = this.skip;
|
|
794
|
+
sort(sort, dir) {
|
|
795
|
+
this._query = this._query.sort(sort, dir);
|
|
796
|
+
return this;
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Synonym of SubQuery#sort()
|
|
800
|
+
*/
|
|
801
|
+
orderby = this.sort;
|
|
802
|
+
/**
|
|
803
|
+
*
|
|
804
|
+
*/
|
|
805
|
+
async _expandFields() {
|
|
806
|
+
const sobject = await this._parent._findRelationObject(this._relName);
|
|
807
|
+
return this._query._expandFields(sobject);
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Back the context to parent query object
|
|
811
|
+
*/
|
|
812
|
+
end() {
|
|
813
|
+
return this._parent;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
exports.SubQuery = SubQuery;
|
|
817
|
+
exports.default = Query;
|