@nocobase/flow-engine 2.0.40 → 2.0.42
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/lib/components/FlowModelRenderer.js +10 -6
- package/lib/models/flowModel.d.ts +2 -1
- package/lib/models/flowModel.js +11 -2
- package/package.json +4 -4
- package/src/components/FlowModelRenderer.tsx +9 -5
- package/src/components/__tests__/FlowModelRenderer.test.tsx +43 -2
- package/src/models/flowModel.tsx +13 -4
|
@@ -61,11 +61,12 @@ const FlowModelRendererWithAutoFlows = (0, import_reactive.observer)(
|
|
|
61
61
|
showErrorFallback,
|
|
62
62
|
settingsMenuLevel,
|
|
63
63
|
extraToolbarItems,
|
|
64
|
-
fallback
|
|
64
|
+
fallback,
|
|
65
|
+
useCache
|
|
65
66
|
}) => {
|
|
66
67
|
const { loading: pending, error: autoFlowsError } = (0, import_hooks.useApplyAutoFlows)(model, inputArgs, {
|
|
67
68
|
throwOnError: false,
|
|
68
|
-
useCache
|
|
69
|
+
useCache
|
|
69
70
|
});
|
|
70
71
|
(0, import_utils.setAutoFlowError)(model, autoFlowsError || null);
|
|
71
72
|
if (pending) {
|
|
@@ -195,13 +196,15 @@ const FlowModelRenderer = (0, import_reactive.observer)(
|
|
|
195
196
|
extraToolbarItems,
|
|
196
197
|
useCache
|
|
197
198
|
}) => {
|
|
199
|
+
var _a;
|
|
200
|
+
const resolvedUseCache = typeof useCache === "boolean" ? useCache : (_a = model == null ? void 0 : model.context) == null ? void 0 : _a.useCache;
|
|
198
201
|
(0, import_react.useEffect)(() => {
|
|
199
|
-
if (model == null ? void 0 : model.context) {
|
|
202
|
+
if ((model == null ? void 0 : model.context) && typeof resolvedUseCache !== "undefined") {
|
|
200
203
|
model.context.defineProperty("useCache", {
|
|
201
|
-
value:
|
|
204
|
+
value: resolvedUseCache
|
|
202
205
|
});
|
|
203
206
|
}
|
|
204
|
-
}, [model == null ? void 0 : model.context,
|
|
207
|
+
}, [model == null ? void 0 : model.context, resolvedUseCache]);
|
|
205
208
|
if (!model || typeof model.render !== "function") {
|
|
206
209
|
console.warn("FlowModelRenderer: Invalid model or render method not found.", model);
|
|
207
210
|
return null;
|
|
@@ -218,7 +221,8 @@ const FlowModelRenderer = (0, import_reactive.observer)(
|
|
|
218
221
|
showErrorFallback,
|
|
219
222
|
settingsMenuLevel,
|
|
220
223
|
extraToolbarItems,
|
|
221
|
-
fallback
|
|
224
|
+
fallback,
|
|
225
|
+
useCache: resolvedUseCache
|
|
222
226
|
}
|
|
223
227
|
);
|
|
224
228
|
if (showErrorFallback) {
|
|
@@ -13,6 +13,7 @@ import { FlowContext, FlowModelContext, FlowRuntimeContext } from '../flowContex
|
|
|
13
13
|
import { FlowEngine } from '../flowEngine';
|
|
14
14
|
import type { ActionDefinition, ArrayElementType, CreateModelOptions, CreateSubModelOptions, DefaultStructure, FlowDefinitionOptions, FlowModelMeta, FlowModelOptions, ModelConstructor, ParamObject, ParentFlowModel, PersistOptions, ResolveUseResult, StepParams } from '../types';
|
|
15
15
|
import { IModelComponentProps, ReadonlyModelProps } from '../types';
|
|
16
|
+
import type { MenuProps } from 'antd';
|
|
16
17
|
import { ModelActionRegistry } from '../action-registry/ModelActionRegistry';
|
|
17
18
|
import { ModelEventRegistry } from '../event-registry/ModelEventRegistry';
|
|
18
19
|
import { GlobalFlowRegistry } from '../flow-registry/GlobalFlowRegistry';
|
|
@@ -21,7 +22,6 @@ import { FlowSettingsOpenOptions } from '../flowSettings';
|
|
|
21
22
|
import type { ScheduleOptions } from '../scheduler/ModelOperationScheduler';
|
|
22
23
|
import type { DispatchEventOptions, EventDefinition } from '../types';
|
|
23
24
|
import { ForkFlowModel } from './forkFlowModel';
|
|
24
|
-
import type { MenuProps } from 'antd';
|
|
25
25
|
type BaseMenuItem = NonNullable<MenuProps['items']>[number];
|
|
26
26
|
type MenuLeafItem = Exclude<BaseMenuItem, {
|
|
27
27
|
children: MenuProps['items'];
|
|
@@ -239,6 +239,7 @@ export declare class FlowModel<Structure extends DefaultStructure = DefaultStruc
|
|
|
239
239
|
* 使用 lodash debounce 避免频繁调用
|
|
240
240
|
*/
|
|
241
241
|
private _rerunLastAutoRun;
|
|
242
|
+
private resetAutoRunState;
|
|
242
243
|
/**
|
|
243
244
|
* 通用事件分发钩子:开始
|
|
244
245
|
* 子类可覆盖;beforeRender 事件可通过抛出 FlowExitException 提前终止。
|
package/lib/models/flowModel.js
CHANGED
|
@@ -63,12 +63,12 @@ var import_InstanceFlowRegistry = require("../flow-registry/InstanceFlowRegistry
|
|
|
63
63
|
var import_flowContext = require("../flowContext");
|
|
64
64
|
var import_utils = require("../utils");
|
|
65
65
|
var import_lib = require("antd/lib");
|
|
66
|
+
var import__ = require("..");
|
|
66
67
|
var import_ModelActionRegistry = require("../action-registry/ModelActionRegistry");
|
|
67
68
|
var import_utils2 = require("../components/subModel/utils");
|
|
68
69
|
var import_ModelEventRegistry = require("../event-registry/ModelEventRegistry");
|
|
69
70
|
var import_GlobalFlowRegistry = require("../flow-registry/GlobalFlowRegistry");
|
|
70
71
|
var import_forkFlowModel = require("./forkFlowModel");
|
|
71
|
-
var import__ = require("..");
|
|
72
72
|
var _flowContext;
|
|
73
73
|
const classActionRegistries = /* @__PURE__ */ new WeakMap();
|
|
74
74
|
const classEventRegistries = /* @__PURE__ */ new WeakMap();
|
|
@@ -183,10 +183,13 @@ const _FlowModel = class _FlowModel {
|
|
|
183
183
|
if (changed.type === "set" && import_lodash.default.isEqual(changed.value, changed.oldValue)) {
|
|
184
184
|
return;
|
|
185
185
|
}
|
|
186
|
+
const hasLastAutoRun = !!this._lastAutoRunParams;
|
|
186
187
|
if (this.flowEngine) {
|
|
187
188
|
this.invalidateFlowCache("beforeRender");
|
|
188
189
|
}
|
|
189
|
-
|
|
190
|
+
if (hasLastAutoRun) {
|
|
191
|
+
this._rerunLastAutoRun();
|
|
192
|
+
}
|
|
190
193
|
this.forks.forEach((fork) => {
|
|
191
194
|
fork.rerender();
|
|
192
195
|
});
|
|
@@ -674,6 +677,11 @@ const _FlowModel = class _FlowModel {
|
|
|
674
677
|
}, "isMatch");
|
|
675
678
|
return Array.from(allFlows.values()).filter(isMatch);
|
|
676
679
|
}
|
|
680
|
+
resetAutoRunState() {
|
|
681
|
+
var _a, _b;
|
|
682
|
+
(_b = (_a = this._rerunLastAutoRun) == null ? void 0 : _a.cancel) == null ? void 0 : _b.call(_a);
|
|
683
|
+
this._lastAutoRunParams = null;
|
|
684
|
+
}
|
|
677
685
|
/**
|
|
678
686
|
* 通用事件分发钩子:开始
|
|
679
687
|
* 子类可覆盖;beforeRender 事件可通过抛出 FlowExitException 提前终止。
|
|
@@ -763,6 +771,7 @@ const _FlowModel = class _FlowModel {
|
|
|
763
771
|
}));
|
|
764
772
|
return () => {
|
|
765
773
|
var _a3, _b3;
|
|
774
|
+
renderTarget.resetAutoRunState();
|
|
766
775
|
if (typeof renderTarget.onUnmount === "function") {
|
|
767
776
|
renderTarget.onUnmount();
|
|
768
777
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/flow-engine",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.42",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A standalone flow engine for NocoBase, managing workflows, models, and actions.",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@formily/antd-v5": "1.x",
|
|
10
10
|
"@formily/reactive": "2.x",
|
|
11
|
-
"@nocobase/sdk": "2.0.
|
|
12
|
-
"@nocobase/shared": "2.0.
|
|
11
|
+
"@nocobase/sdk": "2.0.42",
|
|
12
|
+
"@nocobase/shared": "2.0.42",
|
|
13
13
|
"ahooks": "^3.7.2",
|
|
14
14
|
"axios": "^1.7.0",
|
|
15
15
|
"dayjs": "^1.11.9",
|
|
@@ -37,5 +37,5 @@
|
|
|
37
37
|
],
|
|
38
38
|
"author": "NocoBase Team",
|
|
39
39
|
"license": "Apache-2.0",
|
|
40
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "5365bf0f04c602ffaac61d758a9de9a1c3cffd9b"
|
|
41
41
|
}
|
|
@@ -127,6 +127,7 @@ const FlowModelRendererWithAutoFlows: React.FC<{
|
|
|
127
127
|
settingsMenuLevel?: number;
|
|
128
128
|
extraToolbarItems?: ToolbarItemConfig[];
|
|
129
129
|
fallback?: React.ReactNode;
|
|
130
|
+
useCache?: boolean;
|
|
130
131
|
}> = observer(
|
|
131
132
|
({
|
|
132
133
|
model,
|
|
@@ -139,12 +140,12 @@ const FlowModelRendererWithAutoFlows: React.FC<{
|
|
|
139
140
|
settingsMenuLevel,
|
|
140
141
|
extraToolbarItems,
|
|
141
142
|
fallback,
|
|
143
|
+
useCache,
|
|
142
144
|
}) => {
|
|
143
145
|
// hidden 占位由模型自身处理;无需在此注入
|
|
144
|
-
|
|
145
146
|
const { loading: pending, error: autoFlowsError } = useApplyAutoFlows(model, inputArgs, {
|
|
146
147
|
throwOnError: false,
|
|
147
|
-
useCache
|
|
148
|
+
useCache,
|
|
148
149
|
});
|
|
149
150
|
// 将错误下沉到 model 实例上,供内容层读取(类型安全的 WeakMap 存储)
|
|
150
151
|
setAutoFlowError(model, autoFlowsError || null);
|
|
@@ -348,13 +349,15 @@ export const FlowModelRenderer: React.FC<FlowModelRendererProps> = observer(
|
|
|
348
349
|
extraToolbarItems,
|
|
349
350
|
useCache,
|
|
350
351
|
}) => {
|
|
352
|
+
const resolvedUseCache = typeof useCache === 'boolean' ? useCache : model?.context?.useCache;
|
|
353
|
+
|
|
351
354
|
useEffect(() => {
|
|
352
|
-
if (model?.context) {
|
|
355
|
+
if (model?.context && typeof resolvedUseCache !== 'undefined') {
|
|
353
356
|
model.context.defineProperty('useCache', {
|
|
354
|
-
value:
|
|
357
|
+
value: resolvedUseCache,
|
|
355
358
|
});
|
|
356
359
|
}
|
|
357
|
-
}, [model?.context,
|
|
360
|
+
}, [model?.context, resolvedUseCache]);
|
|
358
361
|
|
|
359
362
|
if (!model || typeof model.render !== 'function') {
|
|
360
363
|
// 可以选择渲染 null 或者一个错误/提示信息
|
|
@@ -375,6 +378,7 @@ export const FlowModelRenderer: React.FC<FlowModelRendererProps> = observer(
|
|
|
375
378
|
settingsMenuLevel={settingsMenuLevel}
|
|
376
379
|
extraToolbarItems={extraToolbarItems}
|
|
377
380
|
fallback={fallback}
|
|
381
|
+
useCache={resolvedUseCache}
|
|
378
382
|
/>
|
|
379
383
|
);
|
|
380
384
|
|
|
@@ -38,9 +38,8 @@ describe('FlowModelRenderer', () => {
|
|
|
38
38
|
test('should pass useCache to useApplyAutoFlows and set it on context', async () => {
|
|
39
39
|
const { unmount } = renderWithProvider(<FlowModelRenderer model={model} useCache={true} />);
|
|
40
40
|
|
|
41
|
-
// Check if dispatchEvent was called with useCache: true
|
|
42
|
-
// useApplyAutoFlows calls dispatchEvent('beforeRender', inputArgs, { useCache })
|
|
43
41
|
await waitFor(() => {
|
|
42
|
+
expect(model.dispatchEvent).toHaveBeenCalledTimes(1);
|
|
44
43
|
expect(model.dispatchEvent).toHaveBeenCalledWith(
|
|
45
44
|
'beforeRender',
|
|
46
45
|
undefined,
|
|
@@ -58,6 +57,7 @@ describe('FlowModelRenderer', () => {
|
|
|
58
57
|
const { unmount } = renderWithProvider(<FlowModelRenderer model={model} useCache={false} />);
|
|
59
58
|
|
|
60
59
|
await waitFor(() => {
|
|
60
|
+
expect(model.dispatchEvent).toHaveBeenCalledTimes(1);
|
|
61
61
|
expect(model.dispatchEvent).toHaveBeenCalledWith(
|
|
62
62
|
'beforeRender',
|
|
63
63
|
undefined,
|
|
@@ -74,6 +74,7 @@ describe('FlowModelRenderer', () => {
|
|
|
74
74
|
const { unmount } = renderWithProvider(<FlowModelRenderer model={model} />);
|
|
75
75
|
|
|
76
76
|
await waitFor(() => {
|
|
77
|
+
expect(model.dispatchEvent).toHaveBeenCalledTimes(1);
|
|
77
78
|
expect(model.dispatchEvent).toHaveBeenCalledWith(
|
|
78
79
|
'beforeRender',
|
|
79
80
|
undefined,
|
|
@@ -86,4 +87,44 @@ describe('FlowModelRenderer', () => {
|
|
|
86
87
|
|
|
87
88
|
unmount();
|
|
88
89
|
});
|
|
90
|
+
|
|
91
|
+
test('should clear stale beforeRender state after unmount when reusing the same model', async () => {
|
|
92
|
+
const statefulEngine = new FlowEngine();
|
|
93
|
+
|
|
94
|
+
class StatefulModel extends FlowModel {
|
|
95
|
+
render(): any {
|
|
96
|
+
return <div>Stateful Content</div>;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const statefulModel = new StatefulModel({
|
|
101
|
+
uid: 'stateful-model',
|
|
102
|
+
flowEngine: statefulEngine,
|
|
103
|
+
});
|
|
104
|
+
const executorSpy = vi.spyOn((statefulEngine as any).executor, 'dispatchEvent').mockResolvedValue([]);
|
|
105
|
+
|
|
106
|
+
const firstRender = renderWithProvider(<FlowModelRenderer model={statefulModel} />);
|
|
107
|
+
await waitFor(() => {
|
|
108
|
+
expect(executorSpy).toHaveBeenCalledTimes(1);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
firstRender.unmount();
|
|
112
|
+
|
|
113
|
+
executorSpy.mockClear();
|
|
114
|
+
statefulModel.setStepParams('anyFlow', 'anyStep', { x: 1 });
|
|
115
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
116
|
+
expect(executorSpy.mock.calls.length).toBe(0);
|
|
117
|
+
|
|
118
|
+
const secondRender = renderWithProvider(<FlowModelRenderer model={statefulModel} />);
|
|
119
|
+
await waitFor(() => {
|
|
120
|
+
expect(executorSpy).toHaveBeenCalledTimes(1);
|
|
121
|
+
});
|
|
122
|
+
const [target, eventName, inputArgs, options] = executorSpy.mock.calls[0];
|
|
123
|
+
expect(target).toBe(statefulModel);
|
|
124
|
+
expect(eventName).toBe('beforeRender');
|
|
125
|
+
expect(inputArgs).toBeUndefined();
|
|
126
|
+
expect(options).toMatchObject({ useCache: true });
|
|
127
|
+
|
|
128
|
+
secondRender.unmount();
|
|
129
|
+
});
|
|
89
130
|
});
|
package/src/models/flowModel.tsx
CHANGED
|
@@ -36,7 +36,9 @@ import type {
|
|
|
36
36
|
import { IModelComponentProps, ReadonlyModelProps } from '../types';
|
|
37
37
|
import { isInheritedFrom, setupRuntimeContextSteps } from '../utils';
|
|
38
38
|
// import { FlowExitAllException } from '../utils/exceptions';
|
|
39
|
+
import type { MenuProps } from 'antd';
|
|
39
40
|
import { Typography } from 'antd/lib';
|
|
41
|
+
import { observer } from '..';
|
|
40
42
|
import { ModelActionRegistry } from '../action-registry/ModelActionRegistry';
|
|
41
43
|
import { buildSubModelItem } from '../components/subModel/utils';
|
|
42
44
|
import { ModelEventRegistry } from '../event-registry/ModelEventRegistry';
|
|
@@ -46,8 +48,6 @@ import { FlowSettingsOpenOptions } from '../flowSettings';
|
|
|
46
48
|
import type { ScheduleOptions } from '../scheduler/ModelOperationScheduler';
|
|
47
49
|
import type { DispatchEventOptions, EventDefinition } from '../types';
|
|
48
50
|
import { ForkFlowModel } from './forkFlowModel';
|
|
49
|
-
import type { MenuProps } from 'antd';
|
|
50
|
-
import { observer } from '..';
|
|
51
51
|
|
|
52
52
|
// 使用 WeakMap 为每个类缓存一个 ModelActionRegistry 实例
|
|
53
53
|
const classActionRegistries = new WeakMap<typeof FlowModel, ModelActionRegistry>();
|
|
@@ -208,11 +208,14 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
208
208
|
if (changed.type === 'set' && _.isEqual(changed.value, changed.oldValue)) {
|
|
209
209
|
return;
|
|
210
210
|
}
|
|
211
|
+
const hasLastAutoRun = !!this._lastAutoRunParams;
|
|
211
212
|
|
|
212
213
|
if (this.flowEngine) {
|
|
213
214
|
this.invalidateFlowCache('beforeRender');
|
|
214
215
|
}
|
|
215
|
-
|
|
216
|
+
if (hasLastAutoRun) {
|
|
217
|
+
this._rerunLastAutoRun();
|
|
218
|
+
}
|
|
216
219
|
this.forks.forEach((fork) => {
|
|
217
220
|
fork.rerender();
|
|
218
221
|
});
|
|
@@ -858,6 +861,11 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
858
861
|
}
|
|
859
862
|
}, 100);
|
|
860
863
|
|
|
864
|
+
private resetAutoRunState(): void {
|
|
865
|
+
this._rerunLastAutoRun?.cancel?.();
|
|
866
|
+
this._lastAutoRunParams = null;
|
|
867
|
+
}
|
|
868
|
+
|
|
861
869
|
/**
|
|
862
870
|
* 通用事件分发钩子:开始
|
|
863
871
|
* 子类可覆盖;beforeRender 事件可通过抛出 FlowExitException 提前终止。
|
|
@@ -951,7 +959,7 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
951
959
|
}
|
|
952
960
|
|
|
953
961
|
// 创建缓存的响应式包装器组件工厂(只创建一次)
|
|
954
|
-
const createReactiveWrapper = (modelInstance:
|
|
962
|
+
const createReactiveWrapper = (modelInstance: FlowModel) => {
|
|
955
963
|
const ReactiveWrapper = observer(() => {
|
|
956
964
|
// 触发响应式更新的关键属性访问(读取 run/渲染目标的 props)
|
|
957
965
|
const renderTarget = modelInstance;
|
|
@@ -977,6 +985,7 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
977
985
|
model: renderTarget,
|
|
978
986
|
});
|
|
979
987
|
return () => {
|
|
988
|
+
renderTarget.resetAutoRunState();
|
|
980
989
|
if (typeof renderTarget.onUnmount === 'function') {
|
|
981
990
|
renderTarget.onUnmount();
|
|
982
991
|
}
|