@tunghtml/strapi-plugin-export-import-clsx 1.0.2 → 1.1.0

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.
@@ -1,102 +1,106 @@
1
- const XLSX = require('xlsx');
2
- const fs = require('fs');
1
+ const XLSX = require("xlsx");
2
+ const fs = require("fs");
3
3
 
4
4
  function toCamel(str) {
5
5
  return str.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
6
6
  }
7
+
7
8
  const SYSTEM_KEYS = [
8
- 'documentId',
9
- 'locale',
10
- 'createdAt',
11
- 'updatedAt',
12
- 'publishedAt',
13
- 'createdBy',
14
- 'updatedBy',
15
- 'localizations',
16
- 'status'
9
+ "documentId",
10
+ "locale",
11
+ "createdAt",
12
+ "updatedAt",
13
+ "publishedAt",
14
+ "createdBy",
15
+ "updatedBy",
16
+ "localizations",
17
+ "status",
17
18
  ];
18
19
 
19
- async function importData(file) {
20
- let result;
21
- try {
22
- let importData;
23
- // Check file extension
24
- const fileName = file.name || file.originalFilename || 'unknown.json';
25
- const fileExtension = fileName.split('.').pop().toLowerCase();
26
- const filePath = file.path || file.filepath;
27
- if (!filePath) {
28
- throw new Error('File path not found');
29
- }
20
+ const SHORTCUT_FIELDS = [
21
+ "email",
22
+ "businessEmail",
23
+ "name",
24
+ "title",
25
+ "tickerCode",
26
+ ];
27
+ module.exports = ({ strapi }) => ({
28
+ async importData(file, targetContentType = null) {
29
+ let result;
30
+ try {
31
+ let importData;
32
+ // Check file extension
33
+ const fileName = file.name || file.originalFilename || "unknown.json";
34
+ const fileExtension = fileName.split(".").pop().toLowerCase();
35
+ const filePath = file.path || file.filepath;
36
+ if (!filePath) {
37
+ throw new Error("File path not found");
38
+ }
30
39
 
31
- if (fileExtension === 'json') {
32
- const fileContent = fs.readFileSync(filePath, 'utf8');
33
- importData = JSON.parse(fileContent);
34
- strapi.log.info('Parsed JSON data:', Object.keys(importData));
35
- } else if (fileExtension === 'xlsx' || fileExtension === 'xls') {
36
- importData = transformExcelData(filePath);
37
- }
38
- result = await bulkInsertData(importData);
39
- return result;
40
- } catch (error) {
41
- // Clean up uploaded file on error
42
- const filePath = file && (file.path || file.filepath);
43
- if (filePath && fs.existsSync(filePath)) {
44
- fs.unlinkSync(filePath);
40
+ if (fileExtension === "json") {
41
+ const fileContent = fs.readFileSync(filePath, "utf8");
42
+ importData = JSON.parse(fileContent);
43
+ strapi.log.info("Parsed JSON data:", Object.keys(importData));
44
+ } else if (fileExtension === "xlsx" || fileExtension === "xls") {
45
+ importData = this.transformExcelData(filePath, targetContentType);
46
+ }
47
+ result = await this.bulkInsertData(importData);
48
+ return result;
49
+ } catch (error) {
50
+ // Clean up uploaded file on error
51
+ const filePath = file && (file.path || file.filepath);
52
+ if (filePath && fs.existsSync(filePath)) {
53
+ fs.unlinkSync(filePath);
54
+ }
55
+ throw error;
45
56
  }
46
- throw error;
47
- }
48
- }
57
+ },
49
58
 
50
-
51
- function transformExcelData(filePath) {
59
+ transformExcelData(filePath, targetContentType = null) {
52
60
  const workbook = XLSX.readFile(filePath);
53
61
  const importData = {};
54
62
 
55
63
  const parseJsonIfNeeded = (value) => {
56
- if (typeof value !== 'string') return value;
57
- const trimmed = value.trim();
58
- if (!trimmed.startsWith('[') && !trimmed.startsWith('{')) return value;
64
+ if (typeof value !== "string") return value;
65
+ const trimmed = value.trim();
66
+ if (!trimmed.startsWith("[") && !trimmed.startsWith("{")) return value;
59
67
 
60
- try {
68
+ try {
61
69
  return JSON.parse(trimmed);
62
- } catch {
70
+ } catch {
63
71
  return value; // keep as string if invalid JSON
64
- }
72
+ }
65
73
  };
66
74
 
67
75
  const isComponentField = (key) => {
68
- const parts = key.split('_');
69
- return parts.length === 2; // exactly one underscore
76
+ const parts = key.split("_");
77
+ return parts.length === 2; // exactly one underscore
70
78
  };
71
79
 
72
- function unflattenRow(rows, targetContentType) {
80
+ const unflattenRow = (rows, targetContentType) => {
73
81
  const result = [];
82
+ const attr = strapi.contentTypes[targetContentType].attributes;
74
83
  for (const row of rows) {
75
84
  const rowData = {};
76
85
 
77
86
  for (const [key, value] of Object.entries(row)) {
78
- if (value === null || value === undefined || value === '') {
79
- rowData[key] = null
80
- }
81
-
82
- if (isComponentField(key)) {
83
- const [comp, field] = key.split('_');
84
-
85
- if (!rowData[comp]) rowData[comp] = {};
86
- rowData[comp][field] = parseJsonIfNeeded(value);
87
-
88
- } else {
89
- rowData[key] = parseJsonIfNeeded(value);
90
- }
91
- }
92
-
93
- existedComponents = getComponentFields(targetContentType);
94
- for (const comp of existedComponents) {
95
- if (!rowData[comp]) {
96
- rowData[comp] = {};
87
+ if (value === null || value === undefined || value === "") {
88
+ rowData[key] = null;
89
+ } else if (
90
+ attr[key] &&
91
+ attr[key].customField &&
92
+ attr[key].type === "json" &&
93
+ attr[key].default === "[]"
94
+ ) {
95
+ rowData[key] = parseJsonIfNeeded(value).split("|");
96
+ } else if (isComponentField(key)) {
97
+ const [comp, field] = key.split("_");
98
+ if (!rowData[comp]) rowData[comp] = {};
99
+ rowData[comp][field] = parseJsonIfNeeded(value);
100
+ } else {
101
+ rowData[key] = parseJsonIfNeeded(value);
97
102
  }
98
103
  }
99
-
100
104
  result.push(rowData);
101
105
  }
102
106
 
@@ -104,255 +108,340 @@ function transformExcelData(filePath) {
104
108
  };
105
109
 
106
110
  const mapSheetNameToContentType = (sheetName) => {
107
- if (!sheetName.startsWith('api__')) return sheetName;
108
- return sheetName.replace(/^api__/, 'api::').replace(/_/g, '.');
111
+ // If targetContentType is provided, use it instead of guessing from sheet name
112
+ if (targetContentType) {
113
+ return targetContentType;
114
+ }
115
+ return "api::" + sheetName + "." + sheetName;
109
116
  };
110
117
 
111
- workbook.SheetNames.forEach(sheetName => {
112
- const worksheet = workbook.Sheets[sheetName];
113
- const rows = XLSX.utils.sheet_to_json(worksheet);
118
+ workbook.SheetNames.forEach((sheetName) => {
119
+ const worksheet = workbook.Sheets[sheetName];
120
+ const rows = XLSX.utils.sheet_to_json(worksheet);
114
121
 
115
- if (!rows.length) return;
122
+ if (!rows.length) return;
116
123
 
117
- const contentTypeName = mapSheetNameToContentType(sheetName);
124
+ const contentTypeName = mapSheetNameToContentType(sheetName);
118
125
 
119
- strapi.log.info(`Reading sheet "${sheetName}" -> ${rows.length} rows`);
120
- strapi.log.info(`Mapped sheet to content-type: ${contentTypeName}`);
126
+ strapi.log.info(`Reading sheet "${sheetName}" -> ${rows.length} rows`);
127
+ strapi.log.info(`Mapped sheet to content-type: ${contentTypeName}`);
121
128
 
122
- if (contentTypeName.startsWith('api::')) {
123
- importData[contentTypeName] = unflattenRow(rows, contentTypeName);
124
- } else {
125
- strapi.log.error(`Unknown content-type: ${contentTypeName}`);
129
+ if (contentTypeName.startsWith("api::")) {
130
+ // Validate that the content type exists
131
+ if (!strapi.contentTypes[contentTypeName]) {
132
+ strapi.log.error(
133
+ `Content type ${contentTypeName} not found. Available types:`,
134
+ Object.keys(strapi.contentTypes)
135
+ );
126
136
  return;
127
137
  }
138
+ importData[contentTypeName] = unflattenRow(rows, contentTypeName);
139
+ } else {
140
+ strapi.log.error(`Unknown content-type: ${contentTypeName}`);
141
+ return;
142
+ }
128
143
  });
129
144
 
130
- strapi.log.info('Final import data keys:', Object.keys(importData));
145
+ strapi.log.info("Final import data keys:", Object.keys(importData));
131
146
  return importData;
132
- }
147
+ },
133
148
 
134
- function getRelationFields(contentType) {
135
- const schema = strapi.contentTypes[contentType];
136
-
137
- if (!schema) {
138
- strapi.log.warn(`Content type ${contentType} not found`);
139
- return [];
140
- }
141
-
142
- return Object.entries(schema.attributes)
143
- .filter(([_, attr]) => attr.type === "relation")
144
- .map(([fieldName, attr]) => ({
145
- field: toCamel(fieldName),
146
- target: attr.target, // e.g. "api::category.category"
147
- relation: attr.relation,
148
- }));
149
- }
149
+ getRelationFields(contentType) {
150
+ const schema = strapi.contentTypes[contentType];
150
151
 
151
- function getRelationFieldsStrArr(contentType) {
152
- const schema = strapi.contentTypes[contentType];
153
-
154
- if (!schema) {
155
- strapi.log.warn(`Content type ${contentType} not found`);
156
- return [];
157
- }
152
+ if (!schema) {
153
+ strapi.log.warn(`Content type ${contentType} not found`);
154
+ return [];
155
+ }
158
156
 
159
- return Object.entries(schema.attributes)
160
- .filter(([_, attr]) => attr.type === "relation")
161
- .map(([fieldName, attr]) => toCamel(fieldName));
162
- }
157
+ return Object.entries(schema.attributes)
158
+ .filter(([_, attr]) => attr.type === "relation")
159
+ .map(([fieldName, attr]) => ({
160
+ field: toCamel(fieldName),
161
+ target: attr.target, // e.g. "api::category.category"
162
+ relation: attr.relation,
163
+ }));
164
+ },
165
+
166
+ getComponentFields(contentType) {
167
+ const schema = strapi.contentTypes[contentType];
168
+
169
+ if (!schema) {
170
+ strapi.log.warn(`Content type ${contentType} not found`);
171
+ return [];
172
+ }
163
173
 
164
- function getComponentFields(contentType) {
165
- const schema = strapi.contentTypes[contentType];
174
+ return Object.entries(schema.attributes)
175
+ .filter(([_, attr]) => attr.type === "component")
176
+ .map(([fieldName, attr]) => toCamel(fieldName));
177
+ },
178
+
179
+ async handleRelations(entry, contentType) {
180
+ const resolveRelationValue = async (field, value, target) => {
181
+ const targetAttr = strapi.contentTypes[target].attributes;
182
+ for (const field of SHORTCUT_FIELDS) {
183
+ if (!targetAttr[field]) continue;
184
+ const existing = await strapi.documents(target).findFirst({
185
+ filters: { [field]: { $eq: value } },
186
+ });
187
+ if (existing) return { id: existing.id };
188
+ throw new Error(`Data with ${field} ${value} not found`);
189
+ }
190
+ return null;
191
+ };
166
192
 
167
- if (!schema) {
168
- strapi.log.warn(`Content type ${contentType} not found`);
169
- return [];
170
- }
193
+ const relationFields = this.getRelationFields(contentType);
194
+ if (relationFields.length === 0) return entry;
171
195
 
172
- return Object.entries(schema.attributes)
173
- .filter(([_, attr]) => attr.type === "component")
174
- .map(([fieldName, attr]) => toCamel(fieldName));
175
- }
196
+ const updatedEntry = { ...entry };
176
197
 
177
- async function handleRelations(entry, contentType) {
178
- let relationFields = getRelationFields(contentType);
179
- if (relationFields.length === 0) {
180
- return entry;
181
- }
198
+ for (const rel of relationFields) {
199
+ const { field, target, relation } = rel;
182
200
 
183
- let existing = null;
184
- const newEntry = { ...entry };
185
- let isUpdated = false;
201
+ let value = entry[field];
202
+ if (!value || value === "") {
203
+ if (relation === "manyToMany" || relation === "oneToMany") {
204
+ updatedEntry[field] = [];
205
+ } else {
206
+ updatedEntry[field] = null;
207
+ }
208
+ continue;
209
+ }
186
210
 
187
- for (const rel of relationFields) {
188
- const { field, target } = rel;
189
- const relValue = entry[field];
190
- try {
191
- if (!relValue) continue;
211
+ // Convert CSV to array
212
+ if (
213
+ typeof value === "string" &&
214
+ (relation === "manyToMany" || relation === "oneToMany")
215
+ ) {
216
+ value = value.split("|");
217
+ } else if (typeof value === "string" && value.includes("|")) {
218
+ throw new Error(
219
+ `Invalid value for field ${field}: ${value}, ${field} is not an array`
220
+ );
221
+ }
192
222
 
193
- if (Array.isArray(relValue)) {
223
+ const values = Array.isArray(value) ? value : [value];
224
+ try {
194
225
  const processed = [];
195
226
 
196
- for (const item of relValue) {
197
- if (item.id) {
198
- existing = await strapi.documents(target).findFirst({
199
- filters: {
200
- id: { $eq: item.id }
201
- },
202
- });
203
- if (existing && hasChanges(existing, item, getRelationFieldsStrArr(target))) {
204
- await strapi.documents(target).update({ documentId: existing.documentId, data: item });
205
- isUpdated = true;
206
- }
207
- processed.push({ id: item.id });
208
- } else {
209
- const created = await strapi.documents(target).create({ data: item });
210
- processed.push({ id: created.id });
211
- }
227
+ for (const v of values) {
228
+ if (!v || v === "") continue;
229
+ const resolved = await resolveRelationValue(field, v, target);
230
+ if (resolved) processed.push(resolved);
212
231
  }
213
- newEntry[field] = processed;
214
- continue;
215
- }
216
232
 
217
- if (!relValue.id) {
218
- const created = await strapi.documents(target).create({ data: relValue });
219
- newEntry[field] = { id: created.id };
220
- } else {
221
- existing = await strapi.documents(target).findFirst({
222
- filters: {
223
- id: { $eq: relValue.id }
224
- },
225
- });
226
- if (hasChanges(existing, relValue, getRelationFieldsStrArr(target))) {
227
- await strapi.documents(target).update({ documentId: existing.documentId, data: relValue });
228
- isUpdated = true;
229
- }
230
- newEntry[field] = { id: relValue.id };
233
+ updatedEntry[field] = Array.isArray(value) ? processed : processed[0];
234
+ } catch (err) {
235
+ throw new Error(
236
+ `Failed processing field ${field} with value ${JSON.stringify(value)}: ${err.message}`
237
+ );
231
238
  }
232
- } catch (err) {
233
- throw new Error(`Field: ${field}, data: ${JSON.stringify(relValue)}, error: ${err.message}`);
234
239
  }
235
- }
236
240
 
237
- return [newEntry, isUpdated];
238
- }
241
+ return updatedEntry;
242
+ },
239
243
 
240
- function hasChanges(existing, incoming, relationFieldStrArr = []) {
241
- if (!incoming || typeof incoming !== "object") return false;
244
+ handleComponents(data, existing, contentType) {
245
+ // Get the component fields for this content type
246
+ const compFields = this.getComponentFields(contentType);
242
247
 
243
- for (const key of Object.keys(incoming)) {
244
- // Skip system keys
245
- if (SYSTEM_KEYS.includes(key)) continue;
248
+ for (const field of compFields) {
249
+ const newValue = data[field];
250
+ const oldValue = existing?.[field];
246
251
 
247
- // Skip relation fields entirely
248
- if (relationFieldStrArr.includes(key)) continue;
252
+ if (!newValue || !oldValue) continue;
249
253
 
250
- const newVal = incoming[key];
251
- const oldVal = existing[key];
254
+ //single component
255
+ if (!Array.isArray(newValue)) {
256
+ if (oldValue?.id) {
257
+ data[field].id = oldValue.id;
258
+ }
259
+ for (const key of Object.keys(data[field])) {
260
+ if (Array.isArray(oldValue[key])) {
261
+ data[field][key] = data[field][key].split("|");
262
+ }
263
+ }
264
+ continue;
265
+ }
252
266
 
253
- // If incoming defines a field but existing doesn't → change
254
- if (oldVal === undefined || newVal === undefined) {
255
- continue;
267
+ //multiple components
268
+ if (Array.isArray(newValue) && Array.isArray(oldValue)) {
269
+ data[field] = newValue.map((block, i) => {
270
+ const oldBlock = oldValue[i];
271
+ if (oldBlock?.id) {
272
+ return { id: oldBlock.id, ...block };
273
+ }
274
+ for (const key of Object.keys(block)) {
275
+ if (Array.isArray(oldBlock[key])) {
276
+ block[key] = block[key].split("|");
277
+ }
278
+ }
279
+ return block;
280
+ });
281
+ }
256
282
  }
257
283
 
258
- // Primitive comparison
259
- if (newVal === null || typeof newVal !== "object") {
260
- if (oldVal !== newVal) {
261
- return true;
284
+ return data;
285
+ },
286
+
287
+ hasChanges(existing, incoming) {
288
+ if (!incoming || typeof incoming !== "object") return false;
289
+ if (!existing || typeof existing !== "object") return true;
290
+ for (const key of Object.keys(incoming)) {
291
+ // Skip system keys
292
+ if (SYSTEM_KEYS.includes(key)) continue;
293
+ const newVal = incoming[key];
294
+ const oldVal = existing[key];
295
+
296
+ // If incoming defines a field but existing doesn't → change
297
+ if (oldVal === undefined || newVal === undefined) {
298
+ continue;
262
299
  }
263
- continue;
264
- }
265
300
 
266
- // ARRAY comparison
267
- if (Array.isArray(newVal)) {
268
- if (!Array.isArray(oldVal)) return true;
269
- if (newVal.length !== oldVal.length) return true;
270
- // Compare values shallowly
271
- for (let i = 0; i < newVal.length; i++) {
272
- if (typeof newVal[i] === "object" && typeof oldVal[i] === "object" && hasChanges(oldVal[i], newVal[i])) {
301
+ // Primitive comparison
302
+ if (newVal === null || typeof newVal !== "object") {
303
+ if (oldVal !== newVal) {
273
304
  return true;
274
- } else if (typeof newVal[i] !== "object" && typeof oldVal[i] !== "object" && newVal[i] !== oldVal[i]) {
305
+ }
306
+ continue;
307
+ }
308
+
309
+ // ARRAY comparison
310
+ if (Array.isArray(newVal)) {
311
+ if (!Array.isArray(oldVal)) return true;
312
+ if (newVal.length !== oldVal.length) return true;
313
+ // Compare values shallowly
314
+ for (let i = 0; i < newVal.length; i++) {
315
+ if (
316
+ typeof newVal[i] === "object" &&
317
+ typeof oldVal[i] === "object" &&
318
+ this.hasChanges(oldVal[i], newVal[i])
319
+ ) {
320
+ return true;
321
+ } else if (
322
+ typeof newVal[i] !== "object" &&
323
+ typeof oldVal[i] !== "object" &&
324
+ newVal[i] !== oldVal[i]
325
+ ) {
326
+ return true;
327
+ }
328
+ }
329
+ continue;
330
+ }
331
+
332
+ // OBJECT comparison (recursive, but ONLY fields in incoming object)
333
+ if (typeof newVal === "object" && typeof oldVal === "object") {
334
+ if (this.hasChanges(oldVal, newVal)) {
275
335
  return true;
276
336
  }
337
+ continue;
277
338
  }
278
- continue;
279
339
  }
280
340
 
281
- // OBJECT comparison (recursive, but ONLY fields in incoming object)
282
- if (typeof newVal === "object" && typeof oldVal === "object") {
283
- if (hasChanges(oldVal, newVal)) {
284
- return true;
341
+ return false;
342
+ },
343
+
344
+ async bulkInsertData(importData) {
345
+ const results = {
346
+ created: 0,
347
+ updated: 0,
348
+ errors: [],
349
+ };
350
+
351
+ for (const [contentType, entries] of Object.entries(importData)) {
352
+ // Validate entries
353
+ if (!strapi.contentTypes[contentType]) {
354
+ results.errors.push(`Content type ${contentType} not found`);
355
+ continue;
356
+ }
357
+ if (!Array.isArray(entries)) {
358
+ results.errors.push(`Invalid data format for ${contentType}`);
359
+ continue;
360
+ }
361
+
362
+ try {
363
+ const { created, updated, errors } = await this.importEntries(
364
+ entries,
365
+ contentType
366
+ );
367
+ results.created += created;
368
+ results.updated += updated;
369
+ results.errors = results.errors.concat(errors);
370
+ } catch (err) {
371
+ results.errors.push(err.message);
285
372
  }
286
- continue;
287
373
  }
288
- }
289
374
 
290
- return false;
291
- }
375
+ return results;
376
+ },
292
377
 
378
+ async importEntries(entries, contentType) {
379
+ const results = { created: 0, updated: 0, errors: [] };
293
380
 
294
- async function bulkInsertData(importData) {
295
- const results = {
296
- created: 0,
297
- updated: 0,
298
- errors: [],
299
- };
381
+ await strapi.db.transaction(async ({ trx, rollback, onRollback }) => {
382
+ onRollback(() => {
383
+ strapi.log.error("Transaction rolled back due to an error!");
384
+ strapi.log.error(results.errors);
385
+ });
300
386
 
301
- for (const [contentType, entries] of Object.entries(importData)) {
302
- // Validate entries
303
- if (!strapi.contentTypes[contentType]) {
304
- results.errors.push(`Content type ${contentType} not found`);
305
- continue;
306
- }
307
- if (!Array.isArray(entries)) {
308
- results.errors.push(`Invalid data format for ${contentType}`);
309
- continue;
310
- }
387
+ for (let i = 0; i < entries.length; i++) {
388
+ const entry = entries[i];
389
+ let existing = null;
311
390
 
312
- for (i = 0; i < entries.length; i++) {
313
- const entry = entries[i];
314
- let existing = null;
315
- try {
316
- let { id, ...data } = entry; // keep id out, keep everything else
317
- let isUpdated = false;
318
- let isCreated = false;
319
- if (id && id !== 'null' && id !== 'undefined') {
320
- existing = await strapi.documents(contentType).findFirst({
321
- filters: {
322
- id: { $eq: id }
323
- },
324
- populate: '*'
325
- });
326
- }
391
+ try {
392
+ let { id, ...data } = entry;
393
+
394
+ // Check if document exists
395
+ if (id && id !== "null" && id !== "undefined") {
396
+ existing = await strapi.documents(contentType).findFirst(
397
+ {
398
+ filters: { id },
399
+ populate: "*",
400
+ },
401
+ { transaction: trx }
402
+ );
403
+ }
327
404
 
328
- [data, isUpdated] = await handleRelations(data, contentType);
405
+ // Handle relations & components
406
+ data = await this.handleRelations(data, contentType, trx);
407
+ data = await this.handleComponents(data, existing, contentType);
408
+
409
+ // Update
410
+ if (existing) {
411
+ if (this.hasChanges(existing, data)) {
412
+ await strapi.documents(contentType).update(
413
+ {
414
+ documentId: existing.documentId,
415
+ data,
416
+ },
417
+ { transaction: trx }
418
+ );
419
+ results.updated++;
420
+ }
421
+ }
329
422
 
330
- if (existing) {
331
- if (hasChanges(existing, data)) {
332
- await strapi.documents(contentType).update({
333
- documentId: existing.documentId,
334
- data,
335
- });
336
- isUpdated = true;
423
+ // Create
424
+ else {
425
+ await strapi
426
+ .documents(contentType)
427
+ .create({ data }, { transaction: trx });
428
+ results.created++;
337
429
  }
338
- } else {
339
- await strapi.documents(contentType).create({ data });
340
- isCreated = true;
430
+ } catch (err) {
431
+ results.errors.push(
432
+ `Failed ${existing ? "updating" : "creating"} on row ${
433
+ i + 2
434
+ }: ${err.message}`
435
+ );
436
+ results.created = 0;
437
+ results.updated = 0;
438
+
439
+ // IMPORTANT: force rollback
440
+ throw err;
341
441
  }
342
- if (isUpdated) {
343
- results.updated++;
344
- } else if (isCreated) {
345
- results.created++;
346
- }
347
- } catch (err) {
348
- results.errors.push(`Failed ${existing ? 'updating' : 'creating'} on row ${i+2}: ${err.message}`);
349
442
  }
350
- }
351
- }
352
-
353
- return results;
354
- }
443
+ });
355
444
 
356
- module.exports = {
357
- importData,
358
- };
445
+ return results;
446
+ },
447
+ });