@salesforce/plugin-data 0.6.10 → 0.6.11
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/CHANGELOG.md +53 -26
- package/README.md +26 -20
- package/lib/api/data/tree/exportApi.d.ts +13 -4
- package/lib/api/data/tree/exportApi.js +137 -224
- package/lib/api/data/tree/exportApi.js.map +1 -1
- package/lib/api/data/tree/importApi.d.ts +2 -1
- package/lib/api/data/tree/importApi.js +75 -91
- package/lib/api/data/tree/importApi.js.map +1 -1
- package/lib/batcher.js.map +1 -1
- package/lib/commands/force/data/bulk/delete.js.map +1 -1
- package/lib/commands/force/data/bulk/status.js.map +1 -1
- package/lib/commands/force/data/bulk/upsert.js.map +1 -1
- package/lib/commands/force/data/record/create.js.map +1 -1
- package/lib/commands/force/data/record/delete.js.map +1 -1
- package/lib/commands/force/data/record/get.js.map +1 -1
- package/lib/commands/force/data/record/update.js.map +1 -1
- package/lib/commands/force/data/soql/query.js.map +1 -1
- package/lib/commands/force/data/tree/export.d.ts +2 -1
- package/lib/commands/force/data/tree/export.js.map +1 -1
- package/lib/dataCommand.js +0 -1
- package/lib/dataCommand.js.map +1 -1
- package/lib/dataSoqlQueryTypes.d.ts +38 -1
- package/lib/dataSoqlQueryTypes.js +10 -2
- package/lib/dataSoqlQueryTypes.js.map +1 -1
- package/lib/reporters.d.ts +3 -3
- package/lib/reporters.js +13 -18
- package/lib/reporters.js.map +1 -1
- package/oclif.manifest.json +1 -1
- package/package.json +8 -8
- package/lib/api/data/tree/executors.d.ts +0 -1
- package/lib/api/data/tree/executors.js +0 -19
- package/lib/api/data/tree/executors.js.map +0 -1
|
@@ -7,12 +7,9 @@
|
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.ExportApi = void 0;
|
|
10
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
11
|
-
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
12
10
|
const path = require("path");
|
|
13
11
|
const core_1 = require("@salesforce/core");
|
|
14
|
-
const
|
|
15
|
-
const executors_1 = require("./executors");
|
|
12
|
+
const dataSoqlQueryTypes_1 = require("../../../dataSoqlQueryTypes");
|
|
16
13
|
core_1.Messages.importMessagesDirectory(__dirname);
|
|
17
14
|
const messages = core_1.Messages.loadMessages('@salesforce/plugin-data', 'exportApi');
|
|
18
15
|
const DATA_PLAN_FILENAME_PART = '-plan.json';
|
|
@@ -25,8 +22,8 @@ class ExportApi {
|
|
|
25
22
|
this.org = org;
|
|
26
23
|
this.ux = ux;
|
|
27
24
|
this.objectTypeRegistry = {}; // registry for object type data plan descriptor
|
|
28
|
-
this.
|
|
29
|
-
this.typeRefIndexes =
|
|
25
|
+
this.refFromIdByType = new Map(); // refFromIdByType.get('account').get(someAccountId) => AccountRef1
|
|
26
|
+
this.typeRefIndexes = new Map(); // registry for object type-specific ref counters
|
|
30
27
|
this.logger = core_1.Logger.childFromRoot(this.constructor.name);
|
|
31
28
|
}
|
|
32
29
|
/**
|
|
@@ -47,7 +44,7 @@ class ExportApi {
|
|
|
47
44
|
queryResults = await this.org.getConnection().query(query);
|
|
48
45
|
}
|
|
49
46
|
catch (err) {
|
|
50
|
-
if (err.name === 'MALFORMED_QUERY') {
|
|
47
|
+
if (err instanceof Error && err.name === 'MALFORMED_QUERY') {
|
|
51
48
|
const errMsg = messages.getMessage('soqlMalformed');
|
|
52
49
|
const errMsgAction = messages.getMessage('soqlMalformedAction');
|
|
53
50
|
throw new core_1.SfdxError(errMsg, 'MalformedQuery', [errMsgAction]);
|
|
@@ -56,17 +53,14 @@ class ExportApi {
|
|
|
56
53
|
throw err;
|
|
57
54
|
}
|
|
58
55
|
}
|
|
59
|
-
const sobjectTree =
|
|
56
|
+
const sobjectTree = await this.processQueryResults(queryResults);
|
|
60
57
|
if (!((_a = sobjectTree.records) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
61
58
|
return sobjectTree;
|
|
62
59
|
}
|
|
63
60
|
if (plan) {
|
|
64
61
|
return this.generateDataPlan(sobjectTree);
|
|
65
62
|
}
|
|
66
|
-
|
|
67
|
-
const fileName = `${Object.keys(this.objectTypeRegistry).join('-')}.json`;
|
|
68
|
-
return this.writeDataFileSync(fileName, sobjectTree);
|
|
69
|
-
}
|
|
63
|
+
return this.writeDataFileSync(`${Object.keys(this.objectTypeRegistry).join('-')}.json`, sobjectTree);
|
|
70
64
|
}
|
|
71
65
|
// P R I V A T E M E T H O D S
|
|
72
66
|
/**
|
|
@@ -98,52 +92,47 @@ class ExportApi {
|
|
|
98
92
|
core_1.fs.mkdirpSync(outputDir);
|
|
99
93
|
}
|
|
100
94
|
catch (err) {
|
|
101
|
-
// It is ok if the directory already
|
|
102
|
-
|
|
103
|
-
if (error.name !== 'EEXIST') {
|
|
95
|
+
// It is ok if the directory already exists
|
|
96
|
+
if (err instanceof Error && err.name !== 'EEXIST') {
|
|
104
97
|
throw err;
|
|
105
98
|
}
|
|
106
99
|
}
|
|
107
100
|
}
|
|
108
101
|
// Process query results generating SObject Tree format
|
|
109
102
|
async processQueryResults(recordList) {
|
|
103
|
+
var _a;
|
|
110
104
|
await this.recordObjectTypes(recordList);
|
|
111
|
-
const
|
|
112
|
-
const processedRecordList = (await this.processRecordList(recordList));
|
|
105
|
+
const processedRecordList = await this.queryResultsToTree(recordList);
|
|
113
106
|
// log record count; warn if > 200 and !options.plan
|
|
114
|
-
const recordCount = (
|
|
115
|
-
this.logger.debug(messages.getMessage('dataExportRecordCount', [recordCount, query]));
|
|
116
|
-
if (recordCount > 200 && !plan) {
|
|
117
|
-
this.ux.warn(messages.getMessage('dataExportRecordCountWarning', [recordCount, query]));
|
|
107
|
+
const recordCount = (_a = processedRecordList.records.length) !== null && _a !== void 0 ? _a : 0;
|
|
108
|
+
this.logger.debug(messages.getMessage('dataExportRecordCount', [recordCount, this.config.query]));
|
|
109
|
+
if (recordCount > 200 && !this.config.plan) {
|
|
110
|
+
this.ux.warn(messages.getMessage('dataExportRecordCountWarning', [recordCount, this.config.query]));
|
|
118
111
|
}
|
|
119
112
|
return this.finalApplyRefs(processedRecordList.records);
|
|
120
113
|
}
|
|
121
|
-
|
|
114
|
+
/**
|
|
115
|
+
* Register object types and type hierarchy for plan generation
|
|
116
|
+
**/
|
|
122
117
|
async recordObjectTypes(recordList) {
|
|
123
118
|
const records = recordList.records;
|
|
124
119
|
if (!records.length) {
|
|
125
120
|
// TODO: should be on the command
|
|
126
121
|
this.ux.log('Query returned no results');
|
|
127
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
128
122
|
return recordList;
|
|
129
123
|
}
|
|
130
124
|
// top level object type
|
|
131
|
-
const topLevelType =
|
|
125
|
+
const topLevelType = records[0].attributes.type;
|
|
132
126
|
this.objectTypeRegistry[topLevelType] = {
|
|
133
127
|
order: 0,
|
|
134
128
|
type: topLevelType,
|
|
135
129
|
saveRefs: true,
|
|
136
130
|
resolveRefs: false, // pre-save, don't resolve relationship references to parent ids (from previous save)
|
|
137
131
|
};
|
|
138
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
139
132
|
records.forEach((record) => {
|
|
140
|
-
Object.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (value && (0, ts_types_1.getNumber)(value, 'records.length')) {
|
|
144
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
145
|
-
const firstRec = value.records[0];
|
|
146
|
-
const type = (0, ts_types_1.getString)(firstRec, 'attributes.type');
|
|
133
|
+
Object.entries(record).map(([key, value]) => {
|
|
134
|
+
if ((0, dataSoqlQueryTypes_1.hasNestedRecords)(value)) {
|
|
135
|
+
const type = value.records[0].attributes.type;
|
|
147
136
|
// found a related object, add to map
|
|
148
137
|
if (type && !this.objectTypeRegistry[type]) {
|
|
149
138
|
this.objectTypeRegistry[type] = {
|
|
@@ -159,22 +148,18 @@ class ExportApi {
|
|
|
159
148
|
});
|
|
160
149
|
// pre-load object metadata
|
|
161
150
|
const promises = Object.keys(this.objectTypeRegistry).map((key) => this.loadMetadata(key));
|
|
162
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
163
151
|
return Promise.all(promises).then(() => recordList);
|
|
164
152
|
}
|
|
165
|
-
async
|
|
153
|
+
async queryResultsToTree(recordList, parentRef) {
|
|
166
154
|
// holds transformed sobject tree
|
|
167
155
|
const sobjectTree = { records: [] };
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const recordFns = recordList.records.map((record) => processRecordsFn(record));
|
|
172
|
-
// TODO: do we really need sequentialExecute? Can't we just use Promise.all()?
|
|
173
|
-
await (0, executors_1.sequentialExecute)(recordFns);
|
|
156
|
+
for (const record of recordList.records) {
|
|
157
|
+
await this.processRecords(record, sobjectTree, parentRef);
|
|
158
|
+
}
|
|
174
159
|
this.logger.debug(JSON.stringify(sobjectTree, null, 4));
|
|
175
160
|
return sobjectTree;
|
|
176
161
|
}
|
|
177
|
-
async processRecords(
|
|
162
|
+
async processRecords(record, sobjectTree, parentRef) {
|
|
178
163
|
// incremented every time we visit another record
|
|
179
164
|
const objRefId = this.incrementTypeRefIndex(record.attributes.type);
|
|
180
165
|
// add the attributes for this record, setting the type and reference
|
|
@@ -189,191 +174,134 @@ class ExportApi {
|
|
|
189
174
|
// handle each record attribute
|
|
190
175
|
await this.processRecordAttributes(record, treeRecord, objRefId);
|
|
191
176
|
if (parentRef && this.config.plan) {
|
|
192
|
-
const parentFieldName =
|
|
177
|
+
const parentFieldName = parentRef.fieldName;
|
|
193
178
|
if (!treeRecord[parentFieldName]) {
|
|
194
179
|
treeRecord[parentFieldName] = parentRef.id;
|
|
195
180
|
}
|
|
196
181
|
}
|
|
197
182
|
// add record to tree
|
|
198
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
199
183
|
sobjectTree.records.push(treeRecord);
|
|
200
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
201
184
|
return sobjectTree;
|
|
202
185
|
}
|
|
203
186
|
// Generate object type reference (<ObjectType>Ref<Counter>)
|
|
204
187
|
incrementTypeRefIndex(type) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
return `${type}Ref${++this.typeRefIndexes[type]}`;
|
|
188
|
+
var _a;
|
|
189
|
+
this.typeRefIndexes.set(type, 1 + ((_a = this.typeRefIndexes.get(type)) !== null && _a !== void 0 ? _a : 0));
|
|
190
|
+
return `${type}Ref${this.typeRefIndexes.get(type)}`;
|
|
209
191
|
}
|
|
210
192
|
async processRecordAttributes(record, treeRecord, objRefId) {
|
|
211
193
|
const promises = Object.keys(record).map((key) => this.processRecordAttribute(record, key, treeRecord, objRefId));
|
|
212
|
-
|
|
213
|
-
return
|
|
194
|
+
await Promise.all(promises);
|
|
195
|
+
return treeRecord;
|
|
214
196
|
}
|
|
215
197
|
async processRecordAttribute(record, key, treeRecord, objRefId) {
|
|
216
|
-
|
|
217
|
-
|
|
198
|
+
var _a;
|
|
199
|
+
// skip attributes and id. Data import does not accept records with IDs.
|
|
200
|
+
if (key === 'attributes' || key === 'Id') {
|
|
201
|
+
// If this is an attributes section then we need to add an object reference
|
|
202
|
+
this.saveRecordRef(record, objRefId);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const metadata = await this.loadMetadata(record.attributes.type);
|
|
206
|
+
if (this.isQueryResult(metadata, key)) {
|
|
218
207
|
const field = record[key];
|
|
219
|
-
//
|
|
220
|
-
if (
|
|
221
|
-
//
|
|
222
|
-
|
|
208
|
+
// handle child records
|
|
209
|
+
if (!field) {
|
|
210
|
+
// The parent has no child records, so return an empty records array
|
|
211
|
+
treeRecord[key] = { records: [] };
|
|
212
|
+
return;
|
|
223
213
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
// The parent has no child records, so return an empty records array
|
|
230
|
-
return { records: [] };
|
|
231
|
-
}
|
|
232
|
-
// handle child records
|
|
233
|
-
return this.loadMetadata(field.records[0].attributes.type).then((childMetadata) => this.processRecordList(field, {
|
|
234
|
-
id: `@${objRefId}`,
|
|
235
|
-
fieldName: this.getRelationshipFieldName(childMetadata, record.attributes.type),
|
|
236
|
-
}));
|
|
237
|
-
}
|
|
238
|
-
else {
|
|
239
|
-
// see if this is a relationship field
|
|
240
|
-
if (this.isRelationshipWithMetadata(metadata, key)) {
|
|
241
|
-
// related to which field?
|
|
242
|
-
const relTo = this.getRelatedToWithMetadata(metadata, key);
|
|
243
|
-
if (this.config.plan) {
|
|
244
|
-
// find reference in record result
|
|
245
|
-
if (this.objectTypeRegistry[relTo]) {
|
|
246
|
-
// add ref to replace the value
|
|
247
|
-
const id = record[key];
|
|
248
|
-
const relatedObject = this.referenceRegistry[relTo];
|
|
249
|
-
if (relatedObject) {
|
|
250
|
-
const ref = relatedObject[id];
|
|
251
|
-
// If ref is not found, then leave intact because we may not have processed
|
|
252
|
-
// this parent fully. We'll go back through the sObject tree
|
|
253
|
-
// later and replace the id with a reference.
|
|
254
|
-
return ref ? `@${ref}` : id;
|
|
255
|
-
}
|
|
256
|
-
else {
|
|
257
|
-
// again, this will just be the id for now and replaced with a ref later.
|
|
258
|
-
return id;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
else {
|
|
262
|
-
// TODO: what to do if ref not found?
|
|
263
|
-
const recordId = record['Id'];
|
|
264
|
-
this.logger.error(`Reference ${relTo} not found for ${key}. Skipping record ${recordId}.`);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
else {
|
|
269
|
-
// not a relationship field, simple key/value insertion
|
|
270
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
271
|
-
return record[key];
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
return null;
|
|
275
|
-
})
|
|
276
|
-
.then((processedAttribute) => {
|
|
277
|
-
if (processedAttribute !== null) {
|
|
278
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
279
|
-
treeRecord[key] = processedAttribute;
|
|
280
|
-
}
|
|
281
|
-
return Promise.resolve(null);
|
|
282
|
-
})
|
|
283
|
-
.catch((e) => {
|
|
284
|
-
throw e;
|
|
214
|
+
if ((0, dataSoqlQueryTypes_1.hasNestedRecords)(field)) {
|
|
215
|
+
const childMetadata = await this.loadMetadata(field.records[0].attributes.type);
|
|
216
|
+
treeRecord[key] = await this.queryResultsToTree(field, {
|
|
217
|
+
id: `@${objRefId}`,
|
|
218
|
+
fieldName: this.getRelationshipFieldName(childMetadata, record.attributes.type),
|
|
285
219
|
});
|
|
220
|
+
return;
|
|
286
221
|
}
|
|
287
|
-
|
|
288
|
-
|
|
222
|
+
}
|
|
223
|
+
if (this.config.plan && this.isRelationshipWithMetadata(metadata, key)) {
|
|
224
|
+
const relTo = this.getRelatedToWithMetadata(metadata, key);
|
|
225
|
+
// find reference in record result
|
|
226
|
+
if (this.objectTypeRegistry[relTo]) {
|
|
227
|
+
// add ref to replace the value
|
|
228
|
+
const id = record[key];
|
|
229
|
+
const ref = (_a = this.refFromIdByType.get(relTo)) === null || _a === void 0 ? void 0 : _a.get(id);
|
|
230
|
+
// If ref is not found, then leave intact because we may not have processed
|
|
231
|
+
// this parent fully. We'll go back through the sObject tree
|
|
232
|
+
// later and replace the id with a reference.
|
|
233
|
+
treeRecord[key] = ref && ref !== id ? `@${ref}` : id;
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
// TODO: what to do if ref not found?
|
|
237
|
+
const recordId = record['Id'];
|
|
238
|
+
this.logger.error(`Reference ${relTo} not found for ${key}. Skipping record ${recordId}.`);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
// not a relationship field, simple key/value
|
|
242
|
+
if (!this.isRelationshipWithMetadata(metadata, key)) {
|
|
243
|
+
treeRecord[key] = record[key];
|
|
244
|
+
}
|
|
289
245
|
}
|
|
290
246
|
// Get sObject description and cache for given object type
|
|
291
247
|
async loadMetadata(objectName) {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
describe[objectName] = await sObject.describe();
|
|
295
|
-
}
|
|
248
|
+
var _a;
|
|
249
|
+
(_a = describe[objectName]) !== null && _a !== void 0 ? _a : (describe[objectName] = await this.org.getConnection().sobject(objectName).describe());
|
|
296
250
|
return describe[objectName];
|
|
297
251
|
}
|
|
298
252
|
isQueryResult(metadata, fieldName) {
|
|
299
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-return
|
|
300
253
|
return metadata.childRelationships.some((cr) => cr.relationshipName === fieldName);
|
|
301
254
|
}
|
|
302
255
|
isSpecificTypeWithMetadata(metadata, fieldName, fieldType) {
|
|
303
|
-
|
|
304
|
-
fld = metadata.fields[i];
|
|
305
|
-
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
306
|
-
if (fld.name.toLowerCase() === fieldName.toLowerCase()) {
|
|
307
|
-
if (fld.type.toLowerCase() === fieldType.toLowerCase()) {
|
|
308
|
-
return true;
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
/* eslint-enable @typescript-eslint/no-unsafe-call */
|
|
312
|
-
}
|
|
313
|
-
return false;
|
|
256
|
+
return metadata.fields.some((f) => f.name.toLowerCase() === fieldName.toLowerCase() && f.type.toLowerCase() === fieldType.toLowerCase());
|
|
314
257
|
}
|
|
315
258
|
getRelationshipFieldName(metadata, parentName) {
|
|
316
|
-
const result = metadata.fields.find((field) => {
|
|
317
|
-
if (field.type === 'reference') {
|
|
318
|
-
for (const refTo of field.referenceTo) {
|
|
319
|
-
if (refTo === parentName) {
|
|
320
|
-
return true;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
return false;
|
|
325
|
-
});
|
|
259
|
+
const result = metadata.fields.find((field) => { var _a; return field.type === 'reference' && ((_a = field.referenceTo) === null || _a === void 0 ? void 0 : _a.includes(parentName)); });
|
|
326
260
|
if (!result) {
|
|
327
261
|
throw new core_1.SfdxError(`Unable to find relationship field name for ${metadata.name}`);
|
|
328
262
|
}
|
|
329
263
|
return result.name;
|
|
330
264
|
}
|
|
331
265
|
isRelationship(objectName, fieldName) {
|
|
332
|
-
|
|
333
|
-
if (!metadata) {
|
|
266
|
+
if (!describe[objectName]) {
|
|
334
267
|
throw new core_1.SfdxError(`Metadata not found for ${objectName}`);
|
|
335
268
|
}
|
|
336
|
-
return this.isRelationshipWithMetadata(
|
|
269
|
+
return this.isRelationshipWithMetadata(describe[objectName], fieldName);
|
|
337
270
|
}
|
|
338
271
|
isRelationshipWithMetadata(metadata, fieldName) {
|
|
339
272
|
return this.isSpecificTypeWithMetadata(metadata, fieldName, 'reference');
|
|
340
273
|
}
|
|
341
274
|
getRelatedTo(objectName, fieldName) {
|
|
342
|
-
|
|
343
|
-
if (!metadata) {
|
|
275
|
+
if (!describe[objectName]) {
|
|
344
276
|
throw new core_1.SfdxError(`Metadata not found for ${objectName}`);
|
|
345
277
|
}
|
|
346
|
-
return this.getRelatedToWithMetadata(
|
|
278
|
+
return this.getRelatedToWithMetadata(describe[objectName], fieldName);
|
|
347
279
|
}
|
|
348
280
|
getRelatedToWithMetadata(metadata, fieldName) {
|
|
349
|
-
const result = metadata.fields.find((field) => {
|
|
350
|
-
|
|
351
|
-
if (field.referenceTo.length) {
|
|
352
|
-
return true;
|
|
353
|
-
}
|
|
354
|
-
return false;
|
|
355
|
-
}
|
|
356
|
-
return false;
|
|
357
|
-
});
|
|
358
|
-
if (!result) {
|
|
281
|
+
const result = metadata.fields.find((field) => { var _a; return field.name === fieldName && ((_a = field.referenceTo) === null || _a === void 0 ? void 0 : _a.length); });
|
|
282
|
+
if (!result || !result.referenceTo) {
|
|
359
283
|
throw new core_1.SfdxError(`Unable to find relation for ${metadata.name}`);
|
|
360
284
|
}
|
|
361
285
|
return result.referenceTo[0];
|
|
362
286
|
}
|
|
363
|
-
|
|
287
|
+
/**
|
|
288
|
+
* Register object type's id to reference mapping
|
|
289
|
+
*
|
|
290
|
+
* @param refId like '@AccountRef1'
|
|
291
|
+
* */
|
|
364
292
|
saveRecordRef(obj, refId) {
|
|
293
|
+
var _a, _b;
|
|
294
|
+
if (!obj.attributes.url) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
365
297
|
const id = path.basename(obj.attributes.url);
|
|
366
|
-
const ref = refId;
|
|
367
298
|
const type = obj.attributes.type;
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
const refEntry = this.referenceRegistry[type][id];
|
|
373
|
-
if (refEntry && refEntry !== ref) {
|
|
374
|
-
throw new core_1.SfdxError(`Overriding ${type} reference for ${id}: existing ${refEntry}, incoming ${ref}`);
|
|
299
|
+
// ensure no existing reference for that Id
|
|
300
|
+
const refEntry = (_a = this.refFromIdByType.get(type)) === null || _a === void 0 ? void 0 : _a.get(id);
|
|
301
|
+
if (refEntry && refEntry !== refId) {
|
|
302
|
+
throw new core_1.SfdxError(`Overriding ${type} reference for ${id}: existing ${refEntry}, incoming ${refId}`);
|
|
375
303
|
}
|
|
376
|
-
this.
|
|
304
|
+
this.refFromIdByType.set(type, ((_b = this.refFromIdByType.get(type)) !== null && _b !== void 0 ? _b : new Map()).set(id, refId));
|
|
377
305
|
}
|
|
378
306
|
/**
|
|
379
307
|
* Walk the final data set and split out into files. The main queried
|
|
@@ -381,61 +309,47 @@ class ExportApi {
|
|
|
381
309
|
* values. All the references have been created at this point.
|
|
382
310
|
*/
|
|
383
311
|
generateDataPlan(sobjectTree) {
|
|
384
|
-
const objects =
|
|
312
|
+
const objects = new Map();
|
|
385
313
|
const dataPlan = [];
|
|
386
|
-
let topLevelObjectType;
|
|
387
314
|
// loop thru object tree extracting type-specific records into separate tree structure
|
|
388
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
389
315
|
sobjectTree.records.forEach((record) => {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
316
|
+
var _a;
|
|
317
|
+
const topLevelObjectType = record.attributes.type;
|
|
318
|
+
if (!objects.has(topLevelObjectType)) {
|
|
319
|
+
objects.set(topLevelObjectType, []);
|
|
393
320
|
}
|
|
394
|
-
Object.
|
|
395
|
-
|
|
396
|
-
if (
|
|
397
|
-
|
|
398
|
-
if (childRecords
|
|
399
|
-
|
|
400
|
-
if (
|
|
401
|
-
|
|
321
|
+
Object.entries(record).map(([key, value]) => {
|
|
322
|
+
var _a;
|
|
323
|
+
if ((0, dataSoqlQueryTypes_1.hasNestedRecords)(value)) {
|
|
324
|
+
const childRecords = value.records;
|
|
325
|
+
if (childRecords) {
|
|
326
|
+
// found child records, add to type-specific registry
|
|
327
|
+
if (childRecords.length) {
|
|
328
|
+
const childObjectType = childRecords[0].attributes.type;
|
|
329
|
+
objects.set(childObjectType, ((_a = objects.get(childObjectType)) !== null && _a !== void 0 ? _a : []).concat(childRecords));
|
|
402
330
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
objects[childObjectType].records.push(child);
|
|
406
|
-
});
|
|
407
|
-
/* eslint-enable @typescript-eslint/no-unsafe-call */
|
|
331
|
+
// remove child from top-level object structure
|
|
332
|
+
delete record[key];
|
|
408
333
|
}
|
|
409
|
-
// remove child from top-level object structure
|
|
410
|
-
delete record[key];
|
|
411
334
|
}
|
|
412
|
-
return key;
|
|
413
335
|
});
|
|
414
|
-
|
|
415
|
-
objects[topLevelObjectType].records.push(record);
|
|
336
|
+
objects.set(topLevelObjectType, ((_a = objects.get(topLevelObjectType)) !== null && _a !== void 0 ? _a : []).concat([record]));
|
|
416
337
|
});
|
|
417
338
|
// sort object types based on insertion dependence
|
|
418
339
|
const objectsSorted = Object.keys(this.objectTypeRegistry).sort((a, b) => this.objectTypeRegistry[a].order - this.objectTypeRegistry[b].order);
|
|
419
340
|
// write data files and update data plan
|
|
420
|
-
objectsSorted.
|
|
421
|
-
const dataPlanPart = this.writeObjectTypeDataFile(key, this.objectTypeRegistry[key].saveRefs, this.objectTypeRegistry[key].resolveRefs, `${key}s.json`, objects[key]);
|
|
422
|
-
dataPlan.push(dataPlanPart);
|
|
423
|
-
});
|
|
341
|
+
dataPlan.push(...objectsSorted.map((key) => this.writeObjectTypeDataFile(key, !!this.objectTypeRegistry[key].saveRefs, !!this.objectTypeRegistry[key].resolveRefs, `${key}s.json`, { records: objects.get(key) })));
|
|
424
342
|
// write data plan file
|
|
425
343
|
const dataPlanFile = Object.keys(this.objectTypeRegistry).join('-') + DATA_PLAN_FILENAME_PART;
|
|
426
344
|
return this.writeDataFileSync(dataPlanFile, dataPlan);
|
|
427
345
|
}
|
|
428
346
|
// generate data plan stanza referencing written object type file
|
|
429
347
|
writeObjectTypeDataFile(type, saveRefs, resolveRefs, fileName, sObject) {
|
|
430
|
-
let finalFilename = fileName;
|
|
431
|
-
if (this.config.prefix) {
|
|
432
|
-
finalFilename = `${this.config.prefix}-${fileName}`;
|
|
433
|
-
}
|
|
434
348
|
const dataPlanPart = {
|
|
435
349
|
sobject: type,
|
|
436
350
|
saveRefs,
|
|
437
351
|
resolveRefs,
|
|
438
|
-
files: [
|
|
352
|
+
files: [this.getPrefixedFileName(fileName)],
|
|
439
353
|
};
|
|
440
354
|
this.writeDataFileSync(fileName, sObject);
|
|
441
355
|
return dataPlanPart;
|
|
@@ -447,33 +361,33 @@ class ExportApi {
|
|
|
447
361
|
*/
|
|
448
362
|
finalApplyRefs(sobjectTree) {
|
|
449
363
|
sobjectTree.forEach((record) => {
|
|
450
|
-
Object.
|
|
451
|
-
|
|
364
|
+
Object.entries(record).map(([field, value]) => {
|
|
365
|
+
var _a;
|
|
366
|
+
if ((0, dataSoqlQueryTypes_1.hasNestedRecords)(value)) {
|
|
452
367
|
// These are children
|
|
453
|
-
this.finalApplyRefs(
|
|
368
|
+
this.finalApplyRefs(value.records);
|
|
454
369
|
}
|
|
455
370
|
else {
|
|
456
371
|
const objType = record.attributes.type;
|
|
457
372
|
if (this.isRelationship(objType, field)) {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
373
|
+
if (typeof value === 'string' && !value.startsWith('@')) {
|
|
374
|
+
// it's still just an ID, so we need to resolve it
|
|
375
|
+
const id = value;
|
|
461
376
|
const refTo = this.getRelatedTo(objType, field);
|
|
462
|
-
const ref = this.
|
|
377
|
+
const ref = (_a = this.refFromIdByType.get(refTo)) === null || _a === void 0 ? void 0 : _a.get(id);
|
|
463
378
|
if (!ref) {
|
|
464
379
|
throw new core_1.SfdxError(`${objType} reference to ${refTo} (${id}) not found in query results.`);
|
|
465
380
|
}
|
|
466
381
|
record[field] = `@${ref}`;
|
|
467
382
|
// Setup dependency ordering for later output
|
|
468
383
|
if (this.objectTypeRegistry[objType].order <= this.objectTypeRegistry[refTo].order) {
|
|
469
|
-
this.objectTypeRegistry[objType].order =
|
|
384
|
+
this.objectTypeRegistry[objType].order = this.objectTypeRegistry[refTo].order + 1;
|
|
470
385
|
this.objectTypeRegistry[refTo].saveRefs = true;
|
|
471
386
|
this.objectTypeRegistry[objType].resolveRefs = true;
|
|
472
387
|
}
|
|
473
388
|
}
|
|
474
389
|
}
|
|
475
390
|
}
|
|
476
|
-
return field;
|
|
477
391
|
});
|
|
478
392
|
});
|
|
479
393
|
return { records: sobjectTree };
|
|
@@ -482,28 +396,27 @@ class ExportApi {
|
|
|
482
396
|
count += records.length;
|
|
483
397
|
records.forEach((record) => {
|
|
484
398
|
Object.values(record).forEach((val) => {
|
|
485
|
-
if (
|
|
399
|
+
if ((0, dataSoqlQueryTypes_1.hasNestedRecords)(val)) {
|
|
486
400
|
this.countRecords(val.records, count);
|
|
487
401
|
}
|
|
488
402
|
});
|
|
489
403
|
});
|
|
490
404
|
return count;
|
|
491
405
|
}
|
|
406
|
+
getPrefixedFileName(fileName) {
|
|
407
|
+
return this.config.prefix ? `${this.config.prefix}-${fileName}` : fileName;
|
|
408
|
+
}
|
|
492
409
|
writeDataFileSync(fileName, jsonObject) {
|
|
493
410
|
let recordCount = 0;
|
|
494
|
-
const
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
if (outputDir) {
|
|
499
|
-
fileName = path.join(outputDir, fileName);
|
|
500
|
-
}
|
|
501
|
-
if ((0, ts_types_1.has)(jsonObject, 'records') && (0, ts_types_1.isArray)(jsonObject.records)) {
|
|
411
|
+
const finalFilename = this.config.outputDir
|
|
412
|
+
? path.join(this.config.outputDir, this.getPrefixedFileName(fileName))
|
|
413
|
+
: this.getPrefixedFileName(fileName);
|
|
414
|
+
if ((0, dataSoqlQueryTypes_1.hasNestedRecords)(jsonObject)) {
|
|
502
415
|
recordCount = this.countRecords(jsonObject.records);
|
|
503
416
|
}
|
|
504
|
-
core_1.fs.writeFileSync(
|
|
417
|
+
core_1.fs.writeFileSync(finalFilename, JSON.stringify(jsonObject, null, 4));
|
|
505
418
|
// TODO: move this to the command
|
|
506
|
-
this.ux.log(`Wrote ${recordCount} records to ${
|
|
419
|
+
this.ux.log(`Wrote ${recordCount} records to ${finalFilename}`);
|
|
507
420
|
return jsonObject;
|
|
508
421
|
}
|
|
509
422
|
}
|