@nocobase/flow-engine 2.2.0-beta.8 → 2.3.0-alpha.1
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/FlowContextSelector.js +55 -12
- package/lib/components/FormItem.js +11 -7
- package/lib/components/MobilePopup.js +28 -10
- package/lib/components/MobilePopup.style.js +11 -1
- package/lib/components/variables/VariableHybridInput.d.ts +9 -0
- package/lib/components/variables/VariableHybridInput.js +146 -17
- package/lib/components/variables/VariableInput.js +19 -7
- package/lib/components/variables/VariableTag.js +48 -36
- package/lib/components/variables/types.d.ts +21 -0
- package/lib/flowContext.js +10 -6
- package/lib/flowEngine.js +6 -0
- package/lib/flowI18n.js +3 -3
- package/lib/types.d.ts +3 -1
- package/lib/types.js +1 -0
- package/lib/utils/dirtyAwareApiClient.js +267 -13
- package/lib/utils/loadedPageCache.d.ts +1 -0
- package/lib/utils/loadedPageCache.js +6 -0
- package/package.json +4 -4
- package/src/__tests__/flowContext.test.ts +23 -0
- package/src/__tests__/flowI18n.test.ts +11 -0
- package/src/__tests__/viewScopedFlowEngine.test.ts +72 -6
- package/src/components/FlowContextSelector.tsx +66 -11
- package/src/components/FormItem.tsx +12 -7
- package/src/components/MobilePopup.style.ts +12 -1
- package/src/components/MobilePopup.tsx +30 -10
- package/src/components/__tests__/FormItem.test.tsx +17 -2
- package/src/components/__tests__/MobilePopup.test.tsx +109 -0
- package/src/components/variables/VariableHybridInput.tsx +185 -14
- package/src/components/variables/VariableInput.tsx +32 -7
- package/src/components/variables/VariableTag.tsx +51 -37
- package/src/components/variables/__tests__/FlowContextSelector.test.tsx +60 -3
- package/src/components/variables/__tests__/VariableHybridInput.test.tsx +212 -0
- package/src/components/variables/__tests__/VariableInput.test.tsx +202 -6
- package/src/components/variables/__tests__/VariableTag.test.tsx +80 -0
- package/src/components/variables/types.ts +21 -0
- package/src/flowContext.ts +4 -3
- package/src/flowEngine.ts +6 -0
- package/src/flowI18n.ts +8 -3
- package/src/types.ts +2 -0
- package/src/utils/__tests__/dirtyAwareApiClient.test.ts +321 -0
- package/src/utils/dirtyAwareApiClient.ts +325 -13
- package/src/utils/loadedPageCache.ts +7 -0
|
@@ -47,35 +47,37 @@ var import_utils = require("./utils");
|
|
|
47
47
|
var import_useResolvedMetaTree = require("./useResolvedMetaTree");
|
|
48
48
|
var import_ahooks = require("ahooks");
|
|
49
49
|
var import_FlowContextProvider = require("../../FlowContextProvider");
|
|
50
|
+
const VARIABLE_PARSING_FAILED_TEXT = "Variable parsing failed";
|
|
50
51
|
const VariableTagComponent = /* @__PURE__ */ __name(({
|
|
51
52
|
value,
|
|
52
53
|
onClear,
|
|
54
|
+
disabled = false,
|
|
55
|
+
allowCustomTagInput = true,
|
|
53
56
|
className,
|
|
54
57
|
style,
|
|
55
58
|
metaTreeNode,
|
|
56
|
-
metaTree
|
|
59
|
+
metaTree,
|
|
60
|
+
resolvedPath
|
|
57
61
|
}) => {
|
|
58
62
|
const { resolvedMetaTree } = (0, import_useResolvedMetaTree.useResolvedMetaTree)(metaTree);
|
|
59
63
|
const ctx = (0, import_FlowContextProvider.useFlowContext)();
|
|
60
|
-
const { data:
|
|
64
|
+
const { data: displayState } = (0, import_ahooks.useRequest)(
|
|
61
65
|
async () => {
|
|
62
66
|
const resolveLabelFromPath = /* @__PURE__ */ __name(async (rawPath2) => {
|
|
63
|
-
if (!rawPath2) return null;
|
|
64
|
-
if (!Array.isArray(rawPath2)) return null;
|
|
65
|
-
if (!Array.isArray(resolvedMetaTree)) return null;
|
|
67
|
+
if (!rawPath2) return { label: null, resolved: false, attempted: false };
|
|
68
|
+
if (!Array.isArray(rawPath2)) return { label: null, resolved: false, attempted: false };
|
|
69
|
+
if (!Array.isArray(resolvedMetaTree)) return { label: null, resolved: false, attempted: false };
|
|
66
70
|
const topNames = new Set((resolvedMetaTree || []).map((n) => String(n == null ? void 0 : n.name)));
|
|
67
71
|
const path = !topNames.has(String(rawPath2[0])) ? rawPath2.slice(1) : rawPath2;
|
|
68
|
-
if (!path.length) return "";
|
|
72
|
+
if (!path.length) return { label: "", resolved: true, attempted: true };
|
|
69
73
|
let nodes = resolvedMetaTree;
|
|
70
74
|
const titleChain = [];
|
|
71
|
-
let matchedCount = 0;
|
|
72
75
|
for (let i = 0; i < path.length; i++) {
|
|
73
|
-
if (!nodes)
|
|
76
|
+
if (!nodes) return { label: null, resolved: false, attempted: true };
|
|
74
77
|
const seg = String(path[i]);
|
|
75
78
|
const node = nodes.find((n) => String(n == null ? void 0 : n.name) === seg);
|
|
76
|
-
if (!node)
|
|
79
|
+
if (!node) return { label: null, resolved: false, attempted: true };
|
|
77
80
|
titleChain.push(String(node.title ?? node.name ?? seg));
|
|
78
|
-
matchedCount = i + 1;
|
|
79
81
|
if (i < path.length - 1) {
|
|
80
82
|
if (Array.isArray(node.children)) {
|
|
81
83
|
nodes = node.children;
|
|
@@ -92,29 +94,37 @@ const VariableTagComponent = /* @__PURE__ */ __name(({
|
|
|
92
94
|
}
|
|
93
95
|
}
|
|
94
96
|
}
|
|
95
|
-
|
|
96
|
-
let label2 = titleChain.map(ctx.t).join("/");
|
|
97
|
-
if (matchedCount < path.length) {
|
|
98
|
-
const tail = path.slice(matchedCount).join("/");
|
|
99
|
-
label2 = tail ? `${label2}/${tail}` : label2;
|
|
100
|
-
}
|
|
101
|
-
return label2;
|
|
97
|
+
return { label: titleChain.map(ctx.t).join("/"), resolved: true, attempted: true };
|
|
102
98
|
}, "resolveLabelFromPath");
|
|
103
99
|
if (metaTreeNode == null ? void 0 : metaTreeNode.parentTitles) {
|
|
104
|
-
return
|
|
100
|
+
return {
|
|
101
|
+
text: [...metaTreeNode.parentTitles, metaTreeNode.title].map(ctx.t).join("/"),
|
|
102
|
+
invalid: false
|
|
103
|
+
};
|
|
105
104
|
}
|
|
106
105
|
if (metaTreeNode) {
|
|
107
|
-
const rawPath2 = (0, import_utils.parseValueToPath)(value) || metaTreeNode.paths;
|
|
108
|
-
const
|
|
109
|
-
|
|
106
|
+
const rawPath2 = resolvedPath || (0, import_utils.parseValueToPath)(value) || metaTreeNode.paths;
|
|
107
|
+
const result2 = await resolveLabelFromPath(rawPath2);
|
|
108
|
+
if (result2.resolved && result2.label != null) {
|
|
109
|
+
return { text: result2.label, invalid: false };
|
|
110
|
+
}
|
|
111
|
+
if (result2.attempted) {
|
|
112
|
+
return { text: VARIABLE_PARSING_FAILED_TEXT, invalid: true, rawValue: String(value ?? "") };
|
|
113
|
+
}
|
|
114
|
+
return { text: ctx.t(metaTreeNode.title) ?? "", invalid: false };
|
|
115
|
+
}
|
|
116
|
+
if (!value) return { text: String(value), invalid: false };
|
|
117
|
+
const rawPath = resolvedPath || (0, import_utils.parseValueToPath)(value);
|
|
118
|
+
const result = await resolveLabelFromPath(rawPath);
|
|
119
|
+
if (result.resolved && result.label != null) {
|
|
120
|
+
return { text: result.label, invalid: false };
|
|
121
|
+
}
|
|
122
|
+
if (result.attempted) {
|
|
123
|
+
return { text: VARIABLE_PARSING_FAILED_TEXT, invalid: true, rawValue: String(value ?? "") };
|
|
110
124
|
}
|
|
111
|
-
|
|
112
|
-
const rawPath = (0, import_utils.parseValueToPath)(value);
|
|
113
|
-
const label = await resolveLabelFromPath(rawPath);
|
|
114
|
-
if (label != null) return label;
|
|
115
|
-
return Array.isArray(rawPath) ? rawPath.join("/") : String(value);
|
|
125
|
+
return { text: Array.isArray(rawPath) ? rawPath.join("/") : String(value), invalid: false };
|
|
116
126
|
},
|
|
117
|
-
{ refreshDeps: [resolvedMetaTree, value, metaTreeNode] }
|
|
127
|
+
{ refreshDeps: [resolvedMetaTree, resolvedPath, value, metaTreeNode] }
|
|
118
128
|
);
|
|
119
129
|
const { token } = import_antd.theme.useToken();
|
|
120
130
|
const shrinkableOverflowItem = import_css.css`
|
|
@@ -129,12 +139,12 @@ const VariableTagComponent = /* @__PURE__ */ __name(({
|
|
|
129
139
|
}
|
|
130
140
|
`;
|
|
131
141
|
const customTagRender = /* @__PURE__ */ __name((props) => {
|
|
132
|
-
const
|
|
133
|
-
const
|
|
134
|
-
return /* @__PURE__ */ import_react.default.createElement(import_antd.Tooltip, { title:
|
|
142
|
+
const fullText = (displayState == null ? void 0 : displayState.text) || (typeof props.label === "string" ? props.label : String(props.label));
|
|
143
|
+
const tooltipText = (displayState == null ? void 0 : displayState.invalid) ? displayState.rawValue || fullText : fullText;
|
|
144
|
+
return /* @__PURE__ */ import_react.default.createElement(import_antd.Tooltip, { title: tooltipText, placement: "top", getPopupContainer: () => document.body }, /* @__PURE__ */ import_react.default.createElement(
|
|
135
145
|
import_antd.Tag,
|
|
136
146
|
{
|
|
137
|
-
color: "blue",
|
|
147
|
+
color: (displayState == null ? void 0 : displayState.invalid) ? "error" : "blue",
|
|
138
148
|
style: {
|
|
139
149
|
margin: `0 ${token.marginXXS || token.marginXS}px`,
|
|
140
150
|
borderRadius: token.borderRadiusSM,
|
|
@@ -175,12 +185,14 @@ const VariableTagComponent = /* @__PURE__ */ __name(({
|
|
|
175
185
|
flex: "1 1 auto",
|
|
176
186
|
...style
|
|
177
187
|
},
|
|
178
|
-
value:
|
|
179
|
-
mode: "tags",
|
|
188
|
+
value: (displayState == null ? void 0 : displayState.text) ? [displayState.text] : [],
|
|
189
|
+
mode: allowCustomTagInput ? "tags" : "multiple",
|
|
180
190
|
open: false,
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
191
|
+
showSearch: allowCustomTagInput,
|
|
192
|
+
searchValue: allowCustomTagInput ? void 0 : "",
|
|
193
|
+
allowClear: !disabled && !!onClear,
|
|
194
|
+
onClear: disabled ? void 0 : onClear,
|
|
195
|
+
disabled: disabled || !onClear,
|
|
184
196
|
variant: "outlined",
|
|
185
197
|
suffixIcon: null,
|
|
186
198
|
tagRender: customTagRender,
|
|
@@ -13,12 +13,30 @@ export interface FlowContextSelectorProps extends Omit<CascaderProps<ContextSele
|
|
|
13
13
|
value?: string;
|
|
14
14
|
onChange?: (value: string, metaTreeNode?: MetaTreeNode) => void;
|
|
15
15
|
children?: CascaderProps<ContextSelectorItem>['children'];
|
|
16
|
+
/**
|
|
17
|
+
* Controls whether the default `x` trigger button is rendered as active
|
|
18
|
+
* (`type="primary"`). When omitted, the selector falls back to its parsed
|
|
19
|
+
* `value` path (`true` iff a valid variable path is currently selected).
|
|
20
|
+
*
|
|
21
|
+
* Use this when callers intentionally feed synthetic paths such as
|
|
22
|
+
* `['constant']` / `['null']` into the cascader to keep menu state aligned,
|
|
23
|
+
* but only want real variable references to show the blue active button.
|
|
24
|
+
*/
|
|
25
|
+
active?: boolean;
|
|
16
26
|
metaTree?: MetaTreeNode[] | (() => MetaTreeNode[] | Promise<MetaTreeNode[]>);
|
|
17
27
|
parseValueToPath?: (value: string) => string[] | undefined;
|
|
18
28
|
formatPathToValue?: (item: MetaTreeNode) => string;
|
|
19
29
|
open?: boolean;
|
|
20
30
|
onlyLeafSelectable?: boolean;
|
|
21
31
|
ignoreFieldNames?: string[];
|
|
32
|
+
/**
|
|
33
|
+
* Footer rendered at the bottom of the dropdown. Defaults to a muted
|
|
34
|
+
* "Double click to choose entire object" hint when non-leaf selection is
|
|
35
|
+
* allowed (`onlyLeafSelectable` is false) — since double-clicking a non-leaf
|
|
36
|
+
* node selects the whole object. Pass an explicit node to override, or `null`
|
|
37
|
+
* to hide it.
|
|
38
|
+
*/
|
|
39
|
+
dropdownFooter?: React.ReactNode;
|
|
22
40
|
}
|
|
23
41
|
export interface ContextSelectorItem {
|
|
24
42
|
label: React.ReactNode;
|
|
@@ -68,7 +86,10 @@ export interface VariableInputProps {
|
|
|
68
86
|
}
|
|
69
87
|
export interface VariableTagProps {
|
|
70
88
|
value?: string;
|
|
89
|
+
resolvedPath?: Array<string | number>;
|
|
71
90
|
onClear?: () => void;
|
|
91
|
+
disabled?: boolean;
|
|
92
|
+
allowCustomTagInput?: boolean;
|
|
72
93
|
className?: string;
|
|
73
94
|
style?: React.CSSProperties;
|
|
74
95
|
metaTreeNode?: MetaTreeNode | null;
|
package/lib/flowContext.js
CHANGED
|
@@ -2626,12 +2626,16 @@ const _FlowEngineContext = class _FlowEngineContext extends BaseFlowEngineContex
|
|
|
2626
2626
|
}, "get")
|
|
2627
2627
|
});
|
|
2628
2628
|
this.defineProperty("auth", {
|
|
2629
|
-
get: /* @__PURE__ */ __name(() =>
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2629
|
+
get: /* @__PURE__ */ __name(() => {
|
|
2630
|
+
var _a, _b, _c, _d, _e, _f;
|
|
2631
|
+
return {
|
|
2632
|
+
roleName: (_b = (_a = this.api) == null ? void 0 : _a.auth) == null ? void 0 : _b.role,
|
|
2633
|
+
locale: (_d = (_c = this.api) == null ? void 0 : _c.auth) == null ? void 0 : _d.locale,
|
|
2634
|
+
token: (_f = (_e = this.api) == null ? void 0 : _e.auth) == null ? void 0 : _f.token,
|
|
2635
|
+
user: this.user
|
|
2636
|
+
};
|
|
2637
|
+
}, "get"),
|
|
2638
|
+
cache: false
|
|
2635
2639
|
});
|
|
2636
2640
|
this.defineProperty("date", {
|
|
2637
2641
|
get: /* @__PURE__ */ __name(() => {
|
package/lib/flowEngine.js
CHANGED
|
@@ -1141,6 +1141,9 @@ const _FlowEngine = class _FlowEngine {
|
|
|
1141
1141
|
if (!this.ensureModelRepository()) return;
|
|
1142
1142
|
const refresh = !!(options == null ? void 0 : options.refresh);
|
|
1143
1143
|
const bypassLoadedPageCache = this._loadedPageCache.shouldBypass(options, () => this.context.flowSettingsEnabled);
|
|
1144
|
+
if (this.context.flowSettingsEnabled) {
|
|
1145
|
+
this._loadedPageCache.markDirtyForOptions(options);
|
|
1146
|
+
}
|
|
1144
1147
|
if (!refresh && !bypassLoadedPageCache) {
|
|
1145
1148
|
const model2 = this.findModelByParentId(options.parentId, options.subKey);
|
|
1146
1149
|
if (model2) {
|
|
@@ -1201,6 +1204,9 @@ const _FlowEngine = class _FlowEngine {
|
|
|
1201
1204
|
if (!this.ensureModelRepository()) return;
|
|
1202
1205
|
const { uid, parentId, subKey } = options;
|
|
1203
1206
|
const bypassLoadedPageCache = this._loadedPageCache.shouldBypass(options, () => this.context.flowSettingsEnabled);
|
|
1207
|
+
if (this.context.flowSettingsEnabled) {
|
|
1208
|
+
this._loadedPageCache.markDirtyForOptions(options);
|
|
1209
|
+
}
|
|
1204
1210
|
if (uid && !bypassLoadedPageCache && this._modelInstances.has(uid)) {
|
|
1205
1211
|
return this._modelInstances.get(uid);
|
|
1206
1212
|
}
|
package/lib/flowI18n.js
CHANGED
|
@@ -81,7 +81,7 @@ const _FlowI18n = class _FlowI18n {
|
|
|
81
81
|
* @private
|
|
82
82
|
*/
|
|
83
83
|
isTemplate(str) {
|
|
84
|
-
return /\{\{\s*t\s*\(\s*["'`]
|
|
84
|
+
return /\{\{\s*t\s*\(\s*(["'`])(?:\\.|(?!\1).)*?\1\s*(?:,\s*.*?)?\s*\)\s*\}\}/.test(str);
|
|
85
85
|
}
|
|
86
86
|
/**
|
|
87
87
|
* 编译模板字符串
|
|
@@ -89,8 +89,8 @@ const _FlowI18n = class _FlowI18n {
|
|
|
89
89
|
*/
|
|
90
90
|
compileTemplate(template) {
|
|
91
91
|
return template.replace(
|
|
92
|
-
/\{\{\s*t\s*\(\s*["'`](
|
|
93
|
-
(match, key, optionsStr) => {
|
|
92
|
+
/\{\{\s*t\s*\(\s*(["'`])((?:\\.|(?!\1).)*?)\1\s*(?:,\s*((?:[^{}]|\{[^}]*\})*?))?\s*\)\s*\}\}/g,
|
|
93
|
+
(match, _quote, key, optionsStr) => {
|
|
94
94
|
try {
|
|
95
95
|
let templateOptions = {};
|
|
96
96
|
if (optionsStr) {
|
package/lib/types.d.ts
CHANGED
|
@@ -122,7 +122,9 @@ export declare enum ActionScene {
|
|
|
122
122
|
/** 动态事件流可用 */
|
|
123
123
|
DYNAMIC_EVENT_FLOW = 6,
|
|
124
124
|
/** 菜单项联动规则可用 */
|
|
125
|
-
MENU_LINKAGE_RULES = 7
|
|
125
|
+
MENU_LINKAGE_RULES = 7,
|
|
126
|
+
/** 标签页联动规则可用 */
|
|
127
|
+
TAB_LINKAGE_RULES = 8
|
|
126
128
|
}
|
|
127
129
|
/**
|
|
128
130
|
* Defines a reusable action with generic model type support.
|
package/lib/types.js
CHANGED
|
@@ -37,6 +37,7 @@ var ActionScene = /* @__PURE__ */ ((ActionScene2) => {
|
|
|
37
37
|
ActionScene2[ActionScene2["ACTION_LINKAGE_RULES"] = 5] = "ACTION_LINKAGE_RULES";
|
|
38
38
|
ActionScene2[ActionScene2["DYNAMIC_EVENT_FLOW"] = 6] = "DYNAMIC_EVENT_FLOW";
|
|
39
39
|
ActionScene2[ActionScene2["MENU_LINKAGE_RULES"] = 7] = "MENU_LINKAGE_RULES";
|
|
40
|
+
ActionScene2[ActionScene2["TAB_LINKAGE_RULES"] = 8] = "TAB_LINKAGE_RULES";
|
|
40
41
|
return ActionScene2;
|
|
41
42
|
})(ActionScene || {});
|
|
42
43
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -31,8 +31,10 @@ __export(dirtyAwareApiClient_exports, {
|
|
|
31
31
|
getDirtyAwareApiClient: () => getDirtyAwareApiClient
|
|
32
32
|
});
|
|
33
33
|
module.exports = __toCommonJS(dirtyAwareApiClient_exports);
|
|
34
|
+
var import_sdk = require("@nocobase/sdk");
|
|
34
35
|
var import_dataSourceDirty = require("./dataSourceDirty");
|
|
35
36
|
const SKIP_DATA_SOURCE_DIRTY = "__nocobaseSkipDataSourceDirty";
|
|
37
|
+
const DIRTY_DISPATCH_TOKEN = Symbol("nocobaseDirtyDispatchToken");
|
|
36
38
|
const dirtyAwareApiClientCache = /* @__PURE__ */ new WeakMap();
|
|
37
39
|
const dirtyAwareApiClientProxies = /* @__PURE__ */ new WeakSet();
|
|
38
40
|
const MUTATING_RESOURCE_ACTIONS = [
|
|
@@ -288,6 +290,43 @@ function resolveDirtyResourceAction(options, context) {
|
|
|
288
290
|
return parseDirtyResourceActionFromUrl(options == null ? void 0 : options.url, context);
|
|
289
291
|
}
|
|
290
292
|
__name(resolveDirtyResourceAction, "resolveDirtyResourceAction");
|
|
293
|
+
function getDirtyResourceActionDispatchKey(dirtyResourceAction, headers) {
|
|
294
|
+
if (!dirtyResourceAction) {
|
|
295
|
+
return void 0;
|
|
296
|
+
}
|
|
297
|
+
return JSON.stringify([
|
|
298
|
+
dirtyResourceAction.dataSourceKey || (0, import_dataSourceDirty.getDataSourceKeyFromHeaders)(headers),
|
|
299
|
+
dirtyResourceAction.resourceName,
|
|
300
|
+
dirtyResourceAction.actionName
|
|
301
|
+
]);
|
|
302
|
+
}
|
|
303
|
+
__name(getDirtyResourceActionDispatchKey, "getDirtyResourceActionDispatchKey");
|
|
304
|
+
function getRequestDispatchKey(options, context) {
|
|
305
|
+
return getDirtyResourceActionDispatchKey(resolveDirtyResourceAction(options, context), options.headers);
|
|
306
|
+
}
|
|
307
|
+
__name(getRequestDispatchKey, "getRequestDispatchKey");
|
|
308
|
+
function getResourceDispatchKey(name, of, headers) {
|
|
309
|
+
const resourceName = String(name ?? "").trim();
|
|
310
|
+
if (!resourceName) {
|
|
311
|
+
return void 0;
|
|
312
|
+
}
|
|
313
|
+
return JSON.stringify([resourceName, String(of ?? ""), (0, import_dataSourceDirty.getDataSourceKeyFromHeaders)(headers)]);
|
|
314
|
+
}
|
|
315
|
+
__name(getResourceDispatchKey, "getResourceDispatchKey");
|
|
316
|
+
function getMatchingRequestToken(token, requestKey) {
|
|
317
|
+
if (!token || !requestKey || token.requestKey && token.requestKey !== requestKey) {
|
|
318
|
+
return void 0;
|
|
319
|
+
}
|
|
320
|
+
return token;
|
|
321
|
+
}
|
|
322
|
+
__name(getMatchingRequestToken, "getMatchingRequestToken");
|
|
323
|
+
function getMatchingResourceToken(token, resourceKey) {
|
|
324
|
+
if (!token || !resourceKey || token.resourceKey && token.resourceKey !== resourceKey) {
|
|
325
|
+
return void 0;
|
|
326
|
+
}
|
|
327
|
+
return token;
|
|
328
|
+
}
|
|
329
|
+
__name(getMatchingResourceToken, "getMatchingResourceToken");
|
|
291
330
|
function markResourceActionDataSourceDirty(context, dirtyResourceAction, headers) {
|
|
292
331
|
(0, import_dataSourceDirty.markDataSourceDirty)({
|
|
293
332
|
engine: context.engine,
|
|
@@ -297,7 +336,19 @@ function markResourceActionDataSourceDirty(context, dirtyResourceAction, headers
|
|
|
297
336
|
});
|
|
298
337
|
}
|
|
299
338
|
__name(markResourceActionDataSourceDirty, "markResourceActionDataSourceDirty");
|
|
300
|
-
function
|
|
339
|
+
function markResourceActionDataSourceDirtyOnce(token, context, dirtyResourceAction, headers) {
|
|
340
|
+
if (token.skip || token.marked || !dirtyResourceAction || !isMutatingResourceAction(dirtyResourceAction.actionName)) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
token.marked = true;
|
|
344
|
+
markResourceActionDataSourceDirty(context, dirtyResourceAction, headers);
|
|
345
|
+
}
|
|
346
|
+
__name(markResourceActionDataSourceDirtyOnce, "markResourceActionDataSourceDirtyOnce");
|
|
347
|
+
function isObjectRecord(value) {
|
|
348
|
+
return !!value && typeof value === "object";
|
|
349
|
+
}
|
|
350
|
+
__name(isObjectRecord, "isObjectRecord");
|
|
351
|
+
function createDirtyAwareResource(context, resource, resourceName, resourceOf, headers, requestTokenStack, parentToken) {
|
|
301
352
|
return new Proxy(resource, {
|
|
302
353
|
get(target, prop, receiver) {
|
|
303
354
|
const original = Reflect.get(target, prop, receiver);
|
|
@@ -306,10 +357,38 @@ function createDirtyAwareResource(context, resource, resourceName, resourceOf, h
|
|
|
306
357
|
}
|
|
307
358
|
const action = original;
|
|
308
359
|
return async (...args) => {
|
|
309
|
-
const
|
|
360
|
+
const actionOptions = isObjectRecord(args[1]) ? args[1] : void 0;
|
|
310
361
|
const dirtyResourceAction = resolveDirtyResourceActionFromResource(resourceName, resourceOf, prop, context);
|
|
311
|
-
|
|
312
|
-
|
|
362
|
+
const requestKey = getDirtyResourceActionDispatchKey(dirtyResourceAction, headers);
|
|
363
|
+
const resourceKey = getResourceDispatchKey(resourceName, resourceOf, headers);
|
|
364
|
+
const inheritedToken = getMatchingRequestToken(
|
|
365
|
+
actionOptions == null ? void 0 : actionOptions[DIRTY_DISPATCH_TOKEN],
|
|
366
|
+
requestKey
|
|
367
|
+
) || getMatchingRequestToken(parentToken, requestKey);
|
|
368
|
+
const token = inheritedToken || { marked: false, skip: false };
|
|
369
|
+
const ownsToken = !inheritedToken;
|
|
370
|
+
token.requestKey ||= requestKey;
|
|
371
|
+
token.resourceKey ||= resourceKey;
|
|
372
|
+
if (actionOptions == null ? void 0 : actionOptions[SKIP_DATA_SOURCE_DIRTY]) {
|
|
373
|
+
token.skip = true;
|
|
374
|
+
}
|
|
375
|
+
const forwardedArgs = actionOptions || args[1] == null ? [
|
|
376
|
+
args[0],
|
|
377
|
+
{
|
|
378
|
+
...actionOptions,
|
|
379
|
+
[DIRTY_DISPATCH_TOKEN]: token
|
|
380
|
+
}
|
|
381
|
+
] : args;
|
|
382
|
+
let actionResult;
|
|
383
|
+
requestTokenStack.push({ key: requestKey, token });
|
|
384
|
+
try {
|
|
385
|
+
actionResult = Reflect.apply(action, receiver, forwardedArgs);
|
|
386
|
+
} finally {
|
|
387
|
+
requestTokenStack.pop();
|
|
388
|
+
}
|
|
389
|
+
const result = await actionResult;
|
|
390
|
+
if (ownsToken) {
|
|
391
|
+
markResourceActionDataSourceDirtyOnce(token, context, dirtyResourceAction, headers);
|
|
313
392
|
}
|
|
314
393
|
return result;
|
|
315
394
|
};
|
|
@@ -318,31 +397,206 @@ function createDirtyAwareResource(context, resource, resourceName, resourceOf, h
|
|
|
318
397
|
}
|
|
319
398
|
__name(createDirtyAwareResource, "createDirtyAwareResource");
|
|
320
399
|
function createDirtyAwareApiClient(api, context) {
|
|
400
|
+
const baseResource = api.resource;
|
|
401
|
+
const baseRequest = api.request;
|
|
402
|
+
const shouldUseResourceDispatchReceiver = baseResource === import_sdk.APIClient.prototype.resource;
|
|
403
|
+
const shouldUseRequestDispatchReceiver = baseRequest === import_sdk.APIClient.prototype.request;
|
|
404
|
+
const resourceTokenStack = [];
|
|
405
|
+
const requestTokenStack = [];
|
|
406
|
+
let hasResourceOverride = false;
|
|
407
|
+
let resourceOverride;
|
|
408
|
+
let hasRequestOverride = false;
|
|
409
|
+
let requestOverride;
|
|
410
|
+
const getCurrentResource = /* @__PURE__ */ __name(() => hasResourceOverride ? resourceOverride : resource, "getCurrentResource");
|
|
411
|
+
const getCurrentRequest = /* @__PURE__ */ __name(() => hasRequestOverride ? requestOverride : request, "getCurrentRequest");
|
|
412
|
+
const dispatchResource = /* @__PURE__ */ __name((token, args) => {
|
|
413
|
+
const resourceKey = getResourceDispatchKey(args[0], args[1], args[2]);
|
|
414
|
+
const activeToken = getMatchingResourceToken(token, resourceKey);
|
|
415
|
+
const shouldWrapActions = !!activeToken && hasResourceOverride;
|
|
416
|
+
if (activeToken) {
|
|
417
|
+
activeToken.resourceKey ||= resourceKey;
|
|
418
|
+
resourceTokenStack.push({ key: resourceKey, token: activeToken });
|
|
419
|
+
}
|
|
420
|
+
try {
|
|
421
|
+
const resourceInstance = Reflect.apply(getCurrentResource(), proxy, args);
|
|
422
|
+
if (!shouldWrapActions || !activeToken) {
|
|
423
|
+
return resourceInstance;
|
|
424
|
+
}
|
|
425
|
+
return new Proxy(resourceInstance, {
|
|
426
|
+
get(target, prop, receiver) {
|
|
427
|
+
const original = Reflect.get(target, prop, receiver);
|
|
428
|
+
if (typeof prop !== "string" || typeof original !== "function" || !isMutatingResourceAction(prop)) {
|
|
429
|
+
return original;
|
|
430
|
+
}
|
|
431
|
+
const action = original;
|
|
432
|
+
return (...actionArgs) => {
|
|
433
|
+
const actionOptions = isObjectRecord(actionArgs[1]) ? actionArgs[1] : void 0;
|
|
434
|
+
const forwardedArgs = actionOptions || actionArgs[1] == null ? [
|
|
435
|
+
actionArgs[0],
|
|
436
|
+
{
|
|
437
|
+
...actionOptions,
|
|
438
|
+
[DIRTY_DISPATCH_TOKEN]: activeToken
|
|
439
|
+
}
|
|
440
|
+
] : actionArgs;
|
|
441
|
+
resourceTokenStack.push({ key: resourceKey, token: activeToken });
|
|
442
|
+
requestTokenStack.push({ key: activeToken.requestKey, token: activeToken });
|
|
443
|
+
try {
|
|
444
|
+
return Reflect.apply(action, receiver, forwardedArgs);
|
|
445
|
+
} finally {
|
|
446
|
+
requestTokenStack.pop();
|
|
447
|
+
resourceTokenStack.pop();
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
} finally {
|
|
453
|
+
if (activeToken) {
|
|
454
|
+
resourceTokenStack.pop();
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}, "dispatchResource");
|
|
458
|
+
const createDispatchReceiver = /* @__PURE__ */ __name((token) => {
|
|
459
|
+
const receiver = Object.create(api);
|
|
460
|
+
Object.defineProperties(receiver, {
|
|
461
|
+
request: {
|
|
462
|
+
configurable: true,
|
|
463
|
+
value: /* @__PURE__ */ __name((config) => {
|
|
464
|
+
const options = config;
|
|
465
|
+
const requestKey = getRequestDispatchKey(options, context);
|
|
466
|
+
const stackFrame = requestTokenStack.at(-1);
|
|
467
|
+
const activeToken = getMatchingRequestToken(options == null ? void 0 : options[DIRTY_DISPATCH_TOKEN], requestKey) || (requestKey && (stackFrame == null ? void 0 : stackFrame.key) === requestKey ? stackFrame.token : void 0) || getMatchingRequestToken(token, requestKey);
|
|
468
|
+
const { [DIRTY_DISPATCH_TOKEN]: _dirtyDispatchToken, ...cleanOptions } = options;
|
|
469
|
+
const configWithToken = activeToken ? { ...cleanOptions, [DIRTY_DISPATCH_TOKEN]: activeToken } : cleanOptions;
|
|
470
|
+
if (activeToken) {
|
|
471
|
+
activeToken.requestKey ||= requestKey;
|
|
472
|
+
requestTokenStack.push({ key: requestKey, token: activeToken });
|
|
473
|
+
}
|
|
474
|
+
try {
|
|
475
|
+
return Reflect.apply(getCurrentRequest(), proxy, [configWithToken]);
|
|
476
|
+
} finally {
|
|
477
|
+
if (activeToken) {
|
|
478
|
+
requestTokenStack.pop();
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}, "value")
|
|
482
|
+
},
|
|
483
|
+
resource: {
|
|
484
|
+
configurable: true,
|
|
485
|
+
value: /* @__PURE__ */ __name((...args) => {
|
|
486
|
+
const resourceKey = getResourceDispatchKey(args[0], args[1], args[2]);
|
|
487
|
+
const stackFrame = resourceTokenStack.at(-1);
|
|
488
|
+
const activeToken = getMatchingResourceToken(token, resourceKey) || (resourceKey && (stackFrame == null ? void 0 : stackFrame.key) === resourceKey ? stackFrame.token : void 0);
|
|
489
|
+
return dispatchResource(activeToken, args);
|
|
490
|
+
}, "value")
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
return receiver;
|
|
494
|
+
}, "createDispatchReceiver");
|
|
321
495
|
const resource = /* @__PURE__ */ __name((name, of, headers, cancel) => {
|
|
322
|
-
const
|
|
323
|
-
|
|
496
|
+
const resourceKey = getResourceDispatchKey(name, of, headers);
|
|
497
|
+
const stackFrame = resourceTokenStack.at(-1);
|
|
498
|
+
const parentToken = resourceKey && (stackFrame == null ? void 0 : stackFrame.key) === resourceKey ? stackFrame.token : void 0;
|
|
499
|
+
const receiver = createDispatchReceiver(parentToken);
|
|
500
|
+
const resourceInstance = Reflect.apply(baseResource, shouldUseResourceDispatchReceiver ? receiver : api, [
|
|
501
|
+
name,
|
|
502
|
+
of,
|
|
503
|
+
headers,
|
|
504
|
+
cancel
|
|
505
|
+
]);
|
|
506
|
+
return createDirtyAwareResource(context, resourceInstance, name, of, headers, requestTokenStack, parentToken);
|
|
324
507
|
}, "resource");
|
|
325
508
|
const request = /* @__PURE__ */ __name((config) => {
|
|
326
509
|
const options = config;
|
|
510
|
+
const requestKey = getRequestDispatchKey(options, context);
|
|
511
|
+
const stackFrame = requestTokenStack.at(-1);
|
|
512
|
+
const inheritedToken = getMatchingRequestToken(options == null ? void 0 : options[DIRTY_DISPATCH_TOKEN], requestKey) || (requestKey && (stackFrame == null ? void 0 : stackFrame.key) === requestKey ? stackFrame.token : void 0);
|
|
513
|
+
const token = inheritedToken || { marked: false, skip: false };
|
|
514
|
+
const ownsToken = !inheritedToken;
|
|
515
|
+
token.requestKey ||= requestKey;
|
|
516
|
+
if (typeof options.resource === "string") {
|
|
517
|
+
token.resourceKey ||= getResourceDispatchKey(options.resource, options.resourceOf, options.headers);
|
|
518
|
+
}
|
|
327
519
|
const skipDataSourceDirty = options == null ? void 0 : options[SKIP_DATA_SOURCE_DIRTY];
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
520
|
+
if (skipDataSourceDirty) {
|
|
521
|
+
token.skip = true;
|
|
522
|
+
}
|
|
523
|
+
const dirtyResourceAction = resolveDirtyResourceAction(options, context);
|
|
524
|
+
const {
|
|
525
|
+
[DIRTY_DISPATCH_TOKEN]: _dirtyDispatchToken,
|
|
526
|
+
[SKIP_DATA_SOURCE_DIRTY]: _skipDataSourceDirty,
|
|
527
|
+
...cleanConfig
|
|
528
|
+
} = options;
|
|
529
|
+
const receiver = createDispatchReceiver(token);
|
|
530
|
+
return Reflect.apply(baseRequest, shouldUseRequestDispatchReceiver ? receiver : api, [cleanConfig]).then((result) => {
|
|
531
|
+
if (ownsToken) {
|
|
532
|
+
markResourceActionDataSourceDirtyOnce(token, context, dirtyResourceAction, options.headers);
|
|
333
533
|
}
|
|
334
534
|
return result;
|
|
335
535
|
});
|
|
336
536
|
}, "request");
|
|
537
|
+
const isLockedOwnProperty = /* @__PURE__ */ __name((target, prop) => {
|
|
538
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(target, prop);
|
|
539
|
+
return !!descriptor && !descriptor.configurable;
|
|
540
|
+
}, "isLockedOwnProperty");
|
|
337
541
|
const proxy = new Proxy(api, {
|
|
338
542
|
get(target, prop, receiver) {
|
|
339
543
|
if (prop === "resource") {
|
|
340
|
-
|
|
544
|
+
if (isLockedOwnProperty(target, prop)) {
|
|
545
|
+
return Reflect.get(target, prop, receiver);
|
|
546
|
+
}
|
|
547
|
+
return hasResourceOverride ? resourceOverride : resource;
|
|
341
548
|
}
|
|
342
549
|
if (prop === "request") {
|
|
343
|
-
|
|
550
|
+
if (isLockedOwnProperty(target, prop)) {
|
|
551
|
+
return Reflect.get(target, prop, receiver);
|
|
552
|
+
}
|
|
553
|
+
return hasRequestOverride ? requestOverride : request;
|
|
344
554
|
}
|
|
345
555
|
return Reflect.get(target, prop, receiver);
|
|
556
|
+
},
|
|
557
|
+
set(target, prop, value, receiver) {
|
|
558
|
+
if (prop === "resource") {
|
|
559
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(target, prop);
|
|
560
|
+
if (descriptor && (!descriptor.configurable || !Reflect.isExtensible(target))) {
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
hasResourceOverride = true;
|
|
564
|
+
resourceOverride = value;
|
|
565
|
+
return true;
|
|
566
|
+
}
|
|
567
|
+
if (prop === "request") {
|
|
568
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(target, prop);
|
|
569
|
+
if (descriptor && (!descriptor.configurable || !Reflect.isExtensible(target))) {
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
hasRequestOverride = true;
|
|
573
|
+
requestOverride = value;
|
|
574
|
+
return true;
|
|
575
|
+
}
|
|
576
|
+
return Reflect.set(target, prop, value, receiver);
|
|
577
|
+
},
|
|
578
|
+
deleteProperty(target, prop) {
|
|
579
|
+
if (prop !== "resource" && prop !== "request") {
|
|
580
|
+
return Reflect.deleteProperty(target, prop);
|
|
581
|
+
}
|
|
582
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(target, prop);
|
|
583
|
+
if (descriptor && (!descriptor.configurable || !Reflect.isExtensible(target))) {
|
|
584
|
+
return false;
|
|
585
|
+
}
|
|
586
|
+
if (prop === "resource") {
|
|
587
|
+
hasResourceOverride = false;
|
|
588
|
+
resourceOverride = void 0;
|
|
589
|
+
} else {
|
|
590
|
+
hasRequestOverride = false;
|
|
591
|
+
requestOverride = void 0;
|
|
592
|
+
}
|
|
593
|
+
return true;
|
|
594
|
+
},
|
|
595
|
+
defineProperty(target, prop, descriptor) {
|
|
596
|
+
if (prop === "resource" || prop === "request") {
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
599
|
+
return Reflect.defineProperty(target, prop, descriptor);
|
|
346
600
|
}
|
|
347
601
|
});
|
|
348
602
|
dirtyAwareApiClientProxies.add(proxy);
|
|
@@ -17,6 +17,7 @@ type DirtyKeyOptions = {
|
|
|
17
17
|
export declare const createLoadedPageCache: () => {
|
|
18
18
|
getDirtyKeyForModel(model?: FlowModel | null, options?: DirtyKeyOptions): string | undefined;
|
|
19
19
|
markDirty(key?: string): void;
|
|
20
|
+
markDirtyForOptions(options?: LoadedPageOptions): void;
|
|
20
21
|
shouldBypass(options?: LoadedPageOptions, isFlowSettingsEnabled?: () => boolean): boolean;
|
|
21
22
|
clear(options?: LoadedPageOptions): void;
|
|
22
23
|
mountModelToParent: <T extends FlowModel<import("..").DefaultStructure> = FlowModel<import("..").DefaultStructure>>(model: T, forceReplace?: boolean) => T;
|
|
@@ -113,6 +113,12 @@ const createLoadedPageCache = /* @__PURE__ */ __name(() => {
|
|
|
113
113
|
dirtyKeys.add(key);
|
|
114
114
|
}
|
|
115
115
|
},
|
|
116
|
+
markDirtyForOptions(options) {
|
|
117
|
+
const key = getLoadedPageKey(options);
|
|
118
|
+
if (key) {
|
|
119
|
+
dirtyKeys.add(key);
|
|
120
|
+
}
|
|
121
|
+
},
|
|
116
122
|
shouldBypass(options, isFlowSettingsEnabled) {
|
|
117
123
|
const key = getLoadedPageKey(options);
|
|
118
124
|
if (!key || !dirtyKeys.has(key)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/flow-engine",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0-alpha.1",
|
|
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.
|
|
12
|
-
"@nocobase/shared": "2.
|
|
11
|
+
"@nocobase/sdk": "2.3.0-alpha.1",
|
|
12
|
+
"@nocobase/shared": "2.3.0-alpha.1",
|
|
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": "2377df8ceb12549149017f7f14a61207bf6e49a2"
|
|
41
41
|
}
|