@tachybase/module-backup 1.0.6 → 1.0.25
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/dist/client/AutoBackup.d.ts +1 -0
- package/dist/client/collections/autoBackup.d.ts +3 -0
- package/dist/client/components/RepeatField.d.ts +4 -0
- package/dist/client/components/locale/Cron.zh-CN.d.ts +34 -0
- package/dist/client/cron-jobs-table/AutoBackupTable.d.ts +1 -0
- package/dist/client/cron-jobs-table/AutoBackupTable.schema.d.ts +629 -0
- package/dist/client/index.js +30 -1
- package/dist/client/locale/index.d.ts +4 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +27 -0
- package/dist/externalVersion.js +7 -6
- package/dist/locale/en-US.json +1 -0
- package/dist/locale/zh-CN.json +30 -0
- package/dist/node_modules/@hapi/topo/package.json +1 -1
- package/dist/node_modules/archiver/package.json +1 -1
- package/dist/node_modules/cron-parser/LICENSE +21 -0
- package/dist/node_modules/cron-parser/lib/date.js +252 -0
- package/dist/node_modules/cron-parser/lib/expression.js +1002 -0
- package/dist/node_modules/cron-parser/lib/field_compactor.js +70 -0
- package/dist/node_modules/cron-parser/lib/field_stringify.js +58 -0
- package/dist/node_modules/cron-parser/lib/parser.js +1 -0
- package/dist/node_modules/cron-parser/package.json +1 -0
- package/dist/node_modules/cron-parser/types/common.d.ts +131 -0
- package/dist/node_modules/cron-parser/types/index.d.ts +45 -0
- package/dist/node_modules/cron-parser/types/ts3/index.d.ts +28 -0
- package/dist/node_modules/mkdirp/package.json +1 -1
- package/dist/node_modules/semver/package.json +1 -1
- package/dist/node_modules/yauzl/package.json +1 -1
- package/dist/server/collections/autoBackup.d.ts +2 -0
- package/dist/server/collections/autoBackup.js +64 -0
- package/dist/server/commands/backup-command.d.ts +2 -0
- package/dist/server/commands/backup-command.js +78 -0
- package/dist/server/dumper.d.ts +10 -4
- package/dist/server/dumper.js +23 -32
- package/dist/server/model/AutoBackupModel.d.ts +15 -0
- package/dist/server/model/AutoBackupModel.js +29 -0
- package/dist/server/resourcers/backup-files.js +30 -16
- package/dist/server/server.d.ts +12 -1
- package/dist/server/server.js +185 -6
- package/dist/server/utils/files.d.ts +6 -0
- package/dist/server/utils/files.js +59 -0
- package/package.json +14 -9
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
var autoBackup_exports = {};
|
|
19
|
+
__export(autoBackup_exports, {
|
|
20
|
+
default: () => autoBackup_default
|
|
21
|
+
});
|
|
22
|
+
module.exports = __toCommonJS(autoBackup_exports);
|
|
23
|
+
var import_constants = require("../../constants");
|
|
24
|
+
function autoBackup_default() {
|
|
25
|
+
return {
|
|
26
|
+
dumpRules: "required",
|
|
27
|
+
name: import_constants.COLLECTION_AUTOBACKUP,
|
|
28
|
+
shared: true,
|
|
29
|
+
createdAt: true,
|
|
30
|
+
updatedAt: true,
|
|
31
|
+
createdBy: true,
|
|
32
|
+
updatedBy: true,
|
|
33
|
+
fields: [
|
|
34
|
+
{
|
|
35
|
+
type: "string",
|
|
36
|
+
name: "title",
|
|
37
|
+
required: true
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
type: "boolean",
|
|
41
|
+
name: "enabled",
|
|
42
|
+
defaultValue: false
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
type: "string",
|
|
46
|
+
name: "repeat"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
type: "array",
|
|
50
|
+
name: "dumpRules"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
type: "integer",
|
|
54
|
+
name: "maxNumber"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
type: "encryption",
|
|
58
|
+
name: "password",
|
|
59
|
+
interface: "encryption",
|
|
60
|
+
iv: "welljzlyq2p2439v"
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
var backup_command_exports = {};
|
|
29
|
+
__export(backup_command_exports, {
|
|
30
|
+
default: () => addBackupCommand
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(backup_command_exports);
|
|
33
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
34
|
+
var import_server = require("@tachybase/server");
|
|
35
|
+
var import_dumper = require("../dumper");
|
|
36
|
+
function addBackupCommand(app) {
|
|
37
|
+
app.command("backup").ipc().option("-a, --app <appName>", "sub app name if you want to backup").option(
|
|
38
|
+
"-g, --groups <groups>",
|
|
39
|
+
"groups to backup",
|
|
40
|
+
(value, previous) => {
|
|
41
|
+
return previous.concat([value]);
|
|
42
|
+
},
|
|
43
|
+
[]
|
|
44
|
+
).action(async (options) => {
|
|
45
|
+
let backupApp = app;
|
|
46
|
+
if (options.app) {
|
|
47
|
+
if (!await app.db.getCollection("applications").repository.findOne({
|
|
48
|
+
filter: { name: options.app }
|
|
49
|
+
})) {
|
|
50
|
+
await app.db.getCollection("applications").repository.create({
|
|
51
|
+
values: {
|
|
52
|
+
name: options.app
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const subApp = await import_server.AppSupervisor.getInstance().getApp(options.app);
|
|
57
|
+
if (!subApp) {
|
|
58
|
+
app.logger.error(`app ${options.app} not found`);
|
|
59
|
+
await app.stop();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
backupApp = subApp;
|
|
63
|
+
}
|
|
64
|
+
const groups = new Set(options.groups);
|
|
65
|
+
groups.add("required");
|
|
66
|
+
const dumper = new import_dumper.Dumper(app);
|
|
67
|
+
const appName = backupApp.name;
|
|
68
|
+
const backupFileName = import_dumper.Dumper.generateFileName();
|
|
69
|
+
const tmpFile = await dumper.writeLockFile(backupFileName, appName);
|
|
70
|
+
await dumper.runDumpTask({
|
|
71
|
+
groups,
|
|
72
|
+
appName,
|
|
73
|
+
fileName: backupFileName
|
|
74
|
+
});
|
|
75
|
+
await import_promises.default.unlink(tmpFile);
|
|
76
|
+
backupApp.logger.info(`${appName} backup into ${backupFileName}!`);
|
|
77
|
+
});
|
|
78
|
+
}
|
package/dist/server/dumper.d.ts
CHANGED
|
@@ -16,6 +16,11 @@ type BackUpStatusDoing = {
|
|
|
16
16
|
inProgress: true;
|
|
17
17
|
status: 'in_progress';
|
|
18
18
|
};
|
|
19
|
+
type BackUpStatusError = {
|
|
20
|
+
name: string;
|
|
21
|
+
createdAt: Date;
|
|
22
|
+
status: 'error';
|
|
23
|
+
};
|
|
19
24
|
export declare class Dumper extends AppMigrator {
|
|
20
25
|
static dumpTasks: Map<string, Promise<any>>;
|
|
21
26
|
direction: "dump";
|
|
@@ -26,7 +31,7 @@ export declare class Dumper extends AppMigrator {
|
|
|
26
31
|
};
|
|
27
32
|
};
|
|
28
33
|
static getTaskPromise(taskId: string): Promise<any> | undefined;
|
|
29
|
-
static getFileStatus(filePath: string): Promise<BackUpStatusOk | BackUpStatusDoing>;
|
|
34
|
+
static getFileStatus(filePath: string): Promise<BackUpStatusOk | BackUpStatusDoing | BackUpStatusError>;
|
|
30
35
|
static generateFileName(): string;
|
|
31
36
|
writeSQLContent(key: string, data: {
|
|
32
37
|
sql: string | string[];
|
|
@@ -49,9 +54,10 @@ export declare class Dumper extends AppMigrator {
|
|
|
49
54
|
}): Promise<string[]>;
|
|
50
55
|
backUpFilePath(fileName: string, appName?: string): string;
|
|
51
56
|
lockFilePath(fileName: string, appName?: string): string;
|
|
52
|
-
writeLockFile(fileName: string, appName?: string): Promise<
|
|
53
|
-
cleanLockFile(fileName: string, appName
|
|
54
|
-
|
|
57
|
+
writeLockFile(fileName: string, appName?: string): Promise<string>;
|
|
58
|
+
cleanLockFile(fileName: string, appName: string): Promise<void>;
|
|
59
|
+
getLockFile(appName: string): Promise<string>;
|
|
60
|
+
runDumpTask(options: DumpOptions): Promise<void>;
|
|
55
61
|
dumpableCollectionsGroupByGroup(): Promise<{
|
|
56
62
|
[x: string]: Pick<any, string>[];
|
|
57
63
|
}>;
|
package/dist/server/dumper.js
CHANGED
|
@@ -36,7 +36,6 @@ var import_path = __toESM(require("path"));
|
|
|
36
36
|
var process = __toESM(require("process"));
|
|
37
37
|
var import_stream = __toESM(require("stream"));
|
|
38
38
|
var import_util = __toESM(require("util"));
|
|
39
|
-
var import_worker_threads = require("worker_threads");
|
|
40
39
|
var import_database = require("@tachybase/database");
|
|
41
40
|
var import_archiver = __toESM(require("archiver"));
|
|
42
41
|
var import_dayjs = __toESM(require("dayjs"));
|
|
@@ -60,11 +59,19 @@ const _Dumper = class _Dumper extends import_app_migrator.AppMigrator {
|
|
|
60
59
|
const fileName = import_path.default.basename(filePath);
|
|
61
60
|
return import_fs.default.promises.stat(lockFile).then((lockFileStat) => {
|
|
62
61
|
if (lockFileStat.isFile()) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
if (lockFileStat.ctime.getTime() < Date.now() - 2 * 60 * 60 * 1e3) {
|
|
63
|
+
return {
|
|
64
|
+
name: fileName,
|
|
65
|
+
createdAt: lockFileStat.ctime,
|
|
66
|
+
status: "error"
|
|
67
|
+
};
|
|
68
|
+
} else {
|
|
69
|
+
return {
|
|
70
|
+
name: fileName,
|
|
71
|
+
inProgress: true,
|
|
72
|
+
status: "in_progress"
|
|
73
|
+
};
|
|
74
|
+
}
|
|
68
75
|
} else {
|
|
69
76
|
throw new Error("Lock file is not a file");
|
|
70
77
|
}
|
|
@@ -182,40 +189,24 @@ const _Dumper = class _Dumper extends import_app_migrator.AppMigrator {
|
|
|
182
189
|
await (0, import_mkdirp.default)(dirname);
|
|
183
190
|
const filePath = this.lockFilePath(fileName, appName);
|
|
184
191
|
await import_promises.default.writeFile(filePath, "lock", "utf8");
|
|
192
|
+
return filePath;
|
|
185
193
|
}
|
|
186
194
|
async cleanLockFile(fileName, appName) {
|
|
187
195
|
const filePath = this.lockFilePath(fileName, appName);
|
|
188
196
|
await import_promises.default.unlink(filePath);
|
|
189
197
|
}
|
|
190
|
-
async
|
|
198
|
+
async getLockFile(appName) {
|
|
191
199
|
const backupFileName = _Dumper.generateFileName();
|
|
192
|
-
await this.writeLockFile(backupFileName,
|
|
193
|
-
if (import_worker_threads.isMainThread) {
|
|
194
|
-
const promise = this.dump({
|
|
195
|
-
groups: options.groups,
|
|
196
|
-
fileName: backupFileName,
|
|
197
|
-
appName: options.appName
|
|
198
|
-
}).finally(() => {
|
|
199
|
-
this.cleanLockFile(backupFileName, options.appName);
|
|
200
|
-
_Dumper.dumpTasks.delete(backupFileName);
|
|
201
|
-
this.app.noticeManager.notify("backup", { msg: "Done" });
|
|
202
|
-
});
|
|
203
|
-
_Dumper.dumpTasks.set(backupFileName, promise);
|
|
204
|
-
} else {
|
|
205
|
-
try {
|
|
206
|
-
await this.dump({
|
|
207
|
-
groups: options.groups,
|
|
208
|
-
fileName: backupFileName,
|
|
209
|
-
appName: options.appName
|
|
210
|
-
});
|
|
211
|
-
} catch (err) {
|
|
212
|
-
throw err;
|
|
213
|
-
} finally {
|
|
214
|
-
this.cleanLockFile(backupFileName, options.appName);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
200
|
+
await this.writeLockFile(backupFileName, appName);
|
|
217
201
|
return backupFileName;
|
|
218
202
|
}
|
|
203
|
+
async runDumpTask(options) {
|
|
204
|
+
await this.dump({
|
|
205
|
+
groups: options.groups,
|
|
206
|
+
fileName: options.fileName,
|
|
207
|
+
appName: options.appName
|
|
208
|
+
});
|
|
209
|
+
}
|
|
219
210
|
async dumpableCollectionsGroupByGroup() {
|
|
220
211
|
return (0, import_lodash.default)(await this.dumpableCollections()).map((c) => import_lodash.default.pick(c, ["name", "group", "origin", "title", "isView", "inherits"])).groupBy("group").mapValues((items) => import_lodash.default.sortBy(items, (item) => item.name)).value();
|
|
221
212
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Model } from '@tachybase/database';
|
|
2
|
+
export declare class AutoBackupModel extends Model {
|
|
3
|
+
id: number;
|
|
4
|
+
title: string;
|
|
5
|
+
startsOn: Date;
|
|
6
|
+
endsOn?: Date;
|
|
7
|
+
repeat: string;
|
|
8
|
+
enabled: boolean;
|
|
9
|
+
createdAt: Date;
|
|
10
|
+
updatedAt: Date;
|
|
11
|
+
allExecuted: number;
|
|
12
|
+
password: string;
|
|
13
|
+
dumpRules: string;
|
|
14
|
+
maxNumber: number;
|
|
15
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
var AutoBackupModel_exports = {};
|
|
19
|
+
__export(AutoBackupModel_exports, {
|
|
20
|
+
AutoBackupModel: () => AutoBackupModel
|
|
21
|
+
});
|
|
22
|
+
module.exports = __toCommonJS(AutoBackupModel_exports);
|
|
23
|
+
var import_database = require("@tachybase/database");
|
|
24
|
+
class AutoBackupModel extends import_database.Model {
|
|
25
|
+
}
|
|
26
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
27
|
+
0 && (module.exports = {
|
|
28
|
+
AutoBackupModel
|
|
29
|
+
});
|
|
@@ -115,31 +115,45 @@ var backup_files_default = {
|
|
|
115
115
|
async create(ctx, next) {
|
|
116
116
|
var _a, _b;
|
|
117
117
|
const data = ctx.request.body;
|
|
118
|
-
let taskId;
|
|
119
118
|
const app = ctx.app;
|
|
120
119
|
if (data.method === "worker" && !((_a = app.worker) == null ? void 0 : _a.available)) {
|
|
121
120
|
ctx.throw(500, ctx.t("No worker thread", { ns: "worker-thread" }));
|
|
122
121
|
return next();
|
|
123
122
|
}
|
|
124
123
|
let useWorker = data.method === "worker" || data.method === "priority" && ((_b = app.worker) == null ? void 0 : _b.available);
|
|
124
|
+
const dumper = new import_dumper.Dumper(ctx.app);
|
|
125
|
+
const taskId = await dumper.getLockFile(ctx.app.name);
|
|
125
126
|
if (useWorker) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
127
|
+
app.worker.callPluginMethod({
|
|
128
|
+
plugin: import_server2.default,
|
|
129
|
+
method: "workerCreateBackUp",
|
|
130
|
+
params: {
|
|
131
|
+
dataTypes: data.dataTypes,
|
|
132
|
+
appName: ctx.app.name,
|
|
133
|
+
filename: taskId
|
|
134
|
+
},
|
|
135
|
+
// 目前限制方法并发为1
|
|
136
|
+
concurrency: 1
|
|
137
|
+
}).then((res) => {
|
|
138
|
+
app.noticeManager.notify("backup", { level: "info", msg: ctx.t("Done", { ns: "backup" }) });
|
|
139
|
+
}).catch((error) => {
|
|
140
|
+
app.noticeManager.notify("backup", { level: "error", msg: error.message });
|
|
141
|
+
}).finally(() => {
|
|
142
|
+
dumper.cleanLockFile(taskId, ctx.app.name);
|
|
143
|
+
});
|
|
140
144
|
} else {
|
|
141
145
|
const plugin = app.pm.get(import_server2.default);
|
|
142
|
-
|
|
146
|
+
plugin.workerCreateBackUp({
|
|
147
|
+
dataTypes: data.dataTypes,
|
|
148
|
+
appName: ctx.app.name,
|
|
149
|
+
filename: taskId
|
|
150
|
+
}).then((res) => {
|
|
151
|
+
app.noticeManager.notify("backup", { level: "info", msg: ctx.t("Done", { ns: "backup" }) });
|
|
152
|
+
}).catch((error) => {
|
|
153
|
+
app.noticeManager.notify("backup", { level: "error", msg: error.message });
|
|
154
|
+
}).finally(() => {
|
|
155
|
+
dumper.cleanLockFile(taskId, ctx.app.name);
|
|
156
|
+
});
|
|
143
157
|
}
|
|
144
158
|
ctx.body = {
|
|
145
159
|
key: taskId
|
package/dist/server/server.d.ts
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import { Plugin } from '@tachybase/server';
|
|
2
|
+
import { AutoBackupModel } from './model/AutoBackupModel';
|
|
2
3
|
export default class PluginBackupRestoreServer extends Plugin {
|
|
4
|
+
private static readonly inspectFields;
|
|
3
5
|
beforeLoad(): void;
|
|
6
|
+
private timers;
|
|
4
7
|
load(): Promise<void>;
|
|
8
|
+
inspect(cronJobs: AutoBackupModel[]): void;
|
|
9
|
+
getNextTime(cronJob: AutoBackupModel, currentDate: Date, nextSecond?: boolean): number;
|
|
10
|
+
schedule(cronJob: AutoBackupModel, nextTime: number, toggle?: boolean): void;
|
|
11
|
+
trigger(cronJobId: number, time: number): Promise<void>;
|
|
12
|
+
on(cronJob: AutoBackupModel): void;
|
|
13
|
+
off(cronJob: AutoBackupModel): void;
|
|
5
14
|
workerCreateBackUp(data: {
|
|
6
15
|
dataTypes: string[];
|
|
7
|
-
|
|
16
|
+
appName: string;
|
|
17
|
+
filename: string;
|
|
18
|
+
}): Promise<void>;
|
|
8
19
|
}
|
package/dist/server/server.js
CHANGED
|
@@ -31,23 +31,202 @@ __export(server_exports, {
|
|
|
31
31
|
});
|
|
32
32
|
module.exports = __toCommonJS(server_exports);
|
|
33
33
|
var import_server = require("@tachybase/server");
|
|
34
|
+
var import_cron_parser = __toESM(require("cron-parser"));
|
|
35
|
+
var import_constants = require("../constants");
|
|
34
36
|
var import_dumper = require("./dumper");
|
|
35
37
|
var import_backup_files = __toESM(require("./resourcers/backup-files"));
|
|
36
|
-
|
|
38
|
+
var import_files = require("./utils/files");
|
|
39
|
+
function parseDateWithoutMs(date) {
|
|
40
|
+
return Math.floor(date.getTime() / 1e3) * 1e3;
|
|
41
|
+
}
|
|
42
|
+
const MAX_SAFE_INTERVAL = 2147483647;
|
|
43
|
+
const _PluginBackupRestoreServer = class _PluginBackupRestoreServer extends import_server.Plugin {
|
|
44
|
+
constructor() {
|
|
45
|
+
super(...arguments);
|
|
46
|
+
this.timers = /* @__PURE__ */ new Map();
|
|
47
|
+
}
|
|
37
48
|
beforeLoad() {
|
|
38
49
|
this.app.acl.registerSnippet({
|
|
39
|
-
name: `pm.${this.name}`,
|
|
50
|
+
name: `pm.${this.name}.files`,
|
|
40
51
|
actions: ["backupFiles:*"]
|
|
41
52
|
});
|
|
53
|
+
this.app.acl.registerSnippet({
|
|
54
|
+
name: `pm.${this.name}.auto`,
|
|
55
|
+
actions: ["autoBackup:*"]
|
|
56
|
+
});
|
|
42
57
|
}
|
|
43
58
|
async load() {
|
|
44
59
|
this.app.resourcer.define(import_backup_files.default);
|
|
60
|
+
this.app.on("afterStart", async (app) => {
|
|
61
|
+
const cronJobs = await app.db.getRepository(import_constants.COLLECTION_AUTOBACKUP).find({
|
|
62
|
+
filter: { enabled: true }
|
|
63
|
+
});
|
|
64
|
+
this.inspect(cronJobs);
|
|
65
|
+
});
|
|
66
|
+
this.app.on("beforeStop", () => {
|
|
67
|
+
for (const timer of this.timers.values()) {
|
|
68
|
+
clearInterval(timer);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
this.db.on(`${import_constants.COLLECTION_AUTOBACKUP}.beforeSave`, async (cronjob, options) => {
|
|
72
|
+
let changed = false;
|
|
73
|
+
for (const field of options.fields) {
|
|
74
|
+
if (_PluginBackupRestoreServer.inspectFields.includes(field)) {
|
|
75
|
+
changed = true;
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (!changed) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
this.off(cronjob);
|
|
83
|
+
});
|
|
84
|
+
this.db.on(`${import_constants.COLLECTION_AUTOBACKUP}.afterSave`, async (cronjob, options) => {
|
|
85
|
+
let changed = false;
|
|
86
|
+
for (const field of options.fields) {
|
|
87
|
+
if (_PluginBackupRestoreServer.inspectFields.includes(field)) {
|
|
88
|
+
changed = true;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (!changed) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (cronjob.get("enabled")) {
|
|
96
|
+
this.on(cronjob);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
this.db.on(`${import_constants.COLLECTION_AUTOBACKUP}.afterDestroy`, async (cronjob) => {
|
|
100
|
+
this.off(cronjob);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
inspect(cronJobs) {
|
|
104
|
+
const now = /* @__PURE__ */ new Date();
|
|
105
|
+
cronJobs.forEach((cronJob) => {
|
|
106
|
+
const nextTime = this.getNextTime(cronJob, now);
|
|
107
|
+
if (nextTime) {
|
|
108
|
+
this.app.logger.info(
|
|
109
|
+
`cronJobs [${cronJob.id}] caching scheduled will run at: ${new Date(nextTime).toISOString()}`
|
|
110
|
+
);
|
|
111
|
+
} else {
|
|
112
|
+
this.app.logger.info(`cronJobs [${cronJob.id}] will not be scheduled`);
|
|
113
|
+
}
|
|
114
|
+
this.schedule(cronJob, nextTime, nextTime >= now.getTime());
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
getNextTime(cronJob, currentDate, nextSecond = false) {
|
|
118
|
+
currentDate.setMilliseconds(nextSecond ? 1e3 : 0);
|
|
119
|
+
const timestamp = currentDate.getTime();
|
|
120
|
+
const startTime = parseDateWithoutMs(cronJob.startsOn || /* @__PURE__ */ new Date());
|
|
121
|
+
if (startTime > timestamp) {
|
|
122
|
+
return startTime;
|
|
123
|
+
}
|
|
124
|
+
if (cronJob.repeat) {
|
|
125
|
+
const endTime = cronJob.endsOn ? parseDateWithoutMs(cronJob.endsOn) : null;
|
|
126
|
+
if (endTime && endTime < timestamp) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
if (cronJob.repeat && isNaN(+cronJob.repeat)) {
|
|
130
|
+
const interval = import_cron_parser.default.parseExpression(cronJob.repeat, { currentDate });
|
|
131
|
+
const next = interval.next();
|
|
132
|
+
return next.getTime();
|
|
133
|
+
} else if (!isNaN(+cronJob.repeat)) {
|
|
134
|
+
const repeat = +cronJob.repeat;
|
|
135
|
+
const next = timestamp + repeat - (timestamp - startTime) % repeat;
|
|
136
|
+
return next;
|
|
137
|
+
} else {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
if (startTime < timestamp) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
return timestamp;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
schedule(cronJob, nextTime, toggle = true) {
|
|
148
|
+
if (toggle) {
|
|
149
|
+
const key = `${cronJob.id}@${nextTime}`;
|
|
150
|
+
if (!this.timers.has(key)) {
|
|
151
|
+
const interval = Math.max(nextTime - Date.now(), 0);
|
|
152
|
+
if (interval > MAX_SAFE_INTERVAL) {
|
|
153
|
+
this.timers.set(
|
|
154
|
+
key,
|
|
155
|
+
setTimeout(() => {
|
|
156
|
+
this.timers.delete(key);
|
|
157
|
+
this.schedule(cronJob, nextTime);
|
|
158
|
+
}, MAX_SAFE_INTERVAL)
|
|
159
|
+
);
|
|
160
|
+
} else {
|
|
161
|
+
this.timers.set(key, setTimeout(this.trigger.bind(this, cronJob.id, nextTime), interval));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
for (const [key, timer] of this.timers.entries()) {
|
|
166
|
+
if (key.startsWith(`${cronJob.id}@`)) {
|
|
167
|
+
clearTimeout(timer);
|
|
168
|
+
this.timers.delete(key);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async trigger(cronJobId, time) {
|
|
174
|
+
try {
|
|
175
|
+
const cronJob = await this.db.getRepository(import_constants.COLLECTION_AUTOBACKUP).findOne({ filterByTk: cronJobId });
|
|
176
|
+
if (!cronJob) {
|
|
177
|
+
this.app.logger.warn(`Scheduled cron job ${cronJobId} no longer exists`);
|
|
178
|
+
const eventKey2 = `${cronJobId}@${time}`;
|
|
179
|
+
this.timers.delete(eventKey2);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const eventKey = `${cronJob.id}@${time}`;
|
|
183
|
+
this.timers.delete(eventKey);
|
|
184
|
+
try {
|
|
185
|
+
const dumper = new import_dumper.Dumper(this.app);
|
|
186
|
+
const filename = await dumper.getLockFile(this.app.name);
|
|
187
|
+
this.app.worker.callPluginMethod({
|
|
188
|
+
plugin: _PluginBackupRestoreServer,
|
|
189
|
+
method: "workerCreateBackUp",
|
|
190
|
+
params: {
|
|
191
|
+
dataTypes: cronJob.dumpRules,
|
|
192
|
+
appName: this.app.name,
|
|
193
|
+
filename
|
|
194
|
+
},
|
|
195
|
+
// 目前限制方法并发为1
|
|
196
|
+
concurrency: 1
|
|
197
|
+
}).finally(() => {
|
|
198
|
+
dumper.cleanLockFile(filename, this.app.name);
|
|
199
|
+
const dirPath = dumper.backUpStorageDir(this.app.name);
|
|
200
|
+
(0, import_files.cleanOldFiles)(dumper.backUpStorageDir(this.app.name), cronJob.maxNumber).then(() => {
|
|
201
|
+
this.app.logger.info(`clean backup ${dirPath} to count: {cronJob.maxNumber}`);
|
|
202
|
+
}).catch((err) => {
|
|
203
|
+
this.app.logger.error("clean backup error", err);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
} catch (e) {
|
|
207
|
+
this.app.logger.error(e);
|
|
208
|
+
}
|
|
209
|
+
const nextTime = this.getNextTime(cronJob, /* @__PURE__ */ new Date(), true);
|
|
210
|
+
if (nextTime) {
|
|
211
|
+
this.schedule(cronJob, nextTime);
|
|
212
|
+
}
|
|
213
|
+
} catch (e) {
|
|
214
|
+
this.app.logger.error(`cronJobs [${cronJobId}] failed: ${e.message}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
on(cronJob) {
|
|
218
|
+
this.inspect([cronJob]);
|
|
219
|
+
}
|
|
220
|
+
off(cronJob) {
|
|
221
|
+
this.schedule(cronJob, null, false);
|
|
45
222
|
}
|
|
46
223
|
async workerCreateBackUp(data) {
|
|
47
|
-
|
|
48
|
-
return dumper.runDumpTask({
|
|
224
|
+
await new import_dumper.Dumper(this.app).runDumpTask({
|
|
49
225
|
groups: new Set(data.dataTypes),
|
|
50
|
-
appName:
|
|
226
|
+
appName: data.appName,
|
|
227
|
+
fileName: data.filename
|
|
51
228
|
});
|
|
52
229
|
}
|
|
53
|
-
}
|
|
230
|
+
};
|
|
231
|
+
_PluginBackupRestoreServer.inspectFields = ["repeat", "enabled"];
|
|
232
|
+
let PluginBackupRestoreServer = _PluginBackupRestoreServer;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
var files_exports = {};
|
|
29
|
+
__export(files_exports, {
|
|
30
|
+
cleanOldFiles: () => cleanOldFiles
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(files_exports);
|
|
33
|
+
var import_promises = require("node:fs/promises");
|
|
34
|
+
var import_path = __toESM(require("path"));
|
|
35
|
+
async function cleanOldFiles(dirPath, maxNumber) {
|
|
36
|
+
const names = await (0, import_promises.readdir)(dirPath);
|
|
37
|
+
const files = await Promise.all(
|
|
38
|
+
names.map(async (name) => {
|
|
39
|
+
const filePath = import_path.default.join(dirPath, name);
|
|
40
|
+
try {
|
|
41
|
+
const info = await (0, import_promises.stat)(filePath);
|
|
42
|
+
if (info.isFile() && import_path.default.extname(name) === ".tbdump") {
|
|
43
|
+
return { name, mtime: info.mtime };
|
|
44
|
+
}
|
|
45
|
+
} catch (err) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
})
|
|
50
|
+
);
|
|
51
|
+
const validFiles = files.filter(Boolean);
|
|
52
|
+
validFiles.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
53
|
+
const toDelete = validFiles.slice(maxNumber).map((f) => f.name);
|
|
54
|
+
await Promise.all(toDelete.map((name) => (0, import_promises.unlink)(import_path.default.join(dirPath, name))));
|
|
55
|
+
}
|
|
56
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
57
|
+
0 && (module.exports = {
|
|
58
|
+
cleanOldFiles
|
|
59
|
+
});
|