@tachybase/plugin-database-clean 1.6.2

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 (53) hide show
  1. package/README.md +134 -0
  2. package/README.zh-CN.md +136 -0
  3. package/client.d.ts +2 -0
  4. package/client.js +1 -0
  5. package/dist/client/index.d.ts +1 -0
  6. package/dist/client/index.js +1 -0
  7. package/dist/client/locale.d.ts +7 -0
  8. package/dist/client/pages/TableDetail.d.ts +1 -0
  9. package/dist/client/pages/TableList.d.ts +1 -0
  10. package/dist/client/plugin.d.ts +7 -0
  11. package/dist/externalVersion.js +11 -0
  12. package/dist/index.d.ts +2 -0
  13. package/dist/index.js +39 -0
  14. package/dist/locale/en-US.json +65 -0
  15. package/dist/locale/zh-CN.json +65 -0
  16. package/dist/node_modules/archiver/LICENSE +22 -0
  17. package/dist/node_modules/archiver/index.js +73 -0
  18. package/dist/node_modules/archiver/lib/core.js +974 -0
  19. package/dist/node_modules/archiver/lib/error.js +40 -0
  20. package/dist/node_modules/archiver/lib/plugins/json.js +110 -0
  21. package/dist/node_modules/archiver/lib/plugins/tar.js +167 -0
  22. package/dist/node_modules/archiver/lib/plugins/zip.js +120 -0
  23. package/dist/node_modules/archiver/package.json +1 -0
  24. package/dist/server/adapters/base-adapter.d.ts +63 -0
  25. package/dist/server/adapters/base-adapter.js +31 -0
  26. package/dist/server/adapters/index.d.ts +36 -0
  27. package/dist/server/adapters/index.js +86 -0
  28. package/dist/server/adapters/mysql-adapter.d.ts +13 -0
  29. package/dist/server/adapters/mysql-adapter.js +118 -0
  30. package/dist/server/adapters/postgres-adapter.d.ts +13 -0
  31. package/dist/server/adapters/postgres-adapter.js +115 -0
  32. package/dist/server/adapters/sqlite-adapter.d.ts +13 -0
  33. package/dist/server/adapters/sqlite-adapter.js +115 -0
  34. package/dist/server/collections/.gitkeep +0 -0
  35. package/dist/server/constants.d.ts +4 -0
  36. package/dist/server/constants.js +38 -0
  37. package/dist/server/index.d.ts +1 -0
  38. package/dist/server/index.js +33 -0
  39. package/dist/server/plugin.d.ts +11 -0
  40. package/dist/server/plugin.js +61 -0
  41. package/dist/server/resourcers/database-clean.d.ts +31 -0
  42. package/dist/server/resourcers/database-clean.js +303 -0
  43. package/dist/server/services/database-service.d.ts +46 -0
  44. package/dist/server/services/database-service.js +138 -0
  45. package/dist/server/services/filtered-backup-service.d.ts +40 -0
  46. package/dist/server/services/filtered-backup-service.js +269 -0
  47. package/dist/server/utils/lock.d.ts +23 -0
  48. package/dist/server/utils/lock.js +62 -0
  49. package/dist/server/utils.d.ts +2 -0
  50. package/dist/server/utils.js +43 -0
  51. package/package.json +26 -0
  52. package/server.d.ts +2 -0
  53. package/server.js +1 -0
@@ -0,0 +1,269 @@
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 filtered_backup_service_exports = {};
29
+ __export(filtered_backup_service_exports, {
30
+ FilteredBackupService: () => FilteredBackupService
31
+ });
32
+ module.exports = __toCommonJS(filtered_backup_service_exports);
33
+ var import_node_fs = __toESM(require("node:fs"));
34
+ var import_promises = __toESM(require("node:fs/promises"));
35
+ var import_node_path = __toESM(require("node:path"));
36
+ var import_node_stream = __toESM(require("node:stream"));
37
+ var import_node_util = __toESM(require("node:util"));
38
+ var import_archiver = __toESM(require("archiver"));
39
+ var import_dayjs = __toESM(require("dayjs"));
40
+ var import_lodash = __toESM(require("lodash"));
41
+ var import_adapters = require("../adapters");
42
+ var import_utils = require("../utils");
43
+ const finished = import_node_util.default.promisify(import_node_stream.default.finished);
44
+ class FieldValueWriter {
45
+ static toDumpedValue(field, val) {
46
+ if (val === null) return val;
47
+ if (field.type === "point" || field.type === "lineString" || field.type === "circle" || field.type === "polygon") {
48
+ const mockObj = {
49
+ getDataValue: () => val
50
+ };
51
+ const newValue = field.options.get.call(mockObj);
52
+ return newValue;
53
+ }
54
+ return val;
55
+ }
56
+ }
57
+ class FilteredBackupService {
58
+ constructor(app) {
59
+ this.app = app;
60
+ this.workDir = import_node_path.default.resolve(process.env.TEGO_RUNTIME_HOME || process.cwd(), "storage", "temp", "database-clean");
61
+ }
62
+ /**
63
+ * 生成备份文件名
64
+ */
65
+ static generateFileName(collectionName, filter) {
66
+ var _a, _b, _c, _d, _e, _f;
67
+ const random = Math.floor(1e3 + Math.random() * 9e3);
68
+ const timestamp = (0, import_dayjs.default)().format("YYYYMMDD_HHmmss");
69
+ let rangeSuffix = "";
70
+ if (((_a = filter == null ? void 0 : filter.createdAt) == null ? void 0 : _a.$gte) || ((_b = filter == null ? void 0 : filter.createdAt) == null ? void 0 : _b.$lte)) {
71
+ const from = filter.createdAt.$gte ? (0, import_dayjs.default)(filter.createdAt.$gte).format("YYYYMMDD") : "start";
72
+ const to = filter.createdAt.$lte ? (0, import_dayjs.default)(filter.createdAt.$lte).format("YYYYMMDD") : "end";
73
+ rangeSuffix = `_createdAt_${from}_${to}`;
74
+ } else if (((_c = filter == null ? void 0 : filter.updatedAt) == null ? void 0 : _c.$gte) || ((_d = filter == null ? void 0 : filter.updatedAt) == null ? void 0 : _d.$lte)) {
75
+ const from = filter.updatedAt.$gte ? (0, import_dayjs.default)(filter.updatedAt.$gte).format("YYYYMMDD") : "start";
76
+ const to = filter.updatedAt.$lte ? (0, import_dayjs.default)(filter.updatedAt.$lte).format("YYYYMMDD") : "end";
77
+ rangeSuffix = `_updatedAt_${from}_${to}`;
78
+ } else if (((_e = filter == null ? void 0 : filter.id) == null ? void 0 : _e.$gte) !== void 0 || ((_f = filter == null ? void 0 : filter.id) == null ? void 0 : _f.$lte) !== void 0) {
79
+ const from = filter.id.$gte !== void 0 ? filter.id.$gte : "start";
80
+ const to = filter.id.$lte !== void 0 ? filter.id.$lte : "end";
81
+ rangeSuffix = `_id_${from}_${to}`;
82
+ }
83
+ return `db-clean_${collectionName}${rangeSuffix}_${timestamp}_${random}.tbdump`;
84
+ }
85
+ /**
86
+ * 备份单个集合的筛选数据
87
+ */
88
+ async backupCollection(options) {
89
+ import_adapters.DatabaseAdapterFactory.getAdapter(this.app);
90
+ const { collectionName, filter, fileName, appName } = options;
91
+ const collection = this.app.db.getCollection(collectionName);
92
+ if (!collection) {
93
+ throw new Error(`Collection ${collectionName} not found`);
94
+ }
95
+ await import_promises.default.mkdir(this.workDir, { recursive: true });
96
+ const collectionDataDir = import_node_path.default.resolve(this.workDir, "collections", collectionName);
97
+ await import_promises.default.mkdir(collectionDataDir, { recursive: true });
98
+ const attributes = collection.model.tableAttributes;
99
+ const columns = [...new Set(import_lodash.default.map(attributes, "field"))];
100
+ const whereConditions = [];
101
+ const replacements = {};
102
+ if (filter == null ? void 0 : filter.createdAt) {
103
+ if (filter.createdAt.$gte) {
104
+ whereConditions.push('"createdAt" >= :createdAtGte');
105
+ replacements.createdAtGte = filter.createdAt.$gte;
106
+ }
107
+ if (filter.createdAt.$lte) {
108
+ whereConditions.push('"createdAt" <= :createdAtLte');
109
+ replacements.createdAtLte = filter.createdAt.$lte;
110
+ }
111
+ }
112
+ if (filter == null ? void 0 : filter.updatedAt) {
113
+ if (filter.updatedAt.$gte) {
114
+ whereConditions.push('"updatedAt" >= :updatedAtGte');
115
+ replacements.updatedAtGte = filter.updatedAt.$gte;
116
+ }
117
+ if (filter.updatedAt.$lte) {
118
+ whereConditions.push('"updatedAt" <= :updatedAtLte');
119
+ replacements.updatedAtLte = filter.updatedAt.$lte;
120
+ }
121
+ }
122
+ if (filter == null ? void 0 : filter.id) {
123
+ const primaryKey = collection.model.primaryKeyAttribute || "id";
124
+ if (filter.id.$gte !== void 0) {
125
+ whereConditions.push(`"${primaryKey}" >= :idGte`);
126
+ replacements.idGte = filter.id.$gte;
127
+ }
128
+ if (filter.id.$lte !== void 0) {
129
+ whereConditions.push(`"${primaryKey}" <= :idLte`);
130
+ replacements.idLte = filter.id.$lte;
131
+ }
132
+ }
133
+ const whereClause = whereConditions.length > 0 ? `WHERE ${whereConditions.join(" AND ")}` : "";
134
+ const dataFilePath = import_node_path.default.resolve(collectionDataDir, "data");
135
+ const dataStream = import_node_fs.default.createWriteStream(dataFilePath);
136
+ let count = 0;
137
+ const sql = `SELECT * FROM ${collection.isParent() ? "ONLY" : ""} ${collection.quotedTableName()} ${whereClause}`;
138
+ const rows = await this.app.db.sequelize.query((0, import_utils.sqlAdapter)(this.app.db, sql), {
139
+ type: "SELECT",
140
+ replacements
141
+ });
142
+ for (const row of rows) {
143
+ const rowData = JSON.stringify(
144
+ columns.map((col) => {
145
+ const val = row[col];
146
+ const field = collection.getField(col);
147
+ return field ? FieldValueWriter.toDumpedValue(field, val) : val;
148
+ })
149
+ );
150
+ if (!dataStream.write(rowData + "\r\n", "utf8")) {
151
+ await new Promise((resolve) => dataStream.once("drain", resolve));
152
+ }
153
+ count++;
154
+ }
155
+ dataStream.end();
156
+ await finished(dataStream);
157
+ const metaAttributes = import_lodash.default.mapValues(attributes, (attr, key) => {
158
+ var _a, _b, _c;
159
+ const collectionField = collection.getField(key);
160
+ const fieldOptionKeys = ["field", "primaryKey", "autoIncrement", "allowNull", "defaultValue", "unique"];
161
+ if (collectionField) {
162
+ const fieldAttributes = {
163
+ field: attr.field,
164
+ isCollectionField: true,
165
+ type: collectionField.type,
166
+ typeOptions: collectionField.options
167
+ };
168
+ if (((_c = (_b = (_a = fieldAttributes.typeOptions) == null ? void 0 : _a.defaultValue) == null ? void 0 : _b.constructor) == null ? void 0 : _c.name) === "UUIDV4") {
169
+ delete fieldAttributes.typeOptions.defaultValue;
170
+ }
171
+ return fieldAttributes;
172
+ }
173
+ return {
174
+ ...import_lodash.default.pick(attr, fieldOptionKeys),
175
+ type: attr.type.constructor.toString(),
176
+ isCollectionField: false,
177
+ typeOptions: attr.type.options
178
+ };
179
+ });
180
+ const meta = {
181
+ name: collectionName,
182
+ tableName: collection.getTableNameWithSchema(),
183
+ count,
184
+ columns,
185
+ attributes: metaAttributes,
186
+ filter
187
+ // 保存筛选条件
188
+ };
189
+ if (collection.options.inherits) {
190
+ meta["inherits"] = import_lodash.default.uniq(collection.options.inherits);
191
+ }
192
+ await import_promises.default.writeFile(import_node_path.default.resolve(collectionDataDir, "meta"), JSON.stringify(meta), "utf8");
193
+ const rootMeta = {
194
+ version: this.app.getVersion(),
195
+ dialect: this.app.db.sequelize.getDialect(),
196
+ DB_UNDERSCORED: process.env.DB_UNDERSCORED || "false",
197
+ DB_SCHEMA: process.env.DB_SCHEMA || "",
198
+ COLLECTION_MANAGER_SCHEMA: process.env.COLLECTION_MANAGER_SCHEMA || "",
199
+ DB_TABLE_PREFIX: process.env.DB_TABLE_PREFIX || "",
200
+ // 标记为 database-clean 插件生成的部分备份
201
+ backupType: "database-clean",
202
+ dumpableCollectionsGroupByGroup: {
203
+ custom: [
204
+ {
205
+ name: collectionName,
206
+ group: "custom",
207
+ origin: "@tachybase/plugin-database-clean",
208
+ title: collectionName,
209
+ isView: false
210
+ }
211
+ ]
212
+ },
213
+ dumpedGroups: ["custom"],
214
+ delayCollections: [],
215
+ filter
216
+ // 保存筛选条件供参考
217
+ };
218
+ await import_promises.default.writeFile(import_node_path.default.resolve(this.workDir, "meta"), JSON.stringify(rootMeta), "utf8");
219
+ const backupFileName = fileName || FilteredBackupService.generateFileName(collectionName, filter);
220
+ const filePath = await this.packDumpedDir(backupFileName, appName);
221
+ await this.clearWorkDir();
222
+ return {
223
+ fileName: backupFileName,
224
+ filePath: filePath.filePath
225
+ };
226
+ }
227
+ async packDumpedDir(fileName, appName) {
228
+ const storageDir = this.getStorageDir(appName);
229
+ await import_promises.default.mkdir(storageDir, { recursive: true });
230
+ const filePath = import_node_path.default.resolve(storageDir, fileName);
231
+ const output = import_node_fs.default.createWriteStream(filePath);
232
+ const archive = (0, import_archiver.default)("zip", {
233
+ zlib: { level: 9 }
234
+ });
235
+ const onClose = new Promise((resolve, reject) => {
236
+ output.on("close", function() {
237
+ resolve({
238
+ filePath,
239
+ dirname: storageDir
240
+ });
241
+ });
242
+ archive.on("error", function(err) {
243
+ reject(err);
244
+ });
245
+ });
246
+ archive.pipe(output);
247
+ archive.directory(this.workDir, false);
248
+ await archive.finalize();
249
+ await onClose;
250
+ return { filePath, dirname: storageDir };
251
+ }
252
+ getStorageDir(appName) {
253
+ if (appName && appName !== "main") {
254
+ return import_node_path.default.resolve(process.env.TEGO_RUNTIME_HOME || process.cwd(), "storage", "backups", appName);
255
+ }
256
+ return import_node_path.default.resolve(process.env.TEGO_RUNTIME_HOME || process.cwd(), "storage", "backups");
257
+ }
258
+ async clearWorkDir() {
259
+ try {
260
+ await import_promises.default.rm(this.workDir, { recursive: true, force: true });
261
+ } catch (error) {
262
+ this.app.logger.warn("Failed to clear work directory:", error);
263
+ }
264
+ }
265
+ }
266
+ // Annotate the CommonJS export names for ESM import in node:
267
+ 0 && (module.exports = {
268
+ FilteredBackupService
269
+ });
@@ -0,0 +1,23 @@
1
+ import { Application } from '@tego/server';
2
+ /**
3
+ * 基于缓存的锁实现,用于防止并发操作
4
+ */
5
+ export declare class DatabaseCleanLock {
6
+ private app;
7
+ private cache;
8
+ private readonly LOCK_PREFIX;
9
+ private readonly LOCK_TTL;
10
+ constructor(app: Application);
11
+ /**
12
+ * 获取锁
13
+ */
14
+ acquire(collectionName: string): Promise<boolean>;
15
+ /**
16
+ * 释放锁
17
+ */
18
+ release(collectionName: string): Promise<void>;
19
+ /**
20
+ * 检查锁是否存在
21
+ */
22
+ isLocked(collectionName: string): Promise<boolean>;
23
+ }
@@ -0,0 +1,62 @@
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 lock_exports = {};
19
+ __export(lock_exports, {
20
+ DatabaseCleanLock: () => DatabaseCleanLock
21
+ });
22
+ module.exports = __toCommonJS(lock_exports);
23
+ class DatabaseCleanLock {
24
+ // 2小时
25
+ constructor(app) {
26
+ this.app = app;
27
+ this.LOCK_PREFIX = "database-clean:lock:";
28
+ this.LOCK_TTL = 2 * 60 * 60 * 1e3;
29
+ this.cache = app.cache;
30
+ }
31
+ /**
32
+ * 获取锁
33
+ */
34
+ async acquire(collectionName) {
35
+ const lockKey = `${this.LOCK_PREFIX}${collectionName}`;
36
+ const existingLock = await this.cache.get(lockKey);
37
+ if (existingLock) {
38
+ return false;
39
+ }
40
+ await this.cache.set(lockKey, Date.now(), this.LOCK_TTL);
41
+ return true;
42
+ }
43
+ /**
44
+ * 释放锁
45
+ */
46
+ async release(collectionName) {
47
+ const lockKey = `${this.LOCK_PREFIX}${collectionName}`;
48
+ await this.cache.del(lockKey);
49
+ }
50
+ /**
51
+ * 检查锁是否存在
52
+ */
53
+ async isLocked(collectionName) {
54
+ const lockKey = `${this.LOCK_PREFIX}${collectionName}`;
55
+ const lock = await this.cache.get(lockKey);
56
+ return !!lock;
57
+ }
58
+ }
59
+ // Annotate the CommonJS export names for ESM import in node:
60
+ 0 && (module.exports = {
61
+ DatabaseCleanLock
62
+ });
@@ -0,0 +1,2 @@
1
+ import { Database } from '@tego/server';
2
+ export declare function sqlAdapter(database: Database, sql: string): string;
@@ -0,0 +1,43 @@
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 utils_exports = {};
29
+ __export(utils_exports, {
30
+ sqlAdapter: () => sqlAdapter
31
+ });
32
+ module.exports = __toCommonJS(utils_exports);
33
+ var import_lodash = __toESM(require("lodash"));
34
+ function sqlAdapter(database, sql) {
35
+ if (database.isMySQLCompatibleDialect()) {
36
+ return import_lodash.default.replace(sql, /"/g, "`");
37
+ }
38
+ return sql;
39
+ }
40
+ // Annotate the CommonJS export names for ESM import in node:
41
+ 0 && (module.exports = {
42
+ sqlAdapter
43
+ });
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@tachybase/plugin-database-clean",
3
+ "displayName": "Database Cleanup Tool",
4
+ "version": "1.6.2",
5
+ "description": "Clean up database.",
6
+ "main": "dist/server/index.js",
7
+ "devDependencies": {
8
+ "@ant-design/icons": "^5.6.1",
9
+ "@tachybase/client": "1.6.1",
10
+ "@tachybase/test": "1.6.1",
11
+ "@tego/client": "1.6.1",
12
+ "@tego/server": "1.6.1",
13
+ "@types/archiver": "^5.3.4",
14
+ "@types/file-saver": "^2.0.7",
15
+ "@types/lodash": "^4.17.20",
16
+ "antd": "5.22.5",
17
+ "archiver": "^7.0.1",
18
+ "dayjs": "1.11.13",
19
+ "file-saver": "^2.0.5",
20
+ "lodash": "4.17.21",
21
+ "react-router-dom": "6.28.1",
22
+ "sequelize": "^6.37.5"
23
+ },
24
+ "description.zh-CN": "数据库清理。",
25
+ "displayName.zh-CN": "数据库清理工具"
26
+ }
package/server.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './dist/server';
2
+ export { default } from './dist/server';
package/server.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require('./dist/server/index.js');