@nocobase/plugin-action-import 2.1.0-alpha.3 → 2.1.0-alpha.30

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.
@@ -8,7 +8,7 @@ postMessage({t:"ready"});
8
8
  onmessage = function (evt) {
9
9
  var v;
10
10
  try {
11
- v = XLSX.read(evt.data.d, {type: evt.data.b, codepage: evt.data.c});
11
+ v = XLSX.read(evt.data.d, {type: evt.data.b});
12
12
  postMessage({t:"xlsx", d:JSON.stringify(v)});
13
13
  } catch(e) { postMessage({t:"e",d:e.stack||e}); }
14
14
  };
@@ -39,7 +39,7 @@ __export(import_xlsx_exports, {
39
39
  importXlsx: () => importXlsx
40
40
  });
41
41
  module.exports = __toCommonJS(import_xlsx_exports);
42
- var import_xlsx = __toESM(require("xlsx"));
42
+ var XLSX = __toESM(require("xlsx"));
43
43
  var import_async_mutex = require("async-mutex");
44
44
  var import_xlsx_importer = require("../services/xlsx-importer");
45
45
  const IMPORT_LIMIT_COUNT = 2e3;
@@ -55,7 +55,7 @@ async function importXlsxAction(ctx, next) {
55
55
  if (ctx.request.body.explain) {
56
56
  readLimit += 1;
57
57
  }
58
- const workbook = import_xlsx.default.read(ctx.file.buffer, {
58
+ const workbook = XLSX.read(ctx.file.buffer, {
59
59
  type: "buffer",
60
60
  sheetRows: readLimit,
61
61
  cellDates: true
@@ -24,7 +24,15 @@ export type ImporterOptions = {
24
24
  collectionManager: ICollectionManager;
25
25
  collection: ICollection;
26
26
  columns: Array<ImportColumn>;
27
- workbook: any;
27
+ /** Parsed SheetJS workbook. Used for the synchronous (small-file) import path. */
28
+ workbook?: any;
29
+ /**
30
+ * Absolute path to the XLSX file on disk. When set, the importer uses ExcelJS
31
+ * streaming reader so the file is never fully loaded into memory — rows are
32
+ * yielded one by one and processed in chunks. This is the preferred path for
33
+ * large async imports.
34
+ */
35
+ filePath?: string;
28
36
  chunkSize?: number;
29
37
  explain?: string;
30
38
  repository?: any;
@@ -47,10 +55,10 @@ export declare class XlsxImporter extends EventEmitter {
47
55
  run(options?: RunOptions): Promise<any>;
48
56
  resetSeq(options?: RunOptions): Promise<void>;
49
57
  private getColumnsByPermission;
50
- private validateColumns;
58
+ protected validateColumns(ctx?: Context): void;
51
59
  performImport(data: string[][], options?: RunOptions): Promise<any>;
52
60
  protected getModel(): typeof Model;
53
- handleRowValuesWithColumns(row: any, rowValues: any, options: RunOptions, columns: ImportColumn[]): Promise<void>;
61
+ handleRowValuesWithColumns(row: any, rowValues: any, options: RunOptions, columns: ImportColumn[], rowIndex?: number): Promise<void>;
54
62
  handleChuckRows(chunkRows: string[][], runOptions?: RunOptions, options?: {
55
63
  handingRowIndex: number;
56
64
  context: any;
@@ -64,7 +72,7 @@ export declare class XlsxImporter extends EventEmitter {
64
72
  associateRecords(targets: Model[], options?: any): Promise<void>;
65
73
  renderErrorMessage(error: any): any;
66
74
  trimString(str: string): string;
67
- private getExpectedHeaders;
75
+ protected getExpectedHeaders(ctx?: Context): string[];
68
76
  getData(ctx?: Context): Promise<string[][]>;
69
77
  private alignWithHeaders;
70
78
  private findAndValidateHeaders;
@@ -84,10 +84,33 @@ class XlsxImporter extends import_events.default {
84
84
  return data;
85
85
  }
86
86
  async run(options = {}) {
87
- var _a, _b;
88
- let transaction = options.transaction;
89
- if (!transaction && this.options.collectionManager.db) {
90
- transaction = options.transaction = await this.options.collectionManager.db.sequelize.transaction();
87
+ var _a, _b, _c, _d;
88
+ const hasExternalTransaction = !!options.transaction;
89
+ const { db } = this.options.collectionManager;
90
+ if (hasExternalTransaction) {
91
+ try {
92
+ const data = await this.loggerService.measureExecutedTime(
93
+ async () => this.validate(options.context),
94
+ "Validation completed in {time}ms"
95
+ );
96
+ const imported = await this.loggerService.measureExecutedTime(
97
+ async () => this.performImport(data, options),
98
+ "Data import completed in {time}ms"
99
+ );
100
+ (_a = this.logger) == null ? void 0 : _a.info(`Import completed successfully, imported ${imported} records`);
101
+ if (db) {
102
+ await this.loggerService.measureExecutedTime(
103
+ async () => this.resetSeq(options),
104
+ "Sequence reset completed in {time}ms"
105
+ );
106
+ }
107
+ return imported;
108
+ } catch (error) {
109
+ (_b = this.logger) == null ? void 0 : _b.error(`Import failed: ${this.renderErrorMessage(error)}`, {
110
+ originalError: error.stack || error.toString()
111
+ });
112
+ throw error;
113
+ }
91
114
  }
92
115
  try {
93
116
  const data = await this.loggerService.measureExecutedTime(
@@ -98,18 +121,16 @@ class XlsxImporter extends import_events.default {
98
121
  async () => this.performImport(data, options),
99
122
  "Data import completed in {time}ms"
100
123
  );
101
- (_a = this.logger) == null ? void 0 : _a.info(`Import completed successfully, imported ${imported} records`);
102
- if (this.options.collectionManager.db) {
124
+ (_c = this.logger) == null ? void 0 : _c.info(`Import completed successfully, imported ${imported} records`);
125
+ if (db) {
103
126
  await this.loggerService.measureExecutedTime(
104
- async () => this.resetSeq(options),
127
+ async () => this.resetSeq({}),
105
128
  "Sequence reset completed in {time}ms"
106
129
  );
107
130
  }
108
- transaction && await transaction.commit();
109
131
  return imported;
110
132
  } catch (error) {
111
- transaction && await transaction.rollback();
112
- (_b = this.logger) == null ? void 0 : _b.error(`Import failed: ${this.renderErrorMessage(error)}`, {
133
+ (_d = this.logger) == null ? void 0 : _d.error(`Import failed: ${this.renderErrorMessage(error)}`, {
113
134
  originalError: error.stack || error.toString()
114
135
  });
115
136
  throw error;
@@ -117,7 +138,7 @@ class XlsxImporter extends import_events.default {
117
138
  }
118
139
  async resetSeq(options) {
119
140
  const { transaction } = options;
120
- const db = this.options.collectionManager.db;
141
+ const { db } = this.options.collectionManager;
121
142
  const collection = this.options.collection;
122
143
  const autoIncrementAttribute = collection.model.autoIncrementAttribute;
123
144
  if (!autoIncrementAttribute) {
@@ -166,13 +187,13 @@ class XlsxImporter extends import_events.default {
166
187
  this.emit("seqReset", { maxVal, seqName: autoIncrInfo.seqName });
167
188
  }
168
189
  getColumnsByPermission(ctx) {
190
+ var _a, _b, _c;
169
191
  const columns = this.options.columns;
170
- return columns.filter(
171
- (x) => {
172
- var _a, _b, _c, _d, _e;
173
- return import_lodash2.default.isEmpty((_b = (_a = ctx == null ? void 0 : ctx.permission) == null ? void 0 : _a.can) == null ? void 0 : _b.params) ? true : import_lodash2.default.includes(((_e = (_d = (_c = ctx == null ? void 0 : ctx.permission) == null ? void 0 : _c.can) == null ? void 0 : _d.params) == null ? void 0 : _e.fields) || [], x.dataIndex[0]);
174
- }
175
- );
192
+ const fields = (_c = (_b = (_a = ctx == null ? void 0 : ctx.permission) == null ? void 0 : _a.can) == null ? void 0 : _b.params) == null ? void 0 : _c.fields;
193
+ if (!Array.isArray(fields)) {
194
+ return columns;
195
+ }
196
+ return columns.filter((x) => import_lodash2.default.includes(fields, x.dataIndex[0]));
176
197
  }
177
198
  validateColumns(ctx) {
178
199
  var _a, _b, _c;
@@ -233,8 +254,10 @@ class XlsxImporter extends import_events.default {
233
254
  if (this.options.explain) {
234
255
  handingRowIndex += 1;
235
256
  }
236
- for (const chunkRows of chunks) {
257
+ let chunkRows;
258
+ while ((chunkRows = chunks.shift()) !== void 0) {
237
259
  await this.handleChuckRows(chunkRows, options, { handingRowIndex, context: options == null ? void 0 : options.context });
260
+ handingRowIndex += chunkRows.length;
238
261
  imported += chunkRows.length;
239
262
  this.emit("progress", {
240
263
  total,
@@ -246,8 +269,7 @@ class XlsxImporter extends import_events.default {
246
269
  getModel() {
247
270
  return this.repository instanceof import_database.RelationRepository ? this.repository.targetModel : this.repository.model;
248
271
  }
249
- async handleRowValuesWithColumns(row, rowValues, options, columns) {
250
- var _a;
272
+ async handleRowValuesWithColumns(row, rowValues, options, columns, rowIndex = 1) {
251
273
  for (let index = 0; index < columns.length; index++) {
252
274
  const column = columns[index];
253
275
  const field = this.options.collection.getField(column.dataIndex[0]);
@@ -279,7 +301,7 @@ class XlsxImporter extends import_events.default {
279
301
  rowValues[dataKey] = str == null ? null : await interfaceInstance.toValue(this.trimString(str), ctx);
280
302
  } catch (error) {
281
303
  throw new import_errors.ImportValidationError("Failed to parse field {{field}} in row {{rowIndex}}: {{message}}", {
282
- rowIndex: ((_a = options == null ? void 0 : options.context) == null ? void 0 : _a.handingRowIndex) || 1,
304
+ rowIndex,
283
305
  field: dataKey,
284
306
  message: error.message
285
307
  });
@@ -295,18 +317,12 @@ class XlsxImporter extends import_events.default {
295
317
  }
296
318
  async handleChuckRows(chunkRows, runOptions, options) {
297
319
  var _a, _b;
298
- let { handingRowIndex = 1 } = options;
299
- const { transaction } = runOptions;
320
+ const { handingRowIndex: chunkStartRowIndex = 1 } = options;
321
+ const db = this.options.collectionManager.db;
322
+ const externalTransaction = runOptions == null ? void 0 : runOptions.transaction;
323
+ const transaction = externalTransaction || (db ? await db.sequelize.transaction() : null);
324
+ const chunkRunOptions = { ...runOptions, transaction };
300
325
  const columns = this.getColumnsByPermission(options == null ? void 0 : options.context);
301
- const rows = [];
302
- for (const row of chunkRows) {
303
- const rowValues = {};
304
- await this.handleRowValuesWithColumns(row, rowValues, runOptions, columns);
305
- rows.push({
306
- ...this.options.rowDefaultValues || {},
307
- ...rowValues
308
- });
309
- }
310
326
  const translate = (message) => {
311
327
  var _a2;
312
328
  if ((_a2 = options.context) == null ? void 0 : _a2.t) {
@@ -315,7 +331,19 @@ class XlsxImporter extends import_events.default {
315
331
  return message;
316
332
  }
317
333
  };
334
+ const rows = [];
335
+ let inChunkRowIndex = 0;
318
336
  try {
337
+ for (let i = 0; i < chunkRows.length; i++) {
338
+ inChunkRowIndex = i;
339
+ const row = chunkRows[i];
340
+ const rowValues = {};
341
+ await this.handleRowValuesWithColumns(row, rowValues, chunkRunOptions, columns, chunkStartRowIndex + i);
342
+ rows.push({
343
+ ...this.options.rowDefaultValues || {},
344
+ ...rowValues
345
+ });
346
+ }
319
347
  await this.loggerService.measureExecutedTime(
320
348
  async () => this.performInsert({
321
349
  values: rows,
@@ -325,23 +353,31 @@ class XlsxImporter extends import_events.default {
325
353
  "Record insertion completed in {time}ms"
326
354
  );
327
355
  await new Promise((resolve) => setTimeout(resolve, 5));
328
- handingRowIndex += chunkRows.length;
356
+ if (!externalTransaction) {
357
+ await (transaction == null ? void 0 : transaction.commit());
358
+ }
329
359
  } catch (error) {
360
+ if (!externalTransaction) {
361
+ await (transaction == null ? void 0 : transaction.rollback());
362
+ }
363
+ if (error.name === "ImportValidationError") {
364
+ throw error;
365
+ }
330
366
  if (error.name === "SequelizeUniqueConstraintError") {
331
367
  throw new Error(`${translate("Unique constraint error, fields:")} ${JSON.stringify(error.fields)}`);
332
368
  }
369
+ const failedRowIndex = chunkStartRowIndex + inChunkRowIndex;
333
370
  if ((_a = error.params) == null ? void 0 : _a.rowIndex) {
334
- handingRowIndex += error.params.rowIndex;
335
- error.params.rowIndex = handingRowIndex;
371
+ error.params.rowIndex = failedRowIndex;
336
372
  }
337
- (_b = this.logger) == null ? void 0 : _b.error(`Import error at row ${handingRowIndex}: ${error.message}`, {
338
- rowIndex: handingRowIndex,
339
- rowData: rows[handingRowIndex],
373
+ (_b = this.logger) == null ? void 0 : _b.error(`Import error at row ${failedRowIndex}: ${error.message}`, {
374
+ rowIndex: failedRowIndex,
375
+ rowData: chunkRows[inChunkRowIndex],
340
376
  originalError: error.stack || error.toString()
341
377
  });
342
- throw new import_errors.ImportError(`Import failed at row ${handingRowIndex}`, {
343
- rowIndex: handingRowIndex,
344
- rowData: rows[handingRowIndex - (this.options.explain ? 2 : 1)],
378
+ throw new import_errors.ImportError(`Import failed at row ${failedRowIndex}`, {
379
+ rowIndex: failedRowIndex,
380
+ rowData: chunkRows[inChunkRowIndex],
345
381
  cause: error
346
382
  });
347
383
  }
@@ -400,6 +436,7 @@ class XlsxImporter extends import_events.default {
400
436
  "debug"
401
437
  );
402
438
  }
439
+ instances[i] = null;
403
440
  }
404
441
  return instances;
405
442
  }
@@ -448,6 +485,7 @@ class XlsxImporter extends import_events.default {
448
485
  const workbook = this.options.workbook;
449
486
  const worksheet = workbook.Sheets[workbook.SheetNames[0]];
450
487
  let data = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: null, blankrows: false });
488
+ this.options.workbook = null;
451
489
  const expectedHeaders = this.getExpectedHeaders(ctx);
452
490
  const { headerRowIndex, headers } = this.findAndValidateHeaders({ data, expectedHeaders });
453
491
  if (headerRowIndex === -1) {
package/package.json CHANGED
@@ -6,8 +6,8 @@
6
6
  "description": "Import records using excel templates. You can configure which fields to import and templates will be generated automatically.",
7
7
  "description.ru-RU": "Импорт записей с помощью шаблонов Excel: можно настроить, какие поля импортировать, шаблоны будут генерироваться автоматически.",
8
8
  "description.zh-CN": "使用 Excel 模板导入数据,可以配置导入哪些字段,自动生成模板。",
9
- "version": "2.1.0-alpha.3",
10
- "license": "AGPL-3.0",
9
+ "version": "2.1.0-alpha.30",
10
+ "license": "Apache-2.0",
11
11
  "main": "./dist/server/index.js",
12
12
  "homepage": "https://docs.nocobase.com/handbook/action-import",
13
13
  "homepage.ru-RU": "https://docs.nocobase.ru/handbook/action-import",
@@ -36,7 +36,7 @@
36
36
  "react": "^18.2.0",
37
37
  "react-dom": "^18.2.0",
38
38
  "react-i18next": "^11.15.1",
39
- "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz"
39
+ "xlsx": "^0.18.5"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "@nocobase/actions": "2.x",
@@ -46,7 +46,7 @@
46
46
  "@nocobase/test": "2.x",
47
47
  "@nocobase/utils": "2.x"
48
48
  },
49
- "gitHead": "b4d7448b938c1c3be8b2299ad32c6cbe012dd4ea",
49
+ "gitHead": "292ae0ad87f195ed201b274902d21ecd96f5ddd0",
50
50
  "keywords": [
51
51
  "Actions"
52
52
  ]
@@ -1,39 +0,0 @@
1
- /* codepage.js (C) 2013-present SheetJS -- http://sheetjs.com */
2
- // TypeScript Version: 2.2
3
-
4
- /** Codepage index type (integer or string representation) */
5
- export type CP$Index = number | string;
6
-
7
- /* Individual codepage converter */
8
- export interface CP$Conv {
9
- enc: {[n: string]: number; };
10
- dec: {[n: number]: string; };
11
- }
12
-
13
- /** Encode input type (string, array of characters, Buffer) */
14
- export type CP$String = string | string[] | Uint8Array;
15
-
16
- /** Encode output / decode input type */
17
- export type CP$Data = string | number[] | Uint8Array;
18
-
19
- /** General utilities */
20
- export interface CP$Utils {
21
- decode(cp: CP$Index, data: CP$Data): string;
22
- encode(cp: CP$Index, data: CP$String, opts?: any): CP$Data;
23
- hascp(n: number): boolean;
24
- magic: {[cp: string]: string};
25
- }
26
-
27
- /* note: TS cannot export top-level indexer, hence default workaround */
28
- export interface CP$Module {
29
- /** Version string */
30
- version: string;
31
-
32
- /** Utility Functions */
33
- utils: CP$Utils;
34
-
35
- /** Codepage Converters */
36
- [cp: number]: CP$Conv;
37
- }
38
- export const cptable: CP$Module;
39
- export default cptable;
@@ -1,4 +0,0 @@
1
- /* zahl.d.ts (C) 2022-present SheetJS */
2
- // TypeScript Version: 2.2
3
- declare const XLSX_ZAHL_PAYLOAD: string;
4
- export default XLSX_ZAHL_PAYLOAD;