@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.
Files changed (42) hide show
  1. package/dist/client/AutoBackup.d.ts +1 -0
  2. package/dist/client/collections/autoBackup.d.ts +3 -0
  3. package/dist/client/components/RepeatField.d.ts +4 -0
  4. package/dist/client/components/locale/Cron.zh-CN.d.ts +34 -0
  5. package/dist/client/cron-jobs-table/AutoBackupTable.d.ts +1 -0
  6. package/dist/client/cron-jobs-table/AutoBackupTable.schema.d.ts +629 -0
  7. package/dist/client/index.js +30 -1
  8. package/dist/client/locale/index.d.ts +4 -1
  9. package/dist/constants.d.ts +1 -0
  10. package/dist/constants.js +27 -0
  11. package/dist/externalVersion.js +7 -6
  12. package/dist/locale/en-US.json +1 -0
  13. package/dist/locale/zh-CN.json +30 -0
  14. package/dist/node_modules/@hapi/topo/package.json +1 -1
  15. package/dist/node_modules/archiver/package.json +1 -1
  16. package/dist/node_modules/cron-parser/LICENSE +21 -0
  17. package/dist/node_modules/cron-parser/lib/date.js +252 -0
  18. package/dist/node_modules/cron-parser/lib/expression.js +1002 -0
  19. package/dist/node_modules/cron-parser/lib/field_compactor.js +70 -0
  20. package/dist/node_modules/cron-parser/lib/field_stringify.js +58 -0
  21. package/dist/node_modules/cron-parser/lib/parser.js +1 -0
  22. package/dist/node_modules/cron-parser/package.json +1 -0
  23. package/dist/node_modules/cron-parser/types/common.d.ts +131 -0
  24. package/dist/node_modules/cron-parser/types/index.d.ts +45 -0
  25. package/dist/node_modules/cron-parser/types/ts3/index.d.ts +28 -0
  26. package/dist/node_modules/mkdirp/package.json +1 -1
  27. package/dist/node_modules/semver/package.json +1 -1
  28. package/dist/node_modules/yauzl/package.json +1 -1
  29. package/dist/server/collections/autoBackup.d.ts +2 -0
  30. package/dist/server/collections/autoBackup.js +64 -0
  31. package/dist/server/commands/backup-command.d.ts +2 -0
  32. package/dist/server/commands/backup-command.js +78 -0
  33. package/dist/server/dumper.d.ts +10 -4
  34. package/dist/server/dumper.js +23 -32
  35. package/dist/server/model/AutoBackupModel.d.ts +15 -0
  36. package/dist/server/model/AutoBackupModel.js +29 -0
  37. package/dist/server/resourcers/backup-files.js +30 -16
  38. package/dist/server/server.d.ts +12 -1
  39. package/dist/server/server.js +185 -6
  40. package/dist/server/utils/files.d.ts +6 -0
  41. package/dist/server/utils/files.js +59 -0
  42. package/package.json +14 -9
@@ -0,0 +1,2 @@
1
+ import { CollectionOptions } from '@tachybase/database';
2
+ export default function (): CollectionOptions;
@@ -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,2 @@
1
+ import { Application } from '@tachybase/server';
2
+ export default function addBackupCommand(app: Application): void;
@@ -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
+ }
@@ -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<void>;
53
- cleanLockFile(fileName: string, appName?: string): Promise<void>;
54
- runDumpTask(options: Omit<DumpOptions, 'fileName'>): Promise<string>;
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
  }>;
@@ -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
- return {
64
- name: fileName,
65
- inProgress: true,
66
- status: "in_progress"
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 runDumpTask(options) {
198
+ async getLockFile(appName) {
191
199
  const backupFileName = _Dumper.generateFileName();
192
- await this.writeLockFile(backupFileName, options.appName);
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
- try {
127
- taskId = await app.worker.callPluginMethod({
128
- plugin: import_server2.default,
129
- method: "workerCreateBackUp",
130
- params: {
131
- dataTypes: data.dataTypes
132
- },
133
- // 目前限制方法并发为1
134
- concurrency: 1
135
- });
136
- app.noticeManager.notify("backup", { level: "info", msg: ctx.t("Done") });
137
- } catch (error) {
138
- ctx.throw(500, ctx.t(error.message, { ns: "worker-thread" }));
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
- taskId = await plugin.workerCreateBackUp(data);
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
@@ -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
- }): Promise<string>;
16
+ appName: string;
17
+ filename: string;
18
+ }): Promise<void>;
8
19
  }
@@ -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
- class PluginBackupRestoreServer extends import_server.Plugin {
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
- const dumper = new import_dumper.Dumper(this.app);
48
- return dumper.runDumpTask({
224
+ await new import_dumper.Dumper(this.app).runDumpTask({
49
225
  groups: new Set(data.dataTypes),
50
- appName: this.app.name
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,6 @@
1
+ /**
2
+ * 清理指定目录中旧的 .tbdump 文件,仅保留最新的 maxNumber 个文件
3
+ * @param dirPath 目标目录路径
4
+ * @param maxNumber 要保留的最新文件数量
5
+ */
6
+ export declare function cleanOldFiles(dirPath: string, maxNumber: number): Promise<void>;
@@ -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
+ });