@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.
@@ -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 ts_types_1 = require("@salesforce/ts-types");
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.referenceRegistry = {}; // registry of object type-specific id-to-ref mappings
29
- this.typeRefIndexes = {}; // registry for object type-specific ref counters
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 = (await this.processQueryResults(queryResults));
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
- else {
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 exist
102
- const error = err;
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 { plan, query } = this.config;
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 = (0, ts_types_1.getNumber)(processedRecordList, 'records.length', 0);
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
- // Register object types and type hierarchy for plan generation
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 = (0, ts_types_1.getString)(records[0], 'attributes.type');
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.keys(record).map((key) => {
141
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
142
- const value = record[key];
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 processRecordList(recordList, parentRef) {
153
+ async queryResultsToTree(recordList, parentRef) {
166
154
  // holds transformed sobject tree
167
155
  const sobjectTree = { records: [] };
168
- // visit each record in the list
169
- const processRecordsFn = (record) => () => this.processRecords(parentRef, record, sobjectTree);
170
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment
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(parentRef, record, sobjectTree) {
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 = (0, ts_types_1.getString)(parentRef, 'fieldName', '');
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
- if (!this.typeRefIndexes[type]) {
206
- this.typeRefIndexes[type] = 0;
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
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
213
- return Promise.all(promises).then(() => treeRecord);
194
+ await Promise.all(promises);
195
+ return treeRecord;
214
196
  }
215
197
  async processRecordAttribute(record, key, treeRecord, objRefId) {
216
- return Promise.resolve().then(() => {
217
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
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
- // skip attributes and id. Data import does not accept records with IDs.
220
- if (key === 'attributes' || key === 'Id') {
221
- // If this is an attributes section then we need to add an object reference
222
- this.saveRecordRef(record, objRefId);
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
- else {
225
- return this.loadMetadata(record.attributes.type)
226
- .then((metadata) => {
227
- if (this.isQueryResult(metadata, key)) {
228
- if (!field) {
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
- return Promise.resolve(null);
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
- if (!describe[objectName]) {
293
- const sObject = this.org.getConnection().sobject(objectName);
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
- for (let i = 0, fld; i < metadata.fields.length; i++) {
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
- const metadata = describe[objectName];
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(metadata, fieldName);
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
- const metadata = describe[objectName];
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(metadata, fieldName);
278
+ return this.getRelatedToWithMetadata(describe[objectName], fieldName);
347
279
  }
348
280
  getRelatedToWithMetadata(metadata, fieldName) {
349
- const result = metadata.fields.find((field) => {
350
- if (field.name === fieldName) {
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
- // Register object type's id to reference mapping
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
- if (typeof this.referenceRegistry[type] === 'undefined') {
369
- this.referenceRegistry[type] = {};
370
- }
371
- // ensure no existing reference
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.referenceRegistry[type][id] = ref;
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
- topLevelObjectType = record.attributes.type;
391
- if (!objects[topLevelObjectType]) {
392
- objects[topLevelObjectType] = { records: [] };
316
+ var _a;
317
+ const topLevelObjectType = record.attributes.type;
318
+ if (!objects.has(topLevelObjectType)) {
319
+ objects.set(topLevelObjectType, []);
393
320
  }
394
- Object.keys(record).map((key) => {
395
- const childRecords = (0, ts_types_1.get)(record, `${key}.records`);
396
- if (childRecords) {
397
- // found child records, add to type-specific registry
398
- if (childRecords.length) {
399
- const childObjectType = childRecords[0].attributes.type;
400
- if (!objects[childObjectType]) {
401
- objects[childObjectType] = { records: [] };
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
- /* eslint-disable @typescript-eslint/no-unsafe-call */
404
- childRecords.forEach((child) => {
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
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call
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.forEach((key) => {
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: [finalFilename],
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.keys(record).map((field) => {
451
- if (record[field].records) {
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(record[field].records);
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
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call
459
- const id = record[field].toString();
460
- if (!id.startsWith('@')) {
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.referenceRegistry[refTo][id];
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 = Number(this.objectTypeRegistry[refTo].order) + 1;
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 (val === null || val === void 0 ? void 0 : val.records) {
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 { outputDir, prefix } = this.config;
495
- if (prefix) {
496
- fileName = `${prefix}-${fileName}`;
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(fileName, JSON.stringify(jsonObject, null, 4));
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 ${fileName}`);
419
+ this.ux.log(`Wrote ${recordCount} records to ${finalFilename}`);
507
420
  return jsonObject;
508
421
  }
509
422
  }