@tachybase/module-backup 0.23.22 → 0.23.40

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.
@@ -196,11 +196,16 @@ class Dumper extends import_app_migrator.AppMigrator {
196
196
  });
197
197
  Dumper.dumpTasks.set(backupFileName, promise);
198
198
  } else {
199
- await this.dump({
200
- groups: options.groups,
201
- fileName: backupFileName
202
- });
203
- await this.cleanLockFile(backupFileName);
199
+ try {
200
+ await this.dump({
201
+ groups: options.groups,
202
+ fileName: backupFileName
203
+ });
204
+ } catch (err) {
205
+ throw err;
206
+ } finally {
207
+ this.cleanLockFile(backupFileName);
208
+ }
204
209
  }
205
210
  return backupFileName;
206
211
  }
@@ -281,14 +286,14 @@ class Dumper extends import_app_migrator.AppMigrator {
281
286
  await import_promises.default.writeFile(metaPath, JSON.stringify(metaObj), "utf8");
282
287
  }
283
288
  async dumpCollection(options) {
284
- var _a;
289
+ var _a, _b;
285
290
  const app = this.app;
286
291
  const dir = this.workDir;
287
292
  const collectionName = options.name;
288
- app.logger.info(`dumping collection ${collectionName}`);
293
+ app.logger.info(`Dumping collection ${collectionName}`);
289
294
  const collection = app.db.getCollection(collectionName);
290
295
  if (!collection) {
291
- this.app.logger.warn(`collection ${collectionName} not found`);
296
+ this.app.logger.warn(`Collection ${collectionName} not found`);
292
297
  return;
293
298
  }
294
299
  const collectionOnDumpOption = (_a = this.app.db.collectionFactory.collectionTypes.get(
@@ -302,35 +307,29 @@ class Dumper extends import_app_migrator.AppMigrator {
302
307
  const collectionDataDir = import_path.default.resolve(dir, "collections", collectionName);
303
308
  await import_promises.default.mkdir(collectionDataDir, { recursive: true });
304
309
  let count = 0;
305
- if (columns.length !== 0) {
306
- const dataFilePath = import_path.default.resolve(collectionDataDir, "data");
307
- const dataStream = import_fs.default.createWriteStream(dataFilePath);
308
- const rows = await app.db.sequelize.query(
309
- (0, import_utils.sqlAdapter)(
310
- app.db,
311
- `SELECT *
312
- FROM ${collection.isParent() ? "ONLY" : ""} ${collection.quotedTableName()}`
313
- ),
314
- {
315
- type: "SELECT"
316
- }
310
+ const dataFilePath = import_path.default.resolve(collectionDataDir, "data");
311
+ const dataStream = import_fs.default.createWriteStream(dataFilePath);
312
+ const rows = await app.db.sequelize.query(
313
+ (0, import_utils.sqlAdapter)(app.db, `SELECT * FROM ${collection.isParent() ? "ONLY" : ""} ${collection.quotedTableName()}`),
314
+ { type: "SELECT" }
315
+ );
316
+ for (const row of rows) {
317
+ const rowData = JSON.stringify(
318
+ columns.map((col) => {
319
+ const val = row[col];
320
+ const field = collection.getField(col);
321
+ return field ? import_field_value_writer.FieldValueWriter.toDumpedValue(field, val) : val;
322
+ })
317
323
  );
318
- for (const row of rows) {
319
- const rowData = JSON.stringify(
320
- columns.map((col) => {
321
- const val = row[col];
322
- const field = collection.getField(col);
323
- return field ? import_field_value_writer.FieldValueWriter.toDumpedValue(field, val) : val;
324
- })
325
- );
326
- dataStream.write(rowData + "\r\n", "utf8");
324
+ if (!dataStream.write(rowData + "\r\n", "utf8")) {
325
+ await new Promise((resolve) => dataStream.once("drain", resolve));
327
326
  }
328
- dataStream.end();
329
- await finished(dataStream);
330
- count = rows.length;
327
+ count++;
331
328
  }
329
+ dataStream.end();
330
+ await finished(dataStream);
332
331
  const metaAttributes = import_lodash.default.mapValues(attributes, (attr, key) => {
333
- var _a2, _b, _c;
332
+ var _a2, _b2, _c;
334
333
  const collectionField = collection.getField(key);
335
334
  const fieldOptionKeys = ["field", "primaryKey", "autoIncrement", "allowNull", "defaultValue", "unique"];
336
335
  if (collectionField) {
@@ -340,7 +339,7 @@ class Dumper extends import_app_migrator.AppMigrator {
340
339
  type: collectionField.type,
341
340
  typeOptions: collectionField.options
342
341
  };
343
- if (((_c = (_b = (_a2 = fieldAttributes.typeOptions) == null ? void 0 : _a2.defaultValue) == null ? void 0 : _b.constructor) == null ? void 0 : _c.name) === "UUIDV4") {
342
+ if (((_c = (_b2 = (_a2 = fieldAttributes.typeOptions) == null ? void 0 : _a2.defaultValue) == null ? void 0 : _b2.constructor) == null ? void 0 : _c.name) === "UUIDV4") {
344
343
  delete fieldAttributes.typeOptions.defaultValue;
345
344
  }
346
345
  return fieldAttributes;
@@ -363,7 +362,7 @@ class Dumper extends import_app_migrator.AppMigrator {
363
362
  meta["inherits"] = import_lodash.default.uniq(collection.options.inherits);
364
363
  }
365
364
  const autoIncrAttr = collection.model.autoIncrementAttribute;
366
- if (autoIncrAttr && collection.model.rawAttributes[autoIncrAttr] && collection.model.rawAttributes[autoIncrAttr].autoIncrement) {
365
+ if (autoIncrAttr && ((_b = collection.model.rawAttributes[autoIncrAttr]) == null ? void 0 : _b.autoIncrement)) {
367
366
  const queryInterface = app.db.queryInterface;
368
367
  const autoIncrInfo = await queryInterface.getAutoIncrementInfo({
369
368
  tableInfo: {
@@ -112,14 +112,16 @@ var backup_files_default = {
112
112
  * @param next
113
113
  */
114
114
  async create(ctx, next) {
115
+ var _a, _b;
115
116
  const data = ctx.request.body;
116
117
  let taskId;
117
118
  const app = ctx.app;
118
- if (data.method === "worker") {
119
- if (!app.worker.available) {
120
- ctx.throw(500, ctx.t("No worker thread", { ns: "worker-thread" }));
121
- return next();
122
- }
119
+ if (data.method === "worker" && !((_a = app.worker) == null ? void 0 : _a.available)) {
120
+ ctx.throw(500, ctx.t("No worker thread", { ns: "worker-thread" }));
121
+ return next();
122
+ }
123
+ let useWorker = data.method === "worker" || data.method === "priority" && ((_b = app.worker) == null ? void 0 : _b.available);
124
+ if (useWorker) {
123
125
  try {
124
126
  taskId = await app.worker.callPluginMethod({
125
127
  plugin: import_server2.default,
@@ -132,7 +134,6 @@ var backup_files_default = {
132
134
  });
133
135
  app.noticeManager.notify("backup", { level: "info", msg: ctx.t("Done") });
134
136
  } catch (error) {
135
- ctx.logger.warn(error);
136
137
  ctx.throw(500, ctx.t(error.message, { ns: "worker-thread" }));
137
138
  }
138
139
  } else {
@@ -22,14 +22,23 @@ export declare class Restorer extends AppMigrator {
22
22
  getImportMeta(): Promise<any>;
23
23
  checkMeta(): Promise<void>;
24
24
  importCollections(options: RestoreOptions): Promise<void>;
25
- decompressBackup(backupFilePath: string): Promise<void>;
25
+ decompressBackup(backupFilePath: string): Promise<unknown>;
26
26
  readCollectionMeta(collectionName: string): Promise<any>;
27
27
  importCollection(options: {
28
28
  name: string;
29
29
  insert?: boolean;
30
30
  clear?: boolean;
31
31
  rowCondition?: (row: any) => boolean;
32
- }): Promise<any>;
32
+ }): Promise<void>;
33
33
  importDb(options: RestoreOptions): Promise<void>;
34
+ insertMetaRows({ rows, collectionName, columns, fieldAttributes, rawAttributes, addSchemaTableName, options }: {
35
+ rows: any;
36
+ collectionName: any;
37
+ columns: any;
38
+ fieldAttributes: any;
39
+ rawAttributes: any;
40
+ addSchemaTableName: any;
41
+ options: any;
42
+ }): Promise<any>;
34
43
  }
35
44
  export {};
@@ -35,9 +35,9 @@ var import_promises = __toESM(require("fs/promises"));
35
35
  var import_path = __toESM(require("path"));
36
36
  var import_database = require("@tachybase/database");
37
37
  var Topo = __toESM(require("@hapi/topo"));
38
- var import_decompress = __toESM(require("decompress"));
39
38
  var import_lodash = __toESM(require("lodash"));
40
39
  var import_semver = __toESM(require("semver"));
40
+ var import_yauzl = __toESM(require("yauzl"));
41
41
  var import_app_migrator = require("./app-migrator");
42
42
  var import_restore_check_error = require("./errors/restore-check-error");
43
43
  var import_field_value_writer = require("./field-value-writer");
@@ -154,7 +154,38 @@ class Restorer extends import_app_migrator.AppMigrator {
154
154
  await this.emitAsync("restoreCollectionsFinished");
155
155
  }
156
156
  async decompressBackup(backupFilePath) {
157
- if (!this.decompressed) await (0, import_decompress.default)(backupFilePath, this.workDir);
157
+ if (!this.decompressed) {
158
+ return new Promise((resolve, reject) => {
159
+ import_yauzl.default.open(backupFilePath, { lazyEntries: true }, (err, zipfile) => {
160
+ if (err) return reject(err);
161
+ zipfile.readEntry();
162
+ zipfile.on("entry", (entry) => {
163
+ const filePath = import_path.default.join(this.workDir, entry.fileName);
164
+ if (/\/$/.test(entry.fileName)) {
165
+ import_fs.default.mkdir(filePath, { recursive: true }, (err2) => {
166
+ if (err2) return reject(err2);
167
+ zipfile.readEntry();
168
+ });
169
+ } else {
170
+ zipfile.openReadStream(entry, (err2, readStream) => {
171
+ if (err2) return reject(err2);
172
+ const writeStream = import_fs.default.createWriteStream(filePath);
173
+ readStream.pipe(writeStream);
174
+ writeStream.on("close", () => {
175
+ zipfile.readEntry();
176
+ });
177
+ });
178
+ }
179
+ });
180
+ zipfile.on("end", () => {
181
+ this.decompressed = true;
182
+ resolve(1);
183
+ });
184
+ });
185
+ });
186
+ } else {
187
+ return Promise.resolve();
188
+ }
158
189
  }
159
190
  async readCollectionMeta(collectionName) {
160
191
  const dir = this.workDir;
@@ -225,52 +256,45 @@ class Restorer extends import_app_migrator.AppMigrator {
225
256
  if (meta.inherits) {
226
257
  for (const inherit of import_lodash.default.uniq(meta.inherits)) {
227
258
  const parentMeta = await this.readCollectionMeta(inherit);
228
- const sql2 = `ALTER TABLE ${app.db.utils.quoteTable(addSchemaTableName)} INHERIT ${app.db.utils.quoteTable(
259
+ const sql = `ALTER TABLE ${app.db.utils.quoteTable(addSchemaTableName)} INHERIT ${app.db.utils.quoteTable(
229
260
  parentMeta.tableName
230
261
  )};`;
231
- await db.sequelize.query(sql2);
262
+ await db.sequelize.query(sql);
232
263
  }
233
264
  }
234
265
  }
235
- const rows = await (0, import_utils.readLines)(collectionDataPath);
236
- if (rows.length === 0) {
237
- app.logger.info(`${collectionName} has no data to import`);
238
- this.importedCollections.push(collectionName);
239
- return;
240
- }
241
- const rowsWithMeta = rows.map(
242
- (row) => JSON.parse(row).map((val, index) => [columns[index], val]).reduce((carry, [column, val]) => {
243
- const field = fieldAttributes[column];
244
- carry[column] = field ? import_field_value_writer.FieldValueWriter.write(field, val) : val;
245
- return carry;
246
- }, {})
247
- ).filter((row) => {
248
- if (options.rowCondition) {
249
- return options.rowCondition(row);
266
+ const batchSize = 1e3;
267
+ let batch = [];
268
+ let allLength = 0;
269
+ await (0, import_utils.readEveryLines)(collectionDataPath, async (line) => {
270
+ batch.push(line);
271
+ if (batch.length >= batchSize) {
272
+ await this.insertMetaRows({
273
+ rows: batch,
274
+ collectionName,
275
+ columns,
276
+ fieldAttributes,
277
+ rawAttributes,
278
+ addSchemaTableName,
279
+ options
280
+ });
281
+ allLength += batchSize;
282
+ batch = [];
250
283
  }
251
- return true;
252
284
  });
253
- if (rowsWithMeta.length === 0) {
254
- app.logger.info(`${collectionName} has no data to import`);
255
- this.importedCollections.push(collectionName);
256
- return;
257
- }
258
- const insertGeneratorAttributes = import_lodash.default.mapKeys(rawAttributes, (value, key) => {
259
- return value.field;
260
- });
261
- const sql = db.sequelize.queryInterface.queryGenerator.bulkInsertQuery(
262
- addSchemaTableName,
263
- rowsWithMeta,
264
- {},
265
- insertGeneratorAttributes
266
- );
267
- if (options.insert === false) {
268
- return sql;
285
+ if (!this.importedCollections.includes(collectionName)) {
286
+ await this.insertMetaRows({
287
+ rows: batch,
288
+ collectionName,
289
+ columns,
290
+ fieldAttributes,
291
+ rawAttributes,
292
+ addSchemaTableName,
293
+ options
294
+ });
295
+ allLength += batch.length;
269
296
  }
270
- await app.db.sequelize.query(sql, {
271
- type: "INSERT"
272
- });
273
- app.logger.info(`${collectionName} imported with ${rowsWithMeta.length} rows`);
297
+ app.logger.info(`${collectionName} imported with ${allLength} rows`);
274
298
  if (meta.autoIncrement) {
275
299
  const queryInterface = app.db.queryInterface;
276
300
  await queryInterface.setAutoIncrementVal({
@@ -313,6 +337,47 @@ class Restorer extends import_app_migrator.AppMigrator {
313
337
  }
314
338
  }
315
339
  }
340
+ async insertMetaRows({ rows, collectionName, columns, fieldAttributes, rawAttributes, addSchemaTableName, options }) {
341
+ const app = this.app;
342
+ const db = app.db;
343
+ if (rows.length === 0) {
344
+ app.logger.info(`${collectionName} has no data to import`);
345
+ this.importedCollections.push(collectionName);
346
+ return;
347
+ }
348
+ const rowsWithMeta = rows.map(
349
+ (row) => JSON.parse(row).map((val, index) => [columns[index], val]).reduce((carry, [column, val]) => {
350
+ const field = fieldAttributes[column];
351
+ carry[column] = field ? import_field_value_writer.FieldValueWriter.write(field, val) : val;
352
+ return carry;
353
+ }, {})
354
+ ).filter((row) => {
355
+ if (options.rowCondition) {
356
+ return options.rowCondition(row);
357
+ }
358
+ return true;
359
+ });
360
+ if (rowsWithMeta.length === 0) {
361
+ app.logger.info(`${collectionName} has no data to import`);
362
+ this.importedCollections.push(collectionName);
363
+ return;
364
+ }
365
+ const insertGeneratorAttributes = import_lodash.default.mapKeys(rawAttributes, (value, key) => {
366
+ return value.field;
367
+ });
368
+ const sql = db.sequelize.queryInterface.queryGenerator.bulkInsertQuery(
369
+ addSchemaTableName,
370
+ rowsWithMeta,
371
+ {},
372
+ insertGeneratorAttributes
373
+ );
374
+ if (options.insert === false) {
375
+ return sql;
376
+ }
377
+ await app.db.sequelize.query(sql, {
378
+ type: "INSERT"
379
+ });
380
+ }
316
381
  }
317
382
  // Annotate the CommonJS export names for ESM import in node:
318
383
  0 && (module.exports = {
@@ -2,4 +2,5 @@ import { Database } from '@tachybase/database';
2
2
  export declare const DUMPED_EXTENSION = "tbdump";
3
3
  export declare function sqlAdapter(database: Database, sql: string): string;
4
4
  export declare function readLines(filePath: string): Promise<any[]>;
5
+ export declare function readEveryLines(filePath: string, processLine: (line: string) => void): Promise<void>;
5
6
  export declare function humanFileSize(bytes: any, si?: boolean, dp?: number): string;
@@ -29,6 +29,7 @@ var utils_exports = {};
29
29
  __export(utils_exports, {
30
30
  DUMPED_EXTENSION: () => DUMPED_EXTENSION,
31
31
  humanFileSize: () => humanFileSize,
32
+ readEveryLines: () => readEveryLines,
32
33
  readLines: () => readLines,
33
34
  sqlAdapter: () => sqlAdapter
34
35
  });
@@ -55,6 +56,16 @@ async function readLines(filePath) {
55
56
  }
56
57
  return results;
57
58
  }
59
+ async function readEveryLines(filePath, processLine) {
60
+ const fileStream = import_fs.default.createReadStream(filePath);
61
+ const rl = import_readline.default.createInterface({
62
+ input: fileStream,
63
+ crlfDelay: Infinity
64
+ });
65
+ for await (const line of rl) {
66
+ await processLine(line);
67
+ }
68
+ }
58
69
  function humanFileSize(bytes, si = false, dp = 1) {
59
70
  const thresh = si ? 1e3 : 1024;
60
71
  if (Math.abs(bytes) < thresh) {
@@ -73,6 +84,7 @@ function humanFileSize(bytes, si = false, dp = 1) {
73
84
  0 && (module.exports = {
74
85
  DUMPED_EXTENSION,
75
86
  humanFileSize,
87
+ readEveryLines,
76
88
  readLines,
77
89
  sqlAdapter
78
90
  });
package/package.json CHANGED
@@ -1,14 +1,13 @@
1
1
  {
2
2
  "name": "@tachybase/module-backup",
3
3
  "displayName": "App backup & restore",
4
- "version": "0.23.22",
4
+ "version": "0.23.40",
5
5
  "description": "Backup and restore applications for scenarios such as application replication, migration, and upgrades.",
6
6
  "keywords": [
7
7
  "System management"
8
8
  ],
9
9
  "license": "Apache-2.0",
10
10
  "main": "./dist/server/index.js",
11
- "dependencies": {},
12
11
  "devDependencies": {
13
12
  "@ant-design/icons": "^5.5.2",
14
13
  "@hapi/topo": "^6.0.2",
@@ -30,16 +29,17 @@
30
29
  "react-i18next": "^15.2.0",
31
30
  "semver": "^7.6.3",
32
31
  "tar": "^6.2.1",
33
- "@tachybase/components": "0.23.22",
34
- "@tachybase/module-worker-thread": "0.23.22"
32
+ "yauzl": "^3.2.0",
33
+ "@tachybase/components": "0.23.40",
34
+ "@tachybase/module-worker-thread": "0.23.40"
35
35
  },
36
36
  "peerDependencies": {
37
- "@tachybase/actions": "0.23.22",
38
- "@tachybase/client": "0.23.22",
39
- "@tachybase/database": "0.23.22",
40
- "@tachybase/server": "0.23.22",
41
- "@tachybase/test": "0.23.22",
42
- "@tachybase/utils": "0.23.22"
37
+ "@tachybase/actions": "0.23.40",
38
+ "@tachybase/client": "0.23.40",
39
+ "@tachybase/database": "0.23.40",
40
+ "@tachybase/test": "0.23.40",
41
+ "@tachybase/server": "0.23.40",
42
+ "@tachybase/utils": "0.23.40"
43
43
  },
44
44
  "description.zh-CN": "备份和还原应用,可用于应用的复制、迁移、升级等场景。",
45
45
  "displayName.zh-CN": "应用的备份与还原",