@nocobase/plugin-workflow-loop 1.9.0-beta.1 → 1.9.0-beta.11

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/.env.example ADDED
@@ -0,0 +1,3 @@
1
+ # Limit of workflow loops executed in one loop node
2
+ # By default, no limit
3
+ # WORKFLOW_LOOP_LIMIT=100
@@ -13,9 +13,9 @@ module.exports = {
13
13
  "antd": "5.24.2",
14
14
  "@formily/antd-v5": "1.2.3",
15
15
  "@formily/react": "2.3.0",
16
- "@nocobase/client": "1.9.0-beta.1",
17
- "@nocobase/plugin-workflow": "1.9.0-beta.1",
16
+ "@nocobase/client": "1.9.0-beta.11",
17
+ "@nocobase/plugin-workflow": "1.9.0-beta.11",
18
18
  "react-i18next": "11.18.6",
19
- "@nocobase/evaluators": "1.9.0-beta.1",
20
- "@nocobase/server": "1.9.0-beta.1"
19
+ "@nocobase/evaluators": "1.9.0-beta.11",
20
+ "@nocobase/server": "1.9.0-beta.11"
21
21
  };
@@ -22,6 +22,7 @@ export default class extends Instruction {
22
22
  status: 1;
23
23
  result: {
24
24
  looped: number;
25
+ done: number;
25
26
  };
26
27
  }>;
27
28
  resume(node: FlowNodeModel, branchJob: any, processor: Processor): Promise<JobModel>;
@@ -42,6 +42,10 @@ module.exports = __toCommonJS(LoopInstruction_exports);
42
42
  var import_evaluators = __toESM(require("@nocobase/evaluators"));
43
43
  var import_plugin_workflow = require("@nocobase/plugin-workflow");
44
44
  var import_constants = require("../constants");
45
+ function getLoopLimit() {
46
+ const limit = process.env.WORKFLOW_LOOP_LIMIT;
47
+ return limit ? parseInt(limit, 10) : null;
48
+ }
45
49
  function getTargetLength(target) {
46
50
  let length = 0;
47
51
  if (typeof target === "number") {
@@ -65,16 +69,16 @@ class LoopInstruction_default extends import_plugin_workflow.Instruction {
65
69
  const [branch] = processor.getBranches(node);
66
70
  const target = processor.getParsedValue(node.config.target, node.id);
67
71
  const length = getTargetLength(target);
68
- const looped = 0;
72
+ const result = { looped: 0, done: 0 };
69
73
  if (!branch || !length) {
70
74
  return {
71
75
  status: import_plugin_workflow.JOB_STATUS.RESOLVED,
72
- result: { looped }
76
+ result
73
77
  };
74
78
  }
75
79
  const job = processor.saveJob({
76
80
  status: import_plugin_workflow.JOB_STATUS.PENDING,
77
- result: { looped },
81
+ result,
78
82
  nodeId: node.id,
79
83
  nodeKey: node.key,
80
84
  upstreamId: (prevJob == null ? void 0 : prevJob.id) ?? null
@@ -82,23 +86,33 @@ class LoopInstruction_default extends import_plugin_workflow.Instruction {
82
86
  if (node.config.condition) {
83
87
  const { checkpoint, calculation, expression, continueOnFalse } = node.config.condition ?? {};
84
88
  if ((calculation || expression) && !checkpoint) {
85
- const condition = calculateCondition(node, processor);
86
- if (!condition) {
89
+ while (result.looped < length) {
90
+ const condition = calculateCondition(node, processor);
91
+ if (condition) {
92
+ break;
93
+ }
94
+ result.looped += 1;
95
+ job.set("result", result);
87
96
  if (continueOnFalse) {
88
- job.set("result", { looped: 1 });
89
97
  processor.saveJob(job);
90
- } else {
91
- job.set({
92
- status: import_plugin_workflow.JOB_STATUS.RESOLVED,
93
- result: { looped, broken: true }
94
- });
95
- return job;
98
+ continue;
96
99
  }
100
+ job.set({
101
+ status: import_plugin_workflow.JOB_STATUS.RESOLVED,
102
+ result: { ...result, broken: true }
103
+ });
104
+ return job;
105
+ }
106
+ if (result.looped >= length) {
107
+ job.set({
108
+ status: import_plugin_workflow.JOB_STATUS.RESOLVED,
109
+ result
110
+ });
111
+ return job;
97
112
  }
98
113
  }
99
114
  }
100
115
  await processor.run(branch, job);
101
- return null;
102
116
  }
103
117
  async resume(node, branchJob, processor) {
104
118
  const job = processor.findBranchParentJob(branchJob, node);
@@ -112,28 +126,46 @@ class LoopInstruction_default extends import_plugin_workflow.Instruction {
112
126
  if (branchJob.id !== job.id && branchJob.status === import_plugin_workflow.JOB_STATUS.PENDING) {
113
127
  return null;
114
128
  }
115
- const nextIndex = result.looped + 1;
129
+ result.looped += 1;
116
130
  const target = processor.getParsedValue(loop.config.target, node.id);
117
131
  if (branchJob.status === import_plugin_workflow.JOB_STATUS.RESOLVED || branchJob.status < import_plugin_workflow.JOB_STATUS.PENDING && loop.config.exit === import_constants.EXIT.CONTINUE) {
118
- job.set({ result: { looped: nextIndex } });
119
- processor.saveJob(job);
132
+ result.done += 1;
120
133
  const length = getTargetLength(target);
121
- if (nextIndex < length) {
122
- if (loop.config.condition) {
123
- const { calculation, expression, continueOnFalse } = loop.config.condition ?? {};
124
- if (calculation || expression) {
134
+ if (loop.config.condition) {
135
+ const { calculation, expression, continueOnFalse } = loop.config.condition ?? {};
136
+ if (calculation || expression) {
137
+ while (result.looped < length) {
125
138
  const condition = calculateCondition(loop, processor);
126
- if (!condition && !continueOnFalse) {
139
+ if (condition) {
140
+ processor.logger.debug(`loop condition matched, continue inner branch (looped: ${result.looped})`);
141
+ break;
142
+ }
143
+ if (!continueOnFalse) {
144
+ processor.logger.debug(`loop condition not matches, break (looped: ${result.looped})`);
127
145
  job.set({
128
146
  status: import_plugin_workflow.JOB_STATUS.RESOLVED,
129
- result: { looped: nextIndex, broken: true }
147
+ result: { ...result, broken: true }
130
148
  });
131
149
  return job;
132
150
  }
151
+ result.looped += 1;
152
+ processor.logger.debug(`loop condition not matches, try next loop item (looped: ${result.looped})`);
133
153
  }
134
154
  }
135
- await processor.run(branch, job);
136
- return null;
155
+ }
156
+ job.set({ result });
157
+ job.changed("result", true);
158
+ processor.saveJob(job);
159
+ if (result.looped < length) {
160
+ const loopLimit = getLoopLimit();
161
+ if (!loopLimit || result.done < loopLimit) {
162
+ await processor.run(branch, job);
163
+ return;
164
+ }
165
+ job.set({
166
+ status: import_plugin_workflow.JOB_STATUS.ERROR,
167
+ result: { ...result, exceeded: true }
168
+ });
137
169
  } else {
138
170
  job.set({
139
171
  status: import_plugin_workflow.JOB_STATUS.RESOLVED
@@ -142,7 +174,7 @@ class LoopInstruction_default extends import_plugin_workflow.Instruction {
142
174
  } else {
143
175
  job.set(
144
176
  loop.config.exit ? {
145
- result: { looped: result.looped, broken: true },
177
+ result: { ...result, broken: true },
146
178
  status: import_plugin_workflow.JOB_STATUS.RESOLVED
147
179
  } : {
148
180
  status: branchJob.status
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "displayName.zh-CN": "工作流:循环节点",
5
5
  "description": "Used to repeat the sub-process processing of each value in an array, and can also be used for fixed times of sub-process processing.",
6
6
  "description.zh-CN": "用于对一个数组中的每个值进行重复的子流程处理,也可用于固定次数的重复子流程处理。",
7
- "version": "1.9.0-beta.1",
7
+ "version": "1.9.0-beta.11",
8
8
  "license": "AGPL-3.0",
9
9
  "main": "./dist/server/index.js",
10
10
  "homepage": "https://docs.nocobase.com/handbook/workflow-loop",
@@ -22,7 +22,7 @@
22
22
  "@nocobase/server": "1.x",
23
23
  "@nocobase/test": "1.x"
24
24
  },
25
- "gitHead": "e0597219574e23bbf15b57848cb9b0fb4953634f",
25
+ "gitHead": "373bc3f829a928bb3cd3dad327dedf3afe2ab407",
26
26
  "keywords": [
27
27
  "Workflow"
28
28
  ]