@nocobase/plugin-collection-tree 2.1.0-beta.37 → 2.1.0-beta.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.
@@ -8,11 +8,11 @@
8
8
  */
9
9
 
10
10
  module.exports = {
11
- "@nocobase/client": "2.1.0-beta.37",
12
- "@nocobase/database": "2.1.0-beta.37",
13
- "@nocobase/utils": "2.1.0-beta.37",
11
+ "@nocobase/client": "2.1.0-beta.40",
12
+ "@nocobase/database": "2.1.0-beta.40",
13
+ "@nocobase/utils": "2.1.0-beta.40",
14
14
  "lodash": "4.18.1",
15
- "@nocobase/data-source-manager": "2.1.0-beta.37",
16
- "@nocobase/server": "2.1.0-beta.37",
15
+ "@nocobase/data-source-manager": "2.1.0-beta.40",
16
+ "@nocobase/server": "2.1.0-beta.40",
17
17
  "sequelize": "6.35.2"
18
18
  };
@@ -42,6 +42,7 @@ module.exports = __toCommonJS(adjacency_list_repository_exports);
42
42
  var import_database = require("@nocobase/database");
43
43
  var import_utils = require("@nocobase/utils");
44
44
  var import_lodash = __toESM(require("lodash"));
45
+ var import_cycle_detection = require("./cycle-detection");
45
46
  class AdjacencyListRepository extends import_database.Repository {
46
47
  async update(options) {
47
48
  return super.update({
@@ -118,13 +119,23 @@ class AdjacencyListRepository extends import_database.Repository {
118
119
  }
119
120
  nodeMap[`${node[foreignKey]}`].push(node);
120
121
  });
121
- function buildTree(rootId) {
122
- const children = nodeMap[rootId];
122
+ const getNodePrimaryKey = (node) => node.get(primaryKey);
123
+ function buildTree(rootId, path, pathIndex) {
124
+ const children = nodeMap[String(rootId)];
123
125
  if (!children) {
124
126
  return [];
125
127
  }
126
128
  return children.map((child) => {
127
- const childrenValues = buildTree(child.id);
129
+ const childPrimaryKey = getNodePrimaryKey(child);
130
+ const cycleStartIndex = pathIndex.get(String(childPrimaryKey));
131
+ if (cycleStartIndex !== void 0) {
132
+ throw new Error((0, import_cycle_detection.formatTreeCycleError)(collection.name, [...path.slice(cycleStartIndex), childPrimaryKey]));
133
+ }
134
+ pathIndex.set(String(childPrimaryKey), path.length);
135
+ path.push(childPrimaryKey);
136
+ const childrenValues = buildTree(childPrimaryKey, path, pathIndex);
137
+ path.pop();
138
+ pathIndex.delete(String(childPrimaryKey));
128
139
  if (childrenValues.length > 0) {
129
140
  child.setDataValue(childrenKey, childrenValues);
130
141
  }
@@ -132,8 +143,8 @@ class AdjacencyListRepository extends import_database.Repository {
132
143
  });
133
144
  }
134
145
  for (const root of rootNodes) {
135
- const rootId = root[primaryKey];
136
- const children = buildTree(rootId);
146
+ const rootId = getNodePrimaryKey(root);
147
+ const children = buildTree(rootId, [rootId], /* @__PURE__ */ new Map([[String(rootId), 0]]));
137
148
  if (children.length > 0) {
138
149
  root.setDataValue(childrenKey, children);
139
150
  }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ export type TreeNodeKey = string | number;
10
+ export declare function findCyclePath(path: TreeNodeKey[], nodeKey: TreeNodeKey): TreeNodeKey[] | null;
11
+ export declare function formatTreeCycleError(collectionName: string, cyclePath: TreeNodeKey[]): string;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
+ var cycle_detection_exports = {};
28
+ __export(cycle_detection_exports, {
29
+ findCyclePath: () => findCyclePath,
30
+ formatTreeCycleError: () => formatTreeCycleError
31
+ });
32
+ module.exports = __toCommonJS(cycle_detection_exports);
33
+ function findCyclePath(path, nodeKey) {
34
+ const nodeKeyString = String(nodeKey);
35
+ const cycleStartIndex = path.findIndex((pathNodeKey) => String(pathNodeKey) === nodeKeyString);
36
+ if (cycleStartIndex === -1) {
37
+ return null;
38
+ }
39
+ return [...path.slice(cycleStartIndex), nodeKey];
40
+ }
41
+ function formatTreeCycleError(collectionName, cyclePath) {
42
+ return `Cycle detected in ${collectionName}: ${cyclePath.join(" -> ")}`;
43
+ }
44
+ // Annotate the CommonJS export names for ESM import in node:
45
+ 0 && (module.exports = {
46
+ findCyclePath,
47
+ formatTreeCycleError
48
+ });
@@ -11,6 +11,7 @@ declare class PluginCollectionTreeServer extends Plugin {
11
11
  beforeLoad(): Promise<void>;
12
12
  private defineTreePathCollection;
13
13
  private getTreePath;
14
+ private validateTreeParent;
14
15
  private updateTreePath;
15
16
  }
16
17
  export default PluginCollectionTreeServer;
@@ -42,6 +42,7 @@ module.exports = __toCommonJS(plugin_exports);
42
42
  var import_data_source_manager = require("@nocobase/data-source-manager");
43
43
  var import_server = require("@nocobase/server");
44
44
  var import_lodash = __toESM(require("lodash"));
45
+ var import_cycle_detection = require("./cycle-detection");
45
46
  var import_tree_collection = require("./tree-collection");
46
47
  class PluginCollectionTreeServer extends import_server.Plugin {
47
48
  async beforeLoad() {
@@ -138,13 +139,28 @@ class PluginCollectionTreeServer extends import_server.Plugin {
138
139
  transaction: options2.transaction
139
140
  });
140
141
  });
141
- this.db.on(`${collection.name}.beforeSave`, async (model) => {
142
- var _a;
142
+ this.db.on(`${collection.name}.beforeSave`, async (model, options2) => {
143
+ var _a, _b;
143
144
  const tk = collection.filterTargetKey;
144
145
  const parentForeignKey = ((_a = collection.treeParentField) == null ? void 0 : _a.foreignKey) || "parentId";
145
- if (model.get(tk) && model.get(parentForeignKey) === model.get(tk)) {
146
+ const nodePrimaryKey = model.get(tk);
147
+ const parentPrimaryKey = model.get(parentForeignKey);
148
+ if (nodePrimaryKey !== null && nodePrimaryKey !== void 0 && parentPrimaryKey !== null && parentPrimaryKey !== void 0 && String(parentPrimaryKey) === String(nodePrimaryKey)) {
146
149
  throw new Error("Cannot set itself as the parent node");
147
150
  }
151
+ if (nodePrimaryKey === null || nodePrimaryKey === void 0 || parentPrimaryKey === null || parentPrimaryKey === void 0) {
152
+ return;
153
+ }
154
+ const isParentChanged = model.isNewRecord || typeof model.changed === "function" && Boolean(model.changed(parentForeignKey)) || ((_b = model["_changed"]) == null ? void 0 : _b.has(parentForeignKey));
155
+ if (!isParentChanged) {
156
+ return;
157
+ }
158
+ await this.validateTreeParent({
159
+ collection,
160
+ nodePrimaryKey,
161
+ parentPrimaryKey,
162
+ transaction: options2 == null ? void 0 : options2.transaction
163
+ });
148
164
  });
149
165
  this.db.on("collections.afterDestroy", async (collection2, { transaction }) => {
150
166
  const name2 = `main_${collection2.get("name")}_path`;
@@ -224,6 +240,35 @@ class PluginCollectionTreeServer extends import_server.Plugin {
224
240
  }
225
241
  return path;
226
242
  }
243
+ async validateTreeParent({
244
+ collection,
245
+ nodePrimaryKey,
246
+ parentPrimaryKey,
247
+ transaction
248
+ }) {
249
+ var _a;
250
+ const tk = collection.filterTargetKey;
251
+ const parentForeignKey = ((_a = collection.treeParentField) == null ? void 0 : _a.foreignKey) || "parentId";
252
+ const path = [nodePrimaryKey];
253
+ let currentParentPrimaryKey = parentPrimaryKey;
254
+ while (currentParentPrimaryKey !== null && currentParentPrimaryKey !== void 0) {
255
+ if ((0, import_cycle_detection.findCyclePath)(path, currentParentPrimaryKey)) {
256
+ throw new Error("Cannot set a descendant node as the parent node");
257
+ }
258
+ path.push(currentParentPrimaryKey);
259
+ const parent = await this.app.db.getRepository(collection.name).findOne({
260
+ fields: [tk, parentForeignKey],
261
+ filter: {
262
+ [tk]: currentParentPrimaryKey
263
+ },
264
+ transaction
265
+ });
266
+ if (!parent) {
267
+ return;
268
+ }
269
+ currentParentPrimaryKey = parent.get(parentForeignKey);
270
+ }
271
+ }
227
272
  async updateTreePath(model, collection, pathCollectionName, transaction) {
228
273
  const tk = collection.filterTargetKey;
229
274
  let path = `/${model.get(tk)}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/plugin-collection-tree",
3
- "version": "2.1.0-beta.37",
3
+ "version": "2.1.0-beta.40",
4
4
  "displayName": "Collection: Tree",
5
5
  "displayName.ru-RU": "Древовидная Коллекция",
6
6
  "displayName.zh-CN": "数据表:树",
@@ -17,5 +17,5 @@
17
17
  "@nocobase/test": "2.x"
18
18
  },
19
19
  "license": "Apache-2.0",
20
- "gitHead": "7132e5b83ecc0e42b54715eaf1429c72bcef34ae"
20
+ "gitHead": "36e906138f6305723abbef676a61006058feb5ea"
21
21
  }